mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 22:34:46 -07:00
global: sigops
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -483,6 +483,7 @@ dependencies = [
|
|||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
"smallvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"vecdb",
|
"vecdb",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3871,6 +3871,7 @@ pub struct SeriesTree_Transactions_Raw {
|
|||||||
pub raw_locktime: SeriesPattern19<RawLockTime>,
|
pub raw_locktime: SeriesPattern19<RawLockTime>,
|
||||||
pub base_size: SeriesPattern19<StoredU32>,
|
pub base_size: SeriesPattern19<StoredU32>,
|
||||||
pub total_size: SeriesPattern19<StoredU32>,
|
pub total_size: SeriesPattern19<StoredU32>,
|
||||||
|
pub total_sigop_cost: SeriesPattern19<SigOps>,
|
||||||
pub is_explicitly_rbf: SeriesPattern19<StoredBool>,
|
pub is_explicitly_rbf: SeriesPattern19<StoredBool>,
|
||||||
pub first_txin_index: SeriesPattern19<TxInIndex>,
|
pub first_txin_index: SeriesPattern19<TxInIndex>,
|
||||||
pub first_txout_index: SeriesPattern19<TxOutIndex>,
|
pub first_txout_index: SeriesPattern19<TxOutIndex>,
|
||||||
@@ -3885,6 +3886,7 @@ impl SeriesTree_Transactions_Raw {
|
|||||||
raw_locktime: SeriesPattern19::new(client.clone(), "raw_locktime".to_string()),
|
raw_locktime: SeriesPattern19::new(client.clone(), "raw_locktime".to_string()),
|
||||||
base_size: SeriesPattern19::new(client.clone(), "base_size".to_string()),
|
base_size: SeriesPattern19::new(client.clone(), "base_size".to_string()),
|
||||||
total_size: SeriesPattern19::new(client.clone(), "total_size".to_string()),
|
total_size: SeriesPattern19::new(client.clone(), "total_size".to_string()),
|
||||||
|
total_sigop_cost: SeriesPattern19::new(client.clone(), "total_sigop_cost".to_string()),
|
||||||
is_explicitly_rbf: SeriesPattern19::new(client.clone(), "is_explicitly_rbf".to_string()),
|
is_explicitly_rbf: SeriesPattern19::new(client.clone(), "is_explicitly_rbf".to_string()),
|
||||||
first_txin_index: SeriesPattern19::new(client.clone(), "first_txin_index".to_string()),
|
first_txin_index: SeriesPattern19::new(client.clone(), "first_txin_index".to_string()),
|
||||||
first_txout_index: SeriesPattern19::new(client.clone(), "first_txout_index".to_string()),
|
first_txout_index: SeriesPattern19::new(client.clone(), "first_txout_index".to_string()),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ serde = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
smallvec = { workspace = true }
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -309,10 +309,13 @@ impl Indexer {
|
|||||||
|
|
||||||
processor.check_txid_collisions(&txs)?;
|
processor.check_txid_collisions(&txs)?;
|
||||||
|
|
||||||
|
let sigops = processor.compute_sigops(&txins);
|
||||||
|
|
||||||
processor.finalize_and_store_metadata(
|
processor.finalize_and_store_metadata(
|
||||||
txs,
|
txs,
|
||||||
txouts,
|
txouts,
|
||||||
txins,
|
txins,
|
||||||
|
sigops,
|
||||||
&buffers.same_block_spent,
|
&buffers.same_block_spent,
|
||||||
&mut buffers.already_added_addrs,
|
&mut buffers.already_added_addrs,
|
||||||
&mut buffers.same_block_output_info,
|
&mut buffers.same_block_output_info,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod metadata;
|
mod metadata;
|
||||||
|
mod sigops;
|
||||||
mod tx;
|
mod tx;
|
||||||
mod txin;
|
mod txin;
|
||||||
mod txout;
|
mod txout;
|
||||||
@@ -8,7 +9,9 @@ pub use types::*;
|
|||||||
|
|
||||||
use brk_cohort::ByAddrType;
|
use brk_cohort::ByAddrType;
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{AddrHash, Block, Height, OutPoint, TxInIndex, TxIndex, TxOutIndex, TypeIndex};
|
use brk_types::{
|
||||||
|
AddrHash, Block, Height, OutPoint, SigOps, TxInIndex, TxIndex, TxOutIndex, TypeIndex,
|
||||||
|
};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::{Indexes, Readers, Stores, Vecs};
|
use crate::{Indexes, Readers, Stores, Vecs};
|
||||||
@@ -39,6 +42,7 @@ impl BlockProcessor<'_> {
|
|||||||
txs: Vec<ComputedTx>,
|
txs: Vec<ComputedTx>,
|
||||||
txouts: Vec<ProcessedOutput>,
|
txouts: Vec<ProcessedOutput>,
|
||||||
txins: Vec<(TxInIndex, InputSource)>,
|
txins: Vec<(TxInIndex, InputSource)>,
|
||||||
|
sigops: Vec<SigOps>,
|
||||||
same_block_spent_outpoints: &FxHashSet<OutPoint>,
|
same_block_spent_outpoints: &FxHashSet<OutPoint>,
|
||||||
already_added: &mut ByAddrType<FxHashMap<AddrHash, TypeIndex>>,
|
already_added: &mut ByAddrType<FxHashMap<AddrHash, TypeIndex>>,
|
||||||
same_block_info: &mut FxHashMap<OutPoint, SameBlockOutputInfo>,
|
same_block_info: &mut FxHashMap<OutPoint, SameBlockOutputInfo>,
|
||||||
@@ -84,7 +88,7 @@ impl BlockProcessor<'_> {
|
|||||||
same_block_info,
|
same_block_info,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|| tx::store_tx_metadata(txs, txid_prefix_store, &mut tx_metadata),
|
|| tx::store_tx_metadata(txs, sigops, txid_prefix_store, &mut tx_metadata),
|
||||||
);
|
);
|
||||||
|
|
||||||
finalize_result?;
|
finalize_result?;
|
||||||
|
|||||||
58
crates/brk_indexer/src/processor/sigops.rs
Normal file
58
crates/brk_indexer/src/processor/sigops.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use brk_types::{OutputType, SigOps, TxInIndex};
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use super::{BlockProcessor, InputSource};
|
||||||
|
|
||||||
|
impl BlockProcessor<'_> {
|
||||||
|
/// BIP-141 sigop cost per tx in the block. Uses each input's prevout
|
||||||
|
/// `OutputType` (already resolved by `process_inputs` for the
|
||||||
|
/// previous-block case, looked up from `block.txdata` for the
|
||||||
|
/// same-block case) to feed canonical-shaped synthetic prevouts into
|
||||||
|
/// `bitcoin::Transaction::total_sigop_cost`.
|
||||||
|
pub fn compute_sigops(&self, txins: &[(TxInIndex, InputSource)]) -> Vec<SigOps> {
|
||||||
|
let txdata = &self.block.txdata;
|
||||||
|
let base_tx_index = u32::from(self.indexes.tx_index);
|
||||||
|
|
||||||
|
let mut tx_input_offsets = Vec::with_capacity(txdata.len());
|
||||||
|
let mut offset = 0usize;
|
||||||
|
for tx in txdata {
|
||||||
|
tx_input_offsets.push(offset);
|
||||||
|
offset += tx.input.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
txdata
|
||||||
|
.par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, tx)| {
|
||||||
|
if tx.is_coinbase() {
|
||||||
|
return SigOps::ZERO;
|
||||||
|
}
|
||||||
|
let start = tx_input_offsets[i];
|
||||||
|
let tx_inputs = &txins[start..start + tx.input.len()];
|
||||||
|
|
||||||
|
let kinds: SmallVec<[(bitcoin::OutPoint, OutputType); 4]> = tx
|
||||||
|
.input
|
||||||
|
.iter()
|
||||||
|
.zip(tx_inputs.iter())
|
||||||
|
.map(|(txin, (_, source))| {
|
||||||
|
let kind = match source {
|
||||||
|
InputSource::PreviousBlock { output_type, .. } => *output_type,
|
||||||
|
InputSource::SameBlock { outpoint, .. } => {
|
||||||
|
let local =
|
||||||
|
(u32::from(outpoint.tx_index()) - base_tx_index) as usize;
|
||||||
|
let vout = u32::from(outpoint.vout()) as usize;
|
||||||
|
OutputType::from(&txdata[local].output[vout].script_pubkey)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(txin.previous_output, kind)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
SigOps::of_bitcoin_tx_with_kinds(tx, |op| {
|
||||||
|
kinds.iter().find(|(o, _)| o == op).map(|(_, k)| *k)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_error::{Error, Result};
|
use brk_error::{Error, Result};
|
||||||
use brk_store::Store;
|
use brk_store::Store;
|
||||||
use brk_types::{StoredBool, TxIndex, Txid, TxidPrefix};
|
use brk_types::{SigOps, StoredBool, TxIndex, Txid, TxidPrefix};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use vecdb::{AnyVec, WritableVec, likely};
|
use vecdb::{AnyVec, WritableVec, likely};
|
||||||
@@ -90,10 +90,12 @@ impl<'a> BlockProcessor<'a> {
|
|||||||
|
|
||||||
pub(super) fn store_tx_metadata(
|
pub(super) fn store_tx_metadata(
|
||||||
txs: Vec<ComputedTx>,
|
txs: Vec<ComputedTx>,
|
||||||
|
sigops: Vec<SigOps>,
|
||||||
store: &mut Store<TxidPrefix, TxIndex>,
|
store: &mut Store<TxidPrefix, TxIndex>,
|
||||||
md: &mut TxMetadataVecs<'_>,
|
md: &mut TxMetadataVecs<'_>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for ct in txs {
|
debug_assert_eq!(txs.len(), sigops.len());
|
||||||
|
for (ct, sigops) in txs.into_iter().zip(sigops) {
|
||||||
if ct.prev_tx_index_opt.is_none() {
|
if ct.prev_tx_index_opt.is_none() {
|
||||||
store.insert(ct.txid_prefix, ct.tx_index);
|
store.insert(ct.txid_prefix, ct.tx_index);
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,7 @@ pub(super) fn store_tx_metadata(
|
|||||||
.checked_push(ct.tx_index, ct.base_size.into())?;
|
.checked_push(ct.tx_index, ct.base_size.into())?;
|
||||||
md.total_size
|
md.total_size
|
||||||
.checked_push(ct.tx_index, ct.total_size.into())?;
|
.checked_push(ct.tx_index, ct.total_size.into())?;
|
||||||
|
md.total_sigop_cost.checked_push(ct.tx_index, sigops)?;
|
||||||
md.is_explicitly_rbf
|
md.is_explicitly_rbf
|
||||||
.checked_push(ct.tx_index, StoredBool::from(ct.tx.is_explicitly_rbf()))?;
|
.checked_push(ct.tx_index, StoredBool::from(ct.tx.is_explicitly_rbf()))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
BlkPosition, Height, RawLockTime, StoredBool, StoredU32, TxInIndex, TxIndex, TxOutIndex,
|
BlkPosition, Height, RawLockTime, SigOps, StoredBool, StoredU32, TxInIndex, TxIndex,
|
||||||
TxVersion, Txid, Version,
|
TxOutIndex, TxVersion, Txid, Version,
|
||||||
};
|
};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
@@ -19,6 +19,7 @@ pub struct TransactionsVecs<M: StorageMode = Rw> {
|
|||||||
pub raw_locktime: M::Stored<PcoVec<TxIndex, RawLockTime>>,
|
pub raw_locktime: M::Stored<PcoVec<TxIndex, RawLockTime>>,
|
||||||
pub base_size: M::Stored<PcoVec<TxIndex, StoredU32>>,
|
pub base_size: M::Stored<PcoVec<TxIndex, StoredU32>>,
|
||||||
pub total_size: M::Stored<PcoVec<TxIndex, StoredU32>>,
|
pub total_size: M::Stored<PcoVec<TxIndex, StoredU32>>,
|
||||||
|
pub total_sigop_cost: M::Stored<PcoVec<TxIndex, SigOps>>,
|
||||||
pub is_explicitly_rbf: M::Stored<PcoVec<TxIndex, StoredBool>>,
|
pub is_explicitly_rbf: M::Stored<PcoVec<TxIndex, StoredBool>>,
|
||||||
pub first_txin_index: M::Stored<PcoVec<TxIndex, TxInIndex>>,
|
pub first_txin_index: M::Stored<PcoVec<TxIndex, TxInIndex>>,
|
||||||
pub first_txout_index: M::Stored<BytesVec<TxIndex, TxOutIndex>>,
|
pub first_txout_index: M::Stored<BytesVec<TxIndex, TxOutIndex>>,
|
||||||
@@ -32,6 +33,7 @@ pub struct TxMetadataVecs<'a> {
|
|||||||
pub raw_locktime: &'a mut PcoVec<TxIndex, RawLockTime>,
|
pub raw_locktime: &'a mut PcoVec<TxIndex, RawLockTime>,
|
||||||
pub base_size: &'a mut PcoVec<TxIndex, StoredU32>,
|
pub base_size: &'a mut PcoVec<TxIndex, StoredU32>,
|
||||||
pub total_size: &'a mut PcoVec<TxIndex, StoredU32>,
|
pub total_size: &'a mut PcoVec<TxIndex, StoredU32>,
|
||||||
|
pub total_sigop_cost: &'a mut PcoVec<TxIndex, SigOps>,
|
||||||
pub is_explicitly_rbf: &'a mut PcoVec<TxIndex, StoredBool>,
|
pub is_explicitly_rbf: &'a mut PcoVec<TxIndex, StoredBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +54,7 @@ impl TransactionsVecs {
|
|||||||
raw_locktime: &mut self.raw_locktime,
|
raw_locktime: &mut self.raw_locktime,
|
||||||
base_size: &mut self.base_size,
|
base_size: &mut self.base_size,
|
||||||
total_size: &mut self.total_size,
|
total_size: &mut self.total_size,
|
||||||
|
total_sigop_cost: &mut self.total_sigop_cost,
|
||||||
is_explicitly_rbf: &mut self.is_explicitly_rbf,
|
is_explicitly_rbf: &mut self.is_explicitly_rbf,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -65,6 +68,7 @@ impl TransactionsVecs {
|
|||||||
raw_locktime,
|
raw_locktime,
|
||||||
base_size,
|
base_size,
|
||||||
total_size,
|
total_size,
|
||||||
|
total_sigop_cost,
|
||||||
is_explicitly_rbf,
|
is_explicitly_rbf,
|
||||||
first_txin_index,
|
first_txin_index,
|
||||||
first_txout_index,
|
first_txout_index,
|
||||||
@@ -76,6 +80,7 @@ impl TransactionsVecs {
|
|||||||
raw_locktime = PcoVec::forced_import(db, "raw_locktime", version),
|
raw_locktime = PcoVec::forced_import(db, "raw_locktime", version),
|
||||||
base_size = PcoVec::forced_import(db, "base_size", version),
|
base_size = PcoVec::forced_import(db, "base_size", version),
|
||||||
total_size = PcoVec::forced_import(db, "total_size", version),
|
total_size = PcoVec::forced_import(db, "total_size", version),
|
||||||
|
total_sigop_cost = PcoVec::forced_import(db, "total_sigop_cost", version),
|
||||||
is_explicitly_rbf = PcoVec::forced_import(db, "is_explicitly_rbf", version),
|
is_explicitly_rbf = PcoVec::forced_import(db, "is_explicitly_rbf", version),
|
||||||
first_txin_index = PcoVec::forced_import(db, "first_txin_index", version),
|
first_txin_index = PcoVec::forced_import(db, "first_txin_index", version),
|
||||||
first_txout_index = BytesVec::forced_import(db, "first_txout_index", version),
|
first_txout_index = BytesVec::forced_import(db, "first_txout_index", version),
|
||||||
@@ -88,6 +93,7 @@ impl TransactionsVecs {
|
|||||||
raw_locktime,
|
raw_locktime,
|
||||||
base_size,
|
base_size,
|
||||||
total_size,
|
total_size,
|
||||||
|
total_sigop_cost,
|
||||||
is_explicitly_rbf,
|
is_explicitly_rbf,
|
||||||
first_txin_index,
|
first_txin_index,
|
||||||
first_txout_index,
|
first_txout_index,
|
||||||
@@ -107,6 +113,8 @@ impl TransactionsVecs {
|
|||||||
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||||
self.total_size
|
self.total_size
|
||||||
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||||
|
self.total_sigop_cost
|
||||||
|
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||||
self.is_explicitly_rbf
|
self.is_explicitly_rbf
|
||||||
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||||
self.first_txin_index
|
self.first_txin_index
|
||||||
@@ -126,6 +134,7 @@ impl TransactionsVecs {
|
|||||||
&mut self.raw_locktime,
|
&mut self.raw_locktime,
|
||||||
&mut self.base_size,
|
&mut self.base_size,
|
||||||
&mut self.total_size,
|
&mut self.total_size,
|
||||||
|
&mut self.total_sigop_cost,
|
||||||
&mut self.is_explicitly_rbf,
|
&mut self.is_explicitly_rbf,
|
||||||
&mut self.first_txin_index,
|
&mut self.first_txin_index,
|
||||||
&mut self.first_txout_index,
|
&mut self.first_txout_index,
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ impl Query {
|
|||||||
/// metadata (sorted by tx_index).
|
/// metadata (sorted by tx_index).
|
||||||
/// Phase 2: resolve each prevout's script_pubkey (sorted by
|
/// Phase 2: resolve each prevout's script_pubkey (sorted by
|
||||||
/// output_type, then type_index, for sequential addr-vec reads).
|
/// output_type, then type_index, for sequential addr-vec reads).
|
||||||
/// Phase 3: assemble `Transaction` objects, compute sigops + fees.
|
/// Phase 3: assemble `Transaction` objects, compute fees.
|
||||||
///
|
///
|
||||||
/// The final `unwrap` is provably safe: `order` is a permutation of
|
/// The final `unwrap` is provably safe: `order` is a permutation of
|
||||||
/// `0..len`, Phase 1 produces exactly one `DecodedTx` per position, and
|
/// `0..len`, Phase 1 produces exactly one `DecodedTx` per position, and
|
||||||
@@ -129,6 +129,7 @@ impl Query {
|
|||||||
|
|
||||||
let mut txid_cursor = indexer.vecs.transactions.txid.cursor();
|
let mut txid_cursor = indexer.vecs.transactions.txid.cursor();
|
||||||
let mut total_size_cursor = indexer.vecs.transactions.total_size.cursor();
|
let mut total_size_cursor = indexer.vecs.transactions.total_size.cursor();
|
||||||
|
let mut sigops_cursor = indexer.vecs.transactions.total_sigop_cost.cursor();
|
||||||
let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
||||||
let mut position_cursor = indexer.vecs.transactions.position.cursor();
|
let mut position_cursor = indexer.vecs.transactions.position.cursor();
|
||||||
|
|
||||||
@@ -137,6 +138,7 @@ impl Query {
|
|||||||
tx_index: TxIndex,
|
tx_index: TxIndex,
|
||||||
txid: Txid,
|
txid: Txid,
|
||||||
total_size: StoredU32,
|
total_size: StoredU32,
|
||||||
|
total_sigop_cost: SigOps,
|
||||||
status: TxStatus,
|
status: TxStatus,
|
||||||
decoded: bitcoin::Transaction,
|
decoded: bitcoin::Transaction,
|
||||||
first_txin_index: TxInIndex,
|
first_txin_index: TxInIndex,
|
||||||
@@ -154,6 +156,7 @@ impl Query {
|
|||||||
|
|
||||||
let txid: Txid = txid_cursor.get(idx).data()?;
|
let txid: Txid = txid_cursor.get(idx).data()?;
|
||||||
let total_size: StoredU32 = total_size_cursor.get(idx).data()?;
|
let total_size: StoredU32 = total_size_cursor.get(idx).data()?;
|
||||||
|
let total_sigop_cost: SigOps = sigops_cursor.get(idx).data()?;
|
||||||
let first_txin_index: TxInIndex = first_txin_cursor.get(idx).data()?;
|
let first_txin_index: TxInIndex = first_txin_cursor.get(idx).data()?;
|
||||||
let position: BlkPosition = position_cursor.get(idx).data()?;
|
let position: BlkPosition = position_cursor.get(idx).data()?;
|
||||||
|
|
||||||
@@ -179,6 +182,7 @@ impl Query {
|
|||||||
tx_index,
|
tx_index,
|
||||||
txid,
|
txid,
|
||||||
total_size,
|
total_size,
|
||||||
|
total_sigop_cost,
|
||||||
status,
|
status,
|
||||||
decoded,
|
decoded,
|
||||||
first_txin_index,
|
first_txin_index,
|
||||||
@@ -277,25 +281,6 @@ impl Query {
|
|||||||
|
|
||||||
let weight = Weight::from(dtx.decoded.weight());
|
let weight = Weight::from(dtx.decoded.weight());
|
||||||
|
|
||||||
// O(n) sigop cost via FxHashMap instead of O(n²) linear scan
|
|
||||||
let outpoint_to_idx: FxHashMap<bitcoin::OutPoint, usize> = dtx
|
|
||||||
.decoded
|
|
||||||
.input
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(j, txin)| (txin.previous_output, j))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let total_sigop_cost = SigOps::of_bitcoin_tx(&dtx.decoded, |outpoint| {
|
|
||||||
outpoint_to_idx
|
|
||||||
.get(outpoint)
|
|
||||||
.and_then(|&j| input[j].prevout.as_ref())
|
|
||||||
.map(|p| bitcoin::TxOut {
|
|
||||||
value: bitcoin::Amount::from_sat(u64::from(p.value)),
|
|
||||||
script_pubkey: p.script_pubkey.clone(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let output: Vec<TxOut> = dtx.decoded.output.into_iter().map(TxOut::from).collect();
|
let output: Vec<TxOut> = dtx.decoded.output.into_iter().map(TxOut::from).collect();
|
||||||
|
|
||||||
let mut transaction = Transaction {
|
let mut transaction = Transaction {
|
||||||
@@ -305,7 +290,7 @@ impl Query {
|
|||||||
lock_time: RawLockTime::from(dtx.decoded.lock_time),
|
lock_time: RawLockTime::from(dtx.decoded.lock_time),
|
||||||
total_size: *dtx.total_size as usize,
|
total_size: *dtx.total_size as usize,
|
||||||
weight,
|
weight,
|
||||||
total_sigop_cost,
|
total_sigop_cost: dtx.total_sigop_cost,
|
||||||
fee: Sats::ZERO,
|
fee: Sats::ZERO,
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -9,14 +9,10 @@
|
|||||||
//! — the same wire converter the mempool path uses, so both produce
|
//! — the same wire converter the mempool path uses, so both produce
|
||||||
//! identical `CpfpInfo` shapes.
|
//! identical `CpfpInfo` shapes.
|
||||||
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use bitcoin::consensus::Decodable;
|
|
||||||
use brk_error::{Error, OptionData, Result};
|
use brk_error::{Error, OptionData, Result};
|
||||||
use brk_mempool::cluster::{Cluster, ClusterNode, LocalIdx};
|
use brk_mempool::cluster::{Cluster, ClusterNode, LocalIdx};
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
CpfpInfo, FeeRate, Height, OutPoint, OutputType, Sats, SigOps, TxIndex, TxInIndex, TypeIndex,
|
CpfpInfo, FeeRate, Height, TxIndex, TxInIndex, Txid, TxidPrefix, VSize, Weight,
|
||||||
Txid, TxidPrefix, VSize, Weight,
|
|
||||||
};
|
};
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@@ -106,78 +102,19 @@ impl Query {
|
|||||||
/// the result so `effectiveFeePerVsize` matches the live path's
|
/// the result so `effectiveFeePerVsize` matches the live path's
|
||||||
/// chunk-rate semantics.
|
/// chunk-rate semantics.
|
||||||
fn confirmed_cpfp(&self, txid: &Txid) -> Result<CpfpInfo> {
|
fn confirmed_cpfp(&self, txid: &Txid) -> Result<CpfpInfo> {
|
||||||
let seed = self.resolve_tx_index(txid)?;
|
let tx_index = self.resolve_tx_index(txid)?;
|
||||||
let height = self.confirmed_status_height(seed)?;
|
let height = self.confirmed_status_height(tx_index)?;
|
||||||
let (cluster, seed_local) = self.build_confirmed_cluster(seed, height)?;
|
let (cluster, seed_local) = self.build_confirmed_cluster(tx_index, height)?;
|
||||||
let sigops = self.seed_sigop_cost(seed)?;
|
let sigops = self
|
||||||
|
.indexer()
|
||||||
|
.vecs
|
||||||
|
.transactions
|
||||||
|
.total_sigop_cost
|
||||||
|
.collect_one(tx_index)
|
||||||
|
.data()?;
|
||||||
Ok(cluster.to_cpfp_info(seed_local, sigops))
|
Ok(cluster.to_cpfp_info(seed_local, sigops))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BIP-141 sigop cost for a single confirmed tx, computed on demand:
|
|
||||||
/// re-decode the raw tx, rebuild its prevout map from `inputs.*` +
|
|
||||||
/// addr vecs, then defer the actual count to `SigOps::of_bitcoin_tx`.
|
|
||||||
/// Cost is one BLK read plus `n_inputs` cursor hops, so a few hundred
|
|
||||||
/// microseconds per CPFP request.
|
|
||||||
fn seed_sigop_cost(&self, tx_index: TxIndex) -> Result<SigOps> {
|
|
||||||
let indexer = self.indexer();
|
|
||||||
let total_size = indexer
|
|
||||||
.vecs
|
|
||||||
.transactions
|
|
||||||
.total_size
|
|
||||||
.collect_one(tx_index)
|
|
||||||
.data()?;
|
|
||||||
let position = indexer
|
|
||||||
.vecs
|
|
||||||
.transactions
|
|
||||||
.position
|
|
||||||
.collect_one(tx_index)
|
|
||||||
.data()?;
|
|
||||||
let buffer = self.reader().read_raw_bytes(position, *total_size as usize)?;
|
|
||||||
let decoded = bitcoin::Transaction::consensus_decode(&mut Cursor::new(buffer))
|
|
||||||
.map_err(|_| Error::Parse("Failed to decode transaction".into()))?;
|
|
||||||
|
|
||||||
let first_txin = indexer
|
|
||||||
.vecs
|
|
||||||
.transactions
|
|
||||||
.first_txin_index
|
|
||||||
.collect_one(tx_index)
|
|
||||||
.data()?;
|
|
||||||
let start = usize::from(first_txin);
|
|
||||||
let count = decoded.input.len();
|
|
||||||
|
|
||||||
let mut outpoint_cursor = indexer.vecs.inputs.outpoint.cursor();
|
|
||||||
let mut output_type_cursor = indexer.vecs.inputs.output_type.cursor();
|
|
||||||
let mut type_index_cursor = indexer.vecs.inputs.type_index.cursor();
|
|
||||||
let mut value_cursor = self.computer().inputs.spent.value.cursor();
|
|
||||||
|
|
||||||
let addr_readers = indexer.vecs.addrs.addr_readers();
|
|
||||||
|
|
||||||
let mut prevout_map: FxHashMap<bitcoin::OutPoint, bitcoin::TxOut> =
|
|
||||||
FxHashMap::with_capacity_and_hasher(count, FxBuildHasher);
|
|
||||||
|
|
||||||
for (j, txin) in decoded.input.iter().enumerate() {
|
|
||||||
let op: OutPoint = outpoint_cursor.get(start + j).data()?;
|
|
||||||
if op.is_coinbase() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let ot: OutputType = output_type_cursor.get(start + j).data()?;
|
|
||||||
let ti: TypeIndex = type_index_cursor.get(start + j).data()?;
|
|
||||||
let val: Sats = value_cursor.get(start + j).data()?;
|
|
||||||
let script_pubkey = addr_readers.script_pubkey(ot, ti);
|
|
||||||
prevout_map.insert(
|
|
||||||
txin.previous_output,
|
|
||||||
bitcoin::TxOut {
|
|
||||||
value: bitcoin::Amount::from_sat(u64::from(val)),
|
|
||||||
script_pubkey,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SigOps::of_bitcoin_tx(&decoded, |outpoint| {
|
|
||||||
prevout_map.get(outpoint).cloned()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk the seed's same-block parent/child edges, materialize each
|
/// Walk the seed's same-block parent/child edges, materialize each
|
||||||
/// member's `(txid, weight, fee)` from indexer/computer cursors,
|
/// member's `(txid, weight, fee)` from indexer/computer cursors,
|
||||||
/// and build a `Cluster<TxIndex>`. The seed's `LocalIdx` comes
|
/// and build a `Cluster<TxIndex>`. The seed's `LocalIdx` comes
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use vecdb::{Formattable, Pco};
|
||||||
|
|
||||||
use crate::VSize;
|
use crate::{OutputType, VSize};
|
||||||
|
|
||||||
/// BIP-141 sigop cost. The block-level budget is 80,000, so a `u32`
|
/// BIP-141 sigop cost. The block-level budget is 80,000, so a `u32`
|
||||||
/// fits a single tx's count with room to spare.
|
/// fits a single tx's count with room to spare.
|
||||||
@@ -21,6 +23,7 @@ use crate::VSize;
|
|||||||
Hash,
|
Hash,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
|
Pco,
|
||||||
JsonSchema,
|
JsonSchema,
|
||||||
)]
|
)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
@@ -51,19 +54,59 @@ impl SigOps {
|
|||||||
vsize.max(self.vsize_cost())
|
vsize.max(self.vsize_cost())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BIP-141 sigop cost of a `bitcoin::Transaction`, given a prevout
|
/// BIP-141 sigop cost using only each input's prevout `OutputType` as
|
||||||
/// lookup closure (P2SH redeem-script and witness sigops need the
|
/// hint. Avoids reading the real `script_pubkey`: bitcoin-rs's sigop
|
||||||
/// spending script). Wraps `bitcoin::Transaction::total_sigop_cost`
|
/// walk only inspects script *structure* (`is_p2sh` / `is_p2wpkh` /
|
||||||
/// and narrows its `usize` result to `SigOps`.
|
/// `is_p2wsh`), so a canonical empty-hash script of the matching shape
|
||||||
#[inline]
|
/// produces the same count as the real one. Other output types
|
||||||
pub fn of_bitcoin_tx<F>(tx: &bitcoin::Transaction, prevout_lookup: F) -> Self
|
/// contribute nothing on the input side, so we return `None` for them.
|
||||||
|
pub fn of_bitcoin_tx_with_kinds<F>(tx: &bitcoin::Transaction, mut kind_at: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnMut(&bitcoin::OutPoint) -> Option<bitcoin::TxOut>,
|
F: FnMut(&OutPoint) -> Option<OutputType>,
|
||||||
{
|
{
|
||||||
Self::from(tx.total_sigop_cost(prevout_lookup))
|
Self::from(tx.total_sigop_cost(|outpoint| {
|
||||||
|
let script_pubkey = match kind_at(outpoint)? {
|
||||||
|
OutputType::P2SH => synthetic_p2sh_spk(),
|
||||||
|
OutputType::P2WPKH => synthetic_p2wpkh_spk(),
|
||||||
|
OutputType::P2WSH => synthetic_p2wsh_spk(),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(TxOut {
|
||||||
|
value: Amount::ZERO,
|
||||||
|
script_pubkey,
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthetic_p2sh_spk() -> ScriptBuf {
|
||||||
|
// OP_HASH160 PUSH20 <20 zero bytes> OP_EQUAL
|
||||||
|
let mut bytes = Vec::with_capacity(23);
|
||||||
|
bytes.push(0xa9);
|
||||||
|
bytes.push(0x14);
|
||||||
|
bytes.extend_from_slice(&[0u8; 20]);
|
||||||
|
bytes.push(0x87);
|
||||||
|
ScriptBuf::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthetic_p2wpkh_spk() -> ScriptBuf {
|
||||||
|
// OP_0 PUSH20 <20 zero bytes>
|
||||||
|
let mut bytes = Vec::with_capacity(22);
|
||||||
|
bytes.push(0x00);
|
||||||
|
bytes.push(0x14);
|
||||||
|
bytes.extend_from_slice(&[0u8; 20]);
|
||||||
|
ScriptBuf::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthetic_p2wsh_spk() -> ScriptBuf {
|
||||||
|
// OP_0 PUSH32 <32 zero bytes>
|
||||||
|
let mut bytes = Vec::with_capacity(34);
|
||||||
|
bytes.push(0x00);
|
||||||
|
bytes.push(0x20);
|
||||||
|
bytes.extend_from_slice(&[0u8; 32]);
|
||||||
|
ScriptBuf::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u32> for SigOps {
|
impl From<u32> for SigOps {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: u32) -> Self {
|
fn from(value: u32) -> Self {
|
||||||
@@ -84,3 +127,11 @@ impl From<SigOps> for u32 {
|
|||||||
value.0
|
value.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Formattable for SigOps {
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_to(&self, buf: &mut Vec<u8>) {
|
||||||
|
let mut b = itoa::Buffer::new();
|
||||||
|
buf.extend_from_slice(b.format(self.0).as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5261,6 +5261,7 @@ function createTransferPattern(client, acc) {
|
|||||||
* @property {SeriesPattern19<RawLockTime>} rawLocktime
|
* @property {SeriesPattern19<RawLockTime>} rawLocktime
|
||||||
* @property {SeriesPattern19<StoredU32>} baseSize
|
* @property {SeriesPattern19<StoredU32>} baseSize
|
||||||
* @property {SeriesPattern19<StoredU32>} totalSize
|
* @property {SeriesPattern19<StoredU32>} totalSize
|
||||||
|
* @property {SeriesPattern19<SigOps>} totalSigopCost
|
||||||
* @property {SeriesPattern19<StoredBool>} isExplicitlyRbf
|
* @property {SeriesPattern19<StoredBool>} isExplicitlyRbf
|
||||||
* @property {SeriesPattern19<TxInIndex>} firstTxinIndex
|
* @property {SeriesPattern19<TxInIndex>} firstTxinIndex
|
||||||
* @property {SeriesPattern19<TxOutIndex>} firstTxoutIndex
|
* @property {SeriesPattern19<TxOutIndex>} firstTxoutIndex
|
||||||
@@ -8796,6 +8797,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
rawLocktime: createSeriesPattern19(this, 'raw_locktime'),
|
rawLocktime: createSeriesPattern19(this, 'raw_locktime'),
|
||||||
baseSize: createSeriesPattern19(this, 'base_size'),
|
baseSize: createSeriesPattern19(this, 'base_size'),
|
||||||
totalSize: createSeriesPattern19(this, 'total_size'),
|
totalSize: createSeriesPattern19(this, 'total_size'),
|
||||||
|
totalSigopCost: createSeriesPattern19(this, 'total_sigop_cost'),
|
||||||
isExplicitlyRbf: createSeriesPattern19(this, 'is_explicitly_rbf'),
|
isExplicitlyRbf: createSeriesPattern19(this, 'is_explicitly_rbf'),
|
||||||
firstTxinIndex: createSeriesPattern19(this, 'first_txin_index'),
|
firstTxinIndex: createSeriesPattern19(this, 'first_txin_index'),
|
||||||
firstTxoutIndex: createSeriesPattern19(this, 'first_txout_index'),
|
firstTxoutIndex: createSeriesPattern19(this, 'first_txout_index'),
|
||||||
|
|||||||
@@ -4183,6 +4183,7 @@ class SeriesTree_Transactions_Raw:
|
|||||||
self.raw_locktime: SeriesPattern19[RawLockTime] = SeriesPattern19(client, 'raw_locktime')
|
self.raw_locktime: SeriesPattern19[RawLockTime] = SeriesPattern19(client, 'raw_locktime')
|
||||||
self.base_size: SeriesPattern19[StoredU32] = SeriesPattern19(client, 'base_size')
|
self.base_size: SeriesPattern19[StoredU32] = SeriesPattern19(client, 'base_size')
|
||||||
self.total_size: SeriesPattern19[StoredU32] = SeriesPattern19(client, 'total_size')
|
self.total_size: SeriesPattern19[StoredU32] = SeriesPattern19(client, 'total_size')
|
||||||
|
self.total_sigop_cost: SeriesPattern19[SigOps] = SeriesPattern19(client, 'total_sigop_cost')
|
||||||
self.is_explicitly_rbf: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_explicitly_rbf')
|
self.is_explicitly_rbf: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_explicitly_rbf')
|
||||||
self.first_txin_index: SeriesPattern19[TxInIndex] = SeriesPattern19(client, 'first_txin_index')
|
self.first_txin_index: SeriesPattern19[TxInIndex] = SeriesPattern19(client, 'first_txin_index')
|
||||||
self.first_txout_index: SeriesPattern19[TxOutIndex] = SeriesPattern19(client, 'first_txout_index')
|
self.first_txout_index: SeriesPattern19[TxOutIndex] = SeriesPattern19(client, 'first_txout_index')
|
||||||
|
|||||||
Reference in New Issue
Block a user