use bitcoin::hex::{DisplayHex, FromHex};
use brk_error::{Error, Result};
use brk_types::{
BlockHash, Height, MerkleProof, Timestamp, Transaction, TxInIndex, TxIndex, TxOutIndex,
TxOutspend, TxStatus, Txid, TxidPrefix, Vin, Vout,
};
use vecdb::{ReadableVec, VecIndex};
use crate::Query;
impl Query {
pub fn transaction(&self, txid: &Txid) -> Result<Transaction> {
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(txid)
{
return Ok(tx_with_hex.tx().clone());
}
let prefix = TxidPrefix::from(txid);
let indexer = self.indexer();
let Ok(Some(tx_index)) = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)
.map(|opt| opt.map(|cow| cow.into_owned()))
else {
return Err(Error::UnknownTxid);
};
self.transaction_by_index(tx_index)
}
pub fn transaction_status(&self, txid: &Txid) -> Result<TxStatus> {
if let Some(mempool) = self.mempool()
&& mempool.get_txs().contains_key(txid)
{
return Ok(TxStatus::UNCONFIRMED);
}
let prefix = TxidPrefix::from(txid);
let indexer = self.indexer();
let Ok(Some(tx_index)) = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)
.map(|opt| opt.map(|cow| cow.into_owned()))
else {
return Err(Error::UnknownTxid);
};
let height = indexer
.vecs
.transactions
.height
.collect_one(tx_index)
.unwrap();
let block_hash = indexer
.vecs
.blocks
.blockhash
.reader()
.get(height.to_usize());
let block_time = indexer.vecs.blocks.timestamp.collect_one(height).unwrap();
Ok(TxStatus {
confirmed: true,
block_height: Some(height),
block_hash: Some(block_hash),
block_time: Some(block_time),
})
}
pub fn transaction_raw(&self, txid: &Txid) -> Result<Vec<u8>> {
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(txid)
{
return Vec::from_hex(tx_with_hex.hex())
.map_err(|_| Error::Parse("Failed to decode mempool tx hex".into()));
}
let prefix = TxidPrefix::from(txid);
let indexer = self.indexer();
let Ok(Some(tx_index)) = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)
.map(|opt| opt.map(|cow| cow.into_owned()))
else {
return Err(Error::UnknownTxid);
};
self.transaction_raw_by_index(tx_index)
}
pub fn transaction_hex(&self, txid: &Txid) -> Result<String> {
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(txid)
{
return Ok(tx_with_hex.hex().to_string());
}
let prefix = TxidPrefix::from(txid);
let indexer = self.indexer();
let Ok(Some(tx_index)) = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)
.map(|opt| opt.map(|cow| cow.into_owned()))
else {
return Err(Error::UnknownTxid);
};
self.transaction_hex_by_index(tx_index)
}
pub fn outspend(&self, txid: &Txid, vout: Vout) -> Result<TxOutspend> {
if self
.mempool()
.is_some_and(|m| m.get_txs().contains_key(txid))
{
return Ok(TxOutspend::UNSPENT);
}
let (_, first_txout, output_count) = self.resolve_tx_outputs(txid)?;
if usize::from(vout) >= output_count {
return Ok(TxOutspend::UNSPENT);
}
self.resolve_outspend(first_txout + vout)
}
pub fn outspends(&self, txid: &Txid) -> Result<Vec<TxOutspend>> {
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(txid)
{
return Ok(vec![TxOutspend::UNSPENT; tx_with_hex.tx().output.len()]);
}
let (_, first_txout, output_count) = self.resolve_tx_outputs(txid)?;
let indexer = self.indexer();
let txin_index_reader = self.computer().outputs.spent.txin_index.reader();
let txid_reader = indexer.vecs.transactions.txid.reader();
let blockhash_reader = indexer.vecs.blocks.blockhash.reader();
let mut input_tx_cursor = indexer.vecs.inputs.tx_index.cursor();
let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor();
let mut height_cursor = indexer.vecs.transactions.height.cursor();
let mut block_ts_cursor = indexer.vecs.blocks.timestamp.cursor();
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
let mut outspends = Vec::with_capacity(output_count);
for i in 0..output_count {
let txin_index = txin_index_reader.get(usize::from(first_txout + Vout::from(i)));
if txin_index == TxInIndex::UNSPENT {
outspends.push(TxOutspend::UNSPENT);
continue;
}
let spending_tx_index = input_tx_cursor.get(usize::from(txin_index)).unwrap();
let spending_first_txin = first_txin_cursor.get(spending_tx_index.to_usize()).unwrap();
let vin = Vin::from(usize::from(txin_index) - usize::from(spending_first_txin));
let spending_txid = txid_reader.get(spending_tx_index.to_usize());
let spending_height = height_cursor.get(spending_tx_index.to_usize()).unwrap();
let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_block
&& h == spending_height
{
(bh.clone(), bt)
} else {
let bh = blockhash_reader.get(spending_height.to_usize());
let bt = block_ts_cursor.get(spending_height.to_usize()).unwrap();
cached_block = Some((spending_height, bh.clone(), bt));
(bh, bt)
};
outspends.push(TxOutspend {
spent: true,
txid: Some(spending_txid),
vin: Some(vin),
status: Some(TxStatus {
confirmed: true,
block_height: Some(spending_height),
block_hash: Some(block_hash),
block_time: Some(block_time),
}),
});
}
Ok(outspends)
}
fn resolve_tx_outputs(&self, txid: &Txid) -> Result<(TxIndex, TxOutIndex, usize)> {
let prefix = TxidPrefix::from(txid);
let indexer = self.indexer();
let tx_index: TxIndex = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)?
.map(|cow| cow.into_owned())
.ok_or(Error::UnknownTxid)?;
let first = indexer
.vecs
.transactions
.first_txout_index
.read_once(tx_index)?;
let next = indexer
.vecs
.transactions
.first_txout_index
.read_once(tx_index.incremented())?;
Ok((tx_index, first, usize::from(next) - usize::from(first)))
}
fn resolve_outspend(&self, txout_index: TxOutIndex) -> Result<TxOutspend> {
let indexer = self.indexer();
let txin_index = self
.computer()
.outputs
.spent
.txin_index
.reader()
.get(usize::from(txout_index));
if txin_index == TxInIndex::UNSPENT {
return Ok(TxOutspend::UNSPENT);
}
let spending_tx_index = indexer
.vecs
.inputs
.tx_index
.collect_one_at(usize::from(txin_index))
.unwrap();
let spending_first_txin = indexer
.vecs
.transactions
.first_txin_index
.collect_one(spending_tx_index)
.unwrap();
let spending_height = indexer
.vecs
.transactions
.height
.collect_one(spending_tx_index)
.unwrap();
Ok(TxOutspend {
spent: true,
txid: Some(
indexer
.vecs
.transactions
.txid
.reader()
.get(spending_tx_index.to_usize()),
),
vin: Some(Vin::from(
usize::from(txin_index) - usize::from(spending_first_txin),
)),
status: Some(TxStatus {
confirmed: true,
block_height: Some(spending_height),
block_hash: Some(
indexer
.vecs
.blocks
.blockhash
.reader()
.get(spending_height.to_usize()),
),
block_time: Some(
indexer
.vecs
.blocks
.timestamp
.collect_one(spending_height)
.unwrap(),
),
}),
})
}
pub fn transaction_by_index(&self, tx_index: TxIndex) -> Result<Transaction> {
self.transactions_by_indices(&[tx_index])?
.into_iter()
.next()
.ok_or(Error::NotFound("Transaction not found".into()))
}
fn transaction_raw_by_index(&self, tx_index: TxIndex) -> Result<Vec<u8>> {
let indexer = self.indexer();
let total_size = indexer
.vecs
.transactions
.total_size
.collect_one(tx_index)
.unwrap();
let position = indexer
.vecs
.transactions
.position
.collect_one(tx_index)
.unwrap();
self.reader().read_raw_bytes(position, *total_size as usize)
}
fn transaction_hex_by_index(&self, tx_index: TxIndex) -> Result<String> {
Ok(self
.transaction_raw_by_index(tx_index)?
.to_lower_hex_string())
}
pub fn resolve_tx(&self, txid: &Txid) -> Result<(TxIndex, Height)> {
let indexer = self.indexer();
let prefix = TxidPrefix::from(txid);
let tx_index: TxIndex = indexer
.stores
.txid_prefix_to_tx_index
.get(&prefix)?
.map(|cow| cow.into_owned())
.ok_or(Error::UnknownTxid)?;
let height: Height = indexer
.vecs
.transactions
.height
.collect_one(tx_index)
.unwrap();
Ok((tx_index, height))
}
pub fn broadcast_transaction(&self, hex: &str) -> Result<Txid> {
self.client().send_raw_transaction(hex)
}
pub fn merkleblock_proof(&self, txid: &Txid) -> Result<String> {
let (_, height) = self.resolve_tx(txid)?;
let header = self.read_block_header(height)?;
let txids = self.block_txids_by_height(height)?;
let target: bitcoin::Txid = txid.into();
let btxids: Vec<bitcoin::Txid> = txids.iter().map(bitcoin::Txid::from).collect();
let mb = bitcoin::MerkleBlock::from_header_txids_with_predicate(&header, &btxids, |t| {
*t == target
});
Ok(bitcoin::consensus::encode::serialize_hex(&mb))
}
pub fn merkle_proof(&self, txid: &Txid) -> Result<MerkleProof> {
let (tx_index, height) = self.resolve_tx(txid)?;
let first_tx = self
.indexer()
.vecs
.transactions
.first_tx_index
.collect_one(height)
.ok_or(Error::NotFound("Block not found".into()))?;
let pos = tx_index.to_usize() - first_tx.to_usize();
let txids = self.block_txids_by_height(height)?;
Ok(MerkleProof {
block_height: height,
merkle: merkle_path(&txids, pos),
pos,
})
}
}
fn merkle_path(txids: &[Txid], pos: usize) -> Vec<String> {
use bitcoin::hashes::{Hash, sha256d};
let mut hashes: Vec<[u8; 32]> = txids
.iter()
.map(|t| bitcoin::Txid::from(t).to_byte_array())
.collect();
let mut proof = Vec::new();
let mut idx = pos;
while hashes.len() > 1 {
let sibling = if idx ^ 1 < hashes.len() { idx ^ 1 } else { idx };
let mut display = hashes[sibling];
display.reverse();
proof.push(bitcoin::hex::DisplayHex::to_lower_hex_string(&display));
hashes = hashes
.chunks(2)
.map(|pair| {
let right = pair.last().unwrap();
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(&pair[0]);
combined[32..].copy_from_slice(right);
sha256d::Hash::hash(&combined).to_byte_array()
})
.collect();
idx /= 2;
}
proof
}