diff --git a/CHANGELOG.md b/CHANGELOG.md index 897b21a2b..74025a3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Database is now created during bootstrap process instead of on first startup. - Data directory is no longer created but is instead expected to exist. - The genesis block can no longer be configured which also removes the `store dump-genesis` command. +- [BREAKING] Use `AccountTree` and update account witness proto definitions (#783). ## v0.8.0 (2025-03-26) diff --git a/Cargo.lock b/Cargo.lock index c14eaaeaa..385681664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arrayref" @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -536,9 +536,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", "clap_derive", @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" dependencies = [ "anstream", "anstyle", @@ -1123,9 +1123,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", @@ -1631,9 +1631,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -1813,7 +1813,7 @@ dependencies = [ [[package]] name = "miden-block-prover" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "miden-crypto", "miden-lib", @@ -1852,7 +1852,7 @@ dependencies = [ "glob", "num", "num-complex", - "rand 0.9.0", + "rand 0.9.1", "rand_core 0.9.3", "sha3", "thiserror 2.0.12", @@ -1877,7 +1877,7 @@ dependencies = [ "miden-objects", "miden-tx", "mime", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "serde", "serde_json", @@ -1906,7 +1906,7 @@ dependencies = [ [[package]] name = "miden-lib" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "miden-assembly", "miden-objects", @@ -1971,7 +1971,7 @@ dependencies = [ "miden-node-store", "miden-node-utils", "miden-objects", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "tokio", "url", @@ -1998,7 +1998,7 @@ dependencies = [ "miden-tx", "miden-tx-batch-prover", "pretty_assertions", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "thiserror 2.0.12", "tokio", @@ -2094,7 +2094,7 @@ dependencies = [ "miden-node-store", "miden-node-utils", "miden-objects", - "rand 0.9.0", + "rand 0.9.1", "rayon", "tokio", "tonic", @@ -2121,7 +2121,7 @@ dependencies = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", - "rand 0.9.0", + "rand 0.9.1", "serde", "tonic", "tracing", @@ -2136,7 +2136,7 @@ dependencies = [ [[package]] name = "miden-objects" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "bech32", "getrandom 0.3.2", @@ -2145,7 +2145,7 @@ dependencies = [ "miden-crypto", "miden-processor", "miden-verifier", - "rand 0.9.0", + "rand 0.9.1", "rand_xoshiro", "semver 1.0.26", "serde", @@ -2183,7 +2183,7 @@ dependencies = [ [[package]] name = "miden-proving-service-client" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "async-trait", "getrandom 0.3.2", @@ -2213,7 +2213,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "async-trait", "miden-lib", @@ -2221,7 +2221,7 @@ dependencies = [ "miden-processor", "miden-prover", "miden-verifier", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "thiserror 2.0.12", "winter-maybe-async", @@ -2230,7 +2230,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" version = "0.9.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#b65c2f57a6c6ba9f16036968371f07e180358cc6" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#0b0a2d8d955772583767ba656dd85a36694fa1f1" dependencies = [ "miden-core", "miden-crypto", @@ -2599,7 +2599,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand 0.9.0", + "rand 0.9.1", "serde_json", "thiserror 2.0.12", "tokio", @@ -2796,9 +2796,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -2962,13 +2962,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", ] [[package]] diff --git a/crates/block-producer/src/block_builder/mod.rs b/crates/block-producer/src/block_builder/mod.rs index 89e61161a..b86445fe9 100644 --- a/crates/block-producer/src/block_builder/mod.rs +++ b/crates/block-producer/src/block_builder/mod.rs @@ -309,21 +309,14 @@ impl TelemetryInjectorExt for ProposedBlock { u32::try_from(self.created_nullifiers().len()) .expect("should have less than u32::MAX created nullifiers"), ); - let num_block_created_notes = self - .output_note_batches() - .iter() - .fold(0, |acc, output_notes| acc + output_notes.len()); + let num_block_created_notes = self.batches().num_created_notes(); span.set_attribute( "block.output_notes.count", u32::try_from(num_block_created_notes) .expect("should have less than u32::MAX output notes"), ); - let num_batch_created_notes = self - .batches() - .as_slice() - .iter() - .fold(0, |acc, batch| acc + batch.output_notes().len()); + let num_batch_created_notes = self.batches().num_created_notes(); span.set_attribute( "block.batches.output_notes.count", u32::try_from(num_batch_created_notes) diff --git a/crates/block-producer/src/test_utils/block.rs b/crates/block-producer/src/test_utils/block.rs index 9383fd9c7..dc679064f 100644 --- a/crates/block-producer/src/test_utils/block.rs +++ b/crates/block-producer/src/test_utils/block.rs @@ -1,11 +1,11 @@ use miden_objects::{ - ACCOUNT_TREE_DEPTH, Digest, + Digest, batch::ProvenBatch, block::{ - BlockAccountUpdate, BlockHeader, BlockNoteIndex, BlockNoteTree, OutputNoteBatch, - ProvenBlock, + AccountTree, BlockAccountUpdate, BlockHeader, BlockNoteIndex, BlockNoteTree, + OutputNoteBatch, ProvenBlock, }, - crypto::merkle::{Mmr, SimpleSmt}, + crypto::merkle::Mmr, note::Nullifier, transaction::{OrderedTransactionHeaders, OutputNote}, }; @@ -34,7 +34,9 @@ pub async fn build_expected_block_header( let new_account_root = { let mut store_accounts = store.accounts.read().await.clone(); for (&account_id, update) in updated_accounts { - store_accounts.insert(account_id.into(), update.final_state_commitment().into()); + store_accounts + .insert(account_id, update.final_state_commitment()) + .expect("account IDs should be unique"); } store_accounts.root() @@ -71,7 +73,7 @@ pub async fn build_expected_block_header( #[derive(Debug)] pub struct MockBlockBuilder { - store_accounts: SimpleSmt, + store_accounts: AccountTree, store_chain_mmr: Mmr, last_block_header: BlockHeader, @@ -105,7 +107,8 @@ impl MockBlockBuilder { pub fn account_updates(mut self, updated_accounts: Vec) -> Self { for update in &updated_accounts { self.store_accounts - .insert(update.account_id().into(), update.final_state_commitment().into()); + .insert(update.account_id(), update.final_state_commitment()) + .expect("account IDs should be unique"); } self.updated_accounts = Some(updated_accounts); diff --git a/crates/block-producer/src/test_utils/store.rs b/crates/block-producer/src/test_utils/store.rs index 5c1abe583..382002943 100644 --- a/crates/block-producer/src/test_utils/store.rs +++ b/crates/block-producer/src/test_utils/store.rs @@ -5,11 +5,11 @@ use std::{ }; use miden_objects::{ - ACCOUNT_TREE_DEPTH, Digest, EMPTY_WORD, ZERO, + Digest, EMPTY_WORD, Word, ZERO, account::AccountId, batch::ProvenBatch, - block::{BlockHeader, BlockNumber, OutputNoteBatch, ProvenBlock}, - crypto::merkle::{Mmr, SimpleSmt, Smt}, + block::{AccountTree, BlockHeader, BlockNumber, OutputNoteBatch, ProvenBlock}, + crypto::merkle::{Mmr, Smt}, note::{NoteId, NoteInclusionProof}, transaction::ProvenTransaction, }; @@ -26,7 +26,7 @@ use crate::{ /// Builds a [`MockStoreSuccess`] #[derive(Debug)] pub struct MockStoreSuccessBuilder { - accounts: Option>, + accounts: Option, notes: Option>, produced_nullifiers: Option>, chain_mmr: Option, @@ -36,16 +36,13 @@ pub struct MockStoreSuccessBuilder { impl MockStoreSuccessBuilder { pub fn from_batches<'a>(batches_iter: impl Iterator + Clone) -> Self { let accounts_smt = { - let accounts = batches_iter - .clone() - .flat_map(|batch| { - batch - .account_updates() - .iter() - .map(|(account_id, update)| (account_id, update.initial_state_commitment())) - }) - .map(|(account_id, commitment)| (account_id.prefix().into(), commitment.into())); - SimpleSmt::::with_leaves(accounts).unwrap() + let accounts = batches_iter.clone().flat_map(|batch| { + batch + .account_updates() + .iter() + .map(|(account_id, update)| (*account_id, update.initial_state_commitment())) + }); + AccountTree::with_entries(accounts).unwrap() }; Self { @@ -58,12 +55,7 @@ impl MockStoreSuccessBuilder { } pub fn from_accounts(accounts: impl Iterator) -> Self { - let accounts_smt = { - let accounts = accounts - .map(|(account_id, commitment)| (account_id.prefix().into(), commitment.into())); - - SimpleSmt::::with_leaves(accounts).unwrap() - }; + let accounts_smt = AccountTree::with_entries(accounts).unwrap(); Self { accounts: Some(accounts_smt), @@ -107,7 +99,7 @@ impl MockStoreSuccessBuilder { pub fn build(self) -> MockStoreSuccess { let block_num = self.block_num.unwrap_or(1.into()); - let accounts_smt = self.accounts.unwrap_or(SimpleSmt::new().unwrap()); + let accounts_smt = self.accounts.unwrap_or_default(); let notes = self.notes.unwrap_or_default(); let block_note_tree = note_created_smt_from_note_batches(notes.iter()); let note_root = block_note_tree.root(); @@ -168,7 +160,7 @@ impl MockStoreSuccessBuilder { pub struct MockStoreSuccess { /// Map account id -> account commitment - pub accounts: Arc>>, + pub accounts: Arc>, /// Stores the nullifiers of the notes that were consumed pub produced_nullifiers: Arc>, @@ -202,7 +194,8 @@ impl MockStoreSuccess { // update accounts for update in block.updated_accounts() { locked_accounts - .insert(update.account_id().into(), update.final_state_commitment().into()); + .insert(update.account_id(), update.final_state_commitment()) + .expect("TODO: what should we do with this error?"); } let header = block.header(); debug_assert_eq!(locked_accounts.root(), header.account_root()); @@ -254,12 +247,12 @@ impl MockStoreSuccess { let locked_produced_nullifiers = self.produced_nullifiers.read().await; let account_commitment = { - let account_commitment = locked_accounts.get_leaf(&proven_tx.account_id().into()); + let account_commitment = locked_accounts.get(proven_tx.account_id()); - if account_commitment == EMPTY_WORD { + if Word::from(account_commitment) == EMPTY_WORD { None } else { - Some(account_commitment.into()) + Some(account_commitment) } }; diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 751b5d2fb..2279b117c 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -4,9 +4,9 @@ use miden_node_utils::formatting::format_opt; use miden_objects::{ Digest, account::{Account, AccountHeader, AccountId}, - block::BlockNumber, - crypto::{hash::rpo::RpoDigest, merkle::MerklePath}, - utils::{Deserializable, Serializable}, + block::{AccountWitness, BlockNumber}, + crypto::hash::rpo::RpoDigest, + utils::{Deserializable, DeserializationError, Serializable}, }; use super::try_convert; @@ -155,16 +155,16 @@ impl TryInto for proto::requests::get_account_proofs_reques #[derive(Clone, Debug)] pub struct AccountWitnessRecord { pub account_id: AccountId, - pub initial_state_commitment: Digest, - pub proof: MerklePath, + pub witness: AccountWitness, } impl From for proto::responses::AccountWitness { fn from(from: AccountWitnessRecord) -> Self { Self { account_id: Some(from.account_id.into()), - initial_state_commitment: Some(from.initial_state_commitment.into()), - proof: Some(Into::into(&from.proof)), + witness_id: Some(from.witness.id().into()), + commitment: Some(from.witness.state_commitment().into()), + path: Some(from.witness.into_proof().into_parts().0.into()), } } } @@ -175,22 +175,33 @@ impl TryFrom for AccountWitnessRecord { fn try_from( account_witness_record: proto::responses::AccountWitness, ) -> Result { + let witness_id = account_witness_record + .witness_id + .ok_or(proto::responses::AccountWitness::missing_field(stringify!(witness_id)))? + .try_into()?; + let commitment = account_witness_record + .commitment + .ok_or(proto::responses::AccountWitness::missing_field(stringify!(commitment)))? + .try_into()?; + let path = account_witness_record + .path + .as_ref() + .ok_or(proto::responses::AccountWitness::missing_field(stringify!(path)))? + .try_into()?; + + let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| { + ConversionError::deserialization_error( + "AccountWitness", + DeserializationError::InvalidValue(err.to_string()), + ) + })?; + Ok(Self { account_id: account_witness_record .account_id .ok_or(proto::responses::AccountWitness::missing_field(stringify!(account_id)))? .try_into()?, - initial_state_commitment: account_witness_record - .initial_state_commitment - .ok_or(proto::responses::AccountWitness::missing_field(stringify!( - account_commitment - )))? - .try_into()?, - proof: account_witness_record - .proof - .as_ref() - .ok_or(proto::responses::AccountWitness::missing_field(stringify!(proof)))? - .try_into()?, + witness, }) } } diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 891243460..5f05efca2 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use miden_objects::{ - block::{AccountWitness, BlockHeader, BlockInputs, NullifierWitness}, + block::{BlockHeader, BlockInputs, NullifierWitness}, note::{NoteId, NoteInclusionProof}, transaction::ChainMmr, utils::{Deserializable, Serializable}, @@ -111,15 +111,7 @@ impl From for GetBlockInputsResponse { latest_block_header: Some(prev_block_header.into()), account_witnesses: account_witnesses .into_iter() - .map(|(id, witness)| { - let (initial_state_commitment, proof) = witness.into_parts(); - AccountWitnessRecord { - account_id: id, - initial_state_commitment, - proof, - } - .into() - }) + .map(|(id, witness)| AccountWitnessRecord { account_id: id, witness }.into()) .collect(), nullifier_witnesses: nullifier_witnesses .into_iter() @@ -151,13 +143,7 @@ impl TryFrom for BlockInputs { .into_iter() .map(|entry| { let witness_record: AccountWitnessRecord = entry.try_into()?; - Ok(( - witness_record.account_id, - AccountWitness::new( - witness_record.initial_state_commitment, - witness_record.proof, - ), - )) + Ok((witness_record.account_id, witness_record.witness)) }) .collect::, ConversionError>>()?; diff --git a/crates/proto/src/generated/responses.rs b/crates/proto/src/generated/responses.rs index b52b15707..bfd3baf09 100644 --- a/crates/proto/src/generated/responses.rs +++ b/crates/proto/src/generated/responses.rs @@ -81,19 +81,23 @@ pub struct SyncNoteResponse { #[prost(message, repeated, tag = "4")] pub notes: ::prost::alloc::vec::Vec, } -/// An account returned as a response to the `GetBlockInputs`. +/// An account witness returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountWitness { - /// The account ID. + /// Account ID for which this proof is requested. #[prost(message, optional, tag = "1")] pub account_id: ::core::option::Option, - /// The latest account state commitment used as the initial state of the requested block. - /// This will be the zero digest if the account doesn't exist. + /// The account ID within the proof, which may be different from the above account ID. + /// This can happen when the requested account ID's prefix matches the prefix of an existing + /// account ID in the tree. Then the witness will prove inclusion of this witness ID in the tree. #[prost(message, optional, tag = "2")] - pub initial_state_commitment: ::core::option::Option, - /// Merkle path to verify the account's inclusion in the account tree. + pub witness_id: ::core::option::Option, + /// The state commitment whose inclusion the witness proves. #[prost(message, optional, tag = "3")] - pub proof: ::core::option::Option, + pub commitment: ::core::option::Option, + /// The merkle path of the state commitment in the account tree. + #[prost(message, optional, tag = "4")] + pub path: ::core::option::Option, } /// A nullifier returned as a response to the `GetBlockInputs`. #[derive(Clone, PartialEq, ::prost::Message)] @@ -220,27 +224,21 @@ pub struct GetAccountStateDeltaResponse { /// Represents the result of getting account proofs. #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetAccountProofsResponse { - /// Block number at which the state of the account was returned. + /// Block number at which the state of the accounts is returned. #[prost(fixed32, tag = "1")] pub block_num: u32, /// List of account state infos for the requested account keys. #[prost(message, repeated, tag = "2")] pub account_proofs: ::prost::alloc::vec::Vec, } -/// A single account proof returned as a response to the `GetAccountProofs`. +/// A single account proof returned as a response to `GetAccountProofs`. #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountProofsResponse { - /// Account ID. + /// The account witness for the current state commitment of one account ID. #[prost(message, optional, tag = "1")] - pub account_id: ::core::option::Option, - /// Account commitment. - #[prost(message, optional, tag = "2")] - pub account_commitment: ::core::option::Option, - /// Authentication path from the `account_root` of the block header to the account. - #[prost(message, optional, tag = "3")] - pub account_proof: ::core::option::Option, + pub witness: ::core::option::Option, /// State header for public accounts. Filled only if `include_headers` flag is set to `true`. - #[prost(message, optional, tag = "4")] + #[prost(message, optional, tag = "2")] pub state_header: ::core::option::Option, } /// State header for public accounts. diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index ed9fb5bd2..e8dbaa3e8 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -2,7 +2,7 @@ use std::io; use deadpool::managed::PoolError; use miden_objects::{ - AccountDeltaError, AccountError, NoteError, + AccountDeltaError, AccountError, AccountTreeError, NoteError, account::AccountId, block::BlockNumber, crypto::{ @@ -108,7 +108,7 @@ pub enum StateInitializationError { #[error("failed to create nullifier tree")] FailedToCreateNullifierTree(#[from] NullifierTreeError), #[error("failed to create accounts tree")] - FailedToCreateAccountsTree(#[from] MerkleError), + FailedToCreateAccountsTree(#[source] AccountTreeError), } #[derive(Debug, Error)] @@ -134,8 +134,8 @@ pub enum GenesisError { // TODO: Check if needed. #[error("block error")] Block, - #[error("merkle error")] - Merkle(#[from] MerkleError), + #[error("failed to build genesis account tree")] + AccountTree(#[source] AccountTreeError), #[error("failed to deserialize genesis file")] GenesisFileDeserialization(#[from] DeserializationError), } diff --git a/crates/store/src/genesis.rs b/crates/store/src/genesis.rs index 7db1d0221..bbe719efa 100644 --- a/crates/store/src/genesis.rs +++ b/crates/store/src/genesis.rs @@ -1,9 +1,11 @@ use miden_lib::transaction::TransactionKernel; use miden_objects::{ - ACCOUNT_TREE_DEPTH, Digest, + Digest, account::{Account, delta::AccountUpdateDetails}, - block::{BlockAccountUpdate, BlockHeader, BlockNoteTree, BlockNumber, ProvenBlock}, - crypto::merkle::{MmrPeaks, SimpleSmt, Smt}, + block::{ + AccountTree, BlockAccountUpdate, BlockHeader, BlockNoteTree, BlockNumber, ProvenBlock, + }, + crypto::merkle::{MmrPeaks, Smt}, note::Nullifier, transaction::OrderedTransactionHeaders, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, @@ -57,10 +59,12 @@ impl GenesisState { }) .collect(); - let account_smt: SimpleSmt = - SimpleSmt::with_leaves(accounts.iter().map(|update| { - (update.account_id().prefix().into(), update.final_state_commitment().into()) - }))?; + let account_smt = AccountTree::with_entries( + accounts + .iter() + .map(|update| (update.account_id(), update.final_state_commitment())), + ) + .map_err(GenesisError::AccountTree)?; let empty_nullifiers: Vec = Vec::new(); let empty_nullifier_tree = Smt::new(); diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 6953ed5a1..56ac506a6 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -10,6 +10,7 @@ use std::{ }; use miden_node_proto::{ + AccountWitnessRecord, domain::{ account::{AccountInfo, AccountProofRequest, StorageMapKeysProof}, batch::BatchInputs, @@ -18,15 +19,15 @@ use miden_node_proto::{ }; use miden_node_utils::formatting::format_array; use miden_objects::{ - ACCOUNT_TREE_DEPTH, AccountError, + AccountError, account::{AccountDelta, AccountHeader, AccountId, StorageSlot}, - block::{AccountWitness, BlockHeader, BlockInputs, BlockNumber, NullifierWitness, ProvenBlock}, + block::{ + AccountTree, AccountWitness, BlockHeader, BlockInputs, BlockNumber, NullifierWitness, + ProvenBlock, + }, crypto::{ hash::rpo::RpoDigest, - merkle::{ - LeafIndex, Mmr, MmrDelta, MmrError, MmrPeaks, MmrProof, PartialMmr, SimpleSmt, - SmtProof, ValuePath, - }, + merkle::{Mmr, MmrDelta, MmrError, MmrPeaks, MmrProof, PartialMmr, SmtProof}, }, note::{NoteId, Nullifier}, transaction::{ChainMmr, OutputNote}, @@ -152,7 +153,7 @@ impl Blockchain { struct InnerState { nullifier_tree: NullifierTree, blockchain: Blockchain, - account_tree: SimpleSmt, + account_tree: AccountTree, } impl InnerState { @@ -234,9 +235,8 @@ impl State { let header = block.header(); - let tx_commitment = BlockHeader::compute_tx_commitment( - block.transactions().as_slice().iter().map(|tx| (tx.id(), tx.account_id())), - ); + let tx_commitment = block.transactions().commitment(); + if header.tx_commitment() != tx_commitment { return Err(InvalidBlockError::InvalidBlockTxCommitment { expected: tx_commitment, @@ -315,15 +315,13 @@ impl State { // compute update for account tree let account_tree_update = inner.account_tree.compute_mutations( - block.updated_accounts().iter().map(|update| { - ( - LeafIndex::new_max_depth(update.account_id().prefix().into()), - update.final_state_commitment().into(), - ) - }), + block + .updated_accounts() + .iter() + .map(|update| (update.account_id(), update.final_state_commitment())), ); - if account_tree_update.root() != header.account_root() { + if account_tree_update.as_mutation_set().root() != header.account_root() { return Err(InvalidBlockError::NewBlockInvalidAccountRoot.into()); } @@ -791,13 +789,7 @@ impl State { let account_witnesses = account_ids .iter() .copied() - .map(|account_id| { - let ValuePath { - value: latest_state_commitment, - path: proof, - } = inner.account_tree.open(&account_id.into()); - (account_id, AccountWitness::new(latest_state_commitment, proof)) - }) + .map(|account_id| (account_id, inner.account_tree.open(account_id))) .collect::>(); // Fetch witnesses for all nullifiers. We don't check whether the nullifiers are spent or @@ -826,10 +818,7 @@ impl State { let inner = self.inner.read().await; - let account_commitment = inner - .account_tree - .open(&LeafIndex::new_max_depth(account_id.prefix().into())) - .value; + let account_commitment = inner.account_tree.get(account_id); let nullifiers = nullifiers .iter() @@ -935,14 +924,13 @@ impl State { let responses = account_ids .into_iter() .map(|account_id| { - let acc_leaf_idx = LeafIndex::new_max_depth(account_id.prefix().into()); - let opening = inner_state.account_tree.open(&acc_leaf_idx); + let witness = inner_state.account_tree.open(account_id); let state_header = state_headers.get(&account_id).cloned(); + let witness_record = AccountWitnessRecord { account_id, witness }; + AccountProofsResponse { - account_id: Some(account_id.into()), - account_commitment: Some(opening.value.into()), - account_proof: Some(opening.path.into()), + witness: Some(witness_record.into()), state_header, } }) @@ -1027,16 +1015,9 @@ async fn load_mmr(db: &mut Db) -> Result { } #[instrument(target = COMPONENT, skip_all)] -async fn load_accounts( - db: &mut Db, -) -> Result, StateInitializationError> { - let account_data: Vec<_> = db - .select_all_account_commitments() - .await? - .into_iter() - .map(|(id, account_commitment)| (id.prefix().into(), account_commitment.into())) - .collect(); +async fn load_accounts(db: &mut Db) -> Result { + let account_data = db.select_all_account_commitments().await?.into_iter().collect::>(); - SimpleSmt::with_leaves(account_data) + AccountTree::with_entries(account_data) .map_err(StateInitializationError::FailedToCreateAccountsTree) } diff --git a/proto/proto/responses.proto b/proto/proto/responses.proto index 0783f8636..8c68ce0e5 100644 --- a/proto/proto/responses.proto +++ b/proto/proto/responses.proto @@ -86,17 +86,21 @@ message SyncNoteResponse { repeated note.NoteSyncRecord notes = 4; } -// An account returned as a response to the `GetBlockInputs`. +// An account witness returned as a response to the `GetBlockInputs`. message AccountWitness { - // The account ID. + // Account ID for which this proof is requested. account.AccountId account_id = 1; - // The latest account state commitment used as the initial state of the requested block. - // This will be the zero digest if the account doesn't exist. - digest.Digest initial_state_commitment = 2; + // The account ID within the proof, which may be different from the above account ID. + // This can happen when the requested account ID's prefix matches the prefix of an existing + // account ID in the tree. Then the witness will prove inclusion of this witness ID in the tree. + account.AccountId witness_id = 2; + + // The state commitment whose inclusion the witness proves. + digest.Digest commitment = 3; - // Merkle path to verify the account's inclusion in the account tree. - merkle.MerklePath proof = 3; + // The merkle path of the state commitment in the account tree. + merkle.MerklePath path = 4; } // A nullifier returned as a response to the `GetBlockInputs`. @@ -210,22 +214,20 @@ message GetAccountStateDeltaResponse { // Represents the result of getting account proofs. message GetAccountProofsResponse { - // Block number at which the state of the account was returned. + // Block number at which the state of the accounts is returned. fixed32 block_num = 1; + // List of account state infos for the requested account keys. repeated AccountProofsResponse account_proofs = 2; } -// A single account proof returned as a response to the `GetAccountProofs`. +// A single account proof returned as a response to `GetAccountProofs`. message AccountProofsResponse { - // Account ID. - account.AccountId account_id = 1; - // Account commitment. - digest.Digest account_commitment = 2; - // Authentication path from the `account_root` of the block header to the account. - merkle.MerklePath account_proof = 3; + // The account witness for the current state commitment of one account ID. + AccountWitness witness = 1; + // State header for public accounts. Filled only if `include_headers` flag is set to `true`. - optional AccountStateHeader state_header = 4; + optional AccountStateHeader state_header = 2; } // State header for public accounts.