From 51e058e08ee90a54012facf825ef4a1944101a66 Mon Sep 17 00:00:00 2001 From: nym21 Date: Sat, 8 Aug 2026 00:33:32 +0200 Subject: [PATCH] global: txs add series and charts --- crates/brk_client/src/lib.rs | 86 ++++--- .../per_block/computed/cumulative_rolling.rs | 82 +++++- .../brk_computer/src/transactions/compute.rs | 12 +- .../src/transactions/features/compute.rs | 30 +++ .../src/transactions/features/import.rs | 35 +++ .../src/transactions/features/mod.rs | 5 + .../src/transactions/features/vecs.rs | 33 +++ .../src/transactions/fees/compute.rs | 12 +- .../src/transactions/fees/import.rs | 22 +- .../src/transactions/fees/vecs.rs | 9 +- .../brk_computer/src/transactions/import.rs | 13 +- crates/brk_computer/src/transactions/mod.rs | 6 + .../src/transactions/patterns/compute.rs | 14 +- .../src/transactions/patterns/import.rs | 35 ++- .../src/transactions/patterns/vecs.rs | 16 +- .../src/transactions/policy/compute.rs | 20 +- .../src/transactions/policy/import.rs | 23 +- .../src/transactions/policy/mod.rs | 1 + .../src/transactions/policy/vecs.rs | 12 +- .../src/transactions/sigops/compute.rs | 25 ++ .../src/transactions/sigops/import.rs | 28 +++ .../src/transactions/sigops/mod.rs | 5 + .../src/transactions/sigops/vecs.rs | 11 + .../src/vecs/transactions/features/counts.rs | 4 +- .../src/vecs/transactions/features/flags.rs | 2 +- .../src/vecs/transactions/features/mod.rs | 2 +- .../src/vecs/transactions/features/schema.rs | 16 +- crates/vecdb/src/variants/lazy/agg/sparse.rs | 41 ++- modules/brk-client/index.js | 72 +++--- packages/brk_client/brk_client/__init__.py | 41 +-- website/llms-full.txt | 4 +- website/llms.txt | 4 +- website/scripts/options/network.js | 98 +------- .../scripts/options/network/transactions.js | 233 ++++++++++++++++++ website/scripts/utils/units.js | 1 + website_next/llms-full.txt | 4 +- website_next/llms.txt | 4 +- 37 files changed, 831 insertions(+), 230 deletions(-) create mode 100644 crates/brk_computer/src/transactions/features/compute.rs create mode 100644 crates/brk_computer/src/transactions/features/import.rs create mode 100644 crates/brk_computer/src/transactions/features/mod.rs create mode 100644 crates/brk_computer/src/transactions/features/vecs.rs create mode 100644 crates/brk_computer/src/transactions/sigops/compute.rs create mode 100644 crates/brk_computer/src/transactions/sigops/import.rs create mode 100644 crates/brk_computer/src/transactions/sigops/mod.rs create mode 100644 crates/brk_computer/src/transactions/sigops/vecs.rs create mode 100644 website/scripts/options/network/transactions.js diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 882080cd6..e84b4e8d2 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -4288,6 +4288,7 @@ pub struct SeriesTree_Transactions { pub fees: SeriesTree_Transactions_Fees, pub patterns: SeriesTree_Transactions_Patterns, pub policy: SeriesTree_Transactions_Policy, + pub sigops: SeriesTree_Transactions_Sigops, pub versions: SeriesTree_Transactions_Versions, pub volume: SeriesTree_Transactions_Volume, } @@ -4302,6 +4303,7 @@ impl SeriesTree_Transactions { fees: SeriesTree_Transactions_Fees::new(client.clone(), format!("{base_path}_fees")), patterns: SeriesTree_Transactions_Patterns::new(client.clone(), format!("{base_path}_patterns")), policy: SeriesTree_Transactions_Policy::new(client.clone(), format!("{base_path}_policy")), + sigops: SeriesTree_Transactions_Sigops::new(client.clone(), format!("{base_path}_sigops")), versions: SeriesTree_Transactions_Versions::new(client.clone(), format!("{base_path}_versions")), volume: SeriesTree_Transactions_Volume::new(client.clone(), format!("{base_path}_volume")), } @@ -4416,14 +4418,14 @@ pub struct SeriesTree_Transactions_Features_Count { pub unknown: SeriesPattern18, pub fake_pubkey: SeriesPattern18, pub fake_scripthash: SeriesPattern18, - pub inscription: SeriesPattern18, - pub annex: SeriesPattern18, - pub sighash_all: SeriesPattern18, - pub sighash_none: SeriesPattern18, - pub sighash_single: SeriesPattern18, - pub sighash_default: SeriesPattern18, - pub sighash_anyone_can_pay: SeriesPattern18, - pub dust_output: SeriesPattern18, + pub inscription: AverageBlockCumulativeSumPattern, + pub annex: AverageBlockCumulativeSumPattern, + pub sighash_all: AverageBlockCumulativeSumPattern, + pub sighash_none: AverageBlockCumulativeSumPattern, + pub sighash_single: AverageBlockCumulativeSumPattern, + pub sighash_default: AverageBlockCumulativeSumPattern, + pub sighash_anyone_can_pay: AverageBlockCumulativeSumPattern, + pub dust_output: AverageBlockCumulativeSumPattern, } impl SeriesTree_Transactions_Features_Count { @@ -4449,14 +4451,14 @@ impl SeriesTree_Transactions_Features_Count { unknown: SeriesPattern18::new(client.clone(), "tx_count_unknown".to_string()), fake_pubkey: SeriesPattern18::new(client.clone(), "tx_count_fake_pubkey".to_string()), fake_scripthash: SeriesPattern18::new(client.clone(), "tx_count_fake_scripthash".to_string()), - inscription: SeriesPattern18::new(client.clone(), "tx_count_inscription".to_string()), - annex: SeriesPattern18::new(client.clone(), "tx_count_annex".to_string()), - sighash_all: SeriesPattern18::new(client.clone(), "tx_count_sighash_all".to_string()), - sighash_none: SeriesPattern18::new(client.clone(), "tx_count_sighash_none".to_string()), - sighash_single: SeriesPattern18::new(client.clone(), "tx_count_sighash_single".to_string()), - sighash_default: SeriesPattern18::new(client.clone(), "tx_count_sighash_default".to_string()), - sighash_anyone_can_pay: SeriesPattern18::new(client.clone(), "tx_count_sighash_anyone_can_pay".to_string()), - dust_output: SeriesPattern18::new(client.clone(), "tx_count_dust_output".to_string()), + inscription: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_inscription".to_string()), + annex: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_annex".to_string()), + sighash_all: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_sighash_all".to_string()), + sighash_none: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_sighash_none".to_string()), + sighash_single: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_sighash_single".to_string()), + sighash_default: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_sighash_default".to_string()), + sighash_anyone_can_pay: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_sighash_anyone_can_pay".to_string()), + dust_output: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_dust_output".to_string()), } } } @@ -4550,15 +4552,15 @@ impl SeriesTree_Transactions_Fees { /// Series tree node. pub struct SeriesTree_Transactions_Fees_Count { - pub cpfp_parent: SeriesPattern18, - pub cpfp_child: SeriesPattern18, + pub cpfp_parent: AverageBlockCumulativeSumPattern, + pub cpfp_child: AverageBlockCumulativeSumPattern, } impl SeriesTree_Transactions_Fees_Count { pub fn new(client: Arc, base_path: String) -> Self { Self { - cpfp_parent: SeriesPattern18::new(client.clone(), "cpfp_parent_count".to_string()), - cpfp_child: SeriesPattern18::new(client.clone(), "cpfp_child_count".to_string()), + cpfp_parent: AverageBlockCumulativeSumPattern::new(client.clone(), "cpfp_parent_count".to_string()), + cpfp_child: AverageBlockCumulativeSumPattern::new(client.clone(), "cpfp_child_count".to_string()), } } } @@ -4584,36 +4586,62 @@ impl SeriesTree_Transactions_Patterns { /// Series tree node. pub struct SeriesTree_Transactions_Patterns_Count { - pub coinjoin: SeriesPattern18, - pub consolidation: SeriesPattern18, - pub batch_payout: SeriesPattern18, + pub coinjoin: AverageBlockCumulativeSumPattern, + pub consolidation: AverageBlockCumulativeSumPattern, + pub batch_payout: AverageBlockCumulativeSumPattern, } impl SeriesTree_Transactions_Patterns_Count { pub fn new(client: Arc, base_path: String) -> Self { Self { - coinjoin: SeriesPattern18::new(client.clone(), "coinjoin_count".to_string()), - consolidation: SeriesPattern18::new(client.clone(), "consolidation_count".to_string()), - batch_payout: SeriesPattern18::new(client.clone(), "batch_payout_count".to_string()), + coinjoin: AverageBlockCumulativeSumPattern::new(client.clone(), "coinjoin_count".to_string()), + consolidation: AverageBlockCumulativeSumPattern::new(client.clone(), "consolidation_count".to_string()), + batch_payout: AverageBlockCumulativeSumPattern::new(client.clone(), "batch_payout_count".to_string()), } } } /// Series tree node. pub struct SeriesTree_Transactions_Policy { - pub count: SeriesPattern18, + pub count: SeriesTree_Transactions_Policy_Count, pub is_nonstandard: SeriesPattern19, } impl SeriesTree_Transactions_Policy { pub fn new(client: Arc, base_path: String) -> Self { Self { - count: SeriesPattern18::new(client.clone(), "nonstandard_count".to_string()), + count: SeriesTree_Transactions_Policy_Count::new(client.clone(), format!("{base_path}_count")), is_nonstandard: SeriesPattern19::new(client.clone(), "is_nonstandard".to_string()), } } } +/// Series tree node. +pub struct SeriesTree_Transactions_Policy_Count { + pub nonstandard: AverageBlockCumulativeSumPattern, +} + +impl SeriesTree_Transactions_Policy_Count { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + nonstandard: AverageBlockCumulativeSumPattern::new(client.clone(), "nonstandard_count".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Transactions_Sigops { + pub total: AverageBlockCumulativeSumPattern, +} + +impl SeriesTree_Transactions_Sigops { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + total: AverageBlockCumulativeSumPattern::new(client.clone(), "total_sigop_cost".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Transactions_Versions { pub v1: AverageBlockCumulativeSumPattern, @@ -9758,7 +9786,7 @@ pub struct BrkClient { impl BrkClient { /// Client version. - pub const VERSION: &'static str = "v0.11.0"; + pub const VERSION: &'static str = "v0.11.1"; /// Create a new client with the given base URL. pub fn new(base_url: impl Into) -> Self { diff --git a/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs index c4b27ca59..084a4b18c 100644 --- a/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs +++ b/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs @@ -8,7 +8,8 @@ use brk_traversable::Traversable; use brk_types::{Height, Version}; use schemars::JsonSchema; use vecdb::{ - AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecValue, WritableVec, + AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, VecValue, + WritableVec, }; use super::lazy_cumulative_rolling::lazy_parts; @@ -122,6 +123,85 @@ where )?) } + #[allow(clippy::too_many_arguments)] + pub(crate) fn compute_cumulative_sum_from_indexes( + &mut self, + max_from: Height, + first_indexes: &impl ReadableVec, + indexes_count: &impl ReadableVec, + source: &impl ReadableVec, + mut transform: impl FnMut(S) -> T, + exit: &Exit, + ) -> Result<()> + where + A: VecIndex + VecValue, + B: VecValue, + S: VecValue, + usize: From, + T: Copy, + { + let target = &mut self.cumulative.height; + target.validate_computed_version_or_reset( + first_indexes.version() + indexes_count.version() + source.version(), + )?; + target.truncate_if_needed(max_from)?; + target.repeat_until_complete(exit, |target| { + let skip = target.len(); + let end = target + .batch_end(indexes_count.len()) + .min(first_indexes.len()); + if skip >= end { + return Ok(()); + } + + let source_start = first_indexes.collect_one_at(skip).unwrap().to_usize(); + let counts = indexes_count + .collect_range_at(skip, end) + .into_iter() + .map(usize::from) + .collect::>(); + let source_end = source_start + counts.iter().sum::(); + let mut cumulative = skip + .checked_sub(1) + .and_then(|index| target.collect_one_at(index)) + .unwrap_or_default(); + let mut group_index = 0; + + while group_index < counts.len() && counts[group_index] == 0 { + target.push(cumulative); + group_index += 1; + } + + if group_index < counts.len() { + let mut remaining = counts[group_index]; + source.fold_range_at(source_start, source_end, T::default(), |sum, value| { + let sum = sum + transform(value); + remaining -= 1; + if remaining == 0 { + cumulative += sum; + target.push(cumulative); + group_index += 1; + while group_index < counts.len() && counts[group_index] == 0 { + target.push(cumulative); + group_index += 1; + } + if group_index < counts.len() { + remaining = counts[group_index]; + } + T::default() + } else { + sum + } + }); + } + + Ok(()) + })?; + + self.last_cumulative = None; + Ok(()) + } + pub(crate) fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> { self.cumulative .height diff --git a/crates/brk_computer/src/transactions/compute.rs b/crates/brk_computer/src/transactions/compute.rs index af0d246bc..bc579d7d5 100644 --- a/crates/brk_computer/src/transactions/compute.rs +++ b/crates/brk_computer/src/transactions/compute.rs @@ -18,8 +18,13 @@ impl Vecs { ) -> Result<()> { self.db.sync_bg_tasks()?; - let (r1, (r2, r3)) = rayon::join( - || self.count.compute(indexer, &blocks.lookback, exit), + let ((r1, r2), (r3, r4)) = rayon::join( + || { + rayon::join( + || self.count.compute(indexer, &blocks.lookback, exit), + || self.features.compute(indexer, exit), + ) + }, || { rayon::join( || self.versions.compute(indexer, exit), @@ -30,6 +35,9 @@ impl Vecs { r1?; r2?; r3?; + r4?; + + self.sigops.compute(indexer, indexes, exit)?; self.fees .compute(indexer, &inputs.value, indexes, &self.size, exit)?; diff --git a/crates/brk_computer/src/transactions/features/compute.rs b/crates/brk_computer/src/transactions/features/compute.rs new file mode 100644 index 000000000..63e4cc153 --- /dev/null +++ b/crates/brk_computer/src/transactions/features/compute.rs @@ -0,0 +1,30 @@ +use brk_error::Result; +use brk_indexer::Indexer; +use vecdb::Exit; + +use super::Vecs; + +impl Vecs { + pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> { + let starting_height = indexer.safe_lengths().height; + let source = &indexer.vecs.transaction_features.count; + + for (metrics, source) in [ + (&mut self.count.inscription, &source.inscription), + (&mut self.count.annex, &source.annex), + (&mut self.count.sighash_all, &source.sighash_all), + (&mut self.count.sighash_none, &source.sighash_none), + (&mut self.count.sighash_single, &source.sighash_single), + (&mut self.count.sighash_default, &source.sighash_default), + ( + &mut self.count.sighash_anyone_can_pay, + &source.sighash_anyone_can_pay, + ), + (&mut self.count.dust_output, &source.dust_output), + ] { + metrics.compute_cumulative(starting_height, source, exit)?; + } + + Ok(()) + } +} diff --git a/crates/brk_computer/src/transactions/features/import.rs b/crates/brk_computer/src/transactions/features/import.rs new file mode 100644 index 000000000..10a176c42 --- /dev/null +++ b/crates/brk_computer/src/transactions/features/import.rs @@ -0,0 +1,35 @@ +use brk_error::Result; +use brk_types::Version; +use vecdb::Database; + +use super::{CountVecs, Vecs}; +use crate::{ + indexes, + internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, +}; + +impl Vecs { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Result { + let import = |name| { + PerBlockCumulativeRolling::forced_import(db, name, version, indexes, cached_starts) + }; + + Ok(Self { + count: CountVecs { + inscription: import("tx_count_inscription")?, + annex: import("tx_count_annex")?, + sighash_all: import("tx_count_sighash_all")?, + sighash_none: import("tx_count_sighash_none")?, + sighash_single: import("tx_count_sighash_single")?, + sighash_default: import("tx_count_sighash_default")?, + sighash_anyone_can_pay: import("tx_count_sighash_anyone_can_pay")?, + dust_output: import("tx_count_dust_output")?, + }, + }) + } +} diff --git a/crates/brk_computer/src/transactions/features/mod.rs b/crates/brk_computer/src/transactions/features/mod.rs new file mode 100644 index 000000000..a3b2299b5 --- /dev/null +++ b/crates/brk_computer/src/transactions/features/mod.rs @@ -0,0 +1,5 @@ +mod compute; +mod import; +mod vecs; + +pub use vecs::{CountVecs, Vecs}; diff --git a/crates/brk_computer/src/transactions/features/vecs.rs b/crates/brk_computer/src/transactions/features/vecs.rs new file mode 100644 index 000000000..46c4e3131 --- /dev/null +++ b/crates/brk_computer/src/transactions/features/vecs.rs @@ -0,0 +1,33 @@ +use brk_traversable::Traversable; +use brk_types::StoredU64; +use vecdb::{Rw, StorageMode}; + +use crate::internal::PerBlockCumulativeRolling; + +/// Transaction counts by detected feature. +/// +/// Each metric counts transactions containing the feature at least once, not +/// individual occurrences. A transaction can contribute to multiple metrics. +#[derive(Traversable)] +pub struct CountVecs { + pub inscription: PerBlockCumulativeRolling, + pub annex: PerBlockCumulativeRolling, + /// Transactions containing at least one `SIGHASH_ALL` signature. + pub sighash_all: PerBlockCumulativeRolling, + /// Transactions containing at least one `SIGHASH_NONE` signature. + pub sighash_none: PerBlockCumulativeRolling, + /// Transactions containing at least one `SIGHASH_SINGLE` signature. + pub sighash_single: PerBlockCumulativeRolling, + /// Transactions containing at least one Taproot `SIGHASH_DEFAULT` signature. + pub sighash_default: PerBlockCumulativeRolling, + /// Transactions containing at least one `SIGHASH_ANYONECANPAY` signature. + /// + /// This modifier is counted independently from ALL, NONE, and SINGLE. + pub sighash_anyone_can_pay: PerBlockCumulativeRolling, + pub dust_output: PerBlockCumulativeRolling, +} + +#[derive(Traversable)] +pub struct Vecs { + pub count: CountVecs, +} diff --git a/crates/brk_computer/src/transactions/fees/compute.rs b/crates/brk_computer/src/transactions/fees/compute.rs index 87ce58593..90c5c90b6 100644 --- a/crates/brk_computer/src/transactions/fees/compute.rs +++ b/crates/brk_computer/src/transactions/fees/compute.rs @@ -131,8 +131,10 @@ impl Vecs { let count_len = self .count .cpfp_parent + .cumulative + .height .len() - .min(self.count.cpfp_child.len()) + .min(self.count.cpfp_child.cumulative.height.len()) .min(max_height); let start_height = count_len.min(next_height); if start_height >= max_height { @@ -241,8 +243,12 @@ impl Vecs { self.is_cpfp_parent.push(StoredBool::from(is_parent)); self.is_cpfp_child.push(StoredBool::from(is_child)); } - self.count.cpfp_parent.push(StoredU64::from(parent_count)); - self.count.cpfp_child.push(StoredU64::from(child_count)); + self.count + .cpfp_parent + .push_block(StoredU64::from(parent_count)); + self.count + .cpfp_child + .push_block(StoredU64::from(child_count)); if h % 1_000 == 0 { let _lock = exit.lock(); diff --git a/crates/brk_computer/src/transactions/fees/import.rs b/crates/brk_computer/src/transactions/fees/import.rs index e18bbc1d8..d73de75ac 100644 --- a/crates/brk_computer/src/transactions/fees/import.rs +++ b/crates/brk_computer/src/transactions/fees/import.rs @@ -3,7 +3,10 @@ use brk_types::Version; use vecdb::{Database, EagerVec, ImportableVec}; use super::{CountVecs, Vecs}; -use crate::{indexes, internal::PerTxDistribution}; +use crate::{ + indexes, + internal::{CachedWindowStartVec, PerBlockCumulativeRolling, PerTxDistribution, Windows}, +}; /// Bump this when fee/feerate aggregation logic changes (e.g., skip coinbase, skip zero-fee). const VERSION: Version = Version::new(3); @@ -13,12 +16,25 @@ impl Vecs { db: &Database, version: Version, indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { let v = version + VERSION; Ok(Self { count: CountVecs { - cpfp_parent: EagerVec::forced_import(db, "cpfp_parent_count", version)?, - cpfp_child: EagerVec::forced_import(db, "cpfp_child_count", version)?, + cpfp_parent: PerBlockCumulativeRolling::forced_import( + db, + "cpfp_parent_count", + version, + indexes, + cached_starts, + )?, + cpfp_child: PerBlockCumulativeRolling::forced_import( + db, + "cpfp_child_count", + version, + indexes, + cached_starts, + )?, }, input_value: EagerVec::forced_import(db, "input_value", version)?, output_value: EagerVec::forced_import(db, "output_value", version)?, diff --git a/crates/brk_computer/src/transactions/fees/vecs.rs b/crates/brk_computer/src/transactions/fees/vecs.rs index cd4b71ae0..c2d4b4328 100644 --- a/crates/brk_computer/src/transactions/fees/vecs.rs +++ b/crates/brk_computer/src/transactions/fees/vecs.rs @@ -1,13 +1,14 @@ use brk_traversable::Traversable; -use brk_types::{FeeRate, Height, Sats, StoredBool, StoredU64, TxIndex}; +use brk_types::{FeeRate, Sats, StoredBool, StoredU64, TxIndex}; use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; -use crate::internal::PerTxDistribution; +use crate::internal::{PerBlockCumulativeRolling, PerTxDistribution}; +/// Confirmed transactions participating in same-block CPFP clusters. #[derive(Traversable)] pub struct CountVecs { - pub cpfp_parent: M::Stored>>, - pub cpfp_child: M::Stored>>, + pub cpfp_parent: PerBlockCumulativeRolling, + pub cpfp_child: PerBlockCumulativeRolling, } #[derive(Traversable)] diff --git a/crates/brk_computer/src/transactions/import.rs b/crates/brk_computer/src/transactions/import.rs index 9dc6f7c56..dff4e6ad7 100644 --- a/crates/brk_computer/src/transactions/import.rs +++ b/crates/brk_computer/src/transactions/import.rs @@ -13,7 +13,8 @@ use crate::{ }; use super::{ - CountVecs, FeesVecs, PatternsVecs, PolicyVecs, SizeVecs, Vecs, VersionsVecs, VolumeVecs, + CountVecs, FeaturesVecs, FeesVecs, PatternsVecs, PolicyVecs, SigopsVecs, SizeVecs, Vecs, + VersionsVecs, VolumeVecs, }; impl Vecs { @@ -28,10 +29,12 @@ impl Vecs { let version = parent_version; let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?; + let features = FeaturesVecs::forced_import(&db, version, indexes, cached_starts)?; let size = SizeVecs::forced_import(&db, version, indexer, indexes)?; - let fees = FeesVecs::forced_import(&db, version, indexes)?; - let patterns = PatternsVecs::forced_import(&db, version)?; - let policy = PolicyVecs::forced_import(&db, version)?; + let fees = FeesVecs::forced_import(&db, version, indexes, cached_starts)?; + let patterns = PatternsVecs::forced_import(&db, version, indexes, cached_starts)?; + let policy = PolicyVecs::forced_import(&db, version, indexes, cached_starts)?; + let sigops = SigopsVecs::forced_import(&db, version, indexes, cached_starts)?; let versions = VersionsVecs::forced_import(&db, version, indexes, cached_starts)?; let volume = VolumeVecs::forced_import( &db, @@ -44,10 +47,12 @@ impl Vecs { let this = Self { db, count, + features, size, fees, patterns, policy, + sigops, versions, volume, }; diff --git a/crates/brk_computer/src/transactions/mod.rs b/crates/brk_computer/src/transactions/mod.rs index 57ecf3f93..3f63b5f2a 100644 --- a/crates/brk_computer/src/transactions/mod.rs +++ b/crates/brk_computer/src/transactions/mod.rs @@ -1,7 +1,9 @@ pub mod count; +pub mod features; pub mod fees; pub mod patterns; pub mod policy; +pub mod sigops; pub mod size; pub mod versions; pub mod volume; @@ -13,9 +15,11 @@ use brk_traversable::Traversable; use vecdb::{Database, Rw, StorageMode}; pub use count::Vecs as CountVecs; +pub use features::Vecs as FeaturesVecs; pub use fees::Vecs as FeesVecs; pub use patterns::Vecs as PatternsVecs; pub use policy::Vecs as PolicyVecs; +pub use sigops::Vecs as SigopsVecs; pub use size::Vecs as SizeVecs; pub use versions::Vecs as VersionsVecs; pub use volume::Vecs as VolumeVecs; @@ -28,10 +32,12 @@ pub struct Vecs { pub(crate) db: Database, pub count: CountVecs, + pub features: FeaturesVecs, pub size: SizeVecs, pub fees: FeesVecs, pub patterns: PatternsVecs, pub policy: PolicyVecs, + pub sigops: SigopsVecs, pub versions: VersionsVecs, pub volume: VolumeVecs, } diff --git a/crates/brk_computer/src/transactions/patterns/compute.rs b/crates/brk_computer/src/transactions/patterns/compute.rs index 7a794429e..506fb76e6 100644 --- a/crates/brk_computer/src/transactions/patterns/compute.rs +++ b/crates/brk_computer/src/transactions/patterns/compute.rs @@ -59,9 +59,11 @@ impl Vecs { let count_len = self .count .coinjoin + .cumulative + .height .len() - .min(self.count.consolidation.len()) - .min(self.count.batch_payout.len()) + .min(self.count.consolidation.cumulative.height.len()) + .min(self.count.batch_payout.cumulative.height.len()) .min(starting_lengths.height.to_usize()); let start_height = count_len.min(next_height(indexes, tx_len, target_tx, target_height)); if start_height >= target_height { @@ -174,13 +176,15 @@ impl Vecs { self.is_batch_payout.push(StoredBool::from(batch_payout)); } - self.count.coinjoin.push(StoredU64::from(coinjoin_count)); + self.count + .coinjoin + .push_block(StoredU64::from(coinjoin_count)); self.count .consolidation - .push(StoredU64::from(consolidation_count)); + .push_block(StoredU64::from(consolidation_count)); self.count .batch_payout - .push(StoredU64::from(batch_payout_count)); + .push_block(StoredU64::from(batch_payout_count)); if (height + 1).is_multiple_of(WRITE_INTERVAL) { let _lock = exit.lock(); diff --git a/crates/brk_computer/src/transactions/patterns/import.rs b/crates/brk_computer/src/transactions/patterns/import.rs index 859b4ff8d..75cff9747 100644 --- a/crates/brk_computer/src/transactions/patterns/import.rs +++ b/crates/brk_computer/src/transactions/patterns/import.rs @@ -3,14 +3,41 @@ use brk_types::Version; use vecdb::{Database, EagerVec, ImportableVec}; use super::{CountVecs, Vecs}; +use crate::{ + indexes, + internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, +}; impl Vecs { - pub(crate) fn forced_import(db: &Database, version: Version) -> Result { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Result { Ok(Self { count: CountVecs { - coinjoin: EagerVec::forced_import(db, "coinjoin_count", version)?, - consolidation: EagerVec::forced_import(db, "consolidation_count", version)?, - batch_payout: EagerVec::forced_import(db, "batch_payout_count", version)?, + coinjoin: PerBlockCumulativeRolling::forced_import( + db, + "coinjoin_count", + version, + indexes, + cached_starts, + )?, + consolidation: PerBlockCumulativeRolling::forced_import( + db, + "consolidation_count", + version, + indexes, + cached_starts, + )?, + batch_payout: PerBlockCumulativeRolling::forced_import( + db, + "batch_payout_count", + version, + indexes, + cached_starts, + )?, }, is_coinjoin: EagerVec::forced_import(db, "is_coinjoin", version)?, is_consolidation: EagerVec::forced_import(db, "is_consolidation", version)?, diff --git a/crates/brk_computer/src/transactions/patterns/vecs.rs b/crates/brk_computer/src/transactions/patterns/vecs.rs index a484f4c8b..ae8b88875 100644 --- a/crates/brk_computer/src/transactions/patterns/vecs.rs +++ b/crates/brk_computer/src/transactions/patterns/vecs.rs @@ -1,12 +1,20 @@ use brk_traversable::Traversable; -use brk_types::{Height, StoredBool, StoredU64, TxIndex}; +use brk_types::{StoredBool, StoredU64, TxIndex}; use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; +use crate::internal::PerBlockCumulativeRolling; + +/// Transaction counts by detected structural pattern. +/// +/// These are heuristic classifications of transactions, not protocol labels. #[derive(Traversable)] pub struct CountVecs { - pub coinjoin: M::Stored>>, - pub consolidation: M::Stored>>, - pub batch_payout: M::Stored>>, + /// CoinJoin candidates with repeated output values and no address reuse. + pub coinjoin: PerBlockCumulativeRolling, + /// Transactions with at least five times as many inputs as outputs. + pub consolidation: PerBlockCumulativeRolling, + /// Non-coinbase transactions with at least five times as many outputs as inputs. + pub batch_payout: PerBlockCumulativeRolling, } #[derive(Traversable)] diff --git a/crates/brk_computer/src/transactions/policy/compute.rs b/crates/brk_computer/src/transactions/policy/compute.rs index cd1a25d3a..1253e9e76 100644 --- a/crates/brk_computer/src/transactions/policy/compute.rs +++ b/crates/brk_computer/src/transactions/policy/compute.rs @@ -25,7 +25,9 @@ impl Vecs { + indexes.height.tx_index_count.version(); self.is_nonstandard .validate_computed_version_or_reset(version)?; - self.count.validate_computed_version_or_reset(version)?; + self.count + .nonstandard + .validate_computed_version_or_reset(version)?; let starting_lengths = indexer.safe_lengths(); let target_tx = fees.fee.tx_index.len(); @@ -34,7 +36,13 @@ impl Vecs { .is_nonstandard .len() .min(starting_lengths.tx_index.to_usize()); - let count_len = self.count.len().min(starting_lengths.height.to_usize()); + let count_len = self + .count + .nonstandard + .cumulative + .height + .len() + .min(starting_lengths.height.to_usize()); let next_height = if tx_len >= target_tx { target_height } else { @@ -52,7 +60,7 @@ impl Vecs { let first_tx = &indexer.vecs.transactions.first_tx_index; let start_tx = first_tx.collect_one_at(start_height).unwrap().to_usize(); self.is_nonstandard.truncate_if_needed_at(start_tx)?; - self.count.truncate_if_needed_at(start_height)?; + self.count.nonstandard.truncate_if_needed_at(start_height)?; let mut unconditional = features.is_unconditionally_nonstandard.cursor(); let mut has_dust = features.has_dust_output.cursor(); @@ -84,12 +92,12 @@ impl Vecs { count += nonstandard as u64; self.is_nonstandard.push(StoredBool::from(nonstandard)); } - self.count.push(StoredU64::from(count)); + self.count.nonstandard.push_block(StoredU64::from(count)); if (height + 1).is_multiple_of(WRITE_INTERVAL) { let _lock = exit.lock(); self.is_nonstandard.write()?; - self.count.write()?; + self.count.nonstandard.write()?; } block_start = block_end; @@ -97,7 +105,7 @@ impl Vecs { let _lock = exit.lock(); self.is_nonstandard.write()?; - self.count.write()?; + self.count.nonstandard.write()?; Ok(()) } } diff --git a/crates/brk_computer/src/transactions/policy/import.rs b/crates/brk_computer/src/transactions/policy/import.rs index 55c8df60e..0593c64da 100644 --- a/crates/brk_computer/src/transactions/policy/import.rs +++ b/crates/brk_computer/src/transactions/policy/import.rs @@ -2,12 +2,29 @@ use brk_error::Result; use brk_types::Version; use vecdb::{Database, EagerVec, ImportableVec}; -use super::Vecs; +use super::{CountVecs, Vecs}; +use crate::{ + indexes, + internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, +}; impl Vecs { - pub(crate) fn forced_import(db: &Database, version: Version) -> Result { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Result { Ok(Self { - count: EagerVec::forced_import(db, "nonstandard_count", version)?, + count: CountVecs { + nonstandard: PerBlockCumulativeRolling::forced_import( + db, + "nonstandard_count", + version, + indexes, + cached_starts, + )?, + }, is_nonstandard: EagerVec::forced_import(db, "is_nonstandard", version)?, }) } diff --git a/crates/brk_computer/src/transactions/policy/mod.rs b/crates/brk_computer/src/transactions/policy/mod.rs index 1136f9ebd..044cd4389 100644 --- a/crates/brk_computer/src/transactions/policy/mod.rs +++ b/crates/brk_computer/src/transactions/policy/mod.rs @@ -2,4 +2,5 @@ mod compute; mod import; mod vecs; +pub use vecs::CountVecs; pub use vecs::Vecs; diff --git a/crates/brk_computer/src/transactions/policy/vecs.rs b/crates/brk_computer/src/transactions/policy/vecs.rs index 7529ccf8e..37582b354 100644 --- a/crates/brk_computer/src/transactions/policy/vecs.rs +++ b/crates/brk_computer/src/transactions/policy/vecs.rs @@ -1,9 +1,17 @@ use brk_traversable::Traversable; -use brk_types::{Height, StoredBool, StoredU64, TxIndex}; +use brk_types::{StoredBool, StoredU64, TxIndex}; use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; +use crate::internal::PerBlockCumulativeRolling; + +/// Transactions that do not satisfy the standard relay policy at their height. +#[derive(Traversable)] +pub struct CountVecs { + pub nonstandard: PerBlockCumulativeRolling, +} + #[derive(Traversable)] pub struct Vecs { - pub count: M::Stored>>, + pub count: CountVecs, pub is_nonstandard: M::Stored>>, } diff --git a/crates/brk_computer/src/transactions/sigops/compute.rs b/crates/brk_computer/src/transactions/sigops/compute.rs new file mode 100644 index 000000000..31900066b --- /dev/null +++ b/crates/brk_computer/src/transactions/sigops/compute.rs @@ -0,0 +1,25 @@ +use brk_error::Result; +use brk_indexer::Indexer; +use brk_types::StoredU64; +use vecdb::Exit; + +use super::Vecs; +use crate::indexes; + +impl Vecs { + pub(crate) fn compute( + &mut self, + indexer: &Indexer, + indexes: &indexes::Vecs, + exit: &Exit, + ) -> Result<()> { + self.total.compute_cumulative_sum_from_indexes( + indexer.safe_lengths().height, + &indexer.vecs.transactions.first_tx_index, + &indexes.height.tx_index_count, + &indexer.vecs.transactions.total_sigop_cost, + |value| StoredU64::from(u64::from(u32::from(value))), + exit, + ) + } +} diff --git a/crates/brk_computer/src/transactions/sigops/import.rs b/crates/brk_computer/src/transactions/sigops/import.rs new file mode 100644 index 000000000..956e34ba6 --- /dev/null +++ b/crates/brk_computer/src/transactions/sigops/import.rs @@ -0,0 +1,28 @@ +use brk_error::Result; +use brk_types::Version; +use vecdb::Database; + +use super::Vecs; +use crate::{ + indexes, + internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, +}; + +impl Vecs { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Result { + Ok(Self { + total: PerBlockCumulativeRolling::forced_import( + db, + "total_sigop_cost", + version, + indexes, + cached_starts, + )?, + }) + } +} diff --git a/crates/brk_computer/src/transactions/sigops/mod.rs b/crates/brk_computer/src/transactions/sigops/mod.rs new file mode 100644 index 000000000..1136f9ebd --- /dev/null +++ b/crates/brk_computer/src/transactions/sigops/mod.rs @@ -0,0 +1,5 @@ +mod compute; +mod import; +mod vecs; + +pub use vecs::Vecs; diff --git a/crates/brk_computer/src/transactions/sigops/vecs.rs b/crates/brk_computer/src/transactions/sigops/vecs.rs new file mode 100644 index 000000000..75afd9ff2 --- /dev/null +++ b/crates/brk_computer/src/transactions/sigops/vecs.rs @@ -0,0 +1,11 @@ +use brk_traversable::Traversable; +use brk_types::StoredU64; +use vecdb::{Rw, StorageMode}; + +use crate::internal::PerBlockCumulativeRolling; + +#[derive(Traversable)] +pub struct Vecs { + /// Total BIP-141 sigop cost, not a raw opcode count. + pub total: PerBlockCumulativeRolling, +} diff --git a/crates/brk_indexer/src/vecs/transactions/features/counts.rs b/crates/brk_indexer/src/vecs/transactions/features/counts.rs index ad871cd1a..e69e4843b 100644 --- a/crates/brk_indexer/src/vecs/transactions/features/counts.rs +++ b/crates/brk_indexer/src/vecs/transactions/features/counts.rs @@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, Database, ImportableVec, PcoVec, Rw, Stamp, StorageMod use super::schema::with_transaction_features; macro_rules! define_counts { - ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident)?;)+) => { + ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident $(, count_attr: $count_attr:meta)?)?;)+) => { #[derive(Default)] pub(crate) struct TransactionCounts { v1: u64, @@ -49,7 +49,7 @@ macro_rules! define_counts { pub explicitly_rbf: M::Stored>, pub one_input: M::Stored>, pub one_output: M::Stored>, - $($(pub $count: M::Stored>,)?) + + $($($(#[$count_attr])* pub $count: M::Stored>,)?) + } impl TransactionCountVecs { diff --git a/crates/brk_indexer/src/vecs/transactions/features/flags.rs b/crates/brk_indexer/src/vecs/transactions/features/flags.rs index 78044f44b..c6a7732fa 100644 --- a/crates/brk_indexer/src/vecs/transactions/features/flags.rs +++ b/crates/brk_indexer/src/vecs/transactions/features/flags.rs @@ -6,7 +6,7 @@ use super::schema::with_transaction_features; pub(crate) struct TxFeatureFlags(u32); macro_rules! define_flags { - ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident)?;)+) => { + ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident $(, count_attr: $count_attr:meta)?)?;)+) => { impl TxFeatureFlags { $(pub(crate) const $flag: u32 = 1 << $bit;)+ } diff --git a/crates/brk_indexer/src/vecs/transactions/features/mod.rs b/crates/brk_indexer/src/vecs/transactions/features/mod.rs index 54900509e..cdbecaa4b 100644 --- a/crates/brk_indexer/src/vecs/transactions/features/mod.rs +++ b/crates/brk_indexer/src/vecs/transactions/features/mod.rs @@ -16,7 +16,7 @@ use self::schema::with_transaction_features; use crate::parallel_import; macro_rules! define_vecs { - ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident)?;)+) => { + ($($(#[$attribute:meta])* $vector:ident: $flag:ident = $bit:literal $(, count: $count:ident $(, count_attr: $count_attr:meta)?)?;)+) => { #[derive(Traversable)] pub struct TransactionFeaturesVecs { pub count: TransactionCountVecs, diff --git a/crates/brk_indexer/src/vecs/transactions/features/schema.rs b/crates/brk_indexer/src/vecs/transactions/features/schema.rs index 96dd1a9f8..11189d6af 100644 --- a/crates/brk_indexer/src/vecs/transactions/features/schema.rs +++ b/crates/brk_indexer/src/vecs/transactions/features/schema.rs @@ -14,16 +14,16 @@ macro_rules! with_transaction_features { has_unknown: UNKNOWN = 10, count: unknown; has_fake_pubkey: FAKE_PUBKEY = 11, count: fake_pubkey; has_fake_scripthash: FAKE_SCRIPTHASH = 12, count: fake_scripthash; - has_inscription: INSCRIPTION = 13, count: inscription; - has_annex: ANNEX = 14, count: annex; - has_sighash_all: SIGHASH_ALL = 15, count: sighash_all; - has_sighash_none: SIGHASH_NONE = 16, count: sighash_none; - has_sighash_single: SIGHASH_SINGLE = 17, count: sighash_single; - has_sighash_default: SIGHASH_DEFAULT = 18, count: sighash_default; - has_sighash_anyone_can_pay: SIGHASH_ANYONE_CAN_PAY = 19, count: sighash_anyone_can_pay; + has_inscription: INSCRIPTION = 13, count: inscription, count_attr: traversable(hidden); + has_annex: ANNEX = 14, count: annex, count_attr: traversable(hidden); + has_sighash_all: SIGHASH_ALL = 15, count: sighash_all, count_attr: traversable(hidden); + has_sighash_none: SIGHASH_NONE = 16, count: sighash_none, count_attr: traversable(hidden); + has_sighash_single: SIGHASH_SINGLE = 17, count: sighash_single, count_attr: traversable(hidden); + has_sighash_default: SIGHASH_DEFAULT = 18, count: sighash_default, count_attr: traversable(hidden); + has_sighash_anyone_can_pay: SIGHASH_ANYONE_CAN_PAY = 19, count: sighash_anyone_can_pay, count_attr: traversable(hidden); #[traversable(hidden)] is_unconditionally_nonstandard: UNCONDITIONALLY_NONSTANDARD = 20; - has_dust_output: DUST_OUTPUT = 21, count: dust_output; + has_dust_output: DUST_OUTPUT = 21, count: dust_output, count_attr: traversable(hidden); } }; } diff --git a/crates/vecdb/src/variants/lazy/agg/sparse.rs b/crates/vecdb/src/variants/lazy/agg/sparse.rs index 345679cf7..2924755b4 100644 --- a/crates/vecdb/src/variants/lazy/agg/sparse.rs +++ b/crates/vecdb/src/variants/lazy/agg/sparse.rs @@ -26,7 +26,8 @@ impl AggFold, SI, SI, T> for Sparse { let next_first = mapping .get(idx + 1) .map(|h| h.to_usize()) - .unwrap_or(source_len); + .unwrap_or(source_len) + .min(source_len); if next_first == 0 || current_first >= next_first { slot_map.push(None); @@ -55,7 +56,8 @@ impl AggFold, SI, SI, T> for Sparse { let next_first = mapping .get(index + 1) .map(|h| h.to_usize()) - .unwrap_or(source_len); + .unwrap_or(source_len) + .min(source_len); if next_first == 0 || current_first >= next_first { return Some(None); @@ -63,3 +65,38 @@ impl AggFold, SI, SI, T> for Sparse { Some(source.collect_one_at(next_first - 1)) } } + +#[cfg(test)] +mod tests { + use super::Sparse; + use crate::{AggFold, BytesVec, ImportableVec, Version, WritableVec}; + + #[test] + fn clamps_partial_final_range_to_source_length() { + let temp = tempfile::tempdir().unwrap(); + let db = crate::Database::open(temp.path()).unwrap(); + let mut source: BytesVec = + BytesVec::forced_import(&db, "source", Version::ONE).unwrap(); + + for value in [10, 20, 30] { + source.push(value); + } + + let mapping = [0, 2, 4]; + let values = Sparse::fold( + &source, + &mapping, + 0, + mapping.len(), + Vec::new(), + |mut values, value| { + values.push(value); + values + }, + ); + + assert_eq!(values, [Some(20), Some(30), None]); + assert_eq!(Sparse::collect_one(&source, &mapping, 1), Some(Some(30))); + assert_eq!(Sparse::collect_one(&source, &mapping, 2), Some(None)); + } +} diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 9b1f46ba0..f5ac62ec4 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -5944,6 +5944,7 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Transactions_Fees} fees * @property {SeriesTree_Transactions_Patterns} patterns * @property {SeriesTree_Transactions_Policy} policy + * @property {SeriesTree_Transactions_Sigops} sigops * @property {SeriesTree_Transactions_Versions} versions * @property {SeriesTree_Transactions_Volume} volume */ @@ -6010,14 +6011,14 @@ function createUnspentPattern(client, acc) { * @property {SeriesPattern18} unknown * @property {SeriesPattern18} fakePubkey * @property {SeriesPattern18} fakeScripthash - * @property {SeriesPattern18} inscription - * @property {SeriesPattern18} annex - * @property {SeriesPattern18} sighashAll - * @property {SeriesPattern18} sighashNone - * @property {SeriesPattern18} sighashSingle - * @property {SeriesPattern18} sighashDefault - * @property {SeriesPattern18} sighashAnyoneCanPay - * @property {SeriesPattern18} dustOutput + * @property {AverageBlockCumulativeSumPattern} inscription + * @property {AverageBlockCumulativeSumPattern} annex + * @property {AverageBlockCumulativeSumPattern} sighashAll + * @property {AverageBlockCumulativeSumPattern} sighashNone + * @property {AverageBlockCumulativeSumPattern} sighashSingle + * @property {AverageBlockCumulativeSumPattern} sighashDefault + * @property {AverageBlockCumulativeSumPattern} sighashAnyoneCanPay + * @property {AverageBlockCumulativeSumPattern} dustOutput */ /** @@ -6058,8 +6059,8 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Transactions_Fees_Count - * @property {SeriesPattern18} cpfpParent - * @property {SeriesPattern18} cpfpChild + * @property {AverageBlockCumulativeSumPattern} cpfpParent + * @property {AverageBlockCumulativeSumPattern} cpfpChild */ /** @@ -6072,17 +6073,27 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Transactions_Patterns_Count - * @property {SeriesPattern18} coinjoin - * @property {SeriesPattern18} consolidation - * @property {SeriesPattern18} batchPayout + * @property {AverageBlockCumulativeSumPattern} coinjoin + * @property {AverageBlockCumulativeSumPattern} consolidation + * @property {AverageBlockCumulativeSumPattern} batchPayout */ /** * @typedef {Object} SeriesTree_Transactions_Policy - * @property {SeriesPattern18} count + * @property {SeriesTree_Transactions_Policy_Count} count * @property {SeriesPattern19} isNonstandard */ +/** + * @typedef {Object} SeriesTree_Transactions_Policy_Count + * @property {AverageBlockCumulativeSumPattern} nonstandard + */ + +/** + * @typedef {Object} SeriesTree_Transactions_Sigops + * @property {AverageBlockCumulativeSumPattern} total + */ + /** * @typedef {Object} SeriesTree_Transactions_Versions * @property {AverageBlockCumulativeSumPattern} v1 @@ -9734,14 +9745,14 @@ class BrkClient extends BrkClientBase { unknown: createSeriesPattern18(this, 'tx_count_unknown'), fakePubkey: createSeriesPattern18(this, 'tx_count_fake_pubkey'), fakeScripthash: createSeriesPattern18(this, 'tx_count_fake_scripthash'), - inscription: createSeriesPattern18(this, 'tx_count_inscription'), - annex: createSeriesPattern18(this, 'tx_count_annex'), - sighashAll: createSeriesPattern18(this, 'tx_count_sighash_all'), - sighashNone: createSeriesPattern18(this, 'tx_count_sighash_none'), - sighashSingle: createSeriesPattern18(this, 'tx_count_sighash_single'), - sighashDefault: createSeriesPattern18(this, 'tx_count_sighash_default'), - sighashAnyoneCanPay: createSeriesPattern18(this, 'tx_count_sighash_anyone_can_pay'), - dustOutput: createSeriesPattern18(this, 'tx_count_dust_output'), + inscription: createAverageBlockCumulativeSumPattern(this, 'tx_count_inscription'), + annex: createAverageBlockCumulativeSumPattern(this, 'tx_count_annex'), + sighashAll: createAverageBlockCumulativeSumPattern(this, 'tx_count_sighash_all'), + sighashNone: createAverageBlockCumulativeSumPattern(this, 'tx_count_sighash_none'), + sighashSingle: createAverageBlockCumulativeSumPattern(this, 'tx_count_sighash_single'), + sighashDefault: createAverageBlockCumulativeSumPattern(this, 'tx_count_sighash_default'), + sighashAnyoneCanPay: createAverageBlockCumulativeSumPattern(this, 'tx_count_sighash_anyone_can_pay'), + dustOutput: createAverageBlockCumulativeSumPattern(this, 'tx_count_dust_output'), }, hasP2pk: createSeriesPattern19(this, 'has_p2pk'), hasP2ms: createSeriesPattern19(this, 'has_p2ms'), @@ -9781,8 +9792,8 @@ class BrkClient extends BrkClientBase { }, fees: { count: { - cpfpParent: createSeriesPattern18(this, 'cpfp_parent_count'), - cpfpChild: createSeriesPattern18(this, 'cpfp_child_count'), + cpfpParent: createAverageBlockCumulativeSumPattern(this, 'cpfp_parent_count'), + cpfpChild: createAverageBlockCumulativeSumPattern(this, 'cpfp_child_count'), }, inputValue: createSeriesPattern19(this, 'input_value'), outputValue: createSeriesPattern19(this, 'output_value'), @@ -9794,18 +9805,23 @@ class BrkClient extends BrkClientBase { }, patterns: { count: { - coinjoin: createSeriesPattern18(this, 'coinjoin_count'), - consolidation: createSeriesPattern18(this, 'consolidation_count'), - batchPayout: createSeriesPattern18(this, 'batch_payout_count'), + coinjoin: createAverageBlockCumulativeSumPattern(this, 'coinjoin_count'), + consolidation: createAverageBlockCumulativeSumPattern(this, 'consolidation_count'), + batchPayout: createAverageBlockCumulativeSumPattern(this, 'batch_payout_count'), }, isCoinjoin: createSeriesPattern19(this, 'is_coinjoin'), isConsolidation: createSeriesPattern19(this, 'is_consolidation'), isBatchPayout: createSeriesPattern19(this, 'is_batch_payout'), }, policy: { - count: createSeriesPattern18(this, 'nonstandard_count'), + count: { + nonstandard: createAverageBlockCumulativeSumPattern(this, 'nonstandard_count'), + }, isNonstandard: createSeriesPattern19(this, 'is_nonstandard'), }, + sigops: { + total: createAverageBlockCumulativeSumPattern(this, 'total_sigop_cost'), + }, versions: { v1: createAverageBlockCumulativeSumPattern(this, 'tx_v1'), v2: createAverageBlockCumulativeSumPattern(this, 'tx_v2'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index e774416e9..9cd246c0f 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -4639,14 +4639,14 @@ class SeriesTree_Transactions_Features_Count: self.unknown: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_unknown') self.fake_pubkey: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_fake_pubkey') self.fake_scripthash: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_fake_scripthash') - self.inscription: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_inscription') - self.annex: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_annex') - self.sighash_all: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_sighash_all') - self.sighash_none: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_sighash_none') - self.sighash_single: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_sighash_single') - self.sighash_default: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_sighash_default') - self.sighash_anyone_can_pay: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_sighash_anyone_can_pay') - self.dust_output: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'tx_count_dust_output') + self.inscription: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_inscription') + self.annex: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_annex') + self.sighash_all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_sighash_all') + self.sighash_none: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_sighash_none') + self.sighash_single: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_sighash_single') + self.sighash_default: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_sighash_default') + self.sighash_anyone_can_pay: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_sighash_anyone_can_pay') + self.dust_output: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_dust_output') class SeriesTree_Transactions_Features: """Series tree node.""" @@ -4707,8 +4707,8 @@ class SeriesTree_Transactions_Fees_Count: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.cpfp_parent: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'cpfp_parent_count') - self.cpfp_child: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'cpfp_child_count') + self.cpfp_parent: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'cpfp_parent_count') + self.cpfp_child: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'cpfp_child_count') class SeriesTree_Transactions_Fees: """Series tree node.""" @@ -4727,9 +4727,9 @@ class SeriesTree_Transactions_Patterns_Count: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.coinjoin: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'coinjoin_count') - self.consolidation: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'consolidation_count') - self.batch_payout: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'batch_payout_count') + self.coinjoin: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'coinjoin_count') + self.consolidation: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'consolidation_count') + self.batch_payout: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'batch_payout_count') class SeriesTree_Transactions_Patterns: """Series tree node.""" @@ -4740,13 +4740,25 @@ class SeriesTree_Transactions_Patterns: self.is_consolidation: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_consolidation') self.is_batch_payout: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_batch_payout') +class SeriesTree_Transactions_Policy_Count: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.nonstandard: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'nonstandard_count') + class SeriesTree_Transactions_Policy: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.count: SeriesPattern18[StoredU64] = SeriesPattern18(client, 'nonstandard_count') + self.count: SeriesTree_Transactions_Policy_Count = SeriesTree_Transactions_Policy_Count(client) self.is_nonstandard: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_nonstandard') +class SeriesTree_Transactions_Sigops: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.total: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'total_sigop_cost') + class SeriesTree_Transactions_Versions: """Series tree node.""" @@ -4774,6 +4786,7 @@ class SeriesTree_Transactions: self.fees: SeriesTree_Transactions_Fees = SeriesTree_Transactions_Fees(client) self.patterns: SeriesTree_Transactions_Patterns = SeriesTree_Transactions_Patterns(client) self.policy: SeriesTree_Transactions_Policy = SeriesTree_Transactions_Policy(client) + self.sigops: SeriesTree_Transactions_Sigops = SeriesTree_Transactions_Sigops(client) self.versions: SeriesTree_Transactions_Versions = SeriesTree_Transactions_Versions(client) self.volume: SeriesTree_Transactions_Volume = SeriesTree_Transactions_Volume(client) diff --git a/website/llms-full.txt b/website/llms-full.txt index 51fcf98b5..e24e3b265 100644 --- a/website/llms-full.txt +++ b/website/llms-full.txt @@ -2,10 +2,10 @@ > Generated from BRK's OpenAPI specification and metric tree. Do not edit this file manually. -- Version: `v0.11.0` +- Version: `v0.11.1` - Base URL: https://bitview.space - MCP endpoint: https://mcp.bitview.space/ -- Metrics: 57886 +- Metrics: 58022 - Operations: 97 For machine-readable tool construction, use [https://bitview.space/openapi.json](https://bitview.space/openapi.json). For the complete source-derived series tree, use [https://bitview.space/api/series](https://bitview.space/api/series). diff --git a/website/llms.txt b/website/llms.txt index 7fe0329df..337f56412 100644 --- a/website/llms.txt +++ b/website/llms.txt @@ -1,10 +1,10 @@ # Bitcoin Research Kit (BRK) -> Free, open-source Bitcoin analytics API and block explorer. 57886 on-chain time-series and 97 API operations. No authentication required. +> Free, open-source Bitcoin analytics API and block explorer. 58022 on-chain time-series and 97 API operations. No authentication required. ## API -- Version: `v0.11.0` +- Version: `v0.11.1` - Base URL: https://bitview.space - [Full plain-text reference](https://bitview.space/llms-full.txt) - [Compact OpenAPI](https://bitview.space/api.json) diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index c3e64f02f..67d3e67c0 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -3,15 +3,12 @@ import { colors } from "../utils/colors.js"; import { brk } from "../utils/client.js"; import { Unit } from "../utils/units.js"; -import { entries } from "../utils/array.js"; import { line, baseline, fromSupplyPattern, chartsFromFull, - chartsFromFullPerBlock, chartsFromCount, - chartsFromCountEntries, chartsFromPercentCumulative, chartsFromPercentCumulativeEntries, chartsFromAggregatedPerBlock, @@ -19,7 +16,6 @@ import { averagesArray, simpleDeltaTree, ROLLING_WINDOWS, - chartsFromBlockAnd6b, multiSeriesTree, percentRatio, percentRatioDots, @@ -27,7 +23,6 @@ import { import { satsBtcUsd, satsBtcUsdFrom, - satsBtcUsdFullTree, formatCohortTitle, groupedWindowsCumulative, avgHoldingsSubtree, @@ -36,6 +31,7 @@ import { reusedSubtree, } from "./shared.js"; import { createOpReturnSection } from "./network/op-return.js"; +import { createTransactionsSection } from "./network/transactions.js"; /** * Create Network section @@ -1121,97 +1117,7 @@ export function createNetworkSection() { ], }, - // Transactions - { - name: "Transactions", - tree: [ - { - name: "Count", - tree: chartsFromFullPerBlock({ - pattern: transactions.count.total, - metric: "Transaction Count", - unit: Unit.count, - }), - }, - { - name: "Per Second", - tree: averagesArray({ - windows: transactions.volume.txPerSec, - metric: "Transactions per Second", - unit: Unit.perSec, - }), - }, - { - name: "Volume", - tree: satsBtcUsdFullTree({ - pattern: transactions.volume.transferVolume, - metric: "Transaction Volume", - }), - }, - { - name: "Effective Fee Rate", - tree: chartsFromBlockAnd6b({ - pattern: transactions.fees.effectiveFeeRate, - metric: "Effective Transaction Fee Rate", - unit: Unit.feeRate, - }), - }, - { - name: "Fee", - tree: chartsFromBlockAnd6b({ - pattern: transactions.fees.fee, - metric: "Transaction Fee", - unit: Unit.sats, - }), - }, - { - name: "Size", - tree: [ - { - name: "Weight", - tree: chartsFromBlockAnd6b({ - pattern: transactions.size.weight, - metric: "Transaction Weight", - unit: Unit.wu, - }), - }, - { - name: "Virtual", - tree: chartsFromBlockAnd6b({ - pattern: transactions.size.vsize, - metric: "Transaction vSize", - unit: Unit.vb, - }), - }, - ], - }, - { - name: "Versions", - tree: chartsFromCountEntries({ - entries: entries(transactions.versions), - metric: "Transaction Versions", - unit: Unit.count, - }), - }, - { - name: "Velocity", - title: "Transaction Velocity", - bottom: [ - line({ - series: supply.velocity.native, - name: "BTC", - unit: Unit.ratio, - }), - line({ - series: supply.velocity.fiat, - name: "USD", - color: colors.usd, - unit: Unit.ratio, - }), - ], - }, - ], - }, + createTransactionsSection(), createOpReturnSection(), diff --git a/website/scripts/options/network/transactions.js b/website/scripts/options/network/transactions.js new file mode 100644 index 000000000..6fdcf943d --- /dev/null +++ b/website/scripts/options/network/transactions.js @@ -0,0 +1,233 @@ +import { entries } from "../../utils/array.js"; +import { brk } from "../../utils/client.js"; +import { colors } from "../../utils/colors.js"; +import { Unit } from "../../utils/units.js"; +import { + averagesArray, + chartsFromBlockAnd6b, + chartsFromCount, + chartsFromCountEntries, + chartsFromFullPerBlock, + line, +} from "../series.js"; +import { satsBtcUsdFullTree } from "../shared.js"; + +/** + * @param {Object} args + * @param {string} args.name + * @param {string} args.metric + * @param {CountPattern} args.pattern + * @param {Unit} [args.unit] + * @returns {PartialOptionsGroup} + */ +function createCountFolder({ name, metric, pattern, unit = Unit.count }) { + return { + name, + tree: chartsFromCount({ pattern, metric, unit }), + }; +} + +/** + * Create the transaction section of the Network tree. + * + * Common metrics stay shallow. Specialist metrics are grouped by the concept + * users are looking for, while every leaf remains an independent chart. + * + * @returns {PartialOptionsGroup} + */ +export function createTransactionsSection() { + const { transactions, supply } = brk.series; + const featureCount = transactions.features.count; + + return { + name: "Transactions", + tree: [ + { + name: "Count", + tree: chartsFromFullPerBlock({ + pattern: transactions.count.total, + metric: "Transaction Count", + unit: Unit.count, + }), + }, + { + name: "Per Second", + tree: averagesArray({ + windows: transactions.volume.txPerSec, + metric: "Transactions per Second", + unit: Unit.perSec, + }), + }, + { + name: "Volume", + tree: satsBtcUsdFullTree({ + pattern: transactions.volume.transferVolume, + metric: "Transaction Volume", + }), + }, + { + name: "Velocity", + title: "Transaction Velocity", + bottom: [ + line({ + series: supply.velocity.native, + name: "BTC", + unit: Unit.ratio, + }), + line({ + series: supply.velocity.fiat, + name: "USD", + color: colors.usd, + unit: Unit.ratio, + }), + ], + }, + { + name: "Effective Fee Rate", + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.effectiveFeeRate, + metric: "Effective Transaction Fee Rate", + unit: Unit.feeRate, + }), + }, + { + name: "Fee", + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.fee, + metric: "Transaction Fee", + unit: Unit.sats, + }), + }, + { + name: "CPFP", + tree: [ + createCountFolder({ + name: "Parents", + metric: "Confirmed CPFP Parent Transactions", + pattern: transactions.fees.count.cpfpParent, + }), + createCountFolder({ + name: "Children", + metric: "Confirmed CPFP Child Transactions", + pattern: transactions.fees.count.cpfpChild, + }), + ], + }, + { + name: "Size", + tree: [ + { + name: "Weight", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.weight, + metric: "Transaction Weight", + unit: Unit.wu, + }), + }, + { + name: "Virtual Size", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.vsize, + metric: "Transaction Virtual Size", + unit: Unit.vb, + }), + }, + ], + }, + createCountFolder({ + name: "Sigops", + metric: "Total Sigop Cost", + pattern: transactions.sigops.total, + unit: Unit.sigopCost, + }), + { + name: "Sighashes", + tree: [ + createCountFolder({ + name: "ALL", + metric: "Transactions with SIGHASH_ALL", + pattern: featureCount.sighashAll, + }), + createCountFolder({ + name: "NONE", + metric: "Transactions with SIGHASH_NONE", + pattern: featureCount.sighashNone, + }), + createCountFolder({ + name: "SINGLE", + metric: "Transactions with SIGHASH_SINGLE", + pattern: featureCount.sighashSingle, + }), + createCountFolder({ + name: "DEFAULT", + metric: "Transactions with SIGHASH_DEFAULT", + pattern: featureCount.sighashDefault, + }), + createCountFolder({ + name: "ANYONECANPAY", + metric: "Transactions with SIGHASH_ANYONECANPAY", + pattern: featureCount.sighashAnyoneCanPay, + }), + ], + }, + { + name: "Data", + tree: [ + createCountFolder({ + name: "Inscriptions", + metric: "Transactions with Inscriptions", + pattern: featureCount.inscription, + }), + createCountFolder({ + name: "Taproot Annexes", + metric: "Transactions with Taproot Annexes", + pattern: featureCount.annex, + }), + ], + }, + { + name: "Patterns", + tree: [ + createCountFolder({ + name: "CoinJoins", + metric: "Detected CoinJoin Transactions", + pattern: transactions.patterns.count.coinjoin, + }), + createCountFolder({ + name: "Consolidations", + metric: "Detected Consolidation Transactions", + pattern: transactions.patterns.count.consolidation, + }), + createCountFolder({ + name: "Batch Payouts", + metric: "Detected Batch Payout Transactions", + pattern: transactions.patterns.count.batchPayout, + }), + ], + }, + { + name: "Policy", + tree: [ + createCountFolder({ + name: "Dust Outputs", + metric: "Transactions with Dust Outputs", + pattern: featureCount.dustOutput, + }), + createCountFolder({ + name: "Nonstandard", + metric: "Nonstandard Transactions", + pattern: transactions.policy.count.nonstandard, + }), + ], + }, + { + name: "Versions", + tree: chartsFromCountEntries({ + entries: entries(transactions.versions), + metric: "Transaction Versions", + unit: Unit.count, + }), + }, + ], + }; +} diff --git a/website/scripts/utils/units.js b/website/scripts/utils/units.js index 599378dca..4704168b5 100644 --- a/website/scripts/utils/units.js +++ b/website/scripts/utils/units.js @@ -28,6 +28,7 @@ export const Unit = /** @type {const} */ ({ // Counts count: { id: "count", name: "Count" }, blocks: { id: "blocks", name: "Blocks" }, + sigopCost: { id: "sigop-cost", name: "Sigop Cost" }, // Size bytes: { id: "bytes", name: "Bytes" }, diff --git a/website_next/llms-full.txt b/website_next/llms-full.txt index 51fcf98b5..e24e3b265 100644 --- a/website_next/llms-full.txt +++ b/website_next/llms-full.txt @@ -2,10 +2,10 @@ > Generated from BRK's OpenAPI specification and metric tree. Do not edit this file manually. -- Version: `v0.11.0` +- Version: `v0.11.1` - Base URL: https://bitview.space - MCP endpoint: https://mcp.bitview.space/ -- Metrics: 57886 +- Metrics: 58022 - Operations: 97 For machine-readable tool construction, use [https://bitview.space/openapi.json](https://bitview.space/openapi.json). For the complete source-derived series tree, use [https://bitview.space/api/series](https://bitview.space/api/series). diff --git a/website_next/llms.txt b/website_next/llms.txt index 7fe0329df..337f56412 100644 --- a/website_next/llms.txt +++ b/website_next/llms.txt @@ -1,10 +1,10 @@ # Bitcoin Research Kit (BRK) -> Free, open-source Bitcoin analytics API and block explorer. 57886 on-chain time-series and 97 API operations. No authentication required. +> Free, open-source Bitcoin analytics API and block explorer. 58022 on-chain time-series and 97 API operations. No authentication required. ## API -- Version: `v0.11.0` +- Version: `v0.11.1` - Base URL: https://bitview.space - [Full plain-text reference](https://bitview.space/llms-full.txt) - [Compact OpenAPI](https://bitview.space/api.json)