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}