Skip to content

Commit b8c7ade

Browse files
committed
feat(7/7): replace Path with various newtypes
`Path` has been replaced with the components added in previous PRs inclusing using `[PathComponent]` that make it easier to reason about paths in code.
1 parent be564fd commit b8c7ade

File tree

25 files changed

+682
-1006
lines changed

25 files changed

+682
-1006
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ metrics-util = "0.20.0"
7676
nonzero_ext = "0.3.0"
7777
rand_distr = "0.5.1"
7878
sha2 = "0.10.9"
79-
smallvec = "1.15.1"
79+
smallvec = { version = "1.15.1", features = ["write", "union", "const_new"] }
8080
test-case = "3.3.1"
8181
thiserror = "2.0.16"
8282

firewood/src/iter.rs

Lines changed: 226 additions & 273 deletions
Large diffs are not rendered by default.

firewood/src/merkle.rs

Lines changed: 57 additions & 129 deletions
Large diffs are not rendered by default.

firewood/src/merkle/tests/ethhash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ fn test_eth_compatible_accounts(
8989
let account = make_key(account);
9090
let expected_key_hash = Keccak256::digest(&account);
9191

92-
let items = once((
92+
let items = std::iter::once((
9393
Box::from(expected_key_hash.as_slice()),
9494
make_key(account_value),
9595
))

firewood/src/proof.rs

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,15 @@
55
clippy::missing_errors_doc,
66
reason = "Found 1 occurrences after enabling the lint."
77
)]
8-
#![expect(
9-
clippy::needless_continue,
10-
reason = "Found 1 occurrences after enabling the lint."
11-
)]
128

139
use firewood_storage::{
14-
Children, FileIoError, HashType, Hashable, IntoHashType, NibblesIterator, Path, PathComponent,
15-
PathIterItem, Preimage, TrieHash, ValueDigest,
10+
Children, FileIoError, HashType, Hashable, IntoHashType, IntoSplitPath, PackedPathRef,
11+
PartialPath, PathComponent, PathIterItem, Preimage, SplitPath, TrieHash, TriePath,
12+
TriePathFromPackedBytes, ValueDigest,
1613
};
1714
use thiserror::Error;
1815

19-
use crate::merkle::{Key, Value};
16+
use crate::merkle::Value;
2017

2118
#[derive(Debug, Error)]
2219
#[non_exhaustive]
@@ -84,7 +81,7 @@ pub enum ProofError {
8481
/// A node in a proof.
8582
pub struct ProofNode {
8683
/// The key this node is at. Each byte is a nibble.
87-
pub key: Key,
84+
pub key: PartialPath,
8885
/// The length of the key prefix that is shared with the previous node.
8986
pub partial_len: usize,
9087
/// None if the node does not have a value.
@@ -112,16 +109,22 @@ impl std::fmt::Debug for ProofNode {
112109
}
113110

114111
impl Hashable for ProofNode {
115-
fn parent_prefix_path(&self) -> impl Iterator<Item = u8> + Clone {
116-
self.full_path().take(self.partial_len)
112+
fn parent_prefix_path(&self) -> impl IntoSplitPath + '_ {
113+
self.full_path()
114+
.into_split_path()
115+
.split_at(self.partial_len)
116+
.0
117117
}
118118

119-
fn partial_path(&self) -> impl Iterator<Item = u8> + Clone {
120-
self.full_path().skip(self.partial_len)
119+
fn partial_path(&self) -> impl IntoSplitPath + '_ {
120+
self.full_path()
121+
.into_split_path()
122+
.split_at(self.partial_len)
123+
.1
121124
}
122125

123-
fn full_path(&self) -> impl Iterator<Item = u8> + Clone {
124-
self.key.as_ref().iter().copied()
126+
fn full_path(&self) -> impl IntoSplitPath + '_ {
127+
&self.key
125128
}
126129

127130
fn value_digest(&self) -> Option<ValueDigest<&[u8]>> {
@@ -182,7 +185,7 @@ impl<T: ProofCollection + ?Sized> Proof<T> {
182185
key: K,
183186
root_hash: &TrieHash,
184187
) -> Result<Option<ValueDigest<&[u8]>>, ProofError> {
185-
let key = Path(NibblesIterator::new(key.as_ref()).collect());
188+
let key = PackedPathRef::path_from_packed_bytes(key.as_ref());
186189

187190
let Some(last_node) = self.0.as_ref().last() else {
188191
return Err(ProofError::Empty);
@@ -199,14 +202,14 @@ impl<T: ProofCollection + ?Sized> Proof<T> {
199202
// Assert that only nodes whose keys are an even number of nibbles
200203
// have a `value_digest`.
201204
#[cfg(not(feature = "branch_factor_256"))]
202-
if node.full_path().count() % 2 != 0 && node.value_digest().is_some() {
205+
if node.full_path().len() % 2 != 0 && node.value_digest().is_some() {
203206
return Err(ProofError::ValueAtOddNibbleLength);
204207
}
205208

206209
if let Some(next_node) = iter.peek() {
207210
// Assert that every node's key is a prefix of `key`, except for the last node,
208211
// whose key can be equal to or a suffix of `key` in an exclusion proof.
209-
if next_nibble(node.full_path(), key.iter().copied()).is_none() {
212+
if next_nibble(node.full_path(), key).is_none() {
210213
return Err(ProofError::ShouldBePrefixOfProvenKey);
211214
}
212215

@@ -217,16 +220,14 @@ impl<T: ProofCollection + ?Sized> Proof<T> {
217220
return Err(ProofError::ShouldBePrefixOfNextKey);
218221
};
219222

220-
let next_nibble =
221-
PathComponent::try_new(next_nibble).ok_or(ProofError::ChildIndexOutOfBounds)?;
222223
expected_hash = node.children()[next_nibble]
223224
.as_ref()
224225
.ok_or(ProofError::NodeNotInTrie)?
225226
.clone();
226227
}
227228
}
228229

229-
if last_node.full_path().eq(key.iter().copied()) {
230+
if last_node.full_path().path_eq(&key) {
230231
return Ok(last_node.value_digest());
231232
}
232233

@@ -384,23 +385,18 @@ impl ProofCollection for EmptyProofCollection {
384385

385386
/// Returns the next nibble in `c` after `b`.
386387
/// Returns None if `b` is not a strict prefix of `c`.
387-
fn next_nibble<B, C>(b: B, c: C) -> Option<u8>
388+
fn next_nibble<B, C>(b: B, c: C) -> Option<PathComponent>
388389
where
389-
B: IntoIterator<Item = u8>,
390-
C: IntoIterator<Item = u8>,
390+
B: IntoSplitPath,
391+
C: IntoSplitPath,
391392
{
392-
let b = b.into_iter();
393-
let mut c = c.into_iter();
394-
395-
// Check if b is a prefix of c
396-
for b_item in b {
397-
match c.next() {
398-
Some(c_item) if b_item == c_item => continue,
399-
_ => return None,
400-
}
393+
let common = b
394+
.into_split_path()
395+
.longest_common_prefix(c.into_split_path());
396+
match (common.a_suffix.split_first(), common.b_suffix.split_first()) {
397+
(None, Some((next, _))) => Some(next),
398+
_ => None,
401399
}
402-
403-
c.next()
404400
}
405401

406402
fn verify_opt_value_digest(

firewood/src/proofs/de.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#[cfg(feature = "ethhash")]
55
use firewood_storage::HashType;
6-
use firewood_storage::{Children, TrieHash, ValueDigest};
6+
use firewood_storage::{Children, PartialPath, TrieHash, TriePathFromUnpackedBytes, ValueDigest};
77
use integer_encoding::VarInt;
88

99
use crate::{
@@ -85,7 +85,7 @@ impl Version0 for FrozenRangeProof {
8585

8686
impl Version0 for ProofNode {
8787
fn read_v0_item(reader: &mut V0Reader<'_>) -> Result<Self, ReadError> {
88-
let key = reader.read_item()?;
88+
let key = reader.read_v0_item()?;
8989
let partial_len = reader.read_item()?;
9090
let value_digest = reader.read_item()?;
9191

@@ -105,6 +105,19 @@ impl Version0 for ProofNode {
105105
}
106106
}
107107

108+
impl Version0 for PartialPath {
109+
fn read_v0_item(reader: &mut V0Reader<'_>) -> Result<Self, ReadError> {
110+
let bytes = reader.read_item::<&[u8]>()?;
111+
PartialPath::path_from_unpacked_bytes(bytes).map_err(|_| {
112+
reader.invalid_item(
113+
"partial path",
114+
"valid packed path bytes",
115+
format!("invalid path: {}", hex::encode(bytes)),
116+
)
117+
})
118+
}
119+
}
120+
108121
impl Version0 for (Box<[u8]>, Box<[u8]>) {
109122
fn read_v0_item(reader: &mut V0Reader<'_>) -> Result<Self, ReadError> {
110123
Ok((reader.read_item()?, reader.read_item()?))

firewood/src/proofs/ser.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
22
// See the file LICENSE.md for licensing terms.
33

4-
use firewood_storage::ValueDigest;
4+
use firewood_storage::{PartialPath, PathComponentSliceExt, ValueDigest};
55
use integer_encoding::VarInt;
66

77
use crate::{
@@ -108,6 +108,13 @@ impl WriteItem for ProofNode {
108108
}
109109
}
110110

111+
impl WriteItem for PartialPath {
112+
fn write_item(&self, out: &mut Vec<u8>) {
113+
out.push_var_int(self.len());
114+
out.extend_from_slice(self.as_byte_slice());
115+
}
116+
}
117+
111118
impl<T: WriteItem> WriteItem for Option<T> {
112119
fn write_item(&self, out: &mut Vec<u8>) {
113120
if let Some(v) = self {

storage/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ metrics.workspace = true
2929
nonzero_ext.workspace = true
3030
rand = { workspace = true, optional = true }
3131
sha2.workspace = true
32-
smallvec = { workspace = true, features = ["write", "union"] }
32+
smallvec.workspace = true
3333
thiserror.workspace = true
3434
# Regular dependencies
3535
arc-swap = "1.7.1"

storage/benches/serializer.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ use std::os::raw::c_int;
1515

1616
use criterion::profiler::Profiler;
1717
use criterion::{Bencher, Criterion, criterion_group, criterion_main};
18-
use firewood_storage::{Children, LeafNode, Node, Path, PathComponent};
18+
use firewood_storage::{
19+
Children, LeafNode, Node, PartialPath, PathComponent, TriePathFromUnpackedBytes,
20+
};
1921
use pprof::ProfilerGuard;
20-
use smallvec::SmallVec;
2122

2223
use std::path::Path as FsPath;
2324

@@ -82,7 +83,7 @@ fn to_bytes(input: &Node) -> Vec<u8> {
8283
fn leaf(c: &mut Criterion) {
8384
let mut group = c.benchmark_group("leaf");
8485
let input = Node::Leaf(LeafNode {
85-
partial_path: Path(SmallVec::from_slice(&[0, 1])),
86+
partial_path: PartialPath::path_from_unpacked_bytes(&[0, 1]).unwrap(),
8687
value: Box::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
8788
});
8889

@@ -94,7 +95,7 @@ fn leaf(c: &mut Criterion) {
9495
fn branch(c: &mut Criterion) {
9596
let mut group = c.benchmark_group("has_value");
9697
let mut input = Node::Branch(Box::new(firewood_storage::BranchNode {
97-
partial_path: Path(SmallVec::from_slice(&[0, 1])),
98+
partial_path: PartialPath::path_from_unpacked_bytes(&[0, 1]).unwrap(),
9899
value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()),
99100
children: Children::from_fn(|i| {
100101
if i.as_u8() == 0 {

0 commit comments

Comments
 (0)