mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-02 06:48:59 -07:00
global: private xpub support part 1
This commit is contained in:
@@ -1,279 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::{Network, PublicKey, ScriptBuf};
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{
|
||||
Addr, AddrBytes, AddrChainStats, AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, AddrStats,
|
||||
AnyAddrDataIndexEnum, Dollars, Height, OutputType, Transaction, TxIndex, TxStatus, Txid,
|
||||
TypeIndex, Unit, Utxo, Vout,
|
||||
};
|
||||
use vecdb::VecIndex;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn addr(&self, addr: Addr) -> Result<AddrStats> {
|
||||
let computer = self.computer();
|
||||
|
||||
let script = if let Ok(addr) = bitcoin::Address::from_str(&addr) {
|
||||
if !addr.is_valid_for_network(Network::Bitcoin) {
|
||||
return Err(Error::InvalidNetwork);
|
||||
}
|
||||
let addr = addr.assume_checked();
|
||||
addr.script_pubkey()
|
||||
} else if let Ok(pubkey) = PublicKey::from_str(&addr) {
|
||||
ScriptBuf::new_p2pk(&pubkey)
|
||||
} else {
|
||||
return Err(Error::InvalidAddr);
|
||||
};
|
||||
|
||||
let output_type = OutputType::from(&script);
|
||||
let Ok(bytes) = AddrBytes::try_from((&script, output_type)) else {
|
||||
return Err(Error::InvalidAddr);
|
||||
};
|
||||
let hash = AddrHash::from(&bytes);
|
||||
let type_index = self.type_index_for(output_type, &hash)?;
|
||||
|
||||
if type_index >= self.safe_lengths().to_type_index(output_type) {
|
||||
return Err(Error::UnknownAddr);
|
||||
}
|
||||
|
||||
let any_addr_index = computer
|
||||
.distribution
|
||||
.any_addr_indexes
|
||||
.get_once(output_type, type_index)?;
|
||||
|
||||
let (addr_data, realized_price) = match any_addr_index.to_enum() {
|
||||
AnyAddrDataIndexEnum::Funded(index) => {
|
||||
let data = computer
|
||||
.distribution
|
||||
.addrs_data
|
||||
.funded
|
||||
.reader()
|
||||
.get(usize::from(index));
|
||||
let price = data.realized_price().to_dollars();
|
||||
(data, price)
|
||||
}
|
||||
AnyAddrDataIndexEnum::Empty(index) => {
|
||||
let data = computer
|
||||
.distribution
|
||||
.addrs_data
|
||||
.empty
|
||||
.reader()
|
||||
.get(usize::from(index))
|
||||
.into();
|
||||
(data, Dollars::default())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(AddrStats {
|
||||
addr,
|
||||
addr_type: output_type,
|
||||
chain_stats: AddrChainStats {
|
||||
type_index,
|
||||
funded_txo_count: addr_data.funded_txo_count,
|
||||
funded_txo_sum: addr_data.received,
|
||||
spent_txo_count: addr_data.spent_txo_count,
|
||||
spent_txo_sum: addr_data.sent,
|
||||
tx_count: addr_data.tx_count,
|
||||
realized_price,
|
||||
},
|
||||
mempool_stats: self
|
||||
.mempool()
|
||||
.and_then(|m| m.addr_stats(&bytes))
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn addr_txs_chain(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Transaction>> {
|
||||
let txindices = self.addr_txindices(addr, after_txid, limit)?;
|
||||
self.transactions_by_indices(&txindices)
|
||||
}
|
||||
|
||||
pub fn addr_txids(
|
||||
&self,
|
||||
addr: Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Txid>> {
|
||||
let txindices = self.addr_txindices(&addr, after_txid, limit)?;
|
||||
let txid_reader = self.indexer().vecs.transactions.txid.reader();
|
||||
Ok(txindices
|
||||
.into_iter()
|
||||
.map(|tx_index| txid_reader.get(tx_index.to_usize()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn addr_txindices(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<TxIndex>> {
|
||||
let stores = &self.indexer().stores;
|
||||
|
||||
let (output_type, type_index) = self.resolve_addr(addr)?;
|
||||
|
||||
let store = stores
|
||||
.addr_type_to_addr_index_and_tx_index
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
|
||||
if let Some(after_txid) = after_txid {
|
||||
let after_tx_index = self.resolve_tx_index(&after_txid)?;
|
||||
let min = AddrIndexTxIndex::min_for_addr(type_index);
|
||||
let cursor = AddrIndexTxIndex::from((type_index, after_tx_index));
|
||||
Ok(store
|
||||
.range(min..cursor)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.filter(|tx_index| *tx_index < tx_index_len)
|
||||
.take(limit)
|
||||
.collect())
|
||||
} else {
|
||||
Ok(store
|
||||
.prefix(type_index)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.filter(|tx_index| *tx_index < tx_index_len)
|
||||
.take(limit)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addr_utxos(&self, addr: Addr, max_utxos: usize) -> Result<Vec<Utxo>> {
|
||||
let indexer = self.indexer();
|
||||
let stores = &indexer.stores;
|
||||
let vecs = &indexer.vecs;
|
||||
|
||||
let (output_type, type_index) = self.resolve_addr(&addr)?;
|
||||
|
||||
let store = stores
|
||||
.addr_type_to_addr_index_and_unspent_outpoint
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
let outpoints: Vec<(TxIndex, Vout)> = store
|
||||
.prefix(type_index)
|
||||
.map(|(key, _): (AddrIndexOutPoint, Unit)| (key.tx_index(), key.vout()))
|
||||
.filter(|(tx_index, _)| *tx_index < tx_index_len)
|
||||
.take(max_utxos + 1)
|
||||
.collect();
|
||||
if outpoints.len() > max_utxos {
|
||||
return Err(Error::TooManyUtxos);
|
||||
}
|
||||
|
||||
let txid_reader = vecs.transactions.txid.reader();
|
||||
let first_txout_index_reader = vecs.transactions.first_txout_index.reader();
|
||||
let value_reader = vecs.outputs.value.reader();
|
||||
|
||||
let mut cached_status: Option<(Height, TxStatus)> = None;
|
||||
let mut utxos = Vec::with_capacity(outpoints.len());
|
||||
|
||||
for (tx_index, vout) in outpoints {
|
||||
let txid = txid_reader.get(tx_index.to_usize());
|
||||
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 height = self.confirmed_status_height(tx_index)?;
|
||||
let status = if let Some((h, ref s)) = cached_status
|
||||
&& h == height
|
||||
{
|
||||
s.clone()
|
||||
} else {
|
||||
let s = self.confirmed_status_at(height)?;
|
||||
cached_status = Some((height, s.clone()));
|
||||
s
|
||||
};
|
||||
|
||||
utxos.push(Utxo {
|
||||
txid,
|
||||
vout,
|
||||
status,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(utxos)
|
||||
}
|
||||
|
||||
pub fn addr_mempool_hash(&self, addr: &Addr) -> Option<u64> {
|
||||
let mempool = self.mempool()?;
|
||||
let bytes = AddrBytes::from_str(addr).ok()?;
|
||||
mempool.addr_state_hash(&bytes)
|
||||
}
|
||||
|
||||
pub fn addr_mempool_txs(&self, addr: &Addr, limit: usize) -> Result<Vec<Transaction>> {
|
||||
let bytes = AddrBytes::from_str(addr)?;
|
||||
let mempool = self.mempool().ok_or(Error::MempoolNotAvailable)?;
|
||||
Ok(mempool.addr_txs(&bytes, limit))
|
||||
}
|
||||
|
||||
/// Height of the last on-chain activity for an address (last tx_index to height).
|
||||
/// With `before_txid`, returns the newest activity strictly older than that
|
||||
/// cursor. Used by paginated chain etags so a new tx above the cursor
|
||||
/// doesn't invalidate deeper pages.
|
||||
pub fn addr_last_activity_height(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
before_txid: Option<&Txid>,
|
||||
) -> Result<Height> {
|
||||
let (output_type, type_index) = self.resolve_addr(addr)?;
|
||||
let store = self
|
||||
.indexer()
|
||||
.stores
|
||||
.addr_type_to_addr_index_and_tx_index
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
let last_tx_index = match before_txid {
|
||||
Some(txid) => {
|
||||
let before_tx_index = self.resolve_tx_index(txid)?;
|
||||
let min = AddrIndexTxIndex::min_for_addr(type_index);
|
||||
let cursor = AddrIndexTxIndex::from((type_index, before_tx_index));
|
||||
store
|
||||
.range(min..cursor)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.find(|tx_index| *tx_index < tx_index_len)
|
||||
.ok_or(Error::UnknownAddr)?
|
||||
}
|
||||
None => store
|
||||
.prefix(type_index)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.find(|tx_index| *tx_index < tx_index_len)
|
||||
.ok_or(Error::UnknownAddr)?,
|
||||
};
|
||||
self.confirmed_status_height(last_tx_index)
|
||||
}
|
||||
|
||||
fn resolve_addr(&self, addr: &Addr) -> Result<(OutputType, TypeIndex)> {
|
||||
let bytes = AddrBytes::from_str(addr)?;
|
||||
let output_type = OutputType::from(&bytes);
|
||||
let hash = AddrHash::from(&bytes);
|
||||
let type_index = self.type_index_for(output_type, &hash)?;
|
||||
Ok((output_type, type_index))
|
||||
}
|
||||
|
||||
/// Lookup the per-type index of an address by `(output_type, hash)`.
|
||||
/// Returns `UnknownAddr` if the hash is absent from the type's index.
|
||||
fn type_index_for(&self, output_type: OutputType, hash: &AddrHash) -> Result<TypeIndex> {
|
||||
self.indexer()
|
||||
.stores
|
||||
.addr_type_to_addr_hash_to_addr_index
|
||||
.get(output_type)
|
||||
.data()?
|
||||
.get(hash)?
|
||||
.map(|cow| cow.into_owned())
|
||||
.ok_or(Error::UnknownAddr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{Addr, AddrIndexTxIndex, Height, Txid, Unit};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
/// Height of the last on-chain activity for an address (last tx_index to height).
|
||||
/// With `before_txid`, returns the newest activity strictly older than that
|
||||
/// cursor. Used by paginated chain etags so a new tx above the cursor
|
||||
/// doesn't invalidate deeper pages.
|
||||
pub fn addr_last_activity_height(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
before_txid: Option<&Txid>,
|
||||
) -> Result<Height> {
|
||||
let (output_type, type_index) = self.resolve_addr(addr)?;
|
||||
let store = self
|
||||
.indexer()
|
||||
.stores
|
||||
.addr_type_to_addr_index_and_tx_index
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
let last_tx_index = match before_txid {
|
||||
Some(txid) => {
|
||||
let before_tx_index = self.resolve_tx_index(txid)?;
|
||||
let min = AddrIndexTxIndex::min_for_addr(type_index);
|
||||
let cursor = AddrIndexTxIndex::from((type_index, before_tx_index));
|
||||
store
|
||||
.range(min..cursor)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.find(|tx_index| *tx_index < tx_index_len)
|
||||
.ok_or(Error::UnknownAddr)?
|
||||
}
|
||||
None => store
|
||||
.prefix(type_index)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.find(|tx_index| *tx_index < tx_index_len)
|
||||
.ok_or(Error::UnknownAddr)?,
|
||||
};
|
||||
self.confirmed_status_height(last_tx_index)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{Addr, AddrHash, AddrHashPrefixMatches, OutputType};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
const ADDR_HASH_PREFIX_MATCH_LIMIT: usize = 100;
|
||||
|
||||
impl Query {
|
||||
pub fn addr_hash_prefix_matches(
|
||||
&self,
|
||||
addr_type: OutputType,
|
||||
prefix: &str,
|
||||
) -> Result<AddrHashPrefixMatches> {
|
||||
if !addr_type.is_addr() {
|
||||
return Err(Error::UnsupportedType(addr_type.to_string()));
|
||||
}
|
||||
|
||||
let prefix = AddrHashPrefix::parse(prefix)?;
|
||||
let store = self
|
||||
.indexer()
|
||||
.stores
|
||||
.addr_type_to_addr_hash_to_addr_index
|
||||
.get(addr_type)
|
||||
.data()?;
|
||||
let safe_type_index = self.safe_lengths().to_type_index(addr_type);
|
||||
let addr_readers = self.indexer().vecs.addrs.addr_readers();
|
||||
let mut addresses = Vec::new();
|
||||
let max_hash = AddrHash::new(u64::MAX);
|
||||
|
||||
if let Some(upper) = prefix.upper {
|
||||
for (_, type_index) in store.range(prefix.lower..upper) {
|
||||
if type_index >= safe_type_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
let script = addr_readers.script_pubkey(addr_type, type_index);
|
||||
addresses.push(Addr::try_from((&script, addr_type))?);
|
||||
|
||||
if addresses.len() > ADDR_HASH_PREFIX_MATCH_LIMIT {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_, type_index) in store.range(prefix.lower..max_hash) {
|
||||
if type_index >= safe_type_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
let script = addr_readers.script_pubkey(addr_type, type_index);
|
||||
addresses.push(Addr::try_from((&script, addr_type))?);
|
||||
|
||||
if addresses.len() > ADDR_HASH_PREFIX_MATCH_LIMIT {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if addresses.len() <= ADDR_HASH_PREFIX_MATCH_LIMIT
|
||||
&& let Some(type_index) = store.get(&max_hash)?.map(|cow| cow.into_owned())
|
||||
&& type_index < safe_type_index
|
||||
{
|
||||
let script = addr_readers.script_pubkey(addr_type, type_index);
|
||||
addresses.push(Addr::try_from((&script, addr_type))?);
|
||||
}
|
||||
}
|
||||
|
||||
let truncated = addresses.len() > ADDR_HASH_PREFIX_MATCH_LIMIT;
|
||||
addresses.truncate(ADDR_HASH_PREFIX_MATCH_LIMIT);
|
||||
|
||||
Ok(AddrHashPrefixMatches {
|
||||
addr_type,
|
||||
prefix: prefix.text,
|
||||
truncated,
|
||||
addresses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct AddrHashPrefix {
|
||||
text: String,
|
||||
lower: AddrHash,
|
||||
upper: Option<AddrHash>,
|
||||
}
|
||||
|
||||
impl AddrHashPrefix {
|
||||
const MAX_NIBBLES: usize = u64::BITS as usize / 4;
|
||||
|
||||
fn parse(prefix: &str) -> Result<Self> {
|
||||
let nibbles = prefix.len();
|
||||
if !(1..=Self::MAX_NIBBLES).contains(&nibbles) {
|
||||
return Err(Self::parse_error());
|
||||
}
|
||||
|
||||
let value = u64::from_str_radix(prefix, 16).map_err(|_| Self::parse_error())?;
|
||||
let shift = (Self::MAX_NIBBLES - nibbles) * 4;
|
||||
let factor = 1_u64 << shift;
|
||||
let lower = value * factor;
|
||||
let upper = value
|
||||
.checked_add(1)
|
||||
.and_then(|value| value.checked_mul(factor))
|
||||
.map(AddrHash::new);
|
||||
|
||||
Ok(Self {
|
||||
text: prefix.to_ascii_lowercase(),
|
||||
lower: AddrHash::new(lower),
|
||||
upper,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_error() -> Error {
|
||||
Error::Parse(format!(
|
||||
"hash prefix must be 1 to {} hexadecimal characters",
|
||||
Self::MAX_NIBBLES
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{Addr, AddrBytes, Transaction};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn addr_mempool_hash(&self, addr: &Addr) -> Option<u64> {
|
||||
let mempool = self.mempool()?;
|
||||
let bytes = AddrBytes::from_str(addr).ok()?;
|
||||
mempool.addr_state_hash(&bytes)
|
||||
}
|
||||
|
||||
pub fn addr_mempool_txs(&self, addr: &Addr, limit: usize) -> Result<Vec<Transaction>> {
|
||||
let bytes = AddrBytes::from_str(addr)?;
|
||||
let mempool = self.mempool().ok_or(Error::MempoolNotAvailable)?;
|
||||
Ok(mempool.addr_txs(&bytes, limit))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod activity;
|
||||
mod hash_prefix;
|
||||
mod mempool;
|
||||
mod resolve;
|
||||
mod stats;
|
||||
mod txs;
|
||||
mod utxos;
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{Addr, AddrBytes, AddrHash, OutputType, TypeIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub(super) fn resolve_addr(&self, addr: &Addr) -> Result<(OutputType, TypeIndex)> {
|
||||
let bytes = AddrBytes::from_str(addr)?;
|
||||
let output_type = OutputType::from(&bytes);
|
||||
let hash = AddrHash::from(&bytes);
|
||||
let type_index = self.type_index_for(output_type, &hash)?;
|
||||
Ok((output_type, type_index))
|
||||
}
|
||||
|
||||
/// Lookup the per-type index of an address by `(output_type, hash)`.
|
||||
/// Returns `UnknownAddr` if the hash is absent from the type's index.
|
||||
pub(super) fn type_index_for(
|
||||
&self,
|
||||
output_type: OutputType,
|
||||
hash: &AddrHash,
|
||||
) -> Result<TypeIndex> {
|
||||
self.indexer()
|
||||
.stores
|
||||
.addr_type_to_addr_hash_to_addr_index
|
||||
.get(output_type)
|
||||
.data()?
|
||||
.get(hash)?
|
||||
.map(|cow| cow.into_owned())
|
||||
.ok_or(Error::UnknownAddr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{
|
||||
Addr, AddrBytes, AddrChainStats, AddrHash, AddrStats, AnyAddrDataIndexEnum, Dollars,
|
||||
OutputType, TypeIndex,
|
||||
};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn addr(&self, addr: Addr) -> Result<AddrStats> {
|
||||
let bytes = AddrBytes::from_str(&addr)?;
|
||||
let output_type = OutputType::from(&bytes);
|
||||
let hash = AddrHash::from(&bytes);
|
||||
let type_index = self.type_index_for(output_type, &hash)?;
|
||||
self.addr_stats(addr, bytes, output_type, type_index)
|
||||
}
|
||||
|
||||
fn addr_stats(
|
||||
&self,
|
||||
addr: Addr,
|
||||
bytes: AddrBytes,
|
||||
output_type: OutputType,
|
||||
type_index: TypeIndex,
|
||||
) -> Result<AddrStats> {
|
||||
if type_index >= self.safe_lengths().to_type_index(output_type) {
|
||||
return Err(Error::UnknownAddr);
|
||||
}
|
||||
|
||||
let computer = self.computer();
|
||||
let any_addr_index = computer
|
||||
.distribution
|
||||
.any_addr_indexes
|
||||
.get_once(output_type, type_index)?;
|
||||
|
||||
let (addr_data, realized_price) = match any_addr_index.to_enum() {
|
||||
AnyAddrDataIndexEnum::Funded(index) => {
|
||||
let data = computer
|
||||
.distribution
|
||||
.addrs_data
|
||||
.funded
|
||||
.reader()
|
||||
.get(usize::from(index));
|
||||
let price = data.realized_price().to_dollars();
|
||||
(data, price)
|
||||
}
|
||||
AnyAddrDataIndexEnum::Empty(index) => {
|
||||
let data = computer
|
||||
.distribution
|
||||
.addrs_data
|
||||
.empty
|
||||
.reader()
|
||||
.get(usize::from(index))
|
||||
.into();
|
||||
(data, Dollars::default())
|
||||
}
|
||||
};
|
||||
|
||||
Ok(AddrStats {
|
||||
addr,
|
||||
addr_type: output_type,
|
||||
chain_stats: AddrChainStats {
|
||||
type_index,
|
||||
funded_txo_count: addr_data.funded_txo_count,
|
||||
funded_txo_sum: addr_data.received,
|
||||
spent_txo_count: addr_data.spent_txo_count,
|
||||
spent_txo_sum: addr_data.sent,
|
||||
tx_count: addr_data.tx_count,
|
||||
realized_price,
|
||||
},
|
||||
mempool_stats: self
|
||||
.mempool()
|
||||
.and_then(|m| m.addr_stats(&bytes))
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use brk_error::{OptionData, Result};
|
||||
use brk_types::{Addr, AddrIndexTxIndex, Transaction, TxIndex, Txid, Unit};
|
||||
use vecdb::VecIndex;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn addr_txs_chain(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Transaction>> {
|
||||
let txindices = self.addr_txindices(addr, after_txid, limit)?;
|
||||
self.transactions_by_indices(&txindices)
|
||||
}
|
||||
|
||||
pub fn addr_txids(
|
||||
&self,
|
||||
addr: Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Txid>> {
|
||||
let txindices = self.addr_txindices(&addr, after_txid, limit)?;
|
||||
let txid_reader = self.indexer().vecs.transactions.txid.reader();
|
||||
Ok(txindices
|
||||
.into_iter()
|
||||
.map(|tx_index| txid_reader.get(tx_index.to_usize()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn addr_txindices(
|
||||
&self,
|
||||
addr: &Addr,
|
||||
after_txid: Option<Txid>,
|
||||
limit: usize,
|
||||
) -> Result<Vec<TxIndex>> {
|
||||
let stores = &self.indexer().stores;
|
||||
|
||||
let (output_type, type_index) = self.resolve_addr(addr)?;
|
||||
|
||||
let store = stores
|
||||
.addr_type_to_addr_index_and_tx_index
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
|
||||
if let Some(after_txid) = after_txid {
|
||||
let after_tx_index = self.resolve_tx_index(&after_txid)?;
|
||||
let min = AddrIndexTxIndex::min_for_addr(type_index);
|
||||
let cursor = AddrIndexTxIndex::from((type_index, after_tx_index));
|
||||
Ok(store
|
||||
.range(min..cursor)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.filter(|tx_index| *tx_index < tx_index_len)
|
||||
.take(limit)
|
||||
.collect())
|
||||
} else {
|
||||
Ok(store
|
||||
.prefix(type_index)
|
||||
.rev()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.filter(|tx_index| *tx_index < tx_index_len)
|
||||
.take(limit)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{Addr, AddrIndexOutPoint, Height, TxIndex, TxStatus, Unit, Utxo, Vout};
|
||||
use vecdb::VecIndex;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn addr_utxos(&self, addr: Addr, max_utxos: usize) -> Result<Vec<Utxo>> {
|
||||
let indexer = self.indexer();
|
||||
let stores = &indexer.stores;
|
||||
let vecs = &indexer.vecs;
|
||||
|
||||
let (output_type, type_index) = self.resolve_addr(&addr)?;
|
||||
|
||||
let store = stores
|
||||
.addr_type_to_addr_index_and_unspent_outpoint
|
||||
.get(output_type)
|
||||
.data()?;
|
||||
|
||||
let tx_index_len = self.safe_lengths().tx_index;
|
||||
let outpoints: Vec<(TxIndex, Vout)> = store
|
||||
.prefix(type_index)
|
||||
.map(|(key, _): (AddrIndexOutPoint, Unit)| (key.tx_index(), key.vout()))
|
||||
.filter(|(tx_index, _)| *tx_index < tx_index_len)
|
||||
.take(max_utxos + 1)
|
||||
.collect();
|
||||
if outpoints.len() > max_utxos {
|
||||
return Err(Error::TooManyUtxos);
|
||||
}
|
||||
|
||||
let txid_reader = vecs.transactions.txid.reader();
|
||||
let first_txout_index_reader = vecs.transactions.first_txout_index.reader();
|
||||
let value_reader = vecs.outputs.value.reader();
|
||||
|
||||
let mut cached_status: Option<(Height, TxStatus)> = None;
|
||||
let mut utxos = Vec::with_capacity(outpoints.len());
|
||||
|
||||
for (tx_index, vout) in outpoints {
|
||||
let txid = txid_reader.get(tx_index.to_usize());
|
||||
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 height = self.confirmed_status_height(tx_index)?;
|
||||
let status = if let Some((h, ref s)) = cached_status
|
||||
&& h == height
|
||||
{
|
||||
s.clone()
|
||||
} else {
|
||||
let s = self.confirmed_status_at(height)?;
|
||||
cached_status = Some((height, s.clone()));
|
||||
s
|
||||
};
|
||||
|
||||
utxos.push(Utxo {
|
||||
txid,
|
||||
vout,
|
||||
status,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(utxos)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user