From 9e03a3b71e0e9e01965f78c084d3f44ebc5f85a2 Mon Sep 17 00:00:00 2001 From: Lukasz Rubaszewski <117115317+lrubasze@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:25:59 +0100 Subject: [PATCH] Reimplement block hash --- .../src/mesh_api/conversions/block.rs | 106 +++++++++++++----- .../src/mesh_api/conversions/errors.rs | 1 + .../src/mesh_api/handlers/account_balance.rs | 1 + .../src/mesh_api/handlers/block.rs | 12 +- .../mesh_api/handlers/block_transaction.rs | 8 +- .../src/mesh_api/handlers/network_status.rs | 3 + 6 files changed, 96 insertions(+), 35 deletions(-) diff --git a/core-rust/mesh-api-server/src/mesh_api/conversions/block.rs b/core-rust/mesh-api-server/src/mesh_api/conversions/block.rs index ed161d79fc..b729aee398 100644 --- a/core-rust/mesh-api-server/src/mesh_api/conversions/block.rs +++ b/core-rust/mesh-api-server/src/mesh_api/conversions/block.rs @@ -2,32 +2,70 @@ use crate::prelude::*; const MAX_API_STATE_VERSION: u64 = 100000000000000; +/// We assume that Block is a single transaction. +/// Block index => State version +/// Block hash => 32 bytes of: transaction_tree_hash[0..12] | receipt_tree_hash[0..12] | state_version +pub fn extract_state_version_from_block_hash( + database: &StateManagerDatabase, + block_hash: &str, +) -> Result { + if block_hash.len() == 32 { + let hash_bytes = + hex::decode(block_hash).map_err(|_| ExtractionError::InvalidBlockIdentifier { + message: format!("Error decoding block hash {}", block_hash), + })?; + + let mut index_bytes: [u8; 8] = [0; 8]; + index_bytes.copy_from_slice(&hash_bytes[24..]); + let index = u64::from_be_bytes(index_bytes); + + let state_version = StateVersion::of(index); + let transaction_identifiers = database + .get_committed_transaction_identifiers(state_version) + .ok_or_else(|| ExtractionError::NotFound)?; + + let transaction_tree_hash = transaction_identifiers + .resultant_ledger_hashes + .transaction_root; + let receipt_tree_hash = transaction_identifiers.resultant_ledger_hashes.receipt_root; + + if hash_bytes[..12] != transaction_tree_hash.as_slice()[0..12] { + return Err(ExtractionError::InvalidBlockIdentifier { + message: format!( + "Block hash {} does not match transaction tree hash", + block_hash + ), + }); + } + if hash_bytes[12..24] != receipt_tree_hash.as_slice()[0..12] { + return Err(ExtractionError::InvalidBlockIdentifier { + message: format!("Block hash {} does not match receipt tree hash", block_hash), + }); + } + + Ok(state_version) + } else { + Err(ExtractionError::InvalidBlockIdentifier { + message: format!("hash length {} not equal 32", block_hash.len()), + }) + } +} + pub fn extract_state_version_from_mesh_api_partial_block_identifier( + database: &StateManagerDatabase, block_identifier: &models::PartialBlockIdentifier, ) -> Result, ExtractionError> { let state_version = match (&block_identifier.hash, &block_identifier.index) { (None, None) => None, - (Some(hash), None) => { - let index_from_hash = - hash.parse::() - .map_err(|_| ExtractionError::InvalidBlockIdentifier { - message: "Error converting hash to integer".to_string(), - })?; - - Some(StateVersion::of(index_from_hash as u64)) - } + (Some(hash), None) => Some(extract_state_version_from_block_hash(database, hash)?), (None, Some(index)) => Some(StateVersion::of(*index as u64)), (Some(hash), Some(index)) => { - let index_from_hash = - hash.parse::() - .map_err(|_| ExtractionError::InvalidBlockIdentifier { - message: "Error converting hash to integer".to_string(), - })?; - if *index == index_from_hash { - Some(StateVersion::of(index_from_hash as u64)) + let state_version = extract_state_version_from_block_hash(database, hash)?; + if *index as u64 == state_version.number() { + Some(state_version) } else { return Err(ExtractionError::InvalidBlockIdentifier { - message: format!("Hash {} does not match index {}", index_from_hash, index), + message: format!("Hash {} does not match index {}", hash, index), }); } } @@ -37,14 +75,12 @@ pub fn extract_state_version_from_mesh_api_partial_block_identifier( } pub fn extract_state_version_from_mesh_api_block_identifier( + database: &StateManagerDatabase, block_identifier: &models::BlockIdentifier, ) -> Result { - let index_from_hash = block_identifier.hash.parse::().map_err(|_| { - ExtractionError::InvalidBlockIdentifier { - message: "Error converting hash to integer".to_string(), - } - })?; - if block_identifier.index != index_from_hash { + let state_version_from_hash = + extract_state_version_from_block_hash(database, &block_identifier.hash)?; + if block_identifier.index as u64 != state_version_from_hash.number() { Err(ExtractionError::InvalidBlockIdentifier { message: format!( "index {} and hash {} mismatch", @@ -56,23 +92,37 @@ pub fn extract_state_version_from_mesh_api_block_identifier( } } -/// We assume that Block is a single transaction. -/// Block index => State version -/// Block hash => State version printed to string and prefixed with zeros pub fn to_mesh_api_block_identifier_from_state_version( + database: &StateManagerDatabase, state_version: StateVersion, ) -> Result { let index = to_mesh_api_block_index_from_state_version(state_version)?; + let transaction_identifiers = database + .get_committed_transaction_identifiers(state_version) + .ok_or_else(|| MappingError::TransactionNotFound)?; + + let transaction_tree_hash = transaction_identifiers + .resultant_ledger_hashes + .transaction_root; + let receipt_tree_hash = transaction_identifiers.resultant_ledger_hashes.receipt_root; + + let mut hash_bytes = [0u8; 32]; + + hash_bytes[..12].copy_from_slice(&transaction_tree_hash.as_slice()[..12]); + hash_bytes[12..24].copy_from_slice(&receipt_tree_hash.as_slice()[..12]); + hash_bytes[24..].copy_from_slice((index as u64).to_be_bytes().as_slice()); + Ok(models::BlockIdentifier { index, - hash: format!("{:0>32}", index), + hash: hex::encode(hash_bytes), }) } pub fn to_mesh_api_block_identifier_from_ledger_header( + database: &StateManagerDatabase, ledger_header: &LedgerStateSummary, ) -> Result { - to_mesh_api_block_identifier_from_state_version(ledger_header.state_version) + to_mesh_api_block_identifier_from_state_version(database, ledger_header.state_version) } pub fn to_mesh_api_block_index_from_state_version( diff --git a/core-rust/mesh-api-server/src/mesh_api/conversions/errors.rs b/core-rust/mesh-api-server/src/mesh_api/conversions/errors.rs index ddae888142..a341a9025b 100644 --- a/core-rust/mesh-api-server/src/mesh_api/conversions/errors.rs +++ b/core-rust/mesh-api-server/src/mesh_api/conversions/errors.rs @@ -32,6 +32,7 @@ pub enum MappingError { InternalIndexDataMismatch { message: String, }, + TransactionNotFound, } impl From for ResponseError { diff --git a/core-rust/mesh-api-server/src/mesh_api/handlers/account_balance.rs b/core-rust/mesh-api-server/src/mesh_api/handlers/account_balance.rs index ba092bc36d..36753b0e3e 100644 --- a/core-rust/mesh-api-server/src/mesh_api/handlers/account_balance.rs +++ b/core-rust/mesh-api-server/src/mesh_api/handlers/account_balance.rs @@ -68,6 +68,7 @@ pub(crate) async fn handle_account_balance( // definitions Ok(Json(models::AccountBalanceResponse { block_identifier: Box::new(to_mesh_api_block_identifier_from_ledger_header( + database.deref(), &header.into(), )?), balances, diff --git a/core-rust/mesh-api-server/src/mesh_api/handlers/block.rs b/core-rust/mesh-api-server/src/mesh_api/handlers/block.rs index 17d867318b..fdf4478d36 100644 --- a/core-rust/mesh-api-server/src/mesh_api/handlers/block.rs +++ b/core-rust/mesh-api-server/src/mesh_api/handlers/block.rs @@ -9,10 +9,12 @@ pub(crate) async fn handle_block( let database = state.state_manager.database.snapshot(); let mapping_context = MappingContext::new(&state.network); - let state_version = - extract_state_version_from_mesh_api_partial_block_identifier(&request.block_identifier) - .map_err(|err| err.into_response_error("block_identifier"))? - .unwrap_or_else(|| database.max_state_version()); + let state_version = extract_state_version_from_mesh_api_partial_block_identifier( + database.deref(), + &request.block_identifier, + ) + .map_err(|err| err.into_response_error("block_identifier"))? + .unwrap_or_else(|| database.max_state_version()); let previous_state_version = state_version.previous().map_err(|_| { ResponseError::from(ApiError::ParentBlockNotAvailable).with_details(format!( @@ -49,9 +51,11 @@ pub(crate) async fn handle_block( // see https://docs.cdp.coinbase.com/mesh/docs/models#block let block = models::Block { block_identifier: Box::new(to_mesh_api_block_identifier_from_state_version( + database.deref(), state_version, )?), parent_block_identifier: Box::new(to_mesh_api_block_identifier_from_state_version( + database.deref(), previous_state_version, )?), timestamp: transaction_identifiers.proposer_timestamp_ms, diff --git a/core-rust/mesh-api-server/src/mesh_api/handlers/block_transaction.rs b/core-rust/mesh-api-server/src/mesh_api/handlers/block_transaction.rs index 8d0dab5276..064b90fcb5 100644 --- a/core-rust/mesh-api-server/src/mesh_api/handlers/block_transaction.rs +++ b/core-rust/mesh-api-server/src/mesh_api/handlers/block_transaction.rs @@ -9,9 +9,11 @@ pub(crate) async fn handle_block_transaction( let database = state.state_manager.database.snapshot(); let mapping_context = MappingContext::new(&state.network); - let state_version = - extract_state_version_from_mesh_api_block_identifier(&request.block_identifier) - .map_err(|err| err.into_response_error("block_identifier"))?; + let state_version = extract_state_version_from_mesh_api_block_identifier( + database.deref(), + &request.block_identifier, + ) + .map_err(|err| err.into_response_error("block_identifier"))?; let transaction_identifiers = database .get_committed_transaction_identifiers(state_version) diff --git a/core-rust/mesh-api-server/src/mesh_api/handlers/network_status.rs b/core-rust/mesh-api-server/src/mesh_api/handlers/network_status.rs index a4b140ad47..0485fa73f4 100644 --- a/core-rust/mesh-api-server/src/mesh_api/handlers/network_status.rs +++ b/core-rust/mesh-api-server/src/mesh_api/handlers/network_status.rs @@ -21,6 +21,7 @@ pub(crate) async fn handle_network_status( .get_post_genesis_epoch_proof() .map(|proof| -> Result<_, MappingError> { Ok(Box::new(to_mesh_api_block_identifier_from_ledger_header( + database.deref(), &proof.ledger_header.into(), )?)) }) @@ -30,6 +31,7 @@ pub(crate) async fn handle_network_status( .get_first_proof() .map(|proof| -> Result<_, MappingError> { Ok(Box::new(to_mesh_api_block_identifier_from_ledger_header( + database.deref(), &proof.ledger_header.into(), )?)) }) @@ -42,6 +44,7 @@ pub(crate) async fn handle_network_status( .get_latest_proof() .map(|proof| -> Result<_, MappingError> { Ok(Box::new(to_mesh_api_block_identifier_from_ledger_header( + database.deref(), &proof.ledger_header.into(), )?)) })