#[cfg(test)]
#[expect(clippy::needless_range_loop)]
#[expect(clippy::uninlined_format_args)]
#[expect(clippy::cast_sign_loss)]
mod account_tree_with_history_tests {
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::block::account_tree::{AccountTree, account_id_to_smt_key};
use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage};
use miden_protocol::testing::account_id::AccountIdBuilder;
use super::super::*;
fn create_account_tree(
entries: impl IntoIterator<Item = (AccountId, Word)>,
) -> InMemoryAccountTree {
let smt_entries = entries
.into_iter()
.map(|(id, commitment)| (account_id_to_smt_key(id), commitment));
let smt = LargeSmt::with_entries(MemoryStorage::default(), smt_entries)
.expect("Failed to create LargeSmt from entries");
AccountTree::new(smt).expect("Failed to create AccountTree")
}
fn assert_verify(root: Word, witness: AccountWitness) {
let proof = witness.into_proof();
let (path, leaf) = proof.into_parts();
path.verify(leaf.index().value(), leaf.hash(), &root).unwrap();
}
#[test]
fn test_historical_queries() {
let ids = [
AccountIdBuilder::new().build_with_seed([1; 32]),
AccountIdBuilder::new().build_with_seed([2; 32]),
AccountIdBuilder::new().build_with_seed([3; 32]),
];
let states = [
vec![
(ids[0], Word::from([1u32, 0, 0, 0])),
(ids[1], Word::from([2u32, 0, 0, 0])),
(ids[2], Word::from([3u32, 0, 0, 0])),
],
vec![
(ids[0], Word::from([10u32, 0, 0, 0])),
(ids[1], Word::from([2u32, 0, 0, 0])),
(ids[2], Word::from([30u32, 0, 0, 0])),
],
vec![
(ids[0], Word::from([10u32, 0, 0, 0])),
(ids[1], Word::from([200u32, 0, 0, 0])),
(ids[2], Word::from([30u32, 0, 0, 0])),
],
];
let trees: Vec<_> = states.iter().map(|s| create_account_tree(s.clone())).collect();
let hist_tree = create_account_tree(states[0].clone());
let mut hist = AccountTreeWithHistory::new(hist_tree, BlockNumber::GENESIS);
hist.compute_and_apply_mutations([(ids[0], states[1][0].1), (ids[2], states[1][2].1)])
.unwrap();
hist.compute_and_apply_mutations([(ids[1], states[2][1].1)]).unwrap();
for (block, tree) in trees.iter().enumerate() {
let hist_root = hist.root_at(BlockNumber::from(block as u32)).unwrap();
assert_eq!(hist_root, tree.root());
for &id in &ids {
let hist_witness = hist.open_at(id, BlockNumber::from(block as u32)).unwrap();
let actual = tree.open(id);
assert_eq!(hist_witness.state_commitment(), actual.state_commitment());
assert_eq!(hist_witness.path(), actual.path());
}
}
}
#[test]
fn test_history_limits() {
const MAX_HIST: u32 = AccountTreeWithHistory::<MemoryStorage>::MAX_HISTORY as u32;
use assert_matches::assert_matches;
let id = AccountIdBuilder::new().build_with_seed([30; 32]);
let tree = create_account_tree([(id, Word::from([1u32, 0, 0, 0]))]);
let mut hist = AccountTreeWithHistory::new(tree, BlockNumber::GENESIS);
for i in 1..=(MAX_HIST + 5) {
hist.compute_and_apply_mutations([(id, Word::from([dbg!(i) as u32, 0, 0, 0]))])
.unwrap();
assert_eq!(hist.block_number_latest(), BlockNumber::from(i));
}
let current = hist.block_number_latest();
assert_matches!(hist.open_at(id, current), Some(_));
assert_matches!(hist.open_at(id, BlockNumber::GENESIS), None);
assert_matches!(hist.open_at(id, current.child()), None);
}
#[test]
fn test_account_lifecycle() {
let id0 = AccountIdBuilder::new().build_with_seed([40; 32]);
let id1 = AccountIdBuilder::new().build_with_seed([41; 32]);
let v0 = Word::from([0u32; 4]);
let v1 = Word::from([1u32; 4]);
let tree0 = create_account_tree(vec![(id0, v0)]);
let tree1 = create_account_tree(vec![(id0, v0), (id1, v1)]);
let tree2 = create_account_tree(vec![(id0, Word::default()), (id1, v1)]);
let hist_tree = create_account_tree(vec![(id0, v0)]);
let mut hist = AccountTreeWithHistory::new(hist_tree, BlockNumber::GENESIS);
hist.compute_and_apply_mutations([(id1, v1)]).unwrap();
hist.compute_and_apply_mutations([(id0, Word::default())]).unwrap();
assert_eq!(hist.block_number_latest(), BlockNumber::from(2));
assert_verify(tree2.root(), tree2.open(id0));
assert_verify(tree2.root(), hist.open_at(id0, BlockNumber::from(2)).unwrap());
assert_verify(tree1.root(), tree1.open(id0));
assert_verify(tree1.root(), hist.open_at(id0, BlockNumber::from(1)).unwrap());
assert_verify(tree0.root(), tree0.open(id0));
assert_verify(tree0.root(), hist.open_at(id0, BlockNumber::GENESIS).unwrap());
assert_eq!(hist.open_at(id0, BlockNumber::GENESIS).unwrap(), tree0.open(id0));
assert_eq!(hist.open_at(id0, BlockNumber::from(1)).unwrap(), tree1.open(id0));
assert_eq!(hist.open_at(id0, BlockNumber::from(2)).unwrap(), tree2.open(id0));
assert_eq!(hist.open_at(id1, BlockNumber::GENESIS).unwrap(), tree0.open(id1));
assert_eq!(hist.open_at(id1, BlockNumber::from(1)).unwrap(), tree1.open(id1));
assert_eq!(hist.open_at(id1, BlockNumber::from(2)).unwrap(), tree2.open(id1));
assert_eq!(hist.open_at(id0, BlockNumber::GENESIS).unwrap().state_commitment(), v0);
assert_eq!(hist.open_at(id0, BlockNumber::from(1)).unwrap().state_commitment(), v0);
assert_eq!(hist.open_at(id1, BlockNumber::from(1)).unwrap().state_commitment(), v1);
assert_eq!(
hist.open_at(id0, BlockNumber::from(2)).unwrap().state_commitment(),
Word::default()
);
assert_eq!(hist.open_at(id1, BlockNumber::from(2)).unwrap().state_commitment(), v1);
}
#[test]
fn test_many_accounts_sequential_updates() {
let account_count = 50;
let account_ids: Vec<_> = (0..account_count)
.map(|i| AccountIdBuilder::new().build_with_seed([i as u8; 32]))
.collect();
let initial_state: Vec<_> = account_ids
.iter()
.enumerate()
.map(|(i, &id)| (id, Word::from([i as u32, 0, 0, 0])))
.collect();
let initial_tree = create_account_tree(initial_state.clone());
let mut hist = AccountTreeWithHistory::new(initial_tree, BlockNumber::GENESIS);
let num_blocks = 5;
for block in 1..=num_blocks {
let updates: Vec<_> = (0..5)
.map(|i| {
let idx = ((block - 1) * 5 + i) % account_count;
let new_value = Word::from([idx as u32 + block as u32 * 100, 0, 0, 0]);
(account_ids[idx], new_value)
})
.collect();
hist.compute_and_apply_mutations(updates).unwrap();
}
assert_eq!(hist.block_number_latest(), BlockNumber::from(num_blocks as u32));
for i in 0..4 {
let witness = hist.open_at(account_ids[i], BlockNumber::GENESIS).unwrap();
assert_eq!(
witness.state_commitment(),
Word::from([i as u32, 0, 0, 0]),
"Account {} at genesis",
i
);
}
for block in 1..=num_blocks {
for i in 0..5 {
let idx = ((block - 1) * 5 + i) % account_count;
let witness =
hist.open_at(account_ids[idx], BlockNumber::from(block as u32)).unwrap();
let expected = Word::from([idx as u32 + block as u32 * 100, 0, 0, 0]);
assert_eq!(
witness.state_commitment(),
expected,
"Account {} at block {}",
idx,
block
);
}
}
}
#[test]
fn test_many_accounts_concurrent_updates() {
let account_count = 100;
let ids: Vec<_> = (0..account_count)
.map(|i| {
AccountIdBuilder::new().build_with_seed([
i as u8,
(i >> 8) as u8,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
])
})
.collect();
let initial_state: Vec<_> = ids
.iter()
.enumerate()
.map(|(i, &id)| (id, Word::from([i as u32, 0, 0, 0])))
.collect();
let initial_tree = create_account_tree(initial_state.clone());
let mut hist = AccountTreeWithHistory::new(initial_tree, BlockNumber::GENESIS);
let num_blocks = 5;
for block in 1..=num_blocks {
let updates: Vec<_> = ids
.iter()
.enumerate()
.map(|(i, &id)| {
let new_value = Word::from([i as u32, block as u32, 0, 0]);
(id, new_value)
})
.collect();
hist.compute_and_apply_mutations(updates).unwrap();
}
for block in 0..=num_blocks {
for (i, &id) in ids.iter().enumerate().take(20) {
let witness = hist.open_at(id, BlockNumber::from(block as u32)).unwrap();
let expected = if block == 0 {
Word::from([i as u32, 0, 0, 0])
} else {
Word::from([i as u32, block as u32, 0, 0])
};
assert_eq!(
witness.state_commitment(),
expected,
"Account {} at block {}",
i,
block
);
}
}
}
#[test]
fn test_sparse_updates_many_accounts() {
let account_count = 200;
let account_ids: Vec<_> = (0..account_count)
.map(|i| {
let mut seed = [0u8; 32];
seed[0] = i as u8;
seed[1] = (i >> 8) as u8;
AccountIdBuilder::new().build_with_seed(seed)
})
.collect();
let initial_state: Vec<_> = account_ids
.iter()
.take(50)
.enumerate()
.map(|(i, &id)| (id, Word::from([i as u32, 0, 0, 0])))
.collect();
let initial_tree = create_account_tree(initial_state.clone());
let mut hist = AccountTreeWithHistory::new(initial_tree, BlockNumber::GENESIS);
let updates1: Vec<_> = account_ids
.iter()
.skip(50)
.take(50)
.enumerate()
.map(|(i, &id)| (id, Word::from([(i + 50) as u32, 1, 0, 0])))
.collect();
hist.compute_and_apply_mutations(updates1).unwrap();
let updates2: Vec<_> = account_ids
.iter()
.enumerate()
.filter(|(i, _)| i % 10 == 0)
.take(10)
.map(|(i, &id)| (id, Word::from([i as u32, 2, 0, 0])))
.collect();
hist.compute_and_apply_mutations(updates2).unwrap();
let updates3: Vec<_> = account_ids
.iter()
.skip(100)
.enumerate()
.map(|(i, &id)| (id, Word::from([(i + 100) as u32, 3, 0, 0])))
.collect();
hist.compute_and_apply_mutations(updates3).unwrap();
for i in 0..50 {
let witness = hist.open_at(account_ids[i], BlockNumber::GENESIS).unwrap();
assert_eq!(witness.state_commitment(), Word::from([i as u32, 0, 0, 0]));
}
for i in 50..100 {
let witness = hist.open_at(account_ids[i], BlockNumber::from(1)).unwrap();
assert_eq!(witness.state_commitment(), Word::from([i as u32, 1, 0, 0]));
}
for i in 0..10 {
let idx = i * 10;
if idx < 100 {
let witness = hist.open_at(account_ids[idx], BlockNumber::from(2)).unwrap();
assert_eq!(witness.state_commitment(), Word::from([idx as u32, 2, 0, 0]));
}
}
for i in [0, 50, 100, 150, 199] {
let witness = hist.open_at(account_ids[i], BlockNumber::from(3));
assert!(witness.is_some(), "Account {} should exist at block 3", i);
}
}
#[test]
fn test_account_deletion_and_recreation() {
let ids: Vec<_> = (0..20)
.map(|i| AccountIdBuilder::new().build_with_seed([i as u8 + 100; 32]))
.collect();
let initial_state: Vec<_> = ids
.iter()
.enumerate()
.map(|(i, &id)| (id, Word::from([i as u32 + 1, 0, 0, 0])))
.collect();
let initial_tree = create_account_tree(initial_state.clone());
let mut hist = AccountTreeWithHistory::new(initial_tree, BlockNumber::GENESIS);
let deletes: Vec<_> = ids.iter().take(10).map(|&id| (id, Word::default())).collect();
hist.compute_and_apply_mutations(deletes).unwrap();
let recreates: Vec<_> =
ids.iter().take(5).map(|&id| (id, Word::from([999u32, 0, 0, 0]))).collect();
hist.compute_and_apply_mutations(recreates).unwrap();
for (i, &id) in ids.iter().enumerate().take(20) {
let witness = hist.open_at(id, BlockNumber::GENESIS).unwrap();
assert_eq!(
witness.state_commitment(),
Word::from([i as u32 + 1, 0, 0, 0]),
"Genesis state for account {}",
i
);
}
for i in 0..10 {
let witness = hist.open_at(ids[i], BlockNumber::from(1)).unwrap();
assert_eq!(
witness.state_commitment(),
Word::default(),
"Account {} should be deleted at block 1",
i
);
}
for i in 0..5 {
let witness = hist.open_at(ids[i], BlockNumber::from(2)).unwrap();
assert_eq!(
witness.state_commitment(),
Word::from([999u32, 0, 0, 0]),
"Account {} should be recreated at block 2",
i
);
}
for i in 5..10 {
let witness = hist.open_at(ids[i], BlockNumber::from(2)).unwrap();
assert_eq!(
witness.state_commitment(),
Word::default(),
"Account {} should still be empty at block 2",
i
);
}
for i in 10..20 {
let witness_b1 = hist.open_at(ids[i], BlockNumber::from(1)).unwrap();
let witness_b2 = hist.open_at(ids[i], BlockNumber::from(2)).unwrap();
assert_eq!(witness_b1.state_commitment(), Word::from([i as u32 + 1, 0, 0, 0]));
assert_eq!(witness_b2.state_commitment(), Word::from([i as u32 + 1, 0, 0, 0]));
}
}
#[test]
fn test_account_tree_with_history_minimal_verify() {
let account_id = AccountIdBuilder::new().build_with_seed([42; 32]);
let genesis_commitment = Word::from([100u32, 200, 300, 400]);
let updated_commitment = Word::from([999u32, 888, 777, 666]);
let tree = create_account_tree(vec![(account_id, genesis_commitment)]);
let mut hist = AccountTreeWithHistory::new(tree, BlockNumber::GENESIS);
hist.compute_and_apply_mutations(vec![(account_id, updated_commitment)])
.expect("Mutation should succeed");
assert_eq!(hist.block_number_latest(), BlockNumber::from(1));
let witness = hist
.open_at(account_id, BlockNumber::GENESIS)
.expect("Account should exist at genesis");
assert_eq!(witness.state_commitment(), genesis_commitment);
let root = hist.root_at(BlockNumber::GENESIS).expect("Root should exist at genesis");
let proof = witness.into_proof();
let (path, leaf) = proof.into_parts();
path.verify(leaf.index().value(), leaf.hash(), &root)
.expect("Proof verification should succeed");
}
#[test]
fn test_account_tree_minimal_verify() {
let account_id = AccountIdBuilder::new().build_with_seed([42; 32]);
let state_commitment = Word::from([100u32, 200, 300, 400]);
let tree = create_account_tree(vec![(account_id, state_commitment)]);
let witness = tree.open(account_id);
assert_eq!(witness.state_commitment(), state_commitment);
let root = tree.root();
let proof = witness.into_proof();
let (path, leaf) = proof.into_parts();
path.verify(leaf.index().value(), leaf.hash(), &root)
.expect("Proof verification should succeed");
}
}