1use std::{
8 borrow::Borrow,
9 collections::HashMap,
10 sync::{Arc, RwLock},
11};
12
13use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
14use jf_merkle_tree_compat::{
15 prelude::{MerkleNode, MerkleProof},
16 LookupResult, ToTraversalPath,
17};
18use serde::{Deserialize, Serialize};
19
20use crate::{
21 reward_mt::{RewardMerkleProof, REWARD_MERKLE_TREE_V2_INNER_HEIGHT},
22 sparse_mt::{Keccak256Hasher, KeccakNode},
23 v0_3::RewardAmount,
24 v0_4::{RewardAccountV2, REWARD_MERKLE_TREE_V2_ARITY, REWARD_MERKLE_TREE_V2_HEIGHT},
25};
26
27pub type InnerRewardMerkleTreeRoot = Arc<MerkleNode<RewardAmount, RewardAccountV2, KeccakNode>>;
30
31#[derive(
46 Copy,
47 Clone,
48 Debug,
49 Serialize,
50 Deserialize,
51 Hash,
52 Eq,
53 Ord,
54 PartialEq,
55 PartialOrd,
56 CanonicalSerialize,
57 CanonicalDeserialize,
58)]
59pub struct OuterIndex(pub u8);
60
61impl<const ARITY: usize> ToTraversalPath<ARITY> for OuterIndex {
62 fn to_traversal_path(&self, height: usize) -> Vec<usize> {
63 <u8 as ToTraversalPath<ARITY>>::to_traversal_path(&self.0, height)
64 }
65}
66
67impl OuterIndex {
68 pub const MAX: u8 = 15;
70
71 pub fn new(account: &RewardAccountV2) -> Self {
91 Self(account.to_fixed_bytes()[0] >> 4)
93 }
94
95 pub fn value(&self) -> u8 {
100 self.0
101 }
102}
103
104pub trait RewardMerkleTreeStorage {
126 type Error: std::error::Error + Send + Sync + 'static;
132
133 fn with_tree<F, R>(&self, index: &OuterIndex, f: F) -> Result<R, Self::Error>
151 where
152 F: FnOnce(&InnerRewardMerkleTreeRoot) -> R;
153
154 fn with_tree_mut<F, R>(&self, index: &OuterIndex, f: F) -> Result<R, Self::Error>
175 where
176 F: FnOnce(&mut InnerRewardMerkleTreeRoot) -> R;
177
178 fn exists(&self, index: &OuterIndex) -> bool;
189
190 fn get_entries(&self) -> Result<Vec<(RewardAccountV2, RewardAmount)>, Self::Error>;
199
200 fn lookup(
220 &self,
221 pos: impl Borrow<RewardAccountV2>,
222 ) -> Result<LookupResult<RewardAmount, RewardMerkleProof, RewardMerkleProof>, Self::Error> {
223 let pos = pos.borrow();
224 let outer_index = OuterIndex::new(pos);
225 let path =
226 <RewardAccountV2 as ToTraversalPath<REWARD_MERKLE_TREE_V2_ARITY>>::to_traversal_path(
227 pos,
228 REWARD_MERKLE_TREE_V2_HEIGHT,
229 );
230 let inner_path = &path[..REWARD_MERKLE_TREE_V2_INNER_HEIGHT];
231 self.with_tree(
232 &outer_index,
233 |tree| -> LookupResult<RewardAmount, RewardMerkleProof, RewardMerkleProof> {
234 match tree.lookup_internal(REWARD_MERKLE_TREE_V2_INNER_HEIGHT, inner_path) {
235 LookupResult::Ok(value, proof) => {
236 LookupResult::Ok(value, MerkleProof::new(*pos, proof))
237 },
238 LookupResult::NotInMemory => LookupResult::NotInMemory,
239 LookupResult::NotFound(proof) => {
240 LookupResult::NotFound(RewardMerkleProof::new(*pos, proof))
241 },
242 }
243 },
244 )
245 }
246
247 #[allow(clippy::type_complexity)]
280 fn update_with<F>(
281 &mut self,
282 pos: impl Borrow<RewardAccountV2>,
283 f: F,
284 ) -> anyhow::Result<(LookupResult<RewardAmount, (), ()>, i64, KeccakNode)>
285 where
286 F: FnOnce(Option<&RewardAmount>) -> Option<RewardAmount>,
287 {
288 let pos = pos.borrow();
289 let outer_index = OuterIndex::new(pos);
290 let path =
291 <RewardAccountV2 as ToTraversalPath<REWARD_MERKLE_TREE_V2_ARITY>>::to_traversal_path(
292 pos,
293 REWARD_MERKLE_TREE_V2_HEIGHT,
294 );
295 let inner_path = &path[..REWARD_MERKLE_TREE_V2_INNER_HEIGHT];
296 self.with_tree_mut(
297 &outer_index,
298 |tree| -> anyhow::Result<(LookupResult<RewardAmount, (), ()>, i64, KeccakNode)> {
299 let (new_root, delta, result) = tree
300 .update_with_internal::<Keccak256Hasher, REWARD_MERKLE_TREE_V2_ARITY, _>(
301 REWARD_MERKLE_TREE_V2_INNER_HEIGHT,
302 pos,
303 inner_path,
304 f,
305 )?;
306 *tree = new_root;
307 Ok((result, delta, tree.value()))
308 },
309 )?
310 }
311}
312
313fn collect_merkle_leaves(
322 node: &InnerRewardMerkleTreeRoot,
323 entries: &mut Vec<(RewardAccountV2, RewardAmount)>,
324) {
325 match node.as_ref() {
326 MerkleNode::Leaf { pos, elem, .. } => {
327 entries.push((*pos, *elem));
328 },
329 MerkleNode::Branch { children, .. } => {
330 for child in children.iter() {
331 collect_merkle_leaves(child, entries);
332 }
333 },
334 MerkleNode::Empty | MerkleNode::ForgettenSubtree { .. } => {
335 },
337 }
338}
339
340#[derive(Debug)]
345struct InMemoryState {
346 storage: [Option<Vec<(RewardAccountV2, RewardAmount)>>; 16],
350
351 cache: Option<(OuterIndex, InnerRewardMerkleTreeRoot, bool)>,
355}
356
357impl InMemoryState {
358 fn build_tree(entries: Vec<(RewardAccountV2, RewardAmount)>) -> InnerRewardMerkleTreeRoot {
360 let mut root = Arc::new(MerkleNode::Empty);
361 for (account, amount) in entries {
362 let path = <RewardAccountV2 as ToTraversalPath<REWARD_MERKLE_TREE_V2_ARITY>>::to_traversal_path(
363 &account,
364 REWARD_MERKLE_TREE_V2_HEIGHT,
365 );
366 let inner_path = &path[..REWARD_MERKLE_TREE_V2_INNER_HEIGHT];
367 let (new_root, ..) = root
368 .update_with_internal::<Keccak256Hasher, REWARD_MERKLE_TREE_V2_ARITY, _>(
369 REWARD_MERKLE_TREE_V2_INNER_HEIGHT,
370 account,
371 inner_path,
372 |_| Some(amount),
373 )
374 .expect("Building tree from valid entries should not fail");
375 root = new_root;
376 }
377 root
378 }
379
380 fn flush_cache(&mut self) {
386 if let Some((index, tree, dirty)) = self.cache.take() {
387 if dirty {
388 let mut entries = Vec::new();
389 collect_merkle_leaves(&tree, &mut entries);
390 if entries.is_empty() {
391 self.storage[index.0 as usize] = None;
392 } else {
393 self.storage[index.0 as usize] = Some(entries);
394 }
395 }
396 }
397 }
398
399 fn slot_entries(&self, index: &OuterIndex) -> Option<Vec<(RewardAccountV2, RewardAmount)>> {
405 if let Some((cached_idx, tree, _)) = &self.cache {
406 if cached_idx == index {
407 let mut entries = Vec::new();
408 collect_merkle_leaves(tree, &mut entries);
409 return if entries.is_empty() {
410 None
411 } else {
412 Some(entries)
413 };
414 }
415 }
416 self.storage[index.0 as usize].clone()
417 }
418
419 fn ensure_loaded(&mut self, index: &OuterIndex) {
429 if let Some((cached_index, ..)) = &self.cache {
431 if cached_index == index {
432 return;
433 }
434 }
435
436 self.flush_cache();
438
439 let entries = self.storage[index.0 as usize].clone();
442 let tree = match entries {
443 Some(entries) => Self::build_tree(entries),
444 None => Arc::new(MerkleNode::Empty),
445 };
446
447 self.cache = Some((*index, tree, false));
448 }
449}
450
451#[derive(Debug)]
485pub struct CachedInMemoryStorage {
486 inner: RwLock<InMemoryState>,
487}
488
489impl Clone for CachedInMemoryStorage {
491 fn clone(&self) -> Self {
492 let state = self.inner.read().unwrap();
493 Self {
494 inner: RwLock::new(InMemoryState {
495 storage: std::array::from_fn(|i| state.slot_entries(&OuterIndex(i as u8))),
496 cache: None,
497 }),
498 }
499 }
500}
501
502impl Serialize for CachedInMemoryStorage {
506 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
507 where
508 S: serde::Serializer,
509 {
510 use serde::ser::SerializeStruct;
511 let state = self.inner.read().unwrap();
512 let map: HashMap<OuterIndex, Vec<(RewardAccountV2, RewardAmount)>> = (0u8..16)
513 .filter_map(|i| {
514 let idx = OuterIndex(i);
515 state.slot_entries(&idx).map(|entries| (idx, entries))
516 })
517 .collect();
518 let mut s = serializer.serialize_struct("CachedInMemoryStorage", 1)?;
519 s.serialize_field("storage", &map)?;
520 s.end()
521 }
522}
523
524impl<'de> Deserialize<'de> for CachedInMemoryStorage {
526 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
527 where
528 D: serde::Deserializer<'de>,
529 {
530 #[derive(Deserialize)]
531 struct CachedInMemoryStorageData {
532 storage: HashMap<OuterIndex, Vec<(RewardAccountV2, RewardAmount)>>,
533 }
534
535 let mut data = CachedInMemoryStorageData::deserialize(deserializer)?;
536 Ok(Self {
537 inner: RwLock::new(InMemoryState {
538 storage: std::array::from_fn(|i| data.storage.remove(&OuterIndex(i as u8))),
539 cache: None,
540 }),
541 })
542 }
543}
544
545impl PartialEq for CachedInMemoryStorage {
550 fn eq(&self, other: &Self) -> bool {
551 let self_state = self.inner.read().unwrap();
552 let other_state = other.inner.read().unwrap();
553 for i in 0u8..16 {
554 let idx = OuterIndex(i);
555 if self_state.slot_entries(&idx) != other_state.slot_entries(&idx) {
556 return false;
557 }
558 }
559 true
560 }
561}
562
563impl Eq for CachedInMemoryStorage {}
564
565impl CachedInMemoryStorage {
566 pub fn new() -> Self {
575 Self {
576 inner: RwLock::new(InMemoryState {
577 storage: std::array::from_fn(|_| None),
578 cache: None,
579 }),
580 }
581 }
582}
583
584impl Default for CachedInMemoryStorage {
585 fn default() -> Self {
586 Self::new()
587 }
588}
589
590impl RewardMerkleTreeStorage for CachedInMemoryStorage {
591 type Error = std::convert::Infallible;
592
593 fn with_tree<F, R>(&self, index: &OuterIndex, f: F) -> Result<R, Self::Error>
594 where
595 F: FnOnce(&InnerRewardMerkleTreeRoot) -> R,
596 {
597 let mut state = self.inner.write().unwrap();
598 state.ensure_loaded(index);
599 let (_, root, _) = state
600 .cache
601 .as_ref()
602 .expect("Tree should be in cache after load");
603 Ok(f(root))
604 }
605
606 fn with_tree_mut<F, R>(&self, index: &OuterIndex, f: F) -> Result<R, Self::Error>
607 where
608 F: FnOnce(&mut InnerRewardMerkleTreeRoot) -> R,
609 {
610 let mut state = self.inner.write().unwrap();
611 state.ensure_loaded(index);
612 let (_, root, dirty) = state
613 .cache
614 .as_mut()
615 .expect("Tree should be in cache after load");
616 let result = f(root);
617 *dirty = true;
618 Ok(result)
619 }
620
621 fn exists(&self, index: &OuterIndex) -> bool {
622 let state = self.inner.read().unwrap();
623 if let Some((cached_index, ..)) = &state.cache {
624 if cached_index == index {
625 return true;
626 }
627 }
628 state.storage[index.0 as usize].is_some()
629 }
630
631 fn get_entries(&self) -> Result<Vec<(RewardAccountV2, RewardAmount)>, Self::Error> {
632 let state = self.inner.read().unwrap();
633 let mut all_entries = Vec::new();
634 for i in 0u8..16 {
635 if let Some(entries) = state.slot_entries(&OuterIndex(i)) {
636 all_entries.extend(entries);
637 }
638 }
639 Ok(all_entries)
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use alloy::primitives::U256;
646 use jf_merkle_tree_compat::{MerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme};
647 use rand::{Rng, SeedableRng};
648 use rand_chacha::ChaCha20Rng;
649
650 use super::*;
651 use crate::{
652 reward_mt::{
653 InMemoryRewardMerkleTreeV2, InnerRewardMerkleTreeV2, REWARD_MERKLE_TREE_V2_HEIGHT,
654 },
655 v0_3::RewardAmount,
656 v0_4::RewardAccountV2,
657 };
658
659 fn random_account(rng: &mut impl Rng) -> RewardAccountV2 {
661 let mut bytes = [0u8; 20];
662 rng.fill(&mut bytes);
663 RewardAccountV2::from(bytes)
664 }
665
666 fn random_amount(rng: &mut impl Rng) -> RewardAmount {
668 RewardAmount(U256::from(rng.gen::<u64>()))
669 }
670
671 #[test]
672 fn test_to_traversal_path() {
673 let mut rng = ChaCha20Rng::seed_from_u64(42);
674 let account = random_account(&mut rng);
675 let full_path = <RewardAccountV2 as ToTraversalPath<2>>::to_traversal_path(&account, 160);
676 let outer_index = OuterIndex::new(&account);
677 let outer_path = <OuterIndex as ToTraversalPath<2>>::to_traversal_path(&outer_index, 4);
678 assert_eq!(
679 &outer_path,
680 &full_path[REWARD_MERKLE_TREE_V2_INNER_HEIGHT..]
681 );
682 }
683
684 #[test]
685 fn test_two_level_tree_matches_single_level() {
686 let mut rng = ChaCha20Rng::seed_from_u64(42);
687
688 let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
690
691 let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
693
694 let num_accounts = 20;
696 let mut test_accounts = Vec::new();
697
698 for _ in 0..num_accounts {
699 let account = random_account(&mut rng);
700 let amount = random_amount(&mut rng);
701 test_accounts.push((account, amount));
702
703 two_level_tree.update(account, amount).unwrap();
705 single_level_tree.update(account, amount).unwrap();
706
707 assert_eq!(
709 two_level_tree.commitment(),
710 single_level_tree.commitment(),
711 "Commitments should match after insertion"
712 );
713 }
714
715 assert_eq!(
717 two_level_tree.num_leaves(),
718 single_level_tree.num_leaves(),
719 "Number of leaves should match"
720 );
721 }
722
723 #[test]
724 fn test_lookup_and_proof_verification() {
725 let mut rng = ChaCha20Rng::seed_from_u64(123);
726
727 let mut tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
728 let mut accounts = Vec::new();
729
730 for _ in 0..10 {
732 let account = random_account(&mut rng);
733 let amount = random_amount(&mut rng);
734 accounts.push((account, amount));
735 tree.update(account, amount).unwrap();
736 }
737
738 for (account, expected_amount) in &accounts {
740 match tree.lookup(account).unwrap() {
741 LookupResult::Ok(amount, proof) => {
742 assert_eq!(amount, *expected_amount, "Amount should match");
743
744 let verification =
746 InMemoryRewardMerkleTreeV2::verify(tree.commitment(), account, &proof)
747 .unwrap();
748 assert!(verification.is_ok(), "Proof should be valid");
749 },
750 _ => panic!("Account should be found"),
751 }
752 }
753
754 let non_existent = random_account(&mut rng);
756 match tree.lookup(non_existent).unwrap() {
757 LookupResult::NotFound(_) => {}, _ => panic!("Non-existent account should not be found"),
759 }
760 }
761
762 #[test]
763 fn test_universal_lookup() {
764 let mut rng = ChaCha20Rng::seed_from_u64(456);
765
766 let mut tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
767
768 let account = random_account(&mut rng);
769 let amount = random_amount(&mut rng);
770
771 tree.update(account, amount).unwrap();
773
774 match tree.universal_lookup(account).unwrap() {
776 LookupResult::Ok(found_amount, proof) => {
777 assert_eq!(found_amount, amount, "Amount should match");
778
779 let verification =
781 InMemoryRewardMerkleTreeV2::verify(tree.commitment(), account, &proof).unwrap();
782 assert!(verification.is_ok(), "Membership proof should be valid");
783 },
784 _ => panic!("Account should be found with membership proof"),
785 }
786
787 let non_existent = random_account(&mut rng);
789 match tree.universal_lookup(non_existent).unwrap() {
790 LookupResult::NotFound(proof) => {
791 let is_valid = InMemoryRewardMerkleTreeV2::non_membership_verify(
793 tree.commitment(),
794 non_existent,
795 &proof,
796 )
797 .unwrap();
798 assert!(is_valid, "Non-membership proof should be valid");
799 },
800 _ => panic!("Non-existent account should return non-membership proof"),
801 }
802 }
803
804 #[test]
805 fn test_update_existing_account() {
806 let mut rng = ChaCha20Rng::seed_from_u64(101112);
807
808 let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
809 let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
810
811 let account = random_account(&mut rng);
812 let amount1 = random_amount(&mut rng);
813 let amount2 = random_amount(&mut rng);
814
815 two_level_tree.update(account, amount1).unwrap();
817 single_level_tree.update(account, amount1).unwrap();
818 assert_eq!(two_level_tree.commitment(), single_level_tree.commitment());
819
820 two_level_tree.update(account, amount2).unwrap();
822 single_level_tree.update(account, amount2).unwrap();
823 assert_eq!(
824 two_level_tree.commitment(),
825 single_level_tree.commitment(),
826 "Commitments should match after update"
827 );
828
829 match two_level_tree.lookup(account).unwrap() {
831 LookupResult::Ok(amount, _) => {
832 assert_eq!(amount, amount2, "Updated amount should match");
833 },
834 _ => panic!("Account should be found"),
835 }
836
837 assert_eq!(two_level_tree.num_leaves(), 1);
839 }
840
841 #[test]
842 fn test_from_kv_set() {
843 let mut rng = ChaCha20Rng::seed_from_u64(161718);
844
845 let mut kv_pairs = Vec::new();
847 for _ in 0..15 {
848 let account = random_account(&mut rng);
849 let amount = random_amount(&mut rng);
850 kv_pairs.push((account, amount));
851 }
852
853 let two_level_tree =
855 InMemoryRewardMerkleTreeV2::from_kv_set(REWARD_MERKLE_TREE_V2_HEIGHT, &kv_pairs)
856 .unwrap();
857
858 let single_level_tree =
860 InnerRewardMerkleTreeV2::from_kv_set(REWARD_MERKLE_TREE_V2_HEIGHT, &kv_pairs).unwrap();
861
862 assert_eq!(
864 two_level_tree.commitment(),
865 single_level_tree.commitment(),
866 "Commitments should match when built from same kv set"
867 );
868
869 for (account, expected_amount) in &kv_pairs {
871 match two_level_tree.lookup(account).unwrap() {
872 LookupResult::Ok(amount, _) => {
873 assert_eq!(amount, *expected_amount, "Amount should match");
874 },
875 _ => panic!("Account should be found"),
876 }
877 }
878 }
879
880 #[test]
881 fn test_update_with_custom_function() {
882 let mut rng = ChaCha20Rng::seed_from_u64(222324);
883
884 let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
885 let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
886
887 let account = random_account(&mut rng);
888 let initial_amount = RewardAmount(U256::from(100u64));
889 let increment = RewardAmount(U256::from(50u64));
890
891 two_level_tree.update(account, initial_amount).unwrap();
893 single_level_tree.update(account, initial_amount).unwrap();
894
895 two_level_tree
897 .update_with(account, |existing| {
898 existing.map(|amt| RewardAmount(amt.0 + increment.0))
899 })
900 .unwrap();
901 single_level_tree
902 .update_with(account, |existing| {
903 existing.map(|amt| RewardAmount(amt.0 + increment.0))
904 })
905 .unwrap();
906
907 assert_eq!(
909 two_level_tree.commitment(),
910 single_level_tree.commitment(),
911 "Commitments should match after update_with"
912 );
913
914 match two_level_tree.lookup(account).unwrap() {
916 LookupResult::Ok(amount, _) => {
917 assert_eq!(
918 amount,
919 RewardAmount(initial_amount.0 + increment.0),
920 "Amount should be incremented"
921 );
922 },
923 _ => panic!("Account should be found"),
924 }
925 }
926
927 #[test]
928 fn test_remove_account() {
929 let mut rng = ChaCha20Rng::seed_from_u64(252627);
930
931 let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
932 let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
933
934 let account = random_account(&mut rng);
935 let amount = random_amount(&mut rng);
936
937 two_level_tree.update(account, amount).unwrap();
939 single_level_tree.update(account, amount).unwrap();
940 assert_eq!(two_level_tree.num_leaves(), 1);
941
942 two_level_tree.update_with(account, |_| None).unwrap();
944 single_level_tree.update_with(account, |_| None).unwrap();
945
946 assert_eq!(
948 two_level_tree.commitment(),
949 single_level_tree.commitment(),
950 "Commitments should match after removal"
951 );
952
953 assert_eq!(two_level_tree.num_leaves(), 0);
955 match two_level_tree.lookup(account).unwrap() {
956 LookupResult::NotFound(_) => {}, _ => panic!("Removed account should not be found"),
958 }
959 }
960
961 #[test]
962 fn test_stress_with_many_operations() {
963 let mut rng = ChaCha20Rng::seed_from_u64(282930);
964
965 let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
966 let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
967
968 let mut known_accounts = Vec::new();
969
970 for i in 0..50 {
972 let op = rng.gen_range(0..3);
973
974 match op {
975 0 => {
976 let account = random_account(&mut rng);
978 let amount = random_amount(&mut rng);
979 known_accounts.push((account, amount));
980
981 two_level_tree.update(account, amount).unwrap();
982 single_level_tree.update(account, amount).unwrap();
983 },
984 1 if !known_accounts.is_empty() => {
985 let idx = rng.gen_range(0..known_accounts.len());
987 let (account, _) = known_accounts[idx];
988 let new_amount = random_amount(&mut rng);
989 known_accounts[idx].1 = new_amount;
990
991 two_level_tree.update(account, new_amount).unwrap();
992 single_level_tree.update(account, new_amount).unwrap();
993 },
994 2 if !known_accounts.is_empty() => {
995 let idx = rng.gen_range(0..known_accounts.len());
997 let (account, _) = known_accounts.remove(idx);
998
999 two_level_tree.update_with(account, |_| None).unwrap();
1000 single_level_tree.update_with(account, |_| None).unwrap();
1001 },
1002 _ => continue,
1003 }
1004
1005 assert_eq!(
1007 two_level_tree.commitment(),
1008 single_level_tree.commitment(),
1009 "Commitments should match after operation {}",
1010 i
1011 );
1012 }
1013
1014 assert_eq!(
1016 two_level_tree.num_leaves(),
1017 known_accounts.len() as u64,
1018 "Number of leaves should match known accounts"
1019 );
1020 }
1021
1022 #[test]
1023 fn test_get_entries() {
1024 let mut rng = ChaCha20Rng::seed_from_u64(424344);
1025
1026 let mut tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1027 let mut expected_entries = std::collections::HashMap::new();
1028
1029 for _ in 0..20 {
1031 let account = random_account(&mut rng);
1032 let amount = random_amount(&mut rng);
1033 expected_entries.insert(account, amount);
1034 tree.update(account, amount).unwrap();
1035 }
1036
1037 let collected_entries: std::collections::HashMap<_, _> =
1039 tree.get_entries().unwrap().into_iter().collect();
1040
1041 assert_eq!(
1043 collected_entries.len(),
1044 expected_entries.len(),
1045 "Iterator should return all entries"
1046 );
1047
1048 for (account, expected_amount) in &expected_entries {
1049 let collected_amount = collected_entries
1050 .get(account)
1051 .expect("Account should be in iterator results");
1052 assert_eq!(
1053 collected_amount, expected_amount,
1054 "Amount should match for account {:?}",
1055 account
1056 );
1057 }
1058 }
1059}