use std::sync::atomic::{AtomicBool, Ordering};
use brk_rpc::Client;
use brk_types::{TxOut, Txid, TxidPrefix, Vin, Vout};
use parking_lot::RwLock;
use tracing::warn;
use crate::{State, stores::TxStore};
pub struct Prevouts;
type Fills = Vec<(Vin, TxOut)>;
type Holes = Vec<(Vin, Txid, Vout)>;
type FillBatch = Vec<(Txid, Fills)>;
type HoleBatch = Vec<(Txid, Holes)>;
impl Prevouts {
pub fn fill<F>(lock: &RwLock<State>, resolver: F) -> bool
where
F: Fn(&Txid, Vout) -> Option<TxOut>,
{
let (in_mempool, holes) = {
let state = lock.read();
Self::gather(&state.txs)
};
let external = Self::resolve_external(holes, resolver);
if in_mempool.is_empty() && external.is_empty() {
return false;
}
let mut state = lock.write();
Self::write_fills(&mut state, in_mempool);
Self::write_fills(&mut state, external);
true
}
pub fn rpc_resolver(client: Client) -> impl Fn(&Txid, Vout) -> Option<TxOut> {
let warned = AtomicBool::new(false);
move |txid, vout| {
let bt: &bitcoin::Txid = txid.into();
match client.get_raw_transaction(bt, None as Option<&bitcoin::BlockHash>) {
Ok(tx) => tx
.output
.get(usize::from(vout))
.map(|o| TxOut::from((o.script_pubkey.clone(), o.value.into()))),
Err(_) => {
if !warned.swap(true, Ordering::Relaxed) {
warn!(
"mempool: getrawtransaction missed for {txid}; ensure bitcoind is running with txindex=1"
);
}
None
}
}
}
}
fn gather(txs: &TxStore) -> (FillBatch, HoleBatch) {
if txs.unresolved().is_empty() {
return (Vec::new(), Vec::new());
}
let mut filled: FillBatch = Vec::new();
let mut holes: HoleBatch = Vec::new();
for prefix in txs.unresolved() {
let Some(record) = txs.record_by_prefix(prefix) else {
continue;
};
let mut tx_fills: Fills = Vec::new();
let mut tx_holes: Holes = Vec::new();
for (i, txin) in record.tx.input.iter().enumerate() {
if txin.prevout.is_some() {
continue;
}
let vin = Vin::from(i);
if let Some(parent) = txs.get(&txin.txid)
&& let Some(out) = parent.output.get(usize::from(txin.vout))
{
tx_fills.push((vin, out.clone()));
} else {
tx_holes.push((vin, txin.txid, txin.vout));
}
}
let txid = record.entry.txid;
if !tx_fills.is_empty() {
filled.push((txid, tx_fills));
}
if !tx_holes.is_empty() {
holes.push((txid, tx_holes));
}
}
(filled, holes)
}
fn resolve_external<F>(holes: HoleBatch, resolver: F) -> FillBatch
where
F: Fn(&Txid, Vout) -> Option<TxOut>,
{
holes
.into_iter()
.filter_map(|(txid, holes)| {
let fills: Fills = holes
.into_iter()
.filter_map(|(vin, prev_txid, vout)| {
resolver(&prev_txid, vout).map(|o| (vin, o))
})
.collect();
(!fills.is_empty()).then_some((txid, fills))
})
.collect()
}
fn write_fills(state: &mut State, fills: FillBatch) {
for (txid, tx_fills) in fills {
let prefix = TxidPrefix::from(&txid);
for prevout in state.txs.apply_fills(&prefix, tx_fills) {
state.addrs.add_input(&txid, &prevout);
}
}
}
}