Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Construction payloads #99

Merged
merged 40 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
55018f5
move PartialUserCommand to types for reusal
piotr-iohk Feb 3, 2025
f7447c4
introduce TransactionMetadata
piotr-iohk Feb 3, 2025
7c47f6b
get metadata and partial_user_command in payloads
piotr-iohk Feb 4, 2025
1086e44
validate source pubkey
piotr-iohk Feb 4, 2025
f457e63
memo
piotr-iohk Feb 5, 2025
8e4e909
to_user_command_payload
piotr-iohk Feb 5, 2025
70303d8
base58 -> signer_utils
piotr-iohk Feb 5, 2025
7e4d87e
adjust memo length
piotr-iohk Feb 6, 2025
65e7413
add mina-hasher
piotr-iohk Feb 6, 2025
cdccc64
to_random_oracle_input - false attempt
piotr-iohk Feb 6, 2025
c0da0ad
add temp test script
piotr-iohk Feb 7, 2025
dab74e4
Refactor nix flake to use crane directly without rust-flake (#97)
joaosreis Feb 7, 2025
07c4627
Merge remote-tracking branch 'origin/main' into construction_payloads
joaosreis Feb 11, 2025
01a7aa1
Create roinput module
joaosreis Feb 12, 2025
30b4949
Add Tag enum and conversion implementations for bit representation
joaosreis Feb 12, 2025
d5ea0bc
Implement TagUnpacked struct and conversion from Tag
joaosreis Feb 12, 2025
82b6308
Add TransactionUnionPayload struct and conversion from UserCommandPay…
joaosreis Feb 12, 2025
a5b47d5
Implement to_random_oracle_input method for TransactionUnionPayload s…
joaosreis Feb 12, 2025
84a330d
Implement to_random_oracle_input method for UserCommandPayload struct
joaosreis Feb 12, 2025
88408bf
Add unit test for TransactionUnionPayload's to_random_oracle_input me…
joaosreis Feb 12, 2025
61f2610
Update random_oracle_input serialization in TransactionUnsigned
joaosreis Feb 12, 2025
03d7c95
Add unit test for Memo's from_string method
joaosreis Feb 12, 2025
927db48
Reformat
joaosreis Feb 12, 2025
0133ef4
Add missing line
joaosreis Feb 12, 2025
42c187a
Add new terms to words.txt
joaosreis Feb 12, 2025
95fdbfc
Reformat
joaosreis Feb 12, 2025
59a79eb
Refactor
joaosreis Feb 12, 2025
d23dd7d
Make clippy happy
joaosreis Feb 12, 2025
293a1b4
More reformats
joaosreis Feb 12, 2025
f185ee8
remove temp script & reformat
piotr-iohk Feb 12, 2025
ec091f3
Signer input encoding
piotr-iohk Feb 13, 2025
6d54b04
complete the TransactionUnsigned
piotr-iohk Feb 13, 2025
017ff9b
cspell
piotr-iohk Feb 13, 2025
44e759e
validate token_id
piotr-iohk Feb 14, 2025
bcebc56
extract payment_operations/delegation_operations
piotr-iohk Feb 14, 2025
32d199c
functional tests
piotr-iohk Feb 14, 2025
8ad04a9
payloads comparison tests
piotr-iohk Feb 14, 2025
1856f29
reuse operations test utils in preprocess tests
piotr-iohk Feb 14, 2025
04c32dc
proptest roundtrip for memo
piotr-iohk Feb 14, 2025
8b48657
cspell
piotr-iohk Feb 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ indoc = "2.0.5"

[dependencies]
anyhow = "1.0.86"
ark-ff = { version = "0.4.2", features = ["parallel", "asm"] }
axum = { version = "0.7.5", features = ["macros"] }
bitvec = "1.0.1"
bs58 = "0.5.1"
clap = { version = "4.5.11", features = ["derive", "env"] }
coinbase-mesh = "0.1.1"
Expand All @@ -29,13 +31,16 @@ hex = "0.4.3"
http = "1.1.0"
http-body-util = "0.1.2"
mime = "0.3.17"
mina-curves = { git = "https://github.com/o1-labs/proof-systems", rev = "872c8f2" }
mina-hasher = { git = "https://github.com/o1-labs/proof-systems", rev = "872c8f2" }
mina-signer = { git = "https://github.com/o1-labs/proof-systems", rev = "872c8f2" }
o1-utils = { git = "https://github.com/o1-labs/proof-systems", rev = "872c8f2" }
paste = "1.0.15"
pretty_assertions = "1.4.1"
reqwest = { version = "0.12.5", features = ["json", "blocking"] }
serde = { version = "1.0.204", features = ["derive"] }
serde_json = { version = "1.0.121" }
serde_with = { version = "3.12.0", features = ["json"] }
sha2 = "0.10.8"
sqlx = { version = "0.8.0", features = ["runtime-tokio", "postgres", "json"] }
strum = "0.26.3"
Expand All @@ -48,4 +53,5 @@ tracing-subscriber = "0.3.18"

[dev-dependencies]
insta = "1.39.0"
proptest = "1.6.0"
tokio = { version = "1.39.2", features = ["full", "test-util"] }
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() -> Result<()> {
.filter_map(|entry_result| match entry_result {
Ok(entry) => {
let entry_path = entry.path();
if entry_path.is_file() && entry_path.extension().map_or(false, |ext| ext == "graphql") {
if entry_path.is_file() && entry_path.extension().is_some_and(|ext| ext == "graphql") {
Some(entry_path)
} else {
None
Expand Down
4 changes: 2 additions & 2 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
profile = "minimal"
profile = "default"
channel = "nightly-2024-09-24"
components = ["rustfmt", "clippy"]
components = ["rust-src"]
40 changes: 6 additions & 34 deletions src/api/construction_derive.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use anyhow::Result;
use coinbase_mesh::models::{AccountIdentifier, ConstructionDeriveRequest, ConstructionDeriveResponse};
use mina_signer::{BaseField, CompressedPubKey};
use o1_utils::FieldHelpers;
use serde_json::{json, Value};

use crate::{base58::validate_base58_with_checksum, util::DEFAULT_TOKEN_ID, MinaMesh, MinaMeshError};
use crate::{
signer_utils::{hex_to_compressed_pub_key, validate_base58_with_checksum},
util::DEFAULT_TOKEN_ID,
MinaMesh, MinaMeshError,
};

/// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/construction.ml#L162
impl MinaMesh {
Expand All @@ -16,7 +18,7 @@ impl MinaMesh {
self.validate_network(&request.network_identifier).await?;

// Decode the hex_bytes payload into an address
let compressed_pk = to_public_key_compressed(&request.public_key.hex_bytes)?;
let compressed_pk = hex_to_compressed_pub_key(&request.public_key.hex_bytes)?;
let address = compressed_pk.into_address();

// Decode the token ID from metadata (if present)
Expand All @@ -38,36 +40,6 @@ impl MinaMesh {
}
}

/// Converts a hex string into a compressed public key
/// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/lib/rosetta_coding/coding.ml#L128
fn to_public_key_compressed(hex: &str) -> Result<CompressedPubKey, MinaMeshError> {
if hex.len() != 64 {
return Err(MinaMeshError::MalformedPublicKey("Invalid length for hex".to_string()));
}

// Decode the hex string
let mut bytes =
hex::decode(hex).map_err(|_| MinaMeshError::MalformedPublicKey("Invalid hex encoding".to_string()))?;
// Reverse the bytes
bytes.reverse();

// Convert bytes to bits
let mut bits: Vec<bool> = bytes.iter().flat_map(|byte| (0 .. 8).rev().map(move |i| (byte >> i) & 1 == 1)).collect();

// Extract the `is_odd` bit
let is_odd = bits.remove(0);

// Reverse the remaining bits
bits.reverse();

// Create the x-coordinate as a BaseField element
let x =
BaseField::from_bits(&bits).map_err(|_| MinaMeshError::MalformedPublicKey("Invalid x-coordinate".to_string()))?;

// Construct the compressed public key
Ok(CompressedPubKey { x, is_odd })
}

/// Decodes the token ID from metadata, or returns a default value if not
/// present
fn decode_token_id(metadata: Option<Value>) -> Result<String, MinaMeshError> {
Expand Down
34 changes: 10 additions & 24 deletions src/api/construction_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use anyhow::Result;
use coinbase_mesh::models::{Amount, ConstructionMetadataRequest, ConstructionMetadataResponse};
use cynic::QueryBuilder;
use serde_json::{json, Map, Value};
use serde_json::{json, Value};

use crate::{
base58::validate_base58_with_checksum,
create_currency,
graphql::{Block3, PublicKey, QueryConstructionMetadata, QueryConstructionMetadataVariables, TokenId},
signer_utils::validate_base58_with_checksum,
util::{DEFAULT_TOKEN_ID, MINIMUM_USER_COMMAND_FEE},
MinaMesh, MinaMeshError,
MinaMesh, MinaMeshError, TransactionMetadata,
};

/// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/construction.ml#L133
Expand Down Expand Up @@ -53,32 +53,18 @@ impl MinaMesh {
.unwrap_or("0".to_string()); // Default to 0 if missing;

// Extract account creation fee
let account_creation_fee = response.genesis_constants.account_creation_fee;
let account_creation_fee_value = response.genesis_constants.account_creation_fee;

// Calculate suggested fee from best_chain
let best_chain = response.best_chain.ok_or(MinaMeshError::ChainInfoMissing)?;
let suggested_fee = self.suggested_fee(best_chain).unwrap_or(MINIMUM_USER_COMMAND_FEE);

// Construct metadata
let mut metadata_map = Map::new();
metadata_map.insert("sender".to_string(), json!(sender));
metadata_map.insert("nonce".to_string(), json!(inferred_nonce));
metadata_map.insert("token_id".to_string(), json!(token_id));
metadata_map.insert("receiver".to_string(), json!(receiver));

if let Some(valid_until) = options.get("valid_until").and_then(|v| v.as_str()) {
metadata_map.insert("valid_until".to_string(), json!(valid_until));
}

if let Some(memo) = options.get("memo").and_then(|v| v.as_str()) {
metadata_map.insert("memo".to_string(), json!(memo));
}

if response.receiver.is_none() {
metadata_map.insert("account_creation_fee".to_string(), json!(account_creation_fee));
}

let metadata = json!(metadata_map);
let account_creation_fee = response.receiver.is_none().then_some(account_creation_fee_value.0);
let valid_until = options.get("valid_until").and_then(|v| v.as_str());
let memo = options.get("memo").and_then(|v| v.as_str());
let metadata =
TransactionMetadata::new(sender, receiver, inferred_nonce, token_id, account_creation_fee, valid_until, memo);

// Construct suggested fee
let suggested_fee_entry = Amount {
Expand All @@ -95,7 +81,7 @@ impl MinaMesh {
})),
};

Ok(ConstructionMetadataResponse { metadata, suggested_fee: Some(vec![suggested_fee_entry]) })
Ok(ConstructionMetadataResponse { metadata: metadata.to_json(), suggested_fee: Some(vec![suggested_fee_entry]) })
}

fn get_field_from_options<'a>(&self, options: &'a Value, field: &'a str) -> Result<&'a str, MinaMeshError> {
Expand Down
Loading