firewood_storage/
lib.rs

1// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4#![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)]
5#![deny(unsafe_code)]
6#![cfg_attr(
7    not(target_pointer_width = "64"),
8    forbid(
9        clippy::cast_possible_truncation,
10        reason = "non-64 bit target likely to cause issues during u64 to usize conversions"
11    )
12)]
13
14//! # storage implements the storage of a [Node] on top of a `LinearStore`
15//!
16//! Nodes are stored at a [`LinearAddress`] within a [`ReadableStorage`].
17//!
18//! The [`NodeStore`] maintains a free list and the [`LinearAddress`] of a root node.
19//!
20//! A [`NodeStore`] is backed by a [`ReadableStorage`] which is persisted storage.
21
22use std::fmt::{Display, Formatter, LowerHex, Result};
23use std::ops::Range;
24
25mod checker;
26mod hashednode;
27mod hashers;
28mod iter;
29mod linear;
30mod node;
31mod nodestore;
32#[cfg(any(test, feature = "test_utils"))]
33mod test_utils;
34mod trie_hash;
35
36/// Logger module for handling logging functionality
37pub mod logger;
38
39#[macro_use]
40/// Macros module for defining macros used in the storage module
41pub mod macros;
42// re-export these so callers don't need to know where they are
43pub use checker::{CheckOpt, CheckerReport, DBStats, FreeListsStats, TrieStats};
44pub use hashednode::{Hashable, Preimage, ValueDigest, hash_node, hash_preimage};
45pub use linear::{FileIoError, ReadableStorage, WritableStorage};
46pub use node::path::{NibblesIterator, Path};
47pub use node::{
48    BranchNode, Child, Children, LeafNode, Node, PathIterItem,
49    branch::{HashType, IntoHashType},
50};
51pub use nodestore::{
52    AreaIndex, Committed, HashedNodeReader, ImmutableProposal, LinearAddress, MutableProposal,
53    NodeReader, NodeStore, Parentable, RootReader, TrieReader,
54};
55
56pub use linear::filebacked::FileBacked;
57pub use linear::memory::MemStore;
58pub use node::persist::MaybePersistedNode;
59#[cfg(any(test, feature = "test_utils"))]
60pub use test_utils::SeededRng;
61pub use trie_hash::{InvalidTrieHashLength, TrieHash};
62
63/// A shared node, which is just a triophe Arc of a node
64pub type SharedNode = triomphe::Arc<Node>;
65
66/// The strategy for caching nodes that are read
67/// from the storage layer. Generally, we only want to
68/// cache write operations, but for some read-heavy workloads
69/// you can enable caching of branch reads or all reads.
70#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum CacheReadStrategy {
72    /// Only cache writes (no reads will be cached)
73    WritesOnly,
74
75    /// Cache branch reads (reads that are not leaf nodes)
76    BranchReads,
77
78    /// Cache all reads (leaves and branches)
79    All,
80}
81
82impl Display for CacheReadStrategy {
83    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
84        write!(f, "{self:?}")
85    }
86}
87
88/// This enum encapsulates what points to the stored area.
89#[derive(Debug, Clone, PartialEq, Eq, Copy)]
90pub enum StoredAreaParent {
91    /// The stored area is a trie node
92    TrieNode(TrieNodeParent),
93    /// The stored area is a free list
94    FreeList(FreeListParent),
95}
96
97/// This enum encapsulates what points to the stored area allocated for a trie node.
98#[derive(Debug, Clone, PartialEq, Eq, Copy)]
99pub enum TrieNodeParent {
100    /// The stored area is the root of the trie, so the header points to it
101    Root,
102    /// The stored area is not the root of the trie, so a parent trie node points to it
103    Parent(LinearAddress, usize),
104}
105
106/// This enum encapsulates what points to the stored area allocated for a free list.
107#[derive(Debug, Clone, PartialEq, Eq, Copy)]
108pub enum FreeListParent {
109    /// The stored area is the head of the free list, so the header points to it
110    FreeListHead(AreaIndex),
111    /// The stored area is not the head of the free list, so a previous free area points to it
112    PrevFreeArea {
113        /// The size index of the area, helps with debugging and fixing
114        area_size_idx: AreaIndex,
115        /// The address of the previous free area
116        parent_addr: LinearAddress,
117    },
118}
119
120impl LowerHex for StoredAreaParent {
121    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
122        match self {
123            StoredAreaParent::TrieNode(trie_parent) => LowerHex::fmt(trie_parent, f),
124            StoredAreaParent::FreeList(free_list_parent) => LowerHex::fmt(free_list_parent, f),
125        }
126    }
127}
128
129impl LowerHex for TrieNodeParent {
130    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
131        match self {
132            TrieNodeParent::Root => f.write_str("Root"),
133            TrieNodeParent::Parent(addr, index) => {
134                f.write_str("TrieNode@")?;
135                LowerHex::fmt(addr, f)?;
136                f.write_fmt(format_args!("[{index}]"))
137            }
138        }
139    }
140}
141
142impl LowerHex for FreeListParent {
143    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
144        match self {
145            FreeListParent::FreeListHead(index) => f.write_fmt(format_args!("FreeLists[{index}]")),
146            FreeListParent::PrevFreeArea {
147                area_size_idx,
148                parent_addr,
149            } => {
150                f.write_fmt(format_args!("FreeArea[{area_size_idx}]@"))?;
151                LowerHex::fmt(parent_addr, f)
152            }
153        }
154    }
155}
156
157use derive_where::derive_where;
158
159/// Errors returned by the checker
160#[derive(thiserror::Error, Debug)]
161#[derive_where(PartialEq, Eq)]
162#[non_exhaustive]
163pub enum CheckerError {
164    /// The file size is not valid
165    #[error("Invalid DB size ({db_size}): {description}")]
166    InvalidDBSize {
167        /// The size of the db
168        db_size: u64,
169        /// The description of the error
170        description: String,
171    },
172
173    /// Hash mismatch for a node
174    #[error(
175        "Hash mismatch for node {path:?} at address {address}: parent stored {parent_stored_hash}, computed {computed_hash}"
176    )]
177    HashMismatch {
178        /// The path of the node
179        path: Path,
180        /// The address of the node
181        address: LinearAddress,
182        /// The parent of the node
183        parent: TrieNodeParent,
184        /// The hash value stored in the parent node
185        parent_stored_hash: HashType,
186        /// The hash value computed for the node
187        computed_hash: HashType,
188    },
189
190    /// The address is out of bounds
191    #[error(
192        "stored area at {start:#x} with size {size} (parent: {parent:#x}) is out of bounds ({bounds:#x?})"
193    )]
194    AreaOutOfBounds {
195        /// Start of the `StoredArea`
196        start: LinearAddress,
197        /// Size of the `StoredArea`
198        size: u64,
199        /// Valid range of addresses
200        bounds: Range<LinearAddress>,
201        /// The parent of the `StoredArea`
202        parent: StoredAreaParent,
203    },
204
205    /// Stored areas intersect
206    #[error(
207        "stored area at {start:#x} with size {size} (parent: {parent:#x}) intersects with other stored areas: {intersection:#x?}"
208    )]
209    AreaIntersects {
210        /// Start of the `StoredArea`
211        start: LinearAddress,
212        /// Size of the `StoredArea`
213        size: u64,
214        /// The intersection
215        intersection: Vec<Range<LinearAddress>>,
216        /// The parent of the `StoredArea`
217        parent: StoredAreaParent,
218    },
219
220    /// Node is larger than the area it is stored in
221    #[error(
222        "stored area at {area_start:#x} with size {area_size} (parent: {parent:#x}) stores a node of size {node_bytes}"
223    )]
224    NodeLargerThanArea {
225        /// Address of the area
226        area_start: LinearAddress,
227        /// Size of the area
228        area_size: u64,
229        /// Size of the node
230        node_bytes: u64,
231        /// The parent of the area
232        parent: TrieNodeParent,
233    },
234
235    /// Freelist area size does not match
236    #[error(
237        "Free area {address:#x} of size {size} (parent: {parent:#x}) is found in free list {actual_free_list} but it should be in freelist {expected_free_list}"
238    )]
239    FreelistAreaSizeMismatch {
240        /// Address of the free area
241        address: LinearAddress,
242        /// Actual size of the free area
243        size: u64,
244        /// Free list on which the area is stored
245        actual_free_list: AreaIndex,
246        /// Expected size of the area
247        expected_free_list: AreaIndex,
248        /// The parent of the free area
249        parent: FreeListParent,
250    },
251
252    /// The start address of a stored area is not a multiple of 16
253    #[error(
254        "The start address of a stored area (parent: {parent:#x}) is not a multiple of {}: {address:#x}",
255        nodestore::primitives::AreaIndex::MIN_AREA_SIZE
256    )]
257    AreaMisaligned {
258        /// The start address of the stored area
259        address: LinearAddress,
260        /// The parent of the `StoredArea`
261        parent: StoredAreaParent,
262    },
263
264    /// Found leaked areas
265    #[error("Found leaked areas: {0}")]
266    #[derive_where(skip_inner)]
267    AreaLeaks(checker::LinearAddressRangeSet),
268
269    /// The root is not persisted
270    #[error("The checker can only check persisted nodestores")]
271    UnpersistedRoot,
272
273    #[error(
274        "The node {key:#x} at {address:#x} (parent: {parent:#x}) has a value but its path is not 32 or 64 bytes long"
275    )]
276    /// A value is found corresponding to an invalid key.
277    /// With ethhash, keys must be 32 or 64 bytes long.
278    /// Without ethhash, keys cannot contain half-bytes (i.e., odd number of nibbles).
279    InvalidKey {
280        /// The key found, or equivalently the path of the node that stores the value
281        key: Path,
282        /// Address of the node
283        address: LinearAddress,
284        /// Parent of the node
285        parent: TrieNodeParent,
286    },
287
288    /// IO error
289    #[error("IO error")]
290    #[derive_where(skip_inner)]
291    IO {
292        /// The error
293        error: FileIoError,
294        /// parent of the area
295        parent: StoredAreaParent,
296    },
297}
298
299impl CheckerError {
300    const fn parent(&self) -> Option<StoredAreaParent> {
301        match self {
302            CheckerError::InvalidDBSize { .. }
303            | CheckerError::AreaLeaks(_)
304            | CheckerError::UnpersistedRoot => None,
305            CheckerError::AreaOutOfBounds { parent, .. }
306            | CheckerError::AreaIntersects { parent, .. }
307            | CheckerError::AreaMisaligned { parent, .. }
308            | CheckerError::IO { parent, .. } => Some(*parent),
309            CheckerError::HashMismatch { parent, .. }
310            | CheckerError::NodeLargerThanArea { parent, .. }
311            | CheckerError::InvalidKey { parent, .. } => Some(StoredAreaParent::TrieNode(*parent)),
312            CheckerError::FreelistAreaSizeMismatch { parent, .. } => {
313                Some(StoredAreaParent::FreeList(*parent))
314            }
315        }
316    }
317}
318
319impl From<CheckerError> for Vec<CheckerError> {
320    fn from(error: CheckerError) -> Self {
321        vec![error]
322    }
323}