subcoin_mempool/
coins_view.rs1use crate::error::MempoolError;
8use bitcoin::{OutPoint as COutPoint, Transaction};
9use sc_client_api::HeaderBackend;
10use schnellru::{ByLength, LruMap};
11use sp_api::ProvideRuntimeApi;
12use sp_runtime::traits::Block as BlockT;
13use std::collections::{HashMap, HashSet};
14use std::marker::PhantomData;
15use std::sync::Arc;
16use subcoin_primitives::{MEMPOOL_HEIGHT, PoolCoin};
17use subcoin_runtime_primitives::SubcoinApi;
18
19pub struct CoinsViewCache<Block: BlockT, Client> {
26 base_cache: LruMap<COutPoint, Option<PoolCoin>, ByLength>,
28
29 mempool_overlay: HashMap<COutPoint, PoolCoin>,
31
32 mempool_spends: HashSet<COutPoint>,
34
35 best_block: Block::Hash,
37
38 client: Arc<Client>,
40
41 _phantom: PhantomData<Block>,
42}
43
44impl<Block, Client> CoinsViewCache<Block, Client>
45where
46 Block: BlockT,
47 Client: ProvideRuntimeApi<Block> + HeaderBackend<Block> + Send + Sync,
48 Client::Api: subcoin_runtime_primitives::SubcoinApi<Block>,
49{
50 pub fn new(client: Arc<Client>, cache_size: u32) -> Self {
56 let best_block = client.info().best_hash;
57
58 Self {
59 base_cache: LruMap::new(ByLength::new(cache_size)),
60 mempool_overlay: HashMap::new(),
61 mempool_spends: HashSet::new(),
62 best_block,
63 client,
64 _phantom: PhantomData,
65 }
66 }
67
68 pub fn get_coin(&mut self, outpoint: &COutPoint) -> Result<Option<PoolCoin>, MempoolError> {
76 if self.mempool_spends.contains(outpoint) {
78 return Ok(None);
79 }
80
81 if let Some(coin) = self.mempool_overlay.get(outpoint) {
83 return Ok(Some(coin.clone()));
84 }
85
86 if let Some(cached) = self.base_cache.peek(outpoint) {
88 return Ok(cached.clone());
89 }
90
91 let coin = self.fetch_from_runtime(outpoint)?;
93 self.base_cache.insert(*outpoint, coin.clone());
94 Ok(coin)
95 }
96
97 pub fn ensure_coins(&mut self, outpoints: &[COutPoint]) -> Result<(), MempoolError> {
104 let missing: Vec<COutPoint> = outpoints
105 .iter()
106 .copied()
107 .filter(|op| {
108 !self.mempool_spends.contains(op)
109 && !self.mempool_overlay.contains_key(op)
110 && self.base_cache.peek(op).is_none()
111 })
112 .collect();
113
114 if missing.is_empty() {
115 return Ok(());
116 }
117
118 let coins = self.batch_fetch_from_runtime(&missing)?;
120
121 for (outpoint, coin_opt) in missing.into_iter().zip(coins) {
122 self.base_cache.insert(outpoint, coin_opt);
123 }
124
125 Ok(())
126 }
127
128 pub fn add_mempool_coins(&mut self, tx: &Transaction) {
133 for (idx, output) in tx.output.iter().enumerate() {
134 let outpoint = COutPoint::new(tx.compute_txid(), idx as u32);
135 self.mempool_overlay.insert(
136 outpoint,
137 PoolCoin {
138 output: output.clone(),
139 height: MEMPOOL_HEIGHT,
140 is_coinbase: false,
141 median_time_past: 0, },
143 );
144 }
145 }
146
147 pub fn spend_coin(&mut self, outpoint: &COutPoint) {
152 self.mempool_spends.insert(*outpoint);
153 }
154
155 pub fn remove_mempool_tx(&mut self, tx: &Transaction) {
160 for idx in 0..tx.output.len() {
162 let outpoint = COutPoint::new(tx.compute_txid(), idx as u32);
163 self.mempool_overlay.remove(&outpoint);
164 }
165
166 for input in &tx.input {
168 self.mempool_spends.remove(&input.previous_output);
169 }
170 }
171
172 pub fn on_block_connected(&mut self, block_hash: Block::Hash) {
177 self.best_block = block_hash;
178 self.base_cache.clear(); }
180
181 pub fn have_coin(&self, outpoint: &COutPoint) -> bool {
183 if self.mempool_spends.contains(outpoint) {
184 return false;
185 }
186
187 self.mempool_overlay.contains_key(outpoint) || self.base_cache.peek(outpoint).is_some()
188 }
189
190 fn fetch_from_runtime(&self, outpoint: &COutPoint) -> Result<Option<PoolCoin>, MempoolError> {
192 let api = self.client.runtime_api();
193 let outpoint_prim = subcoin_runtime_primitives::OutPoint::from(*outpoint);
194
195 api.get_utxos(self.best_block, vec![outpoint_prim])
196 .map_err(|e| MempoolError::RuntimeApi(format!("Failed to fetch UTXO: {e:?}")))?
197 .into_iter()
198 .next()
199 .ok_or_else(|| MempoolError::RuntimeApi("Empty response from runtime API".into()))
200 .map(|opt_coin| opt_coin.map(|coin| coin.into()))
201 }
202
203 fn batch_fetch_from_runtime(
205 &self,
206 outpoints: &[COutPoint],
207 ) -> Result<Vec<Option<PoolCoin>>, MempoolError> {
208 let api = self.client.runtime_api();
209 let outpoints_prim: Vec<_> = outpoints
210 .iter()
211 .map(|op| subcoin_runtime_primitives::OutPoint::from(*op))
212 .collect();
213
214 api.get_utxos(self.best_block, outpoints_prim)
215 .map(|coins| {
216 coins
217 .into_iter()
218 .map(|opt_coin| opt_coin.map(|coin| coin.into()))
219 .collect()
220 })
221 .map_err(|e| MempoolError::RuntimeApi(format!("Batch fetch failed: {e:?}")))
222 }
223
224 pub fn best_block(&self) -> Block::Hash {
226 self.best_block
227 }
228
229 pub fn client(&self) -> &Arc<Client> {
231 &self.client
232 }
233
234 pub fn cache_stats(&self) -> CacheStats {
236 CacheStats {
237 base_cache_entries: self.base_cache.len(),
238 mempool_overlay_entries: self.mempool_overlay.len(),
239 mempool_spends_entries: self.mempool_spends.len(),
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct CacheStats {
247 pub base_cache_entries: usize,
249 pub mempool_overlay_entries: usize,
251 pub mempool_spends_entries: usize,
253}
254
255#[cfg(test)]
256mod tests {
257 #[allow(unused_imports)]
258 use super::*;
259
260 }