From eb94f3f085ef7b558e179bce611987a9c83fe6ec Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Fri, 27 Sep 2024 09:28:03 -0400 Subject: [PATCH 01/20] more work on block endpoint --- src/api.rs | 2 ++ src/api/block.rs | 65 +++++++++++++++++++++++++++--------------------- src/api/types.rs | 10 ++++++++ src/error.rs | 4 +-- tests/block.rs | 21 +++++++++++----- 5 files changed, 66 insertions(+), 36 deletions(-) create mode 100644 src/api/types.rs diff --git a/src/api.rs b/src/api.rs index b68b12a..a61ef5f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ mod network_health_check; mod network_list; mod network_options; mod network_status; +mod types; pub use account_balance::*; pub use block::*; @@ -32,3 +33,4 @@ pub use mempool_transaction::*; pub use network_list::*; pub use network_options::*; pub use network_status::*; +pub use types::*; diff --git a/src/api/block.rs b/src/api/block.rs index 8ea8f17..95476cd 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,21 +1,14 @@ use anyhow::Result; use cynic::QueryBuilder; pub use mesh::models::{BlockRequest, BlockResponse, PartialBlockIdentifier}; +use serde::Serialize; use crate::{ - graphql::{QueryBlockTransactions, QueryBlockTransactionsVariables}, - MinaMesh, MinaMeshError, Wrapper, + graphql::{QueryBlockTransactions, QueryBlockTransactionsVariables, UserCommand}, + ChainStatus, MinaMesh, MinaMeshError, Wrapper, }; -#[derive(sqlx::Type, Debug, PartialEq, Eq)] -#[sqlx(type_name = "chain_status_type", rename_all = "lowercase")] -enum ChainStatus { - Canonical, - Pending, - Orphaned, -} - -#[derive(Debug, PartialEq, Eq, sqlx::FromRow)] +#[derive(Debug, PartialEq, Eq, sqlx::FromRow, Serialize)] pub struct BlockMetadata { id: i32, block_winner_id: i32, @@ -44,39 +37,55 @@ pub struct BlockMetadata { /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 impl MinaMesh { - pub async fn block(&self, request: BlockRequest) -> Result { + // pub async fn block(&self, request: BlockRequest) -> Result { + pub async fn block(&self, request: BlockRequest) -> Result { let block_identifier = *request.block_identifier; let metadata = match self.block_metadata(&block_identifier).await? { Some(metadata) => metadata, None => return Err(MinaMeshError::BlockMissing(Wrapper(&block_identifier).to_string())), }; - let block_transactions = self - .graphql_client - .send(QueryBlockTransactions::build(QueryBlockTransactionsVariables { state_hash: Some(&metadata.state_hash) })) - .await - .map_err(|_| MinaMeshError::ChainInfoMissing)?; - println!("block_transactions: {:?}", block_transactions); - unimplemented!() + Ok(metadata) - // Fetch transactions from DB - // Internal commands, user commands, and zkapps commands + // let user_commands = self.user_commands(&metadata).await?; + // println!("block_transactions: {:?}", user_commands); - // SQL command -> Rosetta/mesh transaction - // Each command will originate multiple atomic Rosetta/mesh operations + // // TODO: what else here?: + // // - Fetch transactions from DB + // // - SQL command -> Rosetta/mesh transaction + // // - Each command will originate multiple atomic Rosetta/mesh + // operations - // Populate the block response from the fetched metadata, if any. + // let transactions = user_commands + // .into_iter() + // .map(|user_commands| + // Transaction::new(TransactionIdentifier::new(user_commands.hash.0), + // vec![])) .collect(); // Ok(BlockResponse { // block: Some(Box::new(Block::new( - // BlockIdentifier::new(0, "".to_string()), - // BlockIdentifier::new(0, "".to_string()), - // 0, - // vec![], + // BlockIdentifier::new(metadata.height, metadata.state_hash), + // // TODO: parent block height + // BlockIdentifier::new(0, metadata.parent_hash), + // metadata.timestamp.parse()?, + // transactions, // ))), // other_transactions: Some(vec![]), // }) } + // Do we also need internal and zkapps commands? + pub async fn user_commands( + &self, + BlockMetadata { state_hash, .. }: &BlockMetadata, + ) -> Result, MinaMeshError> { + let QueryBlockTransactions { block } = self + .graphql_client + .send(QueryBlockTransactions::build(QueryBlockTransactionsVariables { state_hash: Some(state_hash) })) + .await?; + Ok(block.transactions.user_commands) + } + pub async fn block_metadata( &self, PartialBlockIdentifier { index, hash }: &PartialBlockIdentifier, diff --git a/src/api/types.rs b/src/api/types.rs new file mode 100644 index 0000000..5e33527 --- /dev/null +++ b/src/api/types.rs @@ -0,0 +1,10 @@ +use serde::Serialize; +use sqlx::Type; + +#[derive(Type, Debug, PartialEq, Eq, Serialize)] +#[sqlx(type_name = "chain_status_type", rename_all = "lowercase")] +pub enum ChainStatus { + Canonical, + Pending, + Orphaned, +} diff --git a/src/error.rs b/src/error.rs index 2da2857..9cc6810 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use cynic::http::CynicReqwestError; use sqlx::Error as SqlxError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum MinaMeshError { #[error("SQL failure")] Sql(String), @@ -85,7 +85,7 @@ pub enum MinaMeshError { TransactionSubmitExpired, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum PartialReason { LengthMismatch, FeePayerAndSourceMismatch, diff --git a/tests/block.rs b/tests/block.rs index 2d309e4..a1dd68f 100644 --- a/tests/block.rs +++ b/tests/block.rs @@ -2,15 +2,19 @@ use std::sync::OnceLock; use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; -use mina_mesh::{BlockMetadata, MinaMeshConfig, PartialBlockIdentifier}; +use mina_mesh::{ + BlockRequest, BlockResponse, MinaMeshConfig, MinaMeshError, NetworkIdentifier, PartialBlockIdentifier, +}; #[tokio::test] async fn specified() -> Result<()> { let mina_mesh = MinaMeshConfig::from_env().to_mina_mesh().await?; - let mut metadata_futures = - specified_identifiers().iter().map(|item| mina_mesh.block_metadata(item)).collect::>(); - let mut maybe_prev: Option> = None; - while let Some(Ok(resolved)) = metadata_futures.next().await { + let mut futures = specified_identifiers() + .iter() + .map(|item| mina_mesh.block(BlockRequest::new(network_identifier().to_owned(), item.to_owned()))) + .collect::>(); + let mut maybe_prev: Option> = None; + while let Some(resolved) = futures.next().await { if let Some(prev) = maybe_prev { assert_eq!(prev, resolved); } @@ -36,12 +40,17 @@ fn specified_identifiers() -> &'static [PartialBlockIdentifier; 3] { }) } +fn network_identifier() -> &'static NetworkIdentifier { + static NETWORK_IDENTIFIER: OnceLock = OnceLock::new(); + NETWORK_IDENTIFIER.get_or_init(|| NetworkIdentifier::new("mina".to_string(), "mainnet".to_string())) +} + #[tokio::test] async fn unspecified() -> Result<()> { let response = MinaMeshConfig::from_env() .to_mina_mesh() .await? - .block_metadata(&PartialBlockIdentifier { hash: None, index: None }) + .block(BlockRequest::new(network_identifier().to_owned(), PartialBlockIdentifier::new())) .await; assert!(response.is_ok()); Ok(()) From fa7b53c6af76596915b037d4a233a1ec8f64a314 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Fri, 27 Sep 2024 09:30:28 -0400 Subject: [PATCH 02/20] network_list tweak --- src/api/network_list.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/api/network_list.rs b/src/api/network_list.rs index 6ef4656..5764708 100644 --- a/src/api/network_list.rs +++ b/src/api/network_list.rs @@ -8,15 +8,10 @@ use crate::{graphql::QueryNetworkId, MinaMesh}; impl MinaMesh { pub async fn network_list(&self) -> Result { let QueryNetworkId { network_id } = self.graphql_client.send(QueryNetworkId::build(())).await?; - - // Split the network_id into chain_id and network_id by the colon ':' - let parts: Vec<&str> = network_id.split(':').collect(); - - let (chain_id, network_id) = match parts.as_slice() { - [chain, network] => (chain.to_string(), network.to_string()), - _ => ("unknown".to_string(), "unknown".to_string()), - }; - + let (chain_id, network_id) = network_id.split_once(':').map_or_else( + || ("unknown".to_string(), "unknown".to_string()), + |(chain, network)| (chain.to_string(), network.to_string()), + ); Ok(NetworkListResponse::new(vec![NetworkIdentifier::new(chain_id, network_id)])) } } From 1e4daaa03e361491e81812aae3a03da3e7b10e01 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Mon, 30 Sep 2024 10:32:59 -0400 Subject: [PATCH 03/20] more --- ...bba3379651cfdfebeacb1c1f603fc55f3e320.json | 139 +++++++++++++++ ...b267ed9289096a665a13eeabd4b0dae1fc59a.json | 165 ++++++++++++++++++ sql/query_id.sql | 22 ++- sql/user_commands.sql | 19 +- src/api/block.rs | 146 +++++++++++++--- src/api/types.rs | 14 ++ 6 files changed, 477 insertions(+), 28 deletions(-) create mode 100644 .sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json create mode 100644 .sqlx/query-54a347698cebe4f8696a0ba51c9b267ed9289096a665a13eeabd4b0dae1fc59a.json diff --git a/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json b/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json new file mode 100644 index 0000000..b72c684 --- /dev/null +++ b/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json @@ -0,0 +1,139 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n u.id,\n u.command_type AS \"command_type: CommandType\",\n u.fee_payer_id,\n u.source_id,\n u.receiver_id,\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,\n ac.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": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "command_type: CommandType", + "type_info": { + "Custom": { + "name": "user_command_type", + "kind": { + "Enum": [ + "payment", + "delegation" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "fee_payer_id", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "source_id", + "type_info": "Int4" + }, + { + "ordinal": 4, + "name": "receiver_id", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "nonce", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "amount", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "fee", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "valid_until", + "type_info": "Int8" + }, + { + "ordinal": 9, + "name": "memo", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "hash", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "fee_payer", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "source", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "receiver", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "status: TransactionStatus", + "type_info": { + "Custom": { + "name": "transaction_status", + "kind": { + "Enum": [ + "applied", + "failed" + ] + } + } + } + }, + { + "ordinal": 15, + "name": "failure_reason", + "type_info": "Text" + }, + { + "ordinal": 16, + "name": "creation_fee", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + false, + false, + false, + true, + false + ] + }, + "hash": "476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320" +} diff --git a/.sqlx/query-54a347698cebe4f8696a0ba51c9b267ed9289096a665a13eeabd4b0dae1fc59a.json b/.sqlx/query-54a347698cebe4f8696a0ba51c9b267ed9289096a665a13eeabd4b0dae1fc59a.json new file mode 100644 index 0000000..6d50714 --- /dev/null +++ b/.sqlx/query-54a347698cebe4f8696a0ba51c9b267ed9289096a665a13eeabd4b0dae1fc59a.json @@ -0,0 +1,165 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n b.block_winner_id,\n b.chain_status AS \"chain_status: ChainStatus\",\n b.creator_id,\n b.global_slot_since_genesis,\n b.global_slot_since_hard_fork,\n b.height,\n b.id,\n b.last_vrf_output,\n b.ledger_hash,\n b.min_window_density,\n b.next_epoch_data_id,\n b.parent_hash,\n b.parent_id,\n b.proposed_protocol_version_id,\n b.protocol_version_id,\n b.snarked_ledger_hash_id,\n b.staking_epoch_data_id,\n b.state_hash,\n b.sub_window_densities,\n b.timestamp,\n b.total_currency,\n pk.value AS creator,\n bw.value AS winner\nFROM\n blocks b\n INNER JOIN public_keys pk ON pk.id=b.creator_id\n INNER JOIN public_keys bw ON bw.id=b.block_winner_id\nWHERE\n b.id=$1\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "block_winner_id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "chain_status: ChainStatus", + "type_info": { + "Custom": { + "name": "chain_status_type", + "kind": { + "Enum": [ + "canonical", + "orphaned", + "pending" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "creator_id", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "global_slot_since_genesis", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "global_slot_since_hard_fork", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "height", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 7, + "name": "last_vrf_output", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "ledger_hash", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "min_window_density", + "type_info": "Int8" + }, + { + "ordinal": 10, + "name": "next_epoch_data_id", + "type_info": "Int4" + }, + { + "ordinal": 11, + "name": "parent_hash", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "parent_id", + "type_info": "Int4" + }, + { + "ordinal": 13, + "name": "proposed_protocol_version_id", + "type_info": "Int4" + }, + { + "ordinal": 14, + "name": "protocol_version_id", + "type_info": "Int4" + }, + { + "ordinal": 15, + "name": "snarked_ledger_hash_id", + "type_info": "Int4" + }, + { + "ordinal": 16, + "name": "staking_epoch_data_id", + "type_info": "Int4" + }, + { + "ordinal": 17, + "name": "state_hash", + "type_info": "Text" + }, + { + "ordinal": 18, + "name": "sub_window_densities", + "type_info": "Int8Array" + }, + { + "ordinal": 19, + "name": "timestamp", + "type_info": "Text" + }, + { + "ordinal": 20, + "name": "total_currency", + "type_info": "Text" + }, + { + "ordinal": 21, + "name": "creator", + "type_info": "Text" + }, + { + "ordinal": 22, + "name": "winner", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "54a347698cebe4f8696a0ba51c9b267ed9289096a665a13eeabd4b0dae1fc59a" +} diff --git a/sql/query_id.sql b/sql/query_id.sql index 03ae000..626e891 100644 --- a/sql/query_id.sql +++ b/sql/query_id.sql @@ -1,5 +1,25 @@ SELECT - b.*, + b.block_winner_id, + b.chain_status AS "chain_status: ChainStatus", + b.creator_id, + b.global_slot_since_genesis, + b.global_slot_since_hard_fork, + b.height, + b.id, + b.last_vrf_output, + b.ledger_hash, + b.min_window_density, + b.next_epoch_data_id, + b.parent_hash, + b.parent_id, + b.proposed_protocol_version_id, + b.protocol_version_id, + b.snarked_ledger_hash_id, + b.staking_epoch_data_id, + b.state_hash, + b.sub_window_densities, + b.timestamp, + b.total_currency, pk.value AS creator, bw.value AS winner FROM diff --git a/sql/user_commands.sql b/sql/user_commands.sql index 9ce4571..8729eca 100644 --- a/sql/user_commands.sql +++ b/sql/user_commands.sql @@ -1,9 +1,19 @@ SELECT - u.*, + u.id, + u.command_type AS "command_type: CommandType", + u.fee_payer_id, + u.source_id, + u.receiver_id, + u.nonce, + u.amount, + u.fee, + u.valid_until, + u.memo, + u.hash, pk_payer.value AS fee_payer, pk_source.value AS source, pk_receiver.value AS receiver, - buc.status, + buc.status AS "status: TransactionStatus", buc.failure_reason, ac.creation_fee FROM @@ -48,7 +58,4 @@ FROM LEFT JOIN tokens AS t ON ai_receiver.token_id=t.id WHERE buc.block_id=$1 - AND ( - t.value=$2 - OR t.id IS NULL - ) + AND t.value=$2 diff --git a/src/api/block.rs b/src/api/block.rs index 95476cd..15c486e 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,14 +1,12 @@ use anyhow::Result; -use cynic::QueryBuilder; +use mesh::models::{Block, BlockIdentifier, Operation, OperationIdentifier, Transaction, TransactionIdentifier}; pub use mesh::models::{BlockRequest, BlockResponse, PartialBlockIdentifier}; use serde::Serialize; +use sqlx::FromRow; -use crate::{ - graphql::{QueryBlockTransactions, QueryBlockTransactionsVariables, UserCommand}, - ChainStatus, MinaMesh, MinaMeshError, Wrapper, -}; +use crate::{ChainStatus, CommandType, MinaMesh, MinaMeshError, TransactionStatus, Wrapper}; -#[derive(Debug, PartialEq, Eq, sqlx::FromRow, Serialize)] +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] pub struct BlockMetadata { id: i32, block_winner_id: i32, @@ -35,23 +33,53 @@ pub struct BlockMetadata { winner: String, } +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] +pub struct UserCommandMetadata { + id: i32, + command_type: CommandType, + fee_payer_id: i32, + source_id: i32, + receiver_id: i32, + 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, +} + /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 impl MinaMesh { // pub async fn block(&self, request: BlockRequest) -> Result { - pub async fn block(&self, request: BlockRequest) -> Result { + pub async fn block(&self, request: BlockRequest) -> Result { let block_identifier = *request.block_identifier; let metadata = match self.block_metadata(&block_identifier).await? { Some(metadata) => metadata, None => return Err(MinaMeshError::BlockMissing(Wrapper(&block_identifier).to_string())), }; - Ok(metadata) - - // let user_commands = self.user_commands(&metadata).await?; - // println!("block_transactions: {:?}", user_commands); + let parent_block_metadata = sqlx::query_file_as!(BlockMetadata, "sql/query_id.sql", metadata.parent_id.unwrap()) + .fetch_optional(&self.pg_pool) + .await?; + let user_commands = self.user_commands(&metadata).await?; + Ok(BlockResponse { + block: Some(Box::new(Block::new( + BlockIdentifier::new(metadata.height, metadata.state_hash), + BlockIdentifier::new(parent_block_metadata.height, parent_block_metadata.state_hash), + metadata.timestamp.parse()?, + user_commands, + ))), + other_transactions: None, + }) // // TODO: what else here?: - // // - Fetch transactions from DB + // // - fetch user commands from the database // // - SQL command -> Rosetta/mesh transaction // // - Each command will originate multiple atomic Rosetta/mesh // operations @@ -74,16 +102,92 @@ impl MinaMesh { // }) } + fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { + let operations = Vec::new(); + if metadata.fee != "0" { + operations.push(Operation { + operation_identifier: OperationIdentifier::new(0), + amount: metadata.fee, + account: AccountIdentifier::new(metadata.fee_payer), + status: metadata.status, + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + } + match metadata.failure_reason { + Some(failure_reason) => {} + None => { + match metadata.creation_fee { + Some(creation_fee) => { + operations.push(Operation { + operation_identifier: OperationIdentifier::new(1), + amount: metadata.creation_fee, + account: AccountIdentifier::new(metadata.receiver), + status: metadata.status, + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + } + None => {} + } + match metadata.command_type { + CommandType::Delegation => { + operations.push(Operation { + operation_identifier: OperationIdentifier::new(2), + amount: None, + account: AccountIdentifier::new(metadata.source), + status: metadata.status, + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + } + CommandType::Payment => { + operations.push(Operation { + operation_identifier: OperationIdentifier::new(2), + amount: metadata.amount, + account: AccountIdentifier::new(metadata.source), + status: metadata.status, + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + operations.push(Operation { + operation_identifier: OperationIdentifier::new(3), + amount: metadata.amount, + account: AccountIdentifier::new(metadata.receiver), + status: metadata.status, + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + } + } + } + } + } + // Do we also need internal and zkapps commands? - pub async fn user_commands( - &self, - BlockMetadata { state_hash, .. }: &BlockMetadata, - ) -> Result, MinaMeshError> { - let QueryBlockTransactions { block } = self - .graphql_client - .send(QueryBlockTransactions::build(QueryBlockTransactionsVariables { state_hash: Some(state_hash) })) - .await?; - Ok(block.transactions.user_commands) + pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { + sqlx::query_file_as!( + UserCommandMetadata, + "sql/user_commands.sql", + metadata.id, + "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" /* TODO: use default token value, check how to best + * handle this */ + ) + .fetch_all(&self.pg_pool) + .await? + .into_iter() + .map(|x| Transaction::new(TransactionIdentifier::new(x.hash), user_command_metadata_to_operations(x))) + .collect() } pub async fn block_metadata( diff --git a/src/api/types.rs b/src/api/types.rs index 5e33527..140e9ba 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -8,3 +8,17 @@ pub enum ChainStatus { Pending, Orphaned, } + +#[derive(Type, Debug, PartialEq, Eq, Serialize)] +#[sqlx(type_name = "command_type", rename_all = "lowercase")] +pub enum CommandType { + Payment, + Delegation, +} + +#[derive(Type, Debug, PartialEq, Eq, Serialize)] +#[sqlx(type_name = "transaction_status", rename_all = "lowercase")] +pub enum TransactionStatus { + Applied, + Failed, +} From 3321ab1d6023adb925539fc974b95d5acd8b1cb8 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Mon, 30 Sep 2024 12:49:43 -0400 Subject: [PATCH 04/20] fix compile errors --- src/api/block.rs | 124 ++++++++++++++++++++++++++--------------------- src/api/types.rs | 10 ++++ src/util.rs | 2 +- 3 files changed, 81 insertions(+), 55 deletions(-) diff --git a/src/api/block.rs b/src/api/block.rs index 15c486e..ac2723d 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,6 +1,9 @@ use anyhow::Result; -use mesh::models::{Block, BlockIdentifier, Operation, OperationIdentifier, Transaction, TransactionIdentifier}; -pub use mesh::models::{BlockRequest, BlockResponse, PartialBlockIdentifier}; +use mesh::models::{ + AccountIdentifier, Block, BlockIdentifier, Currency, Operation, OperationIdentifier, Transaction, + TransactionIdentifier, +}; +pub use mesh::models::{Amount, BlockRequest, BlockResponse, PartialBlockIdentifier}; use serde::Serialize; use sqlx::FromRow; @@ -59,19 +62,27 @@ impl MinaMesh { // pub async fn block(&self, request: BlockRequest) -> Result { pub async fn block(&self, request: BlockRequest) -> Result { - let block_identifier = *request.block_identifier; - let metadata = match self.block_metadata(&block_identifier).await? { + let partial_block_identifier = *request.block_identifier; + let metadata = match self.block_metadata(&partial_block_identifier).await? { Some(metadata) => metadata, - None => return Err(MinaMeshError::BlockMissing(Wrapper(&block_identifier).to_string())), + None => return Err(MinaMeshError::BlockMissing(Wrapper(&partial_block_identifier).to_string())), + }; + let parent_block_metadata = match &metadata.parent_id { + Some(parent_id) => { + sqlx::query_file_as!(BlockMetadata, "sql/query_id.sql", parent_id).fetch_optional(&self.pg_pool).await? + } + None => None, + }; + let block_identifier = BlockIdentifier::new(metadata.height, metadata.state_hash.clone()); + let parent_block_identifier = match parent_block_metadata { + Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), + None => block_identifier.clone(), }; - let parent_block_metadata = sqlx::query_file_as!(BlockMetadata, "sql/query_id.sql", metadata.parent_id.unwrap()) - .fetch_optional(&self.pg_pool) - .await?; let user_commands = self.user_commands(&metadata).await?; Ok(BlockResponse { block: Some(Box::new(Block::new( - BlockIdentifier::new(metadata.height, metadata.state_hash), - BlockIdentifier::new(parent_block_metadata.height, parent_block_metadata.state_hash), + block_identifier, + parent_block_identifier, metadata.timestamp.parse()?, user_commands, ))), @@ -103,44 +114,41 @@ impl MinaMesh { } fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { - let operations = Vec::new(); + let mut operations = Vec::new(); if metadata.fee != "0" { operations.push(Operation { - operation_identifier: OperationIdentifier::new(0), - amount: metadata.fee, - account: AccountIdentifier::new(metadata.fee_payer), - status: metadata.status, + operation_identifier: Box::new(OperationIdentifier::new(0)), + amount: Wrapper(Some(metadata.fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.fee_payer.clone()))), + status: Some(metadata.status.to_string()), related_operations: None, coin_change: None, r#type: "".to_string(), // TODO: get the correct type metadata: None, // TODO: get the correct metadata }); } - match metadata.failure_reason { - Some(failure_reason) => {} + match &metadata.failure_reason { + Some(_failure_reason) => {} None => { - match metadata.creation_fee { - Some(creation_fee) => { - operations.push(Operation { - operation_identifier: OperationIdentifier::new(1), - amount: metadata.creation_fee, - account: AccountIdentifier::new(metadata.receiver), - status: metadata.status, - related_operations: None, - coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata - }); - } - None => {} + if let Some(creation_fee) = &metadata.creation_fee { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(1)), + amount: Wrapper(Some(creation_fee.to_owned())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(metadata.status.to_string()), + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); } match metadata.command_type { CommandType::Delegation => { operations.push(Operation { - operation_identifier: OperationIdentifier::new(2), + operation_identifier: Box::new(OperationIdentifier::new(2)), amount: None, - account: AccountIdentifier::new(metadata.source), - status: metadata.status, + account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), + status: Some(metadata.status.to_string()), related_operations: None, coin_change: None, r#type: "".to_string(), // TODO: get the correct type @@ -149,45 +157,53 @@ impl MinaMesh { } CommandType::Payment => { operations.push(Operation { - operation_identifier: OperationIdentifier::new(2), - amount: metadata.amount, - account: AccountIdentifier::new(metadata.source), - status: metadata.status, + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: Wrapper(metadata.amount.clone()).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), + status: Some(metadata.status.to_string()), related_operations: None, coin_change: None, r#type: "".to_string(), // TODO: get the correct type metadata: None, // TODO: get the correct metadata }); operations.push(Operation { - operation_identifier: OperationIdentifier::new(3), - amount: metadata.amount, - account: AccountIdentifier::new(metadata.receiver), - status: metadata.status, + operation_identifier: Box::new(OperationIdentifier::new(3)), + amount: Wrapper(metadata.amount.clone()).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(metadata.status.to_string()), related_operations: None, coin_change: None, r#type: "".to_string(), // TODO: get the correct type metadata: None, // TODO: get the correct metadata }); } - } + }; } } + operations } // Do we also need internal and zkapps commands? pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { - sqlx::query_file_as!( - UserCommandMetadata, - "sql/user_commands.sql", - metadata.id, - "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" /* TODO: use default token value, check how to best - * handle this */ + Ok( + sqlx::query_file_as!( + UserCommandMetadata, + "sql/user_commands.sql", + metadata.id, + "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" /* TODO: use default token value, check how to best + * handle this */ + ) + .fetch_all(&self.pg_pool) + .await? + .into_iter() + .map(|item| { + Transaction::new( + TransactionIdentifier::new(item.hash.clone()), + Self::user_command_metadata_to_operations(&item), + ) + }) + .collect(), ) - .fetch_all(&self.pg_pool) - .await? - .into_iter() - .map(|x| Transaction::new(TransactionIdentifier::new(x.hash), user_command_metadata_to_operations(x))) - .collect() } pub async fn block_metadata( diff --git a/src/api/types.rs b/src/api/types.rs index 140e9ba..e61ce29 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -22,3 +22,13 @@ pub enum TransactionStatus { Applied, Failed, } + +impl ToString for TransactionStatus { + fn to_string(&self) -> String { + match self { + Self::Applied => "Applied", + Self::Failed => "Failed", + } + .to_string() + } +} diff --git a/src/util.rs b/src/util.rs index 3b001cd..b718b19 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,7 +4,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use mesh::models::PartialBlockIdentifier; +use mesh::models::{Amount, Currency, PartialBlockIdentifier}; use serde::Serialize; use crate::MinaMeshError; From 9a7457620d3c41e53c2af8bab371060b09ad54c2 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 1 Oct 2024 14:48:00 -0400 Subject: [PATCH 05/20] more cleanup --- src/api/block.rs | 236 +++++++++++++++++++++-------------------------- 1 file changed, 103 insertions(+), 133 deletions(-) diff --git a/src/api/block.rs b/src/api/block.rs index ac2723d..e5e9f09 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,14 +1,85 @@ use anyhow::Result; use mesh::models::{ - AccountIdentifier, Block, BlockIdentifier, Currency, Operation, OperationIdentifier, Transaction, - TransactionIdentifier, + AccountIdentifier, Block, BlockIdentifier, Operation, OperationIdentifier, Transaction, TransactionIdentifier, }; -pub use mesh::models::{Amount, BlockRequest, BlockResponse, PartialBlockIdentifier}; +pub use mesh::models::{BlockRequest, BlockResponse, PartialBlockIdentifier}; use serde::Serialize; use sqlx::FromRow; use crate::{ChainStatus, CommandType, MinaMesh, MinaMeshError, TransactionStatus, Wrapper}; +/// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 +impl MinaMesh { + pub async fn block(&self, request: BlockRequest) -> Result { + let partial_block_identifier = *request.block_identifier; + let metadata = match self.block_metadata(&partial_block_identifier).await? { + Some(metadata) => metadata, + None => return Err(MinaMeshError::BlockMissing(Wrapper(&partial_block_identifier).to_string())), + }; + let parent_block_metadata = match &metadata.parent_id { + Some(parent_id) => { + sqlx::query_file_as!(BlockMetadata, "sql/query_id.sql", parent_id).fetch_optional(&self.pg_pool).await? + } + None => None, + }; + let block_identifier = BlockIdentifier::new(metadata.height, metadata.state_hash.clone()); + let parent_block_identifier = match parent_block_metadata { + Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), + None => block_identifier.clone(), + }; + let user_commands = self.user_commands(&metadata).await?; + Ok(BlockResponse { + block: Some(Box::new(Block::new( + block_identifier, + parent_block_identifier, + metadata.timestamp.parse()?, + user_commands, + ))), + other_transactions: None, + }) + } + + // TODO: use default token value, check how to best handle this + pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { + Ok( + sqlx::query_file_as!( + UserCommandMetadata, + "sql/user_commands.sql", + metadata.id, + // cspell:disable-next-line + "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" + ) + .fetch_all(&self.pg_pool) + .await? + .into_iter() + .map(|item| Transaction::new(TransactionIdentifier::new(item.hash.clone()), Wrapper(&item).into())) + .collect(), + ) + } + + pub async fn block_metadata( + &self, + PartialBlockIdentifier { index, hash }: &PartialBlockIdentifier, + ) -> Result, sqlx::Error> { + if let (Some(index), Some(hash)) = (&index, &hash) { + sqlx::query_file_as!(BlockMetadata, "sql/query_both.sql", hash.to_string(), index) + .fetch_optional(&self.pg_pool) + .await + } else if let Some(index) = index { + let record = sqlx::query_file!("sql/max_canonical_height.sql").fetch_one(&self.pg_pool).await?; + if index <= &record.max_canonical_height.unwrap() { + sqlx::query_file_as!(BlockMetadata, "sql/query_canonical.sql", index).fetch_optional(&self.pg_pool).await + } else { + sqlx::query_file_as!(BlockMetadata, "sql/query_pending.sql", index).fetch_optional(&self.pg_pool).await + } + } else if let Some(hash) = &hash { + sqlx::query_file_as!(BlockMetadata, "sql/query_hash.sql", hash).fetch_optional(&self.pg_pool).await + } else { + sqlx::query_file_as!(BlockMetadata, "sql/query_best.sql").fetch_optional(&self.pg_pool).await + } + } +} + #[derive(Debug, PartialEq, Eq, FromRow, Serialize)] pub struct BlockMetadata { id: i32, @@ -57,63 +128,8 @@ pub struct UserCommandMetadata { creation_fee: Option, } -/// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 -impl MinaMesh { - // pub async fn block(&self, request: BlockRequest) -> Result { - pub async fn block(&self, request: BlockRequest) -> Result { - let partial_block_identifier = *request.block_identifier; - let metadata = match self.block_metadata(&partial_block_identifier).await? { - Some(metadata) => metadata, - None => return Err(MinaMeshError::BlockMissing(Wrapper(&partial_block_identifier).to_string())), - }; - let parent_block_metadata = match &metadata.parent_id { - Some(parent_id) => { - sqlx::query_file_as!(BlockMetadata, "sql/query_id.sql", parent_id).fetch_optional(&self.pg_pool).await? - } - None => None, - }; - let block_identifier = BlockIdentifier::new(metadata.height, metadata.state_hash.clone()); - let parent_block_identifier = match parent_block_metadata { - Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), - None => block_identifier.clone(), - }; - let user_commands = self.user_commands(&metadata).await?; - Ok(BlockResponse { - block: Some(Box::new(Block::new( - block_identifier, - parent_block_identifier, - metadata.timestamp.parse()?, - user_commands, - ))), - other_transactions: None, - }) - - // // TODO: what else here?: - // // - fetch user commands from the database - // // - SQL command -> Rosetta/mesh transaction - // // - Each command will originate multiple atomic Rosetta/mesh - // operations - - // let transactions = user_commands - // .into_iter() - // .map(|user_commands| - // Transaction::new(TransactionIdentifier::new(user_commands.hash.0), - // vec![])) .collect(); - - // Ok(BlockResponse { - // block: Some(Box::new(Block::new( - // BlockIdentifier::new(metadata.height, metadata.state_hash), - // // TODO: parent block height - // BlockIdentifier::new(0, metadata.parent_hash), - // metadata.timestamp.parse()?, - // transactions, - // ))), - // other_transactions: Some(vec![]), - // }) - } - - fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { +impl From> for Vec { + fn from(Wrapper(metadata): Wrapper<&UserCommandMetadata>) -> Self { let mut operations = Vec::new(); if metadata.fee != "0" { operations.push(Operation { @@ -127,14 +143,25 @@ impl MinaMesh { metadata: None, // TODO: get the correct metadata }); } - match &metadata.failure_reason { - Some(_failure_reason) => {} - None => { - if let Some(creation_fee) = &metadata.creation_fee { + if metadata.failure_reason.is_none() { + if let Some(creation_fee) = &metadata.creation_fee { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(1)), + amount: Wrapper(Some(creation_fee.to_owned())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(metadata.status.to_string()), + related_operations: None, + coin_change: None, + r#type: "".to_string(), // TODO: get the correct type + metadata: None, // TODO: get the correct metadata + }); + } + match metadata.command_type { + CommandType::Delegation => { operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(1)), - amount: Wrapper(Some(creation_fee.to_owned())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: None, + account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), status: Some(metadata.status.to_string()), related_operations: None, coin_change: None, @@ -142,21 +169,9 @@ impl MinaMesh { metadata: None, // TODO: get the correct metadata }); } - match metadata.command_type { - CommandType::Delegation => { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: None, - account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), - status: Some(metadata.status.to_string()), - related_operations: None, - coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata - }); - } - CommandType::Payment => { - operations.push(Operation { + CommandType::Payment => { + operations.extend_from_slice(&[ + Operation { operation_identifier: Box::new(OperationIdentifier::new(2)), amount: Wrapper(metadata.amount.clone()).into(), account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), @@ -165,8 +180,8 @@ impl MinaMesh { coin_change: None, r#type: "".to_string(), // TODO: get the correct type metadata: None, // TODO: get the correct metadata - }); - operations.push(Operation { + }, + Operation { operation_identifier: Box::new(OperationIdentifier::new(3)), amount: Wrapper(metadata.amount.clone()).into(), account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), @@ -175,56 +190,11 @@ impl MinaMesh { coin_change: None, r#type: "".to_string(), // TODO: get the correct type metadata: None, // TODO: get the correct metadata - }); - } - }; - } + }, + ]); + } + }; } operations } - - // Do we also need internal and zkapps commands? - pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { - Ok( - sqlx::query_file_as!( - UserCommandMetadata, - "sql/user_commands.sql", - metadata.id, - "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" /* TODO: use default token value, check how to best - * handle this */ - ) - .fetch_all(&self.pg_pool) - .await? - .into_iter() - .map(|item| { - Transaction::new( - TransactionIdentifier::new(item.hash.clone()), - Self::user_command_metadata_to_operations(&item), - ) - }) - .collect(), - ) - } - - pub async fn block_metadata( - &self, - PartialBlockIdentifier { index, hash }: &PartialBlockIdentifier, - ) -> Result, sqlx::Error> { - if let (Some(index), Some(hash)) = (&index, &hash) { - sqlx::query_file_as!(BlockMetadata, "sql/query_both.sql", hash.to_string(), index) - .fetch_optional(&self.pg_pool) - .await - } else if let Some(index) = index { - let record = sqlx::query_file!("sql/max_canonical_height.sql").fetch_one(&self.pg_pool).await?; - if index <= &record.max_canonical_height.unwrap() { - sqlx::query_file_as!(BlockMetadata, "sql/query_canonical.sql", index).fetch_optional(&self.pg_pool).await - } else { - sqlx::query_file_as!(BlockMetadata, "sql/query_pending.sql", index).fetch_optional(&self.pg_pool).await - } - } else if let Some(hash) = &hash { - sqlx::query_file_as!(BlockMetadata, "sql/query_hash.sql", hash).fetch_optional(&self.pg_pool).await - } else { - sqlx::query_file_as!(BlockMetadata, "sql/query_best.sql").fetch_optional(&self.pg_pool).await - } - } } From 0789cc7d892a8424d07498a23862c05485204bcc Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 1 Oct 2024 14:51:32 -0400 Subject: [PATCH 06/20] fix clippy lint --- src/api/types.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/types.rs b/src/api/types.rs index e61ce29..000c15d 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::Serialize; use sqlx::Type; @@ -23,12 +25,11 @@ pub enum TransactionStatus { Failed, } -impl ToString for TransactionStatus { - fn to_string(&self) -> String { +impl Display for TransactionStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Applied => "Applied", - Self::Failed => "Failed", + Self::Applied => write!(f, "Applied"), + Self::Failed => write!(f, "Failed"), } - .to_string() } } From 874bad93ae93e9b743421050c84f91be5a3c5162 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 1 Oct 2024 15:06:58 -0400 Subject: [PATCH 07/20] attempt to fix unspecified test --- src/api/block.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/api/block.rs b/src/api/block.rs index e5e9f09..b0f5fbe 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -41,20 +41,20 @@ impl MinaMesh { // TODO: use default token value, check how to best handle this pub async fn user_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { - Ok( - sqlx::query_file_as!( - UserCommandMetadata, - "sql/user_commands.sql", - metadata.id, - // cspell:disable-next-line - "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" - ) - .fetch_all(&self.pg_pool) - .await? + let metadata = sqlx::query_file_as!( + UserCommandMetadata, + "sql/user_commands.sql", + metadata.id, + // cspell:disable-next-line + "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" + ) + .fetch_all(&self.pg_pool) + .await?; + let transactions = metadata .into_iter() .map(|item| Transaction::new(TransactionIdentifier::new(item.hash.clone()), Wrapper(&item).into())) - .collect(), - ) + .collect(); + Ok(transactions) } pub async fn block_metadata( From c808efb2e631c7a26588584c8edcee529ee07dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Santos=20Reis?= Date: Wed, 2 Oct 2024 14:48:24 +0100 Subject: [PATCH 08/20] Refactor SQL query to explicitly mark failure reason and creation fee in user_commands as nullable --- sql/user_commands.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/user_commands.sql b/sql/user_commands.sql index 8729eca..da35018 100644 --- a/sql/user_commands.sql +++ b/sql/user_commands.sql @@ -14,8 +14,8 @@ SELECT pk_source.value AS source, pk_receiver.value AS receiver, buc.status AS "status: TransactionStatus", - buc.failure_reason, - ac.creation_fee + buc.failure_reason AS "failure_reason?", + ac.creation_fee AS "creation_fee?" FROM user_commands AS u INNER JOIN blocks_user_commands AS buc ON u.id=buc.user_command_id From bb50e89db154e9d44fa68ffd88a5eef4ec93e960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Santos=20Reis?= Date: Wed, 2 Oct 2024 14:50:39 +0100 Subject: [PATCH 09/20] Refactor SQL query to remove unnecessary columns in user_commands.sql and user_command_metadata struct --- ...bba3379651cfdfebeacb1c1f603fc55f3e320.json | 139 ------------------ ...54776aa535ee86ee5da5393b9df832b463253.json | 115 +++++++++++++++ sql/user_commands.sql | 4 - src/api/block.rs | 4 - 4 files changed, 115 insertions(+), 147 deletions(-) delete mode 100644 .sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json create mode 100644 .sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json diff --git a/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json b/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json deleted file mode 100644 index b72c684..0000000 --- a/.sqlx/query-476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n u.id,\n u.command_type AS \"command_type: CommandType\",\n u.fee_payer_id,\n u.source_id,\n u.receiver_id,\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,\n ac.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": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "command_type: CommandType", - "type_info": { - "Custom": { - "name": "user_command_type", - "kind": { - "Enum": [ - "payment", - "delegation" - ] - } - } - } - }, - { - "ordinal": 2, - "name": "fee_payer_id", - "type_info": "Int4" - }, - { - "ordinal": 3, - "name": "source_id", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "receiver_id", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "nonce", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "amount", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "fee", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "valid_until", - "type_info": "Int8" - }, - { - "ordinal": 9, - "name": "memo", - "type_info": "Text" - }, - { - "ordinal": 10, - "name": "hash", - "type_info": "Text" - }, - { - "ordinal": 11, - "name": "fee_payer", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "source", - "type_info": "Text" - }, - { - "ordinal": 13, - "name": "receiver", - "type_info": "Text" - }, - { - "ordinal": 14, - "name": "status: TransactionStatus", - "type_info": { - "Custom": { - "name": "transaction_status", - "kind": { - "Enum": [ - "applied", - "failed" - ] - } - } - } - }, - { - "ordinal": 15, - "name": "failure_reason", - "type_info": "Text" - }, - { - "ordinal": 16, - "name": "creation_fee", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int4", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - false, - false, - false, - true, - false - ] - }, - "hash": "476eb69ab07de896e0f50801665bba3379651cfdfebeacb1c1f603fc55f3e320" -} diff --git a/.sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json b/.sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json new file mode 100644 index 0000000..0e75bf1 --- /dev/null +++ b/.sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json @@ -0,0 +1,115 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n u.command_type AS \"command_type: CommandType\",\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: CommandType", + "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": "e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253" +} diff --git a/sql/user_commands.sql b/sql/user_commands.sql index da35018..f790c60 100644 --- a/sql/user_commands.sql +++ b/sql/user_commands.sql @@ -1,9 +1,5 @@ SELECT - u.id, u.command_type AS "command_type: CommandType", - u.fee_payer_id, - u.source_id, - u.receiver_id, u.nonce, u.amount, u.fee, diff --git a/src/api/block.rs b/src/api/block.rs index b0f5fbe..9accd9a 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -109,11 +109,7 @@ pub struct BlockMetadata { #[derive(Debug, PartialEq, Eq, FromRow, Serialize)] pub struct UserCommandMetadata { - id: i32, command_type: CommandType, - fee_payer_id: i32, - source_id: i32, - receiver_id: i32, nonce: i64, amount: Option, fee: String, From e449a1516e53c5f9acfef2d4103d880eba42fcd3 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 2 Oct 2024 11:40:50 -0400 Subject: [PATCH 10/20] add snapshot for block --- tests/block.rs | 2 + tests/snapshots/block__specified.snap | 2577 +++++++++++++++++++++++++ 2 files changed, 2579 insertions(+) create mode 100644 tests/snapshots/block__specified.snap diff --git a/tests/block.rs b/tests/block.rs index a1dd68f..38deb53 100644 --- a/tests/block.rs +++ b/tests/block.rs @@ -2,6 +2,7 @@ use std::sync::OnceLock; use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; +use insta::assert_debug_snapshot; use mina_mesh::{ BlockRequest, BlockResponse, MinaMeshConfig, MinaMeshError, NetworkIdentifier, PartialBlockIdentifier, }; @@ -20,6 +21,7 @@ async fn specified() -> Result<()> { } maybe_prev = Some(resolved); } + assert_debug_snapshot!(maybe_prev); Ok(()) } diff --git a/tests/snapshots/block__specified.snap b/tests/snapshots/block__specified.snap new file mode 100644 index 0000000..c7b4b65 --- /dev/null +++ b/tests/snapshots/block__specified.snap @@ -0,0 +1,2577 @@ +--- +source: tests/block.rs +expression: maybe_prev +--- +Some( + Ok( + BlockResponse { + block: Some( + Block { + block_identifier: BlockIdentifier { + index: 375991, + hash: "3NKAyx2FuWx3jqtkpigndDRoyydQSUozPYix3hw3FWhZc8iUWwTP", + }, + parent_block_identifier: BlockIdentifier { + index: 375990, + hash: "3NK52dBHg4HELVhqMGuLonyCiHreiD6BXxAZSBmzjNnbNMrdvh5v", + }, + timestamp: 1722725820000, + transactions: [ + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuF58kLXaW9cMRAM5AviaRVUNK3aArLN6jxxbY9JyVz7RcnjgZv", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qntsJ1p1ECs3jLoBByBHkt74G8VM4Q5Uv82e1xa2NtUBbwdUpJR9", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "250000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qntsJ1p1ECs3jLoBByBHkt74G8VM4Q5Uv82e1xa2NtUBbwdUpJR9", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "13149444000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrDtZh2prv8NEUgmW376K6U2u7rtpWGar2MaQzroEcL9i69xLfbw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "13149444000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jtmx39tAzhx4Z6zxFrxGJzEP8U3gkPSukmT3LX9yjCsmN6N4LwU", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpLST3UC1rpVT6SHfB7wqW2iQgiopFAGfrcovPgLjgfpDUN2LLeg", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "95401000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpLST3UC1rpVT6SHfB7wqW2iQgiopFAGfrcovPgLjgfpDUN2LLeg", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnasrSvfAjsuHNpRgcyYw9coPDyQnUT16UDWqGFuQ5BTst7LFrD7", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Ju3dZ9iom6YrQsi1DPC6NzM5gpLe5NC8xURW2LbSnzfsYFXu4KH", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpLST3UC1rpVT6SHfB7wqW2iQgiopFAGfrcovPgLjgfpDUN2LLeg", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "95401000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpLST3UC1rpVT6SHfB7wqW2iQgiopFAGfrcovPgLjgfpDUN2LLeg", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnasrSvfAjsuHNpRgcyYw9coPDyQnUT16UDWqGFuQ5BTst7LFrD7", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jv7rq6pcts8TQm5R2SziD85aLWwFkRb7S1K3VypQeb42ixvRexH", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoXQhp63oNsLSN9Dy7wcF3PzLmdBnnin2rTnNWLbpgF7diABciU6", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "95401000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoXQhp63oNsLSN9Dy7wcF3PzLmdBnnin2rTnNWLbpgF7diABciU6", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qr6VeKCpYdqg7wZyside9EgzDxGMAPbfnMajPm4gH6TE7zf3Z3Xk", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtwYGkYEawWg9Ww1tVKpUz3EY4SZVBsKZtAqvX6Cf2VmkhkrUF3", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoXQhp63oNsLSN9Dy7wcF3PzLmdBnnin2rTnNWLbpgF7diABciU6", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "95401000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoXQhp63oNsLSN9Dy7wcF3PzLmdBnnin2rTnNWLbpgF7diABciU6", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qr6VeKCpYdqg7wZyside9EgzDxGMAPbfnMajPm4gH6TE7zf3Z3Xk", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "85000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuUdWNuk4Qr8LkunKouFJUNrmRznJVNam2ENez1GZP1q5bCCwgG", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "21001011", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "0", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnFCUtCu4bHJZGroNZvmq8ya1E9kAJkQGYnETh9E3CMHV98UvrPZ", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "0", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuywuAvK9adJvfGwD8sXEPFBgjHvfrpwxzCQbVXbB1wijv1AmHq", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "21000005", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "0", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnFCUtCu4bHJZGroNZvmq8ya1E9kAJkQGYnETh9E3CMHV98UvrPZ", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "0", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Ju6aGg5PRHzw2fwg2QaYfnFmX5WeaBQscrcC7ZCdWPLWDNxBxYL", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "21001000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "55000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnFCUtCu4bHJZGroNZvmq8ya1E9kAJkQGYnETh9E3CMHV98UvrPZ", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "55000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jtah6bTfk16HwFanYS9JUVHSrfq4LSct62b6vnuStvESsRmVTa5", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "21001000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qrAWZFqvgJbfU95t1owLAMKtsDTAGgSZzsBJYUzeQZ7dQNMmG5vw", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "55000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnFCUtCu4bHJZGroNZvmq8ya1E9kAJkQGYnETh9E3CMHV98UvrPZ", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "55000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jtg1T9mzmfkwGoH97MD5uma5KDLnJg2MAay1AnKU1vsb1xSq34e", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Ju3Z8WGZTxGbufgvBVVGmUcMdRJmNJZKVnMApGgsDZa4X1Szvvu", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JvCfZH23MXjrqGVH7EXc5S3ee4MwqTgRaX5akap4JNmp7ep7vek", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtWK6Jv3y2iQDq1ikrUNfWf8Nc5PyJQxCtQuiKfE1ABRFwUDLmr", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JvLtChRftJ1g5ibN3YtLdvU5bK9h9bveoyURM7inEfw4ajwxsbf", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuUKT4goESyU49J8JAHvQ58iLvzuoUj4tAMKFZoGxPCxEBXwFGn", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jv8hdmz44CssU1LVD4a99S8C28oNkDX4MiXYCrVa3JEHdMQ1iCZ", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JttmDyScSo77BQvd1yckUyrzr9mkqwhqJV8E6bndHFzg6wJovaZ", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuHeXyGFSr87GdMEUobZWCHJC6KsSvgnFjX52Yr7Zh5CjVXwgwW", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "17400000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qp69bsgUNySCY2wEYDCrRN3gdMB6cDSZGBucTzc9vUUH4jUoDSED", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qq6PqndihT5uoGAXzndoNgYSUMvUPmVqMQATusaoS1ZmCZRcM1ku", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "74000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jv9o4iyshYFWJYjWxykYDgdhTp9f3QmmqeDgmiYj7q73P69cM5h", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnEeb4KAp9WxdMxddHVtJ8gwfyJURG5BZZ6e4LsRjQKHNWqmgSWt", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "15859000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnEeb4KAp9WxdMxddHVtJ8gwfyJURG5BZZ6e4LsRjQKHNWqmgSWt", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "73060000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoVxygiYzqRCj4taZDbRJGY6xLvuzoiLdY5CpGm7L9Tz5cj2Qr6i", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "73060000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jv13YKkkF8cnBtDjnZPveZt1srjca9Jpm9P8iPKmAkScUZez1DE", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnEeb4KAp9WxdMxddHVtJ8gwfyJURG5BZZ6e4LsRjQKHNWqmgSWt", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "15683000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnEeb4KAp9WxdMxddHVtJ8gwfyJURG5BZZ6e4LsRjQKHNWqmgSWt", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "76420000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qoVxygiYzqRCj4taZDbRJGY6xLvuzoiLdY5CpGm7L9Tz5cj2Qr6i", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "76420000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Jv4wH5ADecmogG4s7pn8ud8gt1dfwpGyQHjNUjcj7xYBV2Y5CXr", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnXy1f75qq8c6HS2Am88Gk6UyvTHK3iSYh4Hb3nD6DS2eS6wZ4or", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "8710000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnXy1f75qq8c6HS2Am88Gk6UyvTHK3iSYh4Hb3nD6DS2eS6wZ4or", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "84990000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qqJ1AqK3YQmEEALdJeMw49438Sh6zuQ5cNWUYfCgRsPkduFE2uLU", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "84990000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Juj2n1NowUcbmz462sPP3aoDus84HkY8KF7hq28qdx1TZxfMMeF", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnXy1f75qq8c6HS2Am88Gk6UyvTHK3iSYh4Hb3nD6DS2eS6wZ4or", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "5000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qnXy1f75qq8c6HS2Am88Gk6UyvTHK3iSYh4Hb3nD6DS2eS6wZ4or", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "90486111", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qqJ1AqK3YQmEEALdJeMw49438Sh6zuQ5cNWUYfCgRsPkduFE2uLU", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "90486111", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtiX3kseB6qebRCiyeigmuiTxNhQfWEQBTwdBPCFueFiY9SjV5g", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "1401000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "89999000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpV4EsWwwaoQo9PaVVxk7RNPopWDEd3u4hZZgd83gXCPcuoDBrEz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "89999000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtpvYebhneMcZ9iTaozUKViELaXNSP5vwAj34Np8ZFEs9HTggp8", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "24001000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "44999000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpV4EsWwwaoQo9PaVVxk7RNPopWDEd3u4hZZgd83gXCPcuoDBrEz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "44999000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtoEyG9HC1viU8ASjgphG85JC5g7Ycbk5KR61fBT37fT4WHcT1p", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 0, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "4801000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qov9yv8TayLteD6SDXvxyYtmn3KkUoozAbs47fVo9JZSpcynbzTz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "50399000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + Operation { + operation_identifier: OperationIdentifier { + index: 3, + network_index: None, + }, + related_operations: None, + type: "", + status: Some( + "Applied", + ), + account: Some( + AccountIdentifier { + address: "B62qpV4EsWwwaoQo9PaVVxk7RNPopWDEd3u4hZZgd83gXCPcuoDBrEz", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "50399000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + ], + metadata: None, + }, + ), + other_transactions: None, + }, + ), +) From 7ce97b1a06361ccfc21e10699536de750a70b6ab Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 2 Oct 2024 13:02:47 -0400 Subject: [PATCH 11/20] continued --- ...9631097de2065bc65be1a183d0e440d1372c5.json | 76 +++++ ...4bb1415fb492671cdb2000cfed94131f9ee60.json | 115 +++++++ ...54776aa535ee86ee5da5393b9df832b463253.json | 115 ------- Cargo.lock | 45 +++ Cargo.toml | 2 + sql/internal_commands.sql | 8 +- sql/user_commands.sql | 2 +- src/api/account_balance.rs | 6 +- src/api/block.rs | 232 ++++++++++---- src/api/call.rs | 2 +- src/api/construction_combine.rs | 2 +- src/api/construction_derive.rs | 2 +- src/api/construction_hash.rs | 2 +- src/api/construction_metadata.rs | 2 +- src/api/construction_parse.rs | 2 +- src/api/construction_payloads.rs | 2 +- src/api/construction_preprocess.rs | 2 +- src/api/construction_submit.rs | 2 +- src/api/mempool.rs | 2 +- src/api/mempool_transaction.rs | 2 +- src/api/network_list.rs | 2 +- src/api/network_options.rs | 2 +- src/api/network_status.rs | 2 +- src/api/types.rs | 47 ++- src/commands/serve.rs | 2 +- src/lib.rs | 8 +- src/util.rs | 7 + tests/account_balance.rs | 6 +- tests/block.rs | 3 +- tests/snapshots/block__specified.snap | 300 +++++++++--------- 30 files changed, 635 insertions(+), 367 deletions(-) create mode 100644 .sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json create mode 100644 .sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json delete mode 100644 .sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json diff --git a/.sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json b/.sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json new file mode 100644 index 0000000..5625a55 --- /dev/null +++ b/.sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json @@ -0,0 +1,76 @@ +{ + "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,\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": "0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5" +} diff --git a/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json b/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json new file mode 100644 index 0000000..9ae60c8 --- /dev/null +++ b/.sqlx/query-62d4595387036ec87c261812c744bb1415fb492671cdb2000cfed94131f9ee60.json @@ -0,0 +1,115 @@ +{ + "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-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json b/.sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json deleted file mode 100644 index 0e75bf1..0000000 --- a/.sqlx/query-e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n u.command_type AS \"command_type: CommandType\",\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: CommandType", - "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": "e8429b4b8a3f55b2d5edf4cebc454776aa535ee86ee5da5393b9df832b463253" -} diff --git a/Cargo.lock b/Cargo.lock index 5118ad4..7200a4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,6 +513,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -713,6 +722,28 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.72", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -1497,9 +1528,11 @@ dependencies = [ "anyhow", "axum", "clap", + "convert_case", "cynic", "cynic-codegen", "cynic-querygen", + "derive_more", "dotenv", "envy", "futures", @@ -3129,6 +3162,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_categories" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 28ef164..6646203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,9 @@ aide = { version = "0.13.4", features = ["scalar"] } anyhow = "1.0.86" axum = { version = "0.7.5", features = ["macros"] } clap = { version = "4.5.11", features = ["derive", "env"] } +convert_case = "0.6.0" cynic = { version = "3.7.3", features = ["http-reqwest-blocking"] } +derive_more = { version = "1.0.0", features = ["full"] } dotenv = "0.15.0" envy = "0.4.2" futures = "0.3.30" diff --git a/sql/internal_commands.sql b/sql/internal_commands.sql index c766631..36aa525 100644 --- a/sql/internal_commands.sql +++ b/sql/internal_commands.sql @@ -51,7 +51,13 @@ WITH AND t.value=$2 ) SELECT - ic.*, + ic.command_type AS "command_type: InternalCommandType", + ic.hash, + ic.creation_fee, + ic.receiver, + ic.sequence_no, + ic.secondary_sequence_no, + ic.fee, coinbase_receiver_pk.value AS coinbase_receiver FROM internal_commands_cte AS ic diff --git a/sql/user_commands.sql b/sql/user_commands.sql index f790c60..fb7ea96 100644 --- a/sql/user_commands.sql +++ b/sql/user_commands.sql @@ -1,5 +1,5 @@ SELECT - u.command_type AS "command_type: CommandType", + u.command_type AS "command_type: UserCommandType", u.nonce, u.amount, u.fee, diff --git a/src/api/account_balance.rs b/src/api/account_balance.rs index 689d721..9386c80 100644 --- a/src/api/account_balance.rs +++ b/src/api/account_balance.rs @@ -1,7 +1,7 @@ use cynic::QueryBuilder; -use mesh::models::AccountIdentifier; -pub use mesh::models::{ - AccountBalanceRequest, AccountBalanceResponse, Amount, BlockIdentifier, Currency, PartialBlockIdentifier, +use mesh::models::{ + AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, Amount, BlockIdentifier, Currency, + PartialBlockIdentifier, }; use crate::{ diff --git a/src/api/block.rs b/src/api/block.rs index 9accd9a..8ef11c8 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,12 +1,15 @@ use anyhow::Result; use mesh::models::{ - AccountIdentifier, Block, BlockIdentifier, Operation, OperationIdentifier, Transaction, TransactionIdentifier, + AccountIdentifier, Block, BlockIdentifier, BlockRequest, BlockResponse, Operation, OperationIdentifier, + PartialBlockIdentifier, Transaction, TransactionIdentifier, }; -pub use mesh::models::{BlockRequest, BlockResponse, PartialBlockIdentifier}; use serde::Serialize; use sqlx::FromRow; -use crate::{ChainStatus, CommandType, MinaMesh, MinaMeshError, TransactionStatus, Wrapper}; +use crate::{ + ChainStatus, InternalCommandType, MinaMesh, MinaMeshError, OperationStatus, OperationType, TransactionStatus, + UserCommandType, Wrapper, +}; /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 impl MinaMesh { @@ -28,6 +31,7 @@ impl MinaMesh { None => block_identifier.clone(), }; let user_commands = self.user_commands(&metadata).await?; + // let internal_commands = self.internal_commands(&metadata).await?; Ok(BlockResponse { block: Some(Box::new(Block::new( block_identifier, @@ -41,22 +45,33 @@ impl MinaMesh { // 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/user_commands.sql", - metadata.id, - // cspell:disable-next-line - "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" - ) - .fetch_all(&self.pg_pool) - .await?; + let metadata = sqlx::query_file_as!(UserCommandMetadata, "sql/user_commands.sql", metadata.id, DEFAULT_TOKEN_ID) + .fetch_all(&self.pg_pool) + .await?; let transactions = metadata .into_iter() - .map(|item| Transaction::new(TransactionIdentifier::new(item.hash.clone()), Wrapper(&item).into())) + .map(|item| { + Transaction::new(TransactionIdentifier::new(item.hash.clone()), user_command_metadata_to_operations(&item)) + }) .collect(); Ok(transactions) } + pub async fn internal_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { + // let metadata = + // sqlx::query_file_as!(InternalCommandMetadata, "sql/internal_commands.sql", + // metadata.id, DEFAULT_TOKEN_ID) .fetch_all(&self.pg_pool) + // .await?; + // let transactions = metadata + // .into_iter() + // .map(|item| { + // Transaction::new(TransactionIdentifier::new(item.hash.clone()), + // internal_command_metadata_to_operation(&item)) }) + // .collect(); + // Ok(transactions) + unimplemented!(); + } + pub async fn block_metadata( &self, PartialBlockIdentifier { index, hash }: &PartialBlockIdentifier, @@ -109,7 +124,7 @@ pub struct BlockMetadata { #[derive(Debug, PartialEq, Eq, FromRow, Serialize)] pub struct UserCommandMetadata { - command_type: CommandType, + command_type: UserCommandType, nonce: i64, amount: Option, fee: String, @@ -124,73 +139,156 @@ pub struct UserCommandMetadata { creation_fee: Option, } -impl From> for Vec { - fn from(Wrapper(metadata): Wrapper<&UserCommandMetadata>) -> Self { - let mut operations = Vec::new(); - if metadata.fee != "0" { +#[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, +} + +fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { + let mut operations = Vec::new(); + if metadata.fee != "0" { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(0)), + amount: Wrapper(Some(metadata.fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.fee_payer.clone()))), + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::FeePayment).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + } + if metadata.failure_reason.is_none() { + if let Some(creation_fee) = &metadata.creation_fee { operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(0)), - amount: Wrapper(Some(metadata.fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.fee_payer.clone()))), - status: Some(metadata.status.to_string()), + operation_identifier: Box::new(OperationIdentifier::new(1)), + amount: Wrapper(Some(creation_fee.to_owned())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(OperationStatus::from(metadata.status.clone()).to_string()), related_operations: None, coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata + r#type: Wrapper(OperationType::AccountCreationFeeViaPayment).to_snake_case(), + metadata: None, // TODO: get the correct metadata }); } - if metadata.failure_reason.is_none() { - if let Some(creation_fee) = &metadata.creation_fee { + match metadata.command_type { + UserCommandType::Delegation => { operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(1)), - amount: Wrapper(Some(creation_fee.to_owned())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(metadata.status.to_string()), + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: None, + account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), + status: Some(OperationStatus::from(metadata.status.clone()).to_string()), related_operations: None, coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata + r#type: Wrapper(OperationType::DelegateChange).to_snake_case(), + metadata: None, // TODO: get the correct metadata }); } - match metadata.command_type { - CommandType::Delegation => { - operations.push(Operation { + UserCommandType::Payment => { + operations.extend_from_slice(&[ + Operation { operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: None, + amount: Wrapper(metadata.amount.clone()).into(), // TODO: negate value account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), - status: Some(metadata.status.to_string()), + status: Some(OperationStatus::from(metadata.status.clone()).to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::PaymentSourceDec).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }, + Operation { + operation_identifier: Box::new(OperationIdentifier::new(3)), + amount: Wrapper(metadata.amount.clone()).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(OperationStatus::from(metadata.status.clone()).to_string()), related_operations: None, coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata - }); - } - CommandType::Payment => { - operations.extend_from_slice(&[ - Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: Wrapper(metadata.amount.clone()).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), - status: Some(metadata.status.to_string()), - related_operations: None, - coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata - }, - Operation { - operation_identifier: Box::new(OperationIdentifier::new(3)), - amount: Wrapper(metadata.amount.clone()).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(metadata.status.to_string()), - related_operations: None, - coin_change: None, - r#type: "".to_string(), // TODO: get the correct type - metadata: None, // TODO: get the correct metadata - }, - ]); - } - }; + r#type: Wrapper(OperationType::PaymentReceiverInc).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }, + ]); + } + }; + } + operations +} + +fn internal_command_metadata_to_operation(metadata: &InternalCommandMetadata) -> Result, MinaMeshError> { + let mut operations = Vec::new(); + if let Some(creation_fee) = &metadata.creation_fee { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(0)), + amount: Wrapper(Some(creation_fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::AccountCreationFeeViaFeeReceiver).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + } + + match metadata.command_type { + InternalCommandType::Coinbase => { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: Wrapper(Some(metadata.fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::CoinbaseInc).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + } + InternalCommandType::FeeTransfer => { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: Wrapper(Some(metadata.fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), // TODO: token id + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::FeeReceiverInc).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + } + InternalCommandType::FeeTransferViaCoinbase => { + if let Some(coinbase_receiver) = &metadata.coinbase_receiver { + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(2)), + amount: Wrapper(Some(metadata.fee.clone())).into(), + account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), // TODO: token id + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::FeeReceiverInc).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + operations.push(Operation { + operation_identifier: Box::new(OperationIdentifier::new(3)), + amount: Wrapper(Some(metadata.fee.clone())).into(), // TODO: negate value + account: Some(Box::new(AccountIdentifier::new(coinbase_receiver.clone()))), // TODO: token id + status: Some(OperationStatus::Success.to_string()), + related_operations: None, + coin_change: None, + r#type: Wrapper(OperationType::FeePayerDec).to_snake_case(), + metadata: None, // TODO: get the correct metadata + }); + } else { + return Err(MinaMeshError::InvariantViolation); + } } - operations } + Ok(operations) } + +// cspell:disable-next-line +static DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; diff --git a/src/api/call.rs b/src/api/call.rs index 86ff839..52fed7d 100644 --- a/src/api/call.rs +++ b/src/api/call.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{CallRequest, CallResponse}; +use mesh::models::{CallRequest, CallResponse}; use crate::MinaMesh; diff --git a/src/api/construction_combine.rs b/src/api/construction_combine.rs index ca141dd..318e95c 100644 --- a/src/api/construction_combine.rs +++ b/src/api/construction_combine.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionCombineRequest, ConstructionCombineResponse}; +use mesh::models::{ConstructionCombineRequest, ConstructionCombineResponse}; use crate::MinaMesh; diff --git a/src/api/construction_derive.rs b/src/api/construction_derive.rs index d64bb2a..c92ebf5 100644 --- a/src/api/construction_derive.rs +++ b/src/api/construction_derive.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionDeriveRequest, ConstructionDeriveResponse}; +use mesh::models::{ConstructionDeriveRequest, ConstructionDeriveResponse}; use crate::MinaMesh; diff --git a/src/api/construction_hash.rs b/src/api/construction_hash.rs index 243a404..d1f4438 100644 --- a/src/api/construction_hash.rs +++ b/src/api/construction_hash.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionHashRequest, TransactionIdentifier}; +use mesh::models::{ConstructionHashRequest, TransactionIdentifier}; use crate::MinaMesh; diff --git a/src/api/construction_metadata.rs b/src/api/construction_metadata.rs index 0d260eb..a6e07c0 100644 --- a/src/api/construction_metadata.rs +++ b/src/api/construction_metadata.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionMetadataRequest, ConstructionMetadataResponse}; +use mesh::models::{ConstructionMetadataRequest, ConstructionMetadataResponse}; use crate::MinaMesh; diff --git a/src/api/construction_parse.rs b/src/api/construction_parse.rs index 9045031..30e59f3 100644 --- a/src/api/construction_parse.rs +++ b/src/api/construction_parse.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionParseRequest, ConstructionParseResponse}; +use mesh::models::{ConstructionParseRequest, ConstructionParseResponse}; use crate::MinaMesh; diff --git a/src/api/construction_payloads.rs b/src/api/construction_payloads.rs index a2ac7ff..ffba914 100644 --- a/src/api/construction_payloads.rs +++ b/src/api/construction_payloads.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionPayloadsRequest, ConstructionPayloadsResponse}; +use mesh::models::{ConstructionPayloadsRequest, ConstructionPayloadsResponse}; use crate::MinaMesh; diff --git a/src/api/construction_preprocess.rs b/src/api/construction_preprocess.rs index 35ca076..0f3e443 100644 --- a/src/api/construction_preprocess.rs +++ b/src/api/construction_preprocess.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionPreprocessRequest, ConstructionPreprocessResponse}; +use mesh::models::{ConstructionPreprocessRequest, ConstructionPreprocessResponse}; use crate::MinaMesh; diff --git a/src/api/construction_submit.rs b/src/api/construction_submit.rs index 2d96107..e700599 100644 --- a/src/api/construction_submit.rs +++ b/src/api/construction_submit.rs @@ -1,5 +1,5 @@ use anyhow::Result; -pub use mesh::models::{ConstructionSubmitRequest, TransactionIdentifier}; +use mesh::models::{ConstructionSubmitRequest, TransactionIdentifier}; use crate::MinaMesh; diff --git a/src/api/mempool.rs b/src/api/mempool.rs index 6b4570e..519d413 100644 --- a/src/api/mempool.rs +++ b/src/api/mempool.rs @@ -4,7 +4,7 @@ use anyhow::Result; use cynic::QueryBuilder; -pub use mesh::models::{MempoolResponse, TransactionIdentifier}; +use mesh::models::{MempoolResponse, TransactionIdentifier}; use crate::{graphql::QueryMempool, MinaMesh}; diff --git a/src/api/mempool_transaction.rs b/src/api/mempool_transaction.rs index 2f0cbab..f9f4de0 100644 --- a/src/api/mempool_transaction.rs +++ b/src/api/mempool_transaction.rs @@ -1,5 +1,5 @@ use cynic::QueryBuilder; -pub use mesh::models::{MempoolTransactionRequest, MempoolTransactionResponse, Transaction, TransactionIdentifier}; +use mesh::models::{MempoolTransactionRequest, MempoolTransactionResponse, Transaction, TransactionIdentifier}; use crate::{ graphql::{QueryMempoolTransactions, QueryMempoolTransactionsVariables}, diff --git a/src/api/network_list.rs b/src/api/network_list.rs index 5764708..1c0f8f1 100644 --- a/src/api/network_list.rs +++ b/src/api/network_list.rs @@ -1,6 +1,6 @@ use anyhow::Result; use cynic::QueryBuilder; -pub use mesh::models::{NetworkIdentifier, NetworkListResponse}; +use mesh::models::{NetworkIdentifier, NetworkListResponse}; use crate::{graphql::QueryNetworkId, MinaMesh}; diff --git a/src/api/network_options.rs b/src/api/network_options.rs index a6216c8..18e2a6a 100644 --- a/src/api/network_options.rs +++ b/src/api/network_options.rs @@ -1,7 +1,7 @@ // TODO: double-check the data is correct // TODO: why do long string literals in the error metadata break rustfmt? -pub use mesh::models::{Allow, Case, Error, NetworkOptionsResponse, OperationStatus, Version}; +use mesh::models::{Allow, Case, Error, NetworkOptionsResponse, OperationStatus, Version}; use crate::{MinaMesh, MinaMeshError}; diff --git a/src/api/network_status.rs b/src/api/network_status.rs index 4011353..29bddc6 100644 --- a/src/api/network_status.rs +++ b/src/api/network_status.rs @@ -1,7 +1,7 @@ // TODO: get genesis block identifier from env use cynic::QueryBuilder; -pub use mesh::models::{BlockIdentifier, NetworkStatusResponse, Peer}; +use mesh::models::{BlockIdentifier, NetworkStatusResponse, Peer}; use crate::{ graphql::{Block3, DaemonStatus3, QueryNetworkStatus}, diff --git a/src/api/types.rs b/src/api/types.rs index 000c15d..264df6b 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1,5 +1,4 @@ -use std::fmt::Display; - +use derive_more::derive::Display; use serde::Serialize; use sqlx::Type; @@ -13,23 +12,55 @@ pub enum ChainStatus { #[derive(Type, Debug, PartialEq, Eq, Serialize)] #[sqlx(type_name = "command_type", rename_all = "lowercase")] -pub enum CommandType { +pub enum UserCommandType { Payment, Delegation, } #[derive(Type, Debug, PartialEq, Eq, Serialize)] +#[sqlx(type_name = "internal_command_type", rename_all = "snake_case")] +pub enum InternalCommandType { + FeeTransferViaCoinbase, + FeeTransfer, + Coinbase, +} + +#[derive(Type, Debug, PartialEq, Eq, Serialize, Display, Clone)] #[sqlx(type_name = "transaction_status", rename_all = "lowercase")] pub enum TransactionStatus { Applied, Failed, } -impl Display for TransactionStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Applied => write!(f, "Applied"), - Self::Failed => write!(f, "Failed"), +#[derive(Debug, Display)] +pub enum OperationStatus { + Success, + Failed, +} + +impl From for OperationStatus { + fn from(status: TransactionStatus) -> Self { + match status { + TransactionStatus::Applied => OperationStatus::Success, + TransactionStatus::Failed => OperationStatus::Failed, } } } + +#[derive(Debug, Display)] +pub enum OperationType { + FeePayerDec, + FeeReceiverInc, + CoinbaseInc, + AccountCreationFeeViaPayment, + AccountCreationFeeViaFeePayer, + AccountCreationFeeViaFeeReceiver, + PaymentSourceDec, + PaymentReceiverInc, + FeePayment, + DelegateChange, + CreateToken, + MintTokens, + ZkappFeePayerDec, + ZkappBalanceUpdate, +} diff --git a/src/commands/serve.rs b/src/commands/serve.rs index e858273..c8a5285 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -64,7 +64,7 @@ impl ServeCommand { macro_rules! create_handler { ($name:ident, $request_type:ty) => { paste! { - async fn [](mina_mesh: State>, Json(req): Json) -> impl IntoResponse { + async fn [](mina_mesh: State>, Json(req): Json) -> impl IntoResponse { Wrapper(mina_mesh.$name(req).await) } } diff --git a/src/lib.rs b/src/lib.rs index 772f460..ebc0e01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub mod api; +mod api; mod commands; mod config; mod error; @@ -10,13 +10,13 @@ pub use api::*; pub use commands::*; pub use config::*; pub use error::*; -pub use mesh::models::{AccountIdentifier, BlockIdentifier, NetworkIdentifier}; +pub use mesh::models; use sqlx::PgPool; -pub use util::Wrapper; +pub(crate) use util::Wrapper; #[derive(Debug)] pub struct MinaMesh { pub graphql_client: graphql::GraphQLClient, pub pg_pool: PgPool, - pub genesis_block_identifier: BlockIdentifier, + pub genesis_block_identifier: models::BlockIdentifier, } diff --git a/src/util.rs b/src/util.rs index b718b19..8308bf2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,6 +4,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; +use convert_case::{Case, Casing}; use mesh::models::{Amount, Currency, PartialBlockIdentifier}; use serde::Serialize; @@ -51,3 +52,9 @@ impl ToString for Wrapper<&PartialBlockIdentifier> { pub fn default_mina_proxy_url() -> String { "https://mainnet.minaprotocol.network/graphql".to_string() } + +impl Wrapper { + pub fn to_snake_case(&self) -> String { + self.0.to_string().to_case(Case::Snake) + } +} diff --git a/tests/account_balance.rs b/tests/account_balance.rs index f475c23..3dec2d4 100644 --- a/tests/account_balance.rs +++ b/tests/account_balance.rs @@ -2,8 +2,10 @@ use anyhow::Result; use futures::future::try_join_all; use insta::assert_debug_snapshot; use mina_mesh::{ - AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, MinaMeshConfig, NetworkIdentifier, - PartialBlockIdentifier, + models::{ + AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, NetworkIdentifier, PartialBlockIdentifier, + }, + MinaMeshConfig, }; #[tokio::test] diff --git a/tests/block.rs b/tests/block.rs index 38deb53..552d3a6 100644 --- a/tests/block.rs +++ b/tests/block.rs @@ -4,7 +4,8 @@ use anyhow::Result; use futures::{stream::FuturesUnordered, StreamExt}; use insta::assert_debug_snapshot; use mina_mesh::{ - BlockRequest, BlockResponse, MinaMeshConfig, MinaMeshError, NetworkIdentifier, PartialBlockIdentifier, + models::{BlockRequest, BlockResponse, NetworkIdentifier, PartialBlockIdentifier}, + MinaMeshConfig, MinaMeshError, }; #[tokio::test] diff --git a/tests/snapshots/block__specified.snap b/tests/snapshots/block__specified.snap index c7b4b65..3c850e3 100644 --- a/tests/snapshots/block__specified.snap +++ b/tests/snapshots/block__specified.snap @@ -28,9 +28,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -59,9 +59,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -90,9 +90,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -130,9 +130,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -161,9 +161,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -192,9 +192,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -232,9 +232,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -263,9 +263,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -294,9 +294,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -334,9 +334,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -365,9 +365,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -396,9 +396,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -436,9 +436,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -467,9 +467,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -498,9 +498,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -538,9 +538,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -569,9 +569,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -600,9 +600,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -640,9 +640,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -671,9 +671,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -702,9 +702,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -742,9 +742,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -773,9 +773,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -804,9 +804,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -844,9 +844,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -875,9 +875,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -906,9 +906,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -946,9 +946,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -977,9 +977,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1008,9 +1008,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1048,9 +1048,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1079,9 +1079,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1110,9 +1110,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1150,9 +1150,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1181,9 +1181,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1212,9 +1212,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1252,9 +1252,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1283,9 +1283,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1314,9 +1314,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1354,9 +1354,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1385,9 +1385,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1416,9 +1416,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1456,9 +1456,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1487,9 +1487,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1518,9 +1518,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1558,9 +1558,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1589,9 +1589,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1620,9 +1620,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1660,9 +1660,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1691,9 +1691,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1722,9 +1722,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1762,9 +1762,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1793,9 +1793,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1824,9 +1824,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1864,9 +1864,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1895,9 +1895,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1926,9 +1926,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1966,9 +1966,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -1997,9 +1997,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2028,9 +2028,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2068,9 +2068,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2099,9 +2099,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2130,9 +2130,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2170,9 +2170,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2201,9 +2201,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2232,9 +2232,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2272,9 +2272,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2303,9 +2303,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2334,9 +2334,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2374,9 +2374,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2405,9 +2405,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2436,9 +2436,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2476,9 +2476,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "fee_payment", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2507,9 +2507,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_source_dec", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { @@ -2538,9 +2538,9 @@ Some( network_index: None, }, related_operations: None, - type: "", + type: "payment_receiver_inc", status: Some( - "Applied", + "Success", ), account: Some( AccountIdentifier { From 58d9502c955c5fd9b74852a920d54db130bab8f7 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 09:16:04 -0400 Subject: [PATCH 12/20] refactoring --- src/api.rs | 19 --- src/api/block.rs | 194 +++++++++------------- src/error.rs | 9 + src/lib.rs | 10 +- src/{api => }/types.rs | 0 src/util.rs | 21 --- tests/snapshots/block__specified.snap.new | 12 ++ 7 files changed, 102 insertions(+), 163 deletions(-) rename src/{api => }/types.rs (100%) create mode 100644 tests/snapshots/block__specified.snap.new diff --git a/src/api.rs b/src/api.rs index a61ef5f..6c50edb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,22 +15,3 @@ mod network_health_check; mod network_list; mod network_options; mod network_status; -mod types; - -pub use account_balance::*; -pub use block::*; -pub use call::*; -pub use construction_combine::*; -pub use construction_derive::*; -pub use construction_hash::*; -pub use construction_metadata::*; -pub use construction_parse::*; -pub use construction_payloads::*; -pub use construction_preprocess::*; -pub use construction_submit::*; -pub use mempool::*; -pub use mempool_transaction::*; -pub use network_list::*; -pub use network_options::*; -pub use network_status::*; -pub use types::*; diff --git a/src/api/block.rs b/src/api/block.rs index 8ef11c8..ff97c79 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -1,14 +1,15 @@ use anyhow::Result; +use convert_case::{Case, Casing}; use mesh::models::{ - AccountIdentifier, Block, BlockIdentifier, BlockRequest, BlockResponse, Operation, OperationIdentifier, - PartialBlockIdentifier, Transaction, TransactionIdentifier, + AccountIdentifier, Amount, Block, BlockIdentifier, BlockRequest, BlockResponse, Currency, Operation, + OperationIdentifier, PartialBlockIdentifier, Transaction, TransactionIdentifier, }; use serde::Serialize; use sqlx::FromRow; use crate::{ ChainStatus, InternalCommandType, MinaMesh, MinaMeshError, OperationStatus, OperationType, TransactionStatus, - UserCommandType, Wrapper, + UserCommandType, }; /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 @@ -17,7 +18,7 @@ impl MinaMesh { let partial_block_identifier = *request.block_identifier; let metadata = match self.block_metadata(&partial_block_identifier).await? { Some(metadata) => metadata, - None => return Err(MinaMeshError::BlockMissing(Wrapper(&partial_block_identifier).to_string())), + None => return Err(MinaMeshError::BlockMissing(serde_json::to_string(&partial_block_identifier)?)), }; let parent_block_metadata = match &metadata.parent_id { Some(parent_id) => { @@ -30,8 +31,9 @@ impl MinaMesh { Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), None => block_identifier.clone(), }; - let user_commands = self.user_commands(&metadata).await?; - // let internal_commands = self.internal_commands(&metadata).await?; + let (mut user_commands, internal_commands) = + tokio::try_join!(self.user_commands(&metadata), self.internal_commands(&metadata))?; + user_commands.extend(internal_commands.into_iter()); Ok(BlockResponse { block: Some(Box::new(Block::new( block_identifier, @@ -58,18 +60,18 @@ impl MinaMesh { } pub async fn internal_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { - // let metadata = - // sqlx::query_file_as!(InternalCommandMetadata, "sql/internal_commands.sql", - // metadata.id, DEFAULT_TOKEN_ID) .fetch_all(&self.pg_pool) - // .await?; - // let transactions = metadata - // .into_iter() - // .map(|item| { - // Transaction::new(TransactionIdentifier::new(item.hash.clone()), - // internal_command_metadata_to_operation(&item)) }) - // .collect(); - // Ok(transactions) - unimplemented!(); + let metadata = + sqlx::query_file_as!(InternalCommandMetadata, "sql/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)) + }) + .collect::, MinaMeshError>>()?; + Ok(transactions) } pub async fn block_metadata( @@ -154,65 +156,38 @@ pub struct InternalCommandMetadata { fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { let mut operations = Vec::new(); if metadata.fee != "0" { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(0)), - amount: Wrapper(Some(metadata.fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.fee_payer.clone()))), - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::FeePayment).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation(0, Some(&metadata.fee), &metadata.fee_payer, OperationType::FeePayment, None)); } if metadata.failure_reason.is_none() { if let Some(creation_fee) = &metadata.creation_fee { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(1)), - amount: Wrapper(Some(creation_fee.to_owned())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(OperationStatus::from(metadata.status.clone()).to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::AccountCreationFeeViaPayment).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation( + 1, + Some(creation_fee), + &metadata.receiver, + OperationType::AccountCreationFeeViaPayment, + Some(&metadata.status), + )); } match metadata.command_type { UserCommandType::Delegation => { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: None, - account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), - status: Some(OperationStatus::from(metadata.status.clone()).to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::DelegateChange).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation(2, None, &metadata.source, OperationType::DelegateChange, Some(&metadata.status))); } UserCommandType::Payment => { operations.extend_from_slice(&[ - Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: Wrapper(metadata.amount.clone()).into(), // TODO: negate value - account: Some(Box::new(AccountIdentifier::new(metadata.source.clone()))), - status: Some(OperationStatus::from(metadata.status.clone()).to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::PaymentSourceDec).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }, - Operation { - operation_identifier: Box::new(OperationIdentifier::new(3)), - amount: Wrapper(metadata.amount.clone()).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(OperationStatus::from(metadata.status.clone()).to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::PaymentReceiverInc).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }, + operation( + 2, + metadata.amount.as_ref(), + &metadata.source, + OperationType::PaymentSourceDec, + Some(&metadata.status), + ), + operation( + 3, + metadata.amount.as_ref(), + &metadata.receiver, + OperationType::PaymentReceiverInc, + Some(&metadata.status), + ), ]); } }; @@ -223,65 +198,25 @@ fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec Result, MinaMeshError> { let mut operations = Vec::new(); if let Some(creation_fee) = &metadata.creation_fee { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(0)), - amount: Wrapper(Some(creation_fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::AccountCreationFeeViaFeeReceiver).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation( + 0, + Some(creation_fee), + &metadata.receiver, + OperationType::AccountCreationFeeViaFeeReceiver, + None, + )); } - match metadata.command_type { InternalCommandType::Coinbase => { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: Wrapper(Some(metadata.fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::CoinbaseInc).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation(2, Some(&metadata.fee), &metadata.receiver, OperationType::CoinbaseInc, None)); } InternalCommandType::FeeTransfer => { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: Wrapper(Some(metadata.fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), // TODO: token id - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::FeeReceiverInc).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation(2, Some(&metadata.fee), &metadata.receiver, OperationType::FeeReceiverInc, None)); } InternalCommandType::FeeTransferViaCoinbase => { if let Some(coinbase_receiver) = &metadata.coinbase_receiver { - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(2)), - amount: Wrapper(Some(metadata.fee.clone())).into(), - account: Some(Box::new(AccountIdentifier::new(metadata.receiver.clone()))), // TODO: token id - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::FeeReceiverInc).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); - operations.push(Operation { - operation_identifier: Box::new(OperationIdentifier::new(3)), - amount: Wrapper(Some(metadata.fee.clone())).into(), // TODO: negate value - account: Some(Box::new(AccountIdentifier::new(coinbase_receiver.clone()))), // TODO: token id - status: Some(OperationStatus::Success.to_string()), - related_operations: None, - coin_change: None, - r#type: Wrapper(OperationType::FeePayerDec).to_snake_case(), - metadata: None, // TODO: get the correct metadata - }); + operations.push(operation(2, Some(&metadata.fee), &metadata.receiver, OperationType::FeeReceiverInc, None)); + operations.push(operation(3, Some(&metadata.fee), coinbase_receiver, OperationType::FeePayerDec, None)); } else { return Err(MinaMeshError::InvariantViolation); } @@ -290,5 +225,26 @@ fn internal_command_metadata_to_operation(metadata: &InternalCommandMetadata) -> Ok(operations) } +fn operation( + ident: i64, + amount: Option<&String>, + account: &String, + operation_type: OperationType, + status: Option<&TransactionStatus>, +) -> Operation { + Operation { + operation_identifier: Box::new(OperationIdentifier::new(ident)), + amount: amount.map(|value| Box::new(Amount::new(value.to_owned(), Currency::new("mina".to_string(), 9)))), + account: Some(Box::new(AccountIdentifier::new(account.to_owned()))), + status: Some( + status.map(|item| OperationStatus::from(item.to_owned())).unwrap_or(OperationStatus::Success).to_string(), + ), + related_operations: None, + coin_change: None, + r#type: operation_type.to_string().to_case(Case::Snake), + metadata: None, // TODO: get the correct metadata + } +} + // cspell:disable-next-line static DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; diff --git a/src/error.rs b/src/error.rs index 9cc6810..ffd6047 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ use std::num::ParseIntError; use cynic::http::CynicReqwestError; +use serde_json::Error as SerdeError; use sqlx::Error as SqlxError; use thiserror::Error; @@ -137,3 +138,11 @@ impl From for MinaMeshError { MinaMeshError::GraphqlMinaQuery(value.to_string()) } } + +// TODO: this isn't necessarily accurate, as we use this for a serialization +// errors as well. +impl From for MinaMeshError { + fn from(value: SerdeError) -> Self { + MinaMeshError::JsonParse(Some(value.to_string())) + } +} diff --git a/src/lib.rs b/src/lib.rs index ebc0e01..f1e9fc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,19 +4,21 @@ mod config; mod error; mod graphql; mod playground; +mod types; mod util; -pub use api::*; pub use commands::*; pub use config::*; pub use error::*; +use graphql::GraphQLClient; pub use mesh::models; +use mesh::models::BlockIdentifier; use sqlx::PgPool; -pub(crate) use util::Wrapper; +pub use types::*; #[derive(Debug)] pub struct MinaMesh { - pub graphql_client: graphql::GraphQLClient, + pub graphql_client: GraphQLClient, pub pg_pool: PgPool, - pub genesis_block_identifier: models::BlockIdentifier, + pub genesis_block_identifier: BlockIdentifier, } diff --git a/src/api/types.rs b/src/types.rs similarity index 100% rename from src/api/types.rs rename to src/types.rs diff --git a/src/util.rs b/src/util.rs index 8308bf2..1fbd66b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,8 +4,6 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use convert_case::{Case, Casing}; -use mesh::models::{Amount, Currency, PartialBlockIdentifier}; use serde::Serialize; use crate::MinaMeshError; @@ -36,25 +34,6 @@ impl Wrapper> { // cspell:disable-next-line const DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; -#[allow(clippy::to_string_trait_impl)] -impl ToString for Wrapper<&PartialBlockIdentifier> { - fn to_string(&self) -> String { - match &self.0.hash { - Some(hash) => hash.to_owned(), - None => match self.0.index { - Some(index) => index.to_string(), - None => "latest".to_string(), - }, - } - } -} - pub fn default_mina_proxy_url() -> String { "https://mainnet.minaprotocol.network/graphql".to_string() } - -impl Wrapper { - pub fn to_snake_case(&self) -> String { - self.0.to_string().to_case(Case::Snake) - } -} diff --git a/tests/snapshots/block__specified.snap.new b/tests/snapshots/block__specified.snap.new new file mode 100644 index 0000000..6433619 --- /dev/null +++ b/tests/snapshots/block__specified.snap.new @@ -0,0 +1,12 @@ +--- +source: tests/block.rs +assertion_line: 25 +expression: maybe_prev +--- +Some( + Err( + Sql( + "error occurred while decoding column 2: unexpected null; try decoding as an `Option`", + ), + ), +) From f6cea974ce903ff58da07ec1ed94958d502c4d05 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 09:17:24 -0400 Subject: [PATCH 13/20] use default token id from util --- src/api/block.rs | 7 ++----- src/util.rs | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/api/block.rs b/src/api/block.rs index ff97c79..d454100 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -8,8 +8,8 @@ use serde::Serialize; use sqlx::FromRow; use crate::{ - ChainStatus, InternalCommandType, MinaMesh, MinaMeshError, OperationStatus, OperationType, TransactionStatus, - UserCommandType, + util::DEFAULT_TOKEN_ID, ChainStatus, InternalCommandType, MinaMesh, MinaMeshError, OperationStatus, OperationType, + TransactionStatus, UserCommandType, }; /// https://github.com/MinaProtocol/mina/blob/985eda49bdfabc046ef9001d3c406e688bc7ec45/src/app/rosetta/lib/block.ml#L7 @@ -245,6 +245,3 @@ fn operation( metadata: None, // TODO: get the correct metadata } } - -// cspell:disable-next-line -static DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; diff --git a/src/util.rs b/src/util.rs index 1fbd66b..2de6c9f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -20,8 +20,8 @@ impl IntoResponse for Wrapper> { } impl Wrapper> { - pub fn to_token_id(self) -> Result { - match self.0 { + pub fn to_token_id(&self) -> Result { + match &self.0 { None => Ok(DEFAULT_TOKEN_ID.to_string()), Some(serde_json::Value::Object(map)) => { Ok(map.get("token_id").map(|v| v.to_string()).ok_or(MinaMeshError::JsonParse(None))?) From 9d2e30667b38f2a25d59c12c907c27cfb107e5e4 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 12:18:01 -0400 Subject: [PATCH 14/20] rebase and add internal command usage --- .env.example | 2 + ...1660557b9c084af85a5f4a24ee06cb0741e8.json} | 6 +- sql/internal_commands.sql | 2 +- src/util.rs | 2 +- tests/snapshots/block__specified.snap | 120 ++++++++++++++++++ tests/snapshots/block__specified.snap.new | 12 -- 6 files changed, 127 insertions(+), 17 deletions(-) rename .sqlx/{query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json => query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json} (83%) delete mode 100644 tests/snapshots/block__specified.snap.new diff --git a/.env.example b/.env.example index 2097017..c93adda 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +DATABASE_URL=postgres://mina:whatever@localhost:5432/archive # TODO: we only need this one during development + MINAMESH_ARCHIVE_DATABASE_URL=postgres://mina:whatever@localhost:5432/archive MINAMESH_GENESIS_BLOCK_IDENTIFIER_STATE_HASH=3NK4BpDSekaqsG6tx8Nse2zJchRft2JpnbvMiog55WCr5xJZaKeP MINAMESH_GENESIS_BLOCK_IDENTIFIER_HEIGHT=359605 diff --git a/.sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json b/.sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json similarity index 83% rename from .sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json rename to .sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json index 5625a55..c4f5671 100644 --- a/.sqlx/query-0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5.json +++ b/.sqlx/query-e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8.json @@ -1,6 +1,6 @@ { "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,\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", + "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": [ { @@ -26,7 +26,7 @@ }, { "ordinal": 2, - "name": "creation_fee", + "name": "creation_fee?", "type_info": "Text" }, { @@ -72,5 +72,5 @@ true ] }, - "hash": "0f977a10c3eca4c85fe33141ef79631097de2065bc65be1a183d0e440d1372c5" + "hash": "e67cd389b7cac6565172a2f837861660557b9c084af85a5f4a24ee06cb0741e8" } diff --git a/sql/internal_commands.sql b/sql/internal_commands.sql index 36aa525..51b9e37 100644 --- a/sql/internal_commands.sql +++ b/sql/internal_commands.sql @@ -53,7 +53,7 @@ WITH SELECT ic.command_type AS "command_type: InternalCommandType", ic.hash, - ic.creation_fee, + ic.creation_fee AS "creation_fee?", ic.receiver, ic.sequence_no, ic.secondary_sequence_no, diff --git a/src/util.rs b/src/util.rs index 2de6c9f..d5a949d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -32,7 +32,7 @@ impl Wrapper> { } // cspell:disable-next-line -const DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; +pub const DEFAULT_TOKEN_ID: &str = "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"; pub fn default_mina_proxy_url() -> String { "https://mainnet.minaprotocol.network/graphql".to_string() diff --git a/tests/snapshots/block__specified.snap b/tests/snapshots/block__specified.snap index 3c850e3..49c1aad 100644 --- a/tests/snapshots/block__specified.snap +++ b/tests/snapshots/block__specified.snap @@ -2567,6 +2567,126 @@ Some( related_transactions: None, metadata: None, }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JtyT69gceMuEzmyjbEMZ6E9oTiQwaDjoVyZRM7Ggfm8mbtdGceK", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "fee_receiver_inc", + status: Some( + "Success", + ), + account: Some( + AccountIdentifier { + address: "B62qktPtDFWG8ciSsd6eEPr7S7HuiGaouqDhfPT7H84DPp9mCgTHjwE", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "232055000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5Ju2teejDK5Q2ea1P21dPTdnnqt5c8QmSL1Tpf8PVr2SmLQzA3KY", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "coinbase_inc", + status: Some( + "Success", + ), + account: Some( + AccountIdentifier { + address: "B62qktPtDFWG8ciSsd6eEPr7S7HuiGaouqDhfPT7H84DPp9mCgTHjwE", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "720000000000", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, + Transaction { + transaction_identifier: TransactionIdentifier { + hash: "5JuMZ2QrMWkpQuCFEw9iPVS8zXQkmR1g3LHg4a33xdmUCHEJRQho", + }, + operations: [ + Operation { + operation_identifier: OperationIdentifier { + index: 2, + network_index: None, + }, + related_operations: None, + type: "fee_receiver_inc", + status: Some( + "Success", + ), + account: Some( + AccountIdentifier { + address: "B62qktPtDFWG8ciSsd6eEPr7S7HuiGaouqDhfPT7H84DPp9mCgTHjwE", + sub_account: None, + metadata: None, + }, + ), + amount: Some( + Amount { + value: "715607016", + currency: Currency { + symbol: "mina", + decimals: 9, + metadata: None, + }, + metadata: None, + }, + ), + coin_change: None, + metadata: None, + }, + ], + related_transactions: None, + metadata: None, + }, ], metadata: None, }, diff --git a/tests/snapshots/block__specified.snap.new b/tests/snapshots/block__specified.snap.new deleted file mode 100644 index 6433619..0000000 --- a/tests/snapshots/block__specified.snap.new +++ /dev/null @@ -1,12 +0,0 @@ ---- -source: tests/block.rs -assertion_line: 25 -expression: maybe_prev ---- -Some( - Err( - Sql( - "error occurred while decoding column 2: unexpected null; try decoding as an `Option`", - ), - ), -) From f1ec250f9a92fe97de7fde9dbee1366a36008f49 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 12:40:44 -0400 Subject: [PATCH 15/20] initial zkapp command plumming --- ...0761b3fbb32f2ff125cfc557e41106bb7debd.json | 87 +++++++++++++++++++ ...zkapps_commands.sql => zkapp_commands.sql} | 6 +- src/api/block.rs | 63 +++++++++++++- 3 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 .sqlx/query-de3ae438ab25d585ae49ed2bf4b0761b3fbb32f2ff125cfc557e41106bb7debd.json rename sql/{zkapps_commands.sql => zkapp_commands.sql} (93%) diff --git a/.sqlx/query-de3ae438ab25d585ae49ed2bf4b0761b3fbb32f2ff125cfc557e41106bb7debd.json b/.sqlx/query-de3ae438ab25d585ae49ed2bf4b0761b3fbb32f2ff125cfc557e41106bb7debd.json new file mode 100644 index 0000000..cd3e427 --- /dev/null +++ b/.sqlx/query-de3ae438ab25d585ae49ed2bf4b0761b3fbb32f2ff125cfc557e41106bb7debd.json @@ -0,0 +1,87 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n zc.id,\n zc.memo,\n zc.hash,\n pk_fee_payer.value AS fee_payer,\n zfpb.fee,\n zfpb.valid_until,\n zfpb.nonce,\n bzc.sequence_no,\n bzc.status AS \"status: TransactionStatus\",\n ARRAY(\n SELECT\n unnest(zauf.failures)\n FROM\n zkapp_account_update_failures AS zauf\n WHERE\n zauf.id=ANY (bzc.failure_reasons_ids)\n ) AS failure_reasons\nFROM\n blocks_zkapp_commands AS bzc\n INNER JOIN zkapp_commands AS zc ON bzc.zkapp_command_id=zc.id\n INNER JOIN zkapp_fee_payer_body AS zfpb ON zc.zkapp_fee_payer_body_id=zfpb.id\n INNER JOIN public_keys AS pk_fee_payer ON zfpb.public_key_id=pk_fee_payer.id\n INNER JOIN blocks AS b ON bzc.block_id=b.id\n LEFT JOIN zkapp_account_update AS zau ON zau.id=ANY (zc.zkapp_account_updates_ids)\n LEFT JOIN zkapp_account_update_body AS zaub ON zau.body_id=zaub.id\n LEFT JOIN account_identifiers AS ai_update_body ON zaub.account_identifier_id=ai_update_body.id\n LEFT JOIN public_keys AS pk_update_body ON ai_update_body.public_key_id=pk_update_body.id\n LEFT JOIN tokens AS token_update_body ON ai_update_body.token_id=token_update_body.id\nWHERE\n bzc.block_id=$1\n AND (\n token_update_body.value=$2\n OR token_update_body.id IS NULL\n )\nORDER BY\n zc.id,\n bzc.sequence_no\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "memo", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "hash", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "fee_payer", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "fee", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "valid_until", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "nonce", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "sequence_no", + "type_info": "Int4" + }, + { + "ordinal": 8, + "name": "status: TransactionStatus", + "type_info": { + "Custom": { + "name": "transaction_status", + "kind": { + "Enum": [ + "applied", + "failed" + ] + } + } + } + }, + { + "ordinal": 9, + "name": "failure_reasons", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + null + ] + }, + "hash": "de3ae438ab25d585ae49ed2bf4b0761b3fbb32f2ff125cfc557e41106bb7debd" +} diff --git a/sql/zkapps_commands.sql b/sql/zkapp_commands.sql similarity index 93% rename from sql/zkapps_commands.sql rename to sql/zkapp_commands.sql index c187952..8c6d497 100644 --- a/sql/zkapps_commands.sql +++ b/sql/zkapp_commands.sql @@ -1,11 +1,13 @@ SELECT - zc.*, + zc.id, + zc.memo, + zc.hash, pk_fee_payer.value AS fee_payer, zfpb.fee, zfpb.valid_until, zfpb.nonce, bzc.sequence_no, - bzc.status, + bzc.status AS "status: TransactionStatus", ARRAY( SELECT unnest(zauf.failures) diff --git a/src/api/block.rs b/src/api/block.rs index d454100..d96e3a0 100644 --- a/src/api/block.rs +++ b/src/api/block.rs @@ -31,9 +31,13 @@ impl MinaMesh { Some(block_metadata) => BlockIdentifier::new(block_metadata.height, block_metadata.state_hash), None => block_identifier.clone(), }; - let (mut user_commands, internal_commands) = - tokio::try_join!(self.user_commands(&metadata), self.internal_commands(&metadata))?; + let (mut 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()); Ok(BlockResponse { block: Some(Box::new(Block::new( block_identifier, @@ -74,6 +78,20 @@ impl MinaMesh { Ok(transactions) } + pub async fn zkapp_commands(&self, metadata: &BlockMetadata) -> Result, MinaMeshError> { + let metadata = sqlx::query_file_as!(ZkappCommandMetadata, "sql/zkapp_commands.sql", metadata.id, DEFAULT_TOKEN_ID) + .fetch_all(&self.pg_pool) + .await?; + let transactions = metadata + .into_iter() + .map(|item| { + zkapp_command_metadata_to_operation(&item) + .map(|operation| Transaction::new(TransactionIdentifier::new(item.hash.clone()), operation)) + }) + .collect::, MinaMeshError>>()?; + Ok(transactions) + } + pub async fn block_metadata( &self, PartialBlockIdentifier { index, hash }: &PartialBlockIdentifier, @@ -153,6 +171,42 @@ pub struct InternalCommandMetadata { coinbase_receiver: Option, } +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] +pub struct ZkappCommandMetadata { + id: i64, + memo: Option, + hash: String, + fee_payer: String, + fee: String, + valid_until: Option, + nonce: i64, + sequence_no: i64, + status: TransactionStatus, + failure_reasons: Option>, +} + +#[derive(Debug, PartialEq, Eq, FromRow, Serialize)] +pub struct ZkappAccountUpdateMetadata { + account_identifier_id: i32, + update_id: i32, + balance_change: String, + increment_nonce: bool, + events_id: i32, + actions_id: i32, + call_data_id: i32, + call_depth: i32, + zkapp_network_precondition_id: i32, + zkapp_account_precondition_id: i32, + zkapp_valid_while_precondition_id: Option, + use_full_commitment: bool, + implicit_account_creation_fee: bool, + may_use_token: String, + authorization_kind: String, + verification_key_hash_id: Option, + account: String, + token: String, +} + fn user_command_metadata_to_operations(metadata: &UserCommandMetadata) -> Vec { let mut operations = Vec::new(); if metadata.fee != "0" { @@ -225,6 +279,11 @@ fn internal_command_metadata_to_operation(metadata: &InternalCommandMetadata) -> Ok(operations) } +// TODO: implement +fn zkapp_command_metadata_to_operation(_metadata: &ZkappCommandMetadata) -> Result, MinaMeshError> { + Ok(Vec::new()) +} + fn operation( ident: i64, amount: Option<&String>, From ce26ccdd59c050b64b78f0bc616629a65e40fb6a Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 12:42:11 -0400 Subject: [PATCH 16/20] cspell exclude snapshots --- cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.json b/cspell.json index 2542756..e46a20c 100644 --- a/cspell.json +++ b/cspell.json @@ -17,6 +17,7 @@ "sql/**", "static/scalar.standalone.min.js", "target/**", + "tests/snapshots", "flake.*", "nix/**", ".gitignore" From 79deb9454f6d7cada8a46bf60d680e287da69ef4 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 12:57:28 -0400 Subject: [PATCH 17/20] change runner --- .github/workflows/checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 4a5dc91..df3cce0 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -41,7 +41,7 @@ jobs: - run: RUSTFLAGS="-D warnings" cargo clippy --all-targets test: - runs-on: ubuntu-latest + runs-on: minafoundation-xlarge-runners timeout-minutes: 10 steps: - uses: actions/checkout@v4 From 0e7096122917fc26dbf9c088172bedada3b6fcc8 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 12:58:11 -0400 Subject: [PATCH 18/20] fix cspell --- words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/words.txt b/words.txt index 3f75934..8e15b4b 100644 --- a/words.txt +++ b/words.txt @@ -42,5 +42,6 @@ urlencode utxo walkdir webroot +xlarge zkapp zkapps From be909a74ce48878a6368f4dbc814d8096dcf71a2 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Thu, 3 Oct 2024 13:14:17 -0400 Subject: [PATCH 19/20] fix cspell --- words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/words.txt b/words.txt index 8e15b4b..60db930 100644 --- a/words.txt +++ b/words.txt @@ -24,6 +24,7 @@ keypair lookback microschemas mina +minafoundation navroot nocapture openapi From 73f1ff4dd1d9e2344b776dfa69186aaf4fe53265 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Thu, 3 Oct 2024 19:19:43 +0200 Subject: [PATCH 20/20] try preexisting archive db --- .github/workflows/checks.yaml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index df3cce0..bc62ff8 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -41,7 +41,7 @@ jobs: - run: RUSTFLAGS="-D warnings" cargo clippy --all-targets test: - runs-on: minafoundation-xlarge-runners + runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -54,16 +54,12 @@ jobs: ~/.cargo/git target key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} - - name: Setup - run: | - cp .env.example .env - just get-mainnet-archive-db - just pg - just wait-for-pg - name: Test - run: just test - - name: Tear down - run: just pg-down + run: | + sed "s|postgres://mina:whatever@localhost:5432/archive|$MINAMESH_ARCHIVE_DATABASE_URL|g" .env.example > .env + just test + env: + MINAMESH_ARCHIVE_DATABASE_URL: ${{ secrets.MINAMESH_ARCHIVE_DATABASE_URL }} rustfmt: runs-on: ubuntu-latest