1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! CKP-006: [`dig_block::CheckpointBuilder`] — incremental epoch summary construction per NORMATIVE.
//!
//! **Normative:** `docs/requirements/domains/checkpoint/NORMATIVE.md` (CKP-006)
//! **Spec + test plan:** `docs/requirements/domains/checkpoint/specs/CKP-006.md`
//! **Implementation:** `src/builder/checkpoint_builder.rs`
//!
//! ## How these tests prove CKP-006
//!
//! - **Accumulation:** `add_block` / `add_withdrawal` drive `block_count`, `withdrawal_count`, `tx_count`, and
//! `total_fees` in the built [`Checkpoint`] ([CKP-006 acceptance](docs/requirements/domains/checkpoint/specs/CKP-006.md#acceptance-criteria)).
//! - **Merkle roots:** `block_root` and `withdrawals_root` must match [`chia_sdk_types::MerkleTree`] over the same
//! leaf order as BLK-004 spends/slash roots ([`merkle_tree_root`](docs/requirements/domains/block_types/specs/BLK-004.md) —
//! [HSH-007](docs/requirements/domains/hashing/specs/HSH-007.md) tagging via `MerkleTree`). Empty lists → [`EMPTY_ROOT`]
//! ([BLK-005](docs/requirements/domains/block_types/specs/BLK-005.md)).
//! - **Linkage:** `epoch` and `prev_checkpoint` propagate from [`CheckpointBuilder::new`]; [`CheckpointBuilder::set_state_root`]
//! overwrites the builder’s state commitment before [`CheckpointBuilder::build`].
use chia_sdk_types::MerkleTree;
use dig_block::{Bytes32, CheckpointBuilder, EMPTY_ROOT};
fn byte_tag(b: u8) -> Bytes32 {
Bytes32::new([b; 32])
}
/// **Test plan:** “Build empty checkpoint” — no blocks/withdrawals, roots sentinel ([CKP-006 § Test Plan](docs/requirements/domains/checkpoint/specs/CKP-006.md#test-plan)).
#[test]
fn ckp006_build_empty_checkpoint() {
let prev = byte_tag(0x01);
let cp = CheckpointBuilder::new(9, prev).build();
assert_eq!(cp.epoch, 9);
assert_eq!(cp.prev_checkpoint, prev);
assert_eq!(cp.block_count, 0);
assert_eq!(cp.withdrawal_count, 0);
assert_eq!(cp.block_root, EMPTY_ROOT);
assert_eq!(cp.withdrawals_root, EMPTY_ROOT);
assert_eq!(cp.tx_count, 0);
assert_eq!(cp.total_fees, 0);
assert_eq!(cp.state_root, Bytes32::default());
}
/// **Test plan:** “Build with single block”.
#[test]
fn ckp006_build_single_block() {
let h = byte_tag(0xab);
let mut b = CheckpointBuilder::new(1, byte_tag(0));
b.add_block(h, 3, 100);
let cp = b.build();
assert_eq!(cp.block_count, 1);
assert_eq!(cp.tx_count, 3);
assert_eq!(cp.total_fees, 100);
assert_eq!(cp.block_root, MerkleTree::new(&[h]).root());
}
/// **Test plan:** “Build with multiple blocks” + “Fee aggregation” + “Tx count aggregation”.
#[test]
fn ckp006_build_three_blocks_aggregates_tx_and_fees() {
let h1 = byte_tag(0x10);
let h2 = byte_tag(0x20);
let h3 = byte_tag(0x30);
let mut b = CheckpointBuilder::new(2, byte_tag(0xff));
b.add_block(h1, 5, 10);
b.add_block(h2, 10, 20);
b.add_block(h3, 15, 30);
let cp = b.build();
assert_eq!(cp.block_count, 3);
assert_eq!(cp.tx_count, 30);
assert_eq!(cp.total_fees, 60);
let expected_root = MerkleTree::new(&[h1, h2, h3]).root();
assert_eq!(cp.block_root, expected_root);
}
/// **Test plan:** “Block root Merkle computation” (two leaves).
#[test]
fn ckp006_block_root_matches_merkle_tree() {
let a = byte_tag(0x01);
let c = byte_tag(0x02);
let mut b = CheckpointBuilder::new(0, EMPTY_ROOT);
b.add_block(a, 0, 0);
b.add_block(c, 0, 0);
let cp = b.build();
assert_eq!(cp.block_root, MerkleTree::new(&[a, c]).root());
}
/// **Test plan:** “Withdrawals root Merkle computation” + empty blocks list still allowed.
#[test]
fn ckp006_withdrawals_root_matches_merkle_tree() {
let w1 = byte_tag(0xaa);
let w2 = byte_tag(0xbb);
let mut b = CheckpointBuilder::new(0, EMPTY_ROOT);
b.add_withdrawal(w1);
b.add_withdrawal(w2);
let cp = b.build();
assert_eq!(cp.withdrawal_count, 2);
assert_eq!(cp.withdrawals_root, MerkleTree::new(&[w1, w2]).root());
assert_eq!(cp.block_root, EMPTY_ROOT);
}
/// **Test plan:** “Set state root”.
#[test]
fn ckp006_set_state_root_persists() {
let sr = byte_tag(0x77);
let mut b = CheckpointBuilder::new(3, byte_tag(0x11));
b.set_state_root(sr);
let cp = b.build();
assert_eq!(cp.state_root, sr);
}
/// **Test plan:** “Prev checkpoint linkage” + “Epoch propagation” (covered in empty test; explicit here).
#[test]
fn ckp006_epoch_and_prev_checkpoint_from_new() {
let epoch = 123_456u64;
let prev = byte_tag(0xcd);
let cp = CheckpointBuilder::new(epoch, prev).build();
assert_eq!(cp.epoch, epoch);
assert_eq!(cp.prev_checkpoint, prev);
}