• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 26250451051-1

21 May 2026 08:11PM UTC coverage: 85.585% (-0.1%) from 85.712%
26250451051-1

Pull #7215

github

ec9d4c
web-flow
Merge 9487bf852 into af1280aac
Pull Request #7215: Chore: fix flake in non_blocking_minority_configured_to_favour_...

188844 of 220651 relevant lines covered (85.58%)

18975267.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.35
/stackslib/src/chainstate/stacks/index/node.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2026 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::io::{Read, Seek, Write};
18
use std::{error, fmt};
19

20
use crate::chainstate::stacks::index::bits::{
21
    get_compressed_ptrs_size, get_path_byte_len, get_ptrs_byte_len, get_ptrs_byte_len_compressed,
22
    get_sparse_ptrs_bitmap_size, path_from_bytes, ptrs_from_bytes, write_path_to_bytes,
23
    SPARSE_PTR_BITMAP_MARKER,
24
};
25
use crate::chainstate::stacks::index::{
26
    BlockMap, ClarityMarfTrieId, Error, MARFValue, MarfTrieId, TrieLeaf, MARF_VALUE_ENCODED_SIZE,
27
};
28
use crate::codec::{read_next, write_next, Error as codec_error, StacksMessageCodec};
29
use crate::types::chainstate::{TrieHash, BLOCK_HEADER_HASH_ENCODED_SIZE};
30
use crate::util::hash::to_hex;
31

32
#[derive(Debug, Clone, PartialEq)]
33
pub enum CursorError {
34
    PathDiverged,
35
    BackptrEncountered(TriePtr),
36
    ChrNotFound,
37
}
38

39
impl fmt::Display for CursorError {
40
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
41
        match *self {
×
42
            CursorError::PathDiverged => write!(f, "Path diverged"),
×
43
            CursorError::BackptrEncountered(_) => write!(f, "Back-pointer encountered"),
×
44
            CursorError::ChrNotFound => write!(f, "Node child not found"),
×
45
        }
46
    }
×
47
}
48

49
impl error::Error for CursorError {
50
    fn cause(&self) -> Option<&dyn error::Error> {
×
51
        None
×
52
    }
×
53
}
54

55
// All numeric values of a Trie node when encoded.
56
// The low 4 bits encode the base TrieNodeID value (0-6).
57
// * the 8th bit (0x80) indicates a back-pointer to be followed
58
// * the 7th bit (0x40) indicates the ptrs are compressed. Cleared on read.
59
// * the 6th bit (0x20) indicates the ptr offset is encoded as u64, instead of u32. Cleared on read.
60
// * the 5th bit (0x10) indicates a compressed inline pointer contains a back_block payload. Cleared on read.
61
define_u8_enum!(TrieNodeID {
62
    Empty = 0,
63
    Leaf = 1,
64
    Node4 = 2,
65
    Node16 = 3,
66
    Node48 = 4,
67
    Node256 = 5,
68
    Patch = 6
69
});
70

71
/// A node ID encodes a back-pointer if its high bit is set
72
pub fn is_backptr(id: u8) -> bool {
2,147,483,647✔
73
    id & 0x80 != 0
2,147,483,647✔
74
}
2,147,483,647✔
75

76
/// Set the back-pointer bit
77
pub fn set_backptr(id: u8) -> u8 {
2,147,483,647✔
78
    id | 0x80
2,147,483,647✔
79
}
2,147,483,647✔
80

81
/// Clear the back-pointer bit
82
pub fn clear_backptr(id: u8) -> u8 {
2,147,483,647✔
83
    id & 0x7f
2,147,483,647✔
84
}
2,147,483,647✔
85

86
/// Is this node compressed?
87
pub fn is_compressed(id: u8) -> bool {
672,155,312✔
88
    id & 0x40 != 0
672,155,312✔
89
}
672,155,312✔
90

91
/// Set the compressed bit
92
pub fn set_compressed(id: u8) -> u8 {
59,337,737✔
93
    id | 0x40
59,337,737✔
94
}
59,337,737✔
95

96
/// Clear the compressed bit
97
pub fn clear_compressed(id: u8) -> u8 {
2,147,483,647✔
98
    id & 0xbf
2,147,483,647✔
99
}
2,147,483,647✔
100

101
/// Is this compressed inline pointer flagged to carry `back_block` payload bytes?
102
/// This bit is wire-format-only metadata and is cleared after decoding.
103
pub fn has_inline_back_block(id: u8) -> bool {
2,147,483,647✔
104
    id & 0x10 != 0
2,147,483,647✔
105
}
2,147,483,647✔
106

107
/// Set the compressed inline `back_block` payload bit.
108
pub fn set_inline_back_block(id: u8) -> u8 {
12✔
109
    id | 0x10
12✔
110
}
12✔
111

112
/// Clear the compressed inline `back_block` payload bit.
113
pub fn clear_inline_back_block(id: u8) -> u8 {
2,147,483,647✔
114
    id & 0xef
2,147,483,647✔
115
}
2,147,483,647✔
116

117
/// True if a compressed pointer with this encoded id includes a back_block payload.
118
#[inline]
119
fn has_back_block_payload_bytes(id: u8) -> bool {
2,147,483,647✔
120
    is_backptr(id) || has_inline_back_block(id)
2,147,483,647✔
121
}
2,147,483,647✔
122

123
/// Is this pointer encoded with a u64 offset?
124
pub const fn is_u64_ptr(id: u8) -> bool {
2,147,483,647✔
125
    id & 0x20 != 0
2,147,483,647✔
126
}
2,147,483,647✔
127

128
/// Set the u64-pointer bit
129
pub const fn set_u64_ptr(id: u8) -> u8 {
1,141,113,888✔
130
    id | 0x20
1,141,113,888✔
131
}
1,141,113,888✔
132

133
/// Clear the u64-pointer bit
134
pub const fn clear_u64_ptr(id: u8) -> u8 {
2,147,483,647✔
135
    id & 0xdf
2,147,483,647✔
136
}
2,147,483,647✔
137

138
/// Clear all control bits (backptr, compressed, u64-pointer, squash annotation)
139
pub fn clear_ctrl_bits(id: u8) -> u8 {
2,147,483,647✔
140
    id & 0x0f
2,147,483,647✔
141
}
2,147,483,647✔
142

143
// Byte writing operations for pointer lists, paths.
144

145
/// Write out the list of TriePtrs to the given Write object.
146
/// The written pointers will NOT be compressed.
147
/// Returns Ok(()) on success
148
/// Returns Err(IOError(..)) on disk I/O error
149
fn write_ptrs_to_bytes<W: Write>(ptrs: &[TriePtr], w: &mut W) -> Result<(), Error> {
2,039,991✔
150
    for ptr in ptrs.iter() {
107,752,224✔
151
        ptr.write_bytes(w)?;
107,752,224✔
152
    }
153
    Ok(())
2,039,991✔
154
}
2,039,991✔
155

156
/// Write the list of TriePtrs to the given Write object.
157
/// The given `id` is a node ID with some control bits set -- in particular, the compressed bit.
158
/// If the compressed bit is set, then the TriePtr list will be compressed as best as possible
159
/// before written.  See `bits::ptrs_to_bytes()` for details.
160
///
161
/// Returns Ok(()) on success
162
/// Returns Err(CorruptionError(..)) if the id does not correspond to a valid node ID or is a patch
163
/// node ID
164
/// Returns Err(IOError(..)) on disk I/O error
165
fn write_ptrs_to_bytes_compressed<W: Write>(
2,556,629✔
166
    id: u8,
2,556,629✔
167
    ptrs: &[TriePtr],
2,556,629✔
168
    w: &mut W,
2,556,629✔
169
) -> Result<(), Error> {
2,556,629✔
170
    let Some(node_id) = TrieNodeID::from_u8(id) else {
2,556,629✔
171
        return Err(Error::CorruptionError(
×
172
            "Tried to store invalid trie node ID".to_string(),
×
173
        ));
×
174
    };
175
    if node_id == TrieNodeID::Patch {
2,556,629✔
176
        // NB the only proper way to store a patch node is to have it dumped as part of a TrieRAM
177
        return Err(Error::CorruptionError(
×
178
            "Tried to store patch node's ptrs improperly".to_string(),
×
179
        ));
×
180
    }
2,556,629✔
181

182
    let Some((ptrs_size, is_sparse)) = get_compressed_ptrs_size(id, ptrs) else {
2,556,629✔
183
        // doesn't apply -- this node has no ptrs
184
        return Ok(());
×
185
    };
186

187
    if is_sparse {
2,556,629✔
188
        // do a sparse write -- just write the bitmap and the non-empty trieptrs.
189
        // the first byte is SPARSE_PTR_BITMAP_MARKER to indicate that this is a sparse list, since it cannot be a
190
        // valid trie node ID
191
        w.write_all(&[SPARSE_PTR_BITMAP_MARKER])?;
2,414,591✔
192

193
        // compute the bitmap
194
        let bitmap_size = get_sparse_ptrs_bitmap_size(id).ok_or_else(|| {
2,414,591✔
195
            Error::CorruptionError(format!("No bitmap size defined for node id {id}"))
×
196
        })?;
×
197

198
        let mut bitmap = vec![0u8; bitmap_size];
2,414,591✔
199
        for (i, ptr) in ptrs.iter().enumerate() {
68,264,068✔
200
            if ptr.id() != TrieNodeID::Empty as u8 {
68,264,068✔
201
                // SAFETY: have checked ptrs.len() against bitmap size
202
                let bi = i / 8;
38,308,004✔
203
                let bt = i % 8;
38,308,004✔
204
                let mask = 1u8 << bt;
38,308,004✔
205
                let byte_mut = bitmap
38,308,004✔
206
                    .get_mut(bi)
38,308,004✔
207
                    .ok_or_else(|| Error::CorruptionError("bitmap not long enough".into()))?;
38,308,004✔
208
                *byte_mut |= mask;
38,308,004✔
209
            }
29,956,064✔
210
        }
211
        trace!(
2,414,591✔
212
            "Write sparse compressed ptrs list ({} bytes) for node {}; bitmap {}",
213
            ptrs_size,
214
            id,
215
            to_hex(&bitmap)
×
216
        );
217

218
        // write out bitmap
219
        w.write_all(&bitmap)?;
2,414,591✔
220

221
        // write out non-empty ptrs
222
        for ptr in ptrs.iter() {
68,264,068✔
223
            if ptr.id() != TrieNodeID::Empty as u8 {
68,264,068✔
224
                trace!("write sparse ptr {}", {
38,308,004✔
225
                    let mut byte_buffer = vec![];
×
226
                    _ = ptr.write_bytes_compressed(&mut byte_buffer);
×
227
                    to_hex(&byte_buffer)
×
228
                });
229
                ptr.write_bytes_compressed(w)?;
38,308,004✔
230
            }
29,956,064✔
231
        }
232
        return Ok(());
2,414,591✔
233
    }
142,038✔
234

235
    // ptrs are not sparse enough.
236
    // compute a bitmap of which ptrs are non-empty
237
    trace!(
142,038✔
238
        "Write dense compressed ptrs list ({} bytes) for node {}",
239
        ptrs_size,
240
        id
241
    );
242
    for ptr in ptrs.iter() {
745,784✔
243
        ptr.write_bytes_compressed(w)?;
745,784✔
244
    }
245
    Ok(())
142,038✔
246
}
2,556,629✔
247

248
fn ptrs_consensus_hash<W: Write, M: BlockMap>(
13,493,337✔
249
    ptrs: &[TriePtr],
13,493,337✔
250
    map: &mut M,
13,493,337✔
251
    w: &mut W,
13,493,337✔
252
) -> Result<(), Error> {
13,493,337✔
253
    for ptr in ptrs.iter() {
857,410,500✔
254
        ptr.write_consensus_bytes(map, w)?;
857,410,500✔
255
    }
256
    Ok(())
13,493,337✔
257
}
13,493,337✔
258

259
/// Copy-on-write pointer to a node.  When the MARF writes a new key/value pair, it copies
260
/// intermediate nodes from the parent trie into the new trie being built.  This struct is a
261
/// pointer stored in the new trie's nodes which point back to the node it was copied from.
262
///
263
/// This data is not stored anywhere.  It is used instead to compute TrieNodePatch nodes to write
264
/// to disk as a space-efficient alternative to copying over the same lightly-modified node over
265
/// and over again.
266
///
267
/// Fields are (trie block hash holding the node, pointer to the node in the trie)
268
#[derive(Clone, PartialEq, Copy)]
269
pub struct TrieCowPtr([u8; 32], TriePtr);
270

271
impl TrieCowPtr {
272
    pub fn new<T: MarfTrieId>(trie_id: T, ptr: TriePtr) -> Self {
20,244,449✔
273
        Self(trie_id.to_bytes(), ptr)
20,244,449✔
274
    }
20,244,449✔
275

276
    pub fn block_id<T: MarfTrieId>(&self) -> T {
4,905,704✔
277
        T::from_bytes(self.0)
4,905,704✔
278
    }
4,905,704✔
279

280
    pub fn ptr(&self) -> &TriePtr {
9,809,720✔
281
        &self.1
9,809,720✔
282
    }
9,809,720✔
283
}
284

285
impl fmt::Debug for TrieCowPtr {
286
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
287
        write!(
×
288
            f,
×
289
            "TrieCowPtr({},{})",
290
            &to_hex(&self.0),
×
291
            &ptrs_fmt(&[self.1])
×
292
        )
293
    }
×
294
}
295

296
/// All Trie nodes implement the following methods:
297
pub trait TrieNode {
298
    /// Node ID for encoding/decoding
299
    fn id(&self) -> u8;
300

301
    /// Is the node devoid of children?
302
    fn empty() -> Self;
303

304
    /// Follow a path character to a child pointer
305
    fn walk(&self, chr: u8) -> Option<TriePtr>;
306

307
    /// Insert a child pointer if the path character slot is not occupied.
308
    /// Return true if inserted, false if the slot is already filled
309
    fn insert(&mut self, ptr: &TriePtr) -> bool;
310

311
    /// Replace an existing child pointer with a new one.  Returns true if replaced; false if the
312
    /// child does not exist.
313
    fn replace(&mut self, ptr: &TriePtr) -> bool;
314

315
    /// Read an encoded instance of this node from a byte stream and instantiate it.
316
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<Self, Error>
317
    where
318
        Self: Sized;
319

320
    /// Get a reference to the children of this node.
321
    fn ptrs(&self) -> &[TriePtr];
322

323
    /// Get a reference to the children of this node.
324
    fn path(&self) -> &Vec<u8>;
325

326
    /// Construct a TrieNodeType from a TrieNode
327
    fn as_trie_node_type(&self) -> TrieNodeType;
328

329
    /// Get the ptr to the node we were copied from (on COW)
330
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr>;
331

332
    /// Set the ptr to the node we were copied from (on COW)
333
    fn set_cow_ptr(&mut self, cowptr: TrieCowPtr);
334

335
    /// Apply a list of TrieNodePatches to produce this node
336
    fn apply_patches(
337
        self,
338
        patches: &[(u32, TriePtr, TrieNodePatch)],
339
        cur_block_id: u32,
340
    ) -> Option<Self>
341
    where
342
        Self: Sized;
343

344
    /// Encode this node instance into a byte stream and write it to w.
345
    /// The TriePtrs willl NOT be compressed
346
    fn write_bytes<W: Write>(&self, w: &mut W) -> Result<(), Error> {
1,782,063✔
347
        w.write_all(&[self.id()])?;
1,782,063✔
348
        write_ptrs_to_bytes(self.ptrs(), w)?;
1,782,063✔
349
        write_path_to_bytes(self.path().as_slice(), w)
1,782,063✔
350
    }
1,782,063✔
351

352
    /// Encode this node instance into a byte stream and write it to w.
353
    /// The TriePtrs will be compressed to the smallest possible size.
354
    fn write_bytes_compressed<W: Write>(&self, w: &mut W) -> Result<(), Error> {
2,492,262✔
355
        w.write_all(&[set_compressed(self.id())])?;
2,492,262✔
356
        write_ptrs_to_bytes_compressed(self.id(), self.ptrs(), w)?;
2,492,262✔
357
        write_path_to_bytes(self.path().as_slice(), w)
2,492,262✔
358
    }
2,492,262✔
359

360
    #[cfg(test)]
361
    fn to_bytes(&self) -> Vec<u8> {
5✔
362
        let mut r = Vec::new();
5✔
363
        self.write_bytes(&mut r)
5✔
364
            .expect("Failed to write to byte buffer");
5✔
365
        r
5✔
366
    }
5✔
367

368
    /// Calculate how many bytes this node will take to encode.
369
    fn byte_len(&self) -> usize {
10,318,461✔
370
        get_ptrs_byte_len(self.ptrs()) + get_path_byte_len(self.path())
10,318,461✔
371
    }
10,318,461✔
372

373
    /// Calculate how many bytes this node will take to encode.
374
    fn byte_len_compressed(&self) -> usize {
197,048✔
375
        get_ptrs_byte_len_compressed(self.id(), self.ptrs()) + get_path_byte_len(self.path())
197,048✔
376
    }
197,048✔
377
}
378

379
/// Trait for types that can serialize to consensus bytes
380
/// This is implemented by `TrieNode`s and `ProofTrieNode`s
381
///  and allows hash calculation routines to be the same for
382
///  both types.
383
/// The type `M` is used for any additional data structures required
384
///   (BlockHashMap for TrieNode and () for ProofTrieNode)
385
pub trait ConsensusSerializable<M> {
386
    /// Encode the consensus-relevant bytes of this node and write it to w.
387
    fn write_consensus_bytes<W: Write>(
388
        &self,
389
        additional_data: &mut M,
390
        w: &mut W,
391
    ) -> Result<(), Error>;
392

393
    #[cfg(test)]
394
    fn to_consensus_bytes(&self, additional_data: &mut M) -> Vec<u8> {
4✔
395
        let mut r = Vec::new();
4✔
396
        self.write_consensus_bytes(additional_data, &mut r)
4✔
397
            .expect("Failed to write to byte buffer");
4✔
398
        r
4✔
399
    }
4✔
400
}
401

402
impl<T: TrieNode, M: BlockMap> ConsensusSerializable<M> for T {
403
    fn write_consensus_bytes<W: Write>(&self, map: &mut M, w: &mut W) -> Result<(), Error> {
13,493,337✔
404
        w.write_all(&[self.id()])?;
13,493,337✔
405
        ptrs_consensus_hash(self.ptrs(), map, w)?;
13,493,337✔
406
        write_path_to_bytes(self.path().as_slice(), w)
13,493,337✔
407
    }
13,493,337✔
408
}
409

410
/// Child pointer within a MARF trie node.
411
///
412
/// `back_block` has two modes depending on the backptr flag in `id`:
413
///
414
/// * Back-pointer (`id & 0x80 != 0`): the child lives in a different block's trie.
415
///   `back_block` is the `marf_data` row ID of that block, and `ptr` is the byte offset
416
///   within that block's trie storage.
417
///
418
/// * Inline (`id & 0x80 == 0`): the child lives in the same trie storage.
419
///   `back_block` is normally 0. In a squashed MARF, a non-zero `back_block` is a
420
///   squash annotation: it records the original archival block ID.
421
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
422
pub struct TriePtr {
423
    /// Node type ID of the child (see [`TrieNodeID`]). Bit 0x80 marks a back-pointer.
424
    pub id: u8,
425
    /// Path character at which this child resides.
426
    pub chr: u8,
427
    /// Byte offset of the child's encoded data within the trie storage.
428
    pub ptr: u64,
429
    /// Block ID of the trie containing the child. Zero for same-block inline children
430
    /// (unless carrying a squash annotation).
431
    pub back_block: u32,
432
}
433

434
pub fn ptrs_fmt(ptrs: &[TriePtr]) -> String {
251✔
435
    let mut strs = vec![];
251✔
436
    for ptr in ptrs.iter() {
24,200✔
437
        if ptr.id != TrieNodeID::Empty as u8 {
24,200✔
438
            strs.push(format!(
9,170✔
439
                "id({})chr({:02x})ptr({})bblk({})",
440
                ptr.id, ptr.chr, ptr.ptr, ptr.back_block
441
            ))
442
        }
15,030✔
443
    }
444
    strs.join(",")
251✔
445
}
251✔
446

447
impl Default for TriePtr {
448
    #[inline]
449
    fn default() -> TriePtr {
2,147,483,647✔
450
        TriePtr {
2,147,483,647✔
451
            id: 0,
2,147,483,647✔
452
            chr: 0,
2,147,483,647✔
453
            ptr: 0,
2,147,483,647✔
454
            back_block: 0,
2,147,483,647✔
455
        }
2,147,483,647✔
456
    }
2,147,483,647✔
457
}
458

459
impl TriePtr {
460
    /// Serialized size of the node-ID byte.
461
    const ID_BYTE_LEN: usize = 1;
462
    /// Serialized size of the path-char byte.
463
    const CHR_BYTE_LEN: usize = 1;
464
    /// Serialized size of the `back_block` field (only present in uncompressed
465
    /// form, or in compressed form when the pointer is a backptr).
466
    const BACK_BLOCK_BYTE_LEN: usize = 4;
467

468
    #[inline]
469
    pub fn new(id: u8, chr: u8, ptr: u64) -> TriePtr {
1,739,910,083✔
470
        TriePtr {
1,739,910,083✔
471
            id,
1,739,910,083✔
472
            chr,
1,739,910,083✔
473
            ptr,
1,739,910,083✔
474
            back_block: 0,
1,739,910,083✔
475
        }
1,739,910,083✔
476
    }
1,739,910,083✔
477

478
    /// Create a back-pointer version of a [`TriePtr`]
479
    #[cfg(test)]
480
    pub fn new_backptr(id: u8, chr: u8, ptr: u64, back_block: u32) -> TriePtr {
45✔
481
        TriePtr {
45✔
482
            id: set_backptr(id),
45✔
483
            chr,
45✔
484
            ptr,
45✔
485
            back_block,
45✔
486
        }
45✔
487
    }
45✔
488

489
    #[inline]
490
    pub fn id(&self) -> u8 {
2,147,483,647✔
491
        self.id
2,147,483,647✔
492
    }
2,147,483,647✔
493

494
    #[inline]
495
    /// Is the TriePtr an unoccupied slot?
496
    pub fn is_empty(&self) -> bool {
2,147,483,647✔
497
        self.id() == TrieNodeID::Empty as u8
2,147,483,647✔
498
    }
2,147,483,647✔
499

500
    #[inline]
501
    pub fn chr(&self) -> u8 {
2,147,483,647✔
502
        self.chr
2,147,483,647✔
503
    }
2,147,483,647✔
504

505
    #[inline]
506
    pub fn ptr(&self) -> u64 {
2,147,483,647✔
507
        self.ptr
2,147,483,647✔
508
    }
2,147,483,647✔
509

510
    /// Convert `self.ptr()` to a `u32` in-memory index, or return an error
511
    /// if the value exceeds `u32::MAX`.
512
    #[inline]
513
    pub fn try_ptr_into_u32(&self) -> Result<u32, Error> {
306,369,529✔
514
        u32::try_from(self.ptr).map_err(|_| Error::OverflowError)
306,369,529✔
515
    }
306,369,529✔
516

517
    /// Convert `self.ptr()` to a `usize` in-memory index, or return an error
518
    /// if the value exceeds `usize::MAX`.
519
    #[inline]
520
    pub fn try_ptr_into_usize(&self) -> Result<usize, Error> {
512,140,345✔
521
        usize::try_from(self.ptr).map_err(|_| Error::OverflowError)
512,140,345✔
522
    }
512,140,345✔
523

524
    #[inline]
525
    pub fn back_block(&self) -> u32 {
2,147,483,647✔
526
        self.back_block
2,147,483,647✔
527
    }
2,147,483,647✔
528

529
    #[inline]
530
    pub fn from_backptr(&self) -> TriePtr {
2,147,483,647✔
531
        TriePtr {
2,147,483,647✔
532
            id: clear_backptr(self.id),
2,147,483,647✔
533
            chr: self.chr,
2,147,483,647✔
534
            ptr: self.ptr,
2,147,483,647✔
535
            back_block: 0,
2,147,483,647✔
536
        }
2,147,483,647✔
537
    }
2,147,483,647✔
538

539
    /// Return the identifier byte that will be emitted on disk for this pointer.
540
    ///
541
    /// This preserves the logical node kind while setting or clearing the `0x20`
542
    /// control bit to match the encoded pointer width.
543
    #[inline]
544
    pub fn encoded_id(&self) -> u8 {
2,147,483,647✔
545
        if self.ptr() > u64::from(u32::MAX) {
2,147,483,647✔
546
            set_u64_ptr(self.id())
41✔
547
        } else {
548
            clear_u64_ptr(self.id())
2,147,483,647✔
549
        }
550
    }
2,147,483,647✔
551

552
    /// Return the uncompressed encoded size, in bytes, for a pointer with the
553
    /// given on-disk identifier byte.
554
    ///
555
    /// The `0x20` control bit determines whether the pointer payload is encoded
556
    /// as `u32` or `u64`.
557
    ///
558
    /// Uncompressed layout: `id (1) + chr (1) + ptr (4 or 8) + back_block (4)`.
559
    #[inline]
560
    pub const fn encoded_size_for_id(node_id: u8) -> usize {
2,147,483,647✔
561
        let ptr_bytes = if is_u64_ptr(node_id) { 8 } else { 4 };
2,147,483,647✔
562
        Self::ID_BYTE_LEN + Self::CHR_BYTE_LEN + ptr_bytes + Self::BACK_BLOCK_BYTE_LEN
2,147,483,647✔
563
    }
2,147,483,647✔
564

565
    /// Return the maximum possible uncompressed encoded size for any `TriePtr`.
566
    #[inline]
567
    pub const fn max_encoded_size() -> usize {
1,141,113,835✔
568
        Self::encoded_size_for_id(set_u64_ptr(TrieNodeID::Empty as u8))
1,141,113,835✔
569
    }
1,141,113,835✔
570

571
    /// Return the compressed encoded size, in bytes, for a pointer with the
572
    /// given on-disk identifier byte.
573
    ///
574
    /// The `0x20` control bit determines whether the pointer payload is encoded
575
    /// as `u32` or `u64`.
576
    ///
577
    /// Compressed layout: `id (1) + chr (1) + ptr (4 or 8)`. The `back_block`
578
    /// field is only appended for backptr nodes (see [`Self::compressed_size_for_id`]).
579
    #[inline]
580
    pub const fn encoded_size_compressed_for_id(node_id: u8) -> usize {
2,147,483,647✔
581
        let ptr_bytes = if is_u64_ptr(node_id) { 8 } else { 4 };
2,147,483,647✔
582
        Self::ID_BYTE_LEN + Self::CHR_BYTE_LEN + ptr_bytes
2,147,483,647✔
583
    }
2,147,483,647✔
584

585
    #[inline]
586
    pub fn write_bytes<W: Write>(&self, w: &mut W) -> Result<(), Error> {
107,752,230✔
587
        let encoded_id = self.encoded_id();
107,752,230✔
588
        w.write_all(&[encoded_id, self.chr()])?;
107,752,230✔
589
        if is_u64_ptr(encoded_id) {
107,752,230✔
590
            w.write_all(&self.ptr().to_be_bytes())?;
3✔
591
        } else {
592
            let ptr32 = self.try_ptr_into_u32()?;
107,752,227✔
593
            w.write_all(&ptr32.to_be_bytes())?;
107,752,227✔
594
        }
595
        w.write_all(&self.back_block().to_be_bytes())?;
107,752,230✔
596
        Ok(())
107,752,230✔
597
    }
107,752,230✔
598

599
    #[inline]
600
    pub fn write_bytes_compressed<W: Write>(&self, w: &mut W) -> Result<(), Error> {
56,781,096✔
601
        // Preserve squash annotation payload on disk for inline pointers that
602
        // carry a non-zero back_block, without changing backptr semantics.
603
        let mut encoded_id = set_compressed(self.encoded_id());
56,781,096✔
604
        if !is_backptr(self.id()) && self.back_block() != 0 {
56,781,096✔
605
            encoded_id = set_inline_back_block(encoded_id);
6✔
606
        }
56,781,090✔
607
        w.write_all(&[encoded_id, self.chr()])?;
56,781,096✔
608
        if is_u64_ptr(encoded_id) {
56,781,096✔
609
            w.write_all(&self.ptr().to_be_bytes())?;
18✔
610
        } else {
611
            let ptr32 = self.try_ptr_into_u32()?;
56,781,078✔
612
            w.write_all(&ptr32.to_be_bytes())?;
56,781,078✔
613
        }
614
        if has_back_block_payload_bytes(encoded_id) {
56,781,096✔
615
            w.write_all(&self.back_block().to_be_bytes())?;
36,045,592✔
616
        }
20,735,504✔
617
        Ok(())
56,781,096✔
618
    }
56,781,096✔
619

620
    /// The parts of a child pointer that are relevant for consensus are only its ID, path
621
    /// character, and referred-to block hash.  The software doesn't care about the details of how/where
622
    /// nodes are stored.
623
    pub fn write_consensus_bytes<W: Write, M: BlockMap>(
857,410,500✔
624
        &self,
857,410,500✔
625
        block_map: &mut M,
857,410,500✔
626
        w: &mut W,
857,410,500✔
627
    ) -> Result<(), Error> {
857,410,500✔
628
        w.write_all(&[self.id(), self.chr()])?;
857,410,500✔
629

630
        if is_backptr(self.id()) {
857,410,500✔
631
            w.write_all(
426,172,470✔
632
                block_map
426,172,470✔
633
                    .get_block_hash_caching(self.back_block())
426,172,470✔
634
                    .expect("Block identifier {} refered to an unknown block. Consensus failure.")
426,172,470✔
635
                    .as_bytes(),
426,172,470✔
636
            )?;
×
637
        } else {
638
            w.write_all(&[0; BLOCK_HEADER_HASH_ENCODED_SIZE])?;
431,238,030✔
639
        }
640
        Ok(())
857,410,500✔
641
    }
857,410,500✔
642

643
    #[inline]
644
    #[allow(clippy::indexing_slicing)]
645
    /// Deserialize a pointer from raw bytes and return it with bytes consumed.
646
    pub fn from_bytes(bytes: &[u8]) -> (TriePtr, usize) {
2,147,483,647✔
647
        let encoded_id = bytes[0];
2,147,483,647✔
648
        let min_len = TriePtr::encoded_size_for_id(encoded_id);
2,147,483,647✔
649
        assert!(bytes.len() >= min_len);
2,147,483,647✔
650
        let id = clear_u64_ptr(encoded_id);
2,147,483,647✔
651
        let chr = bytes[1];
2,147,483,647✔
652
        // Layout: [id(1)][chr(1)][ptr(4 or 8)][back_block(4)].
653
        let (ptr, back_block) = if is_u64_ptr(encoded_id) {
2,147,483,647✔
654
            let ptr = u64::from_be_bytes(bytes[2..10].try_into().unwrap());
2✔
655
            let back_block = u32::from_be_bytes(bytes[10..14].try_into().unwrap());
2✔
656
            (ptr, back_block)
2✔
657
        } else {
658
            let ptr = u64::from(u32::from_be_bytes(bytes[2..6].try_into().unwrap()));
2,147,483,647✔
659
            let back_block = u32::from_be_bytes(bytes[6..10].try_into().unwrap());
2,147,483,647✔
660
            (ptr, back_block)
2,147,483,647✔
661
        };
662

663
        (
2,147,483,647✔
664
            TriePtr {
2,147,483,647✔
665
                id,
2,147,483,647✔
666
                chr,
2,147,483,647✔
667
                ptr,
2,147,483,647✔
668
                back_block,
2,147,483,647✔
669
            },
2,147,483,647✔
670
            min_len,
2,147,483,647✔
671
        )
2,147,483,647✔
672
    }
2,147,483,647✔
673

674
    /// Load up this TriePtr from a slice of bytes, assuming that they represent a compressed
675
    /// TriePtr.
676
    ///
677
    /// A compressed TriePtr stores `back_block` bytes if either:
678
    /// * it is a back-pointer (`is_backptr(id)`), or
679
    /// * it is an inline pointer with back_block payload
680
    ///   (`has_inline_back_block(id)`).
681
    ///
682
    /// The annotation bit is wire metadata and is cleared on read.
683
    #[inline]
684
    #[allow(clippy::indexing_slicing)]
685
    pub fn from_bytes_compressed(bytes: &[u8]) -> TriePtr {
2,147,483,647✔
686
        let encoded_id = clear_compressed(bytes[0]);
2,147,483,647✔
687
        assert!(bytes.len() >= TriePtr::compressed_size_for_id(encoded_id));
2,147,483,647✔
688
        let id = clear_u64_ptr(clear_inline_back_block(encoded_id));
2,147,483,647✔
689
        let chr = bytes[1];
2,147,483,647✔
690
        // Layout: [id(1)][chr(1)][ptr(4 or 8)]; backptrs append [back_block(4)].
691
        let ptr = if is_u64_ptr(encoded_id) {
2,147,483,647✔
692
            u64::from_be_bytes(bytes[2..10].try_into().unwrap())
9✔
693
        } else {
694
            u64::from(u32::from_be_bytes(bytes[2..6].try_into().unwrap()))
2,147,483,647✔
695
        };
696

697
        let back_block = if has_back_block_payload_bytes(encoded_id) {
2,147,483,647✔
698
            // Backpointers and squash annotations append a 4-byte `back_block` after the compressed ptr payload.
699
            let back_block_offset = TriePtr::encoded_size_compressed_for_id(encoded_id);
2,147,483,647✔
700
            let back_block_end = back_block_offset + 4;
2,147,483,647✔
701
            assert!(bytes.len() >= back_block_end);
2,147,483,647✔
702
            u32::from_be_bytes(bytes[back_block_offset..back_block_end].try_into().unwrap())
2,147,483,647✔
703
        } else {
704
            0
2,147,483,647✔
705
        };
706

707
        TriePtr {
2,147,483,647✔
708
            id,
2,147,483,647✔
709
            chr,
2,147,483,647✔
710
            ptr,
2,147,483,647✔
711
            back_block,
2,147,483,647✔
712
        }
2,147,483,647✔
713
    }
2,147,483,647✔
714

715
    /// Load up a compressed TriePtr from a Read object.
716
    /// Returns Ok(ptr) on success
717
    /// Returns Err(codec_error::*) on disk I/O failure, or failure to decode the requested bytes
718
    #[inline]
719
    pub fn read_bytes_compressed<R: Read>(fd: &mut R) -> Result<TriePtr, codec_error> {
2,147,483,647✔
720
        let id_bits: u8 = read_next(fd)?;
2,147,483,647✔
721
        let encoded_id = clear_compressed(id_bits);
2,147,483,647✔
722
        let id = clear_u64_ptr(clear_inline_back_block(encoded_id));
2,147,483,647✔
723
        let chr: u8 = read_next(fd)?;
2,147,483,647✔
724
        let ptr = if is_u64_ptr(encoded_id) {
2,147,483,647✔
725
            let hi: [u8; 4] = read_next(fd)?;
14✔
726
            let lo: [u8; 4] = read_next(fd)?;
14✔
727
            u64::from_be_bytes([hi[0], hi[1], hi[2], hi[3], lo[0], lo[1], lo[2], lo[3]])
14✔
728
        } else {
729
            let ptr_be_bytes: [u8; 4] = read_next(fd)?;
2,147,483,647✔
730
            u64::from(u32::from_be_bytes(ptr_be_bytes))
2,147,483,647✔
731
        };
732
        let back_block = if has_back_block_payload_bytes(encoded_id) {
2,147,483,647✔
733
            let bytes: [u8; 4] = read_next(fd)?;
1,047,239,716✔
734
            u32::from_be_bytes(bytes)
1,047,239,715✔
735
        } else {
736
            0
2,147,483,647✔
737
        };
738

739
        Ok(TriePtr {
2,147,483,647✔
740
            id,
2,147,483,647✔
741
            chr,
2,147,483,647✔
742
            ptr,
2,147,483,647✔
743
            back_block,
2,147,483,647✔
744
        })
2,147,483,647✔
745
    }
2,147,483,647✔
746

747
    /// Size of this TriePtr on disk.
748
    #[inline]
749
    pub fn encoded_size(&self) -> usize {
521,652,384✔
750
        Self::encoded_size_for_id(self.encoded_id())
521,652,384✔
751
    }
521,652,384✔
752

753
    /// Size of this TriePtr on disk, if compression is to be used.
754
    #[inline]
755
    pub fn compressed_size(&self) -> usize {
2,147,483,647✔
756
        let encoded_id = self.encoded_id();
2,147,483,647✔
757
        if !is_backptr(self.id) && self.back_block != 0 {
2,147,483,647✔
758
            Self::encoded_size_for_id(encoded_id)
6✔
759
        } else {
760
            Self::compressed_size_for_id(encoded_id)
2,147,483,647✔
761
        }
762
    }
2,147,483,647✔
763

764
    /// Returns the size, in bytes, that a node occupies on disk, taking compression into account.
765
    /// Pointers without a `back_block` payload omit it, while backpointers and
766
    /// inline-annotation pointers store it.
767
    #[inline]
768
    pub fn compressed_size_for_id(node_id: u8) -> usize {
2,147,483,647✔
769
        if !has_back_block_payload_bytes(node_id) {
2,147,483,647✔
770
            Self::encoded_size_compressed_for_id(node_id)
2,147,483,647✔
771
        } else {
772
            Self::encoded_size_for_id(node_id)
2,147,483,647✔
773
        }
774
    }
2,147,483,647✔
775
}
776

777
/// Cursor structure for walking down one or more Tries.  This structure helps other parts of the
778
/// codebase remember which nodes were visited, which blocks they came from, and which pointers
779
/// were walked.  In particular, it's useful for figuring out where to insert a new node, and which
780
/// nodes to visit when updating the root node hash.
781
#[derive(Debug, Clone, PartialEq)]
782
pub struct TrieCursor<T: MarfTrieId> {
783
    pub path: TrieHash,                  // the path to walk
784
    pub index: usize,                    // index into the path
785
    pub node_path_index: usize,          // index into the currently-visited node's compressed path
786
    pub nodes: Vec<TrieNodeType>,        // list of nodes this cursor visits
787
    pub node_ptrs: Vec<TriePtr>,         // list of ptr branches this cursor has taken
788
    pub block_hashes: Vec<T>, // list of Tries we've visited.  block_hashes[i] corresponds to node_ptrs[i]
789
    pub last_error: Option<CursorError>, // last error encountered while walking (used to make sure the client calls the right "recovery" method)
790
}
791

792
impl<T: MarfTrieId> TrieCursor<T> {
793
    pub fn new(path: &TrieHash, root_ptr: TriePtr) -> TrieCursor<T> {
563,689,793✔
794
        TrieCursor {
563,689,793✔
795
            path: *path,
563,689,793✔
796
            index: 0,
563,689,793✔
797
            node_path_index: 0,
563,689,793✔
798
            nodes: vec![],
563,689,793✔
799
            node_ptrs: vec![root_ptr],
563,689,793✔
800
            block_hashes: vec![],
563,689,793✔
801
            last_error: None,
563,689,793✔
802
        }
563,689,793✔
803
    }
563,689,793✔
804

805
    /// what point in the path are we at now?
806
    /// Will be None only if we haven't taken a step yet.
807
    pub fn chr(&self) -> Option<u8> {
32,988,611✔
808
        if self.index > 0 {
32,988,611✔
809
            self.path.as_bytes().get(self.index - 1).copied()
32,988,611✔
810
        } else {
811
            None
×
812
        }
813
    }
32,988,611✔
814

815
    /// what offset in the path are we at?
816
    pub fn tell(&self) -> usize {
4,322,617✔
817
        self.index
4,322,617✔
818
    }
4,322,617✔
819

820
    /// what is the offset in the node's compressed path?
821
    pub fn ntell(&self) -> usize {
8,642,039✔
822
        self.node_path_index
8,642,039✔
823
    }
8,642,039✔
824

825
    /// Are we a the [E]nd [O]f [P]ath?
826
    pub fn eop(&self) -> bool {
534,219,127✔
827
        self.index == self.path.len()
534,219,127✔
828
    }
534,219,127✔
829

830
    /// last ptr visited
831
    pub fn ptr(&self) -> TriePtr {
565,691,810✔
832
        // should always be true by construction
833
        assert!(!self.node_ptrs.is_empty());
565,691,810✔
834
        *self.node_ptrs.last().unwrap()
565,691,810✔
835
    }
565,691,810✔
836

837
    /// last node visited.
838
    /// Will only be None if we haven't taken a step yet.
839
    pub fn node(&self) -> Option<TrieNodeType> {
34,182,234✔
840
        self.nodes.last().cloned()
34,182,234✔
841
    }
34,182,234✔
842

843
    /// Are we at the [E]nd [O]f a [N]ode's [P]ath?
844
    pub fn eonp(&self, node: &TrieNodeType) -> bool {
25,320,741✔
845
        match node {
25,320,741✔
846
            TrieNodeType::Leaf(ref data) => self.node_path_index == data.path.len(),
2,157,921✔
847
            TrieNodeType::Node4(ref data) => self.node_path_index == data.path.len(),
5,833,432✔
848
            TrieNodeType::Node16(ref data) => self.node_path_index == data.path.len(),
3,896,209✔
849
            TrieNodeType::Node48(ref data) => self.node_path_index == data.path.len(),
4,929,797✔
850
            TrieNodeType::Node256(ref data) => self.node_path_index == data.path.len(),
8,503,382✔
851
        }
852
    }
25,320,741✔
853

854
    /// Walk to the next node, following its compressed path as far as we can and then walking to
855
    /// its child pointer.  If we successfully follow the path, then return the pointer we reached.
856
    /// Otherwise, if we reach the end of the path, return None.  If the path diverges or a node
857
    /// cannot be found, then return an Err.
858
    ///
859
    /// This method does not follow back-pointers, and will return Err if a back-pointer is
860
    /// reached.  The caller will need to manually call walk() on the last node visited to get the
861
    /// back-pointer, shunt to the node it points to, and then call walk_backptr_step_backptr() to
862
    /// record the back-pointer that was followed.  Once the back-pointer has been followed,
863
    /// caller should call walk_backptr_step_finish().  This is specifically relevant to the MARF,
864
    /// not to the individual tries.
865
    pub fn walk(
1,520,750,369✔
866
        &mut self,
1,520,750,369✔
867
        node: &TrieNodeType,
1,520,750,369✔
868
        block_hash: &T,
1,520,750,369✔
869
    ) -> Result<Option<TriePtr>, CursorError> {
1,520,750,369✔
870
        // can only be called if we called the appropriate "repair" method or if there is no error
871
        assert!(self.last_error.is_none());
1,520,750,369✔
872

873
        trace!("cursor: walk: node = {:?} block = {:?}", node, block_hash);
1,520,750,369✔
874

875
        // walk this node
876
        self.nodes.push((*node).clone());
1,520,750,369✔
877
        self.node_path_index = 0;
1,520,750,369✔
878

879
        if self.index >= self.path.len() {
1,520,750,369✔
880
            trace!("cursor: out of path");
38,322✔
881
            return Ok(None);
38,322✔
882
        }
1,520,712,047✔
883

884
        let node_path = node.path_bytes();
1,520,712,047✔
885
        let path_bytes = self.path.as_bytes();
1,520,712,047✔
886

887
        // consume as much of the compressed path as we can
888
        for (_i, path_set) in node_path.iter().enumerate() {
2,147,483,647✔
889
            let Some(path_head) = path_bytes.get(self.index) else {
2,147,483,647✔
890
                trace!("cursor: out of path");
×
891
                return Ok(None);
×
892
            };
893
            if path_set != path_head {
2,147,483,647✔
894
                // diverged
895
                trace!("cursor: diverged({} != {}): i = {_i}, self.index = {}, self.node_path_index = {}", to_hex(node_path), to_hex(path_bytes), self.index, self.node_path_index);
13,158,799✔
896
                self.last_error = Some(CursorError::PathDiverged);
13,158,799✔
897
                return Err(CursorError::PathDiverged);
13,158,799✔
898
            }
2,147,483,647✔
899
            self.index += 1;
2,147,483,647✔
900
            self.node_path_index += 1;
2,147,483,647✔
901
        }
902

903
        // walked to end of the node's compressed path.
904
        // Find the pointer to the next node.
905
        if let Some(chr) = path_bytes.get(self.index) {
1,507,553,248✔
906
            self.index += 1;
984,473,357✔
907
            let mut ptr_opt = node.walk(*chr);
984,473,357✔
908

909
            let do_walk = match &ptr_opt {
984,473,357✔
910
                Some(ptr) => {
958,174,187✔
911
                    if !is_backptr(ptr.id()) {
958,174,187✔
912
                        // not going to follow a back-pointer
913
                        self.node_ptrs.push(*ptr);
542,700,439✔
914
                        self.block_hashes.push(block_hash.clone());
542,700,439✔
915
                        true
542,700,439✔
916
                    } else {
917
                        // the caller will need to follow the backptr, and call
918
                        // repair_backptr_step_backptr() for each node visited, and then repair_backptr_finish()
919
                        // once the final ptr and block_hash are discovered.
920
                        self.last_error = Some(CursorError::BackptrEncountered(*ptr));
415,473,748✔
921
                        false
415,473,748✔
922
                    }
923
                }
924
                None => {
925
                    self.last_error = Some(CursorError::ChrNotFound);
26,299,170✔
926
                    false
26,299,170✔
927
                }
928
            };
929

930
            if !do_walk {
984,473,357✔
931
                ptr_opt = None;
441,772,918✔
932
            }
577,919,041✔
933

934
            if ptr_opt.is_none() {
984,473,357✔
935
                assert!(self.last_error.is_some());
441,772,918✔
936

937
                trace!(
441,772,918✔
938
                    "cursor: not found: chr = 0x{:02x}, self.index = {}, self.path = {:?}",
939
                    chr,
940
                    self.index - 1,
×
941
                    &path_bytes
×
942
                );
943
                return Err(self.last_error.clone().unwrap());
441,772,918✔
944
            } else {
945
                return Ok(ptr_opt);
542,700,439✔
946
            }
947
        } else {
948
            trace!("cursor: now out of path");
523,079,891✔
949
            return Ok(None);
523,079,891✔
950
        }
951
    }
1,520,750,369✔
952

953
    /// Replace the last-visited node and ptr within this trie.  Used when doing a copy-on-write or
954
    /// promoting a node, so the cursor state accurately reflects the nodes and tries visited.
955
    #[inline]
956
    pub fn repair_retarget(&mut self, node: &TrieNodeType, ptr: &TriePtr, hash: &T) {
2,630,793✔
957
        // this can only be called if we failed to walk to a node (this method _should not_ be
958
        // called if we walked to a backptr).
959
        if Some(CursorError::ChrNotFound) != self.last_error
2,630,793✔
960
            && Some(CursorError::PathDiverged) != self.last_error
2,161,263✔
961
        {
962
            eprintln!("{:?}", &self.last_error);
×
963
            panic!();
×
964
        }
2,630,793✔
965

966
        self.nodes.pop();
2,630,793✔
967
        self.node_ptrs.pop();
2,630,793✔
968
        self.block_hashes.pop();
2,630,793✔
969

970
        self.nodes.push(node.clone());
2,630,793✔
971
        self.node_ptrs.push(*ptr);
2,630,793✔
972
        self.block_hashes.push(hash.clone());
2,630,793✔
973

974
        self.last_error = None;
2,630,793✔
975
    }
2,630,793✔
976

977
    /// Record that a node was walked to by way of a back-pointer.
978
    /// next_node should be the node walked to.
979
    /// ptr is the ptr we'll be walking from, off of next_node.
980
    /// block_hash is the block where next_node came from.
981
    #[inline]
982
    pub fn repair_backptr_step_backptr(
414,366,234✔
983
        &mut self,
414,366,234✔
984
        next_node: &TrieNodeType,
414,366,234✔
985
        ptr: &TriePtr,
414,366,234✔
986
        block_hash: T,
414,366,234✔
987
    ) {
414,366,234✔
988
        // this can only be called if we walked to a backptr.
989
        // If it's anything else, we're in trouble.
990
        if Some(CursorError::ChrNotFound) == self.last_error
414,366,234✔
991
            || Some(CursorError::PathDiverged) == self.last_error
414,366,234✔
992
        {
993
            eprintln!("{:?}", &self.last_error);
×
994
            panic!();
×
995
        }
414,366,234✔
996

997
        trace!(
414,366,234✔
998
            "Cursor: repair_backptr_step_backptr ptr={:?} block_hash={:?} next_node={:?}",
999
            ptr,
1000
            &block_hash,
×
1001
            next_node
1002
        );
1003

1004
        let backptr = TriePtr::new(set_backptr(ptr.id()), ptr.chr(), ptr.ptr()); // set_backptr() informs update_root_hash() to skip this node
414,366,234✔
1005
        self.node_ptrs.push(backptr);
414,366,234✔
1006
        self.block_hashes.push(block_hash);
414,366,234✔
1007

1008
        self.nodes.push(next_node.clone());
414,366,234✔
1009
    }
414,366,234✔
1010

1011
    /// Record that we landed on a non-backptr from a backptr.
1012
    /// ptr is a non-backptr that refers to the node we landed on.
1013
    #[inline]
1014
    pub fn repair_backptr_finish(&mut self, ptr: &TriePtr, block_hash: T) {
414,366,297✔
1015
        // this can only be called if we walked to a backptr.
1016
        // If it's anything else, we're in trouble.
1017
        if Some(CursorError::ChrNotFound) == self.last_error
414,366,297✔
1018
            || Some(CursorError::PathDiverged) == self.last_error
414,366,297✔
1019
        {
1020
            eprintln!("{:?}", &self.last_error);
×
1021
            panic!();
×
1022
        }
414,366,297✔
1023
        assert!(!is_backptr(ptr.id()));
414,366,297✔
1024

1025
        trace!("Cursor: repair_backptr_finish ptr={ptr:?} block_hash={block_hash:?}");
414,366,297✔
1026

1027
        self.node_ptrs.push(*ptr);
414,366,297✔
1028
        self.block_hashes.push(block_hash);
414,366,297✔
1029

1030
        self.last_error = None;
414,366,297✔
1031
    }
414,366,297✔
1032
}
1033

1034
impl PartialEq for TrieLeaf {
1035
    fn eq(&self, other: &TrieLeaf) -> bool {
19,851✔
1036
        self.path == other.path && self.data.as_bytes() == other.data.as_bytes()
19,851✔
1037
    }
19,851✔
1038
}
1039

1040
impl TrieLeaf {
1041
    pub fn new(path: &[u8], data: &[u8]) -> TrieLeaf {
87,925✔
1042
        assert!(data.len() <= 40);
87,925✔
1043
        let mut bytes = [0u8; 40];
87,925✔
1044
        bytes.copy_from_slice(data);
87,925✔
1045
        TrieLeaf {
87,925✔
1046
            path: path.to_owned(),
87,925✔
1047
            data: MARFValue(bytes),
87,925✔
1048
        }
87,925✔
1049
    }
87,925✔
1050

1051
    pub fn from_value(path: &[u8], value: MARFValue) -> TrieLeaf {
35,043,886✔
1052
        TrieLeaf {
35,043,886✔
1053
            path: path.to_owned(),
35,043,886✔
1054
            data: value,
35,043,886✔
1055
        }
35,043,886✔
1056
    }
35,043,886✔
1057
}
1058

1059
impl fmt::Debug for TrieLeaf {
1060
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6,612✔
1061
        write!(
6,612✔
1062
            f,
6,612✔
1063
            "TrieLeaf(path={} data={})",
1064
            &to_hex(&self.path),
6,612✔
1065
            &self.data.to_hex()
6,612✔
1066
        )
1067
    }
6,612✔
1068
}
1069

1070
impl StacksMessageCodec for TrieLeaf {
1071
    fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
112✔
1072
        self.path.consensus_serialize(fd)?;
112✔
1073
        self.data.consensus_serialize(fd)
112✔
1074
    }
112✔
1075

1076
    fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<TrieLeaf, codec_error> {
×
1077
        let path = read_next(fd)?;
×
1078
        let data = read_next(fd)?;
×
1079

1080
        Ok(TrieLeaf { path, data })
×
1081
    }
×
1082
}
1083

1084
/// Trie node with four children
1085
#[derive(Clone, PartialEq)]
1086
pub struct TrieNode4 {
1087
    pub path: Vec<u8>,
1088
    pub ptrs: [TriePtr; 4],
1089
    /// If this node was created by copy-on-write, then this points to the node it was copied from.
1090
    pub cowptr: Option<TrieCowPtr>,
1091
    /// List of patches applied to this node.  Fields are (node block ID, pointer to node, patch itself)
1092
    pub patches: Vec<(u32, TriePtr, TrieNodePatch)>,
1093
}
1094

1095
impl fmt::Debug for TrieNode4 {
1096
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86✔
1097
        write!(
86✔
1098
            f,
86✔
1099
            "TrieNode4(path={} ptrs={})",
1100
            &to_hex(&self.path),
86✔
1101
            ptrs_fmt(&self.ptrs)
86✔
1102
        )
1103
    }
86✔
1104
}
1105

1106
impl TrieNode4 {
1107
    pub fn new(path: &[u8]) -> TrieNode4 {
2,162,488✔
1108
        TrieNode4 {
2,162,488✔
1109
            path: path.to_owned(),
2,162,488✔
1110
            ptrs: [TriePtr::default(); 4],
2,162,488✔
1111
            cowptr: None,
2,162,488✔
1112
            patches: vec![],
2,162,488✔
1113
        }
2,162,488✔
1114
    }
2,162,488✔
1115
}
1116

1117
/// Trie node with 16 children
1118
#[derive(Clone, PartialEq)]
1119
pub struct TrieNode16 {
1120
    pub path: Vec<u8>,
1121
    pub ptrs: [TriePtr; 16],
1122
    /// If this node was created by copy-on-write, then this points to the node it was copied from.
1123
    pub cowptr: Option<TrieCowPtr>,
1124
    /// List of patches applied to this node.  Fields are (node block ID, pointer to node, patch itself)
1125
    pub patches: Vec<(u32, TriePtr, TrieNodePatch)>,
1126
}
1127

1128
impl fmt::Debug for TrieNode16 {
1129
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48✔
1130
        write!(
48✔
1131
            f,
48✔
1132
            "TrieNode16(path={} ptrs={})",
1133
            &to_hex(&self.path),
48✔
1134
            ptrs_fmt(&self.ptrs)
48✔
1135
        )
1136
    }
48✔
1137
}
1138

1139
impl TrieNode16 {
1140
    pub fn new(path: &[u8]) -> TrieNode16 {
396✔
1141
        TrieNode16 {
396✔
1142
            path: path.to_owned(),
396✔
1143
            ptrs: [TriePtr::default(); 16],
396✔
1144
            cowptr: None,
396✔
1145
            patches: vec![],
396✔
1146
        }
396✔
1147
    }
396✔
1148

1149
    /// Promote a Node4 to a Node16
1150
    pub fn from_node4(node4: &TrieNode4) -> TrieNode16 {
358,809✔
1151
        let mut ptrs = [TriePtr::default(); 16];
358,809✔
1152
        ptrs[..4].copy_from_slice(&node4.ptrs[..4]);
358,809✔
1153
        TrieNode16 {
358,809✔
1154
            path: node4.path.clone(),
358,809✔
1155
            ptrs,
358,809✔
1156
            cowptr: None,
358,809✔
1157
            patches: vec![],
358,809✔
1158
        }
358,809✔
1159
    }
358,809✔
1160
}
1161

1162
/// Trie node with 48 children
1163
#[derive(Clone)]
1164
pub struct TrieNode48 {
1165
    pub path: Vec<u8>,
1166
    /// If indexes[i] is non-negative, then it is an index into ptrs.
1167
    pub indexes: [i8; 256],
1168
    pub ptrs: [TriePtr; 48],
1169
    /// If this node was created by copy-on-write, then this points to the node it was copied from.
1170
    pub cowptr: Option<TrieCowPtr>,
1171
    /// List of patches applied to this node.  Fields are (node block ID, pointer to node, patch itself)
1172
    pub patches: Vec<(u32, TriePtr, TrieNodePatch)>,
1173
}
1174

1175
impl fmt::Debug for TrieNode48 {
1176
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33✔
1177
        write!(
33✔
1178
            f,
33✔
1179
            "TrieNode48(path={} ptrs={})",
1180
            &to_hex(&self.path),
33✔
1181
            ptrs_fmt(&self.ptrs)
33✔
1182
        )
1183
    }
33✔
1184
}
1185

1186
impl PartialEq for TrieNode48 {
1187
    fn eq(&self, other: &TrieNode48) -> bool {
2✔
1188
        self.path == other.path && self.ptrs == other.ptrs && self.indexes == other.indexes
2✔
1189
    }
2✔
1190
}
1191

1192
impl TrieNode48 {
1193
    pub fn new(path: &[u8]) -> TrieNode48 {
396✔
1194
        TrieNode48 {
396✔
1195
            path: path.to_owned(),
396✔
1196
            indexes: [-1; 256],
396✔
1197
            ptrs: [TriePtr::default(); 48],
396✔
1198
            cowptr: None,
396✔
1199
            patches: vec![],
396✔
1200
        }
396✔
1201
    }
396✔
1202

1203
    /// Promote a node16 to a node48
1204
    // allow indexing: this function only indexes constant-size arrays
1205
    // with constant-sized indexes
1206
    #[allow(clippy::indexing_slicing)]
1207
    pub fn from_node16(node16: &TrieNode16) -> TrieNode48 {
92,051✔
1208
        let mut ptrs = [TriePtr::default(); 48];
92,051✔
1209
        let mut indexes = [-1i8; 256];
92,051✔
1210
        for i in 0..16 {
1,472,816✔
1211
            ptrs[i] = node16.ptrs[i];
1,472,816✔
1212
            indexes[ptrs[i].chr() as usize] = i as i8;
1,472,816✔
1213
        }
1,472,816✔
1214
        TrieNode48 {
92,051✔
1215
            path: node16.path.clone(),
92,051✔
1216
            indexes,
92,051✔
1217
            ptrs,
92,051✔
1218
            cowptr: None,
92,051✔
1219
            patches: vec![],
92,051✔
1220
        }
92,051✔
1221
    }
92,051✔
1222
}
1223

1224
/// Trie node with 256 children
1225
#[derive(Clone)]
1226
pub struct TrieNode256 {
1227
    pub path: Vec<u8>,
1228
    pub ptrs: [TriePtr; 256],
1229
    /// If this node was created by copy-on-write, then this points to the node it was copied from.
1230
    pub cowptr: Option<TrieCowPtr>,
1231
    /// List of patches applied to this node.  Fields are (node block ID, pointer to node, patch itself)
1232
    pub patches: Vec<(u32, TriePtr, TrieNodePatch)>,
1233
}
1234

1235
impl fmt::Debug for TrieNode256 {
1236
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84✔
1237
        write!(
84✔
1238
            f,
84✔
1239
            "TrieNode256(path={} ptrs={})",
1240
            &to_hex(&self.path),
84✔
1241
            ptrs_fmt(&self.ptrs)
84✔
1242
        )
1243
    }
84✔
1244
}
1245

1246
impl PartialEq for TrieNode256 {
1247
    fn eq(&self, other: &TrieNode256) -> bool {
12✔
1248
        self.path == other.path && self.ptrs == other.ptrs
12✔
1249
    }
12✔
1250
}
1251

1252
impl TrieNode256 {
1253
    pub fn new(path: &[u8]) -> TrieNode256 {
16,218✔
1254
        TrieNode256 {
16,218✔
1255
            path: path.to_owned(),
16,218✔
1256
            ptrs: [TriePtr::default(); 256],
16,218✔
1257
            cowptr: None,
16,218✔
1258
            patches: vec![],
16,218✔
1259
        }
16,218✔
1260
    }
16,218✔
1261

1262
    // allow indexing because this function operates on
1263
    //  fixed size arrays (256 array can always be indexed by u8)
1264
    #[allow(clippy::indexing_slicing)]
1265
    pub fn from_node4(node4: &TrieNode4) -> TrieNode256 {
64✔
1266
        let mut ptrs = [TriePtr::default(); 256];
64✔
1267
        for node4_ptr in node4.ptrs.iter() {
256✔
1268
            let c = node4_ptr.chr();
256✔
1269
            ptrs[c as usize] = *node4_ptr;
256✔
1270
        }
256✔
1271
        TrieNode256 {
64✔
1272
            path: node4.path.clone(),
64✔
1273
            ptrs,
64✔
1274
            cowptr: None,
64✔
1275
            patches: vec![],
64✔
1276
        }
64✔
1277
    }
64✔
1278

1279
    /// Promote a node48 to a node256
1280
    // allow indexing because this function operates on
1281
    //  fixed size arrays (256 array can always be indexed by u8)
1282
    #[allow(clippy::indexing_slicing)]
1283
    pub fn from_node48(node48: &TrieNode48) -> TrieNode256 {
18,670✔
1284
        let mut ptrs = [TriePtr::default(); 256];
18,670✔
1285
        for node48_ptr in node48.ptrs.iter() {
896,160✔
1286
            let c = node48_ptr.chr();
896,160✔
1287
            ptrs[c as usize] = *node48_ptr;
896,160✔
1288
        }
896,160✔
1289
        TrieNode256 {
18,670✔
1290
            path: node48.path.clone(),
18,670✔
1291
            ptrs,
18,670✔
1292
            cowptr: None,
18,670✔
1293
            patches: vec![],
18,670✔
1294
        }
18,670✔
1295
    }
18,670✔
1296
}
1297

1298
/// This is a non-consensus "patch node" that applies a diff atop a base node.  There can be up to
1299
/// MAX_PATCH_DEPTH patch nodes applied atop the base node.
1300
#[derive(Clone, PartialEq)]
1301
pub struct TrieNodePatch {
1302
    /// Pointer to the node we're patching (will always be a back-block ptr)
1303
    pub ptr: TriePtr,
1304
    /// Field of ptrs to insert atop the base node
1305
    pub ptr_diff: Vec<TriePtr>,
1306
}
1307

1308
impl fmt::Debug for TrieNodePatch {
1309
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
1310
        write!(
×
1311
            f,
×
1312
            "TrieNodePatch(ptr={} ptr_diff={})",
1313
            &ptrs_fmt(&[self.ptr]),
×
1314
            ptrs_fmt(&self.ptr_diff)
×
1315
        )
1316
    }
×
1317
}
1318

1319
impl StacksMessageCodec for TrieNodePatch {
1320
    /// Serializes this [`TrieNodePatch`] to the given writer, with the following format:
1321
    ///
1322
    /// 0    1        1+P      2+P              2+P+N
1323
    /// |----|--------|----------|----------------|
1324
    ///   id     ptr    diff len     ptr diffs
1325
    ///   (1)    (P)       (1)          (N)
1326
    ///
1327
    /// where:
1328
    /// - `id` is [`TrieNodeID::Patch`]
1329
    /// - `ptr` is a compressed [`TriePtr`]
1330
    /// - `diff len` is the number of diffs, serialized as `len - 1`
1331
    /// - `ptr diffs` are the patch diffs written in compressed format
1332
    ///
1333
    /// # Invariants
1334
    ///
1335
    /// The number of diffs must be in the range `1..=256`. A patch is valid only
1336
    /// if it contains at least one diff (see the factory methods
1337
    /// [`TrieNodePatch::try_from_nodetype`] and [`TrieNodePatch::try_from_patch`]).
1338
    ///
1339
    /// To fit in a `u8`, the diff count is normalized to `len - 1` before
1340
    /// serialization.
1341
    ///
1342
    /// # Errors
1343
    ///
1344
    /// Returns `Err(codec_error::SerializeError)` if:
1345
    /// * Writing to `fd` fails.
1346
    /// * `ptr` fails to serialize.
1347
    /// * Any pointer in `ptr diffs` fails to serialize.
1348
    /// * The diff count is `0` or greater than `256`.
1349
    fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
4,904,026✔
1350
        write_next(fd, &(TrieNodeID::Patch as u8))?;
4,904,026✔
1351
        self.ptr
4,904,026✔
1352
            .write_bytes_compressed(fd)
4,904,026✔
1353
            .map_err(|e| codec_error::SerializeError(format!("Failed to serialize .ptr: {e:?}")))?;
4,904,026✔
1354

1355
        let num_ptrs = self.ptr_diff.len();
4,904,026✔
1356
        if num_ptrs == 0 || num_ptrs > 256 {
4,904,026✔
1357
            return Err(codec_error::SerializeError(format!(
4✔
1358
                "Cannot serialize TrieNodePatch with invalid ptrs len {num_ptrs} (expected 1..=256)"
4✔
1359
            )));
4✔
1360
        }
4,904,022✔
1361
        // normalize num_ptrs to range [0, 255] to fit in u8
1362
        let num_ptrs_norm = num_ptrs.checked_sub(1).expect("infallible");
4,904,022✔
1363
        let num_ptrs_u8 = u8::try_from(num_ptrs_norm).expect("infallible");
4,904,022✔
1364
        write_next(fd, &num_ptrs_u8).map_err(|e| {
4,904,022✔
1365
            codec_error::SerializeError(format!("Failed to serialize .ptr_diff.len(): {e:?}"))
×
1366
        })?;
×
1367

1368
        for ptr in self.ptr_diff.iter() {
12,823,270✔
1369
            ptr.write_bytes_compressed(fd).map_err(|e| {
12,823,270✔
1370
                codec_error::SerializeError(format!("Failed to serialize ptr in .ptr_diff: {e:?}"))
×
1371
            })?;
×
1372
        }
1373
        Ok(())
4,904,022✔
1374
    }
4,904,026✔
1375

1376
    /// Deserializes a [`TrieNodePatch`] from the given reader.
1377
    ///
1378
    /// This method expects the byte stream to be in the exact format produced by
1379
    /// [`TrieNodePatch::consensus_serialize`] (see that method for the detailed
1380
    /// wire format description)
1381
    ///
1382
    /// During deserialization, the stored diff length is de-normalized by
1383
    /// adding `1`, reversing the `len - 1` normalization applied during
1384
    /// serialization.
1385
    ///
1386
    /// # Errors
1387
    ///
1388
    /// Returns `Err(codec_error::DeserializeError)` if:
1389
    /// * The node identifier does not match [`TrieNodeID::Patch`].
1390
    /// * Reading from `fd` fails.
1391
    /// * The pointer or any pointer diff fails to deserialize.
1392
    fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, codec_error> {
1,047,239,710✔
1393
        let id: u8 = read_next(fd)?;
1,047,239,710✔
1394
        if id != TrieNodeID::Patch as u8 {
1,047,239,710✔
1395
            return Err(codec_error::DeserializeError(
1✔
1396
                "Did not read a TrieNodeID::Patch".to_string(),
1✔
1397
            ));
1✔
1398
        }
1,047,239,709✔
1399

1400
        let ptr = TriePtr::read_bytes_compressed(fd)?;
1,047,239,709✔
1401
        let num_ptrs_u8: u8 = read_next(fd)?;
1,047,239,709✔
1402
        let num_ptrs_norm = usize::try_from(num_ptrs_u8).expect("infallible");
1,047,239,709✔
1403
        // denormalize num_ptrs to range [1, 256] (reversing the -1 introduced during serialization)
1404
        let num_ptrs = num_ptrs_norm.checked_add(1).expect("infallible");
1,047,239,709✔
1405

1406
        let mut ptr_diff: Vec<TriePtr> = Vec::with_capacity(num_ptrs);
1,047,239,709✔
1407
        for _ in 0..num_ptrs {
1,047,239,709✔
1408
            ptr_diff.push(TriePtr::read_bytes_compressed(fd)?);
2,147,483,647✔
1409
        }
1410
        Ok(Self { ptr, ptr_diff })
1,047,239,690✔
1411
    }
1,047,239,710✔
1412
}
1413

1414
/// Turn each non-empty, non-backptr in `ptrs` into a backptr.
1415
/// If `back_block` is already non-zero (squash annotation), it is preserved;
1416
/// otherwise it is set to `child_block_id`.
1417
pub(crate) fn node_copy_update_ptrs(ptrs: &mut [TriePtr], child_block_id: u32) {
2,102,353,359✔
1418
    for pointer in ptrs.iter_mut() {
2,147,483,647✔
1419
        // if the node is empty, do nothing, if it's a back pointer,
1420
        if pointer.id() == TrieNodeID::Empty as u8 || is_backptr(pointer.id()) {
2,147,483,647✔
1421
            continue;
2,147,483,647✔
1422
        }
2,147,483,647✔
1423
        if pointer.back_block == 0 {
2,147,483,647✔
1424
            pointer.back_block = child_block_id;
2,147,483,647✔
1425
        }
2,147,483,647✔
1426
        pointer.id = set_backptr(pointer.id());
2,147,483,647✔
1427
    }
1428
}
2,102,353,359✔
1429

1430
/// Given the current block ID, convert every backptr pointer whose back_block is equal to
1431
/// `cur_block_id` to a normal pointer.  This is used when applying patches.
1432
fn node_normalize_ptrs(ptrs: &mut [TriePtr], cur_block_id: u32) {
1,047,239,633✔
1433
    for ptr in ptrs.iter_mut() {
2,147,483,647✔
1434
        if is_backptr(ptr.id) && ptr.back_block == cur_block_id {
2,147,483,647✔
1435
            // normalize
2,147,483,647✔
1436
            ptr.id = clear_backptr(ptr.id);
2,147,483,647✔
1437
            ptr.back_block = 0;
2,147,483,647✔
1438
        }
2,147,483,647✔
1439
    }
1440
}
1,047,239,633✔
1441

1442
impl TrieNodePatch {
1443
    /// Compute the difference between `old_ptrs` and `new_ptrs`. In particular, if a pointer in
1444
    /// `new_ptrs` is in the same block as indicated by `old_node_ptr`, this code will first need to
1445
    /// normalize it (i.e. convert it into a non-backpointer) in order to compare it against the
1446
    /// corresponding pointer in `old_ptrs` (which might have that very same pointer, but not yet
1447
    /// made into a backptr by a COW)
1448
    fn make_ptr_diff(
4,904,031✔
1449
        old_node_ptr: &TriePtr,
4,904,031✔
1450
        old_ptrs: &[TriePtr],
4,904,031✔
1451
        new_ptrs: &[TriePtr],
4,904,031✔
1452
    ) -> Vec<TriePtr> {
4,904,031✔
1453
        let mut ret = Vec::with_capacity(new_ptrs.len());
4,904,031✔
1454
        let mut mapped: [Option<&TriePtr>; 256] = [None; 256];
4,904,031✔
1455
        for old_ptr in old_ptrs.iter() {
244,051,864✔
1456
            // SAFETY: chr() is a u8, so it's in range [0, 256)
1457
            if !old_ptr.is_empty() {
244,051,864✔
1458
                let mapped_ptr = mapped
145,169,359✔
1459
                    .get_mut(old_ptr.chr() as usize)
145,169,359✔
1460
                    .expect("infallible: mapped has 256 elements and .chr() is a u8");
145,169,359✔
1461
                *mapped_ptr = Some(old_ptr);
145,169,359✔
1462
            }
148,415,673✔
1463
        }
1464

1465
        for new_ptr in new_ptrs.iter() {
244,051,874✔
1466
            if new_ptr.is_empty() {
244,051,874✔
1467
                continue;
95,993,772✔
1468
            }
148,058,102✔
1469
            // SAFETY: chr() is a u8, so it's in range [0, 256)
1470
            if let Some(old_ptr) = *mapped
148,058,102✔
1471
                .get(new_ptr.chr() as usize)
148,058,102✔
1472
                .expect("infallible: mapped has 256 elements and .chr() is a u8")
148,058,102✔
1473
            {
1474
                if !is_backptr(old_ptr.id())
145,169,359✔
1475
                    && is_backptr(new_ptr.id())
14,664,563✔
1476
                    && new_ptr.back_block == old_node_ptr.back_block
7,189,365✔
1477
                {
1478
                    // new_ptr may be the backptr-ified version of old_ptr
1479
                    let mut normalized_new_ptr =
7,189,364✔
1480
                        TriePtr::new(clear_ctrl_bits(new_ptr.id()), new_ptr.chr(), new_ptr.ptr());
7,189,364✔
1481
                    normalized_new_ptr.back_block = 0;
7,189,364✔
1482
                    if *old_ptr != normalized_new_ptr {
7,189,364✔
1483
                        trace!(
3✔
1484
                            "new overwritten ptr (old_ptr != normalized_new_ptr): {:?} != {:?}",
1485
                            &normalized_new_ptr,
×
1486
                            old_ptr
1487
                        );
1488
                        ret.push(*new_ptr);
3✔
1489
                    }
7,189,361✔
1490
                } else {
1491
                    if old_ptr != new_ptr {
137,979,995✔
1492
                        trace!(
9,934,272✔
1493
                            "new overwritten ptr (old_ptr != new_ptr): {:?} != {:?}",
1494
                            &new_ptr,
×
1495
                            old_ptr
1496
                        );
1497
                        ret.push(*new_ptr);
9,934,272✔
1498
                    } else if !is_backptr(new_ptr.id()) {
128,045,723✔
1499
                        trace!(
4✔
1500
                            "new overwritten ptr (new_ptr not backptr): {:?} != {:?}",
1501
                            &new_ptr,
×
1502
                            old_ptr
1503
                        );
1504
                        ret.push(*new_ptr);
4✔
1505
                    }
128,045,719✔
1506
                }
1507
            } else {
2,888,743✔
1508
                ret.push(*new_ptr);
2,888,743✔
1509
            }
2,888,743✔
1510
        }
1511
        debug_assert!(
4,904,031✔
1512
            ret.len() <= new_ptrs.len(),
4,904,031✔
1513
            "TrieNodePatch has {} diffs for {} pointer slots",
1514
            ret.len(),
×
1515
            new_ptrs.len()
×
1516
        );
1517
        ret
4,904,031✔
1518
    }
4,904,031✔
1519

1520
    /// Test-only wrapper exposing the private [`TrieNodePatch::make_ptr_diff`] for unit testing
1521
    #[cfg(test)]
1522
    pub fn make_ptr_diff_for_test(
13✔
1523
        old_node_ptr: &TriePtr,
13✔
1524
        old_ptrs: &[TriePtr],
13✔
1525
        new_ptrs: &[TriePtr],
13✔
1526
    ) -> Vec<TriePtr> {
13✔
1527
        TrieNodePatch::make_ptr_diff(old_node_ptr, old_ptrs, new_ptrs)
13✔
1528
    }
13✔
1529

1530
    /// Create a patch from one node4 to another
1531
    pub fn from_node4(old_node_ptr: TriePtr, old_node: &TrieNode4, new_node: &TrieNode4) -> Self {
3,497,942✔
1532
        let ptr_diff = Self::make_ptr_diff(&old_node_ptr, old_node.ptrs(), new_node.ptrs());
3,497,942✔
1533
        Self {
3,497,942✔
1534
            ptr: old_node_ptr,
3,497,942✔
1535
            ptr_diff: ptr_diff,
3,497,942✔
1536
        }
3,497,942✔
1537
    }
3,497,942✔
1538

1539
    /// Create a patch from one node16 to another
1540
    pub fn from_node16(
483,943✔
1541
        old_node_ptr: TriePtr,
483,943✔
1542
        old_node: &TrieNode16,
483,943✔
1543
        new_node: &TrieNode16,
483,943✔
1544
    ) -> Self {
483,943✔
1545
        let ptr_diff = Self::make_ptr_diff(&old_node_ptr, old_node.ptrs(), new_node.ptrs());
483,943✔
1546
        Self {
483,943✔
1547
            ptr: old_node_ptr,
483,943✔
1548
            ptr_diff: ptr_diff,
483,943✔
1549
        }
483,943✔
1550
    }
483,943✔
1551

1552
    /// Create a patch from one node48 to another
1553
    pub fn from_node48(
66,100✔
1554
        old_node_ptr: TriePtr,
66,100✔
1555
        old_node: &TrieNode48,
66,100✔
1556
        new_node: &TrieNode48,
66,100✔
1557
    ) -> Self {
66,100✔
1558
        let ptr_diff = Self::make_ptr_diff(&old_node_ptr, old_node.ptrs(), new_node.ptrs());
66,100✔
1559
        Self {
66,100✔
1560
            ptr: old_node_ptr,
66,100✔
1561
            ptr_diff: ptr_diff,
66,100✔
1562
        }
66,100✔
1563
    }
66,100✔
1564

1565
    /// Create a patch from one node256 to another
1566
    pub fn from_node256(
856,032✔
1567
        old_node_ptr: TriePtr,
856,032✔
1568
        old_node: &TrieNode256,
856,032✔
1569
        new_node: &TrieNode256,
856,032✔
1570
    ) -> Self {
856,032✔
1571
        let ptr_diff = Self::make_ptr_diff(&old_node_ptr, old_node.ptrs(), new_node.ptrs());
856,032✔
1572
        Self {
856,032✔
1573
            ptr: old_node_ptr,
856,032✔
1574
            ptr_diff: ptr_diff,
856,032✔
1575
        }
856,032✔
1576
    }
856,032✔
1577

1578
    /// Create a patch from one nodetype to another.  If they're not the same nodetype, then this
1579
    /// function returns None.
1580
    pub fn try_from_nodetype(
4,904,017✔
1581
        old_node_ptr: TriePtr,
4,904,017✔
1582
        old_node: &TrieNodeType,
4,904,017✔
1583
        new_node: &TrieNodeType,
4,904,017✔
1584
    ) -> Option<Self> {
4,904,017✔
1585
        if clear_ctrl_bits(old_node.id()) != clear_ctrl_bits(new_node.id()) {
4,904,017✔
1586
            trace!("Cannot produce TrieNodePatch: old node and new node are not the same type!");
×
1587
            return None;
×
1588
        }
4,904,017✔
1589

1590
        let patch_opt = match (old_node, new_node) {
4,904,017✔
1591
            (TrieNodeType::Node4(old_data), TrieNodeType::Node4(new_data)) => {
3,497,942✔
1592
                Some(Self::from_node4(old_node_ptr, old_data, new_data))
3,497,942✔
1593
            }
1594
            (TrieNodeType::Node16(old_data), TrieNodeType::Node16(new_data)) => {
483,943✔
1595
                Some(Self::from_node16(old_node_ptr, old_data, new_data))
483,943✔
1596
            }
1597
            (TrieNodeType::Node48(old_data), TrieNodeType::Node48(new_data)) => {
66,100✔
1598
                Some(Self::from_node48(old_node_ptr, old_data, new_data))
66,100✔
1599
            }
1600
            (TrieNodeType::Node256(old_data), TrieNodeType::Node256(new_data)) => {
856,032✔
1601
                Some(Self::from_node256(old_node_ptr, old_data, new_data))
856,032✔
1602
            }
1603
            (_, _) => None,
×
1604
        };
1605
        let Some(patch) = patch_opt else {
4,904,017✔
1606
            trace!("Cannot produce TrieNodePatch: old node and new node are type leaf!");
×
1607
            return None;
×
1608
        };
1609
        if patch.ptr_diff.len() == 0 {
4,904,017✔
1610
            trace!("Cannot produce TrieNodePatch: patch has no diffs!");
1✔
1611
            return None;
1✔
1612
        }
4,904,016✔
1613
        Some(patch)
4,904,016✔
1614
    }
4,904,017✔
1615

1616
    /// Create a patch from one patch to a node
1617
    pub fn try_from_patch(
1✔
1618
        old_patch_ptr: TriePtr,
1✔
1619
        old_patch: &TrieNodePatch,
1✔
1620
        new_node: &TrieNodeType,
1✔
1621
    ) -> Option<Self> {
1✔
1622
        if clear_ctrl_bits(old_patch.ptr.id) != clear_ctrl_bits(new_node.id()) {
1✔
1623
            trace!("Cannot produce TrieNodePatch: old node and new node are not the same type!");
×
1624
            return None;
×
1625
        }
1✔
1626

1627
        let ptr_diff = Self::make_ptr_diff(&old_patch_ptr, &old_patch.ptr_diff, new_node.ptrs());
1✔
1628
        let patch = Self {
1✔
1629
            ptr: old_patch_ptr,
1✔
1630
            ptr_diff,
1✔
1631
        };
1✔
1632
        if patch.ptr_diff.len() == 0 {
1✔
1633
            trace!("Cannot produce TrieNodePatch: patch has no diffs!");
1✔
1634
            return None;
1✔
1635
        }
×
1636
        return Some(patch);
×
1637
    }
1✔
1638

1639
    /// Apply this patch to a node4, given the node, block ID where the patch was found, and block
1640
    /// ID where the node was written.
1641
    pub fn apply_node4(
364,390,994✔
1642
        &self,
364,390,994✔
1643
        mut old_node: TrieNode4,
364,390,994✔
1644
        patch_block_id: u32,
364,390,994✔
1645
        cur_block_id: u32,
364,390,994✔
1646
    ) -> Option<TrieNode4> {
364,390,994✔
1647
        trace!("Apply patch {self:?} read from block ID {patch_block_id} to {old_node:?}");
364,390,994✔
1648
        node_copy_update_ptrs(&mut old_node.ptrs, self.ptr.back_block);
364,390,994✔
1649
        for ptr in self.ptr_diff.iter() {
379,170,200✔
1650
            if !old_node.insert(ptr) {
379,170,200✔
1651
                return None;
×
1652
            }
379,170,200✔
1653
        }
1654
        node_copy_update_ptrs(&mut old_node.ptrs, patch_block_id);
364,390,994✔
1655
        node_normalize_ptrs(&mut old_node.ptrs, cur_block_id);
364,390,994✔
1656
        trace!("Patched up to {old_node:?}");
364,390,994✔
1657
        Some(old_node)
364,390,994✔
1658
    }
364,390,994✔
1659

1660
    /// Apply this patch to a node16, given the node, block ID where the patch was found, and block
1661
    /// ID where the node was written.
1662
    pub fn apply_node16(
39,157,706✔
1663
        &self,
39,157,706✔
1664
        mut old_node: TrieNode16,
39,157,706✔
1665
        patch_block_id: u32,
39,157,706✔
1666
        cur_block_id: u32,
39,157,706✔
1667
    ) -> Option<TrieNode16> {
39,157,706✔
1668
        trace!("Apply patch {self:?} read from block ID {patch_block_id} to {old_node:?}");
39,157,706✔
1669
        node_copy_update_ptrs(&mut old_node.ptrs, self.ptr.back_block);
39,157,706✔
1670
        for ptr in self.ptr_diff.iter() {
40,468,330✔
1671
            if !old_node.insert(ptr) {
40,468,330✔
1672
                return None;
×
1673
            }
40,468,330✔
1674
        }
1675
        node_copy_update_ptrs(&mut old_node.ptrs, patch_block_id);
39,157,706✔
1676
        node_normalize_ptrs(&mut old_node.ptrs, cur_block_id);
39,157,706✔
1677
        trace!("Patched up to {old_node:?}");
39,157,706✔
1678
        Some(old_node)
39,157,706✔
1679
    }
39,157,706✔
1680

1681
    /// Apply this patch to a node48, given the node, block ID where the patch was found, and block
1682
    /// ID where the node was written.
1683
    pub fn apply_node48(
850,045✔
1684
        &self,
850,045✔
1685
        mut old_node: TrieNode48,
850,045✔
1686
        patch_block_id: u32,
850,045✔
1687
        cur_block_id: u32,
850,045✔
1688
    ) -> Option<TrieNode48> {
850,045✔
1689
        trace!("Apply patch {self:?} read from block ID {patch_block_id} to {old_node:?}");
850,045✔
1690
        node_copy_update_ptrs(&mut old_node.ptrs, self.ptr.back_block);
850,045✔
1691
        for ptr in self.ptr_diff.iter() {
3,738,172✔
1692
            if !old_node.insert(ptr) {
3,738,172✔
1693
                return None;
×
1694
            }
3,738,172✔
1695
        }
1696
        node_copy_update_ptrs(&mut old_node.ptrs, patch_block_id);
850,045✔
1697
        node_normalize_ptrs(&mut old_node.ptrs, cur_block_id);
850,045✔
1698
        trace!("Patched up to {old_node:?}");
850,045✔
1699
        Some(old_node)
850,045✔
1700
    }
850,045✔
1701

1702
    /// Apply this patch to a node256, given the node, block ID where the patch was found, and block
1703
    /// ID where the node was written.
1704
    pub fn apply_node256(
642,840,897✔
1705
        &self,
642,840,897✔
1706
        mut old_node: TrieNode256,
642,840,897✔
1707
        patch_block_id: u32,
642,840,897✔
1708
        cur_block_id: u32,
642,840,897✔
1709
    ) -> Option<TrieNode256> {
642,840,897✔
1710
        trace!("Apply patch {self:?} read from block ID {patch_block_id} to {old_node:?}");
642,840,897✔
1711
        node_copy_update_ptrs(&mut old_node.ptrs, self.ptr.back_block);
642,840,897✔
1712
        for ptr in self.ptr_diff.iter() {
2,147,483,647✔
1713
            if !old_node.insert(ptr) {
2,147,483,647✔
1714
                return None;
×
1715
            }
2,147,483,647✔
1716
        }
1717
        node_copy_update_ptrs(&mut old_node.ptrs, patch_block_id);
642,840,897✔
1718
        node_normalize_ptrs(&mut old_node.ptrs, cur_block_id);
642,840,897✔
1719
        trace!("Patched up to {old_node:?}");
642,840,897✔
1720
        Some(old_node)
642,840,897✔
1721
    }
642,840,897✔
1722

1723
    /// Compute the size of the TriePatchNode. Its pointers are always compressed.
1724
    #[inline]
1725
    pub fn size(&self) -> usize {
853,521✔
1726
        // ID
1727
        let mut sz = 1;
853,521✔
1728
        // previous node ptr
1729
        sz += self.ptr.compressed_size();
853,521✔
1730
        // length prefix
1731
        sz += 1;
853,521✔
1732
        // ptr_diff
1733
        for ptr in self.ptr_diff.iter() {
7,912,879✔
1734
            sz += ptr.compressed_size();
7,912,879✔
1735
        }
7,912,879✔
1736
        sz
853,521✔
1737
    }
853,521✔
1738
}
1739

1740
impl TrieNode for TrieNode4 {
1741
    fn id(&self) -> u8 {
21,505,655✔
1742
        TrieNodeID::Node4 as u8
21,505,655✔
1743
    }
21,505,655✔
1744

1745
    fn empty() -> TrieNode4 {
×
1746
        TrieNode4 {
×
1747
            path: vec![],
×
1748
            ptrs: [TriePtr::default(); 4],
×
1749
            cowptr: None,
×
1750
            patches: vec![],
×
1751
        }
×
1752
    }
×
1753

1754
    fn walk(&self, chr: u8) -> Option<TriePtr> {
355,168,919✔
1755
        for ptr in self.ptrs.iter() {
654,466,626✔
1756
            if !ptr.is_empty() && ptr.chr() == chr {
654,466,626✔
1757
                return Some(*ptr);
349,219,360✔
1758
            }
305,247,266✔
1759
        }
1760
        None
5,949,559✔
1761
    }
355,168,919✔
1762

1763
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<TrieNode4, Error> {
441,735,206✔
1764
        let mut ptrs_slice = [TriePtr::default(); 4];
441,735,206✔
1765
        ptrs_from_bytes(TrieNodeID::Node4 as u8, r, &mut ptrs_slice)?;
441,735,206✔
1766
        let path = path_from_bytes(r)?;
253,855,093✔
1767

1768
        Ok(TrieNode4 {
253,855,093✔
1769
            path,
253,855,093✔
1770
            ptrs: ptrs_slice,
253,855,093✔
1771
            cowptr: None,
253,855,093✔
1772
            patches: vec![],
253,855,093✔
1773
        })
253,855,093✔
1774
    }
441,735,206✔
1775

1776
    fn insert(&mut self, ptr: &TriePtr) -> bool {
385,079,788✔
1777
        if self.replace(ptr) {
385,079,788✔
1778
            return true;
260,954,232✔
1779
        }
124,125,556✔
1780

1781
        for slot in self.ptrs.iter_mut() {
405,904,961✔
1782
            if slot.is_empty() {
405,904,961✔
1783
                *slot = *ptr;
124,125,556✔
1784
                return true;
124,125,556✔
1785
            }
281,779,405✔
1786
        }
1787
        false
×
1788
    }
385,079,788✔
1789

1790
    fn replace(&mut self, ptr: &TriePtr) -> bool {
394,397,366✔
1791
        for slot in self.ptrs.iter_mut() {
953,795,912✔
1792
            if !slot.is_empty() && slot.chr() == ptr.chr() {
953,795,912✔
1793
                *slot = *ptr;
270,271,810✔
1794
                return true;
270,271,810✔
1795
            }
683,524,102✔
1796
        }
1797
        false
124,125,556✔
1798
    }
394,397,366✔
1799

1800
    fn ptrs(&self) -> &[TriePtr] {
54,940,012✔
1801
        &self.ptrs
54,940,012✔
1802
    }
54,940,012✔
1803

1804
    fn path(&self) -> &Vec<u8> {
19,870,995✔
1805
        &self.path
19,870,995✔
1806
    }
19,870,995✔
1807

1808
    fn as_trie_node_type(&self) -> TrieNodeType {
1✔
1809
        TrieNodeType::Node4(self.clone())
1✔
1810
    }
1✔
1811

1812
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
2,870,580✔
1813
        self.cowptr.as_ref()
2,870,580✔
1814
    }
2,870,580✔
1815

1816
    fn set_cow_ptr(&mut self, cowptr: TrieCowPtr) {
5,200,359✔
1817
        self.cowptr.replace(cowptr);
5,200,359✔
1818
    }
5,200,359✔
1819

1820
    fn apply_patches(
253,853,876✔
1821
        self,
253,853,876✔
1822
        patches: &[(u32, TriePtr, TrieNodePatch)],
253,853,876✔
1823
        cur_block_id: u32,
253,853,876✔
1824
    ) -> Option<Self> {
253,853,876✔
1825
        let mut node = self;
253,853,876✔
1826
        for (patch_block_id, _, patch) in patches.iter() {
389,075,969✔
1827
            let Some(next_node) = patch.apply_node4(node, *patch_block_id, cur_block_id) else {
364,390,994✔
1828
                return None;
×
1829
            };
1830
            node = next_node;
364,390,994✔
1831
        }
1832
        node.patches.extend_from_slice(patches);
253,853,876✔
1833
        Some(node)
253,853,876✔
1834
    }
253,853,876✔
1835
}
1836

1837
impl TrieNode for TrieNode16 {
1838
    fn id(&self) -> u8 {
3,675,410✔
1839
        TrieNodeID::Node16 as u8
3,675,410✔
1840
    }
3,675,410✔
1841

1842
    fn empty() -> TrieNode16 {
×
1843
        TrieNode16 {
×
1844
            path: vec![],
×
1845
            ptrs: [TriePtr::default(); 16],
×
1846
            cowptr: None,
×
1847
            patches: vec![],
×
1848
        }
×
1849
    }
×
1850

1851
    fn walk(&self, chr: u8) -> Option<TriePtr> {
109,131,478✔
1852
        for ptr in self.ptrs.iter() {
345,514,256✔
1853
            if !ptr.is_empty() && ptr.chr() == chr {
345,514,256✔
1854
                return Some(*ptr);
106,087,589✔
1855
            }
239,426,667✔
1856
        }
1857
        None
3,043,889✔
1858
    }
109,131,478✔
1859

1860
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<TrieNode16, Error> {
65,201,860✔
1861
        let mut ptrs_slice = [TriePtr::default(); 16];
65,201,860✔
1862
        ptrs_from_bytes(TrieNodeID::Node16 as u8, r, &mut ptrs_slice)?;
65,201,860✔
1863
        let path = path_from_bytes(r)?;
42,565,956✔
1864

1865
        Ok(TrieNode16 {
42,565,956✔
1866
            path,
42,565,956✔
1867
            ptrs: ptrs_slice,
42,565,956✔
1868
            cowptr: None,
42,565,956✔
1869
            patches: vec![],
42,565,956✔
1870
        })
42,565,956✔
1871
    }
65,201,860✔
1872

1873
    fn insert(&mut self, ptr: &TriePtr) -> bool {
42,038,376✔
1874
        if self.replace(ptr) {
42,038,376✔
1875
            return true;
31,682,325✔
1876
        }
10,356,051✔
1877

1878
        for slot in self.ptrs.iter_mut() {
69,898,406✔
1879
            if slot.is_empty() {
69,898,406✔
1880
                *slot = *ptr;
10,356,051✔
1881
                return true;
10,356,051✔
1882
            }
59,542,355✔
1883
        }
1884
        false
×
1885
    }
42,038,376✔
1886

1887
    fn replace(&mut self, ptr: &TriePtr) -> bool {
46,856,705✔
1888
        for slot in self.ptrs.iter_mut() {
302,324,892✔
1889
            if !slot.is_empty() && slot.chr() == ptr.chr() {
302,324,892✔
1890
                *slot = *ptr;
36,500,654✔
1891
                return true;
36,500,654✔
1892
            }
265,824,238✔
1893
        }
1894
        false
10,356,051✔
1895
    }
46,856,705✔
1896

1897
    fn ptrs(&self) -> &[TriePtr] {
10,942,334✔
1898
        &self.ptrs
10,942,334✔
1899
    }
10,942,334✔
1900

1901
    fn path(&self) -> &Vec<u8> {
3,252,616✔
1902
        &self.path
3,252,616✔
1903
    }
3,252,616✔
1904

1905
    fn as_trie_node_type(&self) -> TrieNodeType {
1✔
1906
        TrieNodeType::Node16(self.clone())
1✔
1907
    }
1✔
1908

1909
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
411,647✔
1910
        self.cowptr.as_ref()
411,647✔
1911
    }
411,647✔
1912

1913
    fn set_cow_ptr(&mut self, cowptr: TrieCowPtr) {
827,084✔
1914
        self.cowptr.replace(cowptr);
827,084✔
1915
    }
827,084✔
1916

1917
    fn apply_patches(
42,565,934✔
1918
        self,
42,565,934✔
1919
        patches: &[(u32, TriePtr, TrieNodePatch)],
42,565,934✔
1920
        cur_block_id: u32,
42,565,934✔
1921
    ) -> Option<Self> {
42,565,934✔
1922
        let mut node = self;
42,565,934✔
1923
        for (patch_block_id, _, patch) in patches.iter() {
53,472,710✔
1924
            let Some(next_node) = patch.apply_node16(node, *patch_block_id, cur_block_id) else {
39,157,706✔
1925
                return None;
×
1926
            };
1927
            node = next_node;
39,157,706✔
1928
        }
1929
        node.patches.extend_from_slice(patches);
42,565,934✔
1930
        Some(node)
42,565,934✔
1931
    }
42,565,934✔
1932
}
1933

1934
impl TrieNode for TrieNode48 {
1935
    fn id(&self) -> u8 {
1,635,426✔
1936
        TrieNodeID::Node48 as u8
1,635,426✔
1937
    }
1,635,426✔
1938

1939
    fn empty() -> TrieNode48 {
×
1940
        TrieNode48 {
×
1941
            path: vec![],
×
1942
            indexes: [-1; 256],
×
1943
            ptrs: [TriePtr::default(); 48],
×
1944
            cowptr: None,
×
1945
            patches: vec![],
×
1946
        }
×
1947
    }
×
1948

1949
    // allow indexing here because self.indexes is an array of
1950
    // 256, so it can always return a u8
1951
    #[allow(clippy::indexing_slicing)]
1952
    fn walk(&self, chr: u8) -> Option<TriePtr> {
83,094,695✔
1953
        let idx = self.indexes[chr as usize];
83,094,695✔
1954
        let ptr = self.ptrs.get(usize::try_from(idx).ok()?)?;
83,094,695✔
1955
        if ptr.is_empty() {
78,607,550✔
1956
            return None;
×
1957
        }
78,607,550✔
1958
        Some(*ptr)
78,607,550✔
1959
    }
83,094,695✔
1960

1961
    fn write_bytes<W: Write>(&self, w: &mut W) -> Result<(), Error> {
257,928✔
1962
        w.write_all(&[self.id()])?;
257,928✔
1963
        write_ptrs_to_bytes(self.ptrs(), w)?;
257,928✔
1964

1965
        for i in self.indexes.iter() {
66,029,568✔
1966
            w.write_all(&[*i as u8])?;
66,029,568✔
1967
        }
1968

1969
        write_path_to_bytes(self.path().as_slice(), w)
257,928✔
1970
    }
257,928✔
1971

1972
    fn write_bytes_compressed<W: Write>(&self, w: &mut W) -> Result<(), Error> {
64,367✔
1973
        w.write_all(&[set_compressed(self.id())])?;
64,367✔
1974
        write_ptrs_to_bytes_compressed(self.id(), self.ptrs(), w)?;
64,367✔
1975

1976
        for i in self.indexes.iter() {
16,477,952✔
1977
            w.write_all(&[*i as u8])?;
16,477,952✔
1978
        }
1979

1980
        write_path_to_bytes(self.path().as_slice(), w)
64,367✔
1981
    }
64,367✔
1982

1983
    fn byte_len(&self) -> usize {
420,628✔
1984
        get_ptrs_byte_len(&self.ptrs) + 256 + get_path_byte_len(&self.path)
420,628✔
1985
    }
420,628✔
1986

1987
    fn byte_len_compressed(&self) -> usize {
×
1988
        get_ptrs_byte_len_compressed(self.id(), &self.ptrs) + 256 + get_path_byte_len(&self.path)
×
1989
    }
×
1990

1991
    #[allow(clippy::indexing_slicing)]
1992
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<TrieNode48, Error> {
15,901,243✔
1993
        let mut ptrs_slice = [TriePtr::default(); 48];
15,901,243✔
1994
        ptrs_from_bytes(TrieNodeID::Node48 as u8, r, &mut ptrs_slice)?;
15,901,243✔
1995

1996
        let mut indexes = [0u8; 256];
15,122,653✔
1997
        r.read_exact(&mut indexes).inspect_err(|e| {
15,122,653✔
1998
            error!("I/O error reading TrieNode48 indexes: {e:?}");
×
1999
        })?;
×
2000

2001
        let path = path_from_bytes(r)?;
15,122,653✔
2002

2003
        let indexes_slice: [i8; 256] = indexes.map(|i| i as i8);
2,147,483,647✔
2004

2005
        let all_ptrs_valid = ptrs_slice.iter().all(|ptr| {
725,887,344✔
2006
            ptr.is_empty()
725,887,344✔
2007
                || indexes_slice[ptr.chr() as usize] >= 0 && indexes_slice[ptr.chr() as usize] < 48
430,266,583✔
2008
        });
725,887,344✔
2009
        if !all_ptrs_valid {
15,122,653✔
2010
            return Err(Error::CorruptionError(
×
2011
                "Node48: corrupt index array: invalid index value".to_string(),
×
2012
            ));
×
2013
        }
15,122,653✔
2014

2015
        let all_indexes_valid = indexes_slice.iter().all(|index| {
2,147,483,647✔
2016
            let Ok(index) = usize::try_from(*index) else {
2,147,483,647✔
2017
                // if the index is < 0, then no corresponding ptr is
2018
                // stored in the slice and so the index is valid
2019
                return true;
2,147,483,647✔
2020
            };
2021
            let Some(ptr) = ptrs_slice.get(index) else {
430,266,583✔
2022
                // if the index is out of bounds, it is invalid
2023
                return false;
×
2024
            };
2025
            // if the index references a pointer, it must reference a
2026
            // non-empty one
2027
            !ptr.is_empty()
430,266,583✔
2028
        });
2,147,483,647✔
2029
        if !all_indexes_valid {
15,122,653✔
2030
            return Err(Error::CorruptionError(
×
2031
                "Node48: corrupt index array: index points to empty node".to_string(),
×
2032
            ));
×
2033
        }
15,122,653✔
2034

2035
        Ok(TrieNode48 {
15,122,653✔
2036
            path,
15,122,653✔
2037
            indexes: indexes_slice,
15,122,653✔
2038
            ptrs: ptrs_slice,
15,122,653✔
2039
            cowptr: None,
15,122,653✔
2040
            patches: vec![],
15,122,653✔
2041
        })
15,122,653✔
2042
    }
15,901,243✔
2043

2044
    #[allow(clippy::indexing_slicing)]
2045
    fn insert(&mut self, ptr: &TriePtr) -> bool {
5,461,216✔
2046
        if self.replace(ptr) {
5,461,216✔
2047
            return true;
1,176,208✔
2048
        }
4,285,008✔
2049

2050
        let c = ptr.chr();
4,285,008✔
2051
        for i in 0..48 {
121,268,555✔
2052
            if self.ptrs[i].is_empty() {
121,268,555✔
2053
                self.indexes[c as usize] = i as i8;
4,285,008✔
2054
                self.ptrs[i] = *ptr;
4,285,008✔
2055
                return true;
4,285,008✔
2056
            }
116,983,547✔
2057
        }
2058
        false
×
2059
    }
5,461,216✔
2060

2061
    #[allow(clippy::indexing_slicing)]
2062
    fn replace(&mut self, ptr: &TriePtr) -> bool {
11,328,616✔
2063
        let i = self.indexes[ptr.chr() as usize];
11,328,616✔
2064
        if i >= 0 {
11,328,616✔
2065
            self.ptrs[i as usize] = *ptr;
7,043,608✔
2066
            true
7,043,608✔
2067
        } else {
2068
            false
4,285,008✔
2069
        }
2070
    }
11,328,616✔
2071

2072
    fn ptrs(&self) -> &[TriePtr] {
6,980,669✔
2073
        &self.ptrs
6,980,669✔
2074
    }
6,980,669✔
2075

2076
    fn path(&self) -> &Vec<u8> {
1,346,808✔
2077
        &self.path
1,346,808✔
2078
    }
1,346,808✔
2079

2080
    fn as_trie_node_type(&self) -> TrieNodeType {
3✔
2081
        TrieNodeType::Node48(Box::new(self.clone()))
3✔
2082
    }
3✔
2083

2084
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
115,005✔
2085
        self.cowptr.as_ref()
115,005✔
2086
    }
115,005✔
2087

2088
    fn set_cow_ptr(&mut self, cowptr: TrieCowPtr) {
328,042✔
2089
        self.cowptr.replace(cowptr);
328,042✔
2090
    }
328,042✔
2091

2092
    fn apply_patches(
15,122,637✔
2093
        self,
15,122,637✔
2094
        patches: &[(u32, TriePtr, TrieNodePatch)],
15,122,637✔
2095
        cur_block_id: u32,
15,122,637✔
2096
    ) -> Option<Self> {
15,122,637✔
2097
        let mut node = self;
15,122,637✔
2098
        for (patch_block_id, _, patch) in patches.iter() {
15,122,637✔
2099
            let Some(next_node) = patch.apply_node48(node, *patch_block_id, cur_block_id) else {
850,045✔
2100
                return None;
×
2101
            };
2102
            node = next_node;
850,045✔
2103
        }
2104
        node.patches.extend_from_slice(patches);
15,122,637✔
2105
        Some(node)
15,122,637✔
2106
    }
15,122,637✔
2107
}
2108

2109
impl TrieNode for TrieNode256 {
2110
    fn id(&self) -> u8 {
8,783,288✔
2111
        TrieNodeID::Node256 as u8
8,783,288✔
2112
    }
8,783,288✔
2113

2114
    fn empty() -> TrieNode256 {
×
2115
        TrieNode256 {
×
2116
            path: vec![],
×
2117
            ptrs: [TriePtr::default(); 256],
×
2118
            cowptr: None,
×
2119
            patches: vec![],
×
2120
        }
×
2121
    }
×
2122

2123
    #[allow(clippy::indexing_slicing)]
2124
    fn walk(&self, chr: u8) -> Option<TriePtr> {
867,193,223✔
2125
        let ptr = self.ptrs.get(chr as usize)?;
867,193,223✔
2126
        if ptr.is_empty() {
867,193,223✔
2127
            return None;
12,818,577✔
2128
        }
854,374,646✔
2129
        Some(*ptr)
854,374,646✔
2130
    }
867,193,223✔
2131

2132
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<TrieNode256, Error> {
618,275,614✔
2133
        let mut ptrs_slice = [TriePtr::default(); 256];
618,275,614✔
2134
        ptrs_from_bytes(TrieNodeID::Node256 as u8, r, &mut ptrs_slice)?;
618,275,614✔
2135

2136
        let path = path_from_bytes(r)?;
360,611,669✔
2137

2138
        Ok(TrieNode256 {
360,611,669✔
2139
            path,
360,611,669✔
2140
            ptrs: ptrs_slice,
360,611,669✔
2141
            cowptr: None,
360,611,669✔
2142
            patches: vec![],
360,611,669✔
2143
        })
360,611,669✔
2144
    }
618,275,614✔
2145

2146
    #[allow(clippy::indexing_slicing)]
2147
    fn insert(&mut self, ptr: &TriePtr) -> bool {
2,147,483,647✔
2148
        if self.replace(ptr) {
2,147,483,647✔
2149
            return true;
2,147,483,647✔
2150
        }
1,720,680,483✔
2151
        let c = ptr.chr() as usize;
1,720,680,483✔
2152
        self.ptrs[c] = *ptr;
1,720,680,483✔
2153
        true
1,720,680,483✔
2154
    }
2,147,483,647✔
2155

2156
    #[allow(clippy::indexing_slicing)]
2157
    fn replace(&mut self, ptr: &TriePtr) -> bool {
2,147,483,647✔
2158
        let c = ptr.chr() as usize;
2,147,483,647✔
2159
        if !self.ptrs[c].is_empty() && self.ptrs[c].chr() == ptr.chr() {
2,147,483,647✔
2160
            self.ptrs[c] = *ptr;
2,147,483,647✔
2161
            true
2,147,483,647✔
2162
        } else {
2163
            false
1,720,680,483✔
2164
        }
2165
    }
2,147,483,647✔
2166

2167
    fn ptrs(&self) -> &[TriePtr] {
36,487,064✔
2168
        &self.ptrs
36,487,064✔
2169
    }
36,487,064✔
2170

2171
    fn path(&self) -> &Vec<u8> {
8,613,627✔
2172
        &self.path
8,613,627✔
2173
    }
8,613,627✔
2174

2175
    fn as_trie_node_type(&self) -> TrieNodeType {
229✔
2176
        TrieNodeType::Node256(Box::new(self.clone()))
229✔
2177
    }
229✔
2178

2179
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
263,782✔
2180
        self.cowptr.as_ref()
263,782✔
2181
    }
263,782✔
2182

2183
    fn set_cow_ptr(&mut self, cowptr: TrieCowPtr) {
1,518,590✔
2184
        self.cowptr.replace(cowptr);
1,518,590✔
2185
    }
1,518,590✔
2186

2187
    fn apply_patches(
360,610,897✔
2188
        self,
360,610,897✔
2189
        patches: &[(u32, TriePtr, TrieNodePatch)],
360,610,897✔
2190
        cur_block_id: u32,
360,610,897✔
2191
    ) -> Option<Self> {
360,610,897✔
2192
        let mut node = self;
360,610,897✔
2193
        for (patch_block_id, _, patch) in patches.iter() {
680,678,473✔
2194
            let Some(next_node) = patch.apply_node256(node, *patch_block_id, cur_block_id) else {
642,840,897✔
2195
                return None;
9✔
2196
            };
2197
            node = next_node;
642,840,888✔
2198
        }
2199
        node.patches.extend_from_slice(patches);
360,610,888✔
2200
        Some(node)
360,610,888✔
2201
    }
360,610,897✔
2202
}
2203

2204
impl TrieNode for TrieLeaf {
2205
    fn id(&self) -> u8 {
65,639,811✔
2206
        TrieNodeID::Leaf as u8
65,639,811✔
2207
    }
65,639,811✔
2208

2209
    fn empty() -> TrieLeaf {
×
2210
        TrieLeaf::new(&[], &[0u8; 40])
×
2211
    }
×
2212

2213
    fn walk(&self, _chr: u8) -> Option<TriePtr> {
×
2214
        None
×
2215
    }
×
2216

2217
    fn write_bytes<W: Write>(&self, w: &mut W) -> Result<(), Error> {
51,316,285✔
2218
        w.write_all(&[self.id()])?;
51,316,285✔
2219
        write_path_to_bytes(&self.path, w)?;
51,316,285✔
2220
        w.write_all(&self.data.0[..])?;
51,316,285✔
2221
        Ok(())
51,316,285✔
2222
    }
51,316,285✔
2223

2224
    fn write_bytes_compressed<W: Write>(&self, w: &mut W) -> Result<(), Error> {
14,323,526✔
2225
        w.write_all(&[self.id()])?;
14,323,526✔
2226
        write_path_to_bytes(&self.path, w)?;
14,323,526✔
2227
        w.write_all(&self.data.0[..])?;
14,323,526✔
2228
        Ok(())
14,323,526✔
2229
    }
14,323,526✔
2230

2231
    fn byte_len(&self) -> usize {
19,174,307✔
2232
        1 + get_path_byte_len(&self.path) + self.data.len()
19,174,307✔
2233
    }
19,174,307✔
2234

2235
    fn byte_len_compressed(&self) -> usize {
×
2236
        1 + get_path_byte_len(&self.path) + self.data.len()
×
2237
    }
×
2238

2239
    fn from_bytes<R: Read + Seek>(r: &mut R) -> Result<TrieLeaf, Error> {
468,488,961✔
2240
        let mut idbuf = [0u8; 1];
468,488,961✔
2241
        r.read_exact(&mut idbuf).inspect_err(|e| {
468,488,961✔
2242
            error!("I/O error reading TrieLeaf ID: {e:?}");
×
2243
        })?;
×
2244

2245
        if clear_ctrl_bits(idbuf[0]) != TrieNodeID::Leaf as u8 {
468,488,961✔
2246
            return Err(Error::CorruptionError(format!(
×
2247
                "Leaf: bad ID 0x{:02x}",
×
2248
                idbuf[0]
×
2249
            )));
×
2250
        }
468,488,961✔
2251

2252
        let path = path_from_bytes(r)?;
468,488,961✔
2253
        let mut leaf_data = [0u8; MARF_VALUE_ENCODED_SIZE as usize];
468,488,961✔
2254

2255
        r.read_exact(&mut leaf_data).inspect_err(|e| {
468,488,961✔
2256
            error!(
×
2257
                "I/O error reading TrieLeaf data: {e:?}. Got idbuf = {:02x}, path = {}",
2258
                &idbuf[0],
×
2259
                &to_hex(&path)
×
2260
            );
2261
        })?;
×
2262

2263
        Ok(TrieLeaf {
468,488,961✔
2264
            path,
468,488,961✔
2265
            data: MARFValue(leaf_data),
468,488,961✔
2266
        })
468,488,961✔
2267
    }
468,488,961✔
2268

2269
    fn insert(&mut self, _ptr: &TriePtr) -> bool {
×
2270
        panic!("can't insert into a leaf");
×
2271
    }
2272

2273
    fn replace(&mut self, _ptr: &TriePtr) -> bool {
×
2274
        panic!("can't replace in a leaf");
×
2275
    }
2276

2277
    fn ptrs(&self) -> &[TriePtr] {
1,882,690✔
2278
        &[]
1,882,690✔
2279
    }
1,882,690✔
2280

2281
    fn path(&self) -> &Vec<u8> {
×
2282
        &self.path
×
2283
    }
×
2284

2285
    fn as_trie_node_type(&self) -> TrieNodeType {
36,359,279✔
2286
        TrieNodeType::Leaf(self.clone())
36,359,279✔
2287
    }
36,359,279✔
2288

2289
    fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
×
2290
        // no-op
2291
        None
×
2292
    }
×
2293

2294
    fn set_cow_ptr(&mut self, _cowptr: TrieCowPtr) {
9,277,567✔
2295
        // no-op
2296
    }
9,277,567✔
2297

2298
    fn apply_patches(
×
2299
        self,
×
2300
        _patches: &[(u32, TriePtr, TrieNodePatch)],
×
2301
        _cur_block_id: u32,
×
2302
    ) -> Option<Self> {
×
2303
        Some(self)
×
2304
    }
×
2305
}
2306

2307
#[derive(Debug, Clone, PartialEq)]
2308
pub enum TrieNodeType {
2309
    Node4(TrieNode4),
2310
    Node16(TrieNode16),
2311
    Node48(Box<TrieNode48>),
2312
    Node256(Box<TrieNode256>),
2313
    Leaf(TrieLeaf),
2314
}
2315

2316
macro_rules! with_node {
2317
    ($self: expr, $pat:pat, $s:expr) => {
2318
        match $self {
2319
            TrieNodeType::Node4($pat) => $s,
2320
            TrieNodeType::Node16($pat) => $s,
2321
            TrieNodeType::Node48($pat) => $s,
2322
            TrieNodeType::Node256($pat) => $s,
2323
            TrieNodeType::Leaf($pat) => $s,
2324
        }
2325
    };
2326
}
2327

2328
impl TrieNodeType {
2329
    pub fn is_leaf(&self) -> bool {
627,397,726✔
2330
        matches!(self, TrieNodeType::Leaf(_))
627,397,726✔
2331
    }
627,397,726✔
2332

2333
    pub fn is_node4(&self) -> bool {
×
2334
        matches!(self, TrieNodeType::Node4(_))
×
2335
    }
×
2336

2337
    pub fn is_node16(&self) -> bool {
×
2338
        matches!(self, TrieNodeType::Node16(_))
×
2339
    }
×
2340

2341
    pub fn is_node48(&self) -> bool {
×
2342
        matches!(self, TrieNodeType::Node48(_))
×
2343
    }
×
2344

2345
    pub fn is_node256(&self) -> bool {
54,770,711✔
2346
        matches!(self, TrieNodeType::Node256(_))
54,770,711✔
2347
    }
54,770,711✔
2348

2349
    pub fn id(&self) -> u8 {
10,277,565✔
2350
        with_node!(self, ref data, data.id())
10,277,565✔
2351
    }
10,277,565✔
2352

2353
    pub fn walk(&self, chr: u8) -> Option<TriePtr> {
1,414,588,315✔
2354
        with_node!(self, ref data, data.walk(chr))
1,414,588,315✔
2355
    }
1,414,588,315✔
2356

2357
    pub fn write_bytes<W: Write>(&self, w: &mut W) -> Result<(), Error> {
5,843,734✔
2358
        with_node!(self, ref data, data.write_bytes(w))
5,843,734✔
2359
    }
5,843,734✔
2360

2361
    pub fn write_bytes_compressed<W: Write>(&self, w: &mut W) -> Result<(), Error> {
16,880,153✔
2362
        with_node!(self, ref data, data.write_bytes_compressed(w))
16,880,153✔
2363
    }
16,880,153✔
2364

2365
    pub fn write_consensus_bytes<W: Write, M: BlockMap>(
10,336,060✔
2366
        &self,
10,336,060✔
2367
        map: &mut M,
10,336,060✔
2368
        w: &mut W,
10,336,060✔
2369
    ) -> Result<(), Error> {
10,336,060✔
2370
        with_node!(self, ref data, data.write_consensus_bytes(map, w))
10,336,060✔
2371
    }
10,336,060✔
2372

2373
    pub fn byte_len(&self) -> usize {
29,913,391✔
2374
        with_node!(self, ref data, data.byte_len())
29,913,391✔
2375
    }
29,913,391✔
2376

2377
    pub fn byte_len_compressed(&self) -> usize {
197,048✔
2378
        with_node!(self, ref data, data.byte_len_compressed())
197,048✔
2379
    }
197,048✔
2380

2381
    pub fn insert(&mut self, ptr: &TriePtr) -> bool {
7,734,993✔
2382
        with_node!(self, ref mut data, data.insert(ptr))
7,734,993✔
2383
    }
7,734,993✔
2384

2385
    pub fn replace(&mut self, ptr: &TriePtr) -> bool {
52,233,114✔
2386
        with_node!(self, ref mut data, data.replace(ptr))
52,233,114✔
2387
    }
52,233,114✔
2388

2389
    pub fn ptrs(&self) -> &[TriePtr] {
68,340,687✔
2390
        with_node!(self, ref data, data.ptrs())
68,340,687✔
2391
    }
68,340,687✔
2392

2393
    pub fn ptrs_mut(&mut self) -> &mut [TriePtr] {
12,470,689✔
2394
        match self {
12,470,689✔
2395
            TrieNodeType::Node4(ref mut data) => &mut data.ptrs,
8,347,883✔
2396
            TrieNodeType::Node16(ref mut data) => &mut data.ptrs,
1,409,410✔
2397
            TrieNodeType::Node48(ref mut data) => &mut data.ptrs,
650,336✔
2398
            TrieNodeType::Node256(ref mut data) => &mut data.ptrs,
2,063,060✔
2399
            TrieNodeType::Leaf(_) => panic!("Leaf has no ptrs"),
×
2400
        }
2401
    }
12,470,689✔
2402

2403
    pub fn max_ptrs(&self) -> usize {
×
2404
        match self {
×
2405
            TrieNodeType::Node4(_) => 4,
×
2406
            TrieNodeType::Node16(_) => 16,
×
2407
            TrieNodeType::Node48(_) => 48,
×
2408
            TrieNodeType::Node256(_) => 256,
×
2409
            TrieNodeType::Leaf(_) => 0,
×
2410
        }
2411
    }
×
2412

2413
    pub fn path_bytes(&self) -> &Vec<u8> {
1,554,887,730✔
2414
        with_node!(self, ref data, &data.path)
1,554,887,730✔
2415
    }
1,554,887,730✔
2416

2417
    pub fn set_path(&mut self, new_path: Vec<u8>) {
3,094✔
2418
        with_node!(self, ref mut data, data.path = new_path)
3,094✔
2419
    }
3,094✔
2420

2421
    pub fn get_cow_ptr(&self) -> Option<&TrieCowPtr> {
3,661,014✔
2422
        with_node!(self, ref data, data.get_cow_ptr())
3,661,014✔
2423
    }
3,661,014✔
2424

2425
    pub fn set_cow_ptr(&mut self, cowptr: TrieCowPtr) {
17,151,642✔
2426
        with_node!(self, ref mut data, data.set_cow_ptr(cowptr))
17,151,642✔
2427
    }
17,151,642✔
2428

2429
    pub fn apply_patches(
1,140,631,017✔
2430
        self,
1,140,631,017✔
2431
        patches: &[(u32, TriePtr, TrieNodePatch)],
1,140,631,017✔
2432
        cur_block_id: u32,
1,140,631,017✔
2433
    ) -> Option<Self> {
1,140,631,017✔
2434
        match self {
1,140,631,017✔
2435
            TrieNodeType::Node4(data) => {
253,853,876✔
2436
                let Some(new_data) = data.apply_patches(patches, cur_block_id) else {
253,853,876✔
2437
                    return None;
×
2438
                };
2439
                Some(TrieNodeType::Node4(new_data))
253,853,876✔
2440
            }
2441
            TrieNodeType::Node16(data) => {
42,565,934✔
2442
                let Some(new_data) = data.apply_patches(patches, cur_block_id) else {
42,565,934✔
2443
                    return None;
×
2444
                };
2445
                Some(TrieNodeType::Node16(new_data))
42,565,934✔
2446
            }
2447
            TrieNodeType::Node48(data) => {
15,122,637✔
2448
                let Some(new_data) = data.apply_patches(patches, cur_block_id) else {
15,122,637✔
2449
                    return None;
×
2450
                };
2451
                Some(TrieNodeType::Node48(Box::new(new_data)))
15,122,637✔
2452
            }
2453
            TrieNodeType::Node256(data) => {
360,610,897✔
2454
                let Some(new_data) = data.apply_patches(patches, cur_block_id) else {
360,610,897✔
2455
                    return None;
×
2456
                };
2457
                Some(TrieNodeType::Node256(Box::new(new_data)))
360,610,897✔
2458
            }
2459
            TrieNodeType::Leaf(data) => Some(TrieNodeType::Leaf(data)),
468,477,673✔
2460
        }
2461
    }
1,140,631,017✔
2462

2463
    pub fn get_patches(&self) -> &[(u32, TriePtr, TrieNodePatch)] {
14,214,464✔
2464
        match self {
14,214,464✔
2465
            TrieNodeType::Node4(ref data) => &data.patches,
10,540,380✔
2466
            TrieNodeType::Node16(ref data) => &data.patches,
1,472,779✔
2467
            TrieNodeType::Node48(ref data) => &data.patches,
260,924✔
2468
            TrieNodeType::Node256(ref data) => &data.patches,
1,940,381✔
2469
            TrieNodeType::Leaf(_) => panic!("Leaf has no patches"),
×
2470
        }
2471
    }
14,214,464✔
2472
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc