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 - } - } }