Skip to main content

chia_query/provider_registry/
chia_query_provider.rs

1//! [`ChiaQueryProvider`] — a synchronous [`ChainSource`] facade over the asynchronous
2//! [`ChiaQuery`](crate::ChiaQuery) router.
3//!
4//! It bridges each sync read to the async router with [`run_blocking`](super::bridge::run_blocking)
5//! (which fails closed with a clear error on a current-thread runtime — SPEC §7), maps the router's
6//! outcome onto the fail-closed `Ok(None)`-vs-`Err` contract via
7//! [`convert`](super::convert), and walks singleton lineages with
8//! [`walk_singleton_lineage`](super::lineage_walk::walk_singleton_lineage).
9//!
10//! ## Runtime requirement (SPEC §7)
11//!
12//! The facade MUST run on a **multi-thread** tokio runtime. Building it there and calling it from
13//! synchronous code is the intended pattern; an async consumer must instead wrap each call in
14//! [`tokio::task::spawn_blocking`] so the blocking read never runs on an async worker thread.
15
16use std::sync::Arc;
17
18use chia_protocol::{Bytes32, CoinSpend};
19use dig_chainsource_interface::{
20    ChainSource, ChainSourceError, ChainSourceProvider, CoinRecord, ProviderInfo, SingletonLineage,
21};
22use tokio::runtime::Handle;
23
24use super::bridge::run_blocking;
25use super::convert::{bytes32_to_hex, coin_record_from_chq, coin_spend_from_chq, map_query_error};
26use super::lineage_walk::{singleton_child_from_spend, walk_singleton_lineage};
27use crate::ChiaQuery;
28
29/// A [`ChainSource`] provider backed by chia-query's async peer+coinset router.
30///
31/// Cloning shares the underlying [`ChiaQuery`] and runtime handle.
32#[derive(Clone)]
33pub struct ChiaQueryProvider {
34    inner: Arc<ChiaQuery>,
35    handle: Handle,
36    info: ProviderInfo,
37}
38
39impl ChiaQueryProvider {
40    /// Builds a provider over `inner`, driving its async reads on `handle` (which MUST belong to a
41    /// multi-thread runtime — see the module docs), and describing itself with `info`.
42    pub fn new(inner: Arc<ChiaQuery>, handle: Handle, info: ProviderInfo) -> Self {
43        Self {
44            inner,
45            handle,
46            info,
47        }
48    }
49}
50
51impl ChainSource for ChiaQueryProvider {
52    type Error = ChainSourceError;
53
54    fn coin_record(&self, coin_id: Bytes32) -> Result<Option<CoinRecord>, Self::Error> {
55        let name = bytes32_to_hex(coin_id);
56        let record = run_blocking(&self.handle, self.inner.get_coin_record_by_name_opt(&name))?
57            .map_err(map_query_error)?;
58        record.as_ref().map(coin_record_from_chq).transpose()
59    }
60
61    fn coin_records_by_puzzle_hash(
62        &self,
63        puzzle_hash: Bytes32,
64        include_spent: bool,
65    ) -> Result<Vec<CoinRecord>, Self::Error> {
66        let hash = bytes32_to_hex(puzzle_hash);
67        let records = run_blocking(
68            &self.handle,
69            self.inner
70                .get_coin_records_by_puzzle_hash(&hash, None, None, include_spent),
71        )?
72        .map_err(map_query_error)?;
73        records.iter().map(coin_record_from_chq).collect()
74    }
75
76    fn coin_records_by_parent(
77        &self,
78        parent_coin_id: Bytes32,
79    ) -> Result<Vec<CoinRecord>, Self::Error> {
80        // The interface's `coin_records_by_parent` wants every child, so include spent coins.
81        let parent_ids = [bytes32_to_hex(parent_coin_id)];
82        let records = run_blocking(
83            &self.handle,
84            self.inner
85                .get_coin_records_by_parent_ids(&parent_ids, None, None, true),
86        )?
87        .map_err(map_query_error)?;
88        records.iter().map(coin_record_from_chq).collect()
89    }
90
91    fn coin_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
92        let id = bytes32_to_hex(coin_id);
93        let spend = run_blocking(&self.handle, self.inner.get_coin_spend_opt(&id))?
94            .map_err(map_query_error)?;
95        spend.as_ref().map(coin_spend_from_chq).transpose()
96    }
97
98    fn resolve_singleton_lineage(
99        &self,
100        launcher_id: Bytes32,
101    ) -> Result<Option<SingletonLineage>, Self::Error> {
102        let inner = self.inner.clone();
103        let walk = async move {
104            walk_singleton_lineage(
105                launcher_id,
106                |coin_id| {
107                    let inner = inner.clone();
108                    async move {
109                        let id = bytes32_to_hex(coin_id);
110                        match inner.get_coin_spend_opt(&id).await {
111                            Ok(Some(spend)) => coin_spend_from_chq(&spend).map(Some),
112                            Ok(None) => Ok(None),
113                            Err(error) => Err(map_query_error(error)),
114                        }
115                    }
116                },
117                move |spend| singleton_child_from_spend(spend, launcher_id),
118            )
119            .await
120        };
121        run_blocking(&self.handle, walk)?
122    }
123
124    fn peak_height(&self) -> Result<Option<u32>, Self::Error> {
125        run_blocking(&self.handle, self.inner.peak_height_opt())?.map_err(map_query_error)
126    }
127
128    fn block_timestamp(&self, height: u32) -> Result<Option<u64>, Self::Error> {
129        run_blocking(&self.handle, self.inner.block_timestamp_opt(height))?.map_err(map_query_error)
130    }
131}
132
133impl ChainSourceProvider for ChiaQueryProvider {
134    fn provider_info(&self) -> ProviderInfo {
135        self.info.clone()
136    }
137}