Skip to main content

dig_block/types/
attested.rs

1//! [`AttestedBlock`] — L2 block plus attestation metadata (signers, aggregate sig, receipts, status).
2//!
3//! ## Requirements trace
4//!
5//! - **[ATT-001](docs/requirements/domains/attestation/specs/ATT-001.md)** — struct fields + [`AttestedBlock::new`].
6//! - **[ATT-002](docs/requirements/domains/attestation/specs/ATT-002.md)** — [`AttestedBlock::signing_percentage`],
7//!   [`AttestedBlock::has_soft_finality`], [`AttestedBlock::hash`] (delegate to [`SignerBitmap`] / [`L2Block::hash`]).
8//! - **[SER-002](docs/requirements/domains/serialization/specs/SER-002.md)** — [`AttestedBlock::to_bytes`] / [`AttestedBlock::from_bytes`] (bincode; decode errors → [`BlockError::InvalidData`](crate::BlockError::InvalidData)).
9//! - **[NORMATIVE § ATT-001 / ATT-002](docs/requirements/domains/attestation/NORMATIVE.md)** — constructor + query API.
10//! - **[SPEC §2.4](docs/resources/SPEC.md)** — wire / semantic context for attested payloads.
11//!
12//! ## Usage
13//!
14//! Wrap a finalized [`crate::L2Block`] once execution produces [`crate::ReceiptList`] entries; [`AttestedBlock::new`]
15//! seeds the `aggregate_signature` field with the **proposer** signature (`L2Block::proposer_signature`) before
16//! validators aggregate their BLS shares (implementation notes in ATT-001). The `signer_bitmap` field starts
17//! empty; consensus layers record attestations via [`SignerBitmap::set_signed`](crate::SignerBitmap::set_signed)
18//! / [`SignerBitmap::merge`](crate::SignerBitmap::merge) (ATT-004 / ATT-005). Use [`AttestedBlock::signing_percentage`]
19//! and [`AttestedBlock::has_soft_finality`] for quorum checks; [`AttestedBlock::hash`] is the same [`Bytes32`] as
20//! [`L2Block::hash`] (ATT-002).
21//!
22//! ## Rationale
23//!
24//! - **Initial aggregate = proposer:** Matches ATT-001 / NORMATIVE — there is no separate “epoch genesis”
25//!   signature slot; the proposer’s block signature bootstraps the aggregate until attesters merge in.
26//! - **Status `Pending`:** Constructor does not imply validation; structural / execution tiers advance status
27//!   outside this crate (see [`BlockStatus`](crate::BlockStatus)).
28
29use serde::{Deserialize, Serialize};
30
31use super::block::L2Block;
32use super::receipt::ReceiptList;
33use super::signer_bitmap::SignerBitmap;
34use super::status::BlockStatus;
35use crate::error::BlockError;
36use crate::primitives::{Bytes32, Signature};
37
38/// L2 block wrapped with attestation state: who signed, aggregate BLS signature, receipts, lifecycle status.
39///
40/// **Fields (NORMATIVE ATT-001):** See [ATT-001](docs/requirements/domains/attestation/specs/ATT-001.md) table.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct AttestedBlock {
43    /// Underlying L2 block (header hash is canonical block id via [`L2Block::hash`]).
44    pub block: L2Block,
45    /// Which validators have attested (bit-packed, sized by epoch validator set).
46    pub signer_bitmap: SignerBitmap,
47    /// Rolling aggregate BLS signature over attester contributions; starts as the proposer signature.
48    pub aggregate_signature: Signature,
49    /// Execution receipts for spends in this block ([`ReceiptList`]; fleshed out in RCP-003/004).
50    pub receipts: ReceiptList,
51    /// Attestation / finality pipeline state ([`BlockStatus`]).
52    pub status: BlockStatus,
53}
54
55impl AttestedBlock {
56    /// Build an attested wrapper: empty bitmap, [`BlockStatus::Pending`], aggregate sig = proposer’s signature.
57    ///
58    /// **Invariants (ATT-001):**
59    /// - [`Self::signer_bitmap`] is [`SignerBitmap::new`](crate::SignerBitmap::new)(`validator_count`) → zero signers.
60    /// - [`Self::status`] is [`BlockStatus::Pending`].
61    /// - [`Self::aggregate_signature`] is a **clone** of [`L2Block::proposer_signature`].
62    /// - [`Self::block`] and [`Self::receipts`] move in from the caller.
63    ///
64    /// **Panics:** If `validator_count > MAX_VALIDATORS` — same cap as [`crate::SignerBitmap::new`].
65    pub fn new(block: L2Block, validator_count: u32, receipts: ReceiptList) -> Self {
66        let aggregate_signature = block.proposer_signature.clone();
67        Self {
68            signer_bitmap: SignerBitmap::new(validator_count),
69            aggregate_signature,
70            status: BlockStatus::Pending,
71            block,
72            receipts,
73        }
74    }
75
76    /// Integer signing progress `0..=100` — thin wrapper over [`SignerBitmap::signing_percentage`].
77    ///
78    /// **ATT-002:** Keeps quorum logic on one type (`SignerBitmap` math in ATT-004) while exposing a stable
79    /// [`AttestedBlock`] surface for consensus code ([NORMATIVE § ATT-002](docs/requirements/domains/attestation/NORMATIVE.md)).
80    #[inline]
81    #[must_use]
82    pub fn signing_percentage(&self) -> u64 {
83        self.signer_bitmap.signing_percentage()
84    }
85
86    /// `true` iff [`Self::signing_percentage`] `>= threshold_pct` (soft-finality / stake-threshold gate).
87    ///
88    /// **ATT-002:** Delegates to [`SignerBitmap::has_threshold`] so boundary behavior matches ATT-004 tests
89    /// (integer division, `validator_count == 0` → `0%`).
90    #[inline]
91    #[must_use]
92    pub fn has_soft_finality(&self, threshold_pct: u64) -> bool {
93        self.signer_bitmap.has_threshold(threshold_pct)
94    }
95
96    /// Block identity: SHA-256 header preimage hash ([`L2Block::hash`], HSH-001 / SPEC §2.3).
97    ///
98    /// **ATT-002 / NORMATIVE:** MUST equal `self.block.hash()` so attested and raw blocks share one id in indices,
99    /// checkpoints, and P2P — no second hash domain for the attestation wrapper.
100    #[inline]
101    #[must_use]
102    pub fn hash(&self) -> Bytes32 {
103        self.block.hash()
104    }
105
106    /// Serialize attested block (inner [`L2Block`] + bitmap + signatures + receipts + status) to **bincode** ([SER-002](docs/requirements/domains/serialization/specs/SER-002.md)).
107    ///
108    /// **Infallible:** Mirrors [`L2Block::to_bytes`] — struct must encode; panics only on invariant violation.
109    #[must_use]
110    pub fn to_bytes(&self) -> Vec<u8> {
111        bincode::serialize(self).expect("AttestedBlock serialization should never fail")
112    }
113
114    /// Deserialize from **bincode** bytes ([SER-002](docs/requirements/domains/serialization/specs/SER-002.md)).
115    pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlockError> {
116        bincode::deserialize(bytes).map_err(|e| BlockError::InvalidData(e.to_string()))
117    }
118}