From 7760bb9185eea228354cf6405f1db552d1548e77 Mon Sep 17 00:00:00 2001 From: Yoav Gross Date: Thu, 9 May 2024 14:09:27 +0300 Subject: [PATCH] feat: calculate block hash --- src/block.rs | 14 +++++ src/block_hash/block_hash_calculator.rs | 59 ++++++++++++++++++-- src/block_hash/block_hash_calculator_test.rs | 42 ++++++++++++++ src/block_hash/event_commitment_test.rs | 5 +- src/block_hash/state_diff_hash_test.rs | 29 +--------- src/block_hash/test_utils.rs | 33 ++++++++++- src/transaction.rs | 10 ++++ 7 files changed, 157 insertions(+), 35 deletions(-) diff --git a/src/block.rs b/src/block.rs index ec6be5b..51b1488 100644 --- a/src/block.rs +++ b/src/block.rs @@ -75,6 +75,20 @@ pub struct BlockHeader { pub starknet_version: StarknetVersion, } +/// The header of a [Block](`crate::block::Block`) without hashing. +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] +pub struct BlockHeaderWithoutHash { + pub parent_hash: BlockHash, + pub block_number: BlockNumber, + pub l1_gas_price: GasPricePerToken, + pub l1_data_gas_price: GasPricePerToken, + pub state_root: GlobalRoot, + pub sequencer: SequencerContractAddress, + pub timestamp: BlockTimestamp, + pub l1_da_mode: L1DataAvailabilityMode, + pub starknet_version: StarknetVersion, +} + /// The [transactions](`crate::transaction::Transaction`) and their /// [outputs](`crate::transaction::TransactionOutput`) in a [block](`crate::block::Block`). #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] diff --git a/src/block_hash/block_hash_calculator.rs b/src/block_hash/block_hash_calculator.rs index 0b9c926..76bb1e1 100644 --- a/src/block_hash/block_hash_calculator.rs +++ b/src/block_hash/block_hash_calculator.rs @@ -1,18 +1,28 @@ +use once_cell::sync::Lazy; + 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::block::{BlockHash, BlockHeaderWithoutHash, GasPricePerToken}; use crate::core::{EventCommitment, ReceiptCommitment, TransactionCommitment}; +use crate::crypto::utils::HashChain; use crate::data_availability::L1DataAvailabilityMode; use crate::hash::{PoseidonHashCalculator, StarkFelt}; use crate::transaction::{ TransactionHash, TransactionOutput, TransactionSignature, TransactionVersion, }; +use crate::state::ThinStateDiff; +use crate::transaction_hash::ascii_as_felt; #[cfg(test)] #[path = "block_hash_calculator_test.rs"] mod block_hash_calculator_test; +static STARKNET_BLOCK_HASH0: Lazy = Lazy::new(|| { + ascii_as_felt("STARKNET_BLOCK_HASH0").expect("ascii_as_felt failed for 'STARKNET_BLOCK_HASH0'") +}); + pub struct TransactionHashingData { pub transaction_signature: Option, pub transaction_output: TransactionOutput, @@ -20,7 +30,6 @@ pub struct TransactionHashingData { pub transaction_version: TransactionVersion, } -#[allow(dead_code)] struct BlockHeaderCommitments { pub n_transactions: usize, pub transactions_commitment: TransactionCommitment, @@ -29,8 +38,51 @@ struct BlockHeaderCommitments { pub receipts_commitment: ReceiptCommitment, } +/// Poseidon ( +/// “STARKNET_BLOCK_HASH0”, block_number, global_state_root, sequencer_address, +/// block_timestamp, concat_counts, state_diff_hash, transaction_commitment, +/// event_commitment, receipt_commitment, gas_price_wei, gas_price_fri, +/// data_gas_price_wei, data_gas_price_fri, starknet_version, 0, parent_block_hash +/// ). +pub fn calculate_block_hash( + header: BlockHeaderWithoutHash, + transactions_data: &[TransactionHashingData], + state_diff: &ThinStateDiff, +) -> BlockHash { + let block_commitments = calculate_block_commitments( + transactions_data, + header.l1_data_gas_price, + header.l1_gas_price, + ); + BlockHash( + HashChain::new() + .chain(&STARKNET_BLOCK_HASH0) + .chain(&header.block_number.0.into()) + .chain(&header.state_root.0) + .chain(&header.sequencer.0) + .chain(&header.timestamp.0.into()) + .chain(&concat_counts( + block_commitments.n_transactions, + block_commitments.n_events, + state_diff.len(), + header.l1_da_mode, + )) + .chain(&calculate_state_diff_hash(state_diff).0.0) + .chain(&block_commitments.transactions_commitment.0) + .chain(&block_commitments.events_commitment.0) + .chain(&block_commitments.receipts_commitment.0) + .chain(&header.l1_gas_price.price_in_wei.0.into()) + .chain(&header.l1_gas_price.price_in_fri.0.into()) + .chain(&header.l1_data_gas_price.price_in_wei.0.into()) + .chain(&header.l1_data_gas_price.price_in_fri.0.into()) + .chain(&ascii_as_felt(&header.starknet_version.0).expect("Expect ASCII version")) + .chain(&StarkFelt::ZERO) + .chain(&header.parent_hash.0) + .get_poseidon_hash(), + ) +} + // Calculates the commitments of the transactions data for the block hash. -#[allow(dead_code)] fn calculate_block_commitments( transactions_data: &[TransactionHashingData], l1_data_gas_price_per_token: GasPricePerToken, @@ -83,7 +135,6 @@ fn calculate_block_commitments( // 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 ... // ]. -#[allow(dead_code)] fn concat_counts( transaction_count: usize, event_count: usize, diff --git a/src/block_hash/block_hash_calculator_test.rs b/src/block_hash/block_hash_calculator_test.rs index b015041..15caa70 100644 --- a/src/block_hash/block_hash_calculator_test.rs +++ b/src/block_hash/block_hash_calculator_test.rs @@ -1,6 +1,48 @@ use super::concat_counts; +use crate::block::{ + BlockHash, BlockHeaderWithoutHash, BlockNumber, BlockTimestamp, GasPrice, GasPricePerToken, + StarknetVersion, +}; +use crate::block_hash::block_hash_calculator::{calculate_block_hash, TransactionHashingData}; +use crate::block_hash::test_utils::{get_state_diff, get_transaction_output}; +use crate::core::{ContractAddress, GlobalRoot, PatriciaKey, SequencerContractAddress}; use crate::data_availability::L1DataAvailabilityMode; use crate::hash::StarkFelt; +use crate::transaction::{TransactionHash, TransactionSignature, TransactionVersion}; + +#[test] +fn test_block_hash_regression() { + let block_header = BlockHeaderWithoutHash { + block_number: BlockNumber(1_u64), + state_root: GlobalRoot(StarkFelt::from(2_u8)), + sequencer: SequencerContractAddress(ContractAddress(PatriciaKey::from(3_u8))), + timestamp: BlockTimestamp(4), + l1_da_mode: L1DataAvailabilityMode::Blob, + l1_gas_price: GasPricePerToken { price_in_fri: GasPrice(6), price_in_wei: GasPrice(7) }, + l1_data_gas_price: GasPricePerToken { + price_in_fri: GasPrice(10), + price_in_wei: GasPrice(9), + }, + starknet_version: StarknetVersion("10".to_owned()), + parent_hash: BlockHash(StarkFelt::from(11_u8)), + }; + let transactions_data = vec![TransactionHashingData { + transaction_signature: Some(TransactionSignature(vec![StarkFelt::TWO, StarkFelt::THREE])), + transaction_output: get_transaction_output(), + transaction_hash: TransactionHash(StarkFelt::ONE), + transaction_version: TransactionVersion::THREE, + }]; + let state_diff = get_state_diff(); + + let expected_hash = + StarkFelt::try_from("0x069c273a5f40b62efb03e0a8f46f6eb68533f578adbfcc57a604e9a63b066f28") + .unwrap(); + + assert_eq!( + BlockHash(expected_hash), + calculate_block_hash(block_header, &transactions_data, &state_diff), + ); +} #[test] fn concat_counts_test() { diff --git a/src/block_hash/event_commitment_test.rs b/src/block_hash/event_commitment_test.rs index a166993..b563ec0 100644 --- a/src/block_hash/event_commitment_test.rs +++ b/src/block_hash/event_commitment_test.rs @@ -2,7 +2,6 @@ use super::{calculate_event_hash, EventLeafElement}; use crate::block_hash::event_commitment::calculate_events_commitment; use crate::core::{ContractAddress, EventCommitment}; use crate::hash::{PoseidonHashCalculator, StarkFelt}; -use crate::stark_felt; use crate::transaction::{Event, EventContent, EventData, EventKey, TransactionHash}; #[test] @@ -36,12 +35,12 @@ fn get_event_leaf_element(seed: u8) -> EventLeafElement { event: Event { from_address: ContractAddress::from(seed + 8), content: EventContent { - keys: [seed, seed + 1].iter().map(|key| EventKey(stark_felt!(*key))).collect(), + keys: [seed, seed + 1].iter().map(|key| EventKey(StarkFelt::from(*key))).collect(), data: EventData( [seed + 2, seed + 3, seed + 4].into_iter().map(StarkFelt::from).collect(), ), }, }, - transaction_hash: TransactionHash(stark_felt!("0x1234")), + transaction_hash: TransactionHash(StarkFelt::from(4660_u16)), } } diff --git a/src/block_hash/state_diff_hash_test.rs b/src/block_hash/state_diff_hash_test.rs index 4f8b0f2..c0da9a5 100644 --- a/src/block_hash/state_diff_hash_test.rs +++ b/src/block_hash/state_diff_hash_test.rs @@ -6,37 +6,12 @@ use crate::block_hash::state_diff_hash::{ }; use crate::core::{ClassHash, CompiledClassHash, Nonce, StateDiffCommitment}; use crate::crypto::utils::HashChain; +use crate::block_hash::test_utils::get_state_diff; use crate::hash::{PoseidonHash, StarkFelt}; -use crate::state::ThinStateDiff; #[test] fn test_state_diff_hash_regression() { - let state_diff = ThinStateDiff { - deployed_contracts: indexmap! { - 0u64.into() => ClassHash(1u64.into()), - 2u64.into() => ClassHash(3u64.into()), - }, - storage_diffs: indexmap! { - 4u64.into() => indexmap! { - 5u64.into() => 6u64.into(), - 7u64.into() => 8u64.into(), - }, - 9u64.into() => indexmap! { - 10u64.into() => 11u64.into(), - }, - }, - declared_classes: indexmap! { - ClassHash(12u64.into()) => CompiledClassHash(13u64.into()), - ClassHash(14u64.into()) => CompiledClassHash(15u64.into()), - }, - deprecated_declared_classes: vec![ClassHash(16u64.into())], - nonces: indexmap! { - 17u64.into() => Nonce(18u64.into()), - }, - replaced_classes: indexmap! { - 19u64.into() => ClassHash(20u64.into()), - }, - }; + let state_diff = get_state_diff(); let expected_hash = StateDiffCommitment(PoseidonHash( StarkFelt::try_from("0x05b8241020c186585f4273cf991d35ad703e808bd9b40242cec584e7f2d86495") diff --git a/src/block_hash/test_utils.rs b/src/block_hash/test_utils.rs index d895d2d..20c0c6f 100644 --- a/src/block_hash/test_utils.rs +++ b/src/block_hash/test_utils.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; +use indexmap::indexmap; use primitive_types::H160; -use crate::core::{ContractAddress, EthAddress}; +use crate::core::{ClassHash, CompiledClassHash, ContractAddress, EthAddress, Nonce}; use crate::hash::StarkFelt; +use crate::state::ThinStateDiff; use crate::transaction::{ Builtin, ExecutionResources, Fee, InvokeTransactionOutput, L2ToL1Payload, MessageToL1, RevertedTransactionExecutionStatus, TransactionExecutionStatus, TransactionOutput, @@ -37,3 +39,32 @@ pub(crate) fn generate_message_to_l1(seed: u64) -> MessageToL1 { payload: L2ToL1Payload(vec![StarkFelt::from(seed + 2), StarkFelt::from(seed + 3)]), } } + +pub(crate) fn get_state_diff() -> ThinStateDiff { + ThinStateDiff { + deployed_contracts: indexmap! { + 0u64.into() => ClassHash(1u64.into()), + 2u64.into() => ClassHash(3u64.into()), + }, + storage_diffs: indexmap! { + 4u64.into() => indexmap! { + 5u64.into() => 6u64.into(), + 7u64.into() => 8u64.into(), + }, + 9u64.into() => indexmap! { + 10u64.into() => 11u64.into(), + }, + }, + declared_classes: indexmap! { + ClassHash(12u64.into()) => CompiledClassHash(13u64.into()), + ClassHash(14u64.into()) => CompiledClassHash(15u64.into()), + }, + deprecated_declared_classes: vec![ClassHash(16u64.into())], + nonces: indexmap! { + 17u64.into() => Nonce(18u64.into()), + }, + replaced_classes: indexmap! { + 19u64.into() => ClassHash(20u64.into()), + }, + } +} diff --git a/src/transaction.rs b/src/transaction.rs index 8e3ecab..645868a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -56,6 +56,16 @@ impl Transaction { Transaction::L1Handler(tx) => tx.version, } } + + pub fn signature(&self) -> Option { + match self { + Transaction::Declare(tx) => Some(tx.signature()), + Transaction::Deploy(_) => None, + Transaction::DeployAccount(tx) => Some(tx.signature()), + Transaction::Invoke(tx) => Some(tx.signature()), + Transaction::L1Handler(_) => None, + } + } } impl TransactionHasher for Transaction {