Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
51fecb2
feat: Only iterate if num leaves is different in account tree
PhilippGackstatter Apr 24, 2025
862eda2
chore: Add changelog entry
PhilippGackstatter Apr 24, 2025
7fdff8a
chore: Remove period from duplicate error message
PhilippGackstatter Apr 24, 2025
fe79ffe
Merge remote-tracking branch 'origin/next' into pgackst-account-tree-…
PhilippGackstatter Apr 25, 2025
29ce0ac
feat: Guarantee account ID prefix uniqueness in mutation set
PhilippGackstatter Apr 25, 2025
45d93f0
chore: Fix changelog
PhilippGackstatter Apr 25, 2025
8a8433d
feat: Add `NullifierTree` with tests
PhilippGackstatter Apr 25, 2025
3916599
feat: Add `BlockChain`
PhilippGackstatter Apr 25, 2025
66730b0
feat: Use `BlockChain` in `MockChain`
PhilippGackstatter Apr 25, 2025
6e74d3c
feat: Use `NullifierTree` in `MockChain`
PhilippGackstatter Apr 25, 2025
950d82c
chore: Add changelog entry
PhilippGackstatter Apr 25, 2025
ee04688
fix: changelog entry
PhilippGackstatter Apr 25, 2025
04e084e
feat: Rename `ChainMmr` -> `PartialBlockChain`
PhilippGackstatter Apr 25, 2025
543b4d5
chore: Add changelog entry
PhilippGackstatter Apr 25, 2025
732d98d
fix: search replace rename
PhilippGackstatter Apr 25, 2025
cb3337e
Merge branch 'next' into pgackst-nullifier-tree-blockchain
bobbinth May 3, 2025
e315e93
chore: Address review comments
PhilippGackstatter May 5, 2025
ff94435
chore: Add tests separator
PhilippGackstatter May 5, 2025
bf4988f
Merge branch 'pgackst-nullifier-tree-blockchain' into pgackst-partial…
PhilippGackstatter May 5, 2025
dc4d275
chore: Rename `PartialBlockChain` -> `PartialBlockchain`
PhilippGackstatter May 5, 2025
9ac9a6b
chore: Follow-up rename for block_chain -> blockchain
PhilippGackstatter May 5, 2025
3c345ac
chore: Rename blockchain hash to commitment
PhilippGackstatter May 5, 2025
1b75b1c
Merge remote-tracking branch 'origin/next' into pgackst-partial-block…
PhilippGackstatter May 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
- [BREAKING] Refactored how foreign account inputs are passed to `TransactionExecutor`, and upgraded Rust version to 1.86 (#1229).
- [BREAKING] Add `TransactionHeader` and include it in batches and blocks (#1247).
- [BREAKING] Hash keys in storage maps before insertion into the SMT (#1250).
- Add `AccountTree` and `PartialAccountTree` wrappers and enforce ID prefix uniqueness (#1254).
- Add `AccountTree` and `PartialAccountTree` wrappers and enforce ID prefix uniqueness (#1254, #1301).
- Added getter for proof security level in `ProvenBatch` and `ProvenBlock` (#1259).
- [BREAKING] Replaced the `ProvenBatch::new_unchecked` with the `ProvenBatch::new` method to initialize the struct with validations (#1260).
- Added pretty print for `AccountCode` (#1273).
- [BREAKING] Add `NetworkAccount` configuration (#1275).
- Added support for environment variables to set up the `miden-proving-service` worker (#1281).
- Move `NullifierTree` and `BlockChain` from node to base (#1304).
- Rename `ChainMmr` to `PartialBlockChain` (#1305).

## 0.8.2 (2025-04-18) - `miden-proving-service` crate only

Expand Down
21 changes: 12 additions & 9 deletions crates/miden-block-prover/src/local_block_prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use miden_objects::{
ProposedBlock, ProvenBlock,
},
note::Nullifier,
transaction::ChainMmr,
transaction::PartialBlockChain,
};

use crate::errors::ProvenBlockError;
Expand Down Expand Up @@ -86,7 +86,7 @@ impl LocalBlockProver {
account_updated_witnesses,
output_note_batches,
created_nullifiers,
chain_mmr,
partial_block_chain,
prev_block_header,
) = proposed_block.into_parts();

Expand All @@ -111,11 +111,11 @@ impl LocalBlockProver {
let new_account_root =
compute_account_root(&account_updated_witnesses, &prev_block_header)?;

// Insert the previous block header into the block chain MMR to get the new chain
// Insert the previous block header into the block partial blockchain to get the new chain
// commitment.
// --------------------------------------------------------------------------------------------

let new_chain_commitment = compute_chain_commitment(chain_mmr, prev_block_header);
let new_chain_commitment = compute_chain_commitment(partial_block_chain, prev_block_header);

// Transform the account update witnesses into block account updates.
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -229,13 +229,16 @@ fn compute_nullifiers(
Ok((nullifiers, partial_nullifier_tree.root()))
}

/// Adds the commitment of the previous block header to the chain MMR to compute the new chain
/// commitment.
fn compute_chain_commitment(mut chain_mmr: ChainMmr, prev_block_header: BlockHeader) -> Digest {
/// Adds the commitment of the previous block header to the partial blockchain to compute the new
/// chain commitment.
fn compute_chain_commitment(
mut partial_block_chain: PartialBlockChain,
prev_block_header: BlockHeader,
) -> Digest {
// SAFETY: This does not panic as long as the block header we're adding is the next one in the
// chain which is validated as part of constructing a `ProposedBlock`.
chain_mmr.add_block(prev_block_header, true);
chain_mmr.peaks().hash_peaks()
partial_block_chain.add_block(prev_block_header, true);
partial_block_chain.peaks().hash_peaks()
}

/// Computes the new account tree root after the given updates.
Expand Down
59 changes: 31 additions & 28 deletions crates/miden-block-prover/src/tests/proposed_block_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use assert_matches::assert_matches;
use miden_objects::{
MAX_BATCHES_PER_BLOCK, ProposedBlockError,
account::AccountId,
block::{BlockInputs, BlockNumber, NullifierWitness, ProposedBlock},
block::{BlockInputs, BlockNumber, ProposedBlock},
note::NoteInclusionProof,
testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
transaction::ProvenTransaction,
Expand Down Expand Up @@ -40,7 +40,7 @@ fn proposed_block_fails_on_too_many_batches() -> anyhow::Result<()> {

let block_inputs = BlockInputs::new(
chain.latest_block_header(),
chain.latest_chain_mmr(),
chain.latest_partial_block_chain(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand All @@ -64,7 +64,7 @@ fn proposed_block_fails_on_duplicate_batches() -> anyhow::Result<()> {

let block_inputs = BlockInputs::new(
chain.latest_block_header(),
chain.latest_chain_mmr(),
chain.latest_partial_block_chain(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand Down Expand Up @@ -124,7 +124,7 @@ fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::R
// Mock BlockInputs.
let block_inputs = BlockInputs::new(
chain.latest_block_header(),
chain.latest_chain_mmr(),
chain.latest_partial_block_chain(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand All @@ -143,22 +143,24 @@ fn proposed_block_fails_on_timestamp_not_increasing_monotonically() -> anyhow::R
Ok(())
}

/// Tests that a chain MMR that is not at the state of the previous block header produces an error.
/// Tests that a partial blockchain that is not at the state of the previous block header produces
/// an error.
#[test]
fn proposed_block_fails_on_chain_mmr_and_prev_block_inconsistency() -> anyhow::Result<()> {
fn proposed_block_fails_on_partial_block_chain_and_prev_block_inconsistency() -> anyhow::Result<()>
{
let TestSetup { mut chain, mut txs, .. } = setup_chain(1);
let proven_tx0 = txs.remove(&0).unwrap();
let batch0 = generate_batch(&mut chain, vec![proven_tx0]);
let batches = vec![batch0];

// Select the chain MMR which is valid for the current block but pass the next block in the
// chain, which is an inconsistent combination.
let mut chain_mmr = chain.latest_chain_mmr();
// Select the partial blockchain which is valid for the current block but pass the next block in
// the chain, which is an inconsistent combination.
let mut partial_block_chain = chain.latest_partial_block_chain();
let block2 = chain.clone().seal_next_block();

let block_inputs = BlockInputs::new(
block2.header().clone(),
chain_mmr.clone(),
partial_block_chain.clone(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand All @@ -170,17 +172,19 @@ fn proposed_block_fails_on_chain_mmr_and_prev_block_inconsistency() -> anyhow::R
ProposedBlockError::ChainLengthNotEqualToPreviousBlockNumber {
chain_length,
prev_block_num
} if chain_length == chain_mmr.chain_length() &&
} if chain_length == partial_block_chain.chain_length() &&
prev_block_num == block2.header().block_num()
);

// Add an invalid value making the chain length equal to block2's number, but resulting in a
// different chain commitment.
chain_mmr.partial_mmr_mut().add(block2.header().nullifier_root(), true);
partial_block_chain
.partial_mmr_mut()
.add(block2.header().nullifier_root(), true);

let block_inputs = BlockInputs::new(
block2.header().clone(),
chain_mmr.clone(),
partial_block_chain.clone(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand All @@ -195,8 +199,8 @@ fn proposed_block_fails_on_chain_mmr_and_prev_block_inconsistency() -> anyhow::R
Ok(())
}

/// Tests that a chain MMR that does not contain all reference blocks of the batches produces an
/// error.
/// Tests that a partial blockchain that does not contain all reference blocks of the batches
/// produces an error.
#[test]
fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()> {
let TestSetup { mut chain, mut txs, .. } = setup_chain(1);
Expand All @@ -208,13 +212,14 @@ fn proposed_block_fails_on_missing_batch_reference_block() -> anyhow::Result<()>

let block2 = chain.seal_next_block();

let (_, chain_mmr) = chain.latest_selective_chain_mmr([BlockNumber::from(0)]);
let (_, partial_block_chain) =
chain.latest_selective_partial_block_chain([BlockNumber::from(0)]);

// The proposed block references block 2 but the chain MMR only contains block 0 but not
// block 1 which is referenced by the batch.
// The proposed block references block 2 but the partial blockchain only contains block 0 but
// not block 1 which is referenced by the batch.
let block_inputs = BlockInputs::new(
block2.header().clone(),
chain_mmr.clone(),
partial_block_chain.clone(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand Down Expand Up @@ -307,7 +312,7 @@ fn proposed_block_fails_on_duplicate_output_note() -> anyhow::Result<()> {

/// Tests that a missing note inclusion proof produces an error.
/// Also tests that an error is produced if the block that the note inclusion proof references is
/// not in the chain MMR.
/// not in the partial blockchain.
#[test]
fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_block()
-> anyhow::Result<()> {
Expand Down Expand Up @@ -337,22 +342,22 @@ fn proposed_block_fails_on_invalid_proof_or_missing_note_inclusion_reference_blo

let original_block_inputs = chain.get_block_inputs(&batches);

// Error: Block referenced by note inclusion proof is not in chain MMR.
// Error: Block referenced by note inclusion proof is not in partial blockchain.
// --------------------------------------------------------------------------------------------

let mut invalid_block_inputs = original_block_inputs.clone();
invalid_block_inputs
.chain_mmr_mut()
.partial_block_chain_mut()
.partial_mmr_mut()
.untrack(block2.header().block_num().as_usize());
invalid_block_inputs
.chain_mmr_mut()
.partial_block_chain_mut()
.block_headers_mut()
.remove(&block2.header().block_num())
.expect("block2 should have been fetched");

let error = ProposedBlock::new(invalid_block_inputs, batches.clone()).unwrap_err();
assert_matches!(error, ProposedBlockError::UnauthenticatedInputNoteBlockNotInChainMmr { block_number, note_id } if block_number == block2.header().block_num() && note_id == note0.id());
assert_matches!(error, ProposedBlockError::UnauthenticatedInputNoteBlockNotInPartialBlockChain { block_number, note_id } if block_number == block2.header().block_num() && note_id == note0.id());

// Error: Invalid note inclusion proof.
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -476,16 +481,14 @@ fn proposed_block_fails_on_spent_nullifier_witness() -> anyhow::Result<()> {
);
alternative_chain.apply_executed_transaction(&transaction);
alternative_chain.seal_next_block();
let spent_proof = alternative_chain.nullifiers().open(&note0.nullifier().inner());
let spent_proof = alternative_chain.nullifiers().open(&note0.nullifier());

let batches = vec![batch0.clone()];
let mut block_inputs = chain.get_block_inputs(&batches);

// Insert the spent nullifier proof from the alternative chain into the block inputs from the
// actual chain.
block_inputs
.nullifier_witnesses_mut()
.insert(note0.nullifier(), NullifierWitness::new(spent_proof));
block_inputs.nullifier_witnesses_mut().insert(note0.nullifier(), spent_proof);

let error = ProposedBlock::new(block_inputs, batches).unwrap_err();
assert_matches!(error, ProposedBlockError::NullifierSpent(nullifier) if nullifier == note0.nullifier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn proposed_block_succeeds_with_empty_batches() -> anyhow::Result<()> {

let block_inputs = BlockInputs::new(
chain.latest_block_header(),
chain.latest_chain_mmr(),
chain.latest_partial_block_chain(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand Down
17 changes: 8 additions & 9 deletions crates/miden-block-prover/src/tests/proven_block_success.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, vec::Vec};
use anyhow::Context;
use miden_crypto::merkle::Smt;
use miden_objects::{
Felt, FieldElement, MIN_PROOF_SECURITY_LEVEL,
MIN_PROOF_SECURITY_LEVEL,
batch::BatchNoteTree,
block::{AccountTree, BlockInputs, BlockNoteIndex, BlockNoteTree, ProposedBlock},
transaction::InputNoteCommitment,
Expand Down Expand Up @@ -98,10 +98,9 @@ fn proven_block_success() -> anyhow::Result<()> {

let mut expected_nullifier_tree = chain.nullifiers().clone();
for nullifier in proposed_block.created_nullifiers().keys() {
expected_nullifier_tree.insert(
nullifier.inner(),
[Felt::from(proposed_block.block_num()), Felt::ZERO, Felt::ZERO, Felt::ZERO],
);
expected_nullifier_tree
.mark_spent(*nullifier, proposed_block.block_num())
.context("failed to mark nullifier as spent")?;
}

// Compute expected account root on the full account tree.
Expand Down Expand Up @@ -130,7 +129,7 @@ fn proven_block_success() -> anyhow::Result<()> {
// The Mmr in MockChain adds a new block after it is sealed, so at this point the chain contains
// block2 and has length 3.
// This means the chain commitment of the mock chain must match the chain commitment of the
// ChainMmr with chain length 2 when the prev block (block2) is added.
// PartialBlockChain with chain length 2 when the prev block (block2) is added.
assert_eq!(
proven_block.header().chain_commitment(),
chain.block_chain().peaks().hash_peaks()
Expand Down Expand Up @@ -370,12 +369,12 @@ fn proven_block_succeeds_with_empty_batches() -> anyhow::Result<()> {
assert_ne!(latest_block_header.account_root(), AccountTree::new().root());
assert_ne!(latest_block_header.nullifier_root(), Smt::new().root());

let (_, empty_chain_mmr) = chain.latest_selective_chain_mmr([]);
assert_eq!(empty_chain_mmr.block_headers().count(), 0);
let (_, empty_partial_block_chain) = chain.latest_selective_partial_block_chain([]);
assert_eq!(empty_partial_block_chain.block_headers().count(), 0);

let block_inputs = BlockInputs::new(
latest_block_header.clone(),
empty_chain_mmr.clone(),
empty_partial_block_chain.clone(),
BTreeMap::default(),
BTreeMap::default(),
BTreeMap::default(),
Expand Down
8 changes: 4 additions & 4 deletions crates/miden-lib/asm/kernels/transaction/lib/account.masm
Original file line number Diff line number Diff line change
Expand Up @@ -750,15 +750,15 @@ end
#!
#! Validation is performed via the following steps:
#! 1. Retrieve the anchor block commitment by computing the block number of the anchor block and
#! retrieving it from the chain mmr.
#! retrieving it from the partial blockchain.
#! 2. Compute the hash of (SEED, CODE_COMMITMENT, STORAGE_COMMITMENT, ANCHOR_BLOCK_COMMITMENT).
#! 3. Assert the two least significant elements of the digest are equal to the account ID of the
#! account the transaction is being executed against.
#!
#! Inputs: []
#! Outputs: []
export.validate_seed
# Load the block commitment of the anchor block from the chain mmr.
# Load the block commitment of the anchor block from the partial blockchain.
# This is the block commitment to which the account ID is anchored and is derived from.
# ---------------------------------------------------------------------------------------------

Expand All @@ -770,8 +770,8 @@ export.validate_seed
dup.3 exec.account_id::id_anchor_block_num
# => [anchor_block_num, 0, 0, account_id_prefix, account_id_suffix]

exec.memory::get_chain_mmr_ptr swap
# => [anchor_block_num, chain_mmr_ptr, 0, 0, account_id_prefix, account_id_suffix]
exec.memory::get_partial_block_chain_ptr swap
# => [anchor_block_num, partial_block_chain_ptr, 0, 0, account_id_prefix, account_id_suffix]

exec.mmr::get
# => [ANCHOR_BLOCK_COMMITMENT, 0, 0, account_id_prefix, account_id_suffix]
Expand Down
Loading
Loading