mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-05 16:18:13 -07:00
query: more optimizations
This commit is contained in:
@@ -4,8 +4,8 @@ use bitcoin::{Network, PublicKey, ScriptBuf};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{
|
||||
Addr, AddrBytes, AddrChainStats, AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, AddrStats,
|
||||
AnyAddrDataIndexEnum, Height, OutputType, Transaction, TxIndex, TxStatus, Txid, TypeIndex,
|
||||
Unit, Utxo, Vout,
|
||||
AnyAddrDataIndexEnum, BlockHash, Height, OutputType, Timestamp, Transaction, TxIndex, TxStatus,
|
||||
Txid, TypeIndex, Unit, Utxo, Vout,
|
||||
};
|
||||
use vecdb::{ReadableVec, VecIndex};
|
||||
|
||||
@@ -131,33 +131,33 @@ impl Query {
|
||||
.get(output_type)
|
||||
.unwrap();
|
||||
|
||||
let prefix = u32::from(type_index).to_be_bytes();
|
||||
|
||||
let after_tx_index = if let Some(after_txid) = after_txid {
|
||||
let tx_index = stores
|
||||
if let Some(after_txid) = after_txid {
|
||||
let after_tx_index = stores
|
||||
.txid_prefix_to_tx_index
|
||||
.get(&after_txid.into())
|
||||
.map_err(|_| Error::UnknownTxid)?
|
||||
.ok_or(Error::UnknownTxid)?
|
||||
.into_owned();
|
||||
Some(tx_index)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(store
|
||||
.prefix(prefix)
|
||||
.rev()
|
||||
.filter(|(key, _): &(AddrIndexTxIndex, Unit)| {
|
||||
if let Some(after) = after_tx_index {
|
||||
key.tx_index() < after
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.take(limit)
|
||||
.map(|(key, _)| key.tx_index())
|
||||
.collect())
|
||||
// Seek directly to after_tx_index and iterate backward — O(limit)
|
||||
let min = AddrIndexTxIndex::min_for_addr(type_index);
|
||||
let bound = AddrIndexTxIndex::from((type_index, after_tx_index));
|
||||
Ok(store
|
||||
.range(min..bound)
|
||||
.rev()
|
||||
.take(limit)
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.collect())
|
||||
} else {
|
||||
// No pagination — scan from end of prefix
|
||||
let prefix = u32::from(type_index).to_be_bytes();
|
||||
Ok(store
|
||||
.prefix(prefix)
|
||||
.rev()
|
||||
.take(limit)
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_utxos(&self, addr: Addr) -> Result<Vec<Utxo>> {
|
||||
@@ -186,30 +186,38 @@ impl Query {
|
||||
let mut height_cursor = vecs.transactions.height.cursor();
|
||||
let mut block_ts_cursor = vecs.blocks.timestamp.cursor();
|
||||
|
||||
let utxos: Vec<Utxo> = outpoints
|
||||
.into_iter()
|
||||
.map(|(tx_index, vout)| {
|
||||
let txid = txid_reader.get(tx_index.to_usize());
|
||||
let height = height_cursor.get(tx_index.to_usize()).unwrap();
|
||||
let first_txout_index = first_txout_index_reader.get(tx_index.to_usize());
|
||||
let txout_index = first_txout_index + vout;
|
||||
let value = value_reader.get(usize::from(txout_index));
|
||||
let block_hash = blockhash_reader.get(usize::from(height));
|
||||
let block_time = block_ts_cursor.get(height.to_usize()).unwrap();
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
let mut utxos = Vec::with_capacity(outpoints.len());
|
||||
|
||||
Utxo {
|
||||
txid,
|
||||
vout,
|
||||
status: TxStatus {
|
||||
confirmed: true,
|
||||
block_height: Some(height),
|
||||
block_hash: Some(block_hash),
|
||||
block_time: Some(block_time),
|
||||
},
|
||||
value,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for (tx_index, vout) in outpoints {
|
||||
let txid = txid_reader.get(tx_index.to_usize());
|
||||
let height: Height = height_cursor.get(tx_index.to_usize()).unwrap();
|
||||
let first_txout_index = first_txout_index_reader.get(tx_index.to_usize());
|
||||
let value = value_reader.get(usize::from(first_txout_index + vout));
|
||||
|
||||
let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_block
|
||||
&& h == height
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = blockhash_reader.get(height.to_usize());
|
||||
let bt = block_ts_cursor.get(height.to_usize()).unwrap();
|
||||
cached_block = Some((height, bh.clone(), bt));
|
||||
(bh, bt)
|
||||
};
|
||||
|
||||
utxos.push(Utxo {
|
||||
txid,
|
||||
vout,
|
||||
status: TxStatus {
|
||||
confirmed: true,
|
||||
block_height: Some(height),
|
||||
block_hash: Some(block_hash),
|
||||
block_time: Some(block_time),
|
||||
},
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(utxos)
|
||||
}
|
||||
|
||||
@@ -288,7 +288,6 @@ impl Query {
|
||||
let varint_len = Self::compact_size_len(tx_count);
|
||||
let coinbase_offset = HEADER_SIZE as u32 + varint_len;
|
||||
let coinbase_pos = positions[i] + coinbase_offset;
|
||||
let coinbase_read_len = size as usize - coinbase_offset as usize;
|
||||
|
||||
let (
|
||||
coinbase_raw,
|
||||
@@ -297,7 +296,7 @@ impl Query {
|
||||
coinbase_signature,
|
||||
coinbase_signature_ascii,
|
||||
scriptsig_bytes,
|
||||
) = Self::parse_coinbase_tx(reader, coinbase_pos, coinbase_read_len);
|
||||
) = Self::parse_coinbase_tx(reader, coinbase_pos);
|
||||
|
||||
let miner_names = if pool_slug == PoolSlug::Ocean {
|
||||
Self::parse_datum_miner_names(&scriptsig_bytes)
|
||||
@@ -514,15 +513,14 @@ impl Query {
|
||||
fn parse_coinbase_tx(
|
||||
reader: &Reader,
|
||||
position: BlkPosition,
|
||||
len: usize,
|
||||
) -> (String, Option<String>, Vec<String>, String, String, Vec<u8>) {
|
||||
let empty = (String::new(), None, vec![], String::new(), String::new(), vec![]);
|
||||
let raw_bytes = match reader.read_raw_bytes(position, len) {
|
||||
Ok(bytes) => bytes,
|
||||
let blk_reader = match reader.reader_at(position) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return empty,
|
||||
};
|
||||
|
||||
let tx = match bitcoin::Transaction::consensus_decode(&mut raw_bytes.as_slice()) {
|
||||
let tx = match bitcoin::Transaction::consensus_decode(&mut bitcoin::io::FromStd::new(blk_reader)) {
|
||||
Ok(tx) => tx,
|
||||
Err(_) => return empty,
|
||||
};
|
||||
|
||||
@@ -31,14 +31,14 @@ impl Query {
|
||||
|
||||
let start: usize = usize::from(first_height_of_day).min(max_height_usize);
|
||||
|
||||
let timestamps = &indexer.vecs.blocks.timestamp;
|
||||
let mut ts_cursor = indexer.vecs.blocks.timestamp.cursor();
|
||||
|
||||
// Search forward from start to find the last block <= target timestamp
|
||||
let mut best_height = start;
|
||||
let mut best_ts = timestamps.collect_one_at(start).unwrap();
|
||||
let mut best_ts = ts_cursor.get(start).unwrap();
|
||||
|
||||
for h in (start + 1)..=max_height_usize {
|
||||
let block_ts = timestamps.collect_one_at(h).unwrap();
|
||||
let block_ts = ts_cursor.get(h).unwrap();
|
||||
if block_ts <= target {
|
||||
best_height = h;
|
||||
best_ts = block_ts;
|
||||
@@ -49,7 +49,7 @@ impl Query {
|
||||
|
||||
// Check one block before start in case we need to go backward
|
||||
if start > 0 && best_ts > target {
|
||||
let prev_ts = timestamps.collect_one_at(start - 1).unwrap();
|
||||
let prev_ts = ts_cursor.get(start - 1).unwrap();
|
||||
if prev_ts <= target {
|
||||
best_height = start - 1;
|
||||
best_ts = prev_ts;
|
||||
|
||||
@@ -91,6 +91,8 @@ impl Query {
|
||||
let output_type_reader = indexer.vecs.outputs.output_type.reader();
|
||||
let type_index_reader = indexer.vecs.outputs.type_index.reader();
|
||||
let addr_readers = indexer.vecs.addrs.addr_readers();
|
||||
let blockhash_reader = indexer.vecs.blocks.blockhash.reader();
|
||||
let mut block_ts_cursor = indexer.vecs.blocks.timestamp.cursor();
|
||||
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
|
||||
@@ -113,8 +115,8 @@ impl Query {
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = indexer.vecs.blocks.blockhash.read_once(height)?;
|
||||
let bt = indexer.vecs.blocks.timestamp.collect_one(height).unwrap();
|
||||
let bh = blockhash_reader.get(height.to_usize());
|
||||
let bt = block_ts_cursor.get(height.to_usize()).unwrap();
|
||||
cached_block = Some((height, bh.clone(), bt));
|
||||
(bh, bt)
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use brk_computer::Computer;
|
||||
use brk_types::{DifficultyAdjustmentEntry, Epoch, Height};
|
||||
use brk_types::{DifficultyAdjustmentEntry, Height};
|
||||
use vecdb::{ReadableVec, Ro, VecIndex};
|
||||
|
||||
/// Iterate over difficulty epochs within a height range.
|
||||
@@ -21,25 +21,24 @@ pub fn iter_difficulty_epochs(
|
||||
.collect_one(Height::from(end_height))
|
||||
.unwrap_or_default();
|
||||
|
||||
let epoch_to_height = &computer.indexes.epoch.first_height;
|
||||
let epoch_to_timestamp = &computer.indexes.timestamp.epoch;
|
||||
let epoch_to_difficulty = &computer.blocks.difficulty.value.epoch;
|
||||
let mut height_cursor = computer.indexes.epoch.first_height.cursor();
|
||||
let mut timestamp_cursor = computer.indexes.timestamp.epoch.cursor();
|
||||
let mut difficulty_cursor = computer.blocks.difficulty.value.epoch.cursor();
|
||||
|
||||
let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1);
|
||||
let mut prev_difficulty: Option<f64> = None;
|
||||
|
||||
for epoch_usize in start_epoch.to_usize()..=end_epoch.to_usize() {
|
||||
let epoch = Epoch::from(epoch_usize);
|
||||
let epoch_height = epoch_to_height.collect_one(epoch).unwrap_or_default();
|
||||
let epoch_height = height_cursor.get(epoch_usize).unwrap_or_default();
|
||||
|
||||
// Skip epochs before our start height but track difficulty
|
||||
if epoch_height.to_usize() < start_height {
|
||||
prev_difficulty = epoch_to_difficulty.collect_one(epoch).map(|d| *d);
|
||||
prev_difficulty = difficulty_cursor.get(epoch_usize).map(|d| *d);
|
||||
continue;
|
||||
}
|
||||
|
||||
let epoch_timestamp = epoch_to_timestamp.collect_one(epoch).unwrap_or_default();
|
||||
let epoch_difficulty = *epoch_to_difficulty.collect_one(epoch).unwrap_or_default();
|
||||
let epoch_timestamp = timestamp_cursor.get(epoch_usize).unwrap_or_default();
|
||||
let epoch_difficulty = *difficulty_cursor.get(epoch_usize).unwrap_or_default();
|
||||
|
||||
let change_percent = match prev_difficulty {
|
||||
Some(prev) if prev > 0.0 => epoch_difficulty / prev,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Day1, DifficultyEntry, HashrateEntry, HashrateSummary, Height, TimePeriod};
|
||||
use brk_types::{DifficultyEntry, HashrateEntry, HashrateSummary, Height, TimePeriod};
|
||||
use vecdb::{ReadableOptionVec, ReadableVec, VecIndex};
|
||||
|
||||
use super::epochs::iter_difficulty_epochs;
|
||||
@@ -56,16 +56,15 @@ impl Query {
|
||||
let total_days = end_day1.to_usize().saturating_sub(start_day1.to_usize()) + 1;
|
||||
let step = (total_days / 200).max(1); // Max ~200 data points
|
||||
|
||||
let hashrate_vec = &computer.mining.hashrate.rate.base.day1;
|
||||
let timestamp_vec = &computer.indexes.timestamp.day1;
|
||||
let mut hr_cursor = computer.mining.hashrate.rate.base.day1.cursor();
|
||||
let mut ts_cursor = computer.indexes.timestamp.day1.cursor();
|
||||
|
||||
let mut hashrates = Vec::with_capacity(total_days / step + 1);
|
||||
let mut di = start_day1.to_usize();
|
||||
while di <= end_day1.to_usize() {
|
||||
let day1 = Day1::from(di);
|
||||
if let (Some(hr), Some(timestamp)) = (
|
||||
hashrate_vec.collect_one_flat(day1),
|
||||
timestamp_vec.collect_one(day1),
|
||||
if let (Some(Some(hr)), Some(timestamp)) = (
|
||||
hr_cursor.get(di),
|
||||
ts_cursor.get(di),
|
||||
) {
|
||||
hashrates.push(HashrateEntry {
|
||||
timestamp,
|
||||
|
||||
@@ -53,13 +53,8 @@ impl Query {
|
||||
};
|
||||
|
||||
// Get block info for status
|
||||
let height = indexer
|
||||
.vecs
|
||||
.transactions
|
||||
.height
|
||||
.collect_one(tx_index)
|
||||
.unwrap();
|
||||
let block_hash = indexer.vecs.blocks.blockhash.read_once(height)?;
|
||||
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 {
|
||||
@@ -136,14 +131,13 @@ impl Query {
|
||||
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();
|
||||
|
||||
// Cursors buffer chunks so nearby indices share decompression
|
||||
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();
|
||||
|
||||
// Spending txs in the same block share block hash/time
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
|
||||
let mut outspends = Vec::with_capacity(output_count);
|
||||
@@ -167,7 +161,7 @@ impl Query {
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = indexer.vecs.blocks.blockhash.read_once(spending_height)?;
|
||||
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)
|
||||
@@ -262,7 +256,14 @@ impl Query {
|
||||
status: Some(TxStatus {
|
||||
confirmed: true,
|
||||
block_height: Some(spending_height),
|
||||
block_hash: Some(indexer.vecs.blocks.blockhash.read_once(spending_height)?),
|
||||
block_hash: Some(
|
||||
indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.blockhash
|
||||
.reader()
|
||||
.get(spending_height.to_usize()),
|
||||
),
|
||||
block_time: Some(
|
||||
indexer
|
||||
.vecs
|
||||
|
||||
Reference in New Issue
Block a user