use std::borrow::Cow;
use chia_protocol::{Bytes32, CoinSpend};
use dig_chainsource_interface::{
ChainSource, ChainSourceProvider, CoinRecord, ProviderId, ProviderInfo, ProviderKind,
SingletonLineage,
};
macro_rules! kinded_provider {
($(#[$meta:meta])* $name:ident, $kind:expr) => {
$(#[$meta])*
pub struct $name<S> {
inner: S,
info: ProviderInfo,
}
impl<S> $name<S> {
pub fn new(id: impl Into<Cow<'static, str>>, priority: i32, source: S) -> Self {
Self {
inner: source,
info: ProviderInfo {
id: ProviderId(id.into()),
kind: $kind,
priority,
trustless: false,
},
}
}
}
impl<S> ChainSource for $name<S>
where
S: ChainSource,
{
type Error = S::Error;
fn coin_record(&self, coin_id: Bytes32) -> Result<Option<CoinRecord>, Self::Error> {
self.inner.coin_record(coin_id)
}
fn coin_records_by_puzzle_hash(
&self,
puzzle_hash: Bytes32,
include_spent: bool,
) -> Result<Vec<CoinRecord>, Self::Error> {
self.inner.coin_records_by_puzzle_hash(puzzle_hash, include_spent)
}
fn coin_records_by_parent(
&self,
parent_coin_id: Bytes32,
) -> Result<Vec<CoinRecord>, Self::Error> {
self.inner.coin_records_by_parent(parent_coin_id)
}
fn coin_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
self.inner.coin_spend(coin_id)
}
fn parent_spend(&self, coin_id: Bytes32) -> Result<Option<CoinSpend>, Self::Error> {
self.inner.parent_spend(coin_id)
}
fn resolve_singleton_lineage(
&self,
launcher_id: Bytes32,
) -> Result<Option<SingletonLineage>, Self::Error> {
self.inner.resolve_singleton_lineage(launcher_id)
}
fn peak_height(&self) -> Result<Option<u32>, Self::Error> {
self.inner.peak_height()
}
fn block_timestamp(&self, height: u32) -> Result<Option<u64>, Self::Error> {
self.inner.block_timestamp(height)
}
}
impl<S> ChainSourceProvider for $name<S>
where
S: ChainSource,
{
fn provider_info(&self) -> ProviderInfo {
self.info.clone()
}
}
};
}
kinded_provider!(
CoinsetProvider,
ProviderKind::PublicOracle
);
kinded_provider!(
LocalNodeProvider,
ProviderKind::LocalNode
);
kinded_provider!(
DigPeersProvider,
ProviderKind::DigPeers
);
kinded_provider!(
CustomProvider,
ProviderKind::Custom
);
#[cfg(test)]
mod tests {
use super::*;
use chia_protocol::Coin;
use dig_chainsource_interface::{
ChainSourceError, CoinRecord as IfaceCoinRecord, MockChainSource, SingletonLineage,
};
#[test]
fn wrapper_delegates_every_read_to_the_inner_source() {
let id = Coin::new(Bytes32::new([0x01; 32]), Bytes32::new([0x22; 32]), 1).coin_id();
let record = IfaceCoinRecord {
coin: Coin::new(id, Bytes32::new([0x22; 32]), 1),
confirmed_height: Some(5),
spent_height: None,
timestamp: Some(9),
coinbase: false,
};
let launcher = Bytes32::new([0x33; 32]);
let mock = MockChainSource::new()
.with_coin(id, record.clone())
.with_lineage(launcher, SingletonLineage::single(launcher))
.with_timestamp(5, 1_000)
.with_peak(42);
let provider = LocalNodeProvider::new("local", 0, mock);
assert_eq!(provider.coin_record(id).unwrap(), Some(record.clone()));
assert_eq!(
provider
.coin_records_by_puzzle_hash(Bytes32::new([0x22; 32]), true)
.unwrap(),
vec![record.clone()]
);
assert_eq!(
provider.coin_records_by_parent(id).unwrap(),
vec![record.clone()]
);
assert_eq!(
provider
.coin_records_by_parent(Bytes32::new([0xEE; 32]))
.unwrap(),
vec![]
);
assert_eq!(provider.coin_spend(id).unwrap(), None);
assert_eq!(provider.parent_spend(id).unwrap(), None);
assert_eq!(
provider.resolve_singleton_lineage(launcher).unwrap(),
Some(SingletonLineage::single(launcher))
);
assert_eq!(provider.peak_height().unwrap(), Some(42));
assert_eq!(provider.block_timestamp(5).unwrap(), Some(1_000));
}
#[test]
fn wrapper_propagates_inner_errors() {
let provider = CoinsetProvider::new(
"coinset",
0,
MockChainSource::new().fail_with(ChainSourceError::Timeout),
);
assert_eq!(
provider.coin_record(Bytes32::new([0x01; 32])),
Err(ChainSourceError::Timeout)
);
}
#[test]
fn wrappers_report_their_kind_and_identity() {
let coinset = CoinsetProvider::new("coinset.org", 10, MockChainSource::new());
assert_eq!(coinset.provider_info().kind, ProviderKind::PublicOracle);
assert_eq!(coinset.provider_info().priority, 10);
assert!(!coinset.provider_info().trustless);
let local = LocalNodeProvider::new("local", 0, MockChainSource::new());
assert_eq!(local.provider_info().kind, ProviderKind::LocalNode);
let peers = DigPeersProvider::new("dig-peers", 20, MockChainSource::new());
assert_eq!(peers.provider_info().kind, ProviderKind::DigPeers);
let custom = CustomProvider::new("custom", 30, MockChainSource::new());
assert_eq!(custom.provider_info().kind, ProviderKind::Custom);
}
}