From 6ab29b7c5af2a3aa52442bf76398b16ee492346c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Delabrouille?= <34384633+tdelabro@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:56:14 +0100 Subject: [PATCH] chore: remove primitives/commitments (#1365) --- CHANGELOG.md | 3 + Cargo.lock | 20 +- Cargo.toml | 3 - crates/client/db/src/lib.rs | 8 + crates/client/rpc/src/lib.rs | 8 +- crates/client/settlement/Cargo.toml | 1 + crates/client/settlement/src/sync_state.rs | 27 +- crates/node/src/service.rs | 7 +- crates/pallets/starknet/Cargo.toml | 1 - crates/pallets/starknet/src/lib.rs | 8 - crates/primitives/block/src/header.rs | 16 - crates/primitives/commitments/Cargo.toml | 46 -- crates/primitives/commitments/src/lib.rs | 295 --------- .../src/merkle_patricia_tree/merkle_node.rs | 303 --------- .../src/merkle_patricia_tree/merkle_tree.rs | 608 ------------------ .../src/merkle_patricia_tree/mod.rs | 6 - .../merkle_patricia_tree/ref_merkle_node.rs | 272 -------- .../merkle_patricia_tree/ref_merkle_tree.rs | 484 -------------- crates/primitives/commitments/src/tests.rs | 119 ---- crates/primitives/genesis-config/Cargo.toml | 2 +- 20 files changed, 43 insertions(+), 2194 deletions(-) delete mode 100644 crates/primitives/commitments/Cargo.toml delete mode 100644 crates/primitives/commitments/src/lib.rs delete mode 100644 crates/primitives/commitments/src/merkle_patricia_tree/merkle_node.rs delete mode 100644 crates/primitives/commitments/src/merkle_patricia_tree/merkle_tree.rs delete mode 100644 crates/primitives/commitments/src/merkle_patricia_tree/mod.rs delete mode 100644 crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_node.rs delete mode 100644 crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_tree.rs delete mode 100644 crates/primitives/commitments/src/tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7100cb4a..8da56ea0d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next release +- chore(primitives/commitment): remove crate +- chore(primitives/block/header): remove starknet-trie dependent fields +- refacto(primitives/db): add a temporary way to get a fake global state root - chore: feature flags for avail and celestia DA - feat(rpc): added support for v0.5.1 JSON-RPC specs - feat(rpc): added ordered messages/events in trace fields diff --git a/Cargo.lock b/Cargo.lock index 15deac69ea..26937faa36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6480,6 +6480,7 @@ dependencies = [ "futures-timer", "log", "mc-data-availability", + "mc-db", "mp-block", "mp-digest-log", "mp-felt", @@ -6728,24 +6729,6 @@ dependencies = [ "starknet-ff 0.3.5 (git+https://github.com/xJonathanLEI/starknet-rs.git?rev=64ebc36)", ] -[[package]] -name = "mp-commitments" -version = "0.6.0" -dependencies = [ - "bitvec", - "derive_more", - "mp-felt", - "mp-hashers", - "mp-transactions", - "parity-scale-codec", - "scale-info", - "serde", - "starknet-core", - "starknet-crypto 0.6.1 (git+https://github.com/xJonathanLEI/starknet-rs.git?rev=64ebc36)", - "starknet-ff 0.3.5 (git+https://github.com/xJonathanLEI/starknet-rs.git?rev=64ebc36)", - "starknet_api", -] - [[package]] name = "mp-digest-log" version = "0.6.0" @@ -7634,7 +7617,6 @@ dependencies = [ "log", "mp-block", "mp-chain-id", - "mp-commitments", "mp-digest-log", "mp-fee", "mp-felt", diff --git a/Cargo.toml b/Cargo.toml index d4bc0c0c42..bfb8689ca8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ members = [ "crates/primitives/block", "crates/primitives/sequencer-address", "crates/primitives/storage", - "crates/primitives/commitments", "crates/primitives/chain-id", "crates/primitives/messages", "crates/primitives/program-hash", @@ -50,7 +49,6 @@ default-members = [ "crates/primitives/block", "crates/primitives/sequencer-address", "crates/primitives/storage", - "crates/primitives/commitments", "crates/primitives/chain-id", "crates/primitives/messages", "crates/primitives/program-hash", @@ -176,7 +174,6 @@ mp-snos-output = { path = "crates/primitives/snos-output", default-features = fa mp-state = { path = "crates/primitives/state", default-features = false } mp-storage = { path = "crates/primitives/storage", default-features = false } mp-transactions = { path = "crates/primitives/transactions", default-features = false } -mp-commitments = { path = "crates/primitives/commitments", default-features = false } mp-chain-id = { path = "crates/primitives/chain-id", default-features = false } mp-simulations = { path = "crates/primitives/simulations", default-features = false } mp-program-hash = { path = "crates/primitives/program-hash", default-features = false } diff --git a/crates/client/db/src/lib.rs b/crates/client/db/src/lib.rs index aa26d5d9ea..0ce1cf8b4e 100644 --- a/crates/client/db/src/lib.rs +++ b/crates/client/db/src/lib.rs @@ -16,6 +16,7 @@ pub use error::DbError; mod mapping_db; pub use mapping_db::MappingCommitment; +use starknet_api::hash::StarkHash; mod da_db; mod db_opening_utils; mod messaging_db; @@ -143,4 +144,11 @@ impl Backend { pub fn messaging(&self) -> &Arc> { &self.messaging } + + /// In the future, we will compute the block global state root asynchronously in the client, + /// using the Starknet-Bonzai-trie. + /// That what replaces it for now :) + pub fn temporary_global_state_root_getter(&self) -> StarkHash { + Default::default() + } } diff --git a/crates/client/rpc/src/lib.rs b/crates/client/rpc/src/lib.rs index a9f077e2fb..d262ac042e 100644 --- a/crates/client/rpc/src/lib.rs +++ b/crates/client/rpc/src/lib.rs @@ -857,7 +857,7 @@ where block_hash: block_hash.into(), parent_hash: Felt252Wrapper::from(parent_blockhash).into(), block_number: starknet_block.header().block_number, - new_root: Felt252Wrapper::from(starknet_block.header().global_state_root).into(), + new_root: Felt252Wrapper::from(self.backend.temporary_global_state_root_getter()).into(), timestamp: starknet_block.header().block_timestamp, sequencer_address: Felt252Wrapper::from(starknet_block.header().sequencer_address).into(), l1_gas_price: starknet_block.header().l1_gas_price.into(), @@ -1097,7 +1097,7 @@ where block_hash: block_hash.into(), parent_hash: Felt252Wrapper::from(starknet_block.header().parent_block_hash).into(), block_number: starknet_block.header().block_number, - new_root: Felt252Wrapper::from(starknet_block.header().global_state_root).into(), + new_root: Felt252Wrapper::from(self.backend.temporary_global_state_root_getter()).into(), timestamp: starknet_block.header().block_timestamp, sequencer_address: Felt252Wrapper::from(starknet_block.header().sequencer_address).into(), transactions, @@ -1144,14 +1144,14 @@ where let parent_block = get_block_by_block_hash(self.client.as_ref(), substrate_parent_block_hash).unwrap_or_default(); - Felt252Wrapper::from(parent_block.header().global_state_root).into() + Felt252Wrapper::from(self.backend.temporary_global_state_root_getter()).into() } else { FieldElement::default() }; Ok(StateUpdate { block_hash: starknet_block.header().hash::().into(), - new_root: Felt252Wrapper::from(starknet_block.header().global_state_root).into(), + new_root: Felt252Wrapper::from(self.backend.temporary_global_state_root_getter()).into(), old_root, state_diff: StateDiff { storage_diffs: Vec::new(), diff --git a/crates/client/settlement/Cargo.toml b/crates/client/settlement/Cargo.toml index cd29ab79cc..b94d2733db 100644 --- a/crates/client/settlement/Cargo.toml +++ b/crates/client/settlement/Cargo.toml @@ -32,6 +32,7 @@ sp-runtime = { workspace = true, default-features = true } # Starknet mc-data-availability = { workspace = true, default-features = true } +mc-db = { workspace = true, default-features = true } mp-block = { workspace = true, default-features = true } mp-digest-log = { workspace = true, features = ["std"] } mp-felt = { workspace = true, default-features = true } diff --git a/crates/client/settlement/src/sync_state.rs b/crates/client/settlement/src/sync_state.rs index c295f91ac7..e44551e037 100644 --- a/crates/client/settlement/src/sync_state.rs +++ b/crates/client/settlement/src/sync_state.rs @@ -40,10 +40,11 @@ where pub async fn sync_state( substrate_client: Arc, settlement_provider: Box>, + madara_backend: Arc>, retry_strategy: Box>, ) { loop { - match Self::sync_state_loop(&substrate_client, settlement_provider.as_ref()).await { + match Self::sync_state_loop(&substrate_client, settlement_provider.as_ref(), &madara_backend).await { Ok(()) => { return; } @@ -80,7 +81,11 @@ where /// In case chain is stuck it won't update the state, even if there are pending blocks. /// It is ok, since it's not a normal condition, and generally we expect that the chain will /// advance indefinitely. - async fn sync_state_loop(substrate_client: &SC, settlement_provider: &SP) -> Result<(), B> + async fn sync_state_loop( + substrate_client: &SC, + settlement_provider: &SP, + madara_backend: &mc_db::Backend, + ) -> Result<(), B> where SP: ?Sized + SettlementProvider, { @@ -99,7 +104,7 @@ where // If we haven't reached the settled level yet (e.g. syncing from scratch) this check will pass. // But we need to run it again once we are up to speed. - Self::verify_starknet_state(substrate_client, &last_settled_state)?; + Self::verify_starknet_state(substrate_client, &last_settled_state, madara_backend)?; let mut finality_notifications = substrate_client.finality_notification_stream(); let mut sync_from: u64 = last_settled_state.block_number.try_into()?; @@ -115,7 +120,7 @@ where if sync_from == sync_to { log::info!("[settlement] Verifying state root for block {}", sync_to); - Self::verify_starknet_state(substrate_client, &last_settled_state)?; + Self::verify_starknet_state(substrate_client, &last_settled_state, madara_backend)?; continue; } @@ -135,6 +140,7 @@ where &next_block, substrate_block_hash, starknet_spec.config_hash, + madara_backend, ) .await?; @@ -204,12 +210,16 @@ where /// /// If Madara chain haven't reached the given block level yet, it returns OK, assuming that as /// soon as it catches up - this check will be done again. - fn verify_starknet_state(substrate_client: &SC, state: &StarknetState) -> Result<(), B> { + fn verify_starknet_state( + substrate_client: &SC, + state: &StarknetState, + madara_backend: &mc_db::Backend, + ) -> Result<(), B> { let height: u64 = state.block_number.try_into()?; match Self::get_starknet_block(substrate_client, height) { - Ok((block, _)) => { - let state_root = block.header().global_state_root; + Ok((_block, _)) => { + let state_root = madara_backend.temporary_global_state_root_getter(); // Verify that current onchain state is consistent with corresponding Madara block if state.state_root != state_root { return Err(Error::StateRootMismatch { height, expected: state_root, actual: state.state_root }); @@ -241,13 +251,14 @@ where next_block: &StarknetBlock, substrate_block_hash: B::Hash, config_hash: StarkHash, + madara_backend: &mc_db::Backend, ) -> Result where SP: ?Sized + SettlementProvider, { let next_state = StarknetState { block_number: next_block.header().block_number.into(), - state_root: next_block.header().global_state_root, + state_root: madara_backend.temporary_global_state_root_getter(), }; let mut messages_to_l1: Vec = Vec::new(); diff --git a/crates/node/src/service.rs b/crates/node/src/service.rs index c32b5b87aa..b6967e9add 100644 --- a/crates/node/src/service.rs +++ b/crates/node/src/service.rs @@ -475,7 +475,12 @@ pub fn new_full( task_manager.spawn_essential_handle().spawn( "settlement-worker-sync-state", Some("madara"), - SettlementWorker::<_, StarknetHasher, _>::sync_state(client.clone(), settlement_provider, retry_strategy), + SettlementWorker::<_, StarknetHasher, _>::sync_state( + client.clone(), + settlement_provider, + madara_backend.clone(), + retry_strategy, + ), ); } diff --git a/crates/pallets/starknet/Cargo.toml b/crates/pallets/starknet/Cargo.toml index 5efa5e734e..0225ee294f 100644 --- a/crates/pallets/starknet/Cargo.toml +++ b/crates/pallets/starknet/Cargo.toml @@ -17,7 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] # Madara primitives mp-block = { workspace = true } mp-chain-id = { workspace = true } -mp-commitments = { workspace = true } mp-digest-log = { workspace = true } mp-fee = { workspace = true } mp-felt = { workspace = true, features = ["parity-scale-codec", "serde"] } diff --git a/crates/pallets/starknet/src/lib.rs b/crates/pallets/starknet/src/lib.rs index 3d1766c4ad..3ff02c6b82 100644 --- a/crates/pallets/starknet/src/lib.rs +++ b/crates/pallets/starknet/src/lib.rs @@ -946,14 +946,9 @@ impl Pallet { let parent_block_hash = Self::parent_block_hash(&block_number); let events: Vec = transaction_hashes.iter().flat_map(TxEvents::::take).collect(); - let global_state_root = Felt252Wrapper::default(); - let sequencer_address = Self::sequencer_address(); let block_timestamp = Self::block_timestamp(); - let chain_id = Self::chain_id(); - let (transaction_commitment, event_commitment) = - mp_commitments::calculate_commitments::(&transactions, &events, chain_id); let protocol_version = T::ProtocolVersion::get(); let extra_data = None; @@ -964,13 +959,10 @@ impl Pallet { StarknetHeader::new( parent_block_hash.into(), block_number, - global_state_root.into(), sequencer_address, block_timestamp, transaction_count as u128, - transaction_commitment.into(), events.len() as u128, - event_commitment.into(), protocol_version, l1_gas_price, extra_data, diff --git a/crates/primitives/block/src/header.rs b/crates/primitives/block/src/header.rs index 3b7e827647..8bcf561e07 100644 --- a/crates/primitives/block/src/header.rs +++ b/crates/primitives/block/src/header.rs @@ -20,20 +20,14 @@ pub struct Header { pub parent_block_hash: StarkHash, /// The number (height) of this block. pub block_number: u64, - /// The state commitment after this block. - pub global_state_root: StarkHash, /// The Starknet address of the sequencer who created this block. pub sequencer_address: ContractAddress, /// The time the sequencer created this block before executing transactions pub block_timestamp: u64, /// The number of transactions in a block pub transaction_count: u128, - /// A commitment to the transactions included in the block - pub transaction_commitment: StarkHash, /// The number of events pub event_count: u128, - /// A commitment to the events produced in this block - pub event_commitment: StarkHash, /// The version of the Starknet protocol used when creating this block pub protocol_version: u8, /// l1 gas price for this block @@ -49,13 +43,10 @@ impl Header { pub fn new( parent_block_hash: StarkHash, block_number: u64, - global_state_root: StarkHash, sequencer_address: ContractAddress, block_timestamp: u64, transaction_count: u128, - transaction_commitment: StarkHash, event_count: u128, - event_commitment: StarkHash, protocol_version: u8, l1_gas_price: ResourcePrice, extra_data: Option, @@ -63,13 +54,10 @@ impl Header { Self { parent_block_hash, block_number, - global_state_root, sequencer_address, block_timestamp, transaction_count, - transaction_commitment, event_count, - event_commitment, protocol_version, l1_gas_price, extra_data, @@ -94,17 +82,13 @@ impl Header { } /// Compute the hash of the header. - #[must_use] pub fn hash(&self) -> Felt252Wrapper { let data: &[Felt252Wrapper] = &[ self.block_number.into(), - self.global_state_root.into(), self.sequencer_address.0.0.into(), self.block_timestamp.into(), self.transaction_count.into(), - self.transaction_commitment.into(), self.event_count.into(), - self.event_commitment.into(), self.protocol_version.into(), Felt252Wrapper::ZERO, self.parent_block_hash.into(), diff --git a/crates/primitives/commitments/Cargo.toml b/crates/primitives/commitments/Cargo.toml deleted file mode 100644 index c025bfba7d..0000000000 --- a/crates/primitives/commitments/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "mp-commitments" -version.workspace = true -edition.workspace = true -license = "MIT" -description = "Starknet commitment computation logic" -authors = { workspace = true } -repository = { workspace = true } - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -bitvec = { workspace = true } -derive_more = { workspace = true, features = ["constructor"] } -mp-felt = { workspace = true } -mp-hashers = { workspace = true } -mp-transactions = { workspace = true } -starknet-crypto = { workspace = true } -starknet_api = { workspace = true } - -# Optional -parity-scale-codec = { workspace = true, features = [ - "derive", -], optional = true } -scale-info = { workspace = true, features = ["derive"], optional = true } -serde = { workspace = true, features = ["derive"], optional = true } -starknet-ff = { workspace = true, optional = true } - -[dev-dependencies] -starknet-core = { workspace = true } - - -[features] -default = ["std"] -std = [ - "mp-hashers/std", - "mp-transactions/std", - "mp-felt/std", - "starknet-crypto/std", - "bitvec/std", - "starknet_api/std", -] -parity-scale-codec = ["dep:parity-scale-codec", "mp-felt/parity-scale-codec"] -scale-info = ["dep:scale-info", "mp-felt/scale-info"] -serde = ["dep:serde", "dep:starknet-ff", "starknet-ff?/serde", "mp-felt/serde"] diff --git a/crates/primitives/commitments/src/lib.rs b/crates/primitives/commitments/src/lib.rs deleted file mode 100644 index 09e1f683a2..0000000000 --- a/crates/primitives/commitments/src/lib.rs +++ /dev/null @@ -1,295 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -#[doc(hidden)] -pub extern crate alloc; - -mod merkle_patricia_tree; - -use alloc::vec::Vec; - -use bitvec::vec::BitVec; -use merkle_patricia_tree::merkle_tree::{MerkleTree, NodesMapping, ProofNode}; -use merkle_patricia_tree::ref_merkle_tree::RefMerkleTree; -use mp_felt::Felt252Wrapper; -use mp_hashers::HasherT; -use mp_transactions::compute_hash::ComputeTransactionHash; -use mp_transactions::Transaction; -use starknet_api::transaction::Event; -use starknet_crypto::FieldElement; - -/// Hash of the leaf of the ClassCommitment tree -pub type ClassCommitmentLeafHash = Felt252Wrapper; - -/// A Patricia Merkle tree with height 64 used to compute transaction and event commitments. -/// -/// According to the [documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Blocks/header/) -/// the commitment trees are of height 64, because the key used is the 64 bit representation -/// of the index of the transaction / event within the block. -/// -/// The tree height is 64 in our case since our set operation takes u64 index values. -struct CommitmentTree { - tree: RefMerkleTree, -} - -impl Default for CommitmentTree { - fn default() -> Self { - Self { tree: RefMerkleTree::empty() } - } -} - -impl CommitmentTree { - /// Sets the value of a key in the merkle tree. - /// - /// # Arguments - /// - /// * `index` - The index of the value to set. - /// * `value` - The value to set. - pub fn set(&mut self, index: u64, value: FieldElement) { - let key = index.to_be_bytes(); - self.tree.set(&BitVec::from_vec(key.to_vec()), Felt252Wrapper(value)) - } - - /// Get the merkle root of the tree. - pub fn commit(&mut self) -> Felt252Wrapper { - self.tree.commit() - } -} - -/// A Patricia Merkle tree with height 251 used to compute contract and class tree commitments. -/// -/// According to the [documentation](https://docs.starknet.io/documentation/architecture_and_concepts/State/starknet-state/) -/// the commitment trees are of height 251, because the key used is a Field Element. -/// -/// The tree height is 251 in our case since our set operation takes Fieldelement index values. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct StateCommitmentTree { - tree: MerkleTree, -} - -impl Default for StateCommitmentTree { - fn default() -> Self { - Self { tree: MerkleTree::empty() } - } -} - -impl StateCommitmentTree { - /// Sets the value of a key in the merkle tree. - /// - /// # Arguments - /// - /// * `index` - The index of the value to set. - /// * `value` - The value to set. - pub fn set(&mut self, index: Felt252Wrapper, value: Felt252Wrapper) { - let key = &index.0.to_bytes_be()[..31]; - self.tree.set(&BitVec::from_vec(key.to_vec()), value) - } - - /// Get the merkle root of the tree. - pub fn commit(&mut self) -> Felt252Wrapper { - self.tree.commit() - } - - /// Generates a proof for `key`. See [`MerkleTree::get_proof`]. - pub fn get_proof(&self, key: Felt252Wrapper) -> Vec { - let key = &key.0.to_bytes_be()[..31]; - self.tree.get_proof(&BitVec::from_vec(key.to_vec())) - } - - /// Returns a leaf of the tree stored at key `key` - /// - /// # Arguments - /// - /// * `key` - The key of the value to retrieve. - /// - /// # Returns - /// - /// `Some(value)` - Value stored at the given key. - pub fn get(&self, key: Felt252Wrapper) -> Option { - let key = &key.0.to_bytes_be()[..31]; - self.tree.get(&BitVec::from_vec(key.to_vec())) - } - - /// Returns the tree's nodes - pub fn nodes(&self) -> NodesMapping { - NodesMapping(self.tree.nodes()) - } -} - -/// Calculate the transaction commitment, the event commitment and the event count. -/// -/// # Arguments -/// -/// * `transactions` - The transactions of the block -/// -/// # Returns -/// -/// The transaction commitment, the event commitment and the event count. -pub fn calculate_commitments( - transactions: &[Transaction], - events: &[Event], - chain_id: Felt252Wrapper, -) -> (Felt252Wrapper, Felt252Wrapper) { - (calculate_transaction_commitment::(transactions, chain_id), calculate_event_commitment::(events)) -} - -/// Calculate transaction commitment hash value. -/// -/// The transaction commitment is the root of the Patricia Merkle tree with height 64 -/// constructed by adding the (transaction_index, transaction_hash_with_signature) -/// key-value pairs to the tree and computing the root hash. -/// -/// # Arguments -/// -/// * `transactions` - The transactions to get the root from. -/// -/// # Returns -/// -/// The merkle root of the merkle tree built from the transactions. -pub(crate) fn calculate_transaction_commitment( - transactions: &[Transaction], - chain_id: Felt252Wrapper, -) -> Felt252Wrapper { - let mut tree = CommitmentTree::::default(); - - transactions.iter().enumerate().for_each(|(idx, tx)| { - let idx: u64 = idx.try_into().expect("too many transactions while calculating commitment"); - let final_hash = calculate_transaction_hash_with_signature::(tx, chain_id); - tree.set(idx, final_hash); - }); - tree.commit() -} - -/// Calculate event commitment hash value. -/// -/// The event commitment is the root of the Patricia Merkle tree with height 64 -/// constructed by adding the event hash -/// (see https://docs.starknet.io/documentation/architecture_and_concepts/Events/starknet-events/#event_hash) -/// to the tree and computing the root hash. -/// -/// # Arguments -/// -/// * `events` - The events to calculate the commitment from. -/// -/// # Returns -/// -/// The merkle root of the merkle tree built from the events. -pub(crate) fn calculate_event_commitment(events: &[Event]) -> Felt252Wrapper { - let mut tree = CommitmentTree::::default(); - events.iter().enumerate().for_each(|(id, event)| { - let final_hash = calculate_event_hash::(event); - tree.set(id as u64, final_hash); - }); - tree.commit() -} - -/// Calculate class commitment tree leaf hash value. -/// -/// See: -/// -/// # Arguments -/// -/// * `compiled_class_hash` - The hash of the compiled class. -/// -/// # Returns -/// -/// The hash of the class commitment tree leaf. -pub fn calculate_class_commitment_leaf_hash( - compiled_class_hash: Felt252Wrapper, -) -> ClassCommitmentLeafHash { - let contract_class_hash_version = Felt252Wrapper::try_from("CONTRACT_CLASS_LEAF_V0".as_bytes()).unwrap(); // Unwrap safu - - let hash = H::compute_hash_on_elements(&[contract_class_hash_version.0, compiled_class_hash.0]); - - hash.into() -} - -/// Calculate class commitment tree root hash value. -/// -/// The classes tree encodes the information about the existing classes in the state of Starknet. -/// It maps (Cairo 1.0) class hashes to their compiled class hashes -/// -/// # Arguments -/// -/// * `classes` - The classes to get the root from. -/// -/// # Returns -/// -/// The merkle root of the merkle tree built from the classes. -pub fn calculate_class_commitment_tree_root_hash(class_hashes: &[Felt252Wrapper]) -> Felt252Wrapper { - let mut tree = StateCommitmentTree::::default(); - class_hashes.iter().for_each(|class_hash| { - let final_hash = calculate_class_commitment_leaf_hash::(*class_hash); - tree.set(*class_hash, final_hash); - }); - tree.commit() -} - -/// Calculates the contract state hash from its preimage. -/// -/// # Arguments -/// -/// * `hash` - The hash of the contract definition. -/// * `root` - The root of root of another Merkle-Patricia tree of height 251 that is constructed -/// from the contract’s storage. -/// * `nonce` - The current nonce of the contract. -/// -/// # Returns -/// -/// The contract state hash. -pub fn calculate_contract_state_hash( - hash: Felt252Wrapper, - root: Felt252Wrapper, - nonce: Felt252Wrapper, -) -> Felt252Wrapper { - const CONTRACT_STATE_HASH_VERSION: Felt252Wrapper = Felt252Wrapper::ZERO; - - // The contract state hash is defined as H(H(H(hash, root), nonce), CONTRACT_STATE_HASH_VERSION) - let hash = H::compute_hash_on_elements(&[hash.0, root.0, nonce.0, CONTRACT_STATE_HASH_VERSION.0]); - - // Compare this with the HashChain construction used in the contract_hash: the number of - // elements is not hashed to this hash, and this is supposed to be different. - hash.into() -} - -/// Compute the combined hash of the transaction hash and the signature. -/// -/// Since the transaction hash doesn't take the signature values as its input -/// computing the transaction commitent uses a hash value that combines -/// the transaction hash with the array of signature values. -/// -/// # Arguments -/// -/// * `tx` - The transaction to compute the hash of. -/// -/// # Returns -/// -/// The transaction hash with signature. -fn calculate_transaction_hash_with_signature(tx: &Transaction, chain_id: Felt252Wrapper) -> FieldElement -where - H: HasherT, -{ - let signature_hash = H::compute_hash_on_elements( - &tx.signature().iter().map(|elt| FieldElement::from(*elt)).collect::>(), - ); - H::hash_elements(FieldElement::from(tx.compute_hash::(chain_id, false)), signature_hash) -} - -/// Calculate the hash of an event. -/// -/// See the [documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Events/starknet-events/#event_hash) -/// for details. -pub fn calculate_event_hash(event: &Event) -> FieldElement { - let keys_hash = H::compute_hash_on_elements( - &event.content.keys.iter().map(|key| Felt252Wrapper::from(key.0).into()).collect::>(), - ); - let data_hash = H::compute_hash_on_elements( - &event.content.data.0.iter().map(|data| Felt252Wrapper::from(*data).into()).collect::>(), - ); - let from_address = Felt252Wrapper::from(event.from_address.0.0).into(); - H::compute_hash_on_elements(&[from_address, keys_hash, data_hash]) -} - -#[cfg(test)] -mod tests; diff --git a/crates/primitives/commitments/src/merkle_patricia_tree/merkle_node.rs b/crates/primitives/commitments/src/merkle_patricia_tree/merkle_node.rs deleted file mode 100644 index 8329cfe24f..0000000000 --- a/crates/primitives/commitments/src/merkle_patricia_tree/merkle_node.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Contains constructs for describing the nodes in a Binary Merkle Patricia Tree -//! used by Starknet. -//! -//! For more information about how these Starknet trees are structured, see -//! [`MerkleTree`](super::merkle_tree::MerkleTree). - -use bitvec::order::Msb0; -use bitvec::prelude::BitVec; -use bitvec::slice::BitSlice; -use bitvec::view::BitView; -use mp_felt::Felt252Wrapper; -use mp_hashers::HasherT; -use starknet_api::stdlib::collections::HashMap; - -/// Id of a Node within the tree -#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct NodeId(pub u64); - -impl NodeId { - /// Mutates the given NodeId to be the next one and returns it. - pub fn next_id(&mut self) -> NodeId { - self.0 += 1; - NodeId(self.0) - } -} - -/// A node in a Binary Merkle-Patricia Tree graph. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub enum Node { - /// A node that has not been fetched from storage yet. - /// - /// As such, all we know is its hash. - Unresolved(Felt252Wrapper), - /// A branch node with exactly two children. - Binary(BinaryNode), - /// Describes a path connecting two other nodes. - Edge(EdgeNode), - /// A leaf node that contains a value. - Leaf(Felt252Wrapper), -} - -/// Describes the [Node::Binary] variant. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct BinaryNode { - /// The hash of this node. Is [None] if the node - /// has not yet been committed. - pub hash: Option, - /// The height of this node in the tree. - pub height: u64, - /// [Left](Direction::Left) child. - pub left: NodeId, - /// [Right](Direction::Right) child. - pub right: NodeId, -} - -/// Node that is an edge. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct EdgeNode { - /// The hash of this node. Is [None] if the node - /// has not yet been committed. - pub hash: Option, - /// The starting height of this node in the tree. - pub height: u64, - /// The path this edge takes. - pub path: BitVec, - /// The child of this node. - pub child: NodeId, -} - -/// Describes the direction a child of a [BinaryNode] may have. -/// -/// Binary nodes have two children, one left and one right. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "parity-scale-codec", derive(parity_scale_codec::Encode, parity_scale_codec::Decode))] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub enum Direction { - /// Left direction. - Left, - /// Right direction. - Right, -} - -impl Direction { - /// Inverts the [Direction]. - /// - /// [Left] becomes [Right], and [Right] becomes [Left]. - /// - /// [Left]: Direction::Left - /// [Right]: Direction::Right - pub fn invert(self) -> Direction { - match self { - Direction::Left => Direction::Right, - Direction::Right => Direction::Left, - } - } -} - -impl From for Direction { - fn from(tf: bool) -> Self { - match tf { - true => Direction::Right, - false => Direction::Left, - } - } -} - -impl From for bool { - fn from(direction: Direction) -> Self { - match direction { - Direction::Left => false, - Direction::Right => true, - } - } -} - -impl BinaryNode { - /// Maps the key's bit at the binary node's height to a [Direction]. - /// - /// This can be used to check which direction the key describes in the context - /// of this binary node i.e. which direction the child along the key's path would - /// take. - /// - /// # Arguments - /// - /// * `key` - The key to get the direction of. - /// - /// # Returns - /// - /// The direction of the key. - pub fn direction(&self, key: &BitSlice) -> Direction { - key[self.height as usize].into() - } - - /// Returns the [Left] or [Right] child. - /// - /// [Left]: Direction::Left - /// [Right]: Direction::Right - /// - /// # Arguments - /// - /// `direction` - The direction where to get the child from. - /// - /// # Returns - /// - /// The child in the specified direction. - pub fn get_child(&self, direction: Direction) -> NodeId { - match direction { - Direction::Left => self.left, - Direction::Right => self.right, - } - } - - /// If possible, calculates and sets its own hash value. - /// - /// Does nothing if the hash is already [Some]. - /// - /// If either child's hash is [None], then the hash cannot - /// be calculated and it will remain [None]. - pub(crate) fn calculate_hash(&mut self, nodes: &HashMap) { - if self.hash.is_some() { - return; - } - - let left = match nodes.get(&self.left) { - Some(node) => match node.hash() { - Some(hash) => hash, - None => unreachable!("subtrees have to be committed first"), - }, - None => unreachable!("left child not found"), - }; - - let right = match nodes.get(&self.right) { - Some(node) => match node.hash() { - Some(hash) => hash, - None => unreachable!("subtrees have to be committed first"), - }, - None => unreachable!("right child not found"), - }; - - self.hash = Some(Felt252Wrapper(H::hash_elements(left.0, right.0))); - } -} - -impl Node { - /// Convenience function which sets the inner node's hash to [None], if - /// applicable. - /// - /// Used to indicate that this node has been mutated. - pub fn mark_dirty(&mut self) { - match self { - Node::Binary(inner) => inner.hash = None, - Node::Edge(inner) => inner.hash = None, - _ => {} - } - } - - /// Returns true if the node represents an empty node -- this is defined as a node - /// with the [Felt252Wrapper::ZERO]. - /// - /// This can occur for the root node in an empty graph. - pub fn is_empty(&self) -> bool { - match self { - Node::Unresolved(hash) => hash == &Felt252Wrapper::ZERO, - _ => false, - } - } - - /// Is the node a binary node. - pub fn is_binary(&self) -> bool { - matches!(self, Node::Binary(..)) - } - - /// Convert to node to binary node type (returns None if it's not a binary node). - pub fn as_binary(&self) -> Option<&BinaryNode> { - match self { - Node::Binary(binary) => Some(binary), - _ => None, - } - } - - /// Convert to node to edge node type (returns None if it's not a edge node). - pub fn as_edge(&self) -> Option<&EdgeNode> { - match self { - Node::Edge(edge) => Some(edge), - _ => None, - } - } - - /// Get the hash of a node. - pub fn hash(&self) -> Option { - match self { - Node::Unresolved(hash) => Some(*hash), - Node::Binary(binary) => binary.hash, - Node::Edge(edge) => edge.hash, - Node::Leaf(value) => Some(*value), - } - } -} - -impl EdgeNode { - /// Returns true if the edge node's path matches the same path given by the key. - /// - /// # Arguments - /// - /// * `key` - The key to check if the path matches with the edge node. - pub fn path_matches(&self, key: &BitSlice) -> bool { - self.path == key[(self.height as usize)..(self.height + self.path.len() as u64) as usize] - } - - /// Returns the common bit prefix between the edge node's path and the given key. - /// - /// This is calculated with the edge's height taken into account. - /// - /// # Arguments - /// - /// * `key` - The key to get the common path from. - pub fn common_path(&self, key: &BitSlice) -> &BitSlice { - let key_path = key.iter().skip(self.height as usize); - let common_length = key_path.zip(self.path.iter()).take_while(|(a, b)| a == b).count(); - - &self.path[..common_length] - } - - /// If possible, calculates and sets its own hash value. - /// - /// Does nothing if the hash is already [Some]. - /// - /// If the child's hash is [None], then the hash cannot - /// be calculated and it will remain [None]. - pub(crate) fn calculate_hash(&mut self, nodes: &HashMap) { - if self.hash.is_some() { - return; - } - - let child = match nodes.get(&self.child) { - Some(node) => match node.hash() { - Some(hash) => hash, - None => unreachable!("subtree has to be committed before"), - }, - None => unreachable!("child node not found"), - }; - - let mut bytes = [0u8; 32]; - bytes.view_bits_mut::()[256 - self.path.len()..].copy_from_bitslice(&self.path); - - let path = Felt252Wrapper::try_from(&bytes).unwrap(); - let mut length = [0; 32]; - // Safe as len() is guaranteed to be <= 251 - length[31] = self.path.len() as u8; - - let length = Felt252Wrapper::try_from(&length).unwrap(); - let hash = Felt252Wrapper(H::hash_elements(child.0, path.0) + length.0); - self.hash = Some(hash); - } -} diff --git a/crates/primitives/commitments/src/merkle_patricia_tree/merkle_tree.rs b/crates/primitives/commitments/src/merkle_patricia_tree/merkle_tree.rs deleted file mode 100644 index 72cb247ef7..0000000000 --- a/crates/primitives/commitments/src/merkle_patricia_tree/merkle_tree.rs +++ /dev/null @@ -1,608 +0,0 @@ -//! This is a gigantic copy pasta from Thanks to the equilibrium team and whoever else contributed for the code. -use alloc::vec::Vec; -use core::iter::once; -use core::marker::PhantomData; - -use bitvec::prelude::{BitSlice, BitVec, Msb0}; -use derive_more::Constructor; -use mp_felt::Felt252Wrapper; -use mp_hashers::HasherT; -#[cfg(feature = "parity-scale-codec")] -use parity_scale_codec::{Decode, Encode, Error, Input, Output}; -#[cfg(feature = "scale-info")] -use scale_info::{build::Fields, Path, Type, TypeInfo}; -#[cfg(feature = "serde")] -use serde::{ser::SerializeStructVariant, Serialize}; -use starknet_api::stdlib::collections::HashMap; - -use super::merkle_node::{BinaryNode, Direction, EdgeNode, Node, NodeId}; - -/// Wrapper type for a [HashMap] object. (It's not really a wrapper it's a -/// copy of the type but we implement the necessary traits.) -#[derive(Clone, Debug, PartialEq, Eq, Default, Constructor)] -pub struct NodesMapping(pub HashMap); - -/// SCALE trait. -#[cfg(feature = "parity-scale-codec")] -impl Encode for NodesMapping { - fn encode_to(&self, dest: &mut T) { - // Convert the NodesMapping to Vec<(NodeId, Node)> to be - // able to use the Encode trait from this type. We implemented it for NodeId, derived it - // for Node so we can use it for Vec<(NodeId, Node)>. - let val: Vec<(NodeId, Node)> = self.0.clone().into_iter().collect(); - dest.write(&Encode::encode(&val)); - } -} -/// SCALE trait. -#[cfg(feature = "parity-scale-codec")] -impl Decode for NodesMapping { - fn decode(input: &mut I) -> Result { - // Convert the NodesMapping to Vec<(NodeId, Node)> to be - // able to use the Decode trait from this type. We implemented it for NodeId, derived it - // for Node so we can use it for Vec<(NodeId, Node)>. - let val: Vec<(NodeId, Node)> = - Decode::decode(input).map_err(|_| Error::from("Can't get NodesMapping from input buffer."))?; - Ok(NodesMapping(HashMap::from_iter(val))) - } -} - -/// SCALE trait. -#[cfg(feature = "scale-info")] -impl TypeInfo for NodesMapping { - type Identity = Self; - - // The type info is saying that the NodesMapping must be seen as an - // array of bytes. - fn type_info() -> Type { - Type::builder() - .path(Path::new("NodesMapping", module_path!())) - .composite(Fields::unnamed().field(|f| f.ty::<[u8]>().type_name("NodesMapping"))) - } -} - -/// Lightweight representation of [BinaryNode]. Only holds left and right hashes. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "parity-scale-codec", derive(Encode, Decode))] -#[cfg_attr(feature = "scale-info", derive(TypeInfo))] -pub struct BinaryProofNode { - /// Left hash. - pub left_hash: Felt252Wrapper, - /// Right hash. - pub right_hash: Felt252Wrapper, -} - -/// Ligthtweight representation of [EdgeNode]. Only holds its path and its child's hash. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "parity-scale-codec", derive(Encode, Decode))] -#[cfg_attr(feature = "scale-info", derive(TypeInfo))] -pub struct EdgeProofNode { - /// Path of the node. - pub path: BitVec, - /// Hash of the child node. - pub child_hash: Felt252Wrapper, -} - -fn get_proof_node(node: &Node, nodes: &HashMap) -> ProofNode { - match node { - Node::Binary(bin) => ProofNode::Binary(BinaryProofNode { - left_hash: nodes.get(&bin.left).unwrap().hash().expect("Node should be committed"), - right_hash: nodes.get(&bin.right).unwrap().hash().expect("Node should be committed"), - }), - Node::Edge(edge) => ProofNode::Edge(EdgeProofNode { - path: edge.path.clone(), - child_hash: nodes.get(&edge.child).unwrap().hash().expect("Node should be committed"), - }), - Node::Leaf(_) => panic!("Leaf nodes should not appear in a proof"), - Node::Unresolved(_) => panic!("Unresolved nodes should not appear in a proof"), - } -} - -/// [ProofNode] s are lightweight versions of their `Node` counterpart. -/// They only consist of [BinaryProofNode] and [EdgeProofNode] because `Leaf` -/// and `Unresolved` nodes should not appear in a proof. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "parity-scale-codec", derive(Encode, Decode))] -#[cfg_attr(feature = "scale-info", derive(TypeInfo))] -pub enum ProofNode { - /// Binary node. - Binary(BinaryProofNode), - /// Edge node. - Edge(EdgeProofNode), -} - -#[cfg(feature = "serde")] -impl Serialize for ProofNode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - /// Utility struct used for serializing. - #[derive(Debug, Serialize)] - struct PathWrapper { - value: starknet_ff::FieldElement, - len: usize, - } - - match &self { - ProofNode::Binary(bin) => { - let mut state = serializer.serialize_struct_variant("ProofNode", 0, "Binary", 2)?; - state.serialize_field("left", &bin.left_hash)?; - state.serialize_field("right", &bin.right_hash)?; - state.end() - } - ProofNode::Edge(edge) => { - let value = starknet_ff::FieldElement::from_byte_slice_be(edge.path.as_raw_slice()).unwrap(); - let path_wrapper = PathWrapper { value, len: edge.path.len() }; - - let mut state = serializer.serialize_struct_variant("ProofNode", 1, "Edge", 2)?; - state.serialize_field("path", &path_wrapper)?; - state.serialize_field("child", &edge.child_hash)?; - state.end() - } - } - } -} - -/// A Starknet binary Merkle-Patricia tree with a specific root entry-point and storage. -/// -/// This is used to update, mutate and access global Starknet state as well as individual contract -/// states. -/// -/// For more information on how this functions internally, see [here](super::merkle_node). -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "parity-scale-codec", derive(Encode, Decode))] -#[cfg_attr(feature = "scale-info", derive(TypeInfo))] -pub struct MerkleTree { - root: NodeId, - nodes: NodesMapping, - latest_node_id: NodeId, - _hasher: PhantomData, -} - -impl MerkleTree { - /// Less visible initialization for `MerkleTree` as the main entry points should be - /// [`MerkleTree::::load`] for persistent trees and [`MerkleTree::empty`] for - /// transient ones. - fn new(root: Felt252Wrapper) -> Self { - let root_node = Node::Unresolved(root); - let mut nodes_mapping: HashMap = HashMap::new(); - let root_id = NodeId(0); // Assign the appropriate initial node ID here - nodes_mapping.insert(root_id, root_node); - - Self { root: root_id, nodes: NodesMapping(nodes_mapping), latest_node_id: root_id, _hasher: PhantomData } - } - - /// Empty tree. - pub fn empty() -> Self { - Self::new(Felt252Wrapper::ZERO) - } - - /// Returns the nodes mapping - pub fn nodes(&self) -> HashMap { - self.nodes.0.clone() - } - - /// Persists all changes to storage and returns the new root hash. - /// - /// Note that the root is reference counted in storage. Committing the - /// same tree again will therefore increment the count again. - pub fn commit(&mut self) -> Felt252Wrapper { - self.commit_mut() - } - - /// Return the state root. - pub fn commit_mut(&mut self) -> Felt252Wrapper { - // Go through the tree, collect dirty nodes, calculate their hashes, and - // persist them. Take care to increment ref counts of child nodes. Start from - // the root and traverse the tree. - self.commit_subtree(&self.root.clone()); - - // Unwrap is safe as `commit_subtree` will set the hash. - let root_hash = self.nodes.0.get(&self.root).unwrap().hash().unwrap(); - root_hash - } - - /// Persists any changes in this subtree to storage. - /// - /// This necessitates recursively calculating the hash of, and - /// in turn persisting, any changed child nodes. This is necessary - /// as the parent node's hash relies on its children hashes. - /// - /// In effect, the entire subtree gets persisted. - /// - /// # Arguments - /// - /// * `node` - The top node from the subtree to commit. - fn commit_subtree(&mut self, node_id: &NodeId) { - use Node::*; - let mut nodes = self.nodes.0.clone(); - let node = nodes.get_mut(node_id).unwrap(); - match node { - Unresolved(_) => { /* Unresolved nodes are already persisted. */ } - Leaf(_) => { /* storage wouldn't persist these even if we asked. */ } - Binary(binary) if binary.hash.is_some() => { /* not dirty, already persisted */ } - Edge(edge) if edge.hash.is_some() => { /* not dirty, already persisted */ } - - Binary(binary) => { - self.commit_subtree(&binary.left); - self.commit_subtree(&binary.right); - // This will succeed as `commit_subtree` will set the child hashes. - binary.calculate_hash::(&self.nodes.0.clone()); - } - - Edge(edge) => { - self.commit_subtree(&edge.child); - edge.calculate_hash::(&self.nodes.0.clone()); - } - } - - // Update internal nodes mapping - self.nodes.0.insert(*node_id, node.clone()); - } - - /// Sets the value of a key. To delete a key, set the value to [Felt252Wrapper::ZERO]. - /// - /// # Arguments - /// - /// * `key` - The key to set. - /// * `value` - The value to set. - pub fn set(&mut self, key: &BitSlice, value: Felt252Wrapper) { - let mut nodes = self.nodes.0.clone(); - - if value == Felt252Wrapper::ZERO { - return self.delete_leaf(key); - } - - // Changing or inserting a new leaf into the tree will change the hashes - // of all nodes along the path to the leaf. - let path = self.traverse(key); - for node in &path { - nodes.get_mut(node).unwrap().mark_dirty(); - } - - // There are three possibilities. - // - // 1. The leaf exists, in which case we simply change its value. - // - // 2. The tree is empty, we insert the new leaf and the root becomes an edge node connecting to it. - // - // 3. The leaf does not exist, and the tree is not empty. The final node in the traversal will be an - // edge node who's path diverges from our new leaf node's. - // - // This edge must be split into a new subtree containing both the existing edge's child and the - // new leaf. This requires an edge followed by a binary node and then further edges to both the - // current child and the new leaf. Any of these new edges may also end with an empty path in - // which case they should be elided. It depends on the common path length of the current edge - // and the new leaf i.e. the split may be at the first bit (in which case there is no leading - // edge), or the split may be in the middle (requires both leading and post edges), or the - // split may be the final bit (no post edge). - use Node::*; - match path.last() { - Some(node) => { - let match_node = self.nodes.0.get(node).unwrap(); - let updated: Node = match match_node { - Edge(edge) => { - let common = edge.common_path(key); - - // Height of the binary node - let branch_height = edge.height as usize + common.len(); - // Height of the binary node's children - let child_height = branch_height + 1; - - // Path from binary node to new leaf - let new_path = key[child_height..].to_bitvec(); - // Path from binary node to existing child - let old_path = edge.path[common.len() + 1..].to_bitvec(); - - // The new leaf branch of the binary node. - // (this may be edge -> leaf, or just leaf depending). - let new_leaf = Node::Leaf(value); - nodes.insert(self.latest_node_id.next_id(), new_leaf); - - let new = if new_path.is_empty() { - self.latest_node_id - } else { - let new_edge = Node::Edge(EdgeNode { - hash: None, - height: child_height as u64, - path: new_path, - child: self.latest_node_id, - }); - nodes.insert(self.latest_node_id.next_id(), new_edge); - self.latest_node_id - }; - - // The existing child branch of the binary node. - let old = if old_path.is_empty() { - edge.child - } else { - let old_edge = Node::Edge(EdgeNode { - hash: None, - height: child_height as u64, - path: old_path, - child: edge.child, - }); - nodes.insert(self.latest_node_id.next_id(), old_edge); - self.latest_node_id - }; - - let new_direction = Direction::from(key[branch_height]); - let (left, right) = match new_direction { - Direction::Left => (new, old), - Direction::Right => (old, new), - }; - - let branch = Node::Binary(BinaryNode { hash: None, height: branch_height as u64, left, right }); - nodes.insert(self.latest_node_id.next_id(), branch.clone()); - - // We may require an edge leading to the binary node. - if common.is_empty() { - branch - } else { - let edge = Node::Edge(EdgeNode { - hash: None, - height: edge.height, - path: common.to_bitvec(), - child: self.latest_node_id, - }); - nodes.insert(self.latest_node_id.next_id(), edge.clone()); - edge - } - } - // Leaf exists, we replace its value. - Leaf(_) => { - let leaf = Node::Leaf(value); - nodes.insert(self.latest_node_id.next_id(), leaf.clone()); - leaf - } - Unresolved(_) | Binary(_) => { - unreachable!("The end of a traversion cannot be unresolved or binary") - } - }; - - // node.swap(&Box::new(updated)); - nodes.insert(*node, updated); - nodes.insert(self.latest_node_id, self.nodes.0.get(node).unwrap().clone()); - } - None => { - // Getting no travel nodes implies that the tree is empty. - // - // Create a new leaf node with the value, and the root becomes - // an edge node connecting to the leaf. - let leaf = Node::Leaf(value); - nodes.insert(self.latest_node_id.next_id(), leaf); - let edge = - Node::Edge(EdgeNode { hash: None, height: 0, path: key.to_bitvec(), child: self.latest_node_id }); - nodes.insert(self.latest_node_id.next_id(), edge); - - self.root = self.latest_node_id; - } - } - - // Updates self nodes mapping - self.nodes.0 = nodes; - } - - /// Deletes a leaf node from the tree. - /// - /// This is not an external facing API; the functionality is instead accessed by calling - /// [`MerkleTree::set`] with value set to [`Felt252Wrapper::ZERO`]. - /// - /// # Arguments - /// - /// * `key` - The key to delete. - fn delete_leaf(&mut self, key: &BitSlice) { - let mut nodes = self.nodes.0.clone(); - // Algorithm explanation: - // - // The leaf's parent node is either an edge, or a binary node. - // If it's an edge node, then it must also be deleted. And its parent - // must be a binary node. In either case we end up with a binary node - // who's one child is deleted. This changes the binary to an edge node. - // - // Note that its possible that there is no binary node -- if the resulting tree would be empty. - // - // This new edge node may need to merge with the old binary node's parent node - // and other remaining child node -- if they're also edges. - // - // Then we are done. - let path = self.traverse(key); - - // Do nothing if the leaf does not exist. - match path.last() { - Some(node) => match nodes.get(node).unwrap() { - Node::Leaf(_) => {} - _ => return, - }, - None => return, - } - - // All hashes along the path will become invalid (if they aren't deleted). - for node in &path { - nodes.get_mut(node).unwrap().mark_dirty(); - } - - // Go backwards until we hit a branch node. - let mut node_iter = path.into_iter().rev().skip_while(|node| !self.nodes.0.get(node).unwrap().is_binary()); - - match node_iter.next() { - Some(node) => { - let new_edge = { - let node = nodes.get_mut(&node).unwrap(); - // This node must be a binary node due to the iteration condition. - let binary = node.as_binary().cloned().unwrap(); - // Create an edge node to replace the old binary node - // i.e. with the remaining child (note the direction invert), - // and a path of just a single bit. - let direction = binary.direction(key).invert(); - let child = binary.get_child(direction.clone()); - let path = once(bool::from(direction)).collect::>(); - let mut edge = EdgeNode { hash: None, height: binary.height, path, child }; - - // Merge the remaining child if it's an edge. - self.merge_edges(&mut edge); - - edge - }; - // Replace the old binary node with the new edge node. - // node.swap(&Box::new(Node::Edge(new_edge))); - nodes.insert(node, Node::Edge(new_edge)); - nodes.insert(self.latest_node_id, nodes.get(&node).unwrap().clone()); - } - None => { - // We reached the root without a hitting binary node. The new tree - // must therefore be empty. - self.root = NodeId(0); - return; - } - }; - - // Check the parent of the new edge. If it is also an edge, then they must merge. - if let Some(node) = node_iter.next() { - if let Node::Edge(edge) = nodes.get_mut(&node).unwrap() { - self.merge_edges(edge); - } - } - } - - /// Returns the value stored at key, or `None` if it does not exist. - /// - /// # Arguments - /// - /// * `key` - The key of the value to get. - /// - /// # Returns - /// - /// The value of the key. - pub fn get(&self, key: &BitSlice) -> Option { - self.traverse(key).last().and_then(|node| match self.nodes.0.get(node).unwrap() { - Node::Leaf(value) if !value.eq(&Felt252Wrapper::ZERO) => Some(*value), - _ => None, - }) - } - - /// Generates a merkle-proof for a given `key`. - /// - /// Returns vector of [`ProofNode`] which form a chain from the root to the key, - /// if it exists, or down to the node which proves that the key does not exist. - /// - /// The nodes are returned in order, root first. - /// - /// Verification is performed by confirming that: - /// 1. the chain follows the path of `key`, and - /// 2. the hashes are correct, and - /// 3. the root hash matches the known root - /// - /// # Arguments - /// - /// * `key` - The key to get the merkle proof of. - /// - /// # Returns - /// - /// The merkle proof and all the child nodes hashes. - pub fn get_proof(&self, key: &BitSlice) -> Vec { - let mut nodes = self.traverse(key); - - // Return an empty list if tree is empty. - let node = match nodes.last() { - Some(node) => node, - None => return Vec::new(), - }; - - // A leaf node is redundant data as the information for it is already contained in the previous - // node. - if matches!(self.nodes.0.get(node).unwrap(), Node::Leaf(_)) { - nodes.pop(); - } - - nodes - .iter() - .map(|node| match self.nodes.0.get(node).unwrap() { - Node::Binary(bin) => get_proof_node(&Node::Binary(bin.clone()), &self.nodes.0), - Node::Edge(edge) => get_proof_node(&Node::Edge(edge.clone()), &self.nodes.0), - _ => unreachable!(), - }) - .collect() - } - - /// Traverses from the current root towards the destination [Leaf](Node::Leaf) node. - /// Returns the list of nodes along the path. - /// - /// If the destination node exists, it will be the final node in the list. - /// - /// This means that the final node will always be either a the destination [Leaf](Node::Leaf) - /// node, or an [Edge](Node::Edge) node who's path suffix does not match the leaf's path. - /// - /// The final node can __not__ be a [Binary](Node::Binary) node since it would always be - /// possible to continue on towards the destination. Nor can it be an - /// [Unresolved](Node::Unresolved) node since this would be resolved to check if we can - /// travel further. - /// - /// # Arguments - /// - /// * `dst` - The node to get to. - /// - /// # Returns - /// - /// The list of nodes along the path. - fn traverse(&self, dst: &BitSlice) -> Vec { - if self.nodes.0.get(&self.root).unwrap().is_empty() { - return Vec::new(); - } - - let mut current = self.root; - #[allow(unused_variables)] - let mut height = 0; - let mut nodes = Vec::new(); - loop { - use Node::*; - - let current_tmp = self.nodes.0.get(¤t).unwrap().clone(); - - let next = match current_tmp { - Unresolved(_hash) => panic!("Resolve is useless"), - Binary(binary) => { - nodes.push(current); - let next = binary.direction(dst); - let next = binary.get_child(next); - height += 1; - next - } - Edge(edge) if edge.path_matches(dst) => { - nodes.push(current); - height += edge.path.len(); - edge.child - } - Leaf(_) | Edge(_) => { - nodes.push(current); - return nodes; - } - }; - - current = next; - } - } - - /// This is a convenience function which merges the edge node with its child __iff__ it is also - /// an edge. - /// - /// Does nothing if the child is not also an edge node. - /// - /// This can occur when mutating the tree (e.g. deleting a child of a binary node), and is an - /// illegal state (since edge nodes __must be__ maximal subtrees). - /// - /// # Arguments - /// - /// * `parent` - The parent node to merge the child with. - fn merge_edges(&self, parent: &mut EdgeNode) { - let resolved_child = match self.nodes.0.get(&parent.child).unwrap() { - Node::Unresolved(_hash) => panic!("Resolve is useless"), - other => other.clone(), - }; - - if let Some(child_edge) = resolved_child.as_edge().cloned() { - parent.path.extend_from_bitslice(&child_edge.path); - parent.child = child_edge.child; - } - } -} diff --git a/crates/primitives/commitments/src/merkle_patricia_tree/mod.rs b/crates/primitives/commitments/src/merkle_patricia_tree/mod.rs deleted file mode 100644 index b6b1078ef0..0000000000 --- a/crates/primitives/commitments/src/merkle_patricia_tree/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Merkle Tree implementation using references -pub mod ref_merkle_node; -pub mod ref_merkle_tree; -// Merkle Tree implementation using nodes mapping -pub mod merkle_node; -pub mod merkle_tree; diff --git a/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_node.rs b/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_node.rs deleted file mode 100644 index 671a35c074..0000000000 --- a/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_node.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! Contains constructs for describing the nodes in a Binary Merkle Patricia Tree -//! used by Starknet. -//! -//! For more information about how these Starknet trees are structured, see -//! [`MerkleTree`](super::ref_merkle_tree::RefMerkleTree). - -use alloc::rc::Rc; -use core::cell::RefCell; - -use bitvec::order::Msb0; -use bitvec::prelude::BitVec; -use bitvec::slice::BitSlice; -use mp_felt::Felt252Wrapper; -use mp_hashers::HasherT; - -/// A node in a Binary Merkle-Patricia Tree graph. -#[derive(Clone, Debug, PartialEq)] -pub enum Node { - /// A node that has not been fetched from storage yet. - /// - /// As such, all we know is its hash. - Unresolved(Felt252Wrapper), - /// A branch node with exactly two children. - Binary(BinaryNode), - /// Describes a path connecting two other nodes. - Edge(EdgeNode), - /// A leaf node that contains a value. - Leaf(Felt252Wrapper), -} - -/// Describes the [Node::Binary] variant. -#[derive(Clone, Debug, PartialEq)] -pub struct BinaryNode { - /// The hash of this node. Is [None] if the node - /// has not yet been committed. - pub hash: Option, - /// The height of this node in the tree. - pub height: usize, - /// [Left](Direction::Left) child. - pub left: Rc>, - /// [Right](Direction::Right) child. - pub right: Rc>, -} - -/// Node that is an edge. -#[derive(Clone, Debug, PartialEq)] -pub struct EdgeNode { - /// The hash of this node. Is [None] if the node - /// has not yet been committed. - pub hash: Option, - /// The starting height of this node in the tree. - pub height: usize, - /// The path this edge takes. - pub path: BitVec, - /// The child of this node. - pub child: Rc>, -} - -/// Describes the direction a child of a [BinaryNode] may have. -/// -/// Binary nodes have two children, one left and one right. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - /// Left direction. - Left, - /// Right direction. - Right, -} - -impl Direction { - /// Inverts the [Direction]. - /// - /// [Left] becomes [Right], and [Right] becomes [Left]. - /// - /// [Left]: Direction::Left - /// [Right]: Direction::Right - pub fn invert(self) -> Direction { - match self { - Direction::Left => Direction::Right, - Direction::Right => Direction::Left, - } - } -} - -impl From for Direction { - fn from(tf: bool) -> Self { - match tf { - true => Direction::Right, - false => Direction::Left, - } - } -} - -impl From for bool { - fn from(direction: Direction) -> Self { - match direction { - Direction::Left => false, - Direction::Right => true, - } - } -} - -impl BinaryNode { - /// Maps the key's bit at the binary node's height to a [Direction]. - /// - /// This can be used to check which direction the key describes in the context - /// of this binary node i.e. which direction the child along the key's path would - /// take. - /// - /// # Arguments - /// - /// * `key` - The key to get the direction of. - /// - /// # Returns - /// - /// The direction of the key. - pub fn direction(&self, key: &BitSlice) -> Direction { - key[self.height].into() - } - - /// Returns the [Left] or [Right] child. - /// - /// [Left]: Direction::Left - /// [Right]: Direction::Right - /// - /// # Arguments - /// - /// `direction` - The direction where to get the child from. - /// - /// # Returns - /// - /// The child in the specified direction. - pub fn get_child(&self, direction: Direction) -> Rc> { - match direction { - Direction::Left => self.left.clone(), - Direction::Right => self.right.clone(), - } - } - - /// If possible, calculates and sets its own hash value. - /// - /// Does nothing if the hash is already [Some]. - /// - /// If either child's hash is [None], then the hash cannot - /// be calculated and it will remain [None]. - pub(crate) fn calculate_hash(&mut self) { - if self.hash.is_some() { - return; - } - - let left = match self.left.borrow().hash() { - Some(hash) => hash, - None => unreachable!("subtrees have to be committed first"), - }; - - let right = match self.right.borrow().hash() { - Some(hash) => hash, - None => unreachable!("subtrees have to be committed first"), - }; - - self.hash = Some(Felt252Wrapper(H::hash_elements(left.0, right.0))); - } -} - -impl Node { - /// Convenience function which sets the inner node's hash to [None], if - /// applicable. - /// - /// Used to indicate that this node has been mutated. - pub fn mark_dirty(&mut self) { - match self { - Node::Binary(inner) => inner.hash = None, - Node::Edge(inner) => inner.hash = None, - _ => {} - } - } - - /// Returns true if the node represents an empty node -- this is defined as a node - /// with the [Felt252Wrapper::ZERO]. - /// - /// This can occur for the root node in an empty graph. - pub fn is_empty(&self) -> bool { - match self { - Node::Unresolved(hash) => hash == &Felt252Wrapper::ZERO, - _ => false, - } - } - - /// Is the node a binary node. - pub fn is_binary(&self) -> bool { - matches!(self, Node::Binary(..)) - } - - /// Convert to node to binary node type (returns None if it's not a binary node). - pub fn as_binary(&self) -> Option<&BinaryNode> { - match self { - Node::Binary(binary) => Some(binary), - _ => None, - } - } - - /// Convert to node to edge node type (returns None if it's not a edge node). - pub fn as_edge(&self) -> Option<&EdgeNode> { - match self { - Node::Edge(edge) => Some(edge), - _ => None, - } - } - - /// Get the hash of a node. - pub fn hash(&self) -> Option { - match self { - Node::Unresolved(hash) => Some(*hash), - Node::Binary(binary) => binary.hash, - Node::Edge(edge) => edge.hash, - Node::Leaf(value) => Some(*value), - } - } -} - -impl EdgeNode { - /// Returns true if the edge node's path matches the same path given by the key. - /// - /// # Arguments - /// - /// * `key` - The key to check if the path matches with the edge node. - pub fn path_matches(&self, key: &BitSlice) -> bool { - self.path == key[self.height..self.height + self.path.len()] - } - - /// Returns the common bit prefix between the edge node's path and the given key. - /// - /// This is calculated with the edge's height taken into account. - /// - /// # Arguments - /// - /// * `key` - The key to get the common path from. - pub fn common_path(&self, key: &BitSlice) -> &BitSlice { - let key_path = key.iter().skip(self.height); - let common_length = key_path.zip(self.path.iter()).take_while(|(a, b)| a == b).count(); - - &self.path[..common_length] - } - - /// If possible, calculates and sets its own hash value. - /// - /// Does nothing if the hash is already [Some]. - /// - /// If the child's hash is [None], then the hash cannot - /// be calculated and it will remain [None]. - pub(crate) fn calculate_hash(&mut self) { - if self.hash.is_some() { - return; - } - - let child = match self.child.borrow().hash() { - Some(hash) => hash, - None => unreachable!("subtree has to be committed before"), - }; - let mut temp_path = self.path.clone(); - temp_path.force_align(); - - let path = Felt252Wrapper::try_from(temp_path.into_vec().as_slice()).unwrap(); - let mut length = [0; 32]; - // Safe as len() is guaranteed to be <= 251 - length[31] = self.path.len() as u8; - - let length = Felt252Wrapper::try_from(&length).unwrap(); - let hash = Felt252Wrapper(H::hash_elements(child.0, path.0) + length.0); - self.hash = Some(hash); - } -} diff --git a/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_tree.rs b/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_tree.rs deleted file mode 100644 index c9f51a2c87..0000000000 --- a/crates/primitives/commitments/src/merkle_patricia_tree/ref_merkle_tree.rs +++ /dev/null @@ -1,484 +0,0 @@ -//! This is a gigantic copy pasta from Thanks to the equilibrium team and whoever else contributed for the code. -use alloc::rc::Rc; -use alloc::vec::Vec; -use core::cell::RefCell; -use core::iter::once; -use core::marker::PhantomData; - -use bitvec::prelude::{BitSlice, BitVec, Msb0}; -use mp_felt::Felt252Wrapper; -use mp_hashers::HasherT; - -use super::ref_merkle_node::{BinaryNode, Direction, EdgeNode, Node}; - -/// Lightweight representation of [BinaryNode]. Only holds left and right hashes. -#[derive(Debug, PartialEq, Eq)] -pub struct BinaryProofNode { - /// Left hash. - pub left_hash: Felt252Wrapper, - /// Right hash. - pub right_hash: Felt252Wrapper, -} - -impl From<&BinaryNode> for ProofNode { - fn from(bin: &BinaryNode) -> Self { - Self::Binary(BinaryProofNode { - left_hash: bin.left.borrow().hash().expect("Node should be committed"), - right_hash: bin.right.borrow().hash().expect("Node should be committed"), - }) - } -} - -/// Ligthtweight representation of [EdgeNode]. Only holds its path and its child's hash. -#[derive(Debug, PartialEq, Eq)] -pub struct EdgeProofNode { - /// Path of the node. - pub path: BitVec, - /// Hash of the child node. - pub child_hash: Felt252Wrapper, -} - -impl From<&EdgeNode> for ProofNode { - fn from(edge: &EdgeNode) -> Self { - Self::Edge(EdgeProofNode { - path: edge.path.clone(), - child_hash: edge.child.borrow().hash().expect("Node should be committed"), - }) - } -} - -/// [ProofNode] s are lightweight versions of their `Node` counterpart. -/// They only consist of [BinaryProofNode] and [EdgeProofNode] because `Leaf` -/// and `Unresolved` nodes should not appear in a proof. -#[derive(Debug, PartialEq, Eq)] -pub enum ProofNode { - /// Binary node. - Binary(BinaryProofNode), - /// Edge node. - Edge(EdgeProofNode), -} - -/// A Starknet binary Merkle-Patricia tree with a specific root entry-point and storage. -/// -/// This is used to update, mutate and access global Starknet state as well as individual contract -/// states. -/// -/// For more information on how this functions internally, see [here](super::ref_merkle_tree). -#[derive(Debug, Clone)] -pub struct RefMerkleTree { - root: Rc>, - _hasher: PhantomData, -} - -impl RefMerkleTree { - /// Less visible initialization for `MerkleTree` as the main entry points should be - /// [`MerkleTree::::load`] for persistent trees and [`MerkleTree::empty`] for - /// transient ones. - fn new(root: Felt252Wrapper) -> Self { - let root_node = Rc::new(RefCell::new(Node::Unresolved(root))); - Self { root: root_node, _hasher: PhantomData } - } - - /// Empty tree. - pub fn empty() -> Self { - Self::new(Felt252Wrapper::ZERO) - } - - /// Persists all changes to storage and returns the new root hash. - /// - /// Note that the root is reference counted in storage. Committing the - /// same tree again will therefore increment the count again. - pub fn commit(&mut self) -> Felt252Wrapper { - self.commit_mut() - } - /// Return the state root. - pub fn commit_mut(&mut self) -> Felt252Wrapper { - // Go through tree, collect dirty nodes, calculate their hashes and - // persist them. Take care to increment ref counts of child nodes. So in order - // to do this correctly, will have to start back-to-front. - Self::commit_subtree(&mut self.root.borrow_mut()); - // unwrap is safe as `commit_subtree` will set the hash. - self.root.borrow().hash().unwrap() - } - - /// Persists any changes in this subtree to storage. - /// - /// This necessitates recursively calculating the hash of, and - /// in turn persisting, any changed child nodes. This is necessary - /// as the parent node's hash relies on its children hashes. - /// - /// In effect, the entire subtree gets persisted. - /// - /// # Arguments - /// - /// * `node` - The top node from the subtree to commit. - fn commit_subtree(node: &mut Node) { - use Node::*; - match node { - Unresolved(_) => { /* Unresolved nodes are already persisted. */ } - Leaf(_) => { /* storage wouldn't persist these even if we asked. */ } - Binary(binary) if binary.hash.is_some() => { /* not dirty, already persisted */ } - Edge(edge) if edge.hash.is_some() => { /* not dirty, already persisted */ } - - Binary(binary) => { - Self::commit_subtree(&mut binary.left.borrow_mut()); - Self::commit_subtree(&mut binary.right.borrow_mut()); - // This will succeed as `commit_subtree` will set the child hashes. - binary.calculate_hash::(); - } - - Edge(edge) => { - Self::commit_subtree(&mut edge.child.borrow_mut()); - // This will succeed as `commit_subtree` will set the child's hash. - edge.calculate_hash::(); - } - } - } - - /// Sets the value of a key. To delete a key, set the value to [Felt252Wrapper::ZERO]. - /// - /// # Arguments - /// - /// * `key` - The key to set. - /// * `value` - The value to set. - pub fn set(&mut self, key: &BitSlice, value: Felt252Wrapper) { - if value == Felt252Wrapper::ZERO { - return self.delete_leaf(key); - } - - // Changing or inserting a new leaf into the tree will change the hashes - // of all nodes along the path to the leaf. - let path = self.traverse(key); - for node in &path { - node.borrow_mut().mark_dirty(); - } - - // There are three possibilities. - // - // 1. The leaf exists, in which case we simply change its value. - // - // 2. The tree is empty, we insert the new leaf and the root becomes an edge node connecting to it. - // - // 3. The leaf does not exist, and the tree is not empty. The final node in the traversal will be an - // edge node who's path diverges from our new leaf node's. - // - // This edge must be split into a new subtree containing both the existing edge's child and the - // new leaf. This requires an edge followed by a binary node and then further edges to both the - // current child and the new leaf. Any of these new edges may also end with an empty path in - // which case they should be elided. It depends on the common path length of the current edge - // and the new leaf i.e. the split may be at the first bit (in which case there is no leading - // edge), or the split may be in the middle (requires both leading and post edges), or the - // split may be the final bit (no post edge). - use Node::*; - match path.last() { - Some(node) => { - let updated = match &*node.borrow() { - Edge(edge) => { - let common = edge.common_path(key); - - // Height of the binary node - let branch_height = edge.height + common.len(); - // Height of the binary node's children - let child_height = branch_height + 1; - - // Path from binary node to new leaf - let new_path = key[child_height..].to_bitvec(); - // Path from binary node to existing child - let old_path = edge.path[common.len() + 1..].to_bitvec(); - - // The new leaf branch of the binary node. - // (this may be edge -> leaf, or just leaf depending). - let new_leaf = Node::Leaf(value); - let new = if new_path.is_empty() { - Rc::new(RefCell::new(new_leaf)) - } else { - let new_edge = Node::Edge(EdgeNode { - hash: None, - height: child_height, - path: new_path, - child: Rc::new(RefCell::new(new_leaf)), - }); - Rc::new(RefCell::new(new_edge)) - }; - - // The existing child branch of the binary node. - let old = if old_path.is_empty() { - edge.child.clone() - } else { - let old_edge = Node::Edge(EdgeNode { - hash: None, - height: child_height, - path: old_path, - child: edge.child.clone(), - }); - Rc::new(RefCell::new(old_edge)) - }; - - let new_direction = Direction::from(key[branch_height]); - let (left, right) = match new_direction { - Direction::Left => (new, old), - Direction::Right => (old, new), - }; - - let branch = Node::Binary(BinaryNode { hash: None, height: branch_height, left, right }); - - // We may require an edge leading to the binary node. - if common.is_empty() { - branch - } else { - Node::Edge(EdgeNode { - hash: None, - height: edge.height, - path: common.to_bitvec(), - child: Rc::new(RefCell::new(branch)), - }) - } - } - // Leaf exists, we replace its value. - Leaf(_) => Node::Leaf(value), - Unresolved(_) | Binary(_) => { - unreachable!("The end of a traversion cannot be unresolved or binary") - } - }; - - node.swap(&RefCell::new(updated)); - } - None => { - // Getting no travel nodes implies that the tree is empty. - // - // Create a new leaf node with the value, and the root becomes - // an edge node connecting to the leaf. - let leaf = Node::Leaf(value); - let edge = Node::Edge(EdgeNode { - hash: None, - height: 0, - path: key.to_bitvec(), - child: Rc::new(RefCell::new(leaf)), - }); - - self.root = Rc::new(RefCell::new(edge)); - } - } - } - - /// Deletes a leaf node from the tree. - /// - /// This is not an external facing API; the functionality is instead accessed by calling - /// [`MerkleTree::set`] with value set to [`Felt252Wrapper::ZERO`]. - /// - /// # Arguments - /// - /// * `key` - The key to delete. - fn delete_leaf(&mut self, key: &BitSlice) { - // Algorithm explanation: - // - // The leaf's parent node is either an edge, or a binary node. - // If it's an edge node, then it must also be deleted. And its parent - // must be a binary node. In either case we end up with a binary node - // who's one child is deleted. This changes the binary to an edge node. - // - // Note that its possible that there is no binary node -- if the resulting tree would be empty. - // - // This new edge node may need to merge with the old binary node's parent node - // and other remaining child node -- if they're also edges. - // - // Then we are done. - let path = self.traverse(key); - - // Do nothing if the leaf does not exist. - match path.last() { - Some(node) => match &*node.borrow() { - Node::Leaf(_) => {} - _ => return, - }, - None => return, - } - - // All hashes along the path will become invalid (if they aren't deleted). - for node in &path { - node.borrow_mut().mark_dirty(); - } - - // Go backwards until we hit a branch node. - let mut node_iter = path.into_iter().rev().skip_while(|node| !node.borrow().is_binary()); - - match node_iter.next() { - Some(node) => { - let new_edge = { - // This node must be a binary node due to the iteration condition. - let binary = node.borrow().as_binary().cloned().unwrap(); - // Create an edge node to replace the old binary node - // i.e. with the remaining child (note the direction invert), - // and a path of just a single bit. - let direction = binary.direction(key).invert(); - let child = binary.get_child(direction); - let path = once(bool::from(direction)).collect::>(); - let mut edge = EdgeNode { hash: None, height: binary.height, path, child }; - - // Merge the remaining child if it's an edge. - self.merge_edges(&mut edge); - - edge - }; - // Replace the old binary node with the new edge node. - node.swap(&RefCell::new(Node::Edge(new_edge))); - } - None => { - // We reached the root without a hitting binary node. The new tree - // must therefore be empty. - self.root = Rc::new(RefCell::new(Node::Unresolved(Felt252Wrapper::ZERO))); - return; - } - }; - - // Check the parent of the new edge. If it is also an edge, then they must merge. - if let Some(node) = node_iter.next() { - if let Node::Edge(edge) = &mut *node.borrow_mut() { - self.merge_edges(edge); - } - } - } - - /// Returns the value stored at key, or `None` if it does not exist. - /// - /// # Arguments - /// - /// * `key` - The key of the value to get. - /// - /// # Returns - /// - /// The value of the key. - #[allow(unused)] - pub fn get(&self, key: &BitSlice) -> Option { - self.traverse(key).last().and_then(|node| match &*node.borrow() { - Node::Leaf(value) if !value.eq(&Felt252Wrapper::ZERO) => Some(*value), - _ => None, - }) - } - - /// Generates a merkle-proof for a given `key`. - /// - /// Returns vector of [`ProofNode`] which form a chain from the root to the key, - /// if it exists, or down to the node which proves that the key does not exist. - /// - /// The nodes are returned in order, root first. - /// - /// Verification is performed by confirming that: - /// 1. the chain follows the path of `key`, and - /// 2. the hashes are correct, and - /// 3. the root hash matches the known root - /// - /// # Arguments - /// - /// * `key` - The key to get the merkle proof of. - /// - /// # Returns - /// - /// The merkle proof and all the child nodes hashes. - #[allow(unused)] - pub fn get_proof(&self, key: &BitSlice) -> Vec { - let mut nodes = self.traverse(key); - - // Return an empty list if tree is empty. - let node = match nodes.last() { - Some(node) => node, - None => return Vec::new(), - }; - - // A leaf node is redundant data as the information for it is already contained in the previous - // node. - if matches!(&*node.borrow(), Node::Leaf(_)) { - nodes.pop(); - } - - nodes - .iter() - .map(|node| match &*node.borrow() { - Node::Binary(bin) => ProofNode::from(bin), - Node::Edge(edge) => ProofNode::from(edge), - _ => unreachable!(), - }) - .collect() - } - - /// Traverses from the current root towards the destination [Leaf](Node::Leaf) node. - /// Returns the list of nodes along the path. - /// - /// If the destination node exists, it will be the final node in the list. - /// - /// This means that the final node will always be either a the destination [Leaf](Node::Leaf) - /// node, or an [Edge](Node::Edge) node who's path suffix does not match the leaf's path. - /// - /// The final node can __not__ be a [Binary](Node::Binary) node since it would always be - /// possible to continue on towards the destination. Nor can it be an - /// [Unresolved](Node::Unresolved) node since this would be resolved to check if we can - /// travel further. - /// - /// # Arguments - /// - /// * `dst` - The node to get to. - /// - /// # Returns - /// - /// The list of nodes along the path. - fn traverse(&self, dst: &BitSlice) -> Vec>> { - if self.root.borrow().is_empty() { - return Vec::new(); - } - - let mut current = self.root.clone(); - #[allow(unused_variables)] - let mut height = 0; - let mut nodes = Vec::new(); - loop { - use Node::*; - - let current_tmp = current.borrow().clone(); - - let next = match current_tmp { - Unresolved(_hash) => panic!("Resolve is useless"), - Binary(binary) => { - nodes.push(current.clone()); - let next = binary.direction(dst); - let next = binary.get_child(next); - height += 1; - next - } - Edge(edge) if edge.path_matches(dst) => { - nodes.push(current.clone()); - height += edge.path.len(); - edge.child.clone() - } - Leaf(_) | Edge(_) => { - nodes.push(current); - return nodes; - } - }; - - current = next; - } - } - - /// This is a convenience function which merges the edge node with its child __iff__ it is also - /// an edge. - /// - /// Does nothing if the child is not also an edge node. - /// - /// This can occur when mutating the tree (e.g. deleting a child of a binary node), and is an - /// illegal state (since edge nodes __must be__ maximal subtrees). - /// - /// # Arguments - /// - /// * `parent` - The parent node to merge the child with. - fn merge_edges(&self, parent: &mut EdgeNode) { - let resolved_child = match &*parent.child.borrow() { - Node::Unresolved(_hash) => panic!("Resolve is useless"), - other => other.clone(), - }; - - if let Some(child_edge) = resolved_child.as_edge().cloned() { - parent.path.extend_from_bitslice(&child_edge.path); - parent.child = child_edge.child; - } - } -} diff --git a/crates/primitives/commitments/src/tests.rs b/crates/primitives/commitments/src/tests.rs deleted file mode 100644 index 0b715aa63f..0000000000 --- a/crates/primitives/commitments/src/tests.rs +++ /dev/null @@ -1,119 +0,0 @@ -use mp_felt::Felt252Wrapper; -use mp_hashers::pedersen::PedersenHasher; -use mp_hashers::HasherT; -use starknet_api::stdlib::collections::HashMap; -use starknet_core::crypto::compute_hash_on_elements; -use starknet_crypto::FieldElement; - -use super::merkle_patricia_tree::merkle_node::{BinaryNode, Direction, Node, NodeId}; - -pub const PEDERSEN_ZERO_HASH: &str = "0x49EE3EBA8C1600700EE1B87EB599F16716B0B1022947733551FDE4050CA6804"; - -#[derive(Default)] -struct TestHasher; - -impl HasherT for TestHasher { - fn hash_bytes(_data: &[u8]) -> Felt252Wrapper { - unimplemented!() - } - - fn compute_hash_on_wrappers(_data: &[Felt252Wrapper]) -> Felt252Wrapper { - unimplemented!() - } - - fn hash_elements(a: FieldElement, b: FieldElement) -> FieldElement { - a + b - } - - fn compute_hash_on_elements(elements: &[FieldElement]) -> FieldElement { - if elements.is_empty() { - FieldElement::ZERO - } else { - let hash = elements.iter().fold(FieldElement::ZERO, |a, b| a + *b); - hash - } - } -} - -#[test] -fn test_binary_node_functions() { - let mut nodes: HashMap = HashMap::new(); - nodes.insert(NodeId(0), Node::Leaf(Felt252Wrapper::from(2_u32))); - nodes.insert(NodeId(1), Node::Leaf(Felt252Wrapper::from(3_u32))); - - let binary_node = - BinaryNode { hash: Some(Felt252Wrapper::from(1_u32)), height: 0, left: NodeId(0), right: NodeId(1) }; - - let unresolved_node = Node::Unresolved(Felt252Wrapper::from(6_u32)); - - let left_child = binary_node.get_child(Direction::Left); - let right_child = binary_node.get_child(Direction::Right); - - assert_eq!(left_child, NodeId(0)); - assert_eq!(right_child, NodeId(1)); - assert_eq!(nodes.get(&left_child).unwrap().hash(), Some(Felt252Wrapper::from(2_u32))); - assert_eq!(nodes.get(&right_child).unwrap().hash(), Some(Felt252Wrapper::from(3_u32))); - - assert_eq!(binary_node.hash, Some(Felt252Wrapper::from(1_u32))); - - assert!(!unresolved_node.is_empty()); - assert!(!unresolved_node.is_binary()); -} - -#[test] -fn test_direction_invert() { - let left = Direction::Left; - let right = Direction::Right; - - assert_eq!(left.invert(), Direction::Right); - assert_eq!(right.invert(), Direction::Left); -} - -#[test] -fn test_binary_node_calculate_hash() { - let mut nodes: HashMap = HashMap::new(); - nodes.insert(NodeId(0), Node::Leaf(Felt252Wrapper::from(2_u32))); - nodes.insert(NodeId(1), Node::Leaf(Felt252Wrapper::from(3_u32))); - - let mut binary_node = BinaryNode { hash: None, height: 0, left: NodeId(0), right: NodeId(1) }; - - binary_node.calculate_hash::(&nodes); - assert_eq!(binary_node.hash, Some(Felt252Wrapper::from(5_u32))); -} - -#[test] -fn test_binary_node_implementations() { - let mut nodes: HashMap = HashMap::new(); - nodes.insert(NodeId(0), Node::Leaf(Felt252Wrapper::from(2_u32))); - nodes.insert(NodeId(1), Node::Leaf(Felt252Wrapper::from(3_u32))); - - let test_node = BinaryNode { hash: None, height: 0, left: NodeId(0), right: NodeId(1) }; - - // Test Display trait implementation - let node_string = format!("{:?}", test_node); - assert_eq!(node_string, "BinaryNode { hash: None, height: 0, left: NodeId(0), right: NodeId(1) }"); - - // Test Debug trait implementation - let debug_string = format!("{:?}", test_node); - assert_eq!(debug_string, "BinaryNode { hash: None, height: 0, left: NodeId(0), right: NodeId(1) }"); -} - -#[test] -fn test_pedersen_hash_elements_zero() { - let elements = vec![Felt252Wrapper::ZERO, Felt252Wrapper::ONE]; - - let expected_hash = compute_hash_on_elements(&[FieldElement::ZERO, FieldElement::ONE]); - assert_eq!(PedersenHasher::compute_hash_on_wrappers(&elements), expected_hash.into()); -} - -#[test] -fn test_pedersen_hash_elements_empty() { - let elements = vec![]; - - assert_eq!( - PedersenHasher::compute_hash_on_wrappers(&elements), - Felt252Wrapper::from_hex_be(PEDERSEN_ZERO_HASH).unwrap() - ); -} - -// TODO: add tests to poseidon hasher too diff --git a/crates/primitives/genesis-config/Cargo.toml b/crates/primitives/genesis-config/Cargo.toml index 3ddff66cdb..83a76435fd 100644 --- a/crates/primitives/genesis-config/Cargo.toml +++ b/crates/primitives/genesis-config/Cargo.toml @@ -17,7 +17,7 @@ blockifier = { workspace = true, features = [ ] } # # third party -derive_more = { workspace = true } +derive_more = { workspace = true, features = ["constructor"] } hex = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true }