From abcb238022de41a9e78865abcb509daf1c47538e Mon Sep 17 00:00:00 2001 From: nym21 Date: Mon, 4 May 2026 19:06:41 +0200 Subject: [PATCH] global: sigops --- Cargo.lock | 1 + crates/brk_client/src/lib.rs | 2 + crates/brk_indexer/Cargo.toml | 1 + crates/brk_indexer/src/lib.rs | 3 + crates/brk_indexer/src/processor/mod.rs | 8 +- crates/brk_indexer/src/processor/sigops.rs | 58 ++++++++++++++ crates/brk_indexer/src/processor/tx.rs | 7 +- crates/brk_indexer/src/vecs/transactions.rs | 13 +++- crates/brk_query/src/impl/block/txs.rs | 27 ++----- crates/brk_query/src/impl/cpfp.rs | 85 +++------------------ crates/brk_types/src/sigops.rs | 69 ++++++++++++++--- modules/brk-client/index.js | 2 + packages/brk_client/brk_client/__init__.py | 1 + 13 files changed, 167 insertions(+), 110 deletions(-) create mode 100644 crates/brk_indexer/src/processor/sigops.rs diff --git a/Cargo.lock b/Cargo.lock index 18ede3b22..7194f448f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,7 @@ dependencies = [ "rustc-hash", "schemars", "serde", + "smallvec", "tracing", "vecdb", ] diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 4f3cb20e9..7357b59a8 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -3871,6 +3871,7 @@ pub struct SeriesTree_Transactions_Raw { pub raw_locktime: SeriesPattern19, pub base_size: SeriesPattern19, pub total_size: SeriesPattern19, + pub total_sigop_cost: SeriesPattern19, pub is_explicitly_rbf: SeriesPattern19, pub first_txin_index: SeriesPattern19, pub first_txout_index: SeriesPattern19, @@ -3885,6 +3886,7 @@ impl SeriesTree_Transactions_Raw { raw_locktime: SeriesPattern19::new(client.clone(), "raw_locktime".to_string()), base_size: SeriesPattern19::new(client.clone(), "base_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()), first_txin_index: SeriesPattern19::new(client.clone(), "first_txin_index".to_string()), first_txout_index: SeriesPattern19::new(client.clone(), "first_txout_index".to_string()), diff --git a/crates/brk_indexer/Cargo.toml b/crates/brk_indexer/Cargo.toml index 87d5646aa..c8b77e44e 100644 --- a/crates/brk_indexer/Cargo.toml +++ b/crates/brk_indexer/Cargo.toml @@ -25,6 +25,7 @@ serde = { workspace = true } tracing = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } +smallvec = { workspace = true } vecdb = { workspace = true } [dev-dependencies] diff --git a/crates/brk_indexer/src/lib.rs b/crates/brk_indexer/src/lib.rs index bbc8e21a2..d58997e63 100644 --- a/crates/brk_indexer/src/lib.rs +++ b/crates/brk_indexer/src/lib.rs @@ -309,10 +309,13 @@ impl Indexer { processor.check_txid_collisions(&txs)?; + let sigops = processor.compute_sigops(&txins); + processor.finalize_and_store_metadata( txs, txouts, txins, + sigops, &buffers.same_block_spent, &mut buffers.already_added_addrs, &mut buffers.same_block_output_info, diff --git a/crates/brk_indexer/src/processor/mod.rs b/crates/brk_indexer/src/processor/mod.rs index d57393b22..d63bab969 100644 --- a/crates/brk_indexer/src/processor/mod.rs +++ b/crates/brk_indexer/src/processor/mod.rs @@ -1,4 +1,5 @@ mod metadata; +mod sigops; mod tx; mod txin; mod txout; @@ -8,7 +9,9 @@ pub use types::*; use brk_cohort::ByAddrType; 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 crate::{Indexes, Readers, Stores, Vecs}; @@ -39,6 +42,7 @@ impl BlockProcessor<'_> { txs: Vec, txouts: Vec, txins: Vec<(TxInIndex, InputSource)>, + sigops: Vec, same_block_spent_outpoints: &FxHashSet, already_added: &mut ByAddrType>, same_block_info: &mut FxHashMap, @@ -84,7 +88,7 @@ impl BlockProcessor<'_> { 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?; diff --git a/crates/brk_indexer/src/processor/sigops.rs b/crates/brk_indexer/src/processor/sigops.rs new file mode 100644 index 000000000..ab352b184 --- /dev/null +++ b/crates/brk_indexer/src/processor/sigops.rs @@ -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 { + 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() + } +} diff --git a/crates/brk_indexer/src/processor/tx.rs b/crates/brk_indexer/src/processor/tx.rs index 1d5e44293..e98f20421 100644 --- a/crates/brk_indexer/src/processor/tx.rs +++ b/crates/brk_indexer/src/processor/tx.rs @@ -1,6 +1,6 @@ use brk_error::{Error, Result}; use brk_store::Store; -use brk_types::{StoredBool, TxIndex, Txid, TxidPrefix}; +use brk_types::{SigOps, StoredBool, TxIndex, Txid, TxidPrefix}; use rayon::prelude::*; use tracing::error; use vecdb::{AnyVec, WritableVec, likely}; @@ -90,10 +90,12 @@ impl<'a> BlockProcessor<'a> { pub(super) fn store_tx_metadata( txs: Vec, + sigops: Vec, store: &mut Store, md: &mut TxMetadataVecs<'_>, ) -> 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() { 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())?; md.total_size .checked_push(ct.tx_index, ct.total_size.into())?; + md.total_sigop_cost.checked_push(ct.tx_index, sigops)?; md.is_explicitly_rbf .checked_push(ct.tx_index, StoredBool::from(ct.tx.is_explicitly_rbf()))?; } diff --git a/crates/brk_indexer/src/vecs/transactions.rs b/crates/brk_indexer/src/vecs/transactions.rs index 2b10b3ba4..6f18362af 100644 --- a/crates/brk_indexer/src/vecs/transactions.rs +++ b/crates/brk_indexer/src/vecs/transactions.rs @@ -1,8 +1,8 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{ - BlkPosition, Height, RawLockTime, StoredBool, StoredU32, TxInIndex, TxIndex, TxOutIndex, - TxVersion, Txid, Version, + BlkPosition, Height, RawLockTime, SigOps, StoredBool, StoredU32, TxInIndex, TxIndex, + TxOutIndex, TxVersion, Txid, Version, }; use rayon::prelude::*; use vecdb::{ @@ -19,6 +19,7 @@ pub struct TransactionsVecs { pub raw_locktime: M::Stored>, pub base_size: M::Stored>, pub total_size: M::Stored>, + pub total_sigop_cost: M::Stored>, pub is_explicitly_rbf: M::Stored>, pub first_txin_index: M::Stored>, pub first_txout_index: M::Stored>, @@ -32,6 +33,7 @@ pub struct TxMetadataVecs<'a> { pub raw_locktime: &'a mut PcoVec, pub base_size: &'a mut PcoVec, pub total_size: &'a mut PcoVec, + pub total_sigop_cost: &'a mut PcoVec, pub is_explicitly_rbf: &'a mut PcoVec, } @@ -52,6 +54,7 @@ impl TransactionsVecs { raw_locktime: &mut self.raw_locktime, base_size: &mut self.base_size, total_size: &mut self.total_size, + total_sigop_cost: &mut self.total_sigop_cost, is_explicitly_rbf: &mut self.is_explicitly_rbf, }, ) @@ -65,6 +68,7 @@ impl TransactionsVecs { raw_locktime, base_size, total_size, + total_sigop_cost, is_explicitly_rbf, first_txin_index, first_txout_index, @@ -76,6 +80,7 @@ impl TransactionsVecs { raw_locktime = PcoVec::forced_import(db, "raw_locktime", version), base_size = PcoVec::forced_import(db, "base_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), first_txin_index = PcoVec::forced_import(db, "first_txin_index", version), first_txout_index = BytesVec::forced_import(db, "first_txout_index", version), @@ -88,6 +93,7 @@ impl TransactionsVecs { raw_locktime, base_size, total_size, + total_sigop_cost, is_explicitly_rbf, first_txin_index, first_txout_index, @@ -107,6 +113,8 @@ impl TransactionsVecs { .truncate_if_needed_with_stamp(tx_index, stamp)?; self.total_size .truncate_if_needed_with_stamp(tx_index, stamp)?; + self.total_sigop_cost + .truncate_if_needed_with_stamp(tx_index, stamp)?; self.is_explicitly_rbf .truncate_if_needed_with_stamp(tx_index, stamp)?; self.first_txin_index @@ -126,6 +134,7 @@ impl TransactionsVecs { &mut self.raw_locktime, &mut self.base_size, &mut self.total_size, + &mut self.total_sigop_cost, &mut self.is_explicitly_rbf, &mut self.first_txin_index, &mut self.first_txout_index, diff --git a/crates/brk_query/src/impl/block/txs.rs b/crates/brk_query/src/impl/block/txs.rs index 74c83af22..5418f88de 100644 --- a/crates/brk_query/src/impl/block/txs.rs +++ b/crates/brk_query/src/impl/block/txs.rs @@ -105,7 +105,7 @@ impl Query { /// metadata (sorted by tx_index). /// Phase 2: resolve each prevout's script_pubkey (sorted by /// 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 /// `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 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 position_cursor = indexer.vecs.transactions.position.cursor(); @@ -137,6 +138,7 @@ impl Query { tx_index: TxIndex, txid: Txid, total_size: StoredU32, + total_sigop_cost: SigOps, status: TxStatus, decoded: bitcoin::Transaction, first_txin_index: TxInIndex, @@ -154,6 +156,7 @@ impl Query { let txid: Txid = txid_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 position: BlkPosition = position_cursor.get(idx).data()?; @@ -179,6 +182,7 @@ impl Query { tx_index, txid, total_size, + total_sigop_cost, status, decoded, first_txin_index, @@ -277,25 +281,6 @@ impl Query { let weight = Weight::from(dtx.decoded.weight()); - // O(n) sigop cost via FxHashMap instead of O(n²) linear scan - let outpoint_to_idx: FxHashMap = 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 = dtx.decoded.output.into_iter().map(TxOut::from).collect(); let mut transaction = Transaction { @@ -305,7 +290,7 @@ impl Query { lock_time: RawLockTime::from(dtx.decoded.lock_time), total_size: *dtx.total_size as usize, weight, - total_sigop_cost, + total_sigop_cost: dtx.total_sigop_cost, fee: Sats::ZERO, input, output, diff --git a/crates/brk_query/src/impl/cpfp.rs b/crates/brk_query/src/impl/cpfp.rs index 05814381d..cd1f69165 100644 --- a/crates/brk_query/src/impl/cpfp.rs +++ b/crates/brk_query/src/impl/cpfp.rs @@ -9,14 +9,10 @@ //! — the same wire converter the mempool path uses, so both produce //! identical `CpfpInfo` shapes. -use std::io::Cursor; - -use bitcoin::consensus::Decodable; use brk_error::{Error, OptionData, Result}; use brk_mempool::cluster::{Cluster, ClusterNode, LocalIdx}; use brk_types::{ - CpfpInfo, FeeRate, Height, OutPoint, OutputType, Sats, SigOps, TxIndex, TxInIndex, TypeIndex, - Txid, TxidPrefix, VSize, Weight, + CpfpInfo, FeeRate, Height, TxIndex, TxInIndex, Txid, TxidPrefix, VSize, Weight, }; use rustc_hash::{FxBuildHasher, FxHashMap}; use smallvec::SmallVec; @@ -106,78 +102,19 @@ impl Query { /// the result so `effectiveFeePerVsize` matches the live path's /// chunk-rate semantics. fn confirmed_cpfp(&self, txid: &Txid) -> Result { - let seed = self.resolve_tx_index(txid)?; - let height = self.confirmed_status_height(seed)?; - let (cluster, seed_local) = self.build_confirmed_cluster(seed, height)?; - let sigops = self.seed_sigop_cost(seed)?; + let tx_index = self.resolve_tx_index(txid)?; + let height = self.confirmed_status_height(tx_index)?; + let (cluster, seed_local) = self.build_confirmed_cluster(tx_index, height)?; + let sigops = self + .indexer() + .vecs + .transactions + .total_sigop_cost + .collect_one(tx_index) + .data()?; 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 { - 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 = - 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 /// member's `(txid, weight, fee)` from indexer/computer cursors, /// and build a `Cluster`. The seed's `LocalIdx` comes diff --git a/crates/brk_types/src/sigops.rs b/crates/brk_types/src/sigops.rs index eefba083f..5fe17cc92 100644 --- a/crates/brk_types/src/sigops.rs +++ b/crates/brk_types/src/sigops.rs @@ -1,7 +1,9 @@ +use bitcoin::{Amount, OutPoint, ScriptBuf, TxOut}; use schemars::JsonSchema; 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` /// fits a single tx's count with room to spare. @@ -21,6 +23,7 @@ use crate::VSize; Hash, Serialize, Deserialize, + Pco, JsonSchema, )] #[serde(transparent)] @@ -51,19 +54,59 @@ impl SigOps { vsize.max(self.vsize_cost()) } - /// BIP-141 sigop cost of a `bitcoin::Transaction`, given a prevout - /// lookup closure (P2SH redeem-script and witness sigops need the - /// spending script). Wraps `bitcoin::Transaction::total_sigop_cost` - /// and narrows its `usize` result to `SigOps`. - #[inline] - pub fn of_bitcoin_tx(tx: &bitcoin::Transaction, prevout_lookup: F) -> Self + /// BIP-141 sigop cost using only each input's prevout `OutputType` as + /// hint. Avoids reading the real `script_pubkey`: bitcoin-rs's sigop + /// walk only inspects script *structure* (`is_p2sh` / `is_p2wpkh` / + /// `is_p2wsh`), so a canonical empty-hash script of the matching shape + /// produces the same count as the real one. Other output types + /// contribute nothing on the input side, so we return `None` for them. + pub fn of_bitcoin_tx_with_kinds(tx: &bitcoin::Transaction, mut kind_at: F) -> Self where - F: FnMut(&bitcoin::OutPoint) -> Option, + F: FnMut(&OutPoint) -> Option, { - 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 for SigOps { #[inline] fn from(value: u32) -> Self { @@ -84,3 +127,11 @@ impl From for u32 { value.0 } } + +impl Formattable for SigOps { + #[inline(always)] + fn write_to(&self, buf: &mut Vec) { + let mut b = itoa::Buffer::new(); + buf.extend_from_slice(b.format(self.0).as_bytes()); + } +} diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 5fac84df5..d309f5753 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -5261,6 +5261,7 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern19} rawLocktime * @property {SeriesPattern19} baseSize * @property {SeriesPattern19} totalSize + * @property {SeriesPattern19} totalSigopCost * @property {SeriesPattern19} isExplicitlyRbf * @property {SeriesPattern19} firstTxinIndex * @property {SeriesPattern19} firstTxoutIndex @@ -8796,6 +8797,7 @@ class BrkClient extends BrkClientBase { rawLocktime: createSeriesPattern19(this, 'raw_locktime'), baseSize: createSeriesPattern19(this, 'base_size'), totalSize: createSeriesPattern19(this, 'total_size'), + totalSigopCost: createSeriesPattern19(this, 'total_sigop_cost'), isExplicitlyRbf: createSeriesPattern19(this, 'is_explicitly_rbf'), firstTxinIndex: createSeriesPattern19(this, 'first_txin_index'), firstTxoutIndex: createSeriesPattern19(this, 'first_txout_index'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index e13ad2eb8..a7b336037 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -4183,6 +4183,7 @@ class SeriesTree_Transactions_Raw: self.raw_locktime: SeriesPattern19[RawLockTime] = SeriesPattern19(client, 'raw_locktime') self.base_size: SeriesPattern19[StoredU32] = SeriesPattern19(client, 'base_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.first_txin_index: SeriesPattern19[TxInIndex] = SeriesPattern19(client, 'first_txin_index') self.first_txout_index: SeriesPattern19[TxOutIndex] = SeriesPattern19(client, 'first_txout_index')