chia_query/provider_registry/
providers.rs1use std::borrow::Cow;
11
12use chia_protocol::{Bytes32, CoinSpend};
13use dig_chainsource_interface::{
14 ChainSource, ChainSourceProvider, CoinRecord, ProviderId, ProviderInfo, ProviderKind,
15 SingletonLineage,
16};
17
18macro_rules! kinded_provider {
21 ($(#[$meta:meta])* $name:ident, $kind:expr) => {
22 $(#[$meta])*
23 pub struct $name<S> {
24 inner: S,
25 info: ProviderInfo,
26 }
27
28 impl<S> $name<S> {
29 pub fn new(id: impl Into<Cow<'static, str>>, priority: i32, source: S) -> Self {
32 Self {
33 inner: source,
34 info: ProviderInfo {
35 id: ProviderId(id.into()),
36 kind: $kind,
37 priority,
38 trustless: false,
40 },
41 }
42 }
43 }
44
45 impl<S> ChainSource for $name<S>
46 where
47 S: ChainSource,
48 {
49 type Error = S::Error;
50
51 fn coin_record(&self, coin_id: Bytes32) -> Result<Option<CoinRecord>, Self::Error> {
52 self.inner.coin_record(coin_id)
53 }
54
55 fn coin_records_by_puzzle_hash(
56 &self,
57 puzzle_hash: Bytes32,
58 include_spent: bool,
59 ) -> Result<Vec<CoinRecord>, Self::Error> {
60 self.inner.coin_records_by_puzzle_hash(puzzle_hash, include_spent)
61 }
62
63 fn coin_records_by_parent(
64 &self,
65 parent_coin_id: Bytes32,
66 ) -> Result<Vec<CoinRecord>, Self::Error> {
67 self.inner.coin_records_by_parent(parent_coin_id)
68 }
69
70 fn coin_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
71 self.inner.coin_spend(coin_id)
72 }
73
74 fn parent_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
75 self.inner.parent_spend(coin_id)
76 }
77
78 fn resolve_singleton_lineage(
79 &self,
80 launcher_id: Bytes32,
81 ) -> Result<Option<SingletonLineage>, Self::Error> {
82 self.inner.resolve_singleton_lineage(launcher_id)
83 }
84
85 fn peak_height(&self) -> Result<Option<u32>, Self::Error> {
86 self.inner.peak_height()
87 }
88
89 fn block_timestamp(&self, height: u32) -> Result<Option<u64>, Self::Error> {
90 self.inner.block_timestamp(height)
91 }
92 }
93
94 impl<S> ChainSourceProvider for $name<S>
95 where
96 S: ChainSource,
97 {
98 fn provider_info(&self) -> ProviderInfo {
99 self.info.clone()
100 }
101 }
102 };
103}
104
105kinded_provider!(
106 CoinsetProvider,
109 ProviderKind::PublicOracle
110);
111
112kinded_provider!(
113 LocalNodeProvider,
116 ProviderKind::LocalNode
117);
118
119kinded_provider!(
120 DigPeersProvider,
122 ProviderKind::DigPeers
123);
124
125kinded_provider!(
126 CustomProvider,
129 ProviderKind::Custom
130);
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use chia_protocol::Coin;
136 use dig_chainsource_interface::{
137 ChainSourceError, CoinRecord as IfaceCoinRecord, MockChainSource, SingletonLineage,
138 };
139
140 #[test]
141 fn wrapper_delegates_every_read_to_the_inner_source() {
142 let id = Coin::new(Bytes32::new([0x01; 32]), Bytes32::new([0x22; 32]), 1).coin_id();
143 let record = IfaceCoinRecord {
144 coin: Coin::new(id, Bytes32::new([0x22; 32]), 1),
145 confirmed_height: Some(5),
146 spent_height: None,
147 timestamp: Some(9),
148 coinbase: false,
149 };
150 let launcher = Bytes32::new([0x33; 32]);
151 let mock = MockChainSource::new()
152 .with_coin(id, record.clone())
153 .with_lineage(launcher, SingletonLineage::single(launcher))
154 .with_timestamp(5, 1_000)
155 .with_peak(42);
156
157 let provider = LocalNodeProvider::new("local", 0, mock);
158
159 assert_eq!(provider.coin_record(id).unwrap(), Some(record.clone()));
160 assert_eq!(
161 provider
162 .coin_records_by_puzzle_hash(Bytes32::new([0x22; 32]), true)
163 .unwrap(),
164 vec![record.clone()]
165 );
166 assert_eq!(
168 provider.coin_records_by_parent(id).unwrap(),
169 vec![record.clone()]
170 );
171 assert_eq!(
172 provider
173 .coin_records_by_parent(Bytes32::new([0xEE; 32]))
174 .unwrap(),
175 vec![]
176 );
177 assert_eq!(provider.coin_spend(id).unwrap(), None);
178 assert_eq!(provider.parent_spend(id).unwrap(), None);
179 assert_eq!(
180 provider.resolve_singleton_lineage(launcher).unwrap(),
181 Some(SingletonLineage::single(launcher))
182 );
183 assert_eq!(provider.peak_height().unwrap(), Some(42));
184 assert_eq!(provider.block_timestamp(5).unwrap(), Some(1_000));
185 }
186
187 #[test]
188 fn wrapper_propagates_inner_errors() {
189 let provider = CoinsetProvider::new(
190 "coinset",
191 0,
192 MockChainSource::new().fail_with(ChainSourceError::Timeout),
193 );
194 assert_eq!(
195 provider.coin_record(Bytes32::new([0x01; 32])),
196 Err(ChainSourceError::Timeout)
197 );
198 }
199
200 #[test]
201 fn wrappers_report_their_kind_and_identity() {
202 let coinset = CoinsetProvider::new("coinset.org", 10, MockChainSource::new());
203 assert_eq!(coinset.provider_info().kind, ProviderKind::PublicOracle);
204 assert_eq!(coinset.provider_info().priority, 10);
205 assert!(!coinset.provider_info().trustless);
206
207 let local = LocalNodeProvider::new("local", 0, MockChainSource::new());
208 assert_eq!(local.provider_info().kind, ProviderKind::LocalNode);
209
210 let peers = DigPeersProvider::new("dig-peers", 20, MockChainSource::new());
211 assert_eq!(peers.provider_info().kind, ProviderKind::DigPeers);
212
213 let custom = CustomProvider::new("custom", 30, MockChainSource::new());
214 assert_eq!(custom.provider_info().kind, ProviderKind::Custom);
215 }
216}