Skip to content

Commit

Permalink
Add feature branch_factor_256 (#746)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkuris authored Nov 3, 2024
1 parent e6dbc92 commit 279e7e2
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 48 deletions.
1 change: 1 addition & 0 deletions firewood/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ default = []
nightly = []
iouring = ["io-uring"]
logger = ["storage/logger"]
branch_factor_256 = [ "storage/branch_factor_256" ]

[dev-dependencies]
criterion = {version = "0.5.1", features = ["async_tokio"]}
Expand Down
39 changes: 26 additions & 13 deletions firewood/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum MerkleError {

// convert a set of nibbles into a printable string
// panics if there is a non-nibble byte in the set
#[cfg(not(feature = "branch_factor_256"))]
fn nibbles_formatter<X: IntoIterator<Item = u8>>(nib: X) -> String {
nib.into_iter()
.map(|c| {
Expand All @@ -60,6 +61,12 @@ fn nibbles_formatter<X: IntoIterator<Item = u8>>(nib: X) -> String {
.collect::<String>()
}

#[cfg(feature = "branch_factor_256")]
fn nibbles_formatter<X: IntoIterator<Item = u8>>(nib: X) -> String {
let collected: Box<[u8]> = nib.into_iter().collect();
hex::encode(&collected)
}

macro_rules! write_attributes {
($writer:ident, $node:expr, $value:expr) => {
if !$node.partial_path.0.is_empty() {
Expand Down Expand Up @@ -182,7 +189,8 @@ impl<T: TrieReader> Merkle<T> {
// No nodes, even the root, are before `key`.
// The root alone proves the non-existence of `key`.
// TODO reduce duplicate code with ProofNode::from<PathIterItem>
let mut child_hashes: [Option<TrieHash>; BranchNode::MAX_CHILDREN] = Default::default();
let mut child_hashes: [Option<TrieHash>; BranchNode::MAX_CHILDREN] =
[const { None }; BranchNode::MAX_CHILDREN];
if let Some(branch) = root.as_branch() {
// TODO danlaine: can we avoid indexing?
#[allow(clippy::indexing_slicing)]
Expand Down Expand Up @@ -480,7 +488,7 @@ impl<S: ReadableStorage> Merkle<NodeStore<MutableProposal, S>> {
let mut branch = BranchNode {
partial_path: path_overlap.shared.into(),
value: Some(value),
children: Default::default(),
children: [const { None }; BranchNode::MAX_CHILDREN],
};

// Shorten the node's partial path since it has a new parent.
Expand Down Expand Up @@ -528,7 +536,7 @@ impl<S: ReadableStorage> Merkle<NodeStore<MutableProposal, S>> {
let mut branch = BranchNode {
partial_path: std::mem::replace(&mut leaf.partial_path, Path::new()),
value: Some(std::mem::take(&mut leaf.value).into_boxed_slice()),
children: Default::default(),
children: [const { None }; BranchNode::MAX_CHILDREN],
};

let new_leaf = Node::Leaf(LeafNode {
Expand All @@ -554,7 +562,7 @@ impl<S: ReadableStorage> Merkle<NodeStore<MutableProposal, S>> {
let mut branch = BranchNode {
partial_path: path_overlap.shared.into(),
value: None,
children: Default::default(),
children: [const { None }; BranchNode::MAX_CHILDREN],
};

node.update_partial_path(node_partial_path);
Expand Down Expand Up @@ -1464,19 +1472,24 @@ mod tests {
#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1])], Some("c3bdc20aff5cba30f81ffd7689e94e1dbeece4a08e27f0104262431604cf45c6"); "root with leaf child")]
#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,1,2],&[0,1,2])], Some("229011c50ad4d5c2f4efe02b8db54f361ad295c4eee2bf76ea4ad1bb92676f97"); "root with branch child")]
#[test_case(vec![(&[0],&[0]),(&[0,1],&[0,1]),(&[0,8],&[0,8]),(&[0,1,2],&[0,1,2])], Some("a683b4881cb540b969f885f538ba5904699d480152f350659475a962d6240ef9"); "root with branch child and leaf child")]
#[allow(unused_variables)]
fn test_root_hash_merkledb_compatible(kvs: Vec<(&[u8], &[u8])>, expected_hash: Option<&str>) {
let merkle = merkle_build_test(kvs).unwrap().hash();
let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else {
assert!(expected_hash.is_none());
return;
};
// TODO: get the hashes from merkledb and verify compatibility with branch factor 256
#[cfg(not(feature = "branch_factor_256"))]
{
let merkle = merkle_build_test(kvs).unwrap().hash();
let Some(got_hash) = merkle.nodestore.root_hash().unwrap() else {
assert!(expected_hash.is_none());
return;
};

let expected_hash = expected_hash.unwrap();
let expected_hash = expected_hash.unwrap();

// This hash is from merkledb
let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap();
// This hash is from merkledb
let expected_hash: [u8; 32] = hex::decode(expected_hash).unwrap().try_into().unwrap();

assert_eq!(got_hash, TrieHash::from(expected_hash));
assert_eq!(got_hash, TrieHash::from(expected_hash));
}
}

#[test]
Expand Down
4 changes: 3 additions & 1 deletion firewood/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ impl Hashable for ProofNode {

impl From<PathIterItem> for ProofNode {
fn from(item: PathIterItem) -> Self {
let mut child_hashes: [Option<TrieHash>; BranchNode::MAX_CHILDREN] = Default::default();
let mut child_hashes: [Option<TrieHash>; BranchNode::MAX_CHILDREN] =
[const { None }; BranchNode::MAX_CHILDREN];

if let Some(branch) = item.node.as_branch() {
// TODO danlaine: can we avoid indexing?
Expand Down Expand Up @@ -170,6 +171,7 @@ impl<T: Hashable> Proof<T> {

// Assert that only nodes whose keys are an even number of nibbles
// have a `value_digest`.
#[cfg(not(feature = "branch_factor_256"))]
if node.key().count() % 2 != 0 && node.value_digest().is_some() {
return Err(ProofError::ValueAtOddNibbleLength);
}
Expand Down
33 changes: 27 additions & 6 deletions firewood/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,12 @@ fn as_enumerated_children_iter(branch: &BranchNode) -> impl Iterator<Item = (u8,
.filter_map(|(pos, child)| child.map(|child| (pos as u8, child)))
}

#[cfg(feature = "branch_factor_256")]
fn key_from_nibble_iter<Iter: Iterator<Item = u8>>(nibbles: Iter) -> Key {
nibbles.collect()
}

#[cfg(not(feature = "branch_factor_256"))]
fn key_from_nibble_iter<Iter: Iterator<Item = u8>>(mut nibbles: Iter) -> Key {
let mut data = Vec::with_capacity(nibbles.size_hint().0 / 2);

Expand Down Expand Up @@ -620,10 +626,13 @@ mod tests {
};

assert!(should_yield_elt);
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(
node.key_nibbles,
vec![0x0B, 0x0E, 0x0E, 0x0F].into_boxed_slice()
);
#[cfg(feature = "branch_factor_256")]
assert_eq!(node.key_nibbles, vec![0xBE, 0xEF].into_boxed_slice());
assert_eq!(node.node.as_leaf().unwrap().value, SmallVec::from([0x42]));
assert_eq!(node.next_nibble, None);

Expand All @@ -643,7 +652,10 @@ mod tests {
Some(Err(e)) => panic!("{:?}", e),
None => panic!("unexpected end of iterator"),
};
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice());
#[cfg(feature = "branch_factor_256")]
assert_eq!(node.key_nibbles, vec![0].into_boxed_slice());
assert_eq!(node.next_nibble, Some(0));
assert!(node.node.as_branch().unwrap().value.is_none());

Expand All @@ -652,11 +664,19 @@ mod tests {
Some(Err(e)) => panic!("{:?}", e),
None => panic!("unexpected end of iterator"),
};
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(
node.key_nibbles,
vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice()
);
#[cfg(feature = "branch_factor_256")]
assert_eq!(node.key_nibbles, vec![0, 0, 0].into_boxed_slice());

#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(node.next_nibble, Some(0x0F));
#[cfg(feature = "branch_factor_256")]
assert_eq!(node.next_nibble, Some(0xFF));

assert_eq!(
node.node.as_branch().unwrap().value,
Some(vec![0x00, 0x00, 0x00].into_boxed_slice()),
Expand All @@ -667,6 +687,7 @@ mod tests {
Some(Err(e)) => panic!("{:?}", e),
None => panic!("unexpected end of iterator"),
};
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(
node.key_nibbles,
vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F].into_boxed_slice()
Expand All @@ -693,7 +714,10 @@ mod tests {
Some(Err(e)) => panic!("{:?}", e),
None => panic!("unexpected end of iterator"),
};
// TODO: make this branch factor 16 compatible
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(node.key_nibbles, vec![0x00, 0x00].into_boxed_slice());

assert!(node.node.as_branch().unwrap().value.is_none());
assert_eq!(node.next_nibble, Some(0));

Expand All @@ -702,6 +726,7 @@ mod tests {
Some(Err(e)) => panic!("{:?}", e),
None => panic!("unexpected end of iterator"),
};
#[cfg(not(feature = "branch_factor_256"))]
assert_eq!(
node.key_nibbles,
vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00].into_boxed_slice()
Expand Down Expand Up @@ -799,40 +824,34 @@ mod tests {
assert_eq!(key, vec![0x00].into_boxed_slice());
let node = node.as_branch().unwrap();
assert!(node.value.is_none());
assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00]);

// Covers case of branch with value
let (key, node) = stream.next().await.unwrap().unwrap();
assert_eq!(key, vec![0x00, 0x00, 0x00].into_boxed_slice());
let node = node.as_branch().unwrap();
assert_eq!(node.value.clone().unwrap().to_vec(), vec![0x00, 0x00, 0x00]);
assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x00, 0x00]);

// Covers case of leaf with partial path
let (key, node) = stream.next().await.unwrap().unwrap();
assert_eq!(key, vec![0x00, 0x00, 0x00, 0x01].into_boxed_slice());
let node = node.as_leaf().unwrap();
assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0x01]);
assert_eq!(node.partial_path.to_vec(), vec![0x01]);

let (key, node) = stream.next().await.unwrap().unwrap();
assert_eq!(key, vec![0x00, 0x00, 0x00, 0xFF].into_boxed_slice());
let node = node.as_leaf().unwrap();
assert_eq!(node.clone().value.to_vec(), vec![0x00, 0x00, 0x00, 0xFF]);
assert_eq!(node.partial_path.to_vec(), vec![0x0F]);

let (key, node) = stream.next().await.unwrap().unwrap();
assert_eq!(key, vec![0x00, 0xD0, 0xD0].into_boxed_slice());
let node = node.as_leaf().unwrap();
assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xD0, 0xD0]);
assert_eq!(node.partial_path.to_vec(), vec![0x00, 0x0D, 0x00]); // 0x0D00 becomes 0xDO

// Covers case of leaf with no partial path
let (key, node) = stream.next().await.unwrap().unwrap();
assert_eq!(key, vec![0x00, 0xFF].into_boxed_slice());
let node = node.as_leaf().unwrap();
assert_eq!(node.clone().value.to_vec(), vec![0x00, 0xFF]);
assert_eq!(node.partial_path.to_vec(), vec![0x0F]);

check_stream_is_done(stream).await;
}
Expand Down Expand Up @@ -1059,6 +1078,8 @@ mod tests {

let mut stream = merkle.key_value_iter();

println!("{}", merkle.dump().unwrap());

assert_eq!(
stream.next().await.unwrap().unwrap(),
(branch.to_vec().into_boxed_slice(), branch.to_vec())
Expand Down
1 change: 1 addition & 0 deletions storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] }

[features]
logger = ["log"]
branch_factor_256 = []

[[bench]]
name = "serializer"
Expand Down
33 changes: 11 additions & 22 deletions storage/benches/serializer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE.md for licensing terms.

use std::num::NonZeroU64;
use std::{array::from_fn, num::NonZeroU64};

use bincode::Options;
use criterion::{criterion_group, criterion_main, Criterion};
Expand All @@ -27,27 +27,16 @@ fn branch(c: &mut Criterion) {
let mut input = Node::Branch(Box::new(storage::BranchNode {
partial_path: Path(SmallVec::from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])),
value: Some(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9].into_boxed_slice()),
children: [
Some(storage::Child::AddressWithHash(
NonZeroU64::new(1).unwrap(),
storage::TrieHash::from([0; 32]),
)),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
],
children: from_fn(|i| {
if i == 0 {
Some(storage::Child::AddressWithHash(
NonZeroU64::new(1).unwrap(),
storage::TrieHash::from([0; 32]),
))
} else {
None
}
}),
}));
let serializer = bincode::DefaultOptions::new().with_varint_encoding();
let benchfn = |b: &mut criterion::Bencher, input: &storage::Node| {
Expand Down
14 changes: 10 additions & 4 deletions storage/src/node/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Serialize for BranchNode {
state.serialize_field("partial_path", &self.partial_path)?;
state.serialize_field("value", &self.value)?;

let children: SmallVec<[(u8, LinearAddress, TrieHash); 16]> = self
let children: SmallVec<[(u8, LinearAddress, TrieHash); Self::MAX_CHILDREN]> = self
.children
.iter()
.enumerate()
Expand Down Expand Up @@ -71,12 +71,13 @@ impl<'de> Deserialize<'de> for BranchNode {
struct SerializedBranchNode {
partial_path: Path,
value: Option<Box<[u8]>>,
children: SmallVec<[(u8, LinearAddress, TrieHash); 16]>,
children: SmallVec<[(u8, LinearAddress, TrieHash); BranchNode::MAX_CHILDREN]>,
}

let s: SerializedBranchNode = Deserialize::deserialize(deserializer)?;

let mut children: [Option<Child>; BranchNode::MAX_CHILDREN] = Default::default();
let mut children: [Option<Child>; BranchNode::MAX_CHILDREN] =
[const { None }; BranchNode::MAX_CHILDREN];
for (offset, addr, hash) in s.children.iter() {
children[*offset as usize] = Some(Child::AddressWithHash(*addr, hash.clone()));
}
Expand Down Expand Up @@ -119,6 +120,11 @@ impl Debug for BranchNode {

impl BranchNode {
/// The maximum number of children in a [BranchNode]
#[cfg(feature = "branch_factor_256")]
pub const MAX_CHILDREN: usize = 256;

/// The maximum number of children in a [BranchNode]
#[cfg(not(feature = "branch_factor_256"))]
pub const MAX_CHILDREN: usize = 16;

/// Returns the address of the child at the given index.
Expand Down Expand Up @@ -158,7 +164,7 @@ impl From<&LeafNode> for BranchNode {
BranchNode {
partial_path: leaf.partial_path.clone(),
value: Some(Box::from(&leaf.value[..])),
children: Default::default(),
children: [const { None }; BranchNode::MAX_CHILDREN],
}
}
}
Loading

0 comments on commit 279e7e2

Please sign in to comment.