mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-22 08:28:10 -07:00
mempool: snapshot 5 + query: new tools + server: endpoints
This commit is contained in:
@@ -3,11 +3,12 @@ use std::collections::BTreeMap;
|
||||
use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_monitor::Mempool;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_types::{
|
||||
Address, AddressStats, BlockInfo, BlockStatus, Height, Index, IndexInfo, Limit, MempoolInfo,
|
||||
Metric, MetricCount, RecommendedFees, Transaction, TreeNode, TxStatus, Txid, TxidPath, Utxo,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, DifficultyAdjustment, Height,
|
||||
Index, IndexInfo, Limit, MempoolBlock, MempoolInfo, Metric, MetricCount, RecommendedFees,
|
||||
Timestamp, Transaction, TreeNode, TxOutspend, TxStatus, Txid, TxidPath, Utxo, Vout,
|
||||
};
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
@@ -57,6 +58,11 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_address_utxos(address)).await?
|
||||
}
|
||||
|
||||
pub async fn get_address_mempool_txids(&self, address: Address) -> Result<Vec<Txid>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_address_mempool_txids(address)).await?
|
||||
}
|
||||
|
||||
pub async fn get_transaction(&self, txid: TxidPath) -> Result<Transaction> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_transaction(txid)).await?
|
||||
@@ -72,6 +78,16 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_transaction_hex(txid)).await?
|
||||
}
|
||||
|
||||
pub async fn get_tx_outspend(&self, txid: TxidPath, vout: Vout) -> Result<TxOutspend> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_tx_outspend(txid, vout)).await?
|
||||
}
|
||||
|
||||
pub async fn get_tx_outspends(&self, txid: TxidPath) -> Result<Vec<TxOutspend>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_tx_outspends(txid)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block(&self, hash: String) -> Result<BlockInfo> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block(&hash)).await?
|
||||
@@ -82,6 +98,10 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_block_by_height(height)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_by_timestamp(&self, timestamp: Timestamp) -> Result<BlockTimestamp> {
|
||||
self.0.get_block_by_timestamp(timestamp)
|
||||
}
|
||||
|
||||
pub async fn get_block_status(&self, hash: String) -> Result<BlockStatus> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_status(&hash)).await?
|
||||
@@ -97,6 +117,20 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_block_txids(&hash)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_txs(&self, hash: String, start_index: usize) -> Result<Vec<Transaction>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_txs(&hash, start_index)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_txid_at_index(&self, hash: String, index: usize) -> Result<Txid> {
|
||||
self.0.get_block_txid_at_index(&hash, index)
|
||||
}
|
||||
|
||||
pub async fn get_block_raw(&self, hash: String) -> Result<Vec<u8>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_raw(&hash)).await?
|
||||
}
|
||||
|
||||
pub async fn get_mempool_info(&self) -> Result<MempoolInfo> {
|
||||
self.0.get_mempool_info()
|
||||
}
|
||||
@@ -109,6 +143,15 @@ impl AsyncQuery {
|
||||
self.0.get_recommended_fees()
|
||||
}
|
||||
|
||||
pub async fn get_mempool_blocks(&self) -> Result<Vec<MempoolBlock>> {
|
||||
self.0.get_mempool_blocks()
|
||||
}
|
||||
|
||||
pub async fn get_difficulty_adjustment(&self) -> Result<DifficultyAdjustment> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_difficulty_adjustment()).await?
|
||||
}
|
||||
|
||||
pub async fn match_metric(&self, metric: Metric, limit: Limit) -> Result<Vec<&'static str>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || Ok(query.match_metric(&metric, limit))).await?
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Address, AddressBytes, Txid};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Maximum number of mempool txids to return
|
||||
const MAX_MEMPOOL_TXIDS: usize = 50;
|
||||
|
||||
/// Get mempool transaction IDs for an address
|
||||
pub fn get_address_mempool_txids(address: Address, query: &Query) -> Result<Vec<Txid>> {
|
||||
let mempool = query.mempool().ok_or(Error::Str("Mempool not available"))?;
|
||||
|
||||
let bytes = AddressBytes::from_str(&address.address)?;
|
||||
let addresses = mempool.get_addresses();
|
||||
|
||||
let txids: Vec<Txid> = addresses
|
||||
.get(&bytes)
|
||||
.map(|(_, txids)| txids.iter().take(MAX_MEMPOOL_TXIDS).cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(txids)
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
mod addr;
|
||||
mod mempool_txids;
|
||||
mod resolve;
|
||||
mod txids;
|
||||
mod utxos;
|
||||
mod validate;
|
||||
|
||||
pub use addr::*;
|
||||
pub use mempool_txids::*;
|
||||
pub use txids::*;
|
||||
pub use utxos::*;
|
||||
pub use validate::*;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use bitcoin::hex::DisplayHex;
|
||||
use brk_types::{AddressBytes, AddressValidation, OutputType};
|
||||
|
||||
/// Validate a Bitcoin address and return details
|
||||
pub fn validate_address(address: &str) -> AddressValidation {
|
||||
let Ok(script) = AddressBytes::address_to_script(address) else {
|
||||
return AddressValidation::invalid();
|
||||
};
|
||||
|
||||
let output_type = OutputType::from(&script);
|
||||
let script_hex = script.as_bytes().to_lower_hex_string();
|
||||
|
||||
let is_script = matches!(output_type, OutputType::P2SH);
|
||||
let is_witness = matches!(
|
||||
output_type,
|
||||
OutputType::P2WPKH | OutputType::P2WSH | OutputType::P2TR | OutputType::P2A
|
||||
);
|
||||
|
||||
let (witness_version, witness_program) = if is_witness {
|
||||
let version = script.witness_version().map(|v| v.to_num());
|
||||
// Witness program is after the version byte and push opcode
|
||||
let program = if script.len() > 2 {
|
||||
Some(script.as_bytes()[2..].to_lower_hex_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(version, program)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
AddressValidation {
|
||||
isvalid: true,
|
||||
address: Some(address.to_string()),
|
||||
script_pub_key: Some(script_hex),
|
||||
isscript: Some(is_script),
|
||||
iswitness: Some(is_witness),
|
||||
witness_version,
|
||||
witness_program,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockTimestamp, Date, DateIndex, Height, Timestamp};
|
||||
use jiff::Timestamp as JiffTimestamp;
|
||||
use vecdb::{AnyVec, GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get the block closest to a given timestamp using dateindex for fast lookup
|
||||
pub fn get_block_by_timestamp(timestamp: Timestamp, query: &Query) -> Result<BlockTimestamp> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
|
||||
let max_height = query.get_height();
|
||||
let max_height_usize: usize = max_height.into();
|
||||
|
||||
if max_height_usize == 0 {
|
||||
return Err(Error::Str("No blocks indexed"));
|
||||
}
|
||||
|
||||
let target = timestamp;
|
||||
let date = Date::from(target);
|
||||
let dateindex = DateIndex::try_from(date).unwrap_or_default();
|
||||
|
||||
// Get first height of the target date
|
||||
let first_height_of_day = computer
|
||||
.indexes
|
||||
.dateindex_to_first_height
|
||||
.read_once(dateindex)
|
||||
.unwrap_or(Height::from(0usize));
|
||||
|
||||
let start: usize = usize::from(first_height_of_day).min(max_height_usize);
|
||||
|
||||
// Use iterator for efficient sequential access
|
||||
let mut timestamp_iter = indexer.vecs.block.height_to_timestamp.iter()?;
|
||||
|
||||
// Search forward from start to find the last block <= target timestamp
|
||||
let mut best_height = start;
|
||||
let mut best_ts = timestamp_iter.get_unwrap(Height::from(start));
|
||||
|
||||
for h in (start + 1)..=max_height_usize {
|
||||
let height = Height::from(h);
|
||||
let block_ts = timestamp_iter.get_unwrap(height);
|
||||
if block_ts <= target {
|
||||
best_height = h;
|
||||
best_ts = block_ts;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check one block before start in case we need to go backward
|
||||
if start > 0 && best_ts > target {
|
||||
let prev_height = Height::from(start - 1);
|
||||
let prev_ts = timestamp_iter.get_unwrap(prev_height);
|
||||
if prev_ts <= target {
|
||||
best_height = start - 1;
|
||||
best_ts = prev_ts;
|
||||
}
|
||||
}
|
||||
|
||||
let height = Height::from(best_height);
|
||||
let blockhash = indexer.vecs.block.height_to_blockhash.iter()?.get_unwrap(height);
|
||||
|
||||
// Convert timestamp to ISO 8601 format
|
||||
let ts_secs: i64 = (*best_ts).into();
|
||||
let iso_timestamp = JiffTimestamp::from_second(ts_secs)
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|_| best_ts.to_string());
|
||||
|
||||
Ok(BlockTimestamp {
|
||||
height,
|
||||
hash: blockhash,
|
||||
timestamp: iso_timestamp,
|
||||
})
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
mod by_timestamp;
|
||||
mod height_by_hash;
|
||||
mod info;
|
||||
mod list;
|
||||
mod raw;
|
||||
mod status;
|
||||
mod txid_at_index;
|
||||
mod txids;
|
||||
mod txs;
|
||||
|
||||
pub use by_timestamp::*;
|
||||
pub use height_by_hash::*;
|
||||
pub use info::*;
|
||||
pub use list::*;
|
||||
pub use raw::*;
|
||||
pub use status::*;
|
||||
pub use txid_at_index::*;
|
||||
pub use txids::*;
|
||||
pub use txs::*;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::Height;
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get raw block bytes by height
|
||||
pub fn get_block_raw(height: Height, query: &Query) -> Result<Vec<u8>> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
let reader = query.reader();
|
||||
|
||||
let max_height = Height::from(
|
||||
indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.len()
|
||||
.saturating_sub(1),
|
||||
);
|
||||
if height > max_height {
|
||||
return Err(Error::Str("Block height out of range"));
|
||||
}
|
||||
|
||||
let position = computer.blks.height_to_position.read_once(height)?;
|
||||
let size = indexer.vecs.block.height_to_total_size.read_once(height)?;
|
||||
|
||||
reader.read_raw_bytes(position, *size as usize)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Height, TxIndex, Txid};
|
||||
use vecdb::{AnyVec, GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get a single txid at a specific index within a block
|
||||
pub fn get_block_txid_at_index(height: Height, index: usize, query: &Query) -> Result<Txid> {
|
||||
let indexer = query.indexer();
|
||||
|
||||
let max_height = query.get_height();
|
||||
if height > max_height {
|
||||
return Err(Error::Str("Block height out of range"));
|
||||
}
|
||||
|
||||
let first_txindex = indexer.vecs.tx.height_to_first_txindex.read_once(height)?;
|
||||
let next_first_txindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.height_to_first_txindex
|
||||
.read_once(height.incremented())
|
||||
.unwrap_or_else(|_| TxIndex::from(indexer.vecs.tx.txindex_to_txid.len()));
|
||||
|
||||
let first: usize = first_txindex.into();
|
||||
let next: usize = next_first_txindex.into();
|
||||
let tx_count = next - first;
|
||||
|
||||
if index >= tx_count {
|
||||
return Err(Error::Str("Transaction index out of range"));
|
||||
}
|
||||
|
||||
let txindex = TxIndex::from(first + index);
|
||||
let txid = indexer.vecs.tx.txindex_to_txid.iter()?.get_unwrap(txindex);
|
||||
|
||||
Ok(txid)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Height, Transaction, TxIndex};
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::{Query, chain::tx::get_transaction_by_index};
|
||||
|
||||
pub const BLOCK_TXS_PAGE_SIZE: usize = 25;
|
||||
|
||||
/// Get paginated transactions in a block by height
|
||||
pub fn get_block_txs(height: Height, start_index: usize, query: &Query) -> Result<Vec<Transaction>> {
|
||||
let indexer = query.indexer();
|
||||
|
||||
let max_height = query.get_height();
|
||||
if height > max_height {
|
||||
return Err(Error::Str("Block height out of range"));
|
||||
}
|
||||
|
||||
let first_txindex = indexer.vecs.tx.height_to_first_txindex.read_once(height)?;
|
||||
let next_first_txindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.height_to_first_txindex
|
||||
.read_once(height.incremented())
|
||||
.unwrap_or_else(|_| TxIndex::from(indexer.vecs.tx.txindex_to_txid.len()));
|
||||
|
||||
let first: usize = first_txindex.into();
|
||||
let next: usize = next_first_txindex.into();
|
||||
let tx_count = next - first;
|
||||
|
||||
if start_index >= tx_count {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let end_index = (start_index + BLOCK_TXS_PAGE_SIZE).min(tx_count);
|
||||
let count = end_index - start_index;
|
||||
|
||||
let mut txs = Vec::with_capacity(count);
|
||||
for i in start_index..end_index {
|
||||
let txindex = TxIndex::from(first + i);
|
||||
let tx = get_transaction_by_index(txindex, query)?;
|
||||
txs.push(tx);
|
||||
}
|
||||
|
||||
Ok(txs)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::MempoolBlock;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get projected mempool blocks for fee estimation
|
||||
pub fn get_mempool_blocks(query: &Query) -> Result<Vec<MempoolBlock>> {
|
||||
let mempool = query.mempool().ok_or(Error::Str("Mempool not available"))?;
|
||||
|
||||
let block_stats = mempool.get_block_stats();
|
||||
|
||||
let blocks = block_stats
|
||||
.into_iter()
|
||||
.map(|stats| {
|
||||
MempoolBlock::new(stats.tx_count, stats.total_vsize, stats.total_fee, stats.fee_range)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
mod blocks;
|
||||
mod fees;
|
||||
mod info;
|
||||
mod txids;
|
||||
|
||||
pub use blocks::*;
|
||||
pub use fees::*;
|
||||
pub use info::*;
|
||||
pub use txids::*;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{DifficultyAdjustment, DifficultyEpoch, Height};
|
||||
use vecdb::GenericStoredVec;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Blocks per difficulty epoch (2 weeks target)
|
||||
const BLOCKS_PER_EPOCH: u32 = 2016;
|
||||
|
||||
/// Target block time in seconds (10 minutes)
|
||||
const TARGET_BLOCK_TIME: u64 = 600;
|
||||
|
||||
/// Get difficulty adjustment information
|
||||
pub fn get_difficulty_adjustment(query: &Query) -> Result<DifficultyAdjustment> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let current_height_u32: u32 = current_height.into();
|
||||
|
||||
// Get current epoch
|
||||
let current_epoch = computer
|
||||
.indexes
|
||||
.height_to_difficultyepoch
|
||||
.read_once(current_height)?;
|
||||
let current_epoch_usize: usize = current_epoch.into();
|
||||
|
||||
// Get epoch start height
|
||||
let epoch_start_height = computer
|
||||
.indexes
|
||||
.difficultyepoch_to_first_height
|
||||
.read_once(current_epoch)?;
|
||||
let epoch_start_u32: u32 = epoch_start_height.into();
|
||||
|
||||
// Calculate epoch progress
|
||||
let next_retarget_height = epoch_start_u32 + BLOCKS_PER_EPOCH;
|
||||
let blocks_into_epoch = current_height_u32 - epoch_start_u32;
|
||||
let remaining_blocks = next_retarget_height - current_height_u32;
|
||||
let progress_percent = (blocks_into_epoch as f64 / BLOCKS_PER_EPOCH as f64) * 100.0;
|
||||
|
||||
// Get timestamps using difficultyepoch_to_timestamp for epoch start
|
||||
let epoch_start_timestamp = computer
|
||||
.chain
|
||||
.difficultyepoch_to_timestamp
|
||||
.read_once(current_epoch)?;
|
||||
let current_timestamp = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_timestamp
|
||||
.read_once(current_height)?;
|
||||
|
||||
// Calculate average block time in current epoch
|
||||
let elapsed_time = (*current_timestamp - *epoch_start_timestamp) as u64;
|
||||
let time_avg = if blocks_into_epoch > 0 {
|
||||
elapsed_time / blocks_into_epoch as u64
|
||||
} else {
|
||||
TARGET_BLOCK_TIME
|
||||
};
|
||||
|
||||
// Estimate remaining time and retarget date
|
||||
let remaining_time = remaining_blocks as u64 * time_avg;
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(*current_timestamp as u64);
|
||||
let estimated_retarget_date = now + remaining_time;
|
||||
|
||||
// Calculate expected vs actual time for difficulty change estimate
|
||||
let expected_time = blocks_into_epoch as u64 * TARGET_BLOCK_TIME;
|
||||
let difficulty_change = if elapsed_time > 0 && blocks_into_epoch > 0 {
|
||||
((expected_time as f64 / elapsed_time as f64) - 1.0) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Time offset from expected schedule
|
||||
let time_offset = expected_time as i64 - elapsed_time as i64;
|
||||
|
||||
// Calculate previous retarget using stored difficulty values
|
||||
let previous_retarget = if current_epoch_usize > 0 {
|
||||
let prev_epoch = DifficultyEpoch::from(current_epoch_usize - 1);
|
||||
let prev_epoch_start = computer
|
||||
.indexes
|
||||
.difficultyepoch_to_first_height
|
||||
.read_once(prev_epoch)?;
|
||||
|
||||
let prev_difficulty = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_difficulty
|
||||
.read_once(prev_epoch_start)?;
|
||||
let curr_difficulty = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_difficulty
|
||||
.read_once(epoch_start_height)?;
|
||||
|
||||
if *prev_difficulty > 0.0 {
|
||||
((*curr_difficulty / *prev_difficulty) - 1.0) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
Ok(DifficultyAdjustment {
|
||||
progress_percent,
|
||||
difficulty_change,
|
||||
estimated_retarget_date,
|
||||
remaining_blocks,
|
||||
remaining_time,
|
||||
previous_retarget,
|
||||
next_retarget_height: Height::from(next_retarget_height),
|
||||
time_avg,
|
||||
adjusted_time_avg: time_avg,
|
||||
time_offset,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod difficulty;
|
||||
|
||||
pub use difficulty::*;
|
||||
@@ -1,9 +1,11 @@
|
||||
mod addr;
|
||||
mod block;
|
||||
mod mempool;
|
||||
mod mining;
|
||||
mod tx;
|
||||
|
||||
pub use addr::*;
|
||||
pub use block::*;
|
||||
pub use mempool::*;
|
||||
pub use mining::*;
|
||||
pub use tx::*;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod hex;
|
||||
mod outspend;
|
||||
mod status;
|
||||
mod tx;
|
||||
|
||||
pub use hex::*;
|
||||
pub use outspend::*;
|
||||
pub use status::*;
|
||||
pub use tx::*;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{TxInIndex, TxOutspend, TxStatus, Txid, TxidPath, TxidPrefix, Vin, Vout};
|
||||
use vecdb::{GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get the spend status of a specific output
|
||||
pub fn get_tx_outspend(
|
||||
TxidPath { txid }: TxidPath,
|
||||
vout: Vout,
|
||||
query: &Query,
|
||||
) -> Result<TxOutspend> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
|
||||
let txid = Txid::from(txid);
|
||||
|
||||
// Mempool outputs are unspent in on-chain terms
|
||||
if let Some(mempool) = query.mempool()
|
||||
&& mempool.get_txs().contains_key(&txid)
|
||||
{
|
||||
return Ok(TxOutspend::UNSPENT);
|
||||
}
|
||||
|
||||
// Look up confirmed transaction
|
||||
let prefix = TxidPrefix::from(&txid);
|
||||
let indexer = query.indexer();
|
||||
let Ok(Some(txindex)) = indexer
|
||||
.stores
|
||||
.txidprefix_to_txindex
|
||||
.get(&prefix)
|
||||
.map(|opt| opt.map(|cow| cow.into_owned()))
|
||||
else {
|
||||
return Err(Error::UnknownTxid);
|
||||
};
|
||||
|
||||
// Calculate txoutindex
|
||||
let first_txoutindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_first_txoutindex
|
||||
.read_once(txindex)?;
|
||||
let txoutindex = first_txoutindex + vout;
|
||||
|
||||
// Look up spend status
|
||||
let computer = query.computer();
|
||||
let txinindex = computer.stateful.txoutindex_to_txinindex.read_once(txoutindex)?;
|
||||
|
||||
if txinindex == TxInIndex::UNSPENT {
|
||||
return Ok(TxOutspend::UNSPENT);
|
||||
}
|
||||
|
||||
get_outspend_details(txinindex, query)
|
||||
}
|
||||
|
||||
/// Get the spend status of all outputs in a transaction
|
||||
pub fn get_tx_outspends(TxidPath { txid }: TxidPath, query: &Query) -> Result<Vec<TxOutspend>> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
|
||||
let txid = Txid::from(txid);
|
||||
|
||||
// Mempool outputs are unspent in on-chain terms
|
||||
if let Some(mempool) = query.mempool()
|
||||
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
|
||||
{
|
||||
let output_count = tx_with_hex.tx().output.len();
|
||||
return Ok(vec![TxOutspend::UNSPENT; output_count]);
|
||||
}
|
||||
|
||||
// Look up confirmed transaction
|
||||
let prefix = TxidPrefix::from(&txid);
|
||||
let indexer = query.indexer();
|
||||
let Ok(Some(txindex)) = indexer
|
||||
.stores
|
||||
.txidprefix_to_txindex
|
||||
.get(&prefix)
|
||||
.map(|opt| opt.map(|cow| cow.into_owned()))
|
||||
else {
|
||||
return Err(Error::UnknownTxid);
|
||||
};
|
||||
|
||||
// Get output range
|
||||
let first_txoutindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_first_txoutindex
|
||||
.read_once(txindex)?;
|
||||
let next_first_txoutindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_first_txoutindex
|
||||
.read_once(txindex.incremented())?;
|
||||
let output_count = usize::from(next_first_txoutindex) - usize::from(first_txoutindex);
|
||||
|
||||
// Get spend status for each output
|
||||
let computer = query.computer();
|
||||
let mut txoutindex_to_txinindex_iter = computer.stateful.txoutindex_to_txinindex.iter()?;
|
||||
|
||||
let mut outspends = Vec::with_capacity(output_count);
|
||||
for i in 0..output_count {
|
||||
let txoutindex = first_txoutindex + Vout::from(i);
|
||||
let txinindex = txoutindex_to_txinindex_iter.get_unwrap(txoutindex);
|
||||
|
||||
if txinindex == TxInIndex::UNSPENT {
|
||||
outspends.push(TxOutspend::UNSPENT);
|
||||
} else {
|
||||
outspends.push(get_outspend_details(txinindex, query)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(outspends)
|
||||
}
|
||||
|
||||
/// Get spending transaction details from a txinindex
|
||||
fn get_outspend_details(txinindex: TxInIndex, query: &Query) -> Result<TxOutspend> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
|
||||
// Look up spending txindex directly
|
||||
let spending_txindex = computer.indexes.txinindex_to_txindex.read_once(txinindex)?;
|
||||
|
||||
// Calculate vin
|
||||
let spending_first_txinindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_first_txinindex
|
||||
.read_once(spending_txindex)?;
|
||||
let vin = Vin::from(usize::from(txinindex) - usize::from(spending_first_txinindex));
|
||||
|
||||
// Get spending tx details
|
||||
let spending_txid = indexer.vecs.tx.txindex_to_txid.read_once(spending_txindex)?;
|
||||
let spending_height = indexer.vecs.tx.txindex_to_height.read_once(spending_txindex)?;
|
||||
let block_hash = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.read_once(spending_height)?;
|
||||
let block_time = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_timestamp
|
||||
.read_once(spending_height)?;
|
||||
|
||||
Ok(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),
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -6,12 +6,13 @@ use std::{collections::BTreeMap, sync::Arc};
|
||||
use brk_computer::Computer;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_indexer::Indexer;
|
||||
use brk_monitor::Mempool;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_traversable::TreeNode;
|
||||
use brk_types::{
|
||||
Address, AddressStats, BlockInfo, BlockStatus, Format, Height, Index, IndexInfo, Limit,
|
||||
MempoolInfo, Metric, MetricCount, RecommendedFees, Transaction, TxStatus, Txid, TxidPath, Utxo,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, Format, Height, Index,
|
||||
IndexInfo, Limit, MempoolInfo, Metric, MetricCount, RecommendedFees, Timestamp, Transaction,
|
||||
TxOutspend, TxStatus, Txid, TxidPath, Utxo, Vout,
|
||||
};
|
||||
use vecdb::{AnyExportableVec, AnyStoredVec};
|
||||
|
||||
@@ -31,12 +32,16 @@ pub use pagination::{PaginatedIndexParam, PaginatedMetrics, PaginationParam};
|
||||
pub use params::{Params, ParamsDeprec, ParamsOpt};
|
||||
use vecs::Vecs;
|
||||
|
||||
pub use crate::chain::BLOCK_TXS_PAGE_SIZE;
|
||||
pub use crate::chain::validate_address;
|
||||
use crate::{
|
||||
chain::{
|
||||
get_address, get_address_txids, get_address_utxos, get_block_by_height,
|
||||
get_block_status_by_height, get_block_txids, get_blocks, get_height_by_hash,
|
||||
get_mempool_info, get_mempool_txids, get_recommended_fees, get_transaction,
|
||||
get_transaction_hex, get_transaction_status,
|
||||
get_address, get_address_mempool_txids, get_address_txids, get_address_utxos,
|
||||
get_block_by_height, get_block_by_timestamp, get_block_raw, get_block_status_by_height,
|
||||
get_block_txid_at_index, get_block_txids, get_block_txs, get_blocks,
|
||||
get_difficulty_adjustment, get_height_by_hash, get_mempool_blocks, get_mempool_info,
|
||||
get_mempool_txids, get_recommended_fees, get_transaction, get_transaction_hex,
|
||||
get_transaction_status, get_tx_outspend, get_tx_outspends,
|
||||
},
|
||||
vecs::{IndexToVec, MetricToVec},
|
||||
};
|
||||
@@ -93,6 +98,10 @@ impl Query {
|
||||
get_address_utxos(address, self)
|
||||
}
|
||||
|
||||
pub fn get_address_mempool_txids(&self, address: Address) -> Result<Vec<Txid>> {
|
||||
get_address_mempool_txids(address, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction(&self, txid: TxidPath) -> Result<Transaction> {
|
||||
get_transaction(txid, self)
|
||||
}
|
||||
@@ -105,6 +114,14 @@ impl Query {
|
||||
get_transaction_hex(txid, self)
|
||||
}
|
||||
|
||||
pub fn get_tx_outspend(&self, txid: TxidPath, vout: Vout) -> Result<TxOutspend> {
|
||||
get_tx_outspend(txid, vout, self)
|
||||
}
|
||||
|
||||
pub fn get_tx_outspends(&self, txid: TxidPath) -> Result<Vec<TxOutspend>> {
|
||||
get_tx_outspends(txid, self)
|
||||
}
|
||||
|
||||
pub fn get_block(&self, hash: &str) -> Result<BlockInfo> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_by_height(height, self)
|
||||
@@ -114,6 +131,10 @@ impl Query {
|
||||
get_block_by_height(height, self)
|
||||
}
|
||||
|
||||
pub fn get_block_by_timestamp(&self, timestamp: Timestamp) -> Result<BlockTimestamp> {
|
||||
get_block_by_timestamp(timestamp, self)
|
||||
}
|
||||
|
||||
pub fn get_block_status(&self, hash: &str) -> Result<BlockStatus> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_status_by_height(height, self)
|
||||
@@ -128,6 +149,21 @@ impl Query {
|
||||
get_block_txids(height, self)
|
||||
}
|
||||
|
||||
pub fn get_block_txs(&self, hash: &str, start_index: usize) -> Result<Vec<Transaction>> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_txs(height, start_index, self)
|
||||
}
|
||||
|
||||
pub fn get_block_txid_at_index(&self, hash: &str, index: usize) -> Result<Txid> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_txid_at_index(height, index, self)
|
||||
}
|
||||
|
||||
pub fn get_block_raw(&self, hash: &str) -> Result<Vec<u8>> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_raw(height, self)
|
||||
}
|
||||
|
||||
pub fn get_mempool_info(&self) -> Result<MempoolInfo> {
|
||||
get_mempool_info(self)
|
||||
}
|
||||
@@ -140,6 +176,14 @@ impl Query {
|
||||
get_recommended_fees(self)
|
||||
}
|
||||
|
||||
pub fn get_mempool_blocks(&self) -> Result<Vec<brk_types::MempoolBlock>> {
|
||||
get_mempool_blocks(self)
|
||||
}
|
||||
|
||||
pub fn get_difficulty_adjustment(&self) -> Result<brk_types::DifficultyAdjustment> {
|
||||
get_difficulty_adjustment(self)
|
||||
}
|
||||
|
||||
pub fn match_metric(&self, metric: &Metric, limit: Limit) -> Vec<&'static str> {
|
||||
self.vecs().matches(metric, limit)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user