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}