use crate::Query;
use brk_error::{Error, Result};
use brk_mempool::{Mempool, RbfForTx, RbfNode};
use brk_types::{
BlockTemplate, BlockTemplateDiff, CheckedSub, FeeRate, MempoolBlock, MempoolInfo,
MempoolRecentTx, NextBlockHash, OutputType, RbfResponse, RbfTx, RecommendedFees,
ReplacementNode, Sats, Timestamp, TxOut, TxOutIndex, Txid, TxidPrefix, TypeIndex, Vout,
};
use rustc_hash::FxHashMap;
const RECENT_REPLACEMENTS_LIMIT: usize = 25;
impl Query {
fn require_mempool(&self) -> Result<&Mempool> {
self.mempool().ok_or(Error::MempoolNotAvailable)
}
pub fn mempool_info(&self) -> Result<MempoolInfo> {
Ok(self.require_mempool()?.info())
}
pub fn mempool_txids(&self) -> Result<Vec<Txid>> {
Ok(self.require_mempool()?.txids())
}
pub fn recommended_fees(&self) -> Result<RecommendedFees> {
self.require_mempool().map(|m| m.fees())
}
pub fn mempool_blocks(&self) -> Result<Vec<MempoolBlock>> {
let mempool = self.require_mempool()?;
Ok(mempool
.block_stats()
.iter()
.map(MempoolBlock::from)
.collect())
}
#[allow(clippy::type_complexity)]
pub fn indexer_prevout_resolver(
&self,
) -> Box<dyn Fn(&[(Txid, Vout)]) -> FxHashMap<(Txid, Vout), TxOut> + Send + Sync> {
let indexer = self.0.indexer;
Box::new(move |holes: &[(Txid, Vout)]| {
if holes.is_empty() {
return FxHashMap::default();
}
let safe = indexer.safe_lengths();
let first_txout_reader = indexer.vecs.transactions.first_txout_index.reader();
let output_type_reader = indexer.vecs.outputs.output_type.reader();
let type_index_reader = indexer.vecs.outputs.type_index.reader();
let value_reader = indexer.vecs.outputs.value.reader();
let addr_readers = indexer.vecs.addrs.addr_readers();
holes
.iter()
.filter_map(|(prev_txid, vout)| {
let prev_tx_index = indexer
.stores
.txid_prefix_to_tx_index
.get(&TxidPrefix::from(prev_txid))
.ok()??
.into_owned();
if prev_tx_index >= safe.tx_index {
return None;
}
let first_txout: TxOutIndex =
first_txout_reader.try_get(usize::from(prev_tx_index))?;
let txout = first_txout + *vout;
if txout >= safe.txout_index {
return None;
}
let txout_idx = usize::from(txout);
let output_type: OutputType = output_type_reader.try_get(txout_idx)?;
let type_index: TypeIndex = type_index_reader.try_get(txout_idx)?;
let value: Sats = value_reader.try_get(txout_idx)?;
let script_pubkey = addr_readers.script_pubkey(output_type, type_index);
Some(((*prev_txid, *vout), TxOut::from((script_pubkey, value))))
})
.collect()
})
}
pub fn mempool_recent(&self) -> Result<Vec<MempoolRecentTx>> {
Ok(self.require_mempool()?.recent_txs())
}
pub fn tx_rbf(&self, txid: &Txid) -> Result<RbfResponse> {
let RbfForTx { root, replaces } = self.require_mempool()?.rbf_for_tx(txid);
let replacements = root.map(|n| self.enrich_rbf_node(n, None));
let replaces = (!replaces.is_empty()).then_some(replaces);
Ok(RbfResponse {
replacements,
replaces,
})
}
pub fn recent_replacements(&self, full_rbf_only: bool) -> Result<Vec<ReplacementNode>> {
Ok(self
.require_mempool()?
.recent_rbf_trees(full_rbf_only, RECENT_REPLACEMENTS_LIMIT)
.into_iter()
.map(|n| self.enrich_rbf_node(n, None))
.collect())
}
fn enrich_rbf_node(&self, node: RbfNode, successor_time: Option<Timestamp>) -> ReplacementNode {
let interval = successor_time
.and_then(|st| st.checked_sub(node.first_seen))
.map(|d| *d);
let mined = self.resolve_tx_index(&node.txid).is_ok().then_some(true);
let rate = self
.effective_fee_rate(&node.txid)
.unwrap_or_else(|_| FeeRate::from((node.fee, node.vsize)));
let first_seen = node.first_seen;
let replaces = node
.replaces
.into_iter()
.map(|child| self.enrich_rbf_node(child, Some(first_seen)))
.collect();
ReplacementNode {
tx: RbfTx {
txid: node.txid,
fee: node.fee,
vsize: node.vsize,
value: node.value,
rate,
time: first_seen,
rbf: node.rbf,
full_rbf: Some(node.full_rbf),
},
time: first_seen,
full_rbf: node.full_rbf,
interval,
mined,
replaces,
}
}
pub fn transaction_times(&self, txids: &[Txid]) -> Result<Vec<u64>> {
Ok(self.require_mempool()?.transaction_times(txids))
}
pub fn mempool_hash(&self) -> Result<NextBlockHash> {
Ok(self.require_mempool()?.next_block_hash())
}
pub fn block_template(&self) -> Result<BlockTemplate> {
Ok(self.require_mempool()?.block_template())
}
pub fn block_template_diff(&self, since: NextBlockHash) -> Result<BlockTemplateDiff> {
self.require_mempool()?
.block_template_diff(since)
.ok_or_else(|| Error::NotFound(format!("unknown since hash: {since}")))
}
}