1#![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
14use 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
36pub mod logger;
38
39#[macro_use]
40pub mod macros;
42pub 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
63pub type SharedNode = triomphe::Arc<Node>;
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum CacheReadStrategy {
72 WritesOnly,
74
75 BranchReads,
77
78 All,
80}
81
82impl Display for CacheReadStrategy {
83 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
84 write!(f, "{self:?}")
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Copy)]
90pub enum StoredAreaParent {
91 TrieNode(TrieNodeParent),
93 FreeList(FreeListParent),
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Copy)]
99pub enum TrieNodeParent {
100 Root,
102 Parent(LinearAddress, usize),
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Copy)]
108pub enum FreeListParent {
109 FreeListHead(AreaIndex),
111 PrevFreeArea {
113 area_size_idx: AreaIndex,
115 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#[derive(thiserror::Error, Debug)]
161#[derive_where(PartialEq, Eq)]
162#[non_exhaustive]
163pub enum CheckerError {
164 #[error("Invalid DB size ({db_size}): {description}")]
166 InvalidDBSize {
167 db_size: u64,
169 description: String,
171 },
172
173 #[error(
175 "Hash mismatch for node {path:?} at address {address}: parent stored {parent_stored_hash}, computed {computed_hash}"
176 )]
177 HashMismatch {
178 path: Path,
180 address: LinearAddress,
182 parent: TrieNodeParent,
184 parent_stored_hash: HashType,
186 computed_hash: HashType,
188 },
189
190 #[error(
192 "stored area at {start:#x} with size {size} (parent: {parent:#x}) is out of bounds ({bounds:#x?})"
193 )]
194 AreaOutOfBounds {
195 start: LinearAddress,
197 size: u64,
199 bounds: Range<LinearAddress>,
201 parent: StoredAreaParent,
203 },
204
205 #[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: LinearAddress,
212 size: u64,
214 intersection: Vec<Range<LinearAddress>>,
216 parent: StoredAreaParent,
218 },
219
220 #[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 area_start: LinearAddress,
227 area_size: u64,
229 node_bytes: u64,
231 parent: TrieNodeParent,
233 },
234
235 #[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: LinearAddress,
242 size: u64,
244 actual_free_list: AreaIndex,
246 expected_free_list: AreaIndex,
248 parent: FreeListParent,
250 },
251
252 #[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 address: LinearAddress,
260 parent: StoredAreaParent,
262 },
263
264 #[error("Found leaked areas: {0}")]
266 #[derive_where(skip_inner)]
267 AreaLeaks(checker::LinearAddressRangeSet),
268
269 #[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 InvalidKey {
280 key: Path,
282 address: LinearAddress,
284 parent: TrieNodeParent,
286 },
287
288 #[error("IO error")]
290 #[derive_where(skip_inner)]
291 IO {
292 error: FileIoError,
294 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}