Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: change proof internal repr to HashMap #43

Merged
merged 5 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/hash_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use super::{
proof::ProofRetainer,
BranchNodeCompact, Nibbles, TrieMask, EMPTY_ROOT_HASH,
};
use crate::HashMap;
use alloy_primitives::{hex, keccak256, Bytes, B256};
use crate::{proof::ProofNodes, HashMap};
use alloy_primitives::{hex, keccak256, B256};
use alloy_rlp::EMPTY_STRING_CODE;
use core::cmp;
use tracing::trace;

#[allow(unused_imports)]
use alloc::{collections::BTreeMap, vec::Vec};
use alloc::vec::Vec;

mod value;
pub use value::HashBuilderValue;
Expand Down Expand Up @@ -90,9 +90,9 @@ impl HashBuilder {
(self, updates.unwrap_or_default())
}

/// Take and return the proofs retained.
pub fn take_proofs(&mut self) -> BTreeMap<Nibbles, Bytes> {
self.proof_retainer.take().map(ProofRetainer::into_proofs).unwrap_or_default()
/// Take and return retained proof nodes.
pub fn take_proof_nodes(&mut self) -> ProofNodes {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is breaking, but so is the type change, so doesn't matter imo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is #42

Copy link
Member Author

@rkrasiuk rkrasiuk Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not only a helper struct, but also forces users to revise their usage of proof nodes, because they might rely on them being sorted already (e.g. like we do in reth)

self.proof_retainer.take().map(ProofRetainer::into_proof_nodes).unwrap_or_default()
}

/// The number of total updates accrued.
Expand Down Expand Up @@ -420,6 +420,7 @@ impl HashBuilder {
mod tests {
use super::*;
use crate::{nodes::LeafNode, triehash_trie_root};
use alloc::collections::BTreeMap;
use alloy_primitives::{b256, hex, U256};
use alloy_rlp::Encodable;

Expand Down
3 changes: 3 additions & 0 deletions src/proof/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ pub use verify::verify_proof;
mod error;
pub use error::ProofVerificationError;

mod proof_nodes;
pub use proof_nodes::ProofNodes;

mod retainer;
pub use retainer::ProofRetainer;
70 changes: 70 additions & 0 deletions src/proof/proof_nodes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{HashMap, Nibbles};
use alloy_primitives::Bytes;
use core::ops::Deref;

#[allow(unused_imports)]
use alloc::vec::Vec;

/// A wrapper struct for trie node key to RLP encoded trie node.
#[derive(PartialEq, Eq, Clone, Default, Debug)]
pub struct ProofNodes(HashMap<Nibbles, Bytes>);

impl Deref for ProofNodes {
type Target = HashMap<Nibbles, Bytes>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl FromIterator<(Nibbles, Bytes)> for ProofNodes {
fn from_iter<T: IntoIterator<Item = (Nibbles, Bytes)>>(iter: T) -> Self {
Self(HashMap::from_iter(iter))
}
}

impl ProofNodes {
/// Return iterator over proof nodes that match the target.
pub fn matching_nodes_iter<'a>(
&'a self,
target: &'a Nibbles,
) -> impl Iterator<Item = (&'a Nibbles, &'a Bytes)> {
self.0.iter().filter(|(key, _)| target.starts_with(key))
}

/// Return the vec of proof nodes that match the target.
pub fn matching_nodes(&self, target: &Nibbles) -> Vec<(Nibbles, Bytes)> {
self.matching_nodes_iter(target).map(|(key, node)| (key.clone(), node.clone())).collect()
}

/// Return the sorted vec of proof nodes that match the target.
pub fn matching_nodes_sorted(&self, target: &Nibbles) -> Vec<(Nibbles, Bytes)> {
let mut nodes = self.matching_nodes(target);
nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
nodes
}

/// Insert the RLP encoded trie node at key.
pub fn insert(&mut self, key: Nibbles, node: Bytes) -> Option<Bytes> {
self.0.insert(key, node)
}

/// Return the sorted vec of all proof nodes.
pub fn nodes_sorted(&self) -> Vec<(Nibbles, Bytes)> {
let mut nodes = Vec::from_iter(self.0.iter().map(|(k, v)| (k.clone(), v.clone())));
nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
nodes
}

/// Convert into sorted vec of all proof nodes.
pub fn into_nodes_sorted(self) -> Vec<(Nibbles, Bytes)> {
let mut nodes = Vec::from_iter(self.0);
nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0));
nodes
}

/// Convert wrapper struct into inner map.
pub fn into_inner(self) -> HashMap<Nibbles, Bytes> {
self.0
}
}
17 changes: 8 additions & 9 deletions src/proof/retainer.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use crate::Nibbles;
use crate::{proof::ProofNodes, Nibbles};
use alloy_primitives::Bytes;

#[allow(unused_imports)]
use alloc::{collections::BTreeMap, vec::Vec};
use alloc::vec::Vec;

/// Proof retainer is used to store proofs during merkle trie construction.
/// It is intended to be used within the [`HashBuilder`](crate::HashBuilder).
#[derive(Default, Debug)]
pub struct ProofRetainer {
/// The nibbles of the target trie keys to retain proofs for.
targets: Vec<Nibbles>,
/// The map of retained proofs (RLP serialized trie nodes)
/// with their corresponding key in the trie.
proofs: BTreeMap<Nibbles, Bytes>,
/// The map retained trie node keys to RLP serialized trie nodes.
proof_nodes: ProofNodes,
}

impl core::iter::FromIterator<Nibbles> for ProofRetainer {
Expand All @@ -24,7 +23,7 @@ impl core::iter::FromIterator<Nibbles> for ProofRetainer {
impl ProofRetainer {
/// Create new retainer with target nibbles.
pub fn new(targets: Vec<Nibbles>) -> Self {
Self { targets, proofs: Default::default() }
Self { targets, proof_nodes: Default::default() }
}

/// Returns `true` if the given prefix matches the retainer target.
Expand All @@ -33,14 +32,14 @@ impl ProofRetainer {
}

/// Returns all collected proofs.
pub fn into_proofs(self) -> BTreeMap<Nibbles, Bytes> {
self.proofs
pub fn into_proof_nodes(self) -> ProofNodes {
self.proof_nodes
}

/// Retain the proof if the key matches any of the targets.
pub fn retain(&mut self, prefix: &Nibbles, proof: &[u8]) {
if prefix.is_empty() || self.matches(prefix) {
self.proofs.insert(prefix.clone(), Bytes::from(proof.to_vec()));
self.proof_nodes.insert(prefix.clone(), Bytes::from(proof.to_vec()));
}
}
}
96 changes: 71 additions & 25 deletions src/proof/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ mod tests {
use super::*;
use crate::{
nodes::{BranchNode, ExtensionNode, LeafNode},
proof::ProofRetainer,
proof::{ProofNodes, ProofRetainer},
triehash_trie_root, HashBuilder, TrieMask,
};
use alloc::collections::BTreeMap;
use alloy_primitives::hex;
use alloy_rlp::{Encodable, EMPTY_STRING_CODE};
use core::str::FromStr;
Expand All @@ -163,9 +162,20 @@ mod tests {
let key = Nibbles::unpack(B256::repeat_byte(42));
let mut hash_builder = HashBuilder::default().with_proof_retainer(ProofRetainer::default());
let root = hash_builder.root();
let proof = hash_builder.take_proofs();
assert_eq!(proof, BTreeMap::from([(Nibbles::default(), Bytes::from([EMPTY_STRING_CODE]))]));
assert_eq!(verify_proof(root, key.clone(), None, proof.values()), Ok(()));
let proof = hash_builder.take_proof_nodes();
assert_eq!(
proof,
ProofNodes::from_iter([(Nibbles::default(), Bytes::from([EMPTY_STRING_CODE]))])
);
assert_eq!(
verify_proof(
root,
key.clone(),
None,
proof.into_nodes_sorted().iter().map(|(_, node)| node)
),
Ok(())
);

let mut dummy_proof = vec![];
BranchNode::default().encode(&mut dummy_proof);
Expand All @@ -191,8 +201,16 @@ mod tests {
let root = hash_builder.root();
assert_eq!(root, triehash_trie_root([(target.pack(), target.pack())]));

let proof = hash_builder.take_proofs();
assert_eq!(verify_proof(root, target, Some(target_value.to_vec()), proof.values()), Ok(()));
let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
assert_eq!(
verify_proof(
root,
target,
Some(target_value.to_vec()),
proof.iter().map(|(_, node)| node)
),
Ok(())
);
}

#[test]
Expand All @@ -212,8 +230,8 @@ mod tests {
triehash_trie_root(range.map(|b| (B256::with_last_byte(b), B256::with_last_byte(b))))
);

let proof = hash_builder.take_proofs();
assert_eq!(verify_proof(root, target, None, proof.values()), Ok(()));
let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
assert_eq!(verify_proof(root, target, None, proof.iter().map(|(_, node)| node)), Ok(()));
}

#[test]
Expand All @@ -240,13 +258,21 @@ mod tests {
root,
triehash_trie_root(existing_keys.map(|key| (B256::from_slice(&key), value)))
);
let proof = hash_builder.take_proofs();
assert_eq!(proof, BTreeMap::from([
let proof = hash_builder.take_proof_nodes();
assert_eq!(proof, ProofNodes::from_iter([
(Nibbles::default(), Bytes::from_str("f851a0c530c099d779362b6bd0be05039b51ccd0a8ed39e0b2abacab8fe0e3441251878080a07d4ee4f073ae7ce32a6cbcdb015eb73dd2616f33ed2e9fb6ba51c1f9ad5b697b80808080808080808080808080").unwrap()),
(Nibbles::from_vec(vec![0x3]), Bytes::from_str("f85180808080808080808080a057fcbd3f97b1093cd39d0f58dafd5058e2d9f79a419e88c2498ff3952cb11a8480a07520d69a83a2bdad373a68b2c9c8c0e1e1c99b6ec80b4b933084da76d644081980808080").unwrap()),
(Nibbles::from_vec(vec![0x3, 0xc]), Bytes::from_str("f842a02015000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001").unwrap())
]));
assert_eq!(verify_proof(root, target.clone(), None, proof.values()), Ok(()));
assert_eq!(
verify_proof(
root,
target.clone(),
None,
proof.into_nodes_sorted().iter().map(|(_, node)| node)
),
Ok(())
);

let retainer = ProofRetainer::from_iter([target.clone()]);
let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
Expand All @@ -264,16 +290,21 @@ mod tests {
.chain([(B256::from_slice(&target.pack()), value)])
)
);
let proof = hash_builder.take_proofs();
assert_eq!(proof, BTreeMap::from([
let proof = hash_builder.take_proof_nodes();
assert_eq!(proof, ProofNodes::from_iter([
(Nibbles::default(), Bytes::from_str("f851a0c530c099d779362b6bd0be05039b51ccd0a8ed39e0b2abacab8fe0e3441251878080a0abd80d939392f6d222f8becc15f8c6f0dbbc6833dd7e54bfbbee0c589b7fd40380808080808080808080808080").unwrap()),
(Nibbles::from_vec(vec![0x3]), Bytes::from_str("f85180808080808080808080a057fcbd3f97b1093cd39d0f58dafd5058e2d9f79a419e88c2498ff3952cb11a8480a09e7b3788773773f15e26ad07b72a2c25a6374bce256d9aab6cea48fbc77d698180808080").unwrap()),
(Nibbles::from_vec(vec![0x3, 0xc]), Bytes::from_str("e211a0338ac0a453edb0e40a23a70aee59e02a6c11597c34d79a5ba94da8eb20dd4d52").unwrap()),
(Nibbles::from_vec(vec![0x3, 0xc, 0x1]), Bytes::from_str("f8518080808080a020dc5b33292bfad9013bf123f7faf1efcc5c8e00c894177fc0bfb447daef522f808080a020dc5b33292bfad9013bf123f7faf1efcc5c8e00c894177fc0bfb447daef522f80808080808080").unwrap()),
(Nibbles::from_vec(vec![0x3, 0xc, 0x1, 0x9]), Bytes::from_str("f8419f20000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001").unwrap()),
]));
assert_eq!(
verify_proof(root, target.clone(), Some(value.to_vec()), proof.values()),
verify_proof(
root,
target.clone(),
Some(value.to_vec()),
proof.into_nodes_sorted().iter().map(|(_, node)| node)
),
Ok(())
);
}
Expand All @@ -296,8 +327,16 @@ mod tests {
triehash_trie_root(range.map(|b| (B256::with_last_byte(b), B256::with_last_byte(b))))
);

let proof = hash_builder.take_proofs();
assert_eq!(verify_proof(root, target, Some(target_value.to_vec()), proof.values()), Ok(()));
let proof = hash_builder.take_proof_nodes().into_nodes_sorted();
assert_eq!(
verify_proof(
root,
target,
Some(target_value.to_vec()),
proof.iter().map(|(_, node)| node)
),
Ok(())
);
}

#[test]
Expand All @@ -320,17 +359,25 @@ mod tests {
triehash_trie_root(range.map(|b| (B256::repeat_byte(b), B256::repeat_byte(b))))
);

let proof = hash_builder.take_proofs();
let proof = hash_builder.take_proof_nodes();

let proof1 = proof.iter().filter_map(|(k, v)| target1.starts_with(k).then_some(v));
assert_eq!(
verify_proof(root, target1.clone(), Some(target1_value.to_vec()), proof1),
verify_proof(
root,
target1.clone(),
Some(target1_value.to_vec()),
proof.matching_nodes_sorted(&target1).iter().map(|(_, node)| node)
),
Ok(())
);

let proof2 = proof.iter().filter_map(|(k, v)| target2.starts_with(k).then_some(v));
assert_eq!(
verify_proof(root, target2.clone(), Some(target2_value.to_vec()), proof2),
verify_proof(
root,
target2.clone(),
Some(target2_value.to_vec()),
proof.matching_nodes_sorted(&target2).iter().map(|(_, node)| node)
),
Ok(())
);
}
Expand Down Expand Up @@ -541,11 +588,10 @@ mod tests {
let root = hash_builder.root();
assert_eq!(root, triehash_trie_root(&hashed));

let proofs = hash_builder.take_proofs();
let proofs = hash_builder.take_proof_nodes();
for (key, value) in hashed {
let nibbles = Nibbles::unpack(key);
let proof = proofs.iter().filter_map(|(k, v)| nibbles.starts_with(k).then_some(v));
assert_eq!(verify_proof(root, nibbles.clone(), Some(value), proof), Ok(()));
assert_eq!(verify_proof(root, nibbles.clone(), Some(value), proofs.matching_nodes_sorted(&nibbles).iter().map(|(_, node)| node)), Ok(()));
}
});
}
Expand Down
Loading