bsv_wallet_toolbox/services/chaintracker/
chaintracks_chain_tracker.rs1use std::collections::HashMap;
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use bsv::transaction::chain_tracker::ChainTracker;
15use bsv::transaction::error::TransactionError;
16use tokio::sync::Mutex;
17
18use crate::chaintracks::{Chaintracks, ChaintracksClient as LocalChaintracksClient};
19use crate::error::{WalletError, WalletResult};
20use crate::services::types::BlockHeader;
21
22use super::chaintracks_service_client::ChaintracksServiceClient;
23
24enum ChaintracksBackend {
29 Remote(ChaintracksServiceClient),
30 Local(Arc<Chaintracks>),
31}
32
33impl From<crate::chaintracks::BlockHeader> for BlockHeader {
41 fn from(h: crate::chaintracks::BlockHeader) -> Self {
42 BlockHeader {
43 version: h.version,
44 previous_hash: h.previous_hash,
45 merkle_root: h.merkle_root,
46 time: h.time,
47 bits: h.bits,
48 nonce: h.nonce,
49 height: h.height,
50 hash: h.hash,
51 }
52 }
53}
54
55pub struct ChaintracksChainTracker {
65 backend: ChaintracksBackend,
66 root_cache: Mutex<HashMap<u32, String>>,
67}
68
69impl ChaintracksChainTracker {
70 pub fn new(service_client: ChaintracksServiceClient) -> Self {
74 Self {
75 backend: ChaintracksBackend::Remote(service_client),
76 root_cache: Mutex::new(HashMap::new()),
77 }
78 }
79
80 pub fn with_local(chaintracks: Arc<Chaintracks>) -> Self {
82 Self {
83 backend: ChaintracksBackend::Local(chaintracks),
84 root_cache: Mutex::new(HashMap::new()),
85 }
86 }
87
88 pub async fn hash_to_header(&self, hash: &str) -> WalletResult<BlockHeader> {
90 match &self.backend {
91 ChaintracksBackend::Remote(client) => client
92 .get_header_for_block_hash(hash)
93 .await?
94 .ok_or_else(|| {
95 WalletError::Internal(format!("No header found for block hash {}", hash))
96 }),
97 ChaintracksBackend::Local(ct) => ct
98 .find_header_for_block_hash(hash)
99 .await?
100 .map(BlockHeader::from)
101 .ok_or_else(|| {
102 WalletError::Internal(format!("No header found for block hash {}", hash))
103 }),
104 }
105 }
106
107 pub async fn get_header_for_height(&self, height: u32) -> WalletResult<BlockHeader> {
109 match &self.backend {
110 ChaintracksBackend::Remote(client) => client
111 .get_header_for_height(height)
112 .await?
113 .ok_or_else(|| {
114 WalletError::Internal(format!("No header found for height {}", height))
115 }),
116 ChaintracksBackend::Local(ct) => ct
117 .find_header_for_height(height)
118 .await?
119 .map(BlockHeader::from)
120 .ok_or_else(|| {
121 WalletError::Internal(format!("No header found for height {}", height))
122 }),
123 }
124 }
125
126 async fn fetch_header_for_height(
130 &self,
131 height: u32,
132 ) -> Result<Option<BlockHeader>, TransactionError> {
133 match &self.backend {
134 ChaintracksBackend::Remote(client) => client
135 .get_header_for_height(height)
136 .await
137 .map_err(|e| {
138 TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))
139 }),
140 ChaintracksBackend::Local(ct) => ct
141 .find_header_for_height(height)
142 .await
143 .map(|opt| opt.map(BlockHeader::from))
144 .map_err(|e| {
145 TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))
146 }),
147 }
148 }
149
150 pub async fn insert_cache(&self, height: u32, merkle_root: String) {
154 let mut cache = self.root_cache.lock().await;
155 cache.insert(height, merkle_root);
156 }
157}
158
159#[async_trait]
164impl ChainTracker for ChaintracksChainTracker {
165 async fn is_valid_root_for_height(
166 &self,
167 root: &str,
168 height: u32,
169 ) -> Result<bool, TransactionError> {
170 {
172 let cache = self.root_cache.lock().await;
173 if let Some(cached_root) = cache.get(&height) {
174 return Ok(cached_root == root);
175 }
176 }
177
178 let header = self.fetch_header_for_height(height).await?;
180
181 match header {
182 None => Ok(false),
183 Some(h) => {
184 let mut cache = self.root_cache.lock().await;
186 cache.insert(height, h.merkle_root.clone());
187 Ok(h.merkle_root == root)
188 }
189 }
190 }
191
192 async fn current_height(&self) -> Result<u32, TransactionError> {
193 match &self.backend {
194 ChaintracksBackend::Remote(client) => client
195 .get_present_height()
196 .await
197 .map_err(|e| {
198 TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))
199 }),
200 ChaintracksBackend::Local(ct) => ct
201 .current_height()
202 .await
203 .map_err(|e| {
204 TransactionError::InvalidFormat(format!("ChainTracker error: {}", e))
205 }),
206 }
207 }
208}