Skip to main content

dig_block/builder/
checkpoint_builder.rs

1//! Incremental construction of [`crate::Checkpoint`] for an L2 epoch ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
2//!
3//! ## Requirements trace
4//!
5//! - **[CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)** — [`CheckpointBuilder`]: `new`, `add_block`,
6//!   `set_state_root`, `add_withdrawal`, `build`.
7//! - **[NORMATIVE § CKP-006](docs/requirements/domains/checkpoint/NORMATIVE.md)** — builder obligations for epoch summaries.
8//! - **[SPEC §6.6](docs/resources/SPEC.md)** — checkpoint builder in block production.
9//! - **Merkle roots:** [`CheckpointBuilder::build`] uses the crate-internal `merkle_tree_root` helper — same tagged binary Merkle tree as
10//!   [`crate::L2Block::compute_spends_root`](crate::L2Block::compute_spends_root) /
11//!   [`crate::L2Block::compute_slash_proposals_root`](crate::L2Block::compute_slash_proposals_root)
12//!   ([BLK-004](docs/requirements/domains/block_types/specs/BLK-004.md), [HSH-007](docs/requirements/domains/hashing/specs/HSH-007.md)
13//!   via `chia_sdk_types::MerkleTree`). **Empty** leaf lists → [`crate::EMPTY_ROOT`]
14//!   ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md) implementation notes).
15//! - **[HSH-002](docs/requirements/domains/hashing/specs/HSH-002.md)** — [`crate::Checkpoint::hash`] applies once the
16//!   built [`Checkpoint`] is complete.
17//!
18//! ## Rationale
19//!
20//! - **Consuming `build`:** [`CheckpointBuilder::build`] takes `self` by value so a finalized [`crate::Checkpoint`] cannot be
21//!   accidentally extended ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md) implementation notes).
22//! - **Ordered leaves:** Block hashes and withdrawal hashes are Merkle leaves in **append order** — matches
23//!   [`CheckpointBuilder::add_block`] / [`CheckpointBuilder::add_withdrawal`] call order during the epoch.
24//! - **State root:** Defaults to [`Bytes32::default`] until [`CheckpointBuilder::set_state_root`] runs; production code should set the
25//!   post-epoch trie root before signing.
26
27use crate::merkle_util::merkle_tree_root;
28use crate::primitives::Bytes32;
29use crate::types::checkpoint::Checkpoint;
30
31/// Accumulates per-epoch block hashes, fees, tx counts, and withdrawal commitments.
32///
33/// [`CheckpointBuilder::build`] materializes a [`Checkpoint`] with Merkle `block_root` / `withdrawals_root` ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
34pub struct CheckpointBuilder {
35    epoch: u64,
36    prev_checkpoint: Bytes32,
37    state_root: Bytes32,
38    block_hashes: Vec<Bytes32>,
39    tx_count: u64,
40    total_fees: u64,
41    withdrawal_hashes: Vec<Bytes32>,
42}
43
44impl CheckpointBuilder {
45    /// Start an epoch summary builder: link to `prev_checkpoint`, initialize empty accumulators ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
46    #[must_use]
47    pub fn new(epoch: u64, prev_checkpoint: Bytes32) -> Self {
48        Self {
49            epoch,
50            prev_checkpoint,
51            state_root: Bytes32::default(),
52            block_hashes: Vec::new(),
53            tx_count: 0,
54            total_fees: 0,
55            withdrawal_hashes: Vec::new(),
56        }
57    }
58
59    /// Append one L2 block’s identity hash and roll up its `tx_count` and `fees` ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
60    pub fn add_block(&mut self, block_hash: Bytes32, tx_count: u64, fees: u64) {
61        self.block_hashes.push(block_hash);
62        self.tx_count += tx_count;
63        self.total_fees += fees;
64    }
65
66    /// Set the finalized L2 state root for this epoch ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
67    pub fn set_state_root(&mut self, state_root: Bytes32) {
68        self.state_root = state_root;
69    }
70
71    /// Append one withdrawal record hash (leaf material for `withdrawals_root`) ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
72    pub fn add_withdrawal(&mut self, withdrawal_hash: Bytes32) {
73        self.withdrawal_hashes.push(withdrawal_hash);
74    }
75
76    /// Finalize: compute Merkle roots over accumulated hashes and return an owned [`Checkpoint`] ([CKP-006](docs/requirements/domains/checkpoint/specs/CKP-006.md)).
77    ///
78    /// **`block_count` / `withdrawal_count`:** Derived from vector lengths, cast to `u32` per [`Checkpoint`] layout (CKP-001).
79    /// Protocol epochs must stay within `u32` item counts; if lengths exceed `u32::MAX`, the cast truncates — callers must bound work.
80    #[must_use]
81    pub fn build(self) -> Checkpoint {
82        let block_root = merkle_tree_root(&self.block_hashes);
83        let withdrawals_root = merkle_tree_root(&self.withdrawal_hashes);
84        Checkpoint {
85            epoch: self.epoch,
86            state_root: self.state_root,
87            block_root,
88            block_count: self.block_hashes.len() as u32,
89            tx_count: self.tx_count,
90            total_fees: self.total_fees,
91            prev_checkpoint: self.prev_checkpoint,
92            withdrawals_root,
93            withdrawal_count: self.withdrawal_hashes.len() as u32,
94        }
95    }
96}