Skip to main content

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}