dig_slashing/evidence/invalid_block.rs
1//! `InvalidBlockProof` — per-offense payload for `OffenseType::InvalidBlock`.
2//!
3//! Traces to: [SPEC.md §3.4](../../docs/resources/SPEC.md), catalogue row
4//! [DSL-008](../../docs/requirements/domains/evidence/specs/DSL-008.md).
5//!
6//! # Role
7//!
8//! Carries everything needed to prove a proposer signed a block that
9//! fails canonical validation:
10//!
11//! - [`SignedBlockHeader`](super::proposer_slashing::SignedBlockHeader)
12//! — the block header + BLS signature (DSL-009).
13//! - `failure_witness` — caller-supplied bytes that the
14//! [`InvalidBlockOracle`](../../traits.rs) (DSL-020) replays to
15//! reproduce the validation failure. Size depends on the failure
16//! reason; `serde_bytes` keeps the binary-format encoding compact.
17//! - [`InvalidBlockReason`] — categorical tag. Eight variants covering
18//! the distinct canonical-validation failure modes.
19//!
20//! # Downstream
21//!
22//! - `verify_invalid_block` (DSL-018..020) consumes all three fields.
23//! - Appeal ground `InvalidBlockAppealGround::FailureReasonMismatch`
24//! (DSL-051) checks oracle-reported reason against the claimed
25//! `failure_reason`.
26
27use serde::{Deserialize, Serialize};
28
29use crate::evidence::proposer_slashing::SignedBlockHeader;
30
31/// Canonical reason categories for `InvalidBlockProof::failure_reason`.
32///
33/// Per [SPEC §3.4](../../docs/resources/SPEC.md). Adding a new variant
34/// is a protocol-version bump — downstream pattern-matches assume
35/// exhaustive coverage of exactly these eight cases (see
36/// `test_dsl_008_all_reasons_enumerated`).
37///
38/// The enum is `Copy` + `Hash` because it's a lightweight discriminant
39/// that shows up in `AppealAdjudicationResult`, oracle return values,
40/// and metrics labels — passing by value is cheap and avoids `.clone()`
41/// noise in consumers.
42#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
43pub enum InvalidBlockReason {
44 /// Post-block state root doesn't match the re-executed state.
45 BadStateRoot,
46 /// Header's parent hash doesn't match the canonical parent.
47 BadParentRoot,
48 /// Timestamp outside allowed window (future or past canonical tip).
49 BadTimestamp,
50 /// `proposer_index` doesn't match the slot's assigned proposer.
51 BadProposerIndex,
52 /// One or more transactions failed during block execution.
53 TransactionExecutionFailure,
54 /// Block exceeds `MAX_BLOCK_COST` or analogous resource cap.
55 OverweightBlock,
56 /// Block contains the same spend bundle twice.
57 DuplicateTransaction,
58 /// Reason not otherwise categorised; witness should carry detail.
59 Other,
60}
61
62/// Evidence that a proposer signed an invalid block.
63///
64/// Per [SPEC §3.4](../../docs/resources/SPEC.md). Passive wire carrier:
65/// no validation happens at construction, only at
66/// `verify_invalid_block` (DSL-018..020).
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub struct InvalidBlockProof {
69 /// The offending signed block header.
70 pub signed_header: SignedBlockHeader,
71 /// Caller-supplied replay material for the `InvalidBlockOracle`.
72 /// Shape depends on `failure_reason`; the verifier is responsible
73 /// for interpreting the bytes against the oracle.
74 #[serde(with = "serde_bytes")]
75 pub failure_witness: Vec<u8>,
76 /// Categorical classification of the failure.
77 pub failure_reason: InvalidBlockReason,
78}