Skip to content

Commit 1889c65

Browse files
feat: retain proofs of non-target nodes in certain edge-cases. (#109)
* feat: retain proofs of non-target nodes in certain edge-cases. There are specific edge-cases where it's necessary to retain the proofs for nodes which aren't given in the target set, namely leaf removals which result in the deletion of a branch, and leaf additions which result in the creation of a branch. Documentation of each case is provided in the code at the point it is handled. This change will cause more proofs than are strictly necessary to be retained, because the `target` set we are given does not tell us if paths are added, updated or removed. This extra work is made up for by us not needing to fetch the nodes which would otherwise be missing later down the pipeline, and in benchmarking the overall change comes out very slightly faster. * Update src/proof/retainer.rs Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> * Fully track branch target children, to properly handle collapse case * Introduce AddedRemovedKeys, use that for trackinge extra proofs * debug * Don't track removed leaves as non-targets * Make AddedRemovedKeys into a concrete type * PR suggestion * Remove assume_added * Revert "Remove assume_added" This reverts commit 1e4be6f. * Fix unnecessary boolean being included * Add clear_added method * PR feedback --------- Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
1 parent a54766b commit 1889c65

File tree

5 files changed

+418
-27
lines changed

5 files changed

+418
-27
lines changed

src/hash_builder/mod.rs

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
use super::{
44
BranchNodeCompact, EMPTY_ROOT_HASH, Nibbles, TrieMask,
55
nodes::{BranchNodeRef, ExtensionNodeRef, LeafNodeRef},
6-
proof::ProofRetainer,
6+
proof::{ProofNodes, ProofRetainer},
77
};
8-
use crate::{HashMap, nodes::RlpNode, proof::ProofNodes};
8+
use crate::{HashMap, nodes::RlpNode, proof::AddedRemovedKeys};
99
use alloc::vec::Vec;
1010
use alloy_primitives::{B256, keccak256};
11-
use alloy_rlp::EMPTY_STRING_CODE;
1211
use core::cmp;
1312
use tracing::trace;
1413

@@ -39,9 +38,9 @@ pub use value::{HashBuilderValue, HashBuilderValueRef};
3938
/// up, combining the hashes of child nodes and ultimately generating the root hash. The root hash
4039
/// can then be used to verify the integrity and authenticity of the trie's data by constructing and
4140
/// verifying Merkle proofs.
42-
#[derive(Debug, Clone, Default)]
41+
#[derive(Debug, Clone)]
4342
#[allow(missing_docs)]
44-
pub struct HashBuilder {
43+
pub struct HashBuilder<K = AddedRemovedKeys> {
4544
pub key: Nibbles,
4645
pub value: HashBuilderValue,
4746
pub stack: Vec<RlpNode>,
@@ -53,12 +52,29 @@ pub struct HashBuilder {
5352
pub stored_in_database: bool,
5453

5554
pub updated_branch_nodes: Option<HashMap<Nibbles, BranchNodeCompact>>,
56-
pub proof_retainer: Option<ProofRetainer>,
55+
pub proof_retainer: Option<ProofRetainer<K>>,
5756

5857
pub rlp_buf: Vec<u8>,
5958
}
6059

61-
impl HashBuilder {
60+
impl Default for HashBuilder {
61+
fn default() -> Self {
62+
Self {
63+
key: Default::default(),
64+
value: Default::default(),
65+
stack: Default::default(),
66+
state_masks: Default::default(),
67+
tree_masks: Default::default(),
68+
hash_masks: Default::default(),
69+
stored_in_database: Default::default(),
70+
updated_branch_nodes: None,
71+
proof_retainer: None,
72+
rlp_buf: Default::default(),
73+
}
74+
}
75+
}
76+
77+
impl<K> HashBuilder<K> {
6278
/// Enables the Hash Builder to store updated branch nodes.
6379
///
6480
/// Call [HashBuilder::split] to get the updates to branch nodes.
@@ -68,9 +84,19 @@ impl HashBuilder {
6884
}
6985

7086
/// Enable specified proof retainer.
71-
pub fn with_proof_retainer(mut self, retainer: ProofRetainer) -> Self {
72-
self.proof_retainer = Some(retainer);
73-
self
87+
pub fn with_proof_retainer<K2>(self, retainer: ProofRetainer<K2>) -> HashBuilder<K2> {
88+
HashBuilder {
89+
key: self.key,
90+
value: self.value,
91+
stack: self.stack,
92+
state_masks: self.state_masks,
93+
tree_masks: self.tree_masks,
94+
hash_masks: self.hash_masks,
95+
stored_in_database: self.stored_in_database,
96+
updated_branch_nodes: self.updated_branch_nodes,
97+
proof_retainer: Some(retainer),
98+
rlp_buf: self.rlp_buf,
99+
}
74100
}
75101

76102
/// Enables the Hash Builder to store updated branch nodes.
@@ -81,7 +107,9 @@ impl HashBuilder {
81107
self.updated_branch_nodes = Some(HashMap::default());
82108
}
83109
}
110+
}
84111

112+
impl<K: AsRef<AddedRemovedKeys>> HashBuilder<K> {
85113
/// Splits the [HashBuilder] into a [HashBuilder] and hash builder updates.
86114
pub fn split(mut self) -> (Self, HashMap<Nibbles, BranchNodeCompact>) {
87115
let updates = self.updated_branch_nodes.take();
@@ -159,7 +187,7 @@ impl HashBuilder {
159187
let root = self.current_root();
160188
if root == EMPTY_ROOT_HASH {
161189
if let Some(proof_retainer) = self.proof_retainer.as_mut() {
162-
proof_retainer.retain(&Nibbles::default(), &[EMPTY_STRING_CODE])
190+
proof_retainer.retain_empty_root_proof();
163191
}
164192
}
165193
root
@@ -267,7 +295,10 @@ impl HashBuilder {
267295
"pushing leaf node",
268296
);
269297
self.stack.push(rlp);
270-
self.retain_proof_from_buf(&path);
298+
299+
if let Some(proof_retainer) = self.proof_retainer.as_mut() {
300+
proof_retainer.retain_leaf_proof(&path, &self.rlp_buf)
301+
}
271302
}
272303
HashBuilderValueRef::Hash(hash) => {
273304
trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash");
@@ -302,7 +333,11 @@ impl HashBuilder {
302333
"pushing extension node",
303334
);
304335
self.stack.push(rlp);
305-
self.retain_proof_from_buf(&path);
336+
337+
if let Some(proof_retainer) = self.proof_retainer.as_mut() {
338+
proof_retainer.retain_extension_proof(&path, &short_node_key, &self.rlp_buf)
339+
}
340+
306341
self.resize_masks(len_from);
307342
}
308343

@@ -368,7 +403,10 @@ impl HashBuilder {
368403
?rlp,
369404
"pushing branch node",
370405
);
371-
self.retain_proof_from_buf(&path);
406+
407+
if let Some(proof_retainer) = self.proof_retainer.as_mut() {
408+
proof_retainer.retain_branch_proof(&path, state_mask, &self.rlp_buf);
409+
}
372410

373411
// Clears the stack from the branch node elements
374412
let first_child_idx = self.stack.len() - state_mask.count_ones() as usize;
@@ -417,12 +455,6 @@ impl HashBuilder {
417455
}
418456
}
419457

420-
fn retain_proof_from_buf(&mut self, prefix: &Nibbles) {
421-
if let Some(proof_retainer) = self.proof_retainer.as_mut() {
422-
proof_retainer.retain(prefix, &self.rlp_buf)
423-
}
424-
}
425-
426458
fn update_masks(&mut self, current: &Nibbles, len_from: usize) {
427459
if len_from > 0 {
428460
let flag = TrieMask::from_nibble(current.get_unchecked(len_from - 1));

src/proof/added_removed_keys.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::{Nibbles, TrieMask};
2+
use alloy_primitives::{B256, map::B256Set};
3+
4+
/// Provides added and removed keys for an account or storage trie.
5+
///
6+
/// Used by the [`crate::proof::ProofRetainer`] to determine which nodes may be ancestors of newly
7+
/// added or removed leaves. This information allows for generation of more complete proofs which
8+
/// include the nodes necessary for adding and removing leaves from the trie.
9+
#[derive(Debug, Default, Clone)]
10+
pub struct AddedRemovedKeys {
11+
/// Keys which are known to be removed from the trie.
12+
removed_keys: B256Set,
13+
/// Keys which are known to be added to the trie.
14+
added_keys: B256Set,
15+
/// Assume that all keys have been added.
16+
assume_added: bool,
17+
}
18+
19+
impl AsRef<Self> for AddedRemovedKeys {
20+
fn as_ref(&self) -> &Self {
21+
self
22+
}
23+
}
24+
25+
impl AddedRemovedKeys {
26+
/// Sets the `assume_added` flag, which can be used instead of `insert_added` if exact
27+
/// additions aren't known and you want to optimistically collect all proofs which _might_ be
28+
/// necessary.
29+
pub fn with_assume_added(mut self, assume_added: bool) -> Self {
30+
self.assume_added = assume_added;
31+
self
32+
}
33+
34+
/// Sets the key as being a removed key. This removes the key from the `added_keys` set if it
35+
/// was previously inserted into it.
36+
pub fn insert_removed(&mut self, key: B256) {
37+
self.added_keys.remove(&key);
38+
self.removed_keys.insert(key);
39+
}
40+
41+
/// Unsets the key as being a removed key.
42+
pub fn remove_removed(&mut self, key: &B256) {
43+
self.removed_keys.remove(key);
44+
}
45+
46+
/// Sets the key as being an added key. This removes the key from the `removed_keys` set if it
47+
/// was previously inserted into it.
48+
pub fn insert_added(&mut self, key: B256) {
49+
self.removed_keys.remove(&key);
50+
self.added_keys.insert(key);
51+
}
52+
53+
/// Clears all keys which have been added via `insert_added`.
54+
pub fn clear_added(&mut self) {
55+
self.added_keys.clear();
56+
}
57+
58+
/// Returns true if the given key path is marked as removed.
59+
pub fn is_removed(&self, path: &B256) -> bool {
60+
self.removed_keys.contains(path)
61+
}
62+
63+
/// Returns true if the given key path is marked as added.
64+
pub fn is_added(&self, path: &B256) -> bool {
65+
self.assume_added || self.added_keys.contains(path)
66+
}
67+
68+
/// Returns true if the given path prefix is the prefix of an added key.
69+
pub fn is_prefix_added(&self, prefix: &Nibbles) -> bool {
70+
self.assume_added
71+
|| self.added_keys.iter().any(|key| {
72+
let key_nibbles = Nibbles::unpack(key);
73+
key_nibbles.starts_with(prefix)
74+
})
75+
}
76+
77+
/// Returns a mask containing a bit set for each child of the branch which is a prefix of a
78+
/// removed leaf.
79+
pub fn get_removed_mask(&self, branch_path: &Nibbles) -> TrieMask {
80+
let mut mask = TrieMask::default();
81+
for key in &self.removed_keys {
82+
let key_nibbles = Nibbles::unpack(key);
83+
if key_nibbles.starts_with(branch_path) {
84+
let child_bit = key_nibbles.get_unchecked(branch_path.len());
85+
mask.set_bit(child_bit);
86+
}
87+
}
88+
mask
89+
}
90+
}

src/proof/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ pub use proof_nodes::ProofNodes;
2020

2121
mod retainer;
2222
pub use retainer::ProofRetainer;
23+
24+
mod added_removed_keys;
25+
pub use added_removed_keys::AddedRemovedKeys;

0 commit comments

Comments
 (0)