mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-27 10:48:11 -07:00
global: snapshot
This commit is contained in:
@@ -6,13 +6,13 @@ use brk_indexer::Indexer;
|
||||
use brk_monitor::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_types::{
|
||||
Address, AddressStats, Height, Index, IndexInfo, Limit, Metric, MetricCount, Transaction,
|
||||
TreeNode, TxidPath,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, Height, Index, IndexInfo, Limit, MempoolInfo,
|
||||
Metric, MetricCount, Transaction, TreeNode, TxStatus, Txid, TxidPath, Utxo,
|
||||
};
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use crate::{
|
||||
Output, PaginatedIndexParam, PaginatedMetrics, PaginationParam, Params, ParamsOpt, Query,
|
||||
Output, PaginatedIndexParam, PaginatedMetrics, PaginationParam, Params, Query,
|
||||
vecs::{IndexToVec, MetricToVec, Vecs},
|
||||
};
|
||||
|
||||
@@ -42,11 +42,71 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_address(address)).await?
|
||||
}
|
||||
|
||||
pub async fn get_address_txids(
|
||||
&self,
|
||||
address: Address,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Txid>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_address_txids(address, after_txid, limit)).await?
|
||||
}
|
||||
|
||||
pub async fn get_address_utxos(&self, address: Address) -> Result<Vec<Utxo>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_address_utxos(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?
|
||||
}
|
||||
|
||||
pub async fn get_transaction_status(&self, txid: TxidPath) -> Result<TxStatus> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_transaction_status(txid)).await?
|
||||
}
|
||||
|
||||
pub async fn get_transaction_hex(&self, txid: TxidPath) -> Result<String> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_transaction_hex(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?
|
||||
}
|
||||
|
||||
pub async fn get_block_by_height(&self, height: Height) -> Result<BlockInfo> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_by_height(height)).await?
|
||||
}
|
||||
|
||||
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?
|
||||
}
|
||||
|
||||
pub async fn get_blocks(&self, start_height: Option<Height>) -> Result<Vec<BlockInfo>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_blocks(start_height)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_txids(&self, hash: String) -> Result<Vec<Txid>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_txids(&hash)).await?
|
||||
}
|
||||
|
||||
pub async fn get_mempool_info(&self) -> Result<MempoolInfo> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_mempool_info()).await?
|
||||
}
|
||||
|
||||
pub async fn get_mempool_txids(&self) -> Result<Vec<Txid>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_mempool_txids()).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,8 @@
|
||||
mod addr;
|
||||
mod resolve;
|
||||
mod txids;
|
||||
mod utxos;
|
||||
|
||||
pub use addr::*;
|
||||
pub use txids::*;
|
||||
pub use utxos::*;
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Address, AddressBytes, AddressHash, OutputType, TypeIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Resolve an address string to its output type and type_index
|
||||
pub fn resolve_address(address: &Address, query: &Query) -> Result<(OutputType, TypeIndex)> {
|
||||
let stores = &query.indexer().stores;
|
||||
|
||||
let bytes = AddressBytes::from_str(&address.address)?;
|
||||
let outputtype = OutputType::from(&bytes);
|
||||
let hash = AddressHash::from(&bytes);
|
||||
|
||||
let Ok(Some(type_index)) = stores
|
||||
.addresstype_to_addresshash_to_addressindex
|
||||
.get(outputtype)
|
||||
.unwrap()
|
||||
.get(&hash)
|
||||
.map(|opt| opt.map(|cow| cow.into_owned()))
|
||||
else {
|
||||
return Err(Error::UnknownAddress);
|
||||
};
|
||||
|
||||
Ok((outputtype, type_index))
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Address, AddressIndexTxIndex, TxIndex, Txid, Unit};
|
||||
use vecdb::TypedVecIterator;
|
||||
|
||||
use super::resolve::resolve_address;
|
||||
use crate::Query;
|
||||
|
||||
/// Get transaction IDs for an address, newest first
|
||||
pub fn get_address_txids(
|
||||
address: Address,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
query: &Query,
|
||||
) -> Result<Vec<Txid>> {
|
||||
let indexer = query.indexer();
|
||||
let stores = &indexer.stores;
|
||||
|
||||
let (outputtype, type_index) = resolve_address(&address, query)?;
|
||||
|
||||
let store = stores
|
||||
.addresstype_to_addressindex_and_txindex
|
||||
.get(outputtype)
|
||||
.unwrap();
|
||||
|
||||
let prefix = u32::from(type_index).to_be_bytes();
|
||||
|
||||
let after_txindex = if let Some(after_txid) = after_txid {
|
||||
let txindex = stores
|
||||
.txidprefix_to_txindex
|
||||
.get(&after_txid.into())
|
||||
.map_err(|_| Error::Str("Failed to look up after_txid"))?
|
||||
.ok_or(Error::Str("after_txid not found"))?
|
||||
.into_owned();
|
||||
Some(txindex)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let txindices: Vec<TxIndex> = store
|
||||
.prefix(prefix)
|
||||
.rev()
|
||||
.filter(|(key, _): &(AddressIndexTxIndex, Unit)| {
|
||||
if let Some(after) = after_txindex {
|
||||
TxIndex::from(key.txindex()) < after
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.take(limit)
|
||||
.map(|(key, _)| TxIndex::from(key.txindex()))
|
||||
.collect();
|
||||
|
||||
let mut txindex_to_txid_iter = indexer.vecs.tx.txindex_to_txid.iter()?;
|
||||
let txids: Vec<Txid> = txindices
|
||||
.into_iter()
|
||||
.map(|txindex| txindex_to_txid_iter.get_unwrap(txindex))
|
||||
.collect();
|
||||
|
||||
Ok(txids)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{
|
||||
Address, AddressIndexOutPoint, Sats, TxIndex, TxStatus, Txid, Unit, Utxo, Vout,
|
||||
};
|
||||
use vecdb::TypedVecIterator;
|
||||
|
||||
use super::resolve::resolve_address;
|
||||
use crate::Query;
|
||||
|
||||
/// Get UTXOs for an address
|
||||
pub fn get_address_utxos(address: Address, query: &Query) -> Result<Vec<Utxo>> {
|
||||
let indexer = query.indexer();
|
||||
let stores = &indexer.stores;
|
||||
let vecs = &indexer.vecs;
|
||||
|
||||
let (outputtype, type_index) = resolve_address(&address, query)?;
|
||||
|
||||
let store = stores
|
||||
.addresstype_to_addressindex_and_unspentoutpoint
|
||||
.get(outputtype)
|
||||
.unwrap();
|
||||
|
||||
let prefix = u32::from(type_index).to_be_bytes();
|
||||
|
||||
// Collect outpoints (txindex, vout)
|
||||
let outpoints: Vec<(TxIndex, Vout)> = store
|
||||
.prefix(prefix)
|
||||
.map(|(key, _): (AddressIndexOutPoint, Unit)| (key.txindex(), key.vout()))
|
||||
.collect();
|
||||
|
||||
// Create iterators for looking up tx data
|
||||
let mut txindex_to_txid_iter = vecs.tx.txindex_to_txid.iter()?;
|
||||
let mut txindex_to_height_iter = vecs.tx.txindex_to_height.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter = vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txoutindex_to_value_iter = vecs.txout.txoutindex_to_value.iter()?;
|
||||
let mut height_to_blockhash_iter = vecs.block.height_to_blockhash.iter()?;
|
||||
let mut height_to_timestamp_iter = vecs.block.height_to_timestamp.iter()?;
|
||||
|
||||
let utxos: Vec<Utxo> = outpoints
|
||||
.into_iter()
|
||||
.map(|(txindex, vout)| {
|
||||
let txid: Txid = txindex_to_txid_iter.get_unwrap(txindex);
|
||||
let height = txindex_to_height_iter.get_unwrap(txindex);
|
||||
let first_txoutindex = txindex_to_first_txoutindex_iter.get_unwrap(txindex);
|
||||
let txoutindex = first_txoutindex + vout;
|
||||
let value: Sats = txoutindex_to_value_iter.get_unwrap(txoutindex);
|
||||
let block_hash = height_to_blockhash_iter.get_unwrap(height);
|
||||
let block_time = height_to_timestamp_iter.get_unwrap(height);
|
||||
|
||||
Utxo {
|
||||
txid,
|
||||
vout,
|
||||
status: TxStatus {
|
||||
confirmed: true,
|
||||
block_height: Some(height),
|
||||
block_hash: Some(block_hash),
|
||||
block_time: Some(block_time),
|
||||
},
|
||||
value,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(utxos)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockHash, BlockHashPrefix, Height};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Resolve a block hash to height
|
||||
pub fn get_height_by_hash(hash: &str, query: &Query) -> Result<Height> {
|
||||
let indexer = query.indexer();
|
||||
|
||||
let blockhash: BlockHash = hash.parse().map_err(|_| Error::Str("Invalid block hash"))?;
|
||||
let prefix = BlockHashPrefix::from(&blockhash);
|
||||
|
||||
indexer
|
||||
.stores
|
||||
.blockhashprefix_to_height
|
||||
.get(&prefix)?
|
||||
.map(|h| *h)
|
||||
.ok_or(Error::Str("Block not found"))
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockInfo, Height, TxIndex};
|
||||
use vecdb::{AnyVec, GenericStoredVec, VecIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get block info by height
|
||||
pub fn get_block_by_height(height: Height, query: &Query) -> Result<BlockInfo> {
|
||||
let indexer = query.indexer();
|
||||
|
||||
let max_height = max_height(query);
|
||||
if height > max_height {
|
||||
return Err(Error::Str("Block height out of range"));
|
||||
}
|
||||
|
||||
let blockhash = indexer.vecs.block.height_to_blockhash.read_once(height)?;
|
||||
let difficulty = indexer.vecs.block.height_to_difficulty.read_once(height)?;
|
||||
let timestamp = indexer.vecs.block.height_to_timestamp.read_once(height)?;
|
||||
let size = indexer.vecs.block.height_to_total_size.read_once(height)?;
|
||||
let weight = indexer.vecs.block.height_to_weight.read_once(height)?;
|
||||
let tx_count = tx_count_at_height(height, max_height, query)?;
|
||||
|
||||
Ok(BlockInfo {
|
||||
id: blockhash,
|
||||
height,
|
||||
tx_count,
|
||||
size: *size,
|
||||
weight,
|
||||
timestamp,
|
||||
difficulty: *difficulty,
|
||||
})
|
||||
}
|
||||
|
||||
fn max_height(query: &Query) -> Height {
|
||||
Height::from(
|
||||
query
|
||||
.indexer()
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.len()
|
||||
.saturating_sub(1),
|
||||
)
|
||||
}
|
||||
|
||||
fn tx_count_at_height(height: Height, max_height: Height, query: &Query) -> Result<u32> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
|
||||
let first_txindex = indexer.vecs.tx.height_to_first_txindex.read_once(height)?;
|
||||
let next_first_txindex = if height < max_height {
|
||||
indexer
|
||||
.vecs
|
||||
.tx
|
||||
.height_to_first_txindex
|
||||
.read_once(height.incremented())?
|
||||
} else {
|
||||
TxIndex::from(computer.indexes.txindex_to_txindex.len())
|
||||
};
|
||||
|
||||
Ok((next_first_txindex.to_usize() - first_txindex.to_usize()) as u32)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockInfo, Height};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
use super::info::get_block_by_height;
|
||||
|
||||
const DEFAULT_BLOCK_COUNT: u32 = 10;
|
||||
|
||||
/// Get a list of blocks, optionally starting from a specific height
|
||||
pub fn get_blocks(start_height: Option<Height>, query: &Query) -> Result<Vec<BlockInfo>> {
|
||||
let max_height = query.get_height();
|
||||
|
||||
let start = start_height.unwrap_or(max_height);
|
||||
let start = start.min(max_height);
|
||||
|
||||
let start_u32: u32 = start.into();
|
||||
let count = DEFAULT_BLOCK_COUNT.min(start_u32 + 1);
|
||||
|
||||
let mut blocks = Vec::with_capacity(count as usize);
|
||||
for i in 0..count {
|
||||
let height = Height::from(start_u32 - i);
|
||||
blocks.push(get_block_by_height(height, query)?);
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod height_by_hash;
|
||||
mod info;
|
||||
mod list;
|
||||
mod status;
|
||||
mod txids;
|
||||
|
||||
pub use height_by_hash::*;
|
||||
pub use info::*;
|
||||
pub use list::*;
|
||||
pub use status::*;
|
||||
pub use txids::*;
|
||||
@@ -0,0 +1,37 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockStatus, Height};
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get block status by height
|
||||
pub fn get_block_status_by_height(height: Height, query: &Query) -> Result<BlockStatus> {
|
||||
let indexer = query.indexer();
|
||||
|
||||
let max_height = Height::from(
|
||||
indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.len()
|
||||
.saturating_sub(1),
|
||||
);
|
||||
|
||||
if height > max_height {
|
||||
return Ok(BlockStatus::not_in_best_chain());
|
||||
}
|
||||
|
||||
let next_best = if height < max_height {
|
||||
Some(
|
||||
indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.read_once(height.incremented())?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(BlockStatus::in_best_chain(height, next_best))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Height, TxIndex, Txid};
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get all txids in a block by height
|
||||
pub fn get_block_txids(height: Height, query: &Query) -> Result<Vec<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 count = next - first;
|
||||
|
||||
let txids: Vec<Txid> = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_txid
|
||||
.iter()?
|
||||
.skip(first)
|
||||
.take(count)
|
||||
.collect();
|
||||
|
||||
Ok(txids)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::MempoolInfo;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get mempool statistics
|
||||
pub fn get_mempool_info(query: &Query) -> Result<MempoolInfo> {
|
||||
let mempool = query.mempool().ok_or(Error::Str("Mempool not available"))?;
|
||||
Ok(mempool.get_info())
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod info;
|
||||
mod txids;
|
||||
|
||||
pub use info::*;
|
||||
pub use txids::*;
|
||||
@@ -0,0 +1,11 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::Txid;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get all mempool transaction IDs
|
||||
pub fn get_mempool_txids(query: &Query) -> Result<Vec<Txid>> {
|
||||
let mempool = query.mempool().ok_or(Error::Str("Mempool not available"))?;
|
||||
let txs = mempool.get_txs();
|
||||
Ok(txs.keys().cloned().collect())
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
mod addresses;
|
||||
mod transactions;
|
||||
mod addr;
|
||||
mod block;
|
||||
mod mempool;
|
||||
mod tx;
|
||||
|
||||
pub use addresses::*;
|
||||
pub use transactions::*;
|
||||
pub use addr::*;
|
||||
pub use block::*;
|
||||
pub use mempool::*;
|
||||
pub use tx::*;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::hex::DisplayHex;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{TxIndex, Txid, TxidPath, TxidPrefix};
|
||||
use vecdb::GenericStoredVec;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_transaction_hex(TxidPath { txid }: TxidPath, query: &Query) -> Result<String> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
|
||||
let txid = Txid::from(txid);
|
||||
|
||||
// First check mempool for unconfirmed transactions
|
||||
if let Some(mempool) = query.mempool()
|
||||
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
|
||||
{
|
||||
return Ok(tx_with_hex.hex().to_string());
|
||||
}
|
||||
|
||||
// Look up confirmed transaction by txid prefix
|
||||
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_transaction_hex_by_index(txindex, query)
|
||||
}
|
||||
|
||||
pub fn get_transaction_hex_by_index(txindex: TxIndex, query: &Query) -> Result<String> {
|
||||
let indexer = query.indexer();
|
||||
let reader = query.reader();
|
||||
let computer = query.computer();
|
||||
|
||||
let total_size = indexer.vecs.tx.txindex_to_total_size.read_once(txindex)?;
|
||||
let position = computer.blks.txindex_to_position.read_once(txindex)?;
|
||||
|
||||
let buffer = reader.read_raw_bytes(position, *total_size as usize)?;
|
||||
|
||||
Ok(buffer.to_lower_hex_string())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod hex;
|
||||
mod status;
|
||||
mod tx;
|
||||
|
||||
pub use hex::*;
|
||||
pub use status::*;
|
||||
pub use tx::*;
|
||||
@@ -0,0 +1,46 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{TxStatus, Txid, TxidPath, TxidPrefix};
|
||||
use vecdb::GenericStoredVec;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_transaction_status(TxidPath { txid }: TxidPath, query: &Query) -> Result<TxStatus> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
|
||||
let txid = Txid::from(txid);
|
||||
|
||||
// First check mempool for unconfirmed transactions
|
||||
if let Some(mempool) = query.mempool()
|
||||
&& mempool.get_txs().contains_key(&txid)
|
||||
{
|
||||
return Ok(TxStatus::UNCONFIRMED);
|
||||
}
|
||||
|
||||
// Look up confirmed transaction by txid prefix
|
||||
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 block info for status
|
||||
let height = indexer.vecs.tx.txindex_to_height.read_once(txindex)?;
|
||||
let block_hash = indexer.vecs.block.height_to_blockhash.read_once(height)?;
|
||||
let block_time = indexer.vecs.block.height_to_timestamp.read_once(height)?;
|
||||
|
||||
Ok(TxStatus {
|
||||
confirmed: true,
|
||||
block_height: Some(height),
|
||||
block_hash: Some(block_hash),
|
||||
block_time: Some(block_time),
|
||||
})
|
||||
}
|
||||
@@ -1,12 +1,7 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Cursor, Read, Seek, SeekFrom},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{io::Cursor, str::FromStr};
|
||||
|
||||
use bitcoin::consensus::Decodable;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_reader::XORIndex;
|
||||
use brk_types::{
|
||||
Sats, Transaction, TxIn, TxIndex, TxOut, TxStatus, Txid, TxidPath, TxidPrefix, Vout, Weight,
|
||||
};
|
||||
@@ -20,6 +15,15 @@ pub fn get_transaction(TxidPath { txid }: TxidPath, query: &Query) -> Result<Tra
|
||||
};
|
||||
|
||||
let txid = Txid::from(txid);
|
||||
|
||||
// First check mempool for unconfirmed transactions
|
||||
if let Some(mempool) = query.mempool()
|
||||
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
|
||||
{
|
||||
return Ok(tx_with_hex.tx().clone());
|
||||
}
|
||||
|
||||
// Look up confirmed transaction by txid prefix
|
||||
let prefix = TxidPrefix::from(&txid);
|
||||
let indexer = query.indexer();
|
||||
let Ok(Some(txindex)) = indexer
|
||||
@@ -45,7 +49,11 @@ pub fn get_transaction_by_index(txindex: TxIndex, query: &Query) -> Result<Trans
|
||||
let version = indexer.vecs.tx.txindex_to_txversion.read_once(txindex)?;
|
||||
let lock_time = indexer.vecs.tx.txindex_to_rawlocktime.read_once(txindex)?;
|
||||
let total_size = indexer.vecs.tx.txindex_to_total_size.read_once(txindex)?;
|
||||
let first_txinindex = indexer.vecs.tx.txindex_to_first_txinindex.read_once(txindex)?;
|
||||
let first_txinindex = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_first_txinindex
|
||||
.read_once(txindex)?;
|
||||
let position = computer.blks.txindex_to_position.read_once(txindex)?;
|
||||
|
||||
// Get block info for status
|
||||
@@ -53,39 +61,15 @@ pub fn get_transaction_by_index(txindex: TxIndex, query: &Query) -> Result<Trans
|
||||
let block_time = indexer.vecs.block.height_to_timestamp.read_once(height)?;
|
||||
|
||||
// Read and decode the raw transaction from blk file
|
||||
let blk_index_to_blk_path = reader.blk_index_to_blk_path();
|
||||
let Some(blk_path) = blk_index_to_blk_path.get(&position.blk_index()) else {
|
||||
return Err(Error::Str("Failed to get the correct blk file"));
|
||||
};
|
||||
|
||||
let mut xori = XORIndex::default();
|
||||
xori.add_assign(position.offset() as usize);
|
||||
|
||||
let Ok(mut file) = File::open(blk_path) else {
|
||||
return Err(Error::Str("Failed to open blk file"));
|
||||
};
|
||||
|
||||
if file
|
||||
.seek(SeekFrom::Start(position.offset() as u64))
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::Str("Failed to seek position in file"));
|
||||
}
|
||||
|
||||
let mut buffer = vec![0u8; *total_size as usize];
|
||||
if file.read_exact(&mut buffer).is_err() {
|
||||
return Err(Error::Str("Failed to read the transaction (read exact)"));
|
||||
}
|
||||
xori.bytes(&mut buffer, reader.xor_bytes());
|
||||
|
||||
let buffer = reader.read_raw_bytes(position, *total_size as usize)?;
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
let Ok(tx) = bitcoin::Transaction::consensus_decode(&mut cursor) else {
|
||||
return Err(Error::Str("Failed decode the transaction"));
|
||||
};
|
||||
let tx = bitcoin::Transaction::consensus_decode(&mut cursor)
|
||||
.map_err(|_| Error::Str("Failed to decode transaction"))?;
|
||||
|
||||
// For iterating through inputs, we need iterators (multiple lookups)
|
||||
let mut txindex_to_txid_iter = indexer.vecs.tx.txindex_to_txid.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter = indexer.vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txindex_to_first_txoutindex_iter =
|
||||
indexer.vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txinindex_to_outpoint_iter = indexer.vecs.txin.txinindex_to_outpoint.iter()?;
|
||||
let mut txoutindex_to_value_iter = indexer.vecs.txout.txoutindex_to_value.iter()?;
|
||||
|
||||
@@ -144,6 +128,12 @@ pub fn get_transaction_by_index(txindex: TxIndex, query: &Query) -> Result<Trans
|
||||
// Calculate weight before consuming tx.output
|
||||
let weight = Weight::from(tx.weight());
|
||||
|
||||
// Calculate sigop cost
|
||||
// Note: Using |_| None means P2SH and SegWit sigops won't be counted accurately
|
||||
// since we don't provide the prevout scripts. This matches mempool tx behavior.
|
||||
// For accurate counting, we'd need to reconstruct prevout scripts from indexed data.
|
||||
let total_sigop_cost = tx.total_sigop_cost(|_| None);
|
||||
|
||||
// Build outputs
|
||||
let output: Vec<TxOut> = tx.output.into_iter().map(TxOut::from).collect();
|
||||
|
||||
@@ -162,8 +152,8 @@ pub fn get_transaction_by_index(txindex: TxIndex, query: &Query) -> Result<Trans
|
||||
lock_time,
|
||||
total_size: *total_size as usize,
|
||||
weight,
|
||||
total_sigop_cost: 0, // Would need to calculate from scripts
|
||||
fee: Sats::ZERO, // Will be computed below
|
||||
total_sigop_cost,
|
||||
fee: Sats::ZERO, // Will be computed below
|
||||
input,
|
||||
output,
|
||||
status,
|
||||
@@ -1,4 +1,5 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
@@ -9,8 +10,8 @@ use brk_monitor::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_traversable::TreeNode;
|
||||
use brk_types::{
|
||||
Address, AddressStats, Format, Height, Index, IndexInfo, Limit, Metric, MetricCount,
|
||||
Transaction, TxidPath,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, Format, Height, Index, IndexInfo, Limit,
|
||||
MempoolInfo, Metric, MetricCount, Transaction, TxStatus, Txid, TxidPath, Utxo,
|
||||
};
|
||||
use vecdb::{AnyExportableVec, AnyStoredVec};
|
||||
|
||||
@@ -31,7 +32,12 @@ pub use params::{Params, ParamsDeprec, ParamsOpt};
|
||||
use vecs::Vecs;
|
||||
|
||||
use crate::{
|
||||
chain::{get_address, get_transaction},
|
||||
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_transaction, get_transaction_hex,
|
||||
get_transaction_status,
|
||||
},
|
||||
vecs::{IndexToVec, MetricToVec},
|
||||
};
|
||||
|
||||
@@ -74,10 +80,62 @@ impl Query {
|
||||
get_address(address, self)
|
||||
}
|
||||
|
||||
pub fn get_address_txids(
|
||||
&self,
|
||||
address: Address,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Txid>> {
|
||||
get_address_txids(address, after_txid, limit, self)
|
||||
}
|
||||
|
||||
pub fn get_address_utxos(&self, address: Address) -> Result<Vec<Utxo>> {
|
||||
get_address_utxos(address, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction(&self, txid: TxidPath) -> Result<Transaction> {
|
||||
get_transaction(txid, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction_status(&self, txid: TxidPath) -> Result<TxStatus> {
|
||||
get_transaction_status(txid, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction_hex(&self, txid: TxidPath) -> Result<String> {
|
||||
get_transaction_hex(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)
|
||||
}
|
||||
|
||||
pub fn get_block_by_height(&self, height: Height) -> Result<BlockInfo> {
|
||||
get_block_by_height(height, 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)
|
||||
}
|
||||
|
||||
pub fn get_blocks(&self, start_height: Option<Height>) -> Result<Vec<BlockInfo>> {
|
||||
get_blocks(start_height, self)
|
||||
}
|
||||
|
||||
pub fn get_block_txids(&self, hash: &str) -> Result<Vec<Txid>> {
|
||||
let height = get_height_by_hash(hash, self)?;
|
||||
get_block_txids(height, self)
|
||||
}
|
||||
|
||||
pub fn get_mempool_info(&self) -> Result<MempoolInfo> {
|
||||
get_mempool_info(self)
|
||||
}
|
||||
|
||||
pub fn get_mempool_txids(&self) -> Result<Vec<Txid>> {
|
||||
get_mempool_txids(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