use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use bsv::transaction::chain_tracker::ChainTracker;
use bsv::transaction::error::TransactionError;
use tokio::sync::Mutex;
use crate::chaintracks::{Chaintracks, ChaintracksClient as LocalChaintracksClient};
use crate::error::{WalletError, WalletResult};
use crate::services::types::BlockHeader;
use super::chaintracks_service_client::ChaintracksServiceClient;
enum ChaintracksBackend {
Remote(ChaintracksServiceClient),
Local(Arc<Chaintracks>),
}
impl From<crate::chaintracks::BlockHeader> for BlockHeader {
fn from(h: crate::chaintracks::BlockHeader) -> Self {
BlockHeader {
version: h.version,
previous_hash: h.previous_hash,
merkle_root: h.merkle_root,
time: h.time,
bits: h.bits,
nonce: h.nonce,
height: h.height,
hash: h.hash,
}
}
}
pub struct ChaintracksChainTracker {
backend: ChaintracksBackend,
root_cache: Mutex<HashMap<u32, String>>,
}
impl ChaintracksChainTracker {
pub fn new(service_client: ChaintracksServiceClient) -> Self {
Self {
backend: ChaintracksBackend::Remote(service_client),
root_cache: Mutex::new(HashMap::new()),
}
}
pub fn with_local(chaintracks: Arc<Chaintracks>) -> Self {
Self {
backend: ChaintracksBackend::Local(chaintracks),
root_cache: Mutex::new(HashMap::new()),
}
}
pub async fn hash_to_header(&self, hash: &str) -> WalletResult<BlockHeader> {
match &self.backend {
ChaintracksBackend::Remote(client) => client
.get_header_for_block_hash(hash)
.await?
.ok_or_else(|| {
WalletError::Internal(format!("No header found for block hash {}", hash))
}),
ChaintracksBackend::Local(ct) => ct
.find_header_for_block_hash(hash)
.await?
.map(BlockHeader::from)
.ok_or_else(|| {
WalletError::Internal(format!("No header found for block hash {}", hash))
}),
}
}
pub async fn get_header_for_height(&self, height: u32) -> WalletResult<BlockHeader> {
match &self.backend {
ChaintracksBackend::Remote(client) => {
client.get_header_for_height(height).await?.ok_or_else(|| {
WalletError::Internal(format!("No header found for height {}", height))
})
}
ChaintracksBackend::Local(ct) => ct
.find_header_for_height(height)
.await?
.map(BlockHeader::from)
.ok_or_else(|| {
WalletError::Internal(format!("No header found for height {}", height))
}),
}
}
async fn fetch_header_for_height(
&self,
height: u32,
) -> Result<Option<BlockHeader>, TransactionError> {
match &self.backend {
ChaintracksBackend::Remote(client) => client
.get_header_for_height(height)
.await
.map_err(|e| TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))),
ChaintracksBackend::Local(ct) => ct
.find_header_for_height(height)
.await
.map(|opt| opt.map(BlockHeader::from))
.map_err(|e| TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))),
}
}
pub async fn insert_cache(&self, height: u32, merkle_root: String) {
let mut cache = self.root_cache.lock().await;
cache.insert(height, merkle_root);
}
}
#[async_trait]
impl ChainTracker for ChaintracksChainTracker {
async fn is_valid_root_for_height(
&self,
root: &str,
height: u32,
) -> Result<bool, TransactionError> {
{
let cache = self.root_cache.lock().await;
if let Some(cached_root) = cache.get(&height) {
return Ok(cached_root == root);
}
}
let header = self.fetch_header_for_height(height).await?;
match header {
None => Ok(false),
Some(h) => {
let mut cache = self.root_cache.lock().await;
cache.insert(height, h.merkle_root.clone());
Ok(h.merkle_root == root)
}
}
}
async fn current_height(&self) -> Result<u32, TransactionError> {
match &self.backend {
ChaintracksBackend::Remote(client) => client
.get_present_height()
.await
.map_err(|e| TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))),
ChaintracksBackend::Local(ct) => ct
.current_height()
.await
.map_err(|e| TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))),
}
}
}