diff --git a/src/block_hash.rs b/src/block_hash.rs index 879a196..6a47694 100644 --- a/src/block_hash.rs +++ b/src/block_hash.rs @@ -3,3 +3,6 @@ pub mod event_commitment; pub mod receipt_commitment; pub mod state_diff_hash; pub mod transaction_commitment; + +#[cfg(test)] +pub mod test_utils; diff --git a/src/block_hash/block_hash_calculator.rs b/src/block_hash/block_hash_calculator.rs index 1b5f544..315bad0 100644 --- a/src/block_hash/block_hash_calculator.rs +++ b/src/block_hash/block_hash_calculator.rs @@ -1,10 +1,84 @@ +use super::event_commitment::{calculate_events_commitment, EventLeafElement}; +use super::receipt_commitment::{calculate_receipt_commitment, ReceiptElement}; +use super::state_diff_hash::calculate_state_diff_hash; +use super::transaction_commitment::{calculate_transactions_commitment, TransactionLeafElement}; +use crate::block::GasPricePerToken; +use crate::core::{EventCommitment, ReceiptCommitment, StateDiffCommitment, TransactionCommitment}; use crate::data_availability::L1DataAvailabilityMode; -use crate::hash::StarkFelt; +use crate::hash::{PoseidonHashCalculator, StarkFelt}; +use crate::state::ThinStateDiff; +use crate::transaction::{ + TransactionHash, TransactionOutput, TransactionSignature, TransactionVersion, +}; #[cfg(test)] #[path = "block_hash_calculator_test.rs"] mod block_hash_calculator_test; +pub struct TransactionHashingData { + pub transaction_signature: Option, + pub transaction_output: TransactionOutput, + pub transaction_hash: TransactionHash, + pub transaction_version: TransactionVersion, +} + +/// Commitments of a block. +pub struct BlockHeaderCommitments { + pub transactions_commitment: TransactionCommitment, + pub events_commitment: EventCommitment, + pub receipts_commitment: ReceiptCommitment, + pub state_diff_commitment: StateDiffCommitment, + pub concatenated_counts: StarkFelt, +} + +/// Calculates the commitments of the transactions data for the block hash. +pub fn calculate_block_commitments( + transactions_data: &[TransactionHashingData], + state_diff: &ThinStateDiff, + l1_data_gas_price_per_token: GasPricePerToken, + l1_gas_price_per_token: GasPricePerToken, + l1_da_mode: L1DataAvailabilityMode, +) -> BlockHeaderCommitments { + let transaction_leaf_elements: Vec = + transactions_data.iter().map(TransactionLeafElement::from).collect(); + let transactions_commitment = + calculate_transactions_commitment::(&transaction_leaf_elements); + + let event_leaf_elements: Vec = transactions_data + .iter() + .flat_map(|transaction_data| { + transaction_data.transaction_output.events().iter().map(|event| EventLeafElement { + event: event.clone(), + transaction_hash: transaction_data.transaction_hash, + }) + }) + .collect(); + let events_commitment = + calculate_events_commitment::(&event_leaf_elements); + + let receipt_elements: Vec = + transactions_data.iter().map(ReceiptElement::from).collect(); + let receipts_commitment = calculate_receipt_commitment::( + &receipt_elements, + l1_data_gas_price_per_token, + l1_gas_price_per_token, + ); + let state_diff_commitment = calculate_state_diff_hash(state_diff); + let concatenated_counts = concat_counts( + transactions_data.len(), + event_leaf_elements.len(), + state_diff.len(), + l1_da_mode, + ); + BlockHeaderCommitments { + transactions_commitment, + events_commitment, + receipts_commitment, + state_diff_commitment, + concatenated_counts, + } +} + // A single felt: [ // transaction_count (64 bits) | event_count (64 bits) | state_diff_length (64 bits) // | L1 data availability mode: 0 for calldata, 1 for blob (1 bit) | 0 ... diff --git a/src/block_hash/event_commitment.rs b/src/block_hash/event_commitment.rs index 53c4ae3..f0e66b2 100644 --- a/src/block_hash/event_commitment.rs +++ b/src/block_hash/event_commitment.rs @@ -11,8 +11,8 @@ mod event_commitment_test; /// The elements used to calculate a leaf in the transactions Patricia tree. #[derive(Clone)] pub struct EventLeafElement { - event: Event, - transaction_hash: TransactionHash, + pub(crate) event: Event, + pub(crate) transaction_hash: TransactionHash, } /// Returns the root of a Patricia tree where each leaf is an event hash. diff --git a/src/block_hash/event_commitment_test.rs b/src/block_hash/event_commitment_test.rs index 7e95b85..80c0739 100644 --- a/src/block_hash/event_commitment_test.rs +++ b/src/block_hash/event_commitment_test.rs @@ -1,5 +1,5 @@ -use super::calculate_event_hash; -use crate::block_hash::event_commitment::{calculate_events_commitment, EventLeafElement}; +use super::{calculate_event_hash, EventLeafElement}; +use crate::block_hash::event_commitment::calculate_events_commitment; use crate::core::{ContractAddress, EventCommitment, PatriciaKey}; use crate::hash::{PoseidonHashCalculator, StarkFelt, StarkHash}; use crate::transaction::{Event, EventContent, EventData, EventKey, TransactionHash}; diff --git a/src/block_hash/receipt_commitment.rs b/src/block_hash/receipt_commitment.rs index 7f070cb..98073b8 100644 --- a/src/block_hash/receipt_commitment.rs +++ b/src/block_hash/receipt_commitment.rs @@ -1,34 +1,47 @@ +use super::block_hash_calculator::TransactionHashingData; use crate::block::{GasPrice, GasPricePerToken}; use crate::core::ReceiptCommitment; use crate::crypto::patricia_hash::calculate_root; use crate::crypto::utils::HashChain; use crate::hash::{starknet_keccak_hash, HashFunction, StarkFelt}; use crate::transaction::{ - ExecutionResources, Fee, MessageToL1, TransactionExecutionStatus, TransactionReceipt, - TransactionVersion, + ExecutionResources, Fee, MessageToL1, TransactionExecutionStatus, TransactionHash, + TransactionOutput, TransactionVersion, }; #[cfg(test)] #[path = "receipt_commitment_test.rs"] mod receipt_commitment_test; +// The elements used to calculate a leaf in the transactions Patricia tree. +#[derive(Clone)] +pub struct ReceiptElement { + pub transaction_hash: TransactionHash, + pub transaction_output: TransactionOutput, + pub transaction_version: TransactionVersion, +} + +impl From<&TransactionHashingData> for ReceiptElement { + fn from(transaction_data: &TransactionHashingData) -> Self { + Self { + transaction_hash: transaction_data.transaction_hash, + transaction_output: transaction_data.transaction_output.clone(), + transaction_version: transaction_data.transaction_version, + } + } +} + /// Returns the root of a Patricia tree where each leaf is a receipt hash. pub fn calculate_receipt_commitment( - transactions_receipt: &[TransactionReceipt], - transaction_version: &TransactionVersion, + receipt_elements: &[ReceiptElement], l1_data_gas_price_per_token: GasPricePerToken, l1_gas_price_per_token: GasPricePerToken, ) -> ReceiptCommitment { ReceiptCommitment(calculate_root::( - transactions_receipt + receipt_elements .iter() .map(|receipt| { - calculate_receipt_hash( - receipt, - transaction_version, - l1_data_gas_price_per_token, - l1_gas_price_per_token, - ) + calculate_receipt_hash(receipt, l1_data_gas_price_per_token, l1_gas_price_per_token) }) .collect(), )) @@ -39,22 +52,23 @@ pub fn calculate_receipt_commitment( // execution resources // ). fn calculate_receipt_hash( - transaction_receipt: &TransactionReceipt, - transaction_version: &TransactionVersion, + receipt_element: &ReceiptElement, l1_data_gas_price_per_token: GasPricePerToken, l1_gas_price_per_token: GasPricePerToken, ) -> StarkFelt { - let l1_gas_price = get_price_by_version(l1_gas_price_per_token, transaction_version); - let l1_data_gas_price = get_price_by_version(l1_data_gas_price_per_token, transaction_version); + let l1_gas_price = + get_price_by_version(l1_gas_price_per_token, &receipt_element.transaction_version); + let l1_data_gas_price = + get_price_by_version(l1_data_gas_price_per_token, &receipt_element.transaction_version); let hash_chain = HashChain::new() - .chain(&transaction_receipt.transaction_hash) - .chain(&transaction_receipt.output.actual_fee().0.into()) - .chain(&calculate_messages_sent_hash(transaction_receipt.output.messages_sent())) - .chain(&get_revert_reason_hash(transaction_receipt.output.execution_status())); + .chain(&receipt_element.transaction_hash) + .chain(&receipt_element.transaction_output.actual_fee().0.into()) + .chain(&calculate_messages_sent_hash(receipt_element.transaction_output.messages_sent())) + .chain(&get_revert_reason_hash(receipt_element.transaction_output.execution_status())); chain_execution_resources( hash_chain, - transaction_receipt.output.execution_resources(), - transaction_receipt.output.actual_fee(), + receipt_element.transaction_output.execution_resources(), + receipt_element.transaction_output.actual_fee(), l1_data_gas_price, l1_gas_price, ) diff --git a/src/block_hash/receipt_commitment_test.rs b/src/block_hash/receipt_commitment_test.rs index 037b6bf..eb49f7b 100644 --- a/src/block_hash/receipt_commitment_test.rs +++ b/src/block_hash/receipt_commitment_test.rs @@ -1,45 +1,22 @@ -use std::collections::HashMap; - -use primitive_types::H160; - use super::calculate_messages_sent_hash; -use crate::block::{BlockHash, BlockNumber, GasPrice, GasPricePerToken}; +use crate::block::{GasPrice, GasPricePerToken}; use crate::block_hash::receipt_commitment::{ - calculate_receipt_commitment, calculate_receipt_hash, get_revert_reason_hash, + calculate_receipt_commitment, calculate_receipt_hash, get_revert_reason_hash, ReceiptElement, }; -use crate::core::{ContractAddress, EthAddress, ReceiptCommitment}; +use crate::block_hash::test_utils::{generate_message_to_l1, get_transaction_output}; +use crate::core::ReceiptCommitment; use crate::hash::{PoseidonHashCalculator, StarkFelt}; use crate::transaction::{ - Builtin, ExecutionResources, Fee, InvokeTransactionOutput, L2ToL1Payload, MessageToL1, RevertedTransactionExecutionStatus, TransactionExecutionStatus, TransactionHash, - TransactionOutput, TransactionReceipt, TransactionVersion, + TransactionVersion, }; #[test] fn test_receipt_hash_regression() { - let execution_status = - TransactionExecutionStatus::Reverted(RevertedTransactionExecutionStatus { - revert_reason: "aborted".to_string(), - }); - let execution_resources = ExecutionResources { - steps: 98, - builtin_instance_counter: HashMap::from([(Builtin::Bitwise, 11), (Builtin::EcOp, 22)]), - memory_holes: 76, - da_l1_gas_consumed: 54, - da_l1_data_gas_consumed: 32, - }; - let invoke_output = TransactionOutput::Invoke(InvokeTransactionOutput { - actual_fee: Fee(99804), - messages_sent: vec![generate_message_to_l1(34), generate_message_to_l1(56)], - events: vec![], - execution_status, - execution_resources, - }); - let transaction_receipt = TransactionReceipt { + let mut transaction_receipt = ReceiptElement { transaction_hash: TransactionHash(StarkFelt::from(1234_u16)), - block_hash: BlockHash(StarkFelt::from(5678_u16)), - block_number: BlockNumber(99), - output: invoke_output, + transaction_output: get_transaction_output(), + transaction_version: TransactionVersion::TWO, }; let l1_data_gas_price = GasPricePerToken { price_in_fri: GasPrice(123), price_in_wei: GasPrice(456) }; @@ -50,12 +27,7 @@ fn test_receipt_hash_regression() { StarkFelt::try_from("0x06cb27bfc55dee54e6d0fc7a6790e39f0f3c003576d50f7b8e8a1be24c351bcf") .unwrap(); assert_eq!( - calculate_receipt_hash( - &transaction_receipt, - &TransactionVersion::TWO, - l1_data_gas_price, - l1_gas_price - ), + calculate_receipt_hash(&transaction_receipt, l1_data_gas_price, l1_gas_price), expected_hash ); @@ -63,10 +35,12 @@ fn test_receipt_hash_regression() { StarkFelt::try_from("0x03a0af1272fc3b0b83894fd7b6b70d89acb07772bc28efc9091e3cc1c2c72493") .unwrap(), ); + + // Test for a V3 transactions. + transaction_receipt.transaction_version = TransactionVersion::THREE; assert_eq!( calculate_receipt_commitment::( &[transaction_receipt], - &TransactionVersion::THREE, l1_data_gas_price, l1_gas_price ), @@ -84,14 +58,6 @@ fn test_messages_sent_regression() { assert_eq!(messages_hash, expected_hash); } -fn generate_message_to_l1(seed: u64) -> MessageToL1 { - MessageToL1 { - from_address: ContractAddress::from(seed), - to_address: EthAddress(H160::from_low_u64_be(seed + 1)), - payload: L2ToL1Payload(vec![StarkFelt::from(seed + 2), StarkFelt::from(seed + 3)]), - } -} - #[test] fn test_revert_reason_hash_regression() { let execution_succeeded = TransactionExecutionStatus::Succeeded; diff --git a/src/block_hash/test_utils.rs b/src/block_hash/test_utils.rs new file mode 100644 index 0000000..d895d2d --- /dev/null +++ b/src/block_hash/test_utils.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use primitive_types::H160; + +use crate::core::{ContractAddress, EthAddress}; +use crate::hash::StarkFelt; +use crate::transaction::{ + Builtin, ExecutionResources, Fee, InvokeTransactionOutput, L2ToL1Payload, MessageToL1, + RevertedTransactionExecutionStatus, TransactionExecutionStatus, TransactionOutput, +}; + +pub(crate) fn get_transaction_output() -> TransactionOutput { + let execution_status = + TransactionExecutionStatus::Reverted(RevertedTransactionExecutionStatus { + revert_reason: "aborted".to_string(), + }); + let execution_resources = ExecutionResources { + steps: 98, + builtin_instance_counter: HashMap::from([(Builtin::Bitwise, 11), (Builtin::EcOp, 22)]), + memory_holes: 76, + da_l1_gas_consumed: 54, + da_l1_data_gas_consumed: 32, + }; + TransactionOutput::Invoke(InvokeTransactionOutput { + actual_fee: Fee(99804), + messages_sent: vec![generate_message_to_l1(34), generate_message_to_l1(56)], + events: vec![], + execution_status, + execution_resources, + }) +} + +pub(crate) fn generate_message_to_l1(seed: u64) -> MessageToL1 { + MessageToL1 { + from_address: ContractAddress::from(seed), + to_address: EthAddress(H160::from_low_u64_be(seed + 1)), + payload: L2ToL1Payload(vec![StarkFelt::from(seed + 2), StarkFelt::from(seed + 3)]), + } +} diff --git a/src/block_hash/transaction_commitment.rs b/src/block_hash/transaction_commitment.rs index b56b7fb..e766090 100644 --- a/src/block_hash/transaction_commitment.rs +++ b/src/block_hash/transaction_commitment.rs @@ -1,3 +1,4 @@ +use super::block_hash_calculator::TransactionHashingData; use crate::core::TransactionCommitment; use crate::crypto::patricia_hash::calculate_root; use crate::crypto::utils::HashChain; @@ -10,24 +11,41 @@ mod transaction_commitment_test; /// The elements used to calculate a leaf in the transactions Patricia tree. #[derive(Clone)] -pub struct TransactionLeafElements { - transaction_hash: TransactionHash, - transaction_signature: TransactionSignature, +pub struct TransactionLeafElement { + pub(crate) transaction_hash: TransactionHash, + pub(crate) transaction_signature: Option, +} + +impl From<&TransactionHashingData> for TransactionLeafElement { + fn from(transaction_data: &TransactionHashingData) -> Self { + Self { + transaction_hash: transaction_data.transaction_hash, + transaction_signature: transaction_data.transaction_signature.clone(), + } + } } /// Returns the root of a Patricia tree where each leaf is -/// Poseidon(transaction_hash, transaction_signature). +/// H(transaction_hash, transaction_signature). +/// The leaf of a transaction types without a signature field is: H(transaction_hash, 0). pub fn calculate_transactions_commitment( - transaction_leaf_elements: &[TransactionLeafElements], + transaction_leaf_elements: &[TransactionLeafElement], ) -> TransactionCommitment { let transaction_leaves = transaction_leaf_elements.iter().map(calculate_transaction_leaf).collect(); TransactionCommitment(calculate_root::(transaction_leaves)) } -fn calculate_transaction_leaf(transaction_leaf_elements: &TransactionLeafElements) -> StarkFelt { +fn calculate_transaction_leaf(transaction_leaf_elements: &TransactionLeafElement) -> StarkFelt { HashChain::new() .chain(&transaction_leaf_elements.transaction_hash.0) - .chain_iter(transaction_leaf_elements.transaction_signature.0.iter()) + .chain_iter( + transaction_leaf_elements + .transaction_signature + .as_ref() + .unwrap_or(&TransactionSignature(vec![StarkFelt::ZERO])) + .0 + .iter(), + ) .get_poseidon_hash() } diff --git a/src/block_hash/transaction_commitment_test.rs b/src/block_hash/transaction_commitment_test.rs index e4f0623..1f7131e 100644 --- a/src/block_hash/transaction_commitment_test.rs +++ b/src/block_hash/transaction_commitment_test.rs @@ -1,5 +1,6 @@ +use super::TransactionLeafElement; use crate::block_hash::transaction_commitment::{ - calculate_transaction_leaf, calculate_transactions_commitment, TransactionLeafElements, + calculate_transaction_leaf, calculate_transactions_commitment, }; use crate::core::TransactionCommitment; use crate::hash::{PoseidonHashCalculator, StarkFelt}; @@ -7,11 +8,7 @@ use crate::transaction::{TransactionHash, TransactionSignature}; #[test] fn test_transaction_leaf_regression() { - let transaction_hash = TransactionHash(StarkFelt::ONE); - let transaction_signature = TransactionSignature(vec![StarkFelt::TWO, StarkFelt::THREE]); - let transaction_leaf_elements = - TransactionLeafElements { transaction_hash, transaction_signature }; - + let transaction_leaf_elements = get_transaction_leaf_element(); let expected_leaf = StarkFelt::try_from("0x2f0d8840bcf3bc629598d8a6cc80cb7c0d9e52d93dab244bbf9cd0dca0ad082") .unwrap(); @@ -20,12 +17,21 @@ fn test_transaction_leaf_regression() { } #[test] -fn test_transactions_commitment_regression() { - let transaction_hash = TransactionHash(StarkFelt::ONE); - let transaction_signature = TransactionSignature(vec![StarkFelt::TWO, StarkFelt::THREE]); - let transaction_leaf_elements = - TransactionLeafElements { transaction_hash, transaction_signature }; +fn test_transaction_leaf_without_signature_regression() { + let transaction_leaf_elements = TransactionLeafElement { + transaction_hash: TransactionHash(StarkFelt::ONE), + transaction_signature: None, + }; + let expected_leaf = + StarkFelt::try_from("0x00a93bf5e58b9378d093aa86ddc2f61a3295a1d1e665bd0ef3384dd07b30e033") + .unwrap(); + assert_eq!(expected_leaf, calculate_transaction_leaf(&transaction_leaf_elements)); +} + +#[test] +fn test_transactions_commitment_regression() { + let transaction_leaf_elements = get_transaction_leaf_element(); let expected_root = StarkFelt::try_from("0x0282b635972328bd1cfa86496fe920d20bd9440cd78ee8dc90ae2b383d664dcf") .unwrap(); @@ -38,3 +44,9 @@ fn test_transactions_commitment_regression() { ],) ); } + +fn get_transaction_leaf_element() -> TransactionLeafElement { + let transaction_hash = TransactionHash(StarkFelt::ONE); + let transaction_signature = TransactionSignature(vec![StarkFelt::TWO, StarkFelt::THREE]); + TransactionLeafElement { transaction_hash, transaction_signature: Some(transaction_signature) } +}