espresso_types/v0/reward_mt/
storage.rs

1//! Storage abstraction for inner Merkle tree roots.
2//!
3//! The [`RewardMerkleTreeStorage`] trait allows pluggable storage backends
4//! (memory, disk, database). [`CachedInMemoryStorage`] provides fast in-memory
5//! storage with single-entry caching.
6
7use 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
27/// Root node of a 156-bit inner Merkle tree, wrapped in `Arc` to avoid deep
28/// clones when the jellyfish internals return a new root after an update.
29pub type InnerRewardMerkleTreeRoot = Arc<MerkleNode<RewardAmount, RewardAccountV2, KeccakNode>>;
30
31/// Outer tree index (0-15) derived from account address high nibble.
32///
33/// Represents the partition index in the outer tree, calculated as the
34/// first 4 bits (high nibble) of an account address:
35///
36/// ```text
37/// Address:     0xABCDEF...
38///                ^
39///              High nibble = A (hex) = 10 (decimal)
40/// OuterIndex:  10
41/// ```
42///
43/// This partitions the 160-bit address space into 16 equal-sized buckets
44/// (2^156 addresses each), allowing efficient storage and lookup.
45#[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    /// Maximum valid outer index (2^4 - 1 = 15).
69    pub const MAX: u8 = 15;
70
71    /// Extract outer index from a reward account address.
72    ///
73    /// Takes the high nibble (first 4 bits) of the account address's first byte.
74    /// Since Ethereum addresses are big-endian, this is byte[0] >> 4.
75    ///
76    /// # Example
77    ///
78    /// ```text
79    /// Account: 0xA1B2C3D4E5F6...
80    /// Byte[0]: 0xA1 = 0b1010_0001
81    /// >> 4:    0x0A = 0b0000_1010 = 10
82    /// Result:  OuterIndex(10)
83    /// ```
84    ///
85    /// # Arguments
86    /// * `account` - The reward account (Ethereum address)
87    ///
88    /// # Returns
89    /// An `OuterIndex` in the range [0, 15]
90    pub fn new(account: &RewardAccountV2) -> Self {
91        // Extract high nibble from first byte (big-endian)
92        Self(account.to_fixed_bytes()[0] >> 4)
93    }
94
95    /// Get the raw index value (0-15).
96    ///
97    /// # Returns
98    /// The partition index as a u8
99    pub fn value(&self) -> u8 {
100        self.0
101    }
102}
103
104/// Storage abstraction for inner Merkle tree roots.
105///
106/// Defines how inner tree roots (one per partition) are persisted and accessed.
107/// Implementations control storage strategy (memory, disk, database) and caching.
108///
109/// # Design Pattern
110///
111/// Uses the "loan" pattern with closures to ensure proper cache management:
112/// - `with_tree` / `with_tree_mut` load trees on-demand and handle flushing
113/// - Callers cannot hold references beyond the closure scope
114/// - Storage backend controls when to load/flush/cache
115///
116/// # Implementations
117///
118/// - [`CachedInMemoryStorage`]: Fast, non-persistent, single-entry cache
119/// - [`fs_storage::RewardMerkleTreeFSStorage`]: File-backed, persistent
120///
121/// # Thread Safety
122///
123/// Implementations use `RwLock` for interior mutability, allowing `&self` methods
124/// to perform cache operations while maintaining Sync/Send.
125pub trait RewardMerkleTreeStorage {
126    /// Error type for storage operations.
127    ///
128    /// - In-memory storage: `std::convert::Infallible` (never fails)
129    /// - File storage: `std::io::Error` (disk I/O failures)
130    /// - Database storage: Custom error type
131    type Error: std::error::Error + Send + Sync + 'static;
132
133    /// Execute a read-only operation on an inner tree.
134    ///
135    /// Loads the tree from storage (or cache) if needed. Creates an empty tree
136    /// if the partition has never been written to.
137    ///
138    /// # Arguments
139    /// * `index` - Partition index (0-15)
140    /// * `f` - Closure that receives immutable tree reference
141    ///
142    /// # Returns
143    /// The closure's return value, or storage error
144    ///
145    /// # Example
146    ///
147    /// ```rust,ignore
148    /// storage.with_tree(outer_index, |tree| tree.num_leaves())?
149    /// ```
150    fn with_tree<F, R>(&self, index: &OuterIndex, f: F) -> Result<R, Self::Error>
151    where
152        F: FnOnce(&InnerRewardMerkleTreeRoot) -> R;
153
154    /// Execute a mutating operation on an inner tree.
155    ///
156    /// Loads the tree from storage (or cache) if needed. Creates an empty tree
157    /// if the partition has never been written to. Changes are written back when
158    /// the cache is flushed.
159    ///
160    /// # Arguments
161    /// * `index` - Partition index (0-15)
162    /// * `f` - Closure that receives mutable tree reference
163    ///
164    /// # Returns
165    /// The closure's return value, or storage error
166    ///
167    /// # Example
168    ///
169    /// ```rust,ignore
170    /// storage.with_tree_mut(outer_index, |tree| {
171    ///     tree.update(account, amount)
172    /// })?
173    /// ```
174    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    /// Check if an inner tree exists at the given partition.
179    ///
180    /// Returns `true` if the partition has any accounts (non-empty tree),
181    /// `false` if it's empty or never been used.
182    ///
183    /// # Arguments
184    /// * `index` - Partition index (0-15)
185    ///
186    /// # Returns
187    /// Whether the partition contains data
188    fn exists(&self, index: &OuterIndex) -> bool;
189
190    /// Return all (account, amount) pairs directly, bypassing tree construction.
191    ///
192    /// Reads entries from each partition's backing store (or cache tree for the
193    /// currently-cached partition). Unlike `with_tree`-based iteration, this
194    /// never builds a Merkle tree — it reads the flat entry lists directly.
195    ///
196    /// # Returns
197    /// All (account, amount) pairs across all 16 partitions
198    fn get_entries(&self) -> Result<Vec<(RewardAccountV2, RewardAmount)>, Self::Error>;
199
200    /// Look up an account's balance and generate a proof (membership or non-membership).
201    ///
202    /// This is a provided method that uses `with_tree` to perform the lookup within
203    /// the appropriate inner tree. Extracts the outer index from the account address,
204    /// loads that partition, and generates an inner proof (156 bits).
205    ///
206    /// The caller (typically `StorageBackedRewardMerkleTreeV2`) must patch the inner
207    /// proof with the outer proof to create a full 160-bit proof.
208    ///
209    /// # Arguments
210    /// * `pos` - Account address to look up
211    ///
212    /// # Returns
213    /// - `Ok(value, proof)` - Account found with balance and inner membership proof
214    /// - `NotInMemory` - Account data not available (sparse tree, needs catchup)
215    /// - `NotFound(proof)` - Account doesn't exist, includes inner non-membership proof
216    ///
217    /// # Errors
218    /// Returns storage error if IO operation fails
219    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    /// Update an account's balance using a custom function.
248    ///
249    /// Provided method that uses `with_tree_mut` to modify an inner tree. The function
250    /// receives the current balance (if any) and returns the new balance. Returning
251    /// `None` removes the account.
252    ///
253    /// # Arguments
254    /// * `pos` - Account address to update
255    /// * `f` - Update function: `Option<&RewardAmount> -> Option<RewardAmount>`
256    ///   - Input `Some(&amount)` if account exists, `None` otherwise
257    ///   - Output `Some(new_amount)` to set balance, `None` to remove
258    ///
259    /// # Returns
260    /// Tuple of:
261    /// - `LookupResult` - Previous value (Ok/NotFound/NotInMemory)
262    /// - `i64` - Leaf count delta (+1 for insert, -1 for remove, 0 for update)
263    /// - `KeccakNode` - New inner tree root hash (or default if tree becomes empty)
264    ///
265    /// # Errors
266    /// Returns error if merkle tree update fails or storage operation fails
267    ///
268    /// # Example
269    ///
270    /// ```rust,ignore
271    /// // Increment balance by 100
272    /// storage.update_with(account, |existing| {
273    ///     Some(RewardAmount(existing.map(|a| a.0).unwrap_or(U256::ZERO) + U256::from(100)))
274    /// })?;
275    ///
276    /// // Remove account
277    /// storage.update_with(account, |_| None)?;
278    /// ```
279    #[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
313/// Recursively traverses a merkle tree node and collects all leaf entries.
314///
315/// Performs depth-first traversal of the tree structure, extracting account-balance
316/// pairs from leaf nodes. Skips empty nodes and forgotten subtrees.
317///
318/// # Arguments
319/// * `node` - Root node to traverse (can be Empty, Leaf, Branch, or ForgettenSubtree)
320/// * `entries` - Mutable vector to append discovered (account, balance) pairs
321fn 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            // No leaves to collect
336        },
337    }
338}
339
340/// Internal state guarded by a single `RwLock` in [`CachedInMemoryStorage`].
341///
342/// Combines the backing store and the single-entry cache under one lock so that
343/// all operations are atomic — no lock ordering to maintain and no TOCTOU gaps.
344#[derive(Debug)]
345struct InMemoryState {
346    /// Backing store: fixed-size array of 16 partition slots.
347    /// `storage[i]` corresponds to `OuterIndex(i)`. A slot is `None` when the
348    /// partition is empty or has never been written.
349    storage: [Option<Vec<(RewardAccountV2, RewardAmount)>>; 16],
350
351    /// Single-entry cache: (partition_index, live tree root, dirty).
352    /// `dirty` is `true` if the tree has been mutated since loading; only dirty
353    /// entries are written back on eviction.
354    cache: Option<(OuterIndex, InnerRewardMerkleTreeRoot, bool)>,
355}
356
357impl InMemoryState {
358    /// Rebuild an inner tree from a flat list of (account, amount) entries.
359    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    /// Evict the cached tree back to a flat entry list.
381    ///
382    /// Only dirty entries are written back to `storage`, saving the
383    /// `collect_merkle_leaves` traversal for partitions that were only read.
384    /// Empty partitions are stored as `None` rather than `Some(vec![])`.
385    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    /// Get the flat entry list for a partition, reading from the cache tree if
400    /// this partition is cached, or from the storage array otherwise.
401    ///
402    /// This is the read-only counterpart to `flush_cache` + storage access:
403    /// it produces the same entries without mutating state.
404    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    /// Ensure the requested partition is in the cache.
420    ///
421    /// Called on `&mut self` — the caller already holds the outer `RwLock` in
422    /// write mode, so this is an uninterrupted critical section.
423    ///
424    /// If the partition is already cached, does nothing. Otherwise:
425    /// 1. Flushes the current cache entry (writes back to storage if dirty)
426    /// 2. Rebuilds the requested tree from its stored entry list (or starts empty)
427    /// 3. Stores the live tree in cache
428    fn ensure_loaded(&mut self, index: &OuterIndex) {
429        // Already cached — nothing to do
430        if let Some((cached_index, ..)) = &self.cache {
431            if cached_index == index {
432                return;
433            }
434        }
435
436        // Flush current cache entry (writes back if dirty, clears cache)
437        self.flush_cache();
438
439        // Clone the entry list from backing storage (the data stays in the
440        // array so a clean eviction can skip the write-back).
441        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/// In-memory storage with single-entry cache.
452///
453/// # Design
454///
455/// Stores each partition as a flat `Vec<(RewardAccountV2, RewardAmount)>` entry list
456/// in a fixed-size array. Only the currently-active partition is held as a live
457/// `InnerRewardMerkleTreeRoot` in the single-entry cache; all other partitions
458/// stay as compact entry lists.
459///
460/// Both the backing store and the cache are protected by a single `RwLock`,
461/// eliminating all lock ordering concerns and TOCTOU gaps.
462///
463/// # Caching Strategy
464///
465/// - **Cache hit**: Direct access to the live tree (no rebuild)
466/// - **Cache miss**: Evict current entry, rebuild tree from stored entries
467///
468/// # Thread Safety
469///
470/// Uses a single `RwLock<InMemoryState>` for interior mutability. All operations
471/// that touch both cache and storage are atomic under one lock acquisition.
472///
473/// # Performance
474///
475/// Best for:
476/// - Sequential access (processing blocks in order)
477/// - Validators without full state persistence
478/// - Testing and development
479/// - Reconstructing state from catchup
480///
481/// Not ideal for:
482/// - Random access across many partitions (thrashing)
483/// - Long-term persistence (loses state on restart)
484#[derive(Debug)]
485pub struct CachedInMemoryStorage {
486    inner: RwLock<InMemoryState>,
487}
488
489// Manual Clone implementation — read lock + slot_entries, no cache mutation.
490impl 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
502// Manual Serialize implementation — read lock + slot_entries, no cache mutation.
503// Wire format: `{ "storage": HashMap<OuterIndex, Vec<(RewardAccountV2, RewardAmount)>> }`
504// preserved for backward compatibility.
505impl 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
524// Manual Deserialize implementation — distribute HashMap entries into array slots.
525impl<'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
545// Manual PartialEq implementation — read lock + slot_entries, no cache mutation.
546// Both entry lists are produced by deterministic tree traversal so ordering is
547// canonical — direct comparison is sufficient.
548// Uses read locks, so self-comparison (a == a) is safe (read locks are reentrant).
549impl 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    /// Create a new empty storage.
567    ///
568    /// # Example
569    ///
570    /// ```rust,ignore
571    /// let storage = CachedInMemoryStorage::new();
572    /// let tree = InMemoryRewardMerkleTreeV2::new_with_storage(storage);
573    /// ```
574    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    /// Generate a random reward account address
660    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    /// Generate a random reward amount
667    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        // Create two-level tree (our implementation)
689        let mut two_level_tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
690
691        // Create single-level tree for comparison
692        let mut single_level_tree = InnerRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
693
694        // Insert random accounts
695        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            // Insert into both trees
704            two_level_tree.update(account, amount).unwrap();
705            single_level_tree.update(account, amount).unwrap();
706
707            // Verify commitments match after each insertion
708            assert_eq!(
709                two_level_tree.commitment(),
710                single_level_tree.commitment(),
711                "Commitments should match after insertion"
712            );
713        }
714
715        // Verify final state
716        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        // Insert several accounts
731        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        // Test lookup for each inserted account
739        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                    // Verify the proof
745                    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        // Test lookup for non-existent account
755        let non_existent = random_account(&mut rng);
756        match tree.lookup(non_existent).unwrap() {
757            LookupResult::NotFound(_) => {}, // Expected
758            _ => 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        // Insert account
772        tree.update(account, amount).unwrap();
773
774        // Test universal lookup for existing account
775        match tree.universal_lookup(account).unwrap() {
776            LookupResult::Ok(found_amount, proof) => {
777                assert_eq!(found_amount, amount, "Amount should match");
778
779                // Verify membership proof
780                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        // Test universal lookup for non-existent account
788        let non_existent = random_account(&mut rng);
789        match tree.universal_lookup(non_existent).unwrap() {
790            LookupResult::NotFound(proof) => {
791                // Verify non-membership proof
792                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        // Initial insert
816        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        // Update existing account
821        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        // Verify the updated value
830        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        // Number of leaves should remain the same (updated, not inserted new)
838        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        // Generate test data
846        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        // Build two-level tree from kv set
854        let two_level_tree =
855            InMemoryRewardMerkleTreeV2::from_kv_set(REWARD_MERKLE_TREE_V2_HEIGHT, &kv_pairs)
856                .unwrap();
857
858        // Build single-level tree from same kv set
859        let single_level_tree =
860            InnerRewardMerkleTreeV2::from_kv_set(REWARD_MERKLE_TREE_V2_HEIGHT, &kv_pairs).unwrap();
861
862        // Verify commitments match
863        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        // Verify all accounts can be looked up
870        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        // Initial insert
892        two_level_tree.update(account, initial_amount).unwrap();
893        single_level_tree.update(account, initial_amount).unwrap();
894
895        // Update using custom function (increment)
896        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        // Verify commitments match
908        assert_eq!(
909            two_level_tree.commitment(),
910            single_level_tree.commitment(),
911            "Commitments should match after update_with"
912        );
913
914        // Verify the updated value
915        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        // Insert account
938        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        // Remove account by returning None
943        two_level_tree.update_with(account, |_| None).unwrap();
944        single_level_tree.update_with(account, |_| None).unwrap();
945
946        // Verify commitments match
947        assert_eq!(
948            two_level_tree.commitment(),
949            single_level_tree.commitment(),
950            "Commitments should match after removal"
951        );
952
953        // Verify account is gone
954        assert_eq!(two_level_tree.num_leaves(), 0);
955        match two_level_tree.lookup(account).unwrap() {
956            LookupResult::NotFound(_) => {}, // Expected
957            _ => 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        // Perform many random operations
971        for i in 0..50 {
972            let op = rng.gen_range(0..3);
973
974            match op {
975                0 => {
976                    // Insert new account
977                    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                    // Update existing account
986                    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                    // Remove account
996                    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            // Verify commitments match after each operation
1006            assert_eq!(
1007                two_level_tree.commitment(),
1008                single_level_tree.commitment(),
1009                "Commitments should match after operation {}",
1010                i
1011            );
1012        }
1013
1014        // Final verification
1015        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        // Insert accounts across multiple partitions
1030        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        // Collect entries via get_entries
1038        let collected_entries: std::collections::HashMap<_, _> =
1039            tree.get_entries().unwrap().into_iter().collect();
1040
1041        // Verify all expected entries are present
1042        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}