mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 22:59:58 -07:00
server: snapshot
This commit is contained in:
103
crates/brk_query/src/impl/block/info.rs
Normal file
103
crates/brk_query/src/impl/block/info.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockHash, BlockHashPrefix, BlockInfo, Height, TxIndex};
|
||||
use vecdb::{AnyVec, GenericStoredVec, VecIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
const DEFAULT_BLOCK_COUNT: u32 = 10;
|
||||
|
||||
impl Query {
|
||||
pub fn block(&self, hash: &str) -> Result<BlockInfo> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_by_height(height)
|
||||
}
|
||||
|
||||
pub fn block_by_height(&self, height: Height) -> Result<BlockInfo> {
|
||||
let indexer = self.indexer();
|
||||
|
||||
let max_height = self.max_height();
|
||||
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 = self.tx_count_at_height(height, max_height)?;
|
||||
|
||||
Ok(BlockInfo {
|
||||
id: blockhash,
|
||||
height,
|
||||
tx_count,
|
||||
size: *size,
|
||||
weight,
|
||||
timestamp,
|
||||
difficulty: *difficulty,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn blocks(&self, start_height: Option<Height>) -> Result<Vec<BlockInfo>> {
|
||||
let max_height = self.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(self.block_by_height(height)?);
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
// === Helper methods ===
|
||||
|
||||
pub fn height_by_hash(&self, hash: &str) -> Result<Height> {
|
||||
let indexer = self.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"))
|
||||
}
|
||||
|
||||
fn max_height(&self) -> Height {
|
||||
Height::from(
|
||||
self.indexer()
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.len()
|
||||
.saturating_sub(1),
|
||||
)
|
||||
}
|
||||
|
||||
fn tx_count_at_height(&self, height: Height, max_height: Height) -> Result<u32> {
|
||||
let indexer = self.indexer();
|
||||
let computer = self.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)
|
||||
}
|
||||
}
|
||||
7
crates/brk_query/src/impl/block/mod.rs
Normal file
7
crates/brk_query/src/impl/block/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod info;
|
||||
mod raw;
|
||||
mod status;
|
||||
mod timestamp;
|
||||
mod txs;
|
||||
|
||||
pub const BLOCK_TXS_PAGE_SIZE: usize = 25;
|
||||
35
crates/brk_query/src/impl/block/raw.rs
Normal file
35
crates/brk_query/src/impl/block/raw.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::Height;
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn block_raw(&self, hash: &str) -> Result<Vec<u8>> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_raw_by_height(height)
|
||||
}
|
||||
|
||||
fn block_raw_by_height(&self, height: Height) -> Result<Vec<u8>> {
|
||||
let indexer = self.indexer();
|
||||
let computer = self.computer();
|
||||
let reader = self.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)
|
||||
}
|
||||
}
|
||||
43
crates/brk_query/src/impl/block/status.rs
Normal file
43
crates/brk_query/src/impl/block/status.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockStatus, Height};
|
||||
use vecdb::{AnyVec, GenericStoredVec};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn block_status(&self, hash: &str) -> Result<BlockStatus> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_status_by_height(height)
|
||||
}
|
||||
|
||||
fn block_status_by_height(&self, height: Height) -> Result<BlockStatus> {
|
||||
let indexer = self.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))
|
||||
}
|
||||
}
|
||||
81
crates/brk_query/src/impl/block/timestamp.rs
Normal file
81
crates/brk_query/src/impl/block/timestamp.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockTimestamp, Date, DateIndex, Height, Timestamp};
|
||||
use jiff::Timestamp as JiffTimestamp;
|
||||
use vecdb::{GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn block_by_timestamp(&self, timestamp: Timestamp) -> Result<BlockTimestamp> {
|
||||
let indexer = self.indexer();
|
||||
let computer = self.computer();
|
||||
|
||||
let max_height = self.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,
|
||||
})
|
||||
}
|
||||
}
|
||||
128
crates/brk_query/src/impl/block/txs.rs
Normal file
128
crates/brk_query/src/impl/block/txs.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Height, Transaction, TxIndex, Txid};
|
||||
use vecdb::{AnyVec, GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use super::BLOCK_TXS_PAGE_SIZE;
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn block_txids(&self, hash: &str) -> Result<Vec<Txid>> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_txids_by_height(height)
|
||||
}
|
||||
|
||||
pub fn block_txs(&self, hash: &str, start_index: usize) -> Result<Vec<Transaction>> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_txs_by_height(height, start_index)
|
||||
}
|
||||
|
||||
pub fn block_txid_at_index(&self, hash: &str, index: usize) -> Result<Txid> {
|
||||
let height = self.height_by_hash(hash)?;
|
||||
self.block_txid_at_index_by_height(height, index)
|
||||
}
|
||||
|
||||
// === Helper methods ===
|
||||
|
||||
fn block_txids_by_height(&self, height: Height) -> Result<Vec<Txid>> {
|
||||
let indexer = self.indexer();
|
||||
|
||||
let max_height = self.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)
|
||||
}
|
||||
|
||||
fn block_txs_by_height(
|
||||
&self,
|
||||
height: Height,
|
||||
start_index: usize,
|
||||
) -> Result<Vec<Transaction>> {
|
||||
let indexer = self.indexer();
|
||||
|
||||
let max_height = self.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 = self.transaction_by_index(txindex)?;
|
||||
txs.push(tx);
|
||||
}
|
||||
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
fn block_txid_at_index_by_height(&self, height: Height, index: usize) -> Result<Txid> {
|
||||
let indexer = self.indexer();
|
||||
|
||||
let max_height = self.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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user