espresso_types/v0/reward_mt/
mod.rs

1//! Two-level Merkle tree for reward account balances.
2//!
3//! Uses an outer tree (4 bits, 16 partitions) of inner trees (156 bits each) for efficient
4//! storage and retrieval across a 160-bit address space.
5//!
6//! # Storage Implementations
7//!
8//! - [`storage::CachedInMemoryStorage`] - In-memory with cache (default)
9//!
10//! # Example
11//!
12//! ```rust,ignore
13//! let mut tree = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
14//! tree.update(&account, &amount)?;
15//! let result = tree.lookup(&account);
16//! ```
17
18use std::{borrow::Borrow, sync::Arc};
19
20use jf_merkle_tree_compat::{
21    prelude::{MerkleNode, MerkleProof},
22    universal_merkle_tree::UniversalMerkleTree,
23    DigestAlgorithm, ForgetableMerkleTreeScheme, LookupResult, MerkleCommitment,
24    MerkleTreeCommitment, MerkleTreeError, MerkleTreeScheme, UniversalMerkleTreeScheme,
25};
26use serde::{Deserialize, Serialize};
27use sha3::{Digest as _, Keccak256};
28
29use crate::{
30    reward_mt::storage::{OuterIndex, RewardMerkleTreeStorage},
31    sparse_mt::Keccak256Hasher,
32    v0::{sparse_mt::KeccakNode, v0_3::RewardAmount, v0_4::RewardAccountV2},
33};
34
35pub mod storage;
36
37/// Total height of the reward Merkle tree (160 bits = Ethereum address space)
38pub const REWARD_MERKLE_TREE_V2_HEIGHT: usize = 160;
39
40/// Arity of the Merkle tree (binary tree)
41pub const REWARD_MERKLE_TREE_V2_ARITY: usize = 2;
42
43/// Two-level reward Merkle tree with pluggable storage backend.
44///
45/// # Architecture
46///
47/// This data structure implements a 160-bit Merkle tree (matching Ethereum's address space)
48/// using a two-level approach for efficient storage and retrieval:
49///
50/// - **Outer tree**: 4-bit tree (16 partitions) that stores the roots of inner trees
51/// - **Inner trees**: 16 separate 156-bit trees, one for each partition
52///
53/// The outer index is derived from the first 4 bits (most significant nibble) of an
54/// Ethereum address, allowing accounts to be distributed across 16 partitions.
55///
56/// # Storage Backend
57///
58/// The storage backend determines how inner tree roots are persisted:
59/// - [`storage::CachedInMemoryStorage`]: Fast in-memory storage with single-entry cache
60/// - [`fs_storage::RewardMerkleTreeFSStorage`]: File system backed, survives restarts
61///
62/// # Benefits
63///
64/// - **Sparse storage**: Only non-empty partitions consume storage
65/// - **Efficient lookups**: Operations only need to load one inner tree at a time
66/// - **Parallelization**: Different partitions can be processed independently
67/// - **Solidity compatibility**: Hashing matches on-chain verification
68///
69/// # Example
70///
71/// ```rust,ignore
72/// let mut tree = InMemoryRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
73/// tree.update(&account, &amount)?;
74/// let LookupResult::Ok(balance, proof) = tree.lookup(&account)?;
75/// ```
76#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
77pub struct StorageBackedRewardMerkleTreeV2<S: RewardMerkleTreeStorage> {
78    /// Outer tree storing roots of 16 inner trees (4-bit indexed)
79    outer: OuterRewardMerkleTreeV2,
80    /// Total number of accounts with non-zero balances across all inner trees
81    num_leaves: u64,
82    /// Storage backend for persisting inner tree roots
83    storage: S,
84}
85
86// Manual Hash implementation that only hashes the outer tree
87// The storage is an implementation detail and doesn't affect logical equality
88impl<S: RewardMerkleTreeStorage> std::hash::Hash for StorageBackedRewardMerkleTreeV2<S> {
89    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
90        self.outer.hash(state);
91    }
92}
93
94/// Merkle tree commitment (root hash).
95///
96/// Contains the Keccak256 digest of the tree root along with metadata
97/// about tree height and number of leaves. Used for efficient verification.
98pub type RewardMerkleCommitmentV2 = MerkleTreeCommitment<KeccakNode>;
99
100/// Membership proof for an account's balance in the tree.
101///
102/// Proves that a specific account has a specific balance at a given tree root.
103/// Contains the merkle path from leaf to root with all sibling hashes needed
104/// for verification. This proof can be verified on-chain in Solidity.
105pub type RewardMerkleProof =
106    MerkleProof<RewardAmount, RewardAccountV2, KeccakNode, REWARD_MERKLE_TREE_V2_ARITY>;
107
108/// Non-membership proof for an account not in the tree.
109///
110/// Proves that a specific account does NOT exist in the tree. In a universal
111/// Merkle tree, this is structurally identical to a membership proof, showing
112/// the path to where the account would be if it existed (an empty position).
113pub type RewardNonMembershipProof = RewardMerkleProof;
114
115/// Membership proof for an inner tree root in the outer tree.
116///
117/// Internal type used when patching inner proofs with outer proofs. Proves
118/// that a specific inner tree root exists at a specific partition index.
119type OuterRewardMerkleProof =
120    MerkleProof<KeccakNode, OuterIndex, KeccakNode, REWARD_MERKLE_TREE_V2_ARITY>;
121
122/// Merkle node containing a reward amount leaf.
123///
124/// Internal representation of tree nodes. Can be Empty, Leaf (account + amount),
125/// Branch (internal node with children), or ForgettenSubtree (sparse placeholder).
126type RewardMerkleNode = MerkleNode<RewardAmount, RewardAccountV2, KeccakNode>;
127
128/// Height of outer tree (4 bits = 16 partitions)
129const REWARD_MERKLE_TREE_V2_OUTER_HEIGHT: usize = 4;
130
131/// Height of each inner tree (156 bits)
132const REWARD_MERKLE_TREE_V2_INNER_HEIGHT: usize =
133    REWARD_MERKLE_TREE_V2_HEIGHT - REWARD_MERKLE_TREE_V2_OUTER_HEIGHT;
134
135/// Inner tree type: 156-bit universal Merkle tree for account balances.
136///
137/// Each inner tree corresponds to one of the 16 partitions (indexed by the first
138/// 4 bits of the account address). Stores reward amounts keyed by full account addresses.
139/// Uses standard Keccak256 hashing for both leaves and internal nodes.
140type InnerRewardMerkleTreeV2 = UniversalMerkleTree<
141    RewardAmount,
142    Keccak256Hasher,
143    RewardAccountV2,
144    REWARD_MERKLE_TREE_V2_ARITY,
145    KeccakNode,
146>;
147
148/// Expected type alias for single-level reward Merkle tree.
149///
150/// Used in tests and comparisons to verify that the two-level implementation
151/// produces identical commitments to a single-level tree.
152pub type ExpectedRewardMerkleTreeV2 = InnerRewardMerkleTreeV2;
153
154/// Outer tree type: 4-bit universal Merkle tree storing inner tree roots.
155///
156/// Contains up to 16 entries (2^4), each storing the root hash of one inner tree.
157/// Uses a custom hasher (OuterKeccak256Hasher) that treats leaves as pre-hashed values.
158type OuterRewardMerkleTreeV2 = UniversalMerkleTree<
159    KeccakNode,
160    OuterKeccak256Hasher,
161    OuterIndex,
162    REWARD_MERKLE_TREE_V2_ARITY,
163    KeccakNode,
164>;
165
166/// Keccak256 hasher for outer tree nodes.
167///
168/// Implements Solidity-compatible hashing with special handling for leaves:
169///
170/// - **Leaf nodes**: Pass-through (no additional hashing) because inner tree roots
171///   are already Keccak256 hashes. Adding another hash layer would break compatibility.
172/// - **Internal nodes**: Standard `keccak256(left || right)` of concatenated children,
173///   matching Solidity's abi.encodePacked + keccak256.
174///
175/// This ensures that proofs generated here can be verified on-chain using
176/// the RewardClaim contract's merkle verification logic.
177#[derive(Clone, Debug, Hash, PartialEq, Eq)]
178pub struct OuterKeccak256Hasher;
179
180impl DigestAlgorithm<KeccakNode, OuterIndex, KeccakNode> for OuterKeccak256Hasher {
181    /// Hash internal nodes by concatenating children and applying Keccak256.
182    ///
183    /// Implements `keccak256(child[0] || child[1] || ... || child[n])` matching
184    /// Solidity's `keccak256(abi.encodePacked(...))`.
185    ///
186    /// # Arguments
187    /// * `data` - Slice of child node hashes to combine
188    ///
189    /// # Returns
190    /// The Keccak256 hash of the concatenated children
191    fn digest(data: &[KeccakNode]) -> Result<KeccakNode, jf_merkle_tree_compat::MerkleTreeError> {
192        let mut hasher = Keccak256::new();
193
194        // Concatenate all child hashes without domain separator
195        for node in data {
196            hasher.update(node.as_ref());
197        }
198
199        let result = hasher.finalize();
200        Ok(KeccakNode(result.into()))
201    }
202
203    /// Pass-through for leaf nodes (inner tree roots are already hashed).
204    ///
205    /// Unlike typical Merkle trees where leaves are hashed, the outer tree's
206    /// "leaves" are inner tree roots that are already Keccak256 hashes.
207    /// Hashing them again would add an unnecessary layer and break Solidity
208    /// verification compatibility.
209    ///
210    /// # Arguments
211    /// * `_pos` - Outer index (0-15), unused
212    /// * `elem` - Inner tree root hash (already a Keccak256 digest)
213    ///
214    /// # Returns
215    /// The input hash unchanged
216    fn digest_leaf(
217        _pos: &OuterIndex,
218        elem: &KeccakNode,
219    ) -> Result<KeccakNode, jf_merkle_tree_compat::MerkleTreeError> {
220        Ok(*elem)
221    }
222}
223
224impl<S: RewardMerkleTreeStorage> StorageBackedRewardMerkleTreeV2<S> {
225    /// Create a new empty tree with the given storage backend
226    pub fn new_with_storage(storage: S) -> Self {
227        Self {
228            outer: OuterRewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_OUTER_HEIGHT),
229            num_leaves: 0,
230            storage,
231        }
232    }
233
234    /// Reconstruct tree from a commitment (root hash) with given storage backend.
235    ///
236    /// Creates a sparse tree that can be populated with remember operations.
237    pub fn from_commitment_with_storage(
238        commitment: impl Borrow<RewardMerkleCommitmentV2>,
239        storage: S,
240    ) -> Self {
241        let num_leaves = commitment.borrow().size();
242        Self {
243            outer: OuterRewardMerkleTreeV2::from_commitment(commitment),
244            num_leaves,
245            storage,
246        }
247    }
248
249    /// Build tree from key-value pairs with given storage backend.
250    ///
251    /// # Arguments
252    /// * `height` - Must be [`REWARD_MERKLE_TREE_V2_HEIGHT`] (160)
253    /// * `data` - Iterator of (account, amount) pairs
254    /// * `storage` - Storage backend for inner tree roots
255    pub fn from_kv_set_with_storage<BI, BE>(
256        height: usize,
257        data: impl IntoIterator<Item = impl Borrow<(BI, BE)>>,
258        storage: S,
259    ) -> anyhow::Result<Self>
260    where
261        BI: Borrow<RewardAccountV2>,
262        BE: Borrow<RewardAmount>,
263    {
264        assert_eq!(
265            height, REWARD_MERKLE_TREE_V2_HEIGHT,
266            "Height must be {}",
267            REWARD_MERKLE_TREE_V2_HEIGHT
268        );
269        let mut mt = Self::new_with_storage(storage);
270        // Sort the data by account for cache efficiency
271        let mut collected: Vec<_> = data
272            .into_iter()
273            .map(|tuple| {
274                let (account, amount) = tuple.borrow();
275                (*account.borrow(), *amount.borrow())
276            })
277            .collect();
278        collected.sort();
279        for (key, value) in collected {
280            Self::update(&mut mt, key.borrow(), value.borrow())?;
281        }
282        Ok(mt)
283    }
284
285    /// Merge inner tree proof with outer tree proof into a single 160-bit proof.
286    ///
287    /// # Two-Level Proof Construction
288    ///
289    /// The two-level tree generates proofs in two stages:
290    /// 1. **Inner proof** (156 bits): Proves account balance within its partition
291    /// 2. **Outer proof** (4 bits): Proves partition root in outer tree
292    ///
293    /// This method combines them into a single 160-bit proof that can be verified
294    /// against the tree's root commitment, matching the format expected by Solidity.
295    ///
296    /// # Implementation Details
297    ///
298    /// - Skips the first outer proof node (it's the inner tree root, already in inner proof)
299    /// - Appends remaining outer proof nodes to the inner proof path
300    /// - Converts outer tree nodes to inner tree node format (ForgettenSubtree placeholders)
301    ///
302    /// # Arguments
303    /// * `proof` - Inner proof (156-bit) to extend (modified in place)
304    /// * `outer_proof` - Outer proof (4-bit) to append
305    fn patch_membership_proof(
306        proof: &mut RewardMerkleProof,
307        outer_proof: &OuterRewardMerkleProof,
308    ) -> anyhow::Result<()> {
309        for node in &outer_proof.proof[1..] {
310            match node {
311                MerkleNode::Branch { value, children } => proof.proof.push(MerkleNode::Branch {
312                    value: *value,
313                    children: children
314                        .iter()
315                        .map(|node| {
316                            Arc::new(if matches!(**node, MerkleNode::Empty) {
317                                RewardMerkleNode::Empty
318                            } else {
319                                RewardMerkleNode::ForgettenSubtree {
320                                    value: node.value(),
321                                }
322                            })
323                        })
324                        .collect::<Vec<_>>(),
325                }),
326                MerkleNode::Empty => proof.proof.push(RewardMerkleNode::Empty),
327                _ => {
328                    return Err(anyhow::anyhow!(
329                        "Outer merkle tree proof contains invalid node type: expected Branch or \
330                         Empty, found {:?}",
331                        node
332                    ));
333                },
334            }
335        }
336        Ok(())
337    }
338}
339
340/// Two-level reward Merkle tree with in-memory storage (default).
341///
342/// Fast, non-persistent storage suitable for:
343/// - Validators that don't store full reward state
344/// - Testing and development
345/// - Reconstructing state from block headers + catchup
346///
347/// Uses [`storage::CachedInMemoryStorage`] with single-entry cache for efficiency.
348pub type InMemoryRewardMerkleTreeV2 =
349    StorageBackedRewardMerkleTreeV2<storage::CachedInMemoryStorage>;
350
351/// Canonical reward Merkle tree type (single-level, 160-bit).
352///
353/// This is the "reference" implementation used for:
354/// - Testing two-level tree correctness (commitments must match)
355/// - Understanding the logical tree structure
356/// - Generating test vectors
357///
358/// Production code uses [`InMemoryRewardMerkleTreeV2`] for better performance and storage
359/// efficiency.
360pub type RewardMerkleTreeV2 = InnerRewardMerkleTreeV2;
361
362// Convenience methods for the default cached storage implementation
363impl InMemoryRewardMerkleTreeV2 {
364    /// Create a new empty tree with default in-memory storage
365    ///
366    /// # Arguments
367    /// * `height` - Must be [`REWARD_MERKLE_TREE_V2_HEIGHT`] (160)
368    pub fn new(height: usize) -> Self {
369        assert_eq!(
370            height, REWARD_MERKLE_TREE_V2_HEIGHT,
371            "Height must be {}",
372            REWARD_MERKLE_TREE_V2_HEIGHT
373        );
374        Self::new_with_storage(storage::CachedInMemoryStorage::new())
375    }
376
377    /// Reconstruct tree from a commitment with default in-memory storage
378    ///
379    /// Creates a sparse tree that can be populated with remember operations.
380    pub fn from_commitment(commitment: impl Borrow<RewardMerkleCommitmentV2>) -> Self {
381        Self::from_commitment_with_storage(commitment, storage::CachedInMemoryStorage::new())
382    }
383
384    /// Build tree from key-value pairs with default in-memory storage
385    ///
386    /// Build a universal merkle tree from a key-value set.
387    /// * `height` - height of the merkle tree (must be REWARD_MERKLE_TREE_V2_HEIGHT = 160)
388    /// * `data` - an iterator of key-value pairs. Could be a hashmap or simply
389    ///   an array or a slice of (key, value) pairs
390    pub fn from_kv_set<BI, BE>(
391        height: usize,
392        data: impl IntoIterator<Item = impl Borrow<(BI, BE)>>,
393    ) -> anyhow::Result<Self>
394    where
395        BI: Borrow<RewardAccountV2>,
396        BE: Borrow<RewardAmount>,
397    {
398        Self::from_kv_set_with_storage(height, data, storage::CachedInMemoryStorage::new())
399    }
400}
401
402/// Verification result for merkle proofs.
403///
404/// - `Ok(())` - Proof is valid, account exists with claimed balance
405/// - `Err(())` - Proof is invalid (wrong proof data or commitment)
406///
407/// Used by [`StorageBackedRewardMerkleTreeV2::verify`] to check membership proofs.
408pub type VerificationResult = Result<(), ()>;
409
410/// Core Merkle tree operations
411impl<S: RewardMerkleTreeStorage> StorageBackedRewardMerkleTreeV2<S> {
412    /// Tree arity (binary tree)
413    pub const ARITY: usize = REWARD_MERKLE_TREE_V2_ARITY;
414
415    /// Returns the tree height (160 bits for Ethereum address space)
416    pub fn height(&self) -> usize {
417        REWARD_MERKLE_TREE_V2_HEIGHT
418    }
419
420    /// Returns the tree capacity (2^160 possible accounts)
421    pub fn capacity(&self) -> bigdecimal::num_bigint::BigUint {
422        bigdecimal::num_bigint::BigUint::from_slice(&[2]).pow(160)
423    }
424
425    /// Returns the number of accounts with non-zero balances
426    pub fn num_leaves(&self) -> u64 {
427        self.num_leaves
428    }
429
430    /// Returns the root commitment (hash) of the tree
431    pub fn commitment(&self) -> RewardMerkleCommitmentV2 {
432        MerkleTreeCommitment::new(
433            self.outer.commitment().digest(),
434            REWARD_MERKLE_TREE_V2_HEIGHT,
435            self.num_leaves,
436        )
437    }
438
439    /// Look up an account's balance and generate a membership proof
440    ///
441    /// Returns the balance and proof if the account exists, NotFound otherwise.
442    ///
443    /// # Errors
444    /// Returns storage error if IO operation fails.
445    pub fn lookup(
446        &self,
447        index: impl Borrow<RewardAccountV2>,
448    ) -> anyhow::Result<LookupResult<RewardAmount, RewardMerkleProof, ()>> {
449        let outer_index = OuterIndex::new(index.borrow());
450        let outer_proof = match self.outer.lookup(outer_index) {
451            LookupResult::Ok(_, proof) => proof,
452            LookupResult::NotInMemory => {
453                return Err(anyhow::anyhow!(
454                    "Never should happen: outer reward merkle tree not in memory"
455                ));
456            },
457            LookupResult::NotFound(_) => return Ok(LookupResult::NotFound(())),
458        };
459        match self.storage.lookup(index)? {
460            LookupResult::Ok(value, mut proof) => {
461                Self::patch_membership_proof(&mut proof, &outer_proof)?;
462                Ok(LookupResult::Ok(value, proof))
463            },
464            LookupResult::NotInMemory => Ok(LookupResult::NotInMemory),
465            LookupResult::NotFound(_) => Ok(LookupResult::NotFound(())),
466        }
467    }
468
469    /// Verify a membership proof against a root commitment
470    ///
471    /// Returns Ok(Ok(())) if proof is valid, Ok(Err(())) if invalid.
472    pub fn verify(
473        root: impl Borrow<RewardMerkleCommitmentV2>,
474        index: impl Borrow<RewardAccountV2>,
475        proof: impl Borrow<RewardMerkleProof>,
476    ) -> Result<VerificationResult, MerkleTreeError> {
477        InnerRewardMerkleTreeV2::verify(root, index, proof)
478    }
479
480    /// Update an account's balance using a custom function
481    ///
482    /// The function receives the current balance (if any) and returns the new balance.
483    /// Returning None removes the account. Updates both inner and outer trees.
484    ///
485    /// # Errors
486    /// If the merkle tree update fails.
487    pub fn update_with<F>(
488        &mut self,
489        pos: impl Borrow<RewardAccountV2>,
490        f: F,
491    ) -> anyhow::Result<LookupResult<RewardAmount, (), ()>>
492    where
493        F: FnOnce(Option<&RewardAmount>) -> Option<RewardAmount>,
494    {
495        let outer_index = OuterIndex::new(pos.borrow());
496        let (result, delta, root) = self.storage.update_with(pos, f)?;
497        self.num_leaves = (self.num_leaves as i64 + delta) as u64;
498        if root == KeccakNode::default() {
499            self.outer.update_with(outer_index, |_| None)?;
500        } else {
501            self.outer.update(outer_index, root)?;
502        }
503        Ok(result)
504    }
505
506    /// Convenience method to update an account's balance directly
507    ///
508    /// # Errors
509    /// Returns `MerkleTreeError` if the merkle tree update fails.
510    /// Panics if storage operation fails.
511    pub fn update(
512        &mut self,
513        pos: impl Borrow<RewardAccountV2>,
514        value: impl Borrow<RewardAmount>,
515    ) -> anyhow::Result<LookupResult<RewardAmount, (), ()>> {
516        self.update_with(pos, |_| Some(*value.borrow()))
517    }
518
519    /// Look up an account and generate proof (membership or non-membership)
520    ///
521    /// Returns membership proof if account exists, non-membership proof otherwise.
522    ///
523    /// # Errors
524    /// Returns storage error if IO operation fails.
525    pub fn universal_lookup(
526        &self,
527        index: impl Borrow<RewardAccountV2>,
528    ) -> anyhow::Result<LookupResult<RewardAmount, RewardMerkleProof, RewardMerkleProof>> {
529        let index = index.borrow();
530        let outer_index = OuterIndex::new(index);
531        let outer_proof = match self.outer.universal_lookup(outer_index) {
532            LookupResult::Ok(_, proof) => proof,
533            LookupResult::NotInMemory => {
534                return Err(anyhow::anyhow!(
535                    "Never should happen: outer reward merkle tree not in memory"
536                ));
537            },
538            LookupResult::NotFound(outer_proof) => {
539                let mut proof = RewardMerkleProof::new(
540                    *index,
541                    vec![MerkleNode::Empty; REWARD_MERKLE_TREE_V2_INNER_HEIGHT + 1],
542                );
543                Self::patch_membership_proof(&mut proof, &outer_proof)?;
544                return Ok(LookupResult::NotFound(proof));
545            },
546        };
547        match self.storage.lookup(index)? {
548            LookupResult::Ok(value, mut proof) => {
549                Self::patch_membership_proof(&mut proof, &outer_proof)?;
550                Ok(LookupResult::Ok(value, proof))
551            },
552            LookupResult::NotInMemory => Ok(LookupResult::NotInMemory),
553            LookupResult::NotFound(mut proof) => {
554                Self::patch_membership_proof(&mut proof, &outer_proof)?;
555                Ok(LookupResult::NotFound(proof))
556            },
557        }
558    }
559
560    /// Verify a non-membership proof against a root commitment
561    ///
562    /// Returns true if proof is valid (account doesn't exist), false otherwise.
563    pub fn non_membership_verify(
564        root: impl Borrow<RewardMerkleCommitmentV2>,
565        index: impl Borrow<RewardAccountV2>,
566        proof: impl Borrow<RewardMerkleProof>,
567    ) -> Result<bool, MerkleTreeError> {
568        InnerRewardMerkleTreeV2::non_membership_verify(root, index, proof)
569    }
570
571    /// Remove an account from memory (not yet implemented, currently just performs lookup)
572    ///
573    /// Returns the account's balance and proof if it exists.
574    ///
575    /// # Errors
576    /// Returns storage error if IO operation fails.
577    pub fn forget(
578        &mut self,
579        index: impl Borrow<RewardAccountV2>,
580    ) -> anyhow::Result<LookupResult<RewardAmount, RewardMerkleProof, ()>> {
581        self.lookup(index)
582    }
583
584    /// Restore an account to memory using a proof (currently a no-op)
585    ///
586    /// Restores an account that was previously forgotten. Currently does nothing.
587    pub fn remember(
588        &mut self,
589        _pos: impl Borrow<RewardAccountV2>,
590        _element: impl Borrow<RewardAmount>,
591        _proof: impl Borrow<RewardMerkleProof>,
592    ) -> anyhow::Result<()> {
593        Ok(())
594    }
595
596    /// Remove an account and generate proof (membership or non-membership)
597    ///
598    /// Currently just performs universal_lookup without actually removing from memory.
599    ///
600    /// # Errors
601    /// Returns storage error if IO operation fails.
602    pub fn universal_forget(
603        &mut self,
604        pos: RewardAccountV2,
605    ) -> anyhow::Result<LookupResult<RewardAmount, RewardMerkleProof, RewardMerkleProof>> {
606        self.universal_lookup(pos)
607    }
608
609    /// Restore non-membership information using a proof (currently a no-op)
610    ///
611    /// Restores non-membership information for an account. Currently does nothing.
612    pub fn non_membership_remember(
613        &mut self,
614        _pos: RewardAccountV2,
615        _proof: impl Borrow<RewardMerkleProof>,
616    ) -> anyhow::Result<()> {
617        Ok(())
618    }
619
620    /// Consume the tree and return all (account, balance) pairs.
621    ///
622    /// Reads flat entry lists directly from the storage backend, bypassing Merkle
623    /// tree construction. Only the cached partition (at most 1) requires a tree
624    /// traversal; the remaining partitions are read as flat entry lists.
625    ///
626    /// # Errors
627    /// Returns a storage error if reading entries from the backend fails.
628    ///
629    /// # Example
630    ///
631    /// ```rust,ignore
632    /// let tree = InMemoryRewardMerkleTreeV2::from_kv_set(height, accounts)?;
633    /// for (account, balance) in tree.get_entries()? {
634    ///     println!("{:?}: {}", account, balance);
635    /// }
636    /// ```
637    pub fn get_entries(self) -> anyhow::Result<Vec<(RewardAccountV2, RewardAmount)>> {
638        Ok(self.storage.get_entries()?)
639    }
640}