Skip to main content

dig_chainsource_interface/
source.rs

1//! The [`ChainSource`] trait — the ONE canonical, reads-only interface for consulting Chia chain
2//! state, and [`ChainSourceProvider`], the self-describing form a registry composes.
3//!
4//! The trait is deliberately **synchronous and object-safe**: every method takes `&self` and
5//! returns a plain `Result`, so `Box<dyn ChainSource<Error = _>>` works and an async backend can
6//! present a blocking facade at the aggregator boundary (see the async->sync bridge note in the
7//! crate docs). It is **reads-only** — there is NO broadcast/push/submit method anywhere in this
8//! crate, by design (SPEC §1).
9
10use chia_protocol::{Bytes32, CoinSpend};
11
12use crate::lineage::SingletonLineage;
13use crate::provider::ProviderInfo;
14use crate::record::CoinRecord;
15
16/// A reads-only view of Chia chain state — the single canonical contract every provider implements
17/// and every consumer depends on.
18///
19/// ## Fail-closed `None` vs `Err` (the crux)
20///
21/// Every fallible method distinguishes two outcomes that consumers MUST treat differently:
22/// - `Ok(None)` / an empty `Vec` — the source reliably answered and the thing genuinely does not
23///   exist (an unlaunched singleton, an unspent coin, a melted lineage). Safe to act on.
24/// - `Err(_)` — the source could NOT reliably answer (transport, timeout, malformed, unsupported).
25///   The answer is unknown; the consumer MUST fail closed and never treat it as an absence.
26///
27/// ## Reads only
28///
29/// This trait cannot broadcast, push, or submit anything to the chain — there is no such method,
30/// deliberately. It is a pure reader; write/spend paths live entirely outside this crate.
31pub trait ChainSource {
32    /// The source's own transport/parse error. [`crate::ChainSourceError`] is the recommended type
33    /// for registry participants, but any `Display` error is accepted.
34    type Error: core::fmt::Display;
35
36    /// Reads the coin with id `coin_id`.
37    ///
38    /// `Ok(None)` = the coin genuinely does not exist per this source; `Err(_)` = could not answer.
39    fn coin_record(&self, coin_id: Bytes32) -> Result<Option<CoinRecord>, Self::Error>;
40
41    /// Reads all coins paying to `puzzle_hash`, optionally including already-spent coins.
42    ///
43    /// An empty `Vec` = no matching coins; `Err(_)` = could not answer.
44    fn coin_records_by_puzzle_hash(
45        &self,
46        puzzle_hash: Bytes32,
47        include_spent: bool,
48    ) -> Result<Vec<CoinRecord>, Self::Error>;
49
50    /// Reads all coins whose parent is `parent_coin_id` — the direct children created by spending
51    /// that coin.
52    ///
53    /// An empty `Vec` = no known children; `Err(_)` = could not answer.
54    fn coin_records_by_parent(
55        &self,
56        parent_coin_id: Bytes32,
57    ) -> Result<Vec<CoinRecord>, Self::Error>;
58
59    /// Reads the spend that SPENT `coin_id` (the spend whose input coin is `coin_id`).
60    ///
61    /// `Ok(None)` = `coin_id` is unspent or unknown; `Err(_)` = could not answer.
62    fn coin_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error>;
63
64    /// Reads the spend that CREATED `coin_id` — i.e. the spend of `coin_id`'s parent — by resolving
65    /// the coin's parent and reading that parent's spend. This is the **money-critical parent-walk
66    /// primitive**.
67    ///
68    /// A coin's `puzzle_hash` is attacker-chosen: anyone can pay a coin whose puzzle hash equals a
69    /// victim singleton's outer hash, so a bare `launcher_id ==` check is spoofable. Authenticating
70    /// a coin as a genuine singleton requires walking `parent_spend` back toward the real launcher,
71    /// where each hop is proven from the parent's actual reveal+solution. A spoofed curried-puzzle
72    /// coin has no genuine recreation parent-spend, so the walk fails closed rather than trusting
73    /// the cheap equality. Consumers MUST walk this primitive, never trust puzzle-hash equality.
74    ///
75    /// The default implementation composes [`coin_record`](Self::coin_record) +
76    /// [`coin_spend`](Self::coin_spend); a source may override it with a direct query. `Ok(None)` =
77    /// the parent is unspent/unknown (a gap → fail closed mid-walk); `Err(_)` = could not answer.
78    fn parent_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
79        let Some(record) = self.coin_record(coin_id)? else {
80            return Ok(None);
81        };
82        self.coin_spend(record.coin.parent_coin_info)
83    }
84
85    /// Resolves the singleton launched at `launcher_id` to its authenticated [`SingletonLineage`]
86    /// (every coin id from launcher to current tip).
87    ///
88    /// This MUST be a genuine forward walk from the launcher to its tip — each coin the singleton
89    /// recreation of the previous — NEVER an echo of a caller-supplied coin, or membership becomes
90    /// meaningless (SPEC §4, the money-critical requirement). `Ok(None)` = the launcher never
91    /// existed or the singleton has been fully melted; `Err(_)` = could not answer.
92    fn resolve_singleton_lineage(
93        &self,
94        launcher_id: Bytes32,
95    ) -> Result<Option<SingletonLineage>, Self::Error>;
96
97    /// Reads the current peak (fully-synced) block height, if the source tracks one.
98    ///
99    /// `Ok(None)` = the source does not expose a peak; `Err(_)` = could not answer.
100    fn peak_height(&self) -> Result<Option<u32>, Self::Error>;
101
102    /// Reads the Unix timestamp of the block at `height`.
103    ///
104    /// `Ok(None)` = no such block or no timestamp index; `Err(_)` = could not answer.
105    fn block_timestamp(&self, height: u32) -> Result<Option<u64>, Self::Error>;
106}
107
108/// A [`ChainSource`] that describes itself to the aggregating registry.
109///
110/// The registry uses [`provider_info`](Self::provider_info) to order and trust-weight providers
111/// (see [`ProviderInfo`]).
112pub trait ChainSourceProvider: ChainSource {
113    /// This provider's registration descriptor (identity, kind, priority, trust posture).
114    fn provider_info(&self) -> ProviderInfo;
115}