dig_block/error.rs
1//! Error enums for dig-block.
2//!
3//! - **Tier 1 [`BlockError`]** (structural): [ERR-001](docs/requirements/domains/error_types/specs/ERR-001.md), [NORMATIVE § ERR-001](docs/requirements/domains/error_types/NORMATIVE.md).
4//! - **Tier 2 /3 [`BlockError`]** (execution / state): [ERR-002](docs/requirements/domains/error_types/specs/ERR-002.md), [NORMATIVE § ERR-002](docs/requirements/domains/error_types/NORMATIVE.md).
5//! - **[`CheckpointError`]** (checkpoints): [ERR-003](docs/requirements/domains/error_types/specs/ERR-003.md), [SPEC §4.2](docs/resources/SPEC.md).
6//! - **[`BuilderError`]** (block construction): [ERR-004](docs/requirements/domains/error_types/specs/ERR-004.md), [SPEC §6.5](docs/resources/SPEC.md).
7//! - **[`SignerBitmapError`] / [`ReceiptError`]**: [ERR-005](docs/requirements/domains/error_types/specs/ERR-005.md), [SPEC §4.3–4.4](docs/resources/SPEC.md).
8//! - **Crate spec:** [SPEC §4.1–4.2](docs/resources/SPEC.md) — error taxonomy and validator layering.
9
10use crate::primitives::Bytes32;
11use thiserror::Error;
12
13/// Block validation failures across three tiers on one enum ([ERR-001](docs/requirements/domains/error_types/specs/ERR-001.md), [ERR-002](docs/requirements/domains/error_types/specs/ERR-002.md)).
14///
15/// **Tier 1 — structural:** cheapest checks first on [`crate::L2BlockHeader`] / [`crate::L2Block`] (SVL-*); no CLVM, no [`crate::CoinLookup`].
16/// [`BlockError::InvalidData`] is also used for SVL-002 DFSP pre-activation root policy ([`crate::L2BlockHeader::validate_with_dfsp_activation`]).
17///
18/// **Tier 2 — execution:** CLVM / puzzle / signature / fee invariants for `validate_execution` (EXE-*;
19/// [execution_validation NORMATIVE](docs/requirements/domains/execution_validation/NORMATIVE.md)). Prefer mapping `dig-clvm` errors into these variants rather than [`BlockError::InvalidData`].
20///
21/// **Tier 3 — state:** coin set and proposer checks in `validate_state` (STV-*;
22/// [state_validation NORMATIVE](docs/requirements/domains/state_validation/NORMATIVE.md)).
23///
24/// **Derivation:** `Debug` + `Clone` + `thiserror::Error` — same rationale as ERR-001 ([acceptance criteria](docs/requirements/domains/error_types/specs/ERR-002.md#acceptance-criteria)).
25///
26/// **Semantic links:** Bincode decode failures from [`crate::L2BlockHeader::from_bytes`], [`crate::L2Block::from_bytes`],
27/// and [`crate::AttestedBlock::from_bytes`] map to [`BlockError::InvalidData`] ([SER-002](docs/requirements/domains/serialization/specs/SER-002.md); wire serde in [SER-001](docs/requirements/domains/serialization/specs/SER-001.md)).
28#[derive(Debug, Clone, Error)]
29pub enum BlockError {
30 // --- Tier 1: Structural validation (ERR-001) ---
31 /// Opaque structural failure (e.g. bincode parse, policy text); prefer specific variants when available.
32 #[error("invalid data: {0}")]
33 InvalidData(String),
34
35 /// Header `version` does not match the height / DFSP activation rule ([SVL-001](docs/requirements/domains/structural_validation/specs/SVL-001.md)).
36 #[error("invalid version: expected {expected}, got {actual}")]
37 InvalidVersion { expected: u16, actual: u16 },
38
39 /// Serialized block size exceeds [`crate::MAX_BLOCK_SIZE`](crate::constants) or configured limit.
40 #[error("block too large: {size} bytes exceeds max {max}")]
41 TooLarge { size: u32, max: u32 },
42
43 /// Sum of spend costs exceeds [`crate::MAX_COST_PER_BLOCK`](crate::constants) or header-declared budget.
44 #[error("cost exceeded: {cost} exceeds max {max}")]
45 CostExceeded { cost: u64, max: u64 },
46
47 /// `header.spend_bundle_count` does not match `spend_bundles.len()`.
48 #[error("spend bundle count mismatch: header={header}, actual={actual}")]
49 SpendBundleCountMismatch { header: u32, actual: u32 },
50
51 /// Merkle root of spends does not match `header.spends_root`.
52 #[error("invalid spends root: expected={expected}, computed={computed}")]
53 InvalidSpendsRoot {
54 expected: Bytes32,
55 computed: Bytes32,
56 },
57
58 /// Merkle root of receipts does not match `header.receipts_root`.
59 #[error("invalid receipts root: expected={expected}, computed={computed}")]
60 InvalidReceiptsRoot {
61 expected: Bytes32,
62 computed: Bytes32,
63 },
64
65 /// `header.prev_hash` chain link failure (wrong parent).
66 #[error("invalid parent: expected={expected}, got={got}")]
67 InvalidParent { expected: Bytes32, got: Bytes32 },
68
69 /// Slash proposals Merkle root does not match commitment in header.
70 #[error("invalid slash proposals root")]
71 InvalidSlashProposalsRoot,
72
73 /// Single slash proposal payload exceeds max serialized size.
74 #[error("slash proposal payload too large")]
75 SlashProposalPayloadTooLarge,
76
77 /// More slash proposals than protocol allows.
78 #[error("too many slash proposals")]
79 TooManySlashProposals,
80
81 /// Additions set root mismatch (see SVL-006).
82 #[error("invalid additions root")]
83 InvalidAdditionsRoot,
84
85 /// Removals set root mismatch.
86 #[error("invalid removals root")]
87 InvalidRemovalsRoot,
88
89 /// Gossip / filter hash in header does not match computed filter.
90 #[error("invalid filter hash")]
91 InvalidFilterHash,
92
93 /// Two outputs mint the same coin id within the block.
94 #[error("duplicate output: coin_id={coin_id}")]
95 DuplicateOutput { coin_id: Bytes32 },
96
97 /// Same coin id spent more than once in removals.
98 #[error("double spend in block: coin_id={coin_id}")]
99 DoubleSpendInBlock { coin_id: Bytes32 },
100
101 /// Header `additions_count` vs computed additions from the body ([SVL-005](docs/requirements/domains/structural_validation/specs/SVL-005.md)).
102 #[error("additions count mismatch: header={header}, actual={actual}")]
103 AdditionsCountMismatch { header: u32, actual: u32 },
104
105 /// Header `removals_count` vs total coin spends in the body ([SVL-005](docs/requirements/domains/structural_validation/specs/SVL-005.md)).
106 #[error("removals count mismatch: header={header}, actual={actual}")]
107 RemovalsCountMismatch { header: u32, actual: u32 },
108
109 /// Header `slash_proposal_count` vs `slash_proposal_payloads.len()` ([SVL-005](docs/requirements/domains/structural_validation/specs/SVL-005.md)).
110 #[error("slash proposal count mismatch: header={header}, actual={actual}")]
111 SlashProposalCountMismatch { header: u32, actual: u32 },
112
113 /// Block timestamp too far ahead of local wall clock ([SVL-004](docs/requirements/domains/structural_validation/specs/SVL-004.md)).
114 #[error("timestamp too far in future: {timestamp} exceeds max_allowed {max_allowed}")]
115 TimestampTooFarInFuture { timestamp: u64, max_allowed: u64 },
116
117 // --- Tier 2: Execution validation (ERR-002) ---
118 /// On-chain puzzle hash does not match hash of serialized puzzle revealed in the spend ([EXE NORMATIVE](docs/requirements/domains/execution_validation/NORMATIVE.md)).
119 #[error("puzzle hash mismatch for coin {coin_id}: expected={expected}, computed={computed}")]
120 PuzzleHashMismatch {
121 coin_id: Bytes32,
122 expected: Bytes32,
123 computed: Bytes32,
124 },
125
126 /// `dig-clvm` / CLVM runtime rejected the spend; `reason` preserves the engine diagnostic ([start.md](docs/prompt/start.md) — use dig-clvm, not raw `chia-consensus` entrypoints).
127 #[error("CLVM execution failed for coin {coin_id}: {reason}")]
128 ClvmExecutionFailed { coin_id: Bytes32, reason: String },
129
130 /// Single spend exceeded the remaining per-block CLVM budget after prior spends.
131 #[error("CLVM cost exceeded for coin {coin_id}: cost={cost}, remaining={remaining}")]
132 ClvmCostExceeded {
133 coin_id: Bytes32,
134 cost: u64,
135 remaining: u64,
136 },
137
138 /// ASSERT_* or concurrent-spend assertion failed; `condition` names the opcode class, `reason` is validator-local detail ([ERR-002 notes](docs/requirements/domains/error_types/specs/ERR-002.md#implementation-notes)).
139 #[error("assertion failed: condition={condition}, reason={reason}")]
140 AssertionFailed { condition: String, reason: String },
141
142 /// Spend expected an announcement that was not present in the ephemeral announcement set.
143 #[error("announcement not found: {announcement_hash}")]
144 AnnouncementNotFound { announcement_hash: Bytes32 },
145
146 /// Aggregate or AGG_SIG verification failed for the spend bundle at `bundle_index`.
147 #[error("signature verification failed for bundle index {bundle_index}")]
148 SignatureFailed { bundle_index: u32 },
149
150 /// Value conservation failure: outputs (`added`) exceed destroyed value (`removed`) without minting authority ([ERR-002 notes](docs/requirements/domains/error_types/specs/ERR-002.md#implementation-notes)).
151 #[error("coin minting: removed={removed}, added={added}")]
152 CoinMinting { removed: u64, added: u64 },
153
154 /// Header `total_fees` does not match summed fees from execution / receipts.
155 #[error("fees mismatch: header={header}, computed={computed}")]
156 FeesMismatch { header: u64, computed: u64 },
157
158 /// Reserve-fee condition not satisfied by available fees in the bundle.
159 #[error("reserve fee failed: required={required}, actual={actual}")]
160 ReserveFeeFailed { required: u64, actual: u64 },
161
162 /// Header `total_cost` does not match summed CLVM costs from spends.
163 #[error("cost mismatch: header={header}, computed={computed}")]
164 CostMismatch { header: u64, computed: u64 },
165
166 // --- Tier 3: State validation (ERR-002) ---
167 /// Proposer BLS signature over the block hash did not verify ([STV-006](docs/requirements/domains/state_validation/specs/STV-006.md)).
168 #[error("invalid proposer signature")]
169 InvalidProposerSignature,
170
171 /// Looked up a block by hash (e.g. parent) that is not in the local view ([ERR-002 notes](docs/requirements/domains/error_types/specs/ERR-002.md#implementation-notes)).
172 #[error("block not found: {0}")]
173 NotFound(Bytes32),
174
175 /// State transition Merkle root after applying removals/additions does not match header `state_root` ([STV-007](docs/requirements/domains/state_validation/specs/STV-007.md)).
176 #[error("invalid state root: expected={expected}, computed={computed}")]
177 InvalidStateRoot {
178 expected: Bytes32,
179 computed: Bytes32,
180 },
181
182 /// Removal references a coin id absent from [`crate::CoinLookup`] and not ephemeral in-block ([STV-002](docs/requirements/domains/state_validation/specs/STV-002.md)).
183 #[error("coin not found: {coin_id}")]
184 CoinNotFound { coin_id: Bytes32 },
185
186 /// Removal targets a coin already marked spent at `spent_height`.
187 #[error("coin already spent: {coin_id} at height {spent_height}")]
188 CoinAlreadySpent { coin_id: Bytes32, spent_height: u64 },
189
190 /// Addition would create a coin id that already exists in the live coin set ([STV NORMATIVE](docs/requirements/domains/state_validation/NORMATIVE.md)).
191 #[error("coin already exists: {coin_id}")]
192 CoinAlreadyExists { coin_id: Bytes32 },
193}
194
195/// Checkpoint lifecycle failures: submission, scoring, epoch alignment, and finalization ([ERR-003](docs/requirements/domains/error_types/specs/ERR-003.md)).
196///
197/// **Design:** Checkpoints aggregate L2 state for an epoch and bridge to L1; errors here are orthogonal to per-block [`BlockError`]
198/// ([ERR-003 implementation notes](docs/requirements/domains/error_types/specs/ERR-003.md#implementation-notes)).
199///
200/// **Usage:** Serialization failures should map to [`CheckpointError::InvalidData`] ([SER-002](docs/requirements/domains/serialization/specs/SER-002.md));
201/// validation layers return structured variants (`ScoreNotHigher`, `EpochMismatch`, etc.) per [checkpoint NORMATIVE](docs/requirements/domains/checkpoint/NORMATIVE.md).
202///
203/// **Derivation:** `Debug` + `Clone` + `thiserror::Error` — same ergonomics as [`BlockError`] ([SPEC §4.2](docs/resources/SPEC.md)).
204#[derive(Debug, Clone, Error)]
205pub enum CheckpointError {
206 /// Bincode or field-level parse failure; also used for generic “bad checkpoint bytes” ([SER NORMATIVE](docs/requirements/domains/serialization/NORMATIVE.md)).
207 #[error("invalid checkpoint data: {0}")]
208 InvalidData(String),
209
210 /// No checkpoint record for the given epoch (sync / indexer gap).
211 #[error("checkpoint not found for epoch {0}")]
212 NotFound(u64),
213
214 /// Checkpoint failed semantic validation (Merkle root, signature set, etc.).
215 #[error("invalid checkpoint: {0}")]
216 Invalid(String),
217
218 /// New submission does not beat the incumbent score ([ERR-003 notes](docs/requirements/domains/error_types/specs/ERR-003.md#implementation-notes)).
219 #[error("score not higher: current={current}, submitted={submitted}")]
220 ScoreNotHigher { current: u64, submitted: u64 },
221
222 /// Submission epoch does not match the expected collecting epoch.
223 #[error("epoch mismatch: expected={expected}, got={got}")]
224 EpochMismatch { expected: u64, got: u64 },
225
226 /// Checkpoint already committed; immutable ([ERR-003 notes](docs/requirements/domains/error_types/specs/ERR-003.md#implementation-notes)).
227 #[error("checkpoint already finalized")]
228 AlreadyFinalized,
229
230 /// Finalize or query called before the epoch checkpoint process began.
231 #[error("checkpoint process not started")]
232 NotStarted,
233}
234
235/// Block assembly failures while mutating [`crate::BlockBuilder`] (budgets, slash payloads, signing, v2 DFSP preconditions).
236///
237/// **Normative:** [ERR-004](docs/requirements/domains/error_types/specs/ERR-004.md), [SPEC §6.5](docs/resources/SPEC.md).
238///
239/// **Rationale:** These errors are raised *during construction* — before structural [`BlockError`] Tier 1 validation — so producers get
240/// immediate feedback on limits defined in [BLK-005 `constants`](crate::constants) (`MAX_COST_PER_BLOCK`, `MAX_BLOCK_SIZE`, slash caps).
241///
242/// **Usage:** [`BlockBuilder::add_spend_bundle`](docs/resources/SPEC.md) maps overruns to [`BuilderError::CostBudgetExceeded`] /
243/// [`BuilderError::SizeBudgetExceeded`]; slash path uses [`BuilderError::TooManySlashProposals`] / [`BuilderError::SlashProposalTooLarge`];
244/// [`crate::BlockSigner`] failures map to [`BuilderError::SigningFailed`] ([block_production NORMATIVE](docs/requirements/domains/block_production/NORMATIVE.md)).
245///
246/// **Derivation:** `Debug` + `Clone` + `thiserror::Error` per ERR-004 acceptance criteria (Display + [`std::error::Error`] for `?`).
247#[derive(Debug, Clone, Error)]
248pub enum BuilderError {
249 /// Cumulative CLVM cost would exceed the per-block budget after adding the candidate spend.
250 #[error("cost budget exceeded: current={current}, addition={addition}, max={max}")]
251 CostBudgetExceeded {
252 current: u64,
253 addition: u64,
254 max: u64,
255 },
256
257 /// Serialized block size would exceed [`crate::MAX_BLOCK_SIZE`](crate::constants) after adding the candidate body bytes.
258 #[error("size budget exceeded: current={current}, addition={addition}, max={max}")]
259 SizeBudgetExceeded {
260 current: u32,
261 addition: u32,
262 max: u32,
263 },
264
265 /// Slash proposal count would exceed [`crate::MAX_SLASH_PROPOSALS_PER_BLOCK`](crate::constants).
266 #[error("too many slash proposals: max={max}")]
267 TooManySlashProposals { max: u32 },
268
269 /// One slash payload exceeds [`crate::MAX_SLASH_PROPOSAL_PAYLOAD_BYTES`](crate::constants).
270 #[error("slash proposal too large: size={size}, max={max}")]
271 SlashProposalTooLarge { size: u32, max: u32 },
272
273 /// [`crate::BlockSigner`] rejected the block hash or could not produce a signature ([BLD-006](docs/requirements/domains/block_production/specs/BLD-006.md)).
274 #[error("signing failed: {0}")]
275 SigningFailed(String),
276
277 /// Finalize called with no spend bundles — blocks must carry at least one user transaction bundle ([ERR-004 notes](docs/requirements/domains/error_types/specs/ERR-004.md#implementation-notes)).
278 #[error("empty block: no spend bundles added")]
279 EmptyBlock,
280
281 /// Header `version >= VERSION_V2` but DFSP root fields were not supplied on the builder ([ERR-004 notes](docs/requirements/domains/error_types/specs/ERR-004.md#implementation-notes)).
282 #[error("missing DFSP roots")]
283 MissingDfspRoots,
284}
285
286/// Signer bitmap subsystem failures: index bounds, wire length, and validator-set cardinality ([ERR-005](docs/requirements/domains/error_types/specs/ERR-005.md)).
287///
288/// **Usage:** [`crate::SignerBitmap::set_signed`] returns [`SignerBitmapError::IndexOutOfBounds`] when `index >= validator_count`;
289/// [`crate::SignerBitmap::merge`] returns [`SignerBitmapError::ValidatorCountMismatch`] when operands were sized for different sets
290/// ([ATT-004](docs/requirements/domains/attestation/specs/ATT-004.md), [ATT-005](docs/requirements/domains/attestation/specs/ATT-005.md)).
291///
292/// **Rationale:** `TooManyValidators` and `InvalidLength` support deserialization / policy checks where the byte vector or declared
293/// count disagrees with protocol limits ([ERR-005 implementation notes](docs/requirements/domains/error_types/specs/ERR-005.md#implementation-notes));
294/// production paths may grow into these without changing the public enum shape again.
295///
296/// **Derivation:** `Debug` + `Clone` + `thiserror::Error`; `PartialEq` + `Eq` retained so ATT-004/ATT-005 integration tests can
297/// compare structured errors without string parsing.
298#[derive(Debug, Clone, Error, PartialEq, Eq)]
299pub enum SignerBitmapError {
300 /// `index` is not a valid signer slot; `max` is the bitmap's [`crate::SignerBitmap::validator_count`] (valid indices: `0..max`).
301 #[error("index out of bounds: index={index}, max={max}")]
302 IndexOutOfBounds { index: u32, max: u32 },
303
304 /// Validator set size exceeds what the protocol or deployment allows (see [`crate::MAX_VALIDATORS`](crate::MAX_VALIDATORS)).
305 #[error("too many validators: {0}")]
306 TooManyValidators(usize),
307
308 /// Byte length of a serialized bitmap does not match `ceil(validator_count / 8)` (or caller’s expected width).
309 #[error("invalid bitmap length: expected={expected}, got={got}")]
310 InvalidLength { expected: usize, got: usize },
311
312 /// Two bitmap operands do not share the same [`crate::SignerBitmap::validator_count`] ([`crate::SignerBitmap::merge`]).
313 #[error("validator count mismatch: expected={expected}, got={got}")]
314 ValidatorCountMismatch { expected: u32, got: u32 },
315}
316
317/// Receipt list / indexer failures for execution receipts ([ERR-005](docs/requirements/domains/error_types/specs/ERR-005.md), [SPEC §4.4](docs/resources/SPEC.md)).
318///
319/// **Usage:** Serialization or field validation maps to [`ReceiptError::InvalidData`]; lookup by receipt or tx id uses [`ReceiptError::NotFound`]
320/// when the key is absent ([RCP domain](docs/requirements/domains/receipt/NORMATIVE.md) — future RCP helpers).
321///
322/// **Derivation:** `Debug` + `Clone` + `thiserror::Error`; `PartialEq` + `Eq` for testability ([`Bytes32`] is compared by value).
323#[derive(Debug, Clone, Error, PartialEq, Eq)]
324pub enum ReceiptError {
325 /// Opaque parse or semantic failure (e.g. bincode, bad status byte).
326 #[error("invalid receipt data: {0}")]
327 InvalidData(String),
328
329 /// No receipt recorded for the given id / hash.
330 #[error("receipt not found: {0}")]
331 NotFound(Bytes32),
332}