dig_block/traits.rs
1//! External integration traits: [`CoinLookup`] and [`BlockSigner`].
2//!
3//! ## Requirements trace
4//!
5//! - **[STR-004](docs/requirements/domains/crate_structure/specs/STR-004.md)** — trait definitions, method signatures, object safety.
6//! - **[NORMATIVE § STR-004](docs/requirements/domains/crate_structure/NORMATIVE.md)** — `CoinLookup` uses `chia-protocol::CoinState` directly;
7//! `BlockSigner` returns `chia-bls::Signature`.
8//! - **[SPEC §7.2](docs/resources/SPEC.md)** — validation context traits.
9//!
10//! ## Design decisions
11//!
12//! - **[`CoinLookup`] returns `Option<CoinState>`, not `Result`:** A missing coin is a normal validation
13//! condition (Tier 3 checks for ephemeral coins — STV-002), not an I/O error. Implementors that wrap a
14//! database should map DB errors to `None` or propagate them through a higher-level API.
15//!
16//! - **[`CoinState`] from `chia-protocol`:** Reuses the same type Chia's peer protocol returns for
17//! `register_for_coin_updates` responses ([`chia_protocol::CoinState`]). This means `dig-coinstore` can
18//! implement [`CoinLookup`] without type conversion.
19//! Reference: [`chia-blockchain/chia/protocols/wallet_protocol.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/protocols/wallet_protocol.py).
20//!
21//! - **[`BlockSigner`] is object-safe:** Both traits can be used as `dyn CoinLookup` / `dyn BlockSigner`
22//! so validation and builder code can be generic over the signing backend (HSM, in-memory key, remote signer).
23//!
24//! - **[`SignerError`]:** Kept minimal (one variant) because the signing failure reason is opaque to
25//! block validation — it only matters for logging. [`crate::BlockBuilder::build`](crate::BlockBuilder::build) maps
26//! failures to [`BuilderError::SigningFailed`](crate::BuilderError::SigningFailed) using [`SignerError::to_string`]
27//! (BLD-006 / [ERR-004](docs/requirements/domains/error_types/specs/ERR-004.md) — the `BuilderError` variant carries a
28//! `String`, not a nested `SignerError`, so integration tests assert diagnostic text rather than type equality).
29//!
30//! ## Downstream implementors
31//!
32//! ```text
33//! dig-coinstore ──► implements CoinLookup (returns chia-protocol::CoinState)
34//! proposer ──► implements BlockSigner (returns chia-bls::Signature)
35//! tests/common ──► MockCoinLookup, MockBlockSigner (STR-005)
36//! ```
37
38use chia_bls::Signature;
39use chia_protocol::{Bytes32, CoinState};
40
41/// Coin state lookup for Tier 3 (state) validation ([SPEC §7.2](docs/resources/SPEC.md), [STV-001](docs/requirements/domains/state_validation/specs/STV-001.md)).
42///
43/// Implementors provide access to the persistent coin set. The three methods supply the chain context
44/// needed for:
45/// - **Coin existence checks** (STV-002): `get_coin_state` returns the coin's lifecycle.
46/// - **Height/time lock evaluation** (STV-005): `get_chain_height` / `get_chain_timestamp` provide
47/// the reference clock for `ASSERT_HEIGHT_*` / `ASSERT_SECONDS_*` conditions.
48///
49/// ## Object safety
50///
51/// All methods take `&self` and return owned/copyable types — no `Self` in return position, no
52/// generic parameters. `Box<dyn CoinLookup>` and `&dyn CoinLookup` are valid.
53///
54/// ## Chia parity
55///
56/// Method signatures mirror [`chia-blockchain/chia/consensus/block_body_validation.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py)
57/// where `CoinStore.get_coin_record(coin_id)` returns `Optional[CoinRecord]` (Check 15).
58pub trait CoinLookup {
59 /// Look up a coin's current state by its ID.
60 ///
61 /// Returns `None` if the coin is unknown to this lookup source. Callers (STV-002)
62 /// then check the ephemeral set (`ExecutionResult.additions`) before rejecting.
63 fn get_coin_state(&self, coin_id: &Bytes32) -> Option<CoinState>;
64
65 /// Current chain tip height as observed by this lookup source.
66 ///
67 /// Used by STV-005 for `ASSERT_HEIGHT_ABSOLUTE` / `BEFORE_HEIGHT_ABSOLUTE` evaluation.
68 fn get_chain_height(&self) -> u64;
69
70 /// Current chain tip timestamp (Unix seconds) as observed by this lookup source.
71 ///
72 /// Used by STV-005 for `ASSERT_SECONDS_ABSOLUTE` / `BEFORE_SECONDS_ABSOLUTE` evaluation.
73 fn get_chain_timestamp(&self) -> u64;
74}
75
76/// Signing failure from a [`BlockSigner`] implementation.
77///
78/// Kept intentionally simple — the failure reason is an opaque string because signing backends
79/// vary (HSM timeout, key-not-found, permission denied). The [`crate::BuilderError::SigningFailed`]
80/// variant (ERR-004) wraps this for builder callers.
81#[derive(Debug, thiserror::Error)]
82pub enum SignerError {
83 /// The signing operation failed; `0` carries a human-readable diagnostic.
84 #[error("signing failed: {0}")]
85 SigningFailed(String),
86}
87
88/// Block header signing hook for [`crate::builder::BlockBuilder::build`] ([SPEC §7.2](docs/resources/SPEC.md), BLD-006).
89///
90/// The proposer calls `sign_block(&header_hash)` at the end of the build pipeline to produce
91/// the BLS signature stored in [`crate::L2Block::proposer_signature`].
92///
93/// ## Object safety
94///
95/// Same constraints as [`CoinLookup`] — `&self`, no generics, returns concrete types.
96/// `Box<dyn BlockSigner>` is valid for dependency injection.
97///
98/// ## Chia parity
99///
100/// Signing uses `chia-bls::sign(secret_key, message)` internally. The message is the
101/// header hash bytes (`Bytes32::as_ref()` → `&[u8; 32]`), matching the pattern in
102/// [`chia-blockchain/chia/consensus/block_creation.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_creation.py)
103/// where the farmer signs the header hash.
104pub trait BlockSigner {
105 /// Sign the given block header hash, producing a BLS12-381 signature.
106 ///
107 /// Returns [`SignerError`] if the key is unavailable or the signing backend fails.
108 fn sign_block(&self, header_hash: &Bytes32) -> Result<Signature, SignerError>;
109}