use super::client::{BlockDetails, Client};
use crate::base::{Transaction, Value256};
use crate::error::Result;
use crate::{ReceivedOutput, Wallet};
#[derive(Clone, Debug)]
pub struct Deposit {
pub height: u64,
pub tx_id: String,
pub tx_pub_key: Value256,
pub global_index: u64,
pub payment_id: Option<Vec<u8>>,
pub out: ReceivedOutput,
}
pub const DEFAULT_BATCH_SIZE: u64 = 100;
pub struct Scanner {
pub wallet: Wallet,
pub rpc: Client,
pub batch_size: u64,
}
impl Scanner {
pub fn new(wallet: Wallet, endpoint: &str) -> Scanner {
Scanner {
wallet,
rpc: Client::new(endpoint),
batch_size: 0,
}
}
pub fn scan_range<F>(&self, from: u64, to: u64, mut fn_: F) -> (u64, Result<()>)
where
F: FnMut(Deposit) -> Result<()>,
{
if to < from {
return (from.saturating_sub(1), Ok(()));
}
let batch = if self.batch_size == 0 {
DEFAULT_BATCH_SIZE
} else {
self.batch_size
};
let mut last_done = from.saturating_sub(1);
let mut h = from;
while h <= to {
let count = batch.min(to - h + 1);
let blocks = match self.rpc.get_blocks_details(h, count, false) {
Ok(b) => b,
Err(e) => return (last_done, Err(e)),
};
for blk in &blocks {
if let Err(e) = self.scan_block(blk, &mut fn_) {
return (last_done, Err(e));
}
last_done = blk.height;
}
let got = blocks.len() as u64;
if got == 0 {
break;
}
h += got;
}
(last_done, Ok(()))
}
pub fn sync<F>(&self, from: u64, fn_: F) -> (u64, Result<()>)
where
F: FnMut(Deposit) -> Result<()>,
{
let count = match self.rpc.get_block_count() {
Ok(c) => c,
Err(e) => return (from, Err(e)),
};
if count == 0 || from >= count {
return (from, Ok(()));
}
let tip = count - 1;
let (last, res) = self.scan_range(from, tip, fn_);
(last + 1, res)
}
fn scan_block<F>(&self, blk: &BlockDetails, fn_: &mut F) -> Result<()>
where
F: FnMut(Deposit) -> Result<()>,
{
for txb in &blk.transactions {
let td = self
.rpc
.get_tx_details(&txb.id)
.map_err(|e| crate::err!("block {} tx {}: {e}", blk.height, txb.id))?;
let tx = Transaction::deserialize_for_scan(&td.blob)
.map_err(|e| crate::err!("block {} tx {}: parse blob: {e}", blk.height, txb.id))?;
let res = self
.wallet
.scan_tx(&tx)
.map_err(|e| crate::err!("block {} tx {}: scan: {e}", blk.height, txb.id))?;
for out in res.outputs {
let global_index = td
.outs
.get(out.output_index)
.map(|o| o.global_index)
.unwrap_or(0);
fn_(Deposit {
height: blk.height,
tx_id: txb.id.clone(),
tx_pub_key: res.tx_pub_key,
global_index,
payment_id: res.payment_id.clone(),
out,
})?;
}
}
Ok(())
}
}