Skip to content

Commit 0e55293

Browse files
NashtareBGluth
andauthored
Add collapse strategy to PartialTrie variants (#501)
* Add collapse strategy to PartialTrie variants * Fix typo Co-authored-by: BGluth <[email protected]> * Change terminology * Rename * Nit --------- Co-authored-by: BGluth <[email protected]>
1 parent e7e83de commit 0e55293

File tree

5 files changed

+124
-41
lines changed

5 files changed

+124
-41
lines changed

mpt_trie/src/partial_trie.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ pub trait PartialTrie:
4949
/// Creates a new partial trie from a node.
5050
fn new(n: Node<Self>) -> Self;
5151

52+
/// Creates a new partial trie from a node with a provided collapse
53+
/// strategy.
54+
fn new_with_strategy(n: Node<Self>, strategy: OnOrphanedHashNode) -> Self;
55+
5256
/// Inserts a node into the trie.
5357
fn insert<K, V>(&mut self, k: K, v: V) -> TrieOpResult<()>
5458
where
@@ -211,6 +215,10 @@ impl PartialTrie for StandardTrie {
211215
Self(n)
212216
}
213217

218+
fn new_with_strategy(n: Node<Self>, _strategy: OnOrphanedHashNode) -> Self {
219+
Self(n)
220+
}
221+
214222
fn insert<K, V>(&mut self, k: K, v: V) -> TrieOpResult<()>
215223
where
216224
K: Into<Nibbles>,
@@ -240,7 +248,7 @@ impl PartialTrie for StandardTrie {
240248
where
241249
K: Into<Nibbles>,
242250
{
243-
self.0.trie_delete(k)
251+
self.0.trie_delete(k, OnOrphanedHashNode::Reject)
244252
}
245253

246254
fn hash(&self) -> H256 {
@@ -304,6 +312,23 @@ where
304312
pub struct HashedPartialTrie {
305313
pub(crate) node: Node<HashedPartialTrie>,
306314
pub(crate) hash: Arc<RwLock<Option<H256>>>,
315+
316+
pub(crate) strategy: OnOrphanedHashNode,
317+
}
318+
319+
/// How to handle the following subtree on deletion of the indicated node.
320+
/// ```text
321+
/// BranchNode
322+
/// / \
323+
/// DeleteMe OrphanedHashNode
324+
/// ```
325+
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
326+
pub enum OnOrphanedHashNode {
327+
/// Replace `BranchNode` with an appropriate `ExtensionNode`
328+
CollapseToExtension,
329+
/// Return an error.
330+
#[default]
331+
Reject,
307332
}
308333

309334
impl_from_for_trie_type!(HashedPartialTrie);
@@ -329,6 +354,15 @@ impl PartialTrie for HashedPartialTrie {
329354
Self {
330355
node,
331356
hash: Arc::new(RwLock::new(None)),
357+
strategy: OnOrphanedHashNode::default(),
358+
}
359+
}
360+
361+
fn new_with_strategy(node: Node<Self>, strategy: OnOrphanedHashNode) -> Self {
362+
Self {
363+
node,
364+
hash: Arc::new(RwLock::new(None)),
365+
strategy,
332366
}
333367
}
334368

@@ -364,7 +398,7 @@ impl PartialTrie for HashedPartialTrie {
364398
where
365399
K: Into<crate::nibbles::Nibbles>,
366400
{
367-
let res = self.node.trie_delete(k);
401+
let res = self.node.trie_delete(k, self.strategy);
368402
self.set_hash(None);
369403

370404
res

mpt_trie/src/trie_ops.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use thiserror::Error;
1010

1111
use crate::{
1212
nibbles::{Nibble, Nibbles},
13-
partial_trie::{Node, PartialTrie, WrappedNode},
13+
partial_trie::{Node, OnOrphanedHashNode, PartialTrie, WrappedNode},
1414
utils::TrieNodeType,
1515
};
1616

@@ -378,23 +378,31 @@ impl<T: PartialTrie> Node<T> {
378378
/// If the key exists, then the existing node value that was deleted is
379379
/// returned. Otherwise, if the key is not present, then `None` is returned
380380
/// instead.
381-
pub(crate) fn trie_delete<K>(&mut self, k: K) -> TrieOpResult<Option<Vec<u8>>>
381+
pub(crate) fn trie_delete<K>(
382+
&mut self,
383+
k: K,
384+
strategy: OnOrphanedHashNode,
385+
) -> TrieOpResult<Option<Vec<u8>>>
382386
where
383387
K: Into<Nibbles>,
384388
{
385389
let k: Nibbles = k.into();
386390
trace!("Deleting a leaf node with key {} if it exists", k);
387391

388-
delete_intern(&self.clone(), k)?.map_or(Ok(None), |(updated_root, deleted_val)| {
389-
// Final check at the root if we have an extension node. While this check also
390-
// exists as we recursively traverse down the trie, it can not perform this
391-
// check on the root node.
392-
let wrapped_node = try_collapse_if_extension(updated_root, &Nibbles::default())?;
393-
let node_ref: &Node<T> = &wrapped_node;
394-
*self = node_ref.clone();
395-
396-
Ok(Some(deleted_val))
397-
})
392+
delete_intern(&self.clone(), k, strategy)?.map_or(
393+
Ok(None),
394+
|(updated_root, deleted_val)| {
395+
// Final check at the root if we have an extension node. While this check also
396+
// exists as we recursively traverse down the trie, it can not perform this
397+
// check on the root node.
398+
let wrapped_node =
399+
try_collapse_if_extension(updated_root, &Nibbles::default(), strategy)?;
400+
let node_ref: &Node<T> = &wrapped_node;
401+
*self = node_ref.clone();
402+
403+
Ok(Some(deleted_val))
404+
},
405+
)
398406
}
399407

400408
pub(crate) fn trie_items(&self) -> impl Iterator<Item = (Nibbles, ValOrHash)> {
@@ -516,6 +524,7 @@ fn insert_into_trie_rec<N: PartialTrie>(
516524
fn delete_intern<N: PartialTrie>(
517525
node: &Node<N>,
518526
mut curr_k: Nibbles,
527+
strategy: OnOrphanedHashNode,
519528
) -> TrieOpResult<Option<(WrappedNode<N>, Vec<u8>)>> {
520529
match node {
521530
Node::Empty => {
@@ -532,7 +541,7 @@ fn delete_intern<N: PartialTrie>(
532541
let nibble = curr_k.pop_next_nibble_front();
533542
trace!("Delete traversed Branch nibble {:x}", nibble);
534543

535-
delete_intern(&children[nibble as usize], curr_k)?.map_or(Ok(None),
544+
delete_intern(&children[nibble as usize], curr_k, strategy)?.map_or(Ok(None),
536545
|(updated_child, value_deleted)| {
537546
// If the child we recursively called is deleted, then we may need to reduce
538547
// this branch to an extension/leaf.
@@ -544,7 +553,7 @@ fn delete_intern<N: PartialTrie>(
544553

545554
let mut updated_children = children.clone();
546555
updated_children[nibble as usize] =
547-
try_collapse_if_extension(updated_child, &curr_k)?;
556+
try_collapse_if_extension(updated_child, &curr_k, strategy)?;
548557
branch(updated_children, value.clone())
549558
}
550559
true => {
@@ -579,10 +588,14 @@ fn delete_intern<N: PartialTrie>(
579588
.then(|| {
580589
curr_k.truncate_n_nibbles_front_mut(ext_nibbles.count);
581590

582-
delete_intern(child, curr_k).and_then(|res| {
591+
delete_intern(child, curr_k, strategy).and_then(|res| {
583592
res.map_or(Ok(None), |(updated_child, value_deleted)| {
584-
let updated_node =
585-
collapse_ext_node_if_needed(ext_nibbles, &updated_child, &curr_k)?;
593+
let updated_node = collapse_ext_node_if_needed(
594+
ext_nibbles,
595+
&updated_child,
596+
&curr_k,
597+
strategy,
598+
)?;
586599
Ok(Some((updated_node, value_deleted)))
587600
})
588601
})
@@ -602,9 +615,12 @@ fn delete_intern<N: PartialTrie>(
602615
fn try_collapse_if_extension<N: PartialTrie>(
603616
node: WrappedNode<N>,
604617
curr_key: &Nibbles,
618+
strategy: OnOrphanedHashNode,
605619
) -> TrieOpResult<WrappedNode<N>> {
606620
match node.as_ref() {
607-
Node::Extension { nibbles, child } => collapse_ext_node_if_needed(nibbles, child, curr_key),
621+
Node::Extension { nibbles, child } => {
622+
collapse_ext_node_if_needed(nibbles, child, curr_key, strategy)
623+
}
608624
_ => Ok(node),
609625
}
610626
}
@@ -637,6 +653,7 @@ fn collapse_ext_node_if_needed<N: PartialTrie>(
637653
ext_nibbles: &Nibbles,
638654
child: &WrappedNode<N>,
639655
curr_key: &Nibbles,
656+
strategy: OnOrphanedHashNode,
640657
) -> TrieOpResult<WrappedNode<N>> {
641658
trace!(
642659
"Collapsing extension node ({:x}) with child {}...",
@@ -657,10 +674,13 @@ fn collapse_ext_node_if_needed<N: PartialTrie>(
657674
nibbles: leaf_nibbles,
658675
value,
659676
} => Ok(leaf(ext_nibbles.merge_nibbles(leaf_nibbles), value.clone())),
660-
Node::Hash(h) => Err(TrieOpError::ExtensionCollapsedIntoHashError(
661-
curr_key.merge_nibbles(ext_nibbles),
662-
*h,
663-
)),
677+
Node::Hash(h) => match strategy {
678+
OnOrphanedHashNode::CollapseToExtension => Ok(extension(*ext_nibbles, child.clone())),
679+
OnOrphanedHashNode::Reject => Err(TrieOpError::ExtensionCollapsedIntoHashError(
680+
curr_key.merge_nibbles(ext_nibbles),
681+
*h,
682+
)),
683+
},
664684
// Can never do this safely, so return an error.
665685
_ => Err(TrieOpError::HashNodeExtError(TrieNodeType::from(child))),
666686
}

trace_decoder/src/lib.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ use evm_arithmetization::proof::{BlockHashes, BlockMetadata};
103103
use evm_arithmetization::GenerationInputs;
104104
use keccak_hash::keccak as hash;
105105
use keccak_hash::H256;
106-
use mpt_trie::partial_trie::HashedPartialTrie;
106+
use mpt_trie::partial_trie::{HashedPartialTrie, OnOrphanedHashNode};
107107
use processed_block_trace::ProcessedTxnInfo;
108108
use serde::{Deserialize, Serialize};
109109
use typed_mpt::{StateTrie, StorageTrie, TrieKey};
@@ -320,7 +320,7 @@ pub fn entrypoint(
320320
}) => ProcessedBlockTracePreImages {
321321
tries: PartialTriePreImages {
322322
state: state.items().try_fold(
323-
StateTrie::default(),
323+
StateTrie::new(OnOrphanedHashNode::Reject),
324324
|mut acc, (nibbles, hash_or_val)| {
325325
let path = TrieKey::from_nibbles(nibbles);
326326
match hash_or_val {
@@ -342,18 +342,21 @@ pub fn entrypoint(
342342
.into_iter()
343343
.map(|(k, SeparateTriePreImage::Direct(v))| {
344344
v.items()
345-
.try_fold(StorageTrie::default(), |mut acc, (nibbles, hash_or_val)| {
346-
let path = TrieKey::from_nibbles(nibbles);
347-
match hash_or_val {
348-
mpt_trie::trie_ops::ValOrHash::Val(value) => {
349-
acc.insert(path, value)?;
350-
}
351-
mpt_trie::trie_ops::ValOrHash::Hash(h) => {
352-
acc.insert_hash(path, h)?;
353-
}
354-
};
355-
anyhow::Ok(acc)
356-
})
345+
.try_fold(
346+
StorageTrie::new(OnOrphanedHashNode::Reject),
347+
|mut acc, (nibbles, hash_or_val)| {
348+
let path = TrieKey::from_nibbles(nibbles);
349+
match hash_or_val {
350+
mpt_trie::trie_ops::ValOrHash::Val(value) => {
351+
acc.insert(path, value)?;
352+
}
353+
mpt_trie::trie_ops::ValOrHash::Hash(h) => {
354+
acc.insert_hash(path, h)?;
355+
}
356+
};
357+
anyhow::Ok(acc)
358+
},
359+
)
357360
.map(|v| (k, v))
358361
})
359362
.collect::<Result<_, _>>()?,

trace_decoder/src/type1.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use std::collections::{BTreeMap, BTreeSet};
77
use anyhow::{bail, ensure, Context as _};
88
use either::Either;
99
use evm_arithmetization::generation::mpt::AccountRlp;
10+
use mpt_trie::partial_trie::OnOrphanedHashNode;
1011
use nunny::NonEmpty;
1112
use u4::U4;
1213

1314
use crate::typed_mpt::{StateTrie, StorageTrie, TrieKey};
1415
use crate::wire::{Instruction, SmtLeaf};
1516

16-
#[derive(Debug, Default, Clone)]
17+
#[derive(Debug, Clone)]
1718
pub struct Frontend {
1819
pub state: StateTrie,
1920
pub code: BTreeSet<NonEmpty<Vec<u8>>>,
@@ -22,6 +23,18 @@ pub struct Frontend {
2223
pub storage: BTreeMap<TrieKey, StorageTrie>,
2324
}
2425

26+
impl Default for Frontend {
27+
// This frontend is intended to be used with our custom `zeroTracer`,
28+
// which covers branch-to-extension collapse edge cases.
29+
fn default() -> Self {
30+
Self {
31+
state: StateTrie::new(OnOrphanedHashNode::CollapseToExtension),
32+
code: BTreeSet::new(),
33+
storage: BTreeMap::new(),
34+
}
35+
}
36+
}
37+
2538
pub fn frontend(instructions: impl IntoIterator<Item = Instruction>) -> anyhow::Result<Frontend> {
2639
let executions = execute(instructions)?;
2740
ensure!(
@@ -157,7 +170,7 @@ fn node2storagetrie(node: Node) -> anyhow::Result<StorageTrie> {
157170
Ok(())
158171
}
159172

160-
let mut mpt = StorageTrie::default();
173+
let mut mpt = StorageTrie::new(OnOrphanedHashNode::CollapseToExtension);
161174
visit(&mut mpt, &stackstack::Stack::new(), node)?;
162175
Ok(mpt)
163176
}

trace_decoder/src/typed_mpt.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use copyvec::CopyVec;
77
use ethereum_types::{Address, H256};
88
use evm_arithmetization::generation::mpt::AccountRlp;
99
use mpt_trie::{
10-
partial_trie::{HashedPartialTrie, Node, PartialTrie as _},
10+
partial_trie::{HashedPartialTrie, Node, OnOrphanedHashNode, PartialTrie as _},
1111
trie_ops::TrieOpError,
1212
};
1313
use u4::{AsNibbles, U4};
@@ -243,6 +243,14 @@ pub struct StateTrie {
243243
}
244244

245245
impl StateTrie {
246+
pub fn new(strategy: OnOrphanedHashNode) -> Self {
247+
Self {
248+
typed: TypedMpt {
249+
inner: HashedPartialTrie::new_with_strategy(Node::Empty, strategy),
250+
_ty: PhantomData,
251+
},
252+
}
253+
}
246254
pub fn insert_by_address(
247255
&mut self,
248256
address: Address,
@@ -319,6 +327,11 @@ pub struct StorageTrie {
319327
untyped: HashedPartialTrie,
320328
}
321329
impl StorageTrie {
330+
pub fn new(strategy: OnOrphanedHashNode) -> Self {
331+
Self {
332+
untyped: HashedPartialTrie::new_with_strategy(Node::Empty, strategy),
333+
}
334+
}
322335
pub fn insert(&mut self, key: TrieKey, value: Vec<u8>) -> Result<Option<Vec<u8>>, Error> {
323336
let prev = self.untyped.get(key.into_nibbles()).map(Vec::from);
324337
self.untyped

0 commit comments

Comments
 (0)