use anyhow::Result; use serde::{Deserialize, Serialize}; use std::{ mem, sync::{ mpsc::{self, Receiver, Sender}, Arc, RwLock, }, }; // use wasm_bindgen::prelude::*; // use sp_client::bitcoin::absolute::Height; // use sp_client::bitcoin::BlockHash; use sp_client::{ bitcoin::{absolute::Height, BlockHash, OutPoint}, OwnedOutput, Updater, }; use std::collections::{HashMap, HashSet}; #[derive(Debug, Serialize, Deserialize)] pub struct ScanProgress { pub start: u32, pub current: u32, pub end: u32, } #[derive(Debug, Serialize, Deserialize)] pub enum StateUpdate { NoUpdate { blkheight: Height, }, Update { blkheight: Height, blkhash: BlockHash, found_outputs: HashMap, found_inputs: HashSet, }, } // #[cfg(target_arch = "wasm32")] // #[wasm_bindgen] // extern "C" { // #[wasm_bindgen(js_namespace = window)] // fn sendScanProgress(progress: JsValue); // #[wasm_bindgen(js_namespace = window)] // fn sendStateUpdate(update: JsValue); // } pub trait UpdateSink: Send + Sync { fn send_scan_progress(&self, progress: ScanProgress) -> Result<()>; fn send_state_update(&self, update: StateUpdate) -> Result<()>; } #[cfg(not(target_arch = "wasm32"))] pub struct NativeUpdateSink { scan_tx: Sender, state_tx: Sender, } #[cfg(not(target_arch = "wasm32"))] impl NativeUpdateSink { pub fn new() -> (Self, Receiver, Receiver) { let (scan_tx, scan_rx) = mpsc::channel(); let (state_tx, state_rx) = mpsc::channel(); (Self { scan_tx, state_tx }, scan_rx, state_rx) } } #[cfg(not(target_arch = "wasm32"))] impl UpdateSink for NativeUpdateSink { fn send_scan_progress(&self, progress: ScanProgress) -> Result<()> { self.scan_tx.send(progress)?; Ok(()) } fn send_state_update(&self, update: StateUpdate) -> Result<()> { self.state_tx.send(update)?; Ok(()) } } // #[cfg(target_arch = "wasm32")] // pub struct WasmUpdateSink; // #[cfg(target_arch = "wasm32")] // impl UpdateSink for WasmUpdateSink { // fn send_scan_progress(&self, progress: ScanProgress) -> Result<()> { // let js_value = serde_wasm_bindgen::to_value(&progress)?; // sendScanProgress(js_value); // Ok(()) // } // fn send_state_update(&self, update: StateUpdate) -> Result<()> { // let js_value = serde_wasm_bindgen::to_value(&update)?; // sendStateUpdate(js_value); // Ok(()) // } // } // Global sink instance static UPDATE_SINK: RwLock>> = RwLock::new(None); pub fn init_update_sink(sink: Arc) { let mut sink_guard = UPDATE_SINK.write().unwrap(); *sink_guard = Some(sink); } pub fn get_update_sink() -> Option> { UPDATE_SINK.read().unwrap().clone() } #[derive(Debug)] pub struct StateUpdater { update: bool, blkhash: Option, blkheight: Option, found_outputs: HashMap, found_inputs: HashSet, } impl StateUpdater { pub fn new() -> Self { Self { update: false, blkheight: None, blkhash: None, found_outputs: HashMap::new(), found_inputs: HashSet::new(), } } pub fn to_update(&mut self) -> Result { let blkheight = self .blkheight .ok_or(anyhow::Error::msg("blkheight not filled"))?; if self.update { self.update = false; let blkhash = self.blkhash.ok_or(anyhow::Error::msg("blkhash not set"))?; self.blkheight = None; self.blkhash = None; // take results, and insert new empty values let found_inputs = mem::take(&mut self.found_inputs); let found_outputs = mem::take(&mut self.found_outputs); Ok(StateUpdate::Update { blkheight, blkhash, found_outputs, found_inputs, }) } else { Ok(StateUpdate::NoUpdate { blkheight }) } } } impl Updater for StateUpdater { fn record_scan_progress(&mut self, start: Height, current: Height, end: Height) -> Result<()> { self.blkheight = Some(current); if let Some(sink) = get_update_sink() { sink.send_scan_progress(ScanProgress { start: start.to_consensus_u32(), current: current.to_consensus_u32(), end: end.to_consensus_u32(), })?; } Ok(()) } fn record_block_outputs( &mut self, height: Height, blkhash: BlockHash, found_outputs: HashMap, ) -> Result<()> { // may have already been written by record_block_inputs self.update = true; self.found_outputs = found_outputs; self.blkhash = Some(blkhash); self.blkheight = Some(height); Ok(()) } fn record_block_inputs( &mut self, blkheight: Height, blkhash: BlockHash, found_inputs: HashSet, ) -> Result<()> { self.update = true; self.blkheight = Some(blkheight); self.blkhash = Some(blkhash); self.found_inputs = found_inputs; Ok(()) } fn save_to_persistent_storage(&mut self) -> Result<()> { if let Some(sink) = get_update_sink() { sink.send_state_update(self.to_update()?)?; } Ok(()) } }