Skip to content

Commit bdbc31d

Browse files
committed
add state root assert
1 parent 4009af2 commit bdbc31d

File tree

1 file changed

+86
-42
lines changed

1 file changed

+86
-42
lines changed

tests/integration_tests/persist_state_update_test.rs

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::utils::*;
22
use function_name::named;
33
use light_compressed_account::indexer_event::event::BatchNullifyContext;
4-
use light_merkle_tree_reference::MerkleTree;
4+
use light_compressed_account::TreeType;
55
use light_hasher::Poseidon;
6+
use light_merkle_tree_reference::MerkleTree;
67
use photon_indexer::common::typedefs::account::AccountData;
78
use photon_indexer::common::typedefs::account::{Account, AccountContext, AccountWithContext};
89
use photon_indexer::common::typedefs::bs64_string::Base64String;
@@ -75,7 +76,7 @@ impl Default for StateUpdateConfig {
7576
fn default() -> Self {
7677
Self {
7778
in_accounts: CollectionConfig::new(0, 5, 0.0),
78-
out_accounts: CollectionConfig::new(0, 5, 1.0),
79+
out_accounts: CollectionConfig::new(1, 5, 1.0),
7980
account_transactions: CollectionConfig::new(0, 3, 0.0),
8081
transactions: CollectionConfig::new(0, 2, 0.0),
8182
leaf_nullifications: CollectionConfig::new(0, 3, 0.0),
@@ -151,7 +152,11 @@ fn get_rnd_state_update(
151152
seq: Some(UnsignedInteger(base_seq + i as u64)),
152153
slot_created: UnsignedInteger(slot),
153154
},
154-
context: AccountContext::default(),
155+
context: AccountContext {
156+
tree_type: TreeType::StateV1 as u16,
157+
queue: tree_info.queue.into(),
158+
..Default::default()
159+
},
155160
};
156161
state_update.out_accounts.push(account);
157162
}
@@ -381,67 +386,106 @@ async fn assert_state_tree_root(
381386
) -> Result<(), Box<dyn std::error::Error>> {
382387
use photon_indexer::dao::generated::state_trees;
383388
use sea_orm::ColumnTrait;
384-
385389
if state_update.out_accounts.is_empty() {
386390
println!("✅ No output accounts - skipping state tree root verification");
387391
return Ok(());
388392
}
389393

390394
// Get the tree pubkey from the first output account (all should use same tree)
391-
let tree_pubkey_bytes = state_update.out_accounts[0].account.tree.0.to_bytes().to_vec();
392-
393-
// Append all new account hashes to reference tree
395+
let tree_pubkey_bytes = state_update.out_accounts[0]
396+
.account
397+
.tree
398+
.0
399+
.to_bytes()
400+
.to_vec();
401+
402+
println!("Output Account Hashes:");
394403
for account_with_context in &state_update.out_accounts {
395-
let account_hash_bytes = account_with_context.account.hash.0.to_vec();
396-
let mut hash_array = [0u8; 32];
397-
hash_array.copy_from_slice(&account_hash_bytes);
398-
reference_tree.append(&hash_array)?;
404+
let account_hash = hex::encode(&account_with_context.account.hash.0);
405+
let leaf_index = account_with_context.account.leaf_index.0;
406+
println!(" Hash({}) at leaf_index {}", account_hash, leaf_index);
399407
}
400408

401-
// Get reference tree root
402-
let reference_root = reference_tree.root();
403-
404-
// First, let's see what nodes are actually in the state_trees table
405-
let all_nodes = state_trees::Entity::find()
409+
// First, get all leaf nodes from database to verify they match our output accounts
410+
let leaf_nodes = state_trees::Entity::find()
406411
.filter(state_trees::Column::Tree.eq(tree_pubkey_bytes.clone()))
412+
.filter(state_trees::Column::Level.eq(0i64)) // Leaf level
407413
.all(db_conn)
408414
.await?;
409415

410-
println!("All nodes in state_trees table for tree {:?}:", hex::encode(&tree_pubkey_bytes));
411-
for node in &all_nodes {
412-
println!(" node_idx: {}, level: {}, leaf_idx: {:?}, seq: {:?}, hash: {:?}",
413-
node.node_idx, node.level, node.leaf_idx, node.seq, hex::encode(&node.hash));
416+
println!("Database Leaf Hashes:");
417+
for leaf in &leaf_nodes {
418+
println!(" Hash({}) at leaf_idx={:?}", hex::encode(&leaf.hash), leaf.leaf_idx);
414419
}
415420

416-
if all_nodes.is_empty() {
417-
println!("✅ No state tree nodes found - this might be expected for the test configuration");
418-
return Ok(());
421+
// Assert that all our new account hashes are present as leaf nodes in the database
422+
for account_with_context in &state_update.out_accounts {
423+
let account_hash = hex::encode(&account_with_context.account.hash.0);
424+
let leaf_index = account_with_context.account.leaf_index.0;
425+
426+
let found_leaf = leaf_nodes.iter().find(|leaf| {
427+
leaf.leaf_idx == Some(leaf_index as i64) &&
428+
hex::encode(&leaf.hash) == account_hash
429+
});
430+
431+
assert!(found_leaf.is_some(),
432+
"Account hash {} at leaf_index {} not found in database leaf nodes",
433+
account_hash, leaf_index);
419434
}
435+
println!("✅ All account hashes verified as leaf nodes in database");
420436

421-
// Find the root node (highest level)
422-
let max_level = all_nodes.iter().map(|node| node.level).max().unwrap_or(0);
423-
let root_nodes: Vec<_> = all_nodes.iter().filter(|node| node.level == max_level).collect();
424-
425-
if root_nodes.len() != 1 {
426-
println!("⚠️ Multiple or no root nodes found at level {}: {:?}", max_level, root_nodes.len());
427-
// For now, just skip the root verification
428-
return Ok(());
437+
// Construct reference tree from output accounts directly
438+
// Find the maximum leaf index to determine tree size needed
439+
let max_leaf_idx = state_update.out_accounts
440+
.iter()
441+
.map(|acc| acc.account.leaf_index.0)
442+
.max()
443+
.unwrap_or(0);
444+
445+
println!("Constructing reference tree up to leaf index {}", max_leaf_idx);
446+
447+
// Append leaves to reference tree in the correct positions
448+
// Fill with zero hashes for missing leaves, actual account hashes for present ones
449+
for i in 0..=max_leaf_idx {
450+
let leaf_hash = state_update.out_accounts
451+
.iter()
452+
.find(|acc| acc.account.leaf_index.0 == i)
453+
.map(|acc| acc.account.hash.0)
454+
.unwrap_or([0u8; 32]); // Zero hash for missing leaves
455+
456+
reference_tree.append(&leaf_hash)?;
429457
}
430458

459+
// Get reference tree root after construction
460+
let reference_root = reference_tree.root();
461+
println!("Reference tree root: {}", hex::encode(&reference_root));
462+
463+
// Get database root node for comparison
464+
let all_nodes = state_trees::Entity::find()
465+
.filter(state_trees::Column::Tree.eq(tree_pubkey_bytes.clone()))
466+
.all(db_conn)
467+
.await?;
468+
469+
let max_level = all_nodes.iter().map(|node| node.level).max().unwrap_or(0);
470+
let root_nodes: Vec<_> = all_nodes
471+
.iter()
472+
.filter(|node| node.level == max_level)
473+
.collect();
474+
475+
assert_eq!(root_nodes.len(), 1, "Expected exactly 1 root node, found {}", root_nodes.len());
476+
431477
let root_node = root_nodes[0];
432478
let mut db_root_array = [0u8; 32];
433479
db_root_array.copy_from_slice(&root_node.hash);
480+
println!("Database root: {}", hex::encode(&db_root_array));
434481

435482
assert_eq!(
436483
reference_root, db_root_array,
437-
"State tree root mismatch!\nReference: {:?}\nDatabase: {:?}",
438-
reference_root, db_root_array
484+
"State tree root mismatch!\nReference: {}\nDatabase: {}",
485+
hex::encode(&reference_root), hex::encode(&db_root_array)
439486
);
440-
441-
println!(
442-
"✅ Successfully verified state tree root matches reference implementation"
443-
);
444-
println!("Tree root: {:?}", reference_root);
487+
488+
println!("✅ State tree root verification successful!");
445489

446490
Ok(())
447491
}
@@ -523,10 +567,10 @@ async fn test_output_accounts(#[values(DatabaseBackend::Sqlite)] db_backend: Dat
523567
let mut rng = StdRng::seed_from_u64(seed);
524568

525569
// Initialize reference Merkle tree for state tree root verification
526-
let tree_info = TreeInfo::get(TEST_TREE_PUBKEY_STR)
527-
.expect("Test tree should exist in QUEUE_TREE_MAPPING");
570+
let tree_info =
571+
TreeInfo::get(TEST_TREE_PUBKEY_STR).expect("Test tree should exist in QUEUE_TREE_MAPPING");
528572
let tree_height = tree_info.height as usize;
529-
let mut reference_tree = MerkleTree::<Poseidon>::new(tree_height, 0);
573+
let mut reference_tree = MerkleTree::<Poseidon>::new(26, 0);
530574

531575
// Test that the new config structure works correctly
532576
let config = StateUpdateConfig::default();
@@ -536,7 +580,7 @@ async fn test_output_accounts(#[values(DatabaseBackend::Sqlite)] db_backend: Dat
536580
assert_eq!(config.in_accounts.max_entries, 5);
537581
assert_eq!(config.in_accounts.probability, 0.0);
538582

539-
assert_eq!(config.out_accounts.min_entries, 0);
583+
assert_eq!(config.out_accounts.min_entries, 1);
540584
assert_eq!(config.out_accounts.max_entries, 5);
541585
assert_eq!(config.out_accounts.probability, 1.0);
542586

0 commit comments

Comments
 (0)