From 9e1fd97c4e5bc861373952d128d0c120bae62e0a Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 22 Nov 2023 13:54:34 -0500 Subject: [PATCH] split node files (#360) --- firewood/Cargo.toml | 1 + firewood/src/merkle.rs | 110 ++++--- firewood/src/merkle/node.rs | 386 +++++------------------ firewood/src/merkle/node/branch.rs | 184 +++++++++++ firewood/src/merkle/node/extension.rs | 94 ++++++ firewood/src/merkle/node/leaf.rs | 75 +++++ firewood/src/merkle/node/partial_path.rs | 129 ++++++++ firewood/src/merkle/partial_path.rs | 91 ------ firewood/src/shale/mod.rs | 2 +- 9 files changed, 628 insertions(+), 444 deletions(-) create mode 100644 firewood/src/merkle/node/branch.rs create mode 100644 firewood/src/merkle/node/extension.rs create mode 100644 firewood/src/merkle/node/leaf.rs create mode 100644 firewood/src/merkle/node/partial_path.rs delete mode 100644 firewood/src/merkle/partial_path.rs diff --git a/firewood/Cargo.toml b/firewood/Cargo.toml index dcbdb0f8c..8ae8ea51f 100644 --- a/firewood/Cargo.toml +++ b/firewood/Cargo.toml @@ -34,6 +34,7 @@ thiserror = "1.0.38" tokio = { version = "1.21.1", features = ["rt", "sync", "macros", "rt-multi-thread"] } typed-builder = "0.18.0" bincode = "1.3.3" +bitflags = "2.4.1" [dev-dependencies] criterion = {version = "0.5.1", features = ["async_tokio"]} diff --git a/firewood/src/merkle.rs b/firewood/src/merkle.rs index 5ab27623d..10fb47869 100644 --- a/firewood/src/merkle.rs +++ b/firewood/src/merkle.rs @@ -17,11 +17,9 @@ use std::{ use thiserror::Error; mod node; -mod partial_path; mod trie_hash; -pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, NBRANCH}; -pub use partial_path::PartialPath; +pub use node::{BranchNode, Data, ExtNode, LeafNode, Node, NodeType, PartialPath, MAX_CHILDREN}; pub use trie_hash::{TrieHash, TRIE_HASH_LEN}; type ObjRef<'a> = shale::ObjRef<'a, Node>; @@ -87,7 +85,8 @@ impl + Send + Sync> Merkle { self.store .put_item( Node::branch(BranchNode { - children: [None; NBRANCH], + // path: vec![].into(), + children: [None; MAX_CHILDREN], value: None, children_encoded: Default::default(), }), @@ -192,7 +191,7 @@ impl + Send + Sync> Merkle { node_to_split.write(|node| { // TODO: handle unwrap better - let path = node.inner.path_mut().unwrap(); + let path = node.inner.path_mut(); *path = PartialPath(new_split_node_path.to_vec()); @@ -202,7 +201,7 @@ impl + Send + Sync> Merkle { let new_node = Node::leaf(PartialPath(new_node_path.to_vec()), Data(val)); let leaf_address = self.put_node(new_node)?.as_ptr(); - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let last_matching_nibble = matching_path[idx]; chd[last_matching_nibble as usize] = Some(leaf_address); @@ -218,6 +217,7 @@ impl + Send + Sync> Merkle { chd[n_path[idx] as usize] = Some(address); let new_branch = Node::branch(BranchNode { + // path: PartialPath(matching_path[..idx].to_vec()), children: chd, value: None, children_encoded: Default::default(), @@ -251,7 +251,7 @@ impl + Send + Sync> Merkle { node_to_split, |u| { match &mut u.inner { - NodeType::Leaf(u) => u.1 = Data(val), + NodeType::Leaf(u) => u.data = Data(val), NodeType::Extension(u) => { let write_result = self.get_node(u.chd()).and_then(|mut b_ref| { @@ -298,7 +298,7 @@ impl + Send + Sync> Merkle { node_to_split .write(|u| { // TODO: handle unwraps better - let path = u.inner.path_mut().unwrap(); + let path = u.inner.path_mut(); *path = PartialPath(n_path[insert_path.len() + 1..].to_vec()); u.rehash(); @@ -306,6 +306,7 @@ impl + Send + Sync> Merkle { .unwrap(); let leaf_address = match &node_to_split.inner { + // TODO: handle BranchNode case NodeType::Extension(u) if u.path.len() == 0 => { deleted.push(node_to_split_address); u.chd() @@ -341,7 +342,7 @@ impl + Send + Sync> Merkle { }; // [parent] (-> [ExtNode]) -> [branch with v] -> [Leaf] - let mut children = [None; NBRANCH]; + let mut children = [None; MAX_CHILDREN]; children[idx] = leaf_address.into(); @@ -428,31 +429,11 @@ impl + Send + Sync> Merkle { // For a Branch node, we look at the child pointer. If it points // to another node, we walk down that. Otherwise, we can store our // value as a leaf and we're done - NodeType::Branch(n) => match n.children[current_nibble as usize] { - Some(c) => (node, c), - None => { - // insert the leaf to the empty slot - // create a new leaf - let leaf_ptr = self - .put_node(Node::leaf(PartialPath(key_nibbles.collect()), Data(val)))? - .as_ptr(); - // set the current child to point to this leaf - node.write(|u| { - let uu = u.inner.as_branch_mut().unwrap(); - uu.children[current_nibble as usize] = Some(leaf_ptr); - u.rehash(); - }) - .unwrap(); - - break None; - } - }, - NodeType::Leaf(n) => { // we collided with another key; make a copy // of the stored key to pass into split - let n_path = n.0.to_vec(); - let n_value = Some(n.1.clone()); + let n_path = n.path.to_vec(); + let n_value = Some(n.data.clone()); let rem_path = once(current_nibble).chain(key_nibbles).collect::>(); self.split( @@ -468,6 +449,31 @@ impl + Send + Sync> Merkle { break None; } + NodeType::Branch(n) => { + match n.children[current_nibble as usize] { + Some(c) => (node, c), + None => { + // insert the leaf to the empty slot + // create a new leaf + let leaf_ptr = self + .put_node(Node::leaf( + PartialPath(key_nibbles.collect()), + Data(val), + ))? + .as_ptr(); + // set the current child to point to this leaf + node.write(|u| { + let uu = u.inner.as_branch_mut().unwrap(); + uu.children[current_nibble as usize] = Some(leaf_ptr); + u.rehash(); + }) + .unwrap(); + + break None; + } + } + } + NodeType::Extension(n) => { let n_path = n.path.to_vec(); let n_ptr = n.chd(); @@ -522,13 +528,13 @@ impl + Send + Sync> Merkle { None } NodeType::Leaf(n) => { - if n.0.len() == 0 { - n.1 = Data(val); + if n.path.len() == 0 { + n.data = Data(val); None } else { - let idx = n.0[0]; - n.0 = PartialPath(n.0[1..].to_vec()); + let idx = n.path[0]; + n.path = PartialPath(n.path[1..].to_vec()); u.rehash(); Some((idx, true, None, val)) @@ -557,7 +563,7 @@ impl + Send + Sync> Merkle { }; if let Some((idx, more, ext, val)) = info { - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let c_ptr = if more { u_ptr @@ -570,6 +576,7 @@ impl + Send + Sync> Merkle { let branch = self .put_node(Node::branch(BranchNode { + // path: vec![].into(), children: chd, value: Some(Data(val)), children_encoded: Default::default(), @@ -687,7 +694,7 @@ impl + Send + Sync> Merkle { // to: [p: Branch] -> [Leaf/Ext] let write_result = c_ref.write(|c| { let partial_path = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -728,7 +735,7 @@ impl + Send + Sync> Merkle { let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -811,7 +818,7 @@ impl + Send + Sync> Merkle { // to: [Branch] -> [Leaf/Ext] let write_result = c_ref.write(|c| { match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), } @@ -840,7 +847,7 @@ impl + Send + Sync> Merkle { let mut path = n.path.clone().into_inner(); path.push(idx); let path0 = match &mut c.inner { - NodeType::Leaf(n) => &mut n.0, + NodeType::Leaf(n) => &mut n.path, NodeType::Extension(n) => &mut n.path, _ => unreachable!(), }; @@ -903,7 +910,7 @@ impl + Send + Sync> Merkle { } NodeType::Leaf(n) => { - found = Some(n.1.clone()); + found = Some(n.data.clone()); deleted.push(node_ref.as_ptr()); self.after_remove_leaf(&mut parents, &mut deleted)? } @@ -1008,7 +1015,7 @@ impl + Send + Sync> Merkle { None => return Ok(None), }, NodeType::Leaf(n) => { - let node_ref = if once(nib).chain(key_nibbles).eq(n.0.iter().copied()) { + let node_ref = if once(nib).chain(key_nibbles).eq(n.path.iter().copied()) { Some(node_ref) } else { None @@ -1043,7 +1050,7 @@ impl + Send + Sync> Merkle { // when we're done iterating over nibbles, check if the node we're at has a value let node_ref = match &node_ref.inner { NodeType::Branch(n) if n.value.as_ref().is_some() => Some(node_ref), - NodeType::Leaf(n) if n.0.len() == 0 => Some(node_ref), + NodeType::Leaf(n) if n.path.len() == 0 => Some(node_ref), _ => None, }; @@ -1142,7 +1149,7 @@ impl + Send + Sync> Merkle { } } NodeType::Leaf(n) => { - if n.0.len() == 0 { + if n.path.len() == 0 { nodes.push(u_ref.as_ptr()); } } @@ -1381,7 +1388,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal let returned_key_value = match last_node.inner() { NodeType::Branch(branch) => (key, branch.value.to_owned().unwrap().to_vec()), - NodeType::Leaf(leaf) => (key, leaf.1.to_vec()), + NodeType::Leaf(leaf) => (key, leaf.data.to_vec()), NodeType::Extension(_) => todo!(), }; @@ -1511,7 +1518,7 @@ impl<'a, S: shale::ShaleStore + Send + Sync> Stream for MerkleKeyVal } => { let value = match last_node.inner() { NodeType::Branch(branch) => branch.value.to_owned().unwrap().to_vec(), - NodeType::Leaf(leaf) => leaf.1.to_vec(), + NodeType::Leaf(leaf) => leaf.data.to_vec(), NodeType::Extension(_) => todo!(), }; @@ -1535,7 +1542,7 @@ fn key_from_parents_and_leaf(parents: &[(ObjRef, u8)], leaf: &LeafNode) -> Vec std::ops::Deref for Ref<'a> { fn deref(&self) -> &[u8] { match &self.0.inner { NodeType::Branch(n) => n.value.as_ref().unwrap(), - NodeType::Leaf(n) => &n.1, + NodeType::Leaf(n) => &n.data, _ => unreachable!(), } } @@ -1604,7 +1611,7 @@ impl<'a, S: ShaleStore + Send + Sync> RefMut<'a, S> { |u| { modify(match &mut u.inner { NodeType::Branch(n) => &mut n.value.as_mut().unwrap().0, - NodeType::Leaf(n) => &mut n.1 .0, + NodeType::Leaf(n) => &mut n.data.0, _ => unreachable!(), }); u.rehash() @@ -1683,13 +1690,14 @@ mod tests { fn branch(value: Vec, encoded_child: Option>) -> Node { let children = Default::default(); let value = Some(Data(value)); - let mut children_encoded = <[Option>; NBRANCH]>::default(); + let mut children_encoded = <[Option>; MAX_CHILDREN]>::default(); if let Some(child) = encoded_child { children_encoded[0] = Some(child); } Node::branch(BranchNode { + // path: vec![].into(), children, value, children_encoded, @@ -1712,7 +1720,7 @@ mod tests { } #[test] - fn insert_and_retrieve() { + fn insert_and_retrieve_one() { let key = b"hello"; let val = b"world"; diff --git a/firewood/src/merkle/node.rs b/firewood/src/merkle/node.rs index 604b38d2e..0b2453e46 100644 --- a/firewood/src/merkle/node.rs +++ b/firewood/src/merkle/node.rs @@ -3,11 +3,12 @@ use crate::shale::{disk_address::DiskAddress, CachedStore, ShaleError, ShaleStore, Storable}; use bincode::{Error, Options}; +use bitflags::bitflags; use enum_as_inner::EnumAsInner; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sha3::{Digest, Keccak256}; use std::{ - fmt::{self, Debug}, + fmt::Debug, io::{Cursor, Read, Write}, sync::{ atomic::{AtomicBool, Ordering}, @@ -15,15 +16,28 @@ use std::{ }, }; +mod branch; +mod extension; +mod leaf; +mod partial_path; + +pub use branch::{BranchNode, MAX_CHILDREN, SIZE as BRANCH_NODE_SIZE}; +pub use extension::{ExtNode, SIZE as EXTENSION_NODE_SIZE}; +pub use leaf::LeafNode; +pub use partial_path::PartialPath; + use crate::merkle::to_nibble_array; use crate::nibbles::Nibbles; -use super::{from_nibbles, PartialPath, TrieHash, TRIE_HASH_LEN}; - -pub const NBRANCH: usize = 16; +use super::{from_nibbles, TrieHash, TRIE_HASH_LEN}; -const EXT_NODE_SIZE: usize = 2; -const BRANCH_NODE_SIZE: usize = 17; +bitflags! { + // should only ever be the size of a nibble + struct Flags: u8 { + const TERMINAL = 0b0010; + const ODD_LEN = 0b0001; + } +} #[derive(Debug, PartialEq, Eq, Clone)] pub struct Data(pub(super) Vec); @@ -35,8 +49,14 @@ impl std::ops::Deref for Data { } } +impl From> for Data { + fn from(v: Vec) -> Self { + Self(v) + } +} + #[derive(Serialize, Deserialize, Debug)] -pub(crate) enum Encoded { +enum Encoded { Raw(T), Data(T), } @@ -57,269 +77,6 @@ impl> Encoded { } } -#[derive(PartialEq, Eq, Clone)] -pub struct BranchNode { - pub(super) children: [Option; NBRANCH], - pub(super) value: Option, - pub(super) children_encoded: [Option>; NBRANCH], -} - -impl Debug for BranchNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Branch")?; - for (i, c) in self.children.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {c:?})")?; - } - } - for (i, c) in self.children_encoded.iter().enumerate() { - if let Some(c) = c { - write!(f, " ({i:x} {:?})", c)?; - } - } - write!( - f, - " v={}]", - match &self.value { - Some(v) => hex::encode(&**v), - None => "nil".to_string(), - } - ) - } -} - -impl BranchNode { - pub(super) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { - let mut has_chd = false; - let mut only_chd = None; - for (i, c) in self.children.iter().enumerate() { - if c.is_some() { - has_chd = true; - if only_chd.is_some() { - only_chd = None; - break; - } - only_chd = (*c).map(|e| (e, i as u8)) - } - } - (only_chd, has_chd) - } - - pub fn decode(buf: &[u8]) -> Result { - let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; - - // we've already validated the size, that's why we can safely unwrap - let data = items.pop().unwrap().decode()?; - // Extract the value of the branch node and set to None if it's an empty Vec - let value = Some(data).filter(|data| !data.is_empty()); - - // encode all children. - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); - - // we popped the last element, so their should only be NBRANCH items left - for (i, chd) in items.into_iter().enumerate() { - let data = chd.decode()?; - chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); - } - - Ok(BranchNode::new([None; NBRANCH], value, chd_encoded)) - } - - fn encode>(&self, store: &S) -> Vec { - let mut list = <[Encoded>; NBRANCH + 1]>::default(); - - for (i, c) in self.children.iter().enumerate() { - match c { - Some(c) => { - let mut c_ref = store.get_item(*c).unwrap(); - - if c_ref.is_encoded_longer_than_hash_len::(store) { - list[i] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*c_ref.get_root_hash::(store))[..]) - .unwrap(), - ); - - // See struct docs for ordering requirements - if c_ref.lazy_dirty.load(Ordering::Relaxed) { - c_ref.write(|_| {}).unwrap(); - c_ref.lazy_dirty.store(false, Ordering::Relaxed) - } - } else { - let child_encoded = &c_ref.get_encoded::(store); - list[i] = Encoded::Raw(child_encoded.to_vec()); - } - } - None => { - // Check if there is already a calculated encoded value for the child, which - // can happen when manually constructing a trie from proof. - if let Some(v) = &self.children_encoded[i] { - if v.len() == TRIE_HASH_LEN { - list[i] = - Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); - } else { - list[i] = Encoded::Raw(v.clone()); - } - } - } - }; - } - - if let Some(Data(val)) = &self.value { - list[NBRANCH] = Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); - } - - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .unwrap() - } - - pub fn new( - chd: [Option; NBRANCH], - value: Option>, - chd_encoded: [Option>; NBRANCH], - ) -> Self { - BranchNode { - children: chd, - value: value.map(Data), - children_encoded: chd_encoded, - } - } - - pub fn value(&self) -> &Option { - &self.value - } - - pub fn chd(&self) -> &[Option; NBRANCH] { - &self.children - } - - pub fn chd_mut(&mut self) -> &mut [Option; NBRANCH] { - &mut self.children - } - - pub fn chd_encode(&self) -> &[Option>; NBRANCH] { - &self.children_encoded - } - - pub fn chd_encoded_mut(&mut self) -> &mut [Option>; NBRANCH] { - &mut self.children_encoded - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct LeafNode(pub(super) PartialPath, pub(super) Data); - -impl Debug for LeafNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "[Leaf {:?} {}]", self.0, hex::encode(&*self.1)) - } -} - -impl LeafNode { - fn encode(&self) -> Vec { - bincode::DefaultOptions::new() - .serialize( - [ - Encoded::Raw(from_nibbles(&self.0.encode(true)).collect()), - Encoded::Raw(self.1.to_vec()), - ] - .as_slice(), - ) - .unwrap() - } - - pub fn new(path: Vec, data: Vec) -> Self { - LeafNode(PartialPath(path), Data(data)) - } - - pub fn path(&self) -> &PartialPath { - &self.0 - } - - pub fn data(&self) -> &Data { - &self.1 - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct ExtNode { - pub(crate) path: PartialPath, - pub(crate) child: DiskAddress, - pub(crate) child_encoded: Option>, -} - -impl Debug for ExtNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let Self { - path, - child, - child_encoded, - } = self; - write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) - } -} - -impl ExtNode { - fn encode>(&self, store: &S) -> Vec { - let mut list = <[Encoded>; 2]>::default(); - list[0] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) - .unwrap(), - ); - - if !self.child.is_null() { - let mut r = store.get_item(self.child).unwrap(); - - if r.is_encoded_longer_than_hash_len(store) { - list[1] = Encoded::Data( - bincode::DefaultOptions::new() - .serialize(&&(*r.get_root_hash(store))[..]) - .unwrap(), - ); - - if r.lazy_dirty.load(Ordering::Relaxed) { - r.write(|_| {}).unwrap(); - r.lazy_dirty.store(false, Ordering::Relaxed); - } - } else { - list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); - } - } else { - // Check if there is already a caclucated encoded value for the child, which - // can happen when manually constructing a trie from proof. - if let Some(v) = &self.child_encoded { - if v.len() == TRIE_HASH_LEN { - list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); - } else { - list[1] = Encoded::Raw(v.clone()); - } - } - } - - bincode::DefaultOptions::new() - .serialize(list.as_slice()) - .unwrap() - } - - pub fn chd(&self) -> DiskAddress { - self.child - } - - pub fn chd_encoded(&self) -> Option<&[u8]> { - self.child_encoded.as_deref() - } - - pub fn chd_mut(&mut self) -> &mut DiskAddress { - &mut self.child - } - - pub fn chd_encoded_mut(&mut self) -> &mut Option> { - &mut self.child_encoded - } -} - #[derive(Debug)] pub struct Node { pub(super) root_hash: OnceLock, @@ -333,6 +90,7 @@ pub struct Node { } impl Eq for Node {} + impl PartialEq for Node { fn eq(&self, other: &Self) -> bool { let Node { @@ -349,6 +107,7 @@ impl PartialEq for Node { && *inner == other.inner } } + impl Clone for Node { fn clone(&self) -> Self { Self { @@ -374,7 +133,7 @@ impl NodeType { let items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; match items.len() { - EXT_NODE_SIZE => { + EXTENSION_NODE_SIZE => { let mut items = items.into_iter(); let decoded_key: Vec = items.next().unwrap().decode()?; @@ -383,6 +142,7 @@ impl NodeType { let (cur_key_path, term) = PartialPath::from_nibbles(decoded_key_nibbles.into_iter()); + let cur_key = cur_key_path.into_inner(); let data: Vec = items.next().unwrap().decode()?; @@ -396,6 +156,7 @@ impl NodeType { })) } } + // TODO: add path BRANCH_NODE_SIZE => Ok(NodeType::Branch(BranchNode::decode(buf)?)), size => Err(Box::new(bincode::ErrorKind::Custom(format!( "invalid size: {size}" @@ -411,14 +172,12 @@ impl NodeType { } } - pub fn path_mut(&mut self) -> Option<&mut PartialPath> { - let path = match self { - NodeType::Branch(_) => return None, - NodeType::Leaf(node) => &mut node.0, + pub fn path_mut(&mut self) -> &mut PartialPath { + match self { + NodeType::Branch(_u) => todo!(), + NodeType::Leaf(node) => &mut node.path, NodeType::Extension(node) => &mut node.path, - }; - - path.into() + } } } @@ -449,7 +208,8 @@ impl Node { is_encoded_longer_than_hash_len: OnceLock::new(), encoded: OnceLock::new(), inner: NodeType::Branch(BranchNode { - children: [Some(DiskAddress::null()); NBRANCH], + // path: vec![].into(), + children: [Some(DiskAddress::null()); MAX_CHILDREN], value: Some(Data(Vec::new())), children_encoded: Default::default(), }), @@ -488,7 +248,7 @@ impl Node { } pub fn leaf(path: PartialPath, data: Data) -> Self { - Self::from(NodeType::Leaf(LeafNode(path, data))) + Self::from(NodeType::Leaf(LeafNode { path, data })) } pub fn inner(&self) -> &NodeType { @@ -527,31 +287,37 @@ impl Node { impl Storable for Node { fn deserialize(addr: usize, mem: &T) -> Result { - const META_SIZE: usize = 32 + 1 + 1; + const META_SIZE: usize = TRIE_HASH_LEN + 1 + 1; + let meta_raw = mem.get_view(addr, META_SIZE as u64) .ok_or(ShaleError::InvalidCacheView { offset: addr, size: META_SIZE as u64, })?; - let attrs = meta_raw.as_deref()[32]; + + let attrs = meta_raw.as_deref()[TRIE_HASH_LEN]; + let root_hash = if attrs & Node::ROOT_HASH_VALID_BIT == 0 { None } else { Some(TrieHash( - meta_raw.as_deref()[0..32] + meta_raw.as_deref()[..TRIE_HASH_LEN] .try_into() .expect("invalid slice"), )) }; + let is_encoded_longer_than_hash_len = if attrs & Node::IS_ENCODED_BIG_VALID == 0 { None } else { Some(attrs & Node::LONG_BIT != 0) }; + match meta_raw.as_deref()[33] { Self::BRANCH_NODE => { - let branch_header_size = NBRANCH as u64 * 8 + 4; + // TODO: add path + let branch_header_size = MAX_CHILDREN as u64 * 8 + 4; let node_raw = mem.get_view(addr + META_SIZE, branch_header_size).ok_or( ShaleError::InvalidCacheView { offset: addr + META_SIZE, @@ -559,7 +325,7 @@ impl Storable for Node { }, )?; let mut cur = Cursor::new(node_raw.as_deref()); - let mut chd = [None; NBRANCH]; + let mut chd = [None; MAX_CHILDREN]; let mut buff = [0; 8]; for chd in chd.iter_mut() { cur.read_exact(&mut buff)?; @@ -583,7 +349,7 @@ impl Storable for Node { .as_deref(), )) }; - let mut chd_encoded: [Option>; NBRANCH] = Default::default(); + let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); let offset = if raw_len == u32::MAX as u64 { addr + META_SIZE + branch_header_size as usize } else { @@ -619,12 +385,14 @@ impl Storable for Node { root_hash, is_encoded_longer_than_hash_len, NodeType::Branch(BranchNode { + // path: vec![].into(), children: chd, value, children_encoded: chd_encoded, }), )) } + Self::EXT_NODE => { let ext_header_size = 1 + 8; let node_raw = mem.get_view(addr + META_SIZE, ext_header_size).ok_or( @@ -663,9 +431,12 @@ impl Storable for Node { offset: addr + META_SIZE + ext_header_size as usize + path_len as usize, size: 1, })?; + cur = Cursor::new(encoded_len_raw.as_deref()); cur.read_exact(&mut buff)?; + let encoded_len = buff[0] as u64; + let encoded: Option> = if encoded_len != 0 { let emcoded_raw = mem .get_view( @@ -686,7 +457,7 @@ impl Storable for Node { None }; - Ok(Self::new_from_hash( + let node = Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, NodeType::Extension(ExtNode { @@ -694,8 +465,11 @@ impl Storable for Node { child: DiskAddress::from(ptr as usize), child_encoded: encoded, }), - )) + ); + + Ok(node) } + Self::LEAF_NODE => { let leaf_header_size = 1 + 4; let node_raw = mem.get_view(addr + META_SIZE, leaf_header_size).ok_or( @@ -704,11 +478,15 @@ impl Storable for Node { size: leaf_header_size, }, )?; + let mut cur = Cursor::new(node_raw.as_deref()); let mut buff = [0; 4]; cur.read_exact(&mut buff[..1])?; + let path_len = buff[0] as u64; + cur.read_exact(&mut buff)?; + let data_len = u32::from_le_bytes(buff) as u64; let remainder = mem .get_view( @@ -728,12 +506,15 @@ impl Storable for Node { .collect(); let (path, _) = PartialPath::decode(&nibbles); - let value = Data(remainder.as_deref()[path_len as usize..].to_vec()); - Ok(Self::new_from_hash( + let data = Data(remainder.as_deref()[path_len as usize..].to_vec()); + + let node = Self::new_from_hash( root_hash, is_encoded_longer_than_hash_len, - NodeType::Leaf(LeafNode(path, value)), - )) + NodeType::Leaf(LeafNode { path, data }), + ); + + Ok(node) } _ => Err(ShaleError::InvalidNodeType), } @@ -744,6 +525,7 @@ impl Storable for Node { + 1 + match &self.inner { NodeType::Branch(n) => { + // TODO: add path let mut encoded_len = 0; for emcoded in n.children_encoded.iter() { encoded_len += match emcoded { @@ -751,7 +533,7 @@ impl Storable for Node { None => 1, } } - NBRANCH as u64 * 8 + MAX_CHILDREN as u64 * 8 + 4 + match &n.value { Some(val) => val.len() as u64, @@ -761,13 +543,13 @@ impl Storable for Node { } NodeType::Extension(n) => { 1 + 8 - + n.path.dehydrated_len() + + n.path.serialized_len() + match n.chd_encoded() { Some(v) => 1 + v.len() as u64, None => 1, } } - NodeType::Leaf(n) => 1 + 4 + n.0.dehydrated_len() + n.1.len() as u64, + NodeType::Leaf(n) => 1 + 4 + n.path.serialized_len() + n.data.len() as u64, } } @@ -793,6 +575,7 @@ impl Storable for Node { match &self.inner { NodeType::Branch(n) => { + // TODO: add path cur.write_all(&[Self::BRANCH_NODE]).unwrap(); for c in n.children.iter() { cur.write_all(&match c { @@ -836,11 +619,11 @@ impl Storable for Node { } NodeType::Leaf(n) => { cur.write_all(&[Self::LEAF_NODE])?; - let path: Vec = from_nibbles(&n.0.encode(true)).collect(); + let path: Vec = from_nibbles(&n.path.encode(true)).collect(); cur.write_all(&[path.len() as u8])?; - cur.write_all(&(n.1.len() as u32).to_le_bytes())?; + cur.write_all(&(n.data.len() as u32).to_le_bytes())?; cur.write_all(&path)?; - cur.write_all(&n.1).map_err(ShaleError::Io) + cur.write_all(&n.data).map_err(ShaleError::Io) } } } @@ -863,8 +646,8 @@ pub(super) mod tests { value: Option>, repeated_encoded_child: Option>, ) -> Node { - let children: [Option; NBRANCH] = from_fn(|i| { - if i < NBRANCH / 2 { + let children: [Option; MAX_CHILDREN] = from_fn(|i| { + if i < MAX_CHILDREN / 2 { DiskAddress::from(repeated_disk_address).into() } else { None @@ -874,7 +657,7 @@ pub(super) mod tests { let children_encoded = repeated_encoded_child .map(|child| { from_fn(|i| { - if i < NBRANCH / 2 { + if i < MAX_CHILDREN / 2 { child.clone().into() } else { None @@ -884,6 +667,7 @@ pub(super) mod tests { .unwrap_or_default(); Node::branch(BranchNode { + // path: vec![].into(), children, value: value.map(Data), children_encoded, diff --git a/firewood/src/merkle/node/branch.rs b/firewood/src/merkle/node/branch.rs new file mode 100644 index 000000000..9a6469511 --- /dev/null +++ b/firewood/src/merkle/node/branch.rs @@ -0,0 +1,184 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::{Data, Encoded, Node}; +use crate::{ + merkle::{PartialPath, TRIE_HASH_LEN}, + shale::DiskAddress, + shale::ShaleStore, +}; +use bincode::{Error, Options}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + sync::atomic::Ordering, +}; + +pub const MAX_CHILDREN: usize = 16; +pub const SIZE: usize = MAX_CHILDREN + 1; + +#[derive(PartialEq, Eq, Clone)] +pub struct BranchNode { + // pub(crate) path: PartialPath, + pub(crate) children: [Option; MAX_CHILDREN], + pub(crate) value: Option, + pub(crate) children_encoded: [Option>; MAX_CHILDREN], +} + +impl Debug for BranchNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "[Branch")?; + // write!(f, " path={:?}", self.path)?; + + for (i, c) in self.children.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {c:?})")?; + } + } + + for (i, c) in self.children_encoded.iter().enumerate() { + if let Some(c) = c { + write!(f, " ({i:x} {:?})", c)?; + } + } + + write!( + f, + " v={}]", + match &self.value { + Some(v) => hex::encode(&**v), + None => "nil".to_string(), + } + ) + } +} + +impl BranchNode { + pub fn new( + _path: PartialPath, + chd: [Option; MAX_CHILDREN], + value: Option>, + chd_encoded: [Option>; MAX_CHILDREN], + ) -> Self { + BranchNode { + // path, + children: chd, + value: value.map(Data), + children_encoded: chd_encoded, + } + } + + pub fn value(&self) -> &Option { + &self.value + } + + pub fn chd(&self) -> &[Option; MAX_CHILDREN] { + &self.children + } + + pub fn chd_mut(&mut self) -> &mut [Option; MAX_CHILDREN] { + &mut self.children + } + + pub fn chd_encode(&self) -> &[Option>; MAX_CHILDREN] { + &self.children_encoded + } + + pub fn chd_encoded_mut(&mut self) -> &mut [Option>; MAX_CHILDREN] { + &mut self.children_encoded + } + + pub(crate) fn single_child(&self) -> (Option<(DiskAddress, u8)>, bool) { + let mut has_chd = false; + let mut only_chd = None; + for (i, c) in self.children.iter().enumerate() { + if c.is_some() { + has_chd = true; + if only_chd.is_some() { + only_chd = None; + break; + } + only_chd = (*c).map(|e| (e, i as u8)) + } + } + (only_chd, has_chd) + } + + pub(super) fn decode(buf: &[u8]) -> Result { + let mut items: Vec>> = bincode::DefaultOptions::new().deserialize(buf)?; + + // we've already validated the size, that's why we can safely unwrap + let data = items.pop().unwrap().decode()?; + // Extract the value of the branch node and set to None if it's an empty Vec + let value = Some(data).filter(|data| !data.is_empty()); + + // encode all children. + let mut chd_encoded: [Option>; MAX_CHILDREN] = Default::default(); + + // we popped the last element, so their should only be NBRANCH items left + for (i, chd) in items.into_iter().enumerate() { + let data = chd.decode()?; + chd_encoded[i] = Some(data).filter(|data| !data.is_empty()); + } + + // TODO: add path + let path = Vec::new().into(); + + Ok(BranchNode::new( + path, + [None; MAX_CHILDREN], + value, + chd_encoded, + )) + } + + pub(super) fn encode>(&self, store: &S) -> Vec { + // TODO: add path to encoded node + let mut list = <[Encoded>; MAX_CHILDREN + 1]>::default(); + + for (i, c) in self.children.iter().enumerate() { + match c { + Some(c) => { + let mut c_ref = store.get_item(*c).unwrap(); + + if c_ref.is_encoded_longer_than_hash_len::(store) { + list[i] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*c_ref.get_root_hash::(store))[..]) + .unwrap(), + ); + + // See struct docs for ordering requirements + if c_ref.lazy_dirty.load(Ordering::Relaxed) { + c_ref.write(|_| {}).unwrap(); + c_ref.lazy_dirty.store(false, Ordering::Relaxed) + } + } else { + let child_encoded = &c_ref.get_encoded::(store); + list[i] = Encoded::Raw(child_encoded.to_vec()); + } + } + None => { + // Check if there is already a calculated encoded value for the child, which + // can happen when manually constructing a trie from proof. + if let Some(v) = &self.children_encoded[i] { + if v.len() == TRIE_HASH_LEN { + list[i] = + Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); + } else { + list[i] = Encoded::Raw(v.clone()); + } + } + } + }; + } + + if let Some(Data(val)) = &self.value { + list[MAX_CHILDREN] = + Encoded::Data(bincode::DefaultOptions::new().serialize(val).unwrap()); + } + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() + } +} diff --git a/firewood/src/merkle/node/extension.rs b/firewood/src/merkle/node/extension.rs new file mode 100644 index 000000000..28f9549cd --- /dev/null +++ b/firewood/src/merkle/node/extension.rs @@ -0,0 +1,94 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use bincode::Options; + +use super::{Encoded, Node}; +use crate::{ + merkle::{from_nibbles, PartialPath, TRIE_HASH_LEN}, + shale::{DiskAddress, ShaleStore}, +}; +use std::{ + fmt::{Debug, Error as FmtError, Formatter}, + sync::atomic::Ordering, +}; + +pub const SIZE: usize = 2; + +#[derive(PartialEq, Eq, Clone)] +pub struct ExtNode { + pub(crate) path: PartialPath, + pub(crate) child: DiskAddress, + pub(crate) child_encoded: Option>, +} + +impl Debug for ExtNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + let Self { + path, + child, + child_encoded, + } = self; + write!(f, "[Extension {path:?} {child:?} {child_encoded:?}]",) + } +} + +impl ExtNode { + pub(super) fn encode>(&self, store: &S) -> Vec { + let mut list = <[Encoded>; 2]>::default(); + list[0] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&from_nibbles(&self.path.encode(false)).collect::>()) + .unwrap(), + ); + + if !self.child.is_null() { + let mut r = store.get_item(self.child).unwrap(); + + if r.is_encoded_longer_than_hash_len(store) { + list[1] = Encoded::Data( + bincode::DefaultOptions::new() + .serialize(&&(*r.get_root_hash(store))[..]) + .unwrap(), + ); + + if r.lazy_dirty.load(Ordering::Relaxed) { + r.write(|_| {}).unwrap(); + r.lazy_dirty.store(false, Ordering::Relaxed); + } + } else { + list[1] = Encoded::Raw(r.get_encoded(store).to_vec()); + } + } else { + // Check if there is already a caclucated encoded value for the child, which + // can happen when manually constructing a trie from proof. + if let Some(v) = &self.child_encoded { + if v.len() == TRIE_HASH_LEN { + list[1] = Encoded::Data(bincode::DefaultOptions::new().serialize(v).unwrap()); + } else { + list[1] = Encoded::Raw(v.clone()); + } + } + } + + bincode::DefaultOptions::new() + .serialize(list.as_slice()) + .unwrap() + } + + pub fn chd(&self) -> DiskAddress { + self.child + } + + pub fn chd_encoded(&self) -> Option<&[u8]> { + self.child_encoded.as_deref() + } + + pub fn chd_mut(&mut self) -> &mut DiskAddress { + &mut self.child + } + + pub fn chd_encoded_mut(&mut self) -> &mut Option> { + &mut self.child_encoded + } +} diff --git a/firewood/src/merkle/node/leaf.rs b/firewood/src/merkle/node/leaf.rs new file mode 100644 index 000000000..0418ab888 --- /dev/null +++ b/firewood/src/merkle/node/leaf.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use std::fmt::{Debug, Error as FmtError, Formatter}; + +use bincode::Options; + +use super::{Data, Encoded}; +use crate::merkle::{from_nibbles, PartialPath}; + +#[derive(PartialEq, Eq, Clone)] +pub struct LeafNode { + pub(crate) path: PartialPath, + pub(crate) data: Data, +} + +impl Debug for LeafNode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + write!(f, "[Leaf {:?} {}]", self.path, hex::encode(&*self.data)) + } +} + +impl LeafNode { + pub fn new, D: Into>(path: P, data: D) -> Self { + Self { + path: path.into(), + data: data.into(), + } + } + + pub fn path(&self) -> &PartialPath { + &self.path + } + + pub fn data(&self) -> &Data { + &self.data + } + + pub(super) fn encode(&self) -> Vec { + bincode::DefaultOptions::new() + .serialize( + [ + Encoded::Raw(from_nibbles(&self.path.encode(true)).collect()), + Encoded::Raw(self.data.to_vec()), + ] + .as_slice(), + ) + .unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // these tests will fail if the encoding mechanism changes and should be updated accordingly + #[test_case(0b10 << 4, vec![0x12, 0x34], vec![1, 2, 3, 4]; "even length")] + // first nibble is part of the prefix + #[test_case((0b11 << 4) + 2, vec![0x34], vec![2, 3, 4]; "odd length")] + fn encode_regression_test(prefix: u8, path: Vec, nibbles: Vec) { + let data = vec![5, 6, 7, 8]; + + let serialized_path = [vec![prefix], path.clone()].concat(); + // 0 represents Encoded::Raw + let serialized_path = [vec![0, serialized_path.len() as u8], serialized_path].concat(); + let serialized_data = [vec![0, data.len() as u8], data.clone()].concat(); + + let serialized = [vec![2], serialized_path, serialized_data].concat(); + + let node = LeafNode::new(nibbles, data.clone()); + + assert_eq!(node.encode(), serialized); + } +} diff --git a/firewood/src/merkle/node/partial_path.rs b/firewood/src/merkle/node/partial_path.rs new file mode 100644 index 000000000..d213dd05d --- /dev/null +++ b/firewood/src/merkle/node/partial_path.rs @@ -0,0 +1,129 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE.md for licensing terms. + +use super::Flags; +use crate::nibbles::NibblesIterator; +use std::{ + fmt::{self, Debug}, + iter::once, +}; + +// TODO: use smallvec +/// PartialPath keeps a list of nibbles to represent a path on the Trie. +#[derive(PartialEq, Eq, Clone)] +pub struct PartialPath(pub Vec); + +impl Debug for PartialPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + for nib in self.0.iter() { + write!(f, "{:x}", *nib & 0xf)?; + } + Ok(()) + } +} + +impl std::ops::Deref for PartialPath { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl From> for PartialPath { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl PartialPath { + pub fn into_inner(self) -> Vec { + self.0 + } + + pub(super) fn encode(&self, is_terminal: bool) -> Vec { + let mut flags = Flags::empty(); + + if is_terminal { + flags.insert(Flags::TERMINAL); + } + + let has_odd_len = self.0.len() & 1 == 1; + + let extra_byte = if has_odd_len { + flags.insert(Flags::ODD_LEN); + + None + } else { + Some(0) + }; + + once(flags.bits()) + .chain(extra_byte) + .chain(self.0.iter().copied()) + .collect() + } + + // TODO: remove all non `Nibbles` usages and delete this function. + // I also think `PartialPath` could probably borrow instead of own data. + // + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn decode(raw: &[u8]) -> (Self, bool) { + let mut raw = raw.iter().copied(); + let flags = Flags::from_bits_retain(raw.next().unwrap_or_default()); + + if !flags.contains(Flags::ODD_LEN) { + let _ = raw.next(); + } + + (Self(raw.collect()), flags.contains(Flags::TERMINAL)) + } + + /// returns a tuple of the decoded partial path and whether the path is terminal + pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { + let flags = Flags::from_bits_retain(nibbles.next().unwrap_or_default()); + + if !flags.contains(Flags::ODD_LEN) { + let _ = nibbles.next(); + } + + (Self(nibbles.collect()), flags.contains(Flags::TERMINAL)) + } + + pub(super) fn serialized_len(&self) -> u64 { + let len = self.0.len(); + + // if len is even the prefix takes an extra byte + // otherwise is combined with the first nibble + let len = if len & 1 == 1 { + (len + 1) / 2 + } else { + len / 2 + 1 + }; + + len as u64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ops::Deref; + use test_case::test_case; + + #[test_case(&[1, 2, 3, 4], true)] + #[test_case(&[1, 2, 3], false)] + #[test_case(&[0, 1, 2], false)] + #[test_case(&[1, 2], true)] + #[test_case(&[1], true)] + fn test_encoding(steps: &[u8], term: bool) { + let path = PartialPath(steps.to_vec()); + let encoded = path.encode(term); + + assert_eq!(encoded.len(), path.serialized_len() as usize * 2); + + let (decoded, decoded_term) = PartialPath::decode(&encoded); + + assert_eq!(&decoded.deref(), &steps); + assert_eq!(decoded_term, term); + } +} diff --git a/firewood/src/merkle/partial_path.rs b/firewood/src/merkle/partial_path.rs deleted file mode 100644 index eb2bd94d8..000000000 --- a/firewood/src/merkle/partial_path.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE.md for licensing terms. - -use crate::nibbles::NibblesIterator; -use std::fmt::{self, Debug}; - -/// PartialPath keeps a list of nibbles to represent a path on the Trie. -#[derive(PartialEq, Eq, Clone)] -pub struct PartialPath(pub Vec); - -impl Debug for PartialPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - for nib in self.0.iter() { - write!(f, "{:x}", *nib & 0xf)?; - } - Ok(()) - } -} - -impl std::ops::Deref for PartialPath { - type Target = [u8]; - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl PartialPath { - pub fn into_inner(self) -> Vec { - self.0 - } - - pub(super) fn encode(&self, term: bool) -> Vec { - let odd_len = (self.0.len() & 1) as u8; - let flags = if term { 2 } else { 0 } + odd_len; - let mut res = if odd_len == 1 { - vec![flags] - } else { - vec![flags, 0x0] - }; - res.extend(&self.0); - res - } - - // TODO: remove all non `Nibbles` usages and delete this function. - // I also think `PartialPath` could probably borrow instead of own data. - // - /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn decode(raw: &[u8]) -> (Self, bool) { - let prefix = raw[0]; - let is_odd = (prefix & 1) as usize; - let decoded = raw.iter().skip(1).skip(1 - is_odd).copied().collect(); - - (Self(decoded), prefix > 1) - } - - /// returns a tuple of the decoded partial path and whether the path is terminal - pub fn from_nibbles(mut nibbles: NibblesIterator<'_, N>) -> (Self, bool) { - let prefix = nibbles.next().unwrap(); - let is_odd = (prefix & 1) as usize; - let decoded = nibbles.skip(1 - is_odd).collect(); - - (Self(decoded), prefix > 1) - } - - pub(super) fn dehydrated_len(&self) -> u64 { - let len = self.0.len() as u64; - if len & 1 == 1 { - (len + 1) >> 1 - } else { - (len >> 1) + 1 - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - #[test_case(&[1, 2, 3, 4], true)] - #[test_case(&[1, 2, 3], false)] - #[test_case(&[0, 1, 2], false)] - #[test_case(&[1, 2], true)] - #[test_case(&[1], true)] - fn test_encoding(steps: &[u8], term: bool) { - let path = PartialPath(steps.to_vec()).encode(term); - let (decoded, decoded_term) = PartialPath::decode(&path); - assert_eq!(&decoded.0, &steps); - assert_eq!(decoded_term, term); - } -} diff --git a/firewood/src/shale/mod.rs b/firewood/src/shale/mod.rs index 7d1d549d6..1a2d69a92 100644 --- a/firewood/src/shale/mod.rs +++ b/firewood/src/shale/mod.rs @@ -1,7 +1,7 @@ // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE.md for licensing terms. -use disk_address::DiskAddress; +pub(crate) use disk_address::DiskAddress; use std::any::type_name; use std::collections::{HashMap, HashSet}; use std::fmt::{self, Debug, Formatter};