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}