From 6597a57c2cf59098c27abce541b9e655b38e9346 Mon Sep 17 00:00:00 2001 From: piotr-iohk <42900201+piotr-iohk@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:04:57 +0100 Subject: [PATCH] Block adjustments (user_commands, internal_commands) (#81) --- ...4bb1415fb492671cdb2000cfed94131f9ee60.json | 115 ------- ...457babec8b8f82560d6f8283987a3db7b21ff.json | 92 ++++++ ...fc52f05b9a0d816707908498821b85d8e3d1e.json | 114 +++++++ ...61660557b9c084af85a5f4a24ee06cb0741e8.json | 76 ----- sql/queries/internal_commands.sql | 4 +- sql/queries/user_commands.sql | 9 +- src/api/block.rs | 235 +++----------- src/api/search_transactions.rs | 276 +--------------- src/lib.rs | 4 +- src/operation.rs | 44 --- src/test.rs | 30 +- src/transaction_operations.rs | 278 ++++++++++++++++ src/types.rs | 249 ++++++++++++++ tests/compare_to_ocaml.rs | 6 + tests/fixtures/block.rs | 3 +- tests/fixtures/mod.rs | 1 - tests/snapshots/block__specified.snap | 304 ++++++++++++------ 17 files changed, 1023 insertions(+), 817 deletions(-) delete mode 100644 .sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json create mode 100644 .sqlx/query-bde32fe15f8f99a3a5a665c3971457babec8b8f82560d6f8283987a3db7b21ff.json create mode 100644 .sqlx/query-cefb2a681997768a61ac8efe3dcfc52f05b9a0d816707908498821b85d8e3d1e.json delete mode 100644 .sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json delete mode 100644 src/operation.rs create mode 100644 src/transaction_operations.rs diff --git a/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json b/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json deleted file mode 100644 index 9ae60c8..0000000 --- a/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n u.command_type AS \"command_type: UserCommandType\",\n u.nonce,\n u.amount,\n u.fee,\n u.valid_until,\n u.memo,\n u.hash,\n pk_payer.value AS fee_payer,\n pk_source.value AS source,\n pk_receiver.value AS receiver,\n buc.status AS \"status: TransactionStatus\",\n buc.failure_reason AS \"failure_reason?\",\n ac.creation_fee AS \"creation_fee?\"\nFROM\n user_commands AS u\n INNER JOIN blocks_user_commands AS buc ON u.id=buc.user_command_id\n INNER JOIN public_keys AS pk_payer ON u.fee_payer_id=pk_payer.id\n INNER JOIN public_keys AS pk_source ON u.source_id=pk_source.id\n INNER JOIN public_keys AS pk_receiver ON u.receiver_id=pk_receiver.id\n LEFT JOIN account_identifiers AS ai_receiver ON pk_receiver.id=ai_receiver.public_key_id\n /* Account creation fees are attributed to the first successful command in the\n block that mentions the account with the following LEFT JOIN */\n LEFT JOIN accounts_created AS ac ON buc.block_id=ac.block_id\n AND ai_receiver.id=ac.account_identifier_id\n AND buc.status='applied'\n AND buc.sequence_no=(\n SELECT\n least(\n (\n SELECT\n min(bic2.sequence_no)\n FROM\n blocks_internal_commands AS bic2\n INNER JOIN internal_commands AS ic2 ON bic2.internal_command_id=ic2.id\n WHERE\n u.receiver_id=ic2.receiver_id\n AND bic2.block_id=buc.block_id\n AND bic2.status='applied'\n ),\n (\n SELECT\n min(buc2.sequence_no)\n FROM\n blocks_user_commands AS buc2\n INNER JOIN user_commands AS uc2 ON buc2.user_command_id=uc2.id\n WHERE\n u.receiver_id=uc2.receiver_id\n AND buc2.block_id=buc.block_id\n AND buc2.status='applied'\n )\n )\n )\n LEFT JOIN tokens AS t ON ai_receiver.token_id=t.id\nWHERE\n buc.block_id=$1\n AND t.value=$2\n", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "command_type: UserCommandType", - "type_info": { - "Custom": { - "name": "user_command_type", - "kind": { - "Enum": [ - "payment", - "delegation" - ] - } - } - } - }, - { - "ordinal": 1, - "name": "nonce", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "amount", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "fee", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "valid_until", - "type_info": "Int8" - }, - { - "ordinal": 5, - "name": "memo", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "hash", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "fee_payer", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "source", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "receiver", - "type_info": "Text" - }, - { - "ordinal": 10, - "name": "status: TransactionStatus", - "type_info": { - "Custom": { - "name": "transaction_status", - "kind": { - "Enum": [ - "applied", - "failed" - ] - } - } - } - }, - { - "ordinal": 11, - "name": "failure_reason?", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "creation_fee?", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int4", - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - true, - false, - false, - false, - false, - false, - false, - true, - false - ] - }, - "hash": "62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60" -} diff --git a/.sqlx/query-bde32fe15f8f99a3a5a665c3971457babec8b8f82560d6f8283987a3db7b21ff.json b/.sqlx/query-bde32fe15f8f99a3a5a665c3971457babec8b8f82560d6f8283987a3db7b21ff.json new file mode 100644 index 0000000..448229b --- /dev/null +++ b/.sqlx/query-bde32fe15f8f99a3a5a665c3971457babec8b8f82560d6f8283987a3db7b21ff.json @@ -0,0 +1,92 @@ +{ + "db_name": "PostgreSQL", + "query": "WITH\n internal_commands_cte AS (\n SELECT DISTINCT\n ON (\n i.hash,\n i.command_type,\n bic.sequence_no,\n bic.secondary_sequence_no\n ) i.*,\n ac.creation_fee,\n pk.value AS receiver,\n bic.sequence_no,\n bic.secondary_sequence_no,\n bic.status\n FROM\n internal_commands AS i\n INNER JOIN blocks_internal_commands AS bic ON i.id=bic.internal_command_id\n INNER JOIN public_keys AS pk ON i.receiver_id=pk.id\n INNER JOIN account_identifiers AS ai ON i.receiver_id=ai.public_key_id\n LEFT JOIN accounts_created AS ac ON ai.id=ac.account_identifier_id\n AND bic.block_id=ac.block_id\n AND bic.sequence_no=(\n SELECT\n least(\n (\n SELECT\n min(bic2.sequence_no)\n FROM\n blocks_internal_commands AS bic2\n INNER JOIN internal_commands AS ic2 ON bic2.internal_command_id=ic2.id\n WHERE\n i.receiver_id=ic2.receiver_id\n AND bic2.block_id=bic.block_id\n AND bic2.status='applied'\n ),\n (\n SELECT\n min(buc2.sequence_no)\n FROM\n blocks_user_commands AS buc2\n INNER JOIN user_commands AS uc2 ON buc2.user_command_id=uc2.id\n WHERE\n i.receiver_id=uc2.receiver_id\n AND buc2.block_id=bic.block_id\n AND buc2.status='applied'\n )\n )\n )\n INNER JOIN tokens AS t ON ai.token_id=t.id\n WHERE\n bic.block_id=$1\n AND t.value=$2\n )\nSELECT\n ic.command_type AS \"command_type: InternalCommandType\",\n ic.hash,\n ic.creation_fee AS \"creation_fee?\",\n ic.receiver,\n ic.sequence_no,\n ic.secondary_sequence_no,\n ic.fee,\n ic.status AS \"status: TransactionStatus\",\n coinbase_receiver_pk.value AS coinbase_receiver\nFROM\n internal_commands_cte AS ic\n LEFT JOIN internal_commands_cte AS ic_coinbase_receiver ON ic.command_type='fee_transfer_via_coinbase'\n AND ic_coinbase_receiver.command_type='coinbase'\n LEFT JOIN public_keys AS coinbase_receiver_pk ON ic_coinbase_receiver.receiver_id=coinbase_receiver_pk.id\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "command_type: InternalCommandType", + "type_info": { + "Custom": { + "name": "internal_command_type", + "kind": { + "Enum": [ + "fee_transfer_via_coinbase", + "fee_transfer", + "coinbase" + ] + } + } + } + }, + { + "ordinal": 1, + "name": "hash", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "creation_fee?", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "receiver", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "sequence_no", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "secondary_sequence_no", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "fee", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "status: TransactionStatus", + "type_info": { + "Custom": { + "name": "transaction_status", + "kind": { + "Enum": [ + "applied", + "failed" + ] + } + } + } + }, + { + "ordinal": 8, + "name": "coinbase_receiver", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + true + ] + }, + "hash": "bde32fe15f8f99a3a5a665c3971457babec8b8f82560d6f8283987a3db7b21ff" +} diff --git a/.sqlx/query-cefb2a681997768a61ac8efe3dcfc52f05b9a0d816707908498821b85d8e3d1e.json b/.sqlx/query-cefb2a681997768a61ac8efe3dcfc52f05b9a0d816707908498821b85d8e3d1e.json new file mode 100644 index 0000000..af8ae76 --- /dev/null +++ b/.sqlx/query-cefb2a681997768a61ac8efe3dcfc52f05b9a0d816707908498821b85d8e3d1e.json @@ -0,0 +1,114 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT DISTINCT\n ON (\n buc.block_id,\n buc.user_command_id,\n buc.sequence_no\n ) u.command_type AS \"command_type: UserCommandType\",\n u.nonce,\n u.amount,\n u.fee,\n u.valid_until,\n u.memo,\n u.hash,\n pk_payer.value AS fee_payer,\n pk_source.value AS source,\n pk_receiver.value AS receiver,\n buc.status AS \"status: TransactionStatus\",\n buc.failure_reason AS \"failure_reason?\",\n ac.creation_fee AS \"creation_fee?\"\nFROM\n user_commands AS u\n INNER JOIN blocks_user_commands AS buc ON u.id=buc.user_command_id\n INNER JOIN public_keys AS pk_payer ON u.fee_payer_id=pk_payer.id\n INNER JOIN public_keys AS pk_source ON u.source_id=pk_source.id\n INNER JOIN public_keys AS pk_receiver ON u.receiver_id=pk_receiver.id\n LEFT JOIN account_identifiers AS ai_receiver ON pk_receiver.id=ai_receiver.public_key_id\n /* Account creation fees are attributed to the first successful command in the\n block that mentions the account with the following LEFT JOIN */\n LEFT JOIN accounts_created AS ac ON buc.block_id=ac.block_id\n AND ai_receiver.id=ac.account_identifier_id\n AND buc.status='applied'\n AND buc.sequence_no=(\n SELECT\n least(\n (\n SELECT\n min(bic2.sequence_no)\n FROM\n blocks_internal_commands AS bic2\n INNER JOIN internal_commands AS ic2 ON bic2.internal_command_id=ic2.id\n WHERE\n u.receiver_id=ic2.receiver_id\n AND bic2.block_id=buc.block_id\n AND bic2.status='applied'\n ),\n (\n SELECT\n min(buc2.sequence_no)\n FROM\n blocks_user_commands AS buc2\n INNER JOIN user_commands AS uc2 ON buc2.user_command_id=uc2.id\n WHERE\n u.receiver_id=uc2.receiver_id\n AND buc2.block_id=buc.block_id\n AND buc2.status='applied'\n )\n )\n )\n LEFT JOIN tokens AS t ON ai_receiver.token_id=t.id\nWHERE\n buc.block_id=$1\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "command_type: UserCommandType", + "type_info": { + "Custom": { + "name": "user_command_type", + "kind": { + "Enum": [ + "payment", + "delegation" + ] + } + } + } + }, + { + "ordinal": 1, + "name": "nonce", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "amount", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "fee", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "valid_until", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "memo", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "hash", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "fee_payer", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "source", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "receiver", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "status: TransactionStatus", + "type_info": { + "Custom": { + "name": "transaction_status", + "kind": { + "Enum": [ + "applied", + "failed" + ] + } + } + } + }, + { + "ordinal": 11, + "name": "failure_reason?", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "creation_fee?", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + true, + false, + true, + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "cefb2a681997768a61ac8efe3dcfc52f05b9a0d816707908498821b85d8e3d1e" +} diff --git a/.sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json b/.sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json deleted file mode 100644 index c4f5671..0000000 --- a/.sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH\n internal_commands_cte AS (\n SELECT DISTINCT\n ON (\n i.hash,\n i.command_type,\n bic.sequence_no,\n bic.secondary_sequence_no\n ) i.*,\n ac.creation_fee,\n pk.value AS receiver,\n bic.sequence_no,\n bic.secondary_sequence_no\n FROM\n internal_commands AS i\n INNER JOIN blocks_internal_commands AS bic ON i.id=bic.internal_command_id\n INNER JOIN public_keys AS pk ON i.receiver_id=pk.id\n INNER JOIN account_identifiers AS ai ON i.receiver_id=ai.public_key_id\n LEFT JOIN accounts_created AS ac ON ai.id=ac.account_identifier_id\n AND bic.block_id=ac.block_id\n AND bic.sequence_no=(\n SELECT\n least(\n (\n SELECT\n min(bic2.sequence_no)\n FROM\n blocks_internal_commands AS bic2\n INNER JOIN internal_commands AS ic2 ON bic2.internal_command_id=ic2.id\n WHERE\n i.receiver_id=ic2.receiver_id\n AND bic2.block_id=bic.block_id\n AND bic2.status='applied'\n ),\n (\n SELECT\n min(buc2.sequence_no)\n FROM\n blocks_user_commands AS buc2\n INNER JOIN user_commands AS uc2 ON buc2.user_command_id=uc2.id\n WHERE\n i.receiver_id=uc2.receiver_id\n AND buc2.block_id=bic.block_id\n AND buc2.status='applied'\n )\n )\n )\n INNER JOIN tokens AS t ON ai.token_id=t.id\n WHERE\n bic.block_id=$1\n AND t.value=$2\n )\nSELECT\n ic.command_type AS \"command_type: InternalCommandType\",\n ic.hash,\n ic.creation_fee AS \"creation_fee?\",\n ic.receiver,\n ic.sequence_no,\n ic.secondary_sequence_no,\n ic.fee,\n coinbase_receiver_pk.value AS coinbase_receiver\nFROM\n internal_commands_cte AS ic\n LEFT JOIN internal_commands_cte AS ic_coinbase_receiver ON ic.command_type='fee_transfer_via_coinbase'\n AND ic_coinbase_receiver.command_type='coinbase'\n LEFT JOIN public_keys AS coinbase_receiver_pk ON ic_coinbase_receiver.receiver_id=coinbase_receiver_pk.id\n", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "command_type: InternalCommandType", - "type_info": { - "Custom": { - "name": "internal_command_type", - "kind": { - "Enum": [ - "fee_transfer_via_coinbase", - "fee_transfer", - "coinbase" - ] - } - } - } - }, - { - "ordinal": 1, - "name": "hash", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "creation_fee?", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "receiver", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "sequence_no", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "secondary_sequence_no", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "fee", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "coinbase_receiver", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int4", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8" -} diff --git a/sql/queries/internal_commands.sql b/sql/queries/internal_commands.sql index 51b9e37..f5f05c8 100644 --- a/sql/queries/internal_commands.sql +++ b/sql/queries/internal_commands.sql @@ -10,7 +10,8 @@ WITH ac.creation_fee, pk.value AS receiver, bic.sequence_no, - bic.secondary_sequence_no + bic.secondary_sequence_no, + bic.status FROM internal_commands AS i INNER JOIN blocks_internal_commands AS bic ON i.id=bic.internal_command_id @@ -58,6 +59,7 @@ SELECT ic.sequence_no, ic.secondary_sequence_no, ic.fee, + ic.status AS "status: TransactionStatus", coinbase_receiver_pk.value AS coinbase_receiver FROM internal_commands_cte AS ic diff --git a/sql/queries/user_commands.sql b/sql/queries/user_commands.sql index fb7ea96..273f222 100644 --- a/sql/queries/user_commands.sql +++ b/sql/queries/user_commands.sql @@ -1,5 +1,9 @@ -SELECT - u.command_type AS "command_type: UserCommandType", +SELECT DISTINCT + ON ( + buc.block_id, + buc.user_command_id, + buc.sequence_no + ) u.command_type AS "command_type: UserCommandType", u.nonce, u.amount, u.fee, @@ -54,4 +58,3 @@ FROM LEFT JOIN tokens AS t ON ai_receiver.token_id=t.id WHERE buc.block_id=$1 - AND t.value=$2 diff --git a/src/api/block.rs b/src/api/block.rs index c613012..7dd2a17 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,14 +1,16 @@ use anyhow::Result; use coinbase_mesh::models::{ - AccountIdentifier, Block, BlockIdentifier, BlockRequest, BlockResponse, Operation, PartialBlockIdentifier, - Transaction, TransactionIdentifier, + Block, BlockIdentifier, BlockRequest, BlockResponse, PartialBlockIdentifier, Transaction, TransactionIdentifier, }; use serde::Serialize; +use serde_json::json; use sqlx::FromRow; use crate::{ - operation, sql_to_mesh::zkapp_commands_to_transactions, util::DEFAULT_TOKEN_ID, ChainStatus, InternalCommandType, - MinaMesh, MinaMeshError, OperationType, TransactionStatus, UserCommandType, ZkAppCommand, + generate_internal_command_transaction_identifier, generate_operations_internal_command, + generate_operations_user_command, generate_transaction_metadata, sql_to_mesh::zkapp_commands_to_transactions, + util::DEFAULT_TOKEN_ID, ChainStatus, InternalCommandMetadata, InternalCommandType, MinaMesh, MinaMeshError, + TransactionStatus, UserCommandMetadata, UserCommandType, ZkAppCommand, }; /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 @@ -31,34 +33,44 @@ impl MinaMesh { Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), None => block_identifier.clone(), }; - let (mut user_commands, internal_commands, zkapp_commands) = tokio::try_join!( + let (user_commands, internal_commands, zkapp_commands) = tokio::try_join!( self.user_commands(&metadata), self.internal_commands(&metadata), self.zkapp_commands(&metadata) )?; - user_commands.extend(internal_commands.into_iter()); - user_commands.extend(zkapp_commands.into_iter()); + + let all_commands: Vec<_> = + internal_commands.into_iter().chain(user_commands.into_iter()).chain(zkapp_commands.into_iter()).collect(); + Ok(BlockResponse { - block: Some(Box::new(Block::new( - block_identifier, - parent_block_identifier, - metadata.timestamp.parse()?, - user_commands, - ))), + block: Some(Box::new(Block { + block_identifier: Box::new(block_identifier), + parent_block_identifier: Box::new(parent_block_identifier), + timestamp: metadata.timestamp.parse()?, + transactions: all_commands, + metadata: Some(json!({ "creator": metadata.creator })), + })), other_transactions: None, }) } // TODO: use default token value, check how to best handle this pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { - let metadata = - sqlx::query_file_as!(UserCommandMetadata, "sql/queries/user_commands.sql", metadata.id, DEFAULT_TOKEN_ID) - .fetch_all(&self.pg_pool) - .await?; + let metadata = sqlx::query_file_as!(UserCommandMetadata, "sql/queries/user_commands.sql", metadata.id) + .fetch_all(&self.pg_pool) + .await?; let transactions = metadata .into_iter() .map(|item| { - Transaction::new(TransactionIdentifier::new(item.hash.clone()), user_command_metadata_to_operations(&item)) + let metadata = generate_transaction_metadata(&item); + let operations = generate_operations_user_command(&item); + + Transaction { + transaction_identifier: Box::new(TransactionIdentifier::new(item.hash.clone())), + operations, + metadata, + related_transactions: None, + } }) .collect(); Ok(transactions) @@ -69,13 +81,22 @@ impl MinaMesh { sqlx::query_file_as!(InternalCommandMetadata, "sql/queries/internal_commands.sql", metadata.id, DEFAULT_TOKEN_ID) .fetch_all(&self.pg_pool) .await?; + let transactions = metadata .into_iter() .map(|item| { - internal_command_metadata_to_operation(&item) - .map(|operation| Transaction::new(TransactionIdentifier::new(item.hash.clone()), operation)) + let transaction_identifier = generate_internal_command_transaction_identifier( + &item.command_type, + item.sequence_no, + item.secondary_sequence_no, + &item.hash, + ); + Transaction::new( + TransactionIdentifier::new(transaction_identifier), + generate_operations_internal_command(&item), + ) }) - .collect::, MinaMeshError>>()?; + .collect(); Ok(transactions) } @@ -139,35 +160,6 @@ pub struct BlockMetadata { winner: String, } -#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] -pub struct UserCommandMetadata { - command_type: UserCommandType, - nonce: i64, - amount: Option, - fee: String, - valid_until: Option, - memo: String, - hash: String, - fee_payer: String, - source: String, - receiver: String, - status: TransactionStatus, - failure_reason: Option, - creation_fee: Option, -} - -#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] -pub struct InternalCommandMetadata { - command_type: InternalCommandType, - receiver: String, - fee: String, - hash: String, - creation_fee: Option, - sequence_no: i32, - secondary_sequence_no: i32, - coinbase_receiver: Option, -} - #[derive(Debug, PartialEq, Eq, FromRow, Serialize)] pub struct ZkappCommandMetadata { id: i64, @@ -206,146 +198,3 @@ pub struct ZkappAccountUpdateMetadata { account: String, token: String, } - -fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { - let fee_payer_account_id = &AccountIdentifier::new(metadata.fee_payer.clone()); - let receiver_account_id = &AccountIdentifier::new(metadata.receiver.clone()); - let source_account_id = &AccountIdentifier::new(metadata.source.clone()); - - let mut operations = Vec::new(); - if metadata.fee != "0" { - operations.push(operation( - 0, - Some(&metadata.fee), - fee_payer_account_id, - OperationType::FeePayment, - None, - None, - None, - None, - )); - } - if metadata.failure_reason.is_none() { - if let Some(creation_fee) = &metadata.creation_fee { - operations.push(operation( - 1, - Some(creation_fee), - receiver_account_id, - OperationType::AccountCreationFeeViaPayment, - Some(&metadata.status), - None, - None, - None, - )); - } - match metadata.command_type { - UserCommandType::Delegation => { - operations.push(operation( - 2, - None, - source_account_id, - OperationType::DelegateChange, - Some(&metadata.status), - None, - None, - None, - )); - } - UserCommandType::Payment => { - operations.extend_from_slice(&[ - operation( - 2, - metadata.amount.as_ref(), - source_account_id, - OperationType::PaymentSourceDec, - Some(&metadata.status), - None, - None, - None, - ), - operation( - 3, - metadata.amount.as_ref(), - receiver_account_id, - OperationType::PaymentReceiverInc, - Some(&metadata.status), - None, - None, - None, - ), - ]); - } - }; - } - operations -} - -fn internal_command_metadata_to_operation(metadata: &InternalCommandMetadata) -> Result, MinaMeshError> { - let receiver_account_id = &AccountIdentifier::new(metadata.receiver.clone()); - let mut operations = Vec::new(); - if let Some(creation_fee) = &metadata.creation_fee { - operations.push(operation( - 0, - Some(creation_fee), - receiver_account_id, - OperationType::AccountCreationFeeViaFeeReceiver, - None, - None, - None, - None, - )); - } - match metadata.command_type { - InternalCommandType::Coinbase => { - operations.push(operation( - 2, - Some(&metadata.fee), - receiver_account_id, - OperationType::CoinbaseInc, - None, - None, - None, - None, - )); - } - InternalCommandType::FeeTransfer => { - operations.push(operation( - 2, - Some(&metadata.fee), - receiver_account_id, - OperationType::FeeReceiverInc, - None, - None, - None, - None, - )); - } - InternalCommandType::FeeTransferViaCoinbase => { - if let Some(coinbase_receiver) = &metadata.coinbase_receiver { - operations.push(operation( - 2, - Some(&metadata.fee), - receiver_account_id, - OperationType::FeeReceiverInc, - None, - None, - None, - None, - )); - operations.push(operation( - 3, - Some(&metadata.fee), - &AccountIdentifier::new(coinbase_receiver.to_string()), - OperationType::FeePayerDec, - None, - None, - None, - None, - )); - } else { - return Err(MinaMeshError::InvariantViolation); - } - } - } - Ok(operations) -} diff --git a/src/api/search_transactions.rs b/src/api/search_transactions.rs index 389d59d..62aad2c 100644 --- a/src/api/search_transactions.rs +++ b/src/api/search_transactions.rs @@ -4,13 +4,13 @@ use coinbase_mesh::models::{ AccountIdentifier, BlockIdentifier, BlockTransaction, Operation, SearchTransactionsRequest, SearchTransactionsResponse, Transaction, TransactionIdentifier, }; -use convert_case::{Case, Casing}; -use serde_json::{json, Map, Value}; -use sqlx::FromRow; +use serde_json::json; use crate::{ - operation, util::DEFAULT_TOKEN_ID, ChainStatus, InternalCommandType, MinaMesh, MinaMeshError, OperationType, - TransactionStatus, UserCommandType, ZkAppCommand, + generate_internal_command_transaction_identifier, generate_operations_internal_command, + generate_operations_user_command, generate_transaction_metadata, operation, util::DEFAULT_TOKEN_ID, ChainStatus, + InternalCommand, InternalCommandType, MinaMesh, MinaMeshError, OperationType, TransactionStatus, UserCommand, + UserCommandType, ZkAppCommand, }; impl MinaMesh { @@ -289,118 +289,18 @@ pub fn zkapp_commands_to_block_transactions(commands: Vec) -> Vec< result } -#[derive(Debug, FromRow)] -pub struct InternalCommand { - pub id: Option, - pub command_type: InternalCommandType, - pub receiver_id: Option, - pub fee: Option, - pub hash: String, - pub receiver: String, - pub coinbase_receiver: Option, - pub sequence_no: i32, - pub secondary_sequence_no: i32, - pub block_id: i32, - pub status: TransactionStatus, - pub state_hash: Option, - pub height: Option, - pub total_count: Option, - pub creation_fee: Option, -} - impl From for BlockTransaction { fn from(internal_command: InternalCommand) -> Self { // Derive transaction_identifier by combining command_type, sequence numbers, // and the hash - let transaction_identifier = format!( - "{}:{}:{}:{}", - internal_command.command_type.to_string().to_case(Case::Snake), + let transaction_identifier = generate_internal_command_transaction_identifier( + &internal_command.command_type, internal_command.sequence_no, internal_command.secondary_sequence_no, - internal_command.hash + &internal_command.hash, ); - let fee = internal_command.fee.unwrap_or("0".to_string()); - let status = &internal_command.status; - let mut operations = Vec::new(); - let mut operation_index = 0; - - // Receiver Account Identifier - let receiver_account_id = &AccountIdentifier { - address: internal_command.receiver.clone(), - metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), - sub_account: None, - }; - - // Handle Account Creation Fee if applicable - if let Some(creation_fee) = &internal_command.creation_fee { - operations.push(operation( - operation_index, - Some(creation_fee), - receiver_account_id, - OperationType::AccountCreationFeeViaFeeReceiver, - Some(status), - None, - None, - None, - )); - operation_index += 1; - } - - match internal_command.command_type { - InternalCommandType::Coinbase => { - operations.push(operation( - operation_index, - Some(&fee), - receiver_account_id, - OperationType::CoinbaseInc, - Some(status), - None, - None, - None, - )); - } - - InternalCommandType::FeeTransfer => { - operations.push(operation( - operation_index, - Some(&fee), - receiver_account_id, - OperationType::FeeReceiverInc, - Some(status), - None, - None, - None, - )); - } - - InternalCommandType::FeeTransferViaCoinbase => { - if let Some(coinbase_receiver) = &internal_command.coinbase_receiver { - operations.push(operation( - operation_index, - Some(&fee), - receiver_account_id, - OperationType::FeeReceiverInc, - Some(status), - None, - None, - None, - )); - operation_index += 1; - - operations.push(operation( - operation_index, - Some(&fee), - &AccountIdentifier::new(coinbase_receiver.to_string()), - OperationType::FeePayerDec, - Some(status), - Some(vec![operation_index - 1]), - None, - None, - )); - } - } - } + let operations = generate_operations_internal_command(&internal_command); let block_identifier = BlockIdentifier::new( internal_command.height.unwrap_or_default(), @@ -417,170 +317,18 @@ impl From for BlockTransaction { } } -#[derive(Debug, FromRow)] -pub struct UserCommand { - pub id: Option, - pub command_type: UserCommandType, - pub fee_payer_id: Option, - pub source_id: Option, - pub receiver_id: Option, - pub nonce: i64, - pub amount: Option, - pub fee: Option, - pub valid_until: Option, - pub memo: Option, - pub hash: String, - pub block_id: Option, - pub sequence_no: Option, - pub status: TransactionStatus, - pub failure_reason: Option, - pub state_hash: Option, - pub chain_status: Option, - pub height: Option, - pub total_count: Option, - pub fee_payer: String, - pub source: String, - pub receiver: String, - pub creation_fee: Option, -} - -impl UserCommand { - pub fn decoded_memo(&self) -> Option { - let memo = self.memo.clone().unwrap_or_default(); - match bs58::decode(memo).into_vec() { - Ok(decoded_bytes) => { - let cleaned = &decoded_bytes[3 .. decoded_bytes[2] as usize + 3]; - Some(String::from_utf8_lossy(cleaned).to_string()) - } - Err(_) => None, - } - } -} - impl From for BlockTransaction { fn from(user_command: UserCommand) -> Self { - let decoded_memo = user_command.decoded_memo().unwrap_or_default(); - let amt = user_command.amount.clone().unwrap_or_else(|| "0".to_string()); - let receiver_account_id = &AccountIdentifier { - address: user_command.receiver.clone(), - metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), - sub_account: None, - }; - let source_account_id = &AccountIdentifier { - address: user_command.source, - metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), - sub_account: None, - }; - let fee_payer_account_id = &AccountIdentifier { - address: user_command.fee_payer, - metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), - sub_account: None, - }; - - // Construct operations_metadata - let mut operations_metadata = Map::new(); - if let Some(failure_reason) = user_command.failure_reason.clone() { - operations_metadata.insert("reason".to_string(), json!(failure_reason)); - } - let operations_metadata_value = - if operations_metadata.is_empty() { None } else { Some(Value::Object(operations_metadata)) }; - - // Construct transaction metadata - let mut transaction_metadata = Map::new(); - transaction_metadata.insert("nonce".to_string(), json!(user_command.nonce)); - if !decoded_memo.is_empty() { - transaction_metadata.insert("memo".to_string(), json!(decoded_memo)); - } - let transaction_metadata_value = - if transaction_metadata.is_empty() { None } else { Some(Value::Object(transaction_metadata)) }; - - let mut operations = Vec::new(); - let mut operation_index = 0; - - // Operation 1: Fee Payment - operations.push(operation( - operation_index, - Some(&format!("-{}", user_command.fee.unwrap_or("0".to_string()))), - fee_payer_account_id, - OperationType::FeePayment, - Some(&TransactionStatus::Applied), - None, - operations_metadata_value.as_ref(), - None, - )); - - operation_index += 1; - - // Operation 2: Account Creation Fee (if applicable) - if let Some(creation_fee) = &user_command.creation_fee { - let negated_creation_fee = format!("-{}", creation_fee); - operations.push(operation( - operation_index, - if user_command.status == TransactionStatus::Applied { Some(&negated_creation_fee) } else { None }, - receiver_account_id, - OperationType::AccountCreationFeeViaPayment, - Some(&user_command.status), - None, - operations_metadata_value.as_ref(), - None, - )); - - operation_index += 1; - } - - // Decide on the type of operation based on command type - match user_command.command_type { - // Operation 3: Payment Source Decrement - UserCommandType::Payment => { - let negated_amt = format!("-{}", amt); - operations.push(operation( - operation_index, - if user_command.status == TransactionStatus::Applied { Some(&negated_amt) } else { None }, - source_account_id, - OperationType::PaymentSourceDec, - Some(&user_command.status), - None, - operations_metadata_value.as_ref(), - None, - )); - - operation_index += 1; - - // Operation 4: Payment Receiver Increment - operations.push(operation( - operation_index, - if user_command.status == TransactionStatus::Applied { Some(&amt) } else { None }, - receiver_account_id, - OperationType::PaymentReceiverInc, - Some(&user_command.status), - Some(vec![operation_index - 1]), - operations_metadata_value.as_ref(), - None, - )); - } - - // Operation 3: Delegate Change - UserCommandType::Delegation => { - operations.push(operation( - operation_index, - None, - source_account_id, - OperationType::DelegateChange, - Some(&user_command.status), - None, - Some(&json!({ "delegate_change_target": user_command.receiver })), - None, - )); - } - } + let metadata = generate_transaction_metadata(&user_command); + let operations = generate_operations_user_command(&user_command); let block_identifier = BlockIdentifier::new(user_command.height.unwrap_or_default(), user_command.state_hash.unwrap_or_default()); let transaction = Transaction { transaction_identifier: Box::new(TransactionIdentifier::new(user_command.hash)), operations, + metadata, related_transactions: None, - metadata: transaction_metadata_value, }; BlockTransaction::new(block_identifier, transaction) } diff --git a/src/lib.rs b/src/lib.rs index 32bccd5..2f4b46d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,10 @@ mod config; mod create_router; mod error; mod graphql; -mod operation; mod playground; mod sql_to_mesh; pub mod test; +mod transaction_operations; mod types; pub mod util; @@ -21,8 +21,8 @@ pub use create_router::create_router; use dashmap::DashMap; pub use error::*; use graphql::GraphQLClient; -pub use operation::*; use sqlx::PgPool; +pub use transaction_operations::*; pub use types::*; #[derive(Debug)] pub struct MinaMesh { diff --git a/src/operation.rs b/src/operation.rs deleted file mode 100644 index 5ac56e5..0000000 --- a/src/operation.rs +++ /dev/null @@ -1,44 +0,0 @@ -use coinbase_mesh::models::{AccountIdentifier, Amount, Currency, Operation, OperationIdentifier}; -use convert_case::{Case, Casing}; -use serde_json::json; - -use crate::{util::DEFAULT_TOKEN_ID, OperationStatus, OperationType, TransactionStatus}; - -/// Creates a `Currency` based on the token provided. -/// If the token is `DEFAULT_TOKEN_ID`, it creates a MINA currency. -/// Otherwise, it creates a MINA+ currency with the token ID in metadata. -pub fn create_currency(token: Option<&String>) -> Currency { - match token { - Some(token_id) if token_id != DEFAULT_TOKEN_ID => { - Currency { symbol: "MINA+".to_owned(), decimals: 9, metadata: Some(json!({ "token_id": token_id })) } - } - _ => Currency::new("MINA".to_owned(), 9), - } -} - -#[allow(clippy::too_many_arguments)] -pub fn operation( - ident: i64, - amount: Option<&String>, - account: &AccountIdentifier, - operation_type: OperationType, - status: Option<&TransactionStatus>, - related_operations: Option>, - metadata: Option<&serde_json::Value>, - token: Option<&String>, -) -> Operation { - let currency = create_currency(token); - Operation { - operation_identifier: Box::new(OperationIdentifier::new(ident)), - amount: amount.map(|value| Box::new(Amount::new(value.to_owned(), currency))), - account: Some(Box::new(account.to_owned())), - status: Some( - status.map(|item| OperationStatus::from(item.to_owned())).unwrap_or(OperationStatus::Success).to_string(), - ), - related_operations: related_operations - .map(|items| items.iter().map(|item| OperationIdentifier::new(item.to_owned())).collect()), - coin_change: None, - r#type: operation_type.to_string().to_case(Case::Snake), - metadata: metadata.cloned(), - } -} diff --git a/src/test.rs b/src/test.rs index d0f9f5b..ff7d438 100644 --- a/src/test.rs +++ b/src/test.rs @@ -105,7 +105,7 @@ impl Display for ErrorContainer { fn normalize_body(raw: &str) -> Result { let mut json_unsorted: Value = serde_json::from_str(raw)?; sort_json_value(&mut json_unsorted); - remove_empty_related_transactions(&mut json_unsorted); + remove_empty_tx_fields(&mut json_unsorted); Ok(serde_json::to_string_pretty(&json_unsorted)?) } @@ -132,30 +132,28 @@ fn sort_json_value(value: &mut Value) { } } -// Remove empty "related_transactions" arrays from the JSON -// This is necessary because Rosetta OCaml includes empty arrays in the response -// but mina-mesh does not +// Remove empty "related_transactions" | "other_transactions" arrays from the +// JSON This is necessary because Rosetta OCaml includes empty arrays in the +// response but mina-mesh does not // Workaround for https://github.com/MinaFoundation/MinaMesh/issues/48 -fn remove_empty_related_transactions(value: &mut Value) { +fn remove_empty_tx_fields(value: &mut Value) { match value { Value::Object(map) => { - map.retain( - |key, v| { - if key == "related_transactions" { - !matches!(v, Value::Array(arr) if arr.is_empty()) - } else { - true - } - }, - ); + map.retain(|key, v| { + if key == "related_transactions" || key == "other_transactions" { + !matches!(v, Value::Array(arr) if arr.is_empty()) + } else { + true + } + }); for v in map.values_mut() { - remove_empty_related_transactions(v); + remove_empty_tx_fields(v); } } Value::Array(vec) => { for v in vec.iter_mut() { - remove_empty_related_transactions(v); + remove_empty_tx_fields(v); } } _ => {} diff --git a/src/transaction_operations.rs b/src/transaction_operations.rs new file mode 100644 index 0000000..5a5d7cc --- /dev/null +++ b/src/transaction_operations.rs @@ -0,0 +1,278 @@ +use coinbase_mesh::models::{AccountIdentifier, Amount, Currency, Operation, OperationIdentifier}; +use convert_case::{Case, Casing}; +use serde_json::{json, Map, Value}; + +use crate::{ + util::DEFAULT_TOKEN_ID, InternalCommandOperationsData, InternalCommandType, OperationStatus, OperationType, + TransactionStatus, UserCommandOperationsData, UserCommandType, +}; + +/// Creates a `Currency` based on the token provided. +/// If the token is `DEFAULT_TOKEN_ID`, it creates a MINA currency. +/// Otherwise, it creates a MINA+ currency with the token ID in metadata. +pub fn create_currency(token: Option<&String>) -> Currency { + match token { + Some(token_id) if token_id != DEFAULT_TOKEN_ID => { + Currency { symbol: "MINA+".to_owned(), decimals: 9, metadata: Some(json!({ "token_id": token_id })) } + } + _ => Currency::new("MINA".to_owned(), 9), + } +} + +#[allow(clippy::too_many_arguments)] +pub fn operation( + ident: i64, + amount: Option<&String>, + account: &AccountIdentifier, + operation_type: OperationType, + status: Option<&TransactionStatus>, + related_operations: Option>, + metadata: Option<&serde_json::Value>, + token: Option<&String>, +) -> Operation { + let currency = create_currency(token); + Operation { + operation_identifier: Box::new(OperationIdentifier::new(ident)), + amount: amount.map(|value| Box::new(Amount::new(value.to_owned(), currency))), + account: Some(Box::new(account.to_owned())), + status: Some( + status.map(|item| OperationStatus::from(item.to_owned())).unwrap_or(OperationStatus::Success).to_string(), + ), + related_operations: related_operations + .map(|items| items.iter().map(|item| OperationIdentifier::new(item.to_owned())).collect()), + coin_change: None, + r#type: operation_type.to_string().to_case(Case::Snake), + metadata: metadata.cloned(), + } +} + +// Decode a transaction memo +pub fn decode_memo(memo: &Option) -> Option { + let memo = memo.clone(); + if let Some(memo) = memo { + match bs58::decode(memo).into_vec() { + Ok(decoded_bytes) => { + let cleaned = &decoded_bytes[3 .. decoded_bytes[2] as usize + 3]; + Some(String::from_utf8_lossy(cleaned).to_string()) + } + Err(_) => None, + } + } else { + None + } +} + +// Construct transaction metadata +pub fn generate_transaction_metadata(data: &T) -> Option { + let decoded_memo = decode_memo(&data.memo()).unwrap_or_default(); + let mut transaction_metadata = Map::new(); + transaction_metadata.insert("nonce".to_string(), json!(data.nonce())); + if !decoded_memo.is_empty() { + transaction_metadata.insert("memo".to_string(), json!(decoded_memo)); + } + if transaction_metadata.is_empty() { + None + } else { + Some(Value::Object(transaction_metadata)) + } +} + +pub fn generate_operations_user_command(data: &T) -> Vec { + let amt = data.amount().unwrap_or("0").to_string(); + let receiver_account_id = &AccountIdentifier { + address: data.receiver().to_string(), + metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), + sub_account: None, + }; + let source_account_id = &AccountIdentifier { + address: data.source().to_string(), + metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), + sub_account: None, + }; + let fee_payer_account_id = &AccountIdentifier { + address: data.fee_payer().to_string(), + metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), + sub_account: None, + }; + + // Construct operations_metadata + let mut operations_metadata = Map::new(); + if let Some(failure_reason) = data.failure_reason() { + operations_metadata.insert("reason".to_string(), json!(failure_reason)); + } + let operations_metadata_value = + if operations_metadata.is_empty() { None } else { Some(Value::Object(operations_metadata)) }; + + let mut operations = Vec::new(); + let mut operation_index = 0; + + // Operation 1: Fee Payment + operations.push(operation( + operation_index, + Some(&format!("-{}", data.fee())), + fee_payer_account_id, + OperationType::FeePayment, + Some(&TransactionStatus::Applied), + None, + operations_metadata_value.as_ref(), + None, + )); + operation_index += 1; + + // Operation 2: Account Creation Fee (if applicable) + if let Some(creation_fee) = data.creation_fee() { + let negated_creation_fee = format!("-{}", creation_fee); + operations.push(operation( + operation_index, + if data.status() == &TransactionStatus::Applied { Some(&negated_creation_fee) } else { None }, + receiver_account_id, + OperationType::AccountCreationFeeViaPayment, + Some(data.status()), + None, + operations_metadata_value.as_ref(), + None, + )); + operation_index += 1; + } + + // Decide on the type of operation based on command type + match data.command_type() { + UserCommandType::Payment => { + let negated_amt = format!("-{}", amt); + operations.push(operation( + operation_index, + if data.status() == &TransactionStatus::Applied { Some(&negated_amt) } else { None }, + source_account_id, + OperationType::PaymentSourceDec, + Some(data.status()), + None, + operations_metadata_value.as_ref(), + None, + )); + operation_index += 1; + + operations.push(operation( + operation_index, + if data.status() == &TransactionStatus::Applied { Some(&amt) } else { None }, + receiver_account_id, + OperationType::PaymentReceiverInc, + Some(data.status()), + Some(vec![operation_index - 1]), + operations_metadata_value.as_ref(), + None, + )); + } + UserCommandType::Delegation => { + operations.push(operation( + operation_index, + None, + source_account_id, + OperationType::DelegateChange, + Some(data.status()), + None, + Some(&json!({ "delegate_change_target": data.receiver() })), + None, + )); + } + } + + operations +} + +pub fn generate_internal_command_transaction_identifier( + command_type: &InternalCommandType, + sequence_no: i32, + secondary_sequence_no: i32, + hash: &str, +) -> String { + format!("{}:{}:{}:{}", command_type.to_string().to_case(Case::Snake), sequence_no, secondary_sequence_no, hash) +} + +pub fn generate_operations_internal_command(data: &T) -> Vec { + let mut operations = Vec::new(); + let mut operation_index = 0; + + // Receiver Account Identifier + let receiver_account_id = &AccountIdentifier { + address: data.receiver().to_string(), + metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), + sub_account: None, + }; + + // Handle Account Creation Fee if applicable + if let Some(creation_fee) = data.creation_fee() { + operations.push(operation( + operation_index, + Some(creation_fee), + receiver_account_id, + OperationType::AccountCreationFeeViaFeeReceiver, + Some(data.status()), + None, + None, + None, + )); + operation_index += 1; + } + + // Process operations based on command type + match data.command_type() { + InternalCommandType::Coinbase => { + operations.push(operation( + operation_index, + Some(&data.fee()), + receiver_account_id, + OperationType::CoinbaseInc, + Some(data.status()), + None, + None, + None, + )); + } + + InternalCommandType::FeeTransfer => { + operations.push(operation( + operation_index, + Some(&data.fee()), + receiver_account_id, + OperationType::FeeReceiverInc, + Some(data.status()), + None, + None, + None, + )); + } + + InternalCommandType::FeeTransferViaCoinbase => { + if let Some(coinbase_receiver) = data.coinbase_receiver() { + operations.push(operation( + operation_index, + Some(&data.fee()), + receiver_account_id, + OperationType::FeeReceiverInc, + Some(data.status()), + None, + None, + None, + )); + operation_index += 1; + + operations.push(operation( + operation_index, + Some(&format!("-{}", data.fee())), + &AccountIdentifier { + address: coinbase_receiver.to_string(), + metadata: Some(json!({ "token_id": DEFAULT_TOKEN_ID })), + sub_account: None, + }, + OperationType::FeePayerDec, + Some(data.status()), + Some(vec![operation_index - 1]), + None, + None, + )); + } + } + } + + operations +} diff --git a/src/types.rs b/src/types.rs index a1edb9b..05a9549 100644 --- a/src/types.rs +++ b/src/types.rs @@ -110,6 +110,255 @@ pub struct ZkAppCommand { pub block_id: Option, } +// Used in search transactions +#[derive(Debug, FromRow)] +pub struct UserCommand { + pub id: Option, + pub command_type: UserCommandType, + pub fee_payer_id: Option, + pub source_id: Option, + pub receiver_id: Option, + pub nonce: i64, + pub amount: Option, + pub fee: Option, + pub valid_until: Option, + pub memo: Option, + pub hash: String, + pub block_id: Option, + pub sequence_no: Option, + pub status: TransactionStatus, + pub failure_reason: Option, + pub state_hash: Option, + pub chain_status: Option, + pub height: Option, + pub total_count: Option, + pub fee_payer: String, + pub source: String, + pub receiver: String, + pub creation_fee: Option, +} + +// Used in block +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] +pub struct UserCommandMetadata { + pub command_type: UserCommandType, + pub nonce: i64, + pub amount: Option, + pub fee: String, + pub valid_until: Option, + pub memo: Option, + pub hash: String, + pub fee_payer: String, + pub source: String, + pub receiver: String, + pub status: TransactionStatus, + pub failure_reason: Option, + pub creation_fee: Option, +} + +// Common trait for producing operations from user commands +pub trait UserCommandOperationsData { + fn command_type(&self) -> &UserCommandType; + fn fee_payer(&self) -> &str; + fn source(&self) -> &str; + fn receiver(&self) -> &str; + fn nonce(&self) -> i64; + fn memo(&self) -> Option; + fn amount(&self) -> Option<&str>; + fn fee(&self) -> &str; + fn status(&self) -> &TransactionStatus; + fn failure_reason(&self) -> Option<&str>; + fn creation_fee(&self) -> Option<&str>; +} + +impl UserCommandOperationsData for UserCommand { + fn command_type(&self) -> &UserCommandType { + &self.command_type + } + + fn fee_payer(&self) -> &str { + &self.fee_payer + } + + fn source(&self) -> &str { + &self.source + } + + fn receiver(&self) -> &str { + &self.receiver + } + + fn nonce(&self) -> i64 { + self.nonce + } + + fn memo(&self) -> Option { + self.memo.clone() + } + + fn amount(&self) -> Option<&str> { + self.amount.as_deref() + } + + fn fee(&self) -> &str { + self.fee.as_deref().unwrap_or("0") + } + + fn status(&self) -> &TransactionStatus { + &self.status + } + + fn failure_reason(&self) -> Option<&str> { + self.failure_reason.as_deref() + } + + fn creation_fee(&self) -> Option<&str> { + self.creation_fee.as_deref() + } +} + +impl UserCommandOperationsData for UserCommandMetadata { + fn command_type(&self) -> &UserCommandType { + &self.command_type + } + + fn fee_payer(&self) -> &str { + &self.fee_payer + } + + fn source(&self) -> &str { + &self.source + } + + fn receiver(&self) -> &str { + &self.receiver + } + + fn nonce(&self) -> i64 { + self.nonce + } + + fn memo(&self) -> Option { + self.memo.clone() + } + + fn amount(&self) -> Option<&str> { + self.amount.as_deref() + } + + fn fee(&self) -> &str { + &self.fee + } + + fn status(&self) -> &TransactionStatus { + &self.status + } + + fn failure_reason(&self) -> Option<&str> { + self.failure_reason.as_deref() + } + + fn creation_fee(&self) -> Option<&str> { + self.creation_fee.as_deref() + } +} + +// Used in search transactions +#[derive(Debug, FromRow)] +pub struct InternalCommand { + pub id: Option, + pub command_type: InternalCommandType, + pub receiver_id: Option, + pub fee: Option, + pub hash: String, + pub receiver: String, + pub coinbase_receiver: Option, + pub sequence_no: i32, + pub secondary_sequence_no: i32, + pub block_id: i32, + pub status: TransactionStatus, + pub state_hash: Option, + pub height: Option, + pub total_count: Option, + pub creation_fee: Option, +} + +// Used in block +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] +pub struct InternalCommandMetadata { + pub command_type: InternalCommandType, + pub receiver: String, + pub fee: String, + pub hash: String, + pub creation_fee: Option, + pub sequence_no: i32, + pub secondary_sequence_no: i32, + pub status: TransactionStatus, + pub coinbase_receiver: Option, +} + +pub trait InternalCommandOperationsData { + fn command_type(&self) -> &InternalCommandType; + fn receiver(&self) -> &str; + fn fee(&self) -> String; + fn creation_fee(&self) -> Option<&String>; + fn coinbase_receiver(&self) -> Option<&str>; + fn status(&self) -> &TransactionStatus; +} + +impl InternalCommandOperationsData for InternalCommand { + fn command_type(&self) -> &InternalCommandType { + &self.command_type + } + + fn receiver(&self) -> &str { + &self.receiver + } + + fn fee(&self) -> String { + self.fee.clone().unwrap_or("0".to_string()) + } + + fn creation_fee(&self) -> Option<&String> { + self.creation_fee.as_ref() + } + + fn coinbase_receiver(&self) -> Option<&str> { + self.coinbase_receiver.as_deref() + } + + fn status(&self) -> &TransactionStatus { + &self.status + } +} + +impl InternalCommandOperationsData for InternalCommandMetadata { + fn command_type(&self) -> &InternalCommandType { + &self.command_type + } + + fn receiver(&self) -> &str { + &self.receiver + } + + fn fee(&self) -> String { + self.fee.clone() + } + + fn creation_fee(&self) -> Option<&String> { + self.creation_fee.as_ref() + } + + fn coinbase_receiver(&self) -> Option<&str> { + self.coinbase_receiver.as_deref() + } + + fn status(&self) -> &TransactionStatus { + // Assuming metadata always represents applied status + &TransactionStatus::Applied + } +} + #[derive(Debug, Display, Hash, PartialEq, Eq)] pub enum CacheKey { NetworkId, diff --git a/tests/compare_to_ocaml.rs b/tests/compare_to_ocaml.rs index 9a2ef78..9c9bcf4 100644 --- a/tests/compare_to_ocaml.rs +++ b/tests/compare_to_ocaml.rs @@ -80,3 +80,9 @@ async fn account_balance_not_exists() -> Result<()> { let (subpath, reqs) = fixtures::account_balance_not_exists(); assert_responses_contain(subpath, &reqs, "\"message\": \"Account not found").await } + +#[tokio::test] +async fn block() -> Result<()> { + let (subpath, reqs) = fixtures::block(); + assert_responses_eq(subpath, &reqs).await +} diff --git a/tests/fixtures/block.rs b/tests/fixtures/block.rs index a42c87f..6fdb090 100644 --- a/tests/fixtures/block.rs +++ b/tests/fixtures/block.rs @@ -5,12 +5,11 @@ use mina_mesh::{ use super::CompareGroup; -#[allow(dead_code)] pub fn block<'a>() -> CompareGroup<'a> { ("/block", vec![ Box::new(BlockRequest { network_identifier: Box::new(network_id()), - block_identifier: Box::new(PartialBlockIdentifier::new()), + block_identifier: Box::new(PartialBlockIdentifier { index: Some(373797), hash: None }), }), Box::new(BlockRequest { network_identifier: Box::new(network_id()), diff --git a/tests/fixtures/mod.rs b/tests/fixtures/mod.rs index b7e5e7f..79e34c8 100644 --- a/tests/fixtures/mod.rs +++ b/tests/fixtures/mod.rs @@ -7,7 +7,6 @@ mod network; mod search_transactions; pub use account_balance::*; -#[allow(unused_imports)] pub use block::*; pub use mempool::*; pub use network::*; diff --git a/tests/snapshots/block__specified.snap b/tests/snapshots/block__specified.snap index bb11316..96cbca4 100644 --- a/tests/snapshots/block__specified.snap +++ b/tests/snapshots/block__specified.snap @@ -19,7 +19,7 @@ Some( transactions: [ Transaction { transaction_identifier: TransactionIdentifier { - hash: "5Jue65F8EuA9en5HGEpuGSFZy4ezMNJyoLNsDQ8cmwnbaRZgZUSS", + hash: "fee_transfer_via_coinbase:19:0:5JucW5kgMKJwU46GZf7NHtMmHHmTQHrJPqZZk8owP6C2tnDr45vr", }, operations: [ Operation { @@ -28,7 +28,7 @@ Some( network_index: None, }, related_operations: None, - type: "fee_payment", + type: "fee_receiver_inc", status: Some( "Success", ), @@ -36,12 +36,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "100000000", + value: "10000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -55,24 +59,35 @@ Some( }, Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 1, network_index: None, }, - related_operations: None, - type: "payment_source_dec", + related_operations: Some( + [ + OperationIdentifier { + index: 0, + network_index: None, + }, + ], + ), + type: "fee_payer_dec", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", + address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "2000000000", + value: "-10000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -84,26 +99,39 @@ Some( coin_change: None, metadata: None, }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "fee_transfer:4:0:5Junx6hHb9g6ZWkS8zFFSv9yXvWK885U18JVKANatKYtbG7CubE6", + }, + operations: [ Operation { operation_identifier: OperationIdentifier { - index: 3, + index: 0, network_index: None, }, related_operations: None, - type: "payment_receiver_inc", + type: "fee_receiver_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qkCHcbWjMDWTyVL21wWsRv7oV8XyfYwVKTNtCC7rM3qTit8jNavD", + address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "2000000000", + value: "800000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -121,7 +149,7 @@ Some( }, Transaction { transaction_identifier: TransactionIdentifier { - hash: "5JvMQzS8pWjMaAv9VVGAQFWsEvEDeeVaENybCNSEtSLVM4Kjj6Z8", + hash: "coinbase:19:0:5JuUi8aafVghGFo76NKsQdb4MrgpxyVSvJAsef7G9XefW9HttAsY", }, operations: [ Operation { @@ -130,20 +158,24 @@ Some( network_index: None, }, related_operations: None, - type: "fee_payment", + type: "coinbase_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", + address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "100000000", + value: "720000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -155,13 +187,22 @@ Some( coin_change: None, metadata: None, }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "fee_transfer:20:1:5Jv4UiLMkG333ixncR1xSi2yvkdiEz8vcR35gggWPRkdmQDhru7i", + }, + operations: [ Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 0, network_index: None, }, related_operations: None, - type: "payment_source_dec", + type: "fee_receiver_inc", status: Some( "Success", ), @@ -169,12 +210,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "2000000000", + value: "1220000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -186,26 +231,39 @@ Some( coin_change: None, metadata: None, }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "fee_transfer:20:0:5JvB3cwSEui2izjshaT9vLStF1R34VydAt7rx7ttSQPwnht3X1Mw", + }, + operations: [ Operation { operation_identifier: OperationIdentifier { - index: 3, + index: 0, network_index: None, }, related_operations: None, - type: "payment_receiver_inc", + type: "fee_receiver_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qoy1wCYLMeUydbjcX24XZt9N2UeU8V48SDMpAnp6kjaofyY563P6", + address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "2000000000", + value: "980000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -223,7 +281,7 @@ Some( }, Transaction { transaction_identifier: TransactionIdentifier { - hash: "5Jttb21koii7SLVPDkTbYuVtfrRWaoWEQ4ZWfhwZ9xcx4UMiHwT9", + hash: "5Jue65F8EuA9en5HGEpuGSFZy4ezMNJyoLNsDQ8cmwnbaRZgZUSS", }, operations: [ Operation { @@ -240,12 +298,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "100000000", + value: "-100000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -259,7 +321,7 @@ Some( }, Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 1, network_index: None, }, related_operations: None, @@ -271,12 +333,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "2000000000", + value: "-2000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -290,19 +356,30 @@ Some( }, Operation { operation_identifier: OperationIdentifier { - index: 3, + index: 2, network_index: None, }, - related_operations: None, + related_operations: Some( + [ + OperationIdentifier { + index: 1, + network_index: None, + }, + ], + ), type: "payment_receiver_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qpYCQrD9qxKcQF2Ja2bPTtfJ2Zj3SsVsgaivT515NknHsimg63CX", + address: "B62qkCHcbWjMDWTyVL21wWsRv7oV8XyfYwVKTNtCC7rM3qTit8jNavD", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( @@ -321,20 +398,24 @@ Some( }, ], related_transactions: None, - metadata: None, + metadata: Some( + Object { + "nonce": Number(258932), + }, + ), }, Transaction { transaction_identifier: TransactionIdentifier { - hash: "5JucW5kgMKJwU46GZf7NHtMmHHmTQHrJPqZZk8owP6C2tnDr45vr", + hash: "5JvMQzS8pWjMaAv9VVGAQFWsEvEDeeVaENybCNSEtSLVM4Kjj6Z8", }, operations: [ Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 0, network_index: None, }, related_operations: None, - type: "fee_receiver_inc", + type: "fee_payment", status: Some( "Success", ), @@ -342,12 +423,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "10000000", + value: "-100000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -361,24 +446,28 @@ Some( }, Operation { operation_identifier: OperationIdentifier { - index: 3, + index: 1, network_index: None, }, related_operations: None, - type: "fee_payer_dec", + type: "payment_source_dec", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", + address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "10000000", + value: "-2000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -390,35 +479,37 @@ Some( coin_change: None, metadata: None, }, - ], - related_transactions: None, - metadata: None, - }, - Transaction { - transaction_identifier: TransactionIdentifier { - hash: "5Junx6hHb9g6ZWkS8zFFSv9yXvWK885U18JVKANatKYtbG7CubE6", - }, - operations: [ Operation { operation_identifier: OperationIdentifier { index: 2, network_index: None, }, - related_operations: None, - type: "fee_receiver_inc", + related_operations: Some( + [ + OperationIdentifier { + index: 1, + network_index: None, + }, + ], + ), + type: "payment_receiver_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", + address: "B62qoy1wCYLMeUydbjcX24XZt9N2UeU8V48SDMpAnp6kjaofyY563P6", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "800000000", + value: "2000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -432,33 +523,41 @@ Some( }, ], related_transactions: None, - metadata: None, + metadata: Some( + Object { + "nonce": Number(258934), + }, + ), }, Transaction { transaction_identifier: TransactionIdentifier { - hash: "5JuUi8aafVghGFo76NKsQdb4MrgpxyVSvJAsef7G9XefW9HttAsY", + hash: "5Jttb21koii7SLVPDkTbYuVtfrRWaoWEQ4ZWfhwZ9xcx4UMiHwT9", }, operations: [ Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 0, network_index: None, }, related_operations: None, - type: "coinbase_inc", + type: "fee_payment", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", + address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "720000000000", + value: "-100000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -470,22 +569,13 @@ Some( coin_change: None, metadata: None, }, - ], - related_transactions: None, - metadata: None, - }, - Transaction { - transaction_identifier: TransactionIdentifier { - hash: "5Jv4UiLMkG333ixncR1xSi2yvkdiEz8vcR35gggWPRkdmQDhru7i", - }, - operations: [ Operation { operation_identifier: OperationIdentifier { - index: 2, + index: 1, network_index: None, }, related_operations: None, - type: "fee_receiver_inc", + type: "payment_source_dec", status: Some( "Success", ), @@ -493,12 +583,16 @@ Some( AccountIdentifier { address: "B62qpfgnUm7zVqi8MJHNB2m37rtgMNDbFNhC2DpMmmVpQt8x6gKv9Ww", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "1220000000", + value: "-2000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -510,35 +604,37 @@ Some( coin_change: None, metadata: None, }, - ], - related_transactions: None, - metadata: None, - }, - Transaction { - transaction_identifier: TransactionIdentifier { - hash: "5JvB3cwSEui2izjshaT9vLStF1R34VydAt7rx7ttSQPwnht3X1Mw", - }, - operations: [ Operation { operation_identifier: OperationIdentifier { index: 2, network_index: None, }, - related_operations: None, - type: "fee_receiver_inc", + related_operations: Some( + [ + OperationIdentifier { + index: 1, + network_index: None, + }, + ], + ), + type: "payment_receiver_inc", status: Some( "Success", ), account: Some( AccountIdentifier { - address: "B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk", + address: "B62qpYCQrD9qxKcQF2Ja2bPTtfJ2Zj3SsVsgaivT515NknHsimg63CX", sub_account: None, - metadata: None, + metadata: Some( + Object { + "token_id": String("wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"), + }, + ), }, ), amount: Some( Amount { - value: "980000000", + value: "2000000000", currency: Currency { symbol: "MINA", decimals: 9, @@ -552,7 +648,11 @@ Some( }, ], related_transactions: None, - metadata: None, + metadata: Some( + Object { + "nonce": Number(258936), + }, + ), }, Transaction { transaction_identifier: TransactionIdentifier { @@ -2265,7 +2365,11 @@ Some( metadata: None, }, ], - metadata: None, + metadata: Some( + Object { + "creator": String("B62qkUHaJUHERZuCHQhXCQ8xsGBqyYSgjQsKnKN5HhSJecakuJ4pYyk"), + }, + ), }, ), other_transactions: None,