global: txs add series and charts

This commit is contained in:
nym21
2026-08-08 00:33:32 +02:00
parent 31748a712d
commit 51e058e08e
37 changed files with 831 additions and 230 deletions
+57 -29
View File
@@ -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<StoredU64>,
pub fake_pubkey: SeriesPattern18<StoredU64>,
pub fake_scripthash: SeriesPattern18<StoredU64>,
pub inscription: SeriesPattern18<StoredU64>,
pub annex: SeriesPattern18<StoredU64>,
pub sighash_all: SeriesPattern18<StoredU64>,
pub sighash_none: SeriesPattern18<StoredU64>,
pub sighash_single: SeriesPattern18<StoredU64>,
pub sighash_default: SeriesPattern18<StoredU64>,
pub sighash_anyone_can_pay: SeriesPattern18<StoredU64>,
pub dust_output: SeriesPattern18<StoredU64>,
pub inscription: AverageBlockCumulativeSumPattern<StoredU64>,
pub annex: AverageBlockCumulativeSumPattern<StoredU64>,
pub sighash_all: AverageBlockCumulativeSumPattern<StoredU64>,
pub sighash_none: AverageBlockCumulativeSumPattern<StoredU64>,
pub sighash_single: AverageBlockCumulativeSumPattern<StoredU64>,
pub sighash_default: AverageBlockCumulativeSumPattern<StoredU64>,
pub sighash_anyone_can_pay: AverageBlockCumulativeSumPattern<StoredU64>,
pub dust_output: AverageBlockCumulativeSumPattern<StoredU64>,
}
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<StoredU64>,
pub cpfp_child: SeriesPattern18<StoredU64>,
pub cpfp_parent: AverageBlockCumulativeSumPattern<StoredU64>,
pub cpfp_child: AverageBlockCumulativeSumPattern<StoredU64>,
}
impl SeriesTree_Transactions_Fees_Count {
pub fn new(client: Arc<BrkClientBase>, 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<StoredU64>,
pub consolidation: SeriesPattern18<StoredU64>,
pub batch_payout: SeriesPattern18<StoredU64>,
pub coinjoin: AverageBlockCumulativeSumPattern<StoredU64>,
pub consolidation: AverageBlockCumulativeSumPattern<StoredU64>,
pub batch_payout: AverageBlockCumulativeSumPattern<StoredU64>,
}
impl SeriesTree_Transactions_Patterns_Count {
pub fn new(client: Arc<BrkClientBase>, 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<StoredU64>,
pub count: SeriesTree_Transactions_Policy_Count,
pub is_nonstandard: SeriesPattern19<StoredBool>,
}
impl SeriesTree_Transactions_Policy {
pub fn new(client: Arc<BrkClientBase>, 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<StoredU64>,
}
impl SeriesTree_Transactions_Policy_Count {
pub fn new(client: Arc<BrkClientBase>, 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<StoredU64>,
}
impl SeriesTree_Transactions_Sigops {
pub fn new(client: Arc<BrkClientBase>, 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<StoredU64>,
@@ -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<String>) -> Self {
@@ -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<A, B, S>(
&mut self,
max_from: Height,
first_indexes: &impl ReadableVec<Height, A>,
indexes_count: &impl ReadableVec<Height, B>,
source: &impl ReadableVec<A, S>,
mut transform: impl FnMut(S) -> T,
exit: &Exit,
) -> Result<()>
where
A: VecIndex + VecValue,
B: VecValue,
S: VecValue,
usize: From<B>,
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::<Vec<_>>();
let source_end = source_start + counts.iter().sum::<usize>();
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
@@ -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)?;
@@ -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(())
}
}
@@ -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<Self> {
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")?,
},
})
}
}
@@ -0,0 +1,5 @@
mod compute;
mod import;
mod vecs;
pub use vecs::{CountVecs, Vecs};
@@ -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<M: StorageMode = Rw> {
pub inscription: PerBlockCumulativeRolling<StoredU64, M>,
pub annex: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions containing at least one `SIGHASH_ALL` signature.
pub sighash_all: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions containing at least one `SIGHASH_NONE` signature.
pub sighash_none: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions containing at least one `SIGHASH_SINGLE` signature.
pub sighash_single: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions containing at least one Taproot `SIGHASH_DEFAULT` signature.
pub sighash_default: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions containing at least one `SIGHASH_ANYONECANPAY` signature.
///
/// This modifier is counted independently from ALL, NONE, and SINGLE.
pub sighash_anyone_can_pay: PerBlockCumulativeRolling<StoredU64, M>,
pub dust_output: PerBlockCumulativeRolling<StoredU64, M>,
}
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub count: CountVecs<M>,
}
@@ -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();
@@ -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<Self> {
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)?,
@@ -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<M: StorageMode = Rw> {
pub cpfp_parent: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
pub cpfp_child: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
pub cpfp_parent: PerBlockCumulativeRolling<StoredU64, M>,
pub cpfp_child: PerBlockCumulativeRolling<StoredU64, M>,
}
#[derive(Traversable)]
@@ -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,
};
@@ -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<M: StorageMode = Rw> {
pub(crate) db: Database,
pub count: CountVecs<M>,
pub features: FeaturesVecs<M>,
pub size: SizeVecs<M>,
pub fees: FeesVecs<M>,
pub patterns: PatternsVecs<M>,
pub policy: PolicyVecs<M>,
pub sigops: SigopsVecs<M>,
pub versions: VersionsVecs<M>,
pub volume: VolumeVecs<M>,
}
@@ -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();
@@ -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<Self> {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&CachedWindowStartVec>,
) -> Result<Self> {
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)?,
@@ -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<M: StorageMode = Rw> {
pub coinjoin: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
pub consolidation: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
pub batch_payout: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
/// CoinJoin candidates with repeated output values and no address reuse.
pub coinjoin: PerBlockCumulativeRolling<StoredU64, M>,
/// Transactions with at least five times as many inputs as outputs.
pub consolidation: PerBlockCumulativeRolling<StoredU64, M>,
/// Non-coinbase transactions with at least five times as many outputs as inputs.
pub batch_payout: PerBlockCumulativeRolling<StoredU64, M>,
}
#[derive(Traversable)]
@@ -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(())
}
}
@@ -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<Self> {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&CachedWindowStartVec>,
) -> Result<Self> {
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)?,
})
}
@@ -2,4 +2,5 @@ mod compute;
mod import;
mod vecs;
pub use vecs::CountVecs;
pub use vecs::Vecs;
@@ -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<M: StorageMode = Rw> {
pub nonstandard: PerBlockCumulativeRolling<StoredU64, M>,
}
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub count: M::Stored<EagerVec<PcoVec<Height, StoredU64>>>,
pub count: CountVecs<M>,
pub is_nonstandard: M::Stored<EagerVec<PcoVec<TxIndex, StoredBool>>>,
}
@@ -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,
)
}
}
@@ -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<Self> {
Ok(Self {
total: PerBlockCumulativeRolling::forced_import(
db,
"total_sigop_cost",
version,
indexes,
cached_starts,
)?,
})
}
}
@@ -0,0 +1,5 @@
mod compute;
mod import;
mod vecs;
pub use vecs::Vecs;
@@ -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<M: StorageMode = Rw> {
/// Total BIP-141 sigop cost, not a raw opcode count.
pub total: PerBlockCumulativeRolling<StoredU64, M>,
}
@@ -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<PcoVec<Height, StoredU64>>,
pub one_input: M::Stored<PcoVec<Height, StoredU64>>,
pub one_output: M::Stored<PcoVec<Height, StoredU64>>,
$($(pub $count: M::Stored<PcoVec<Height, StoredU64>>,)?) +
$($($(#[$count_attr])* pub $count: M::Stored<PcoVec<Height, StoredU64>>,)?) +
}
impl TransactionCountVecs {
@@ -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;)+
}
@@ -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<M: StorageMode = Rw> {
pub count: TransactionCountVecs<M>,
@@ -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);
}
};
}
+39 -2
View File
@@ -26,7 +26,8 @@ impl<T: VecValue, SI: VecIndex> AggFold<Option<T>, 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<T: VecValue, SI: VecIndex> AggFold<Option<T>, 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<T: VecValue, SI: VecIndex> AggFold<Option<T>, 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<usize, u64> =
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));
}
}
+44 -28
View File
@@ -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<StoredU64>} unknown
* @property {SeriesPattern18<StoredU64>} fakePubkey
* @property {SeriesPattern18<StoredU64>} fakeScripthash
* @property {SeriesPattern18<StoredU64>} inscription
* @property {SeriesPattern18<StoredU64>} annex
* @property {SeriesPattern18<StoredU64>} sighashAll
* @property {SeriesPattern18<StoredU64>} sighashNone
* @property {SeriesPattern18<StoredU64>} sighashSingle
* @property {SeriesPattern18<StoredU64>} sighashDefault
* @property {SeriesPattern18<StoredU64>} sighashAnyoneCanPay
* @property {SeriesPattern18<StoredU64>} dustOutput
* @property {AverageBlockCumulativeSumPattern<StoredU64>} inscription
* @property {AverageBlockCumulativeSumPattern<StoredU64>} annex
* @property {AverageBlockCumulativeSumPattern<StoredU64>} sighashAll
* @property {AverageBlockCumulativeSumPattern<StoredU64>} sighashNone
* @property {AverageBlockCumulativeSumPattern<StoredU64>} sighashSingle
* @property {AverageBlockCumulativeSumPattern<StoredU64>} sighashDefault
* @property {AverageBlockCumulativeSumPattern<StoredU64>} sighashAnyoneCanPay
* @property {AverageBlockCumulativeSumPattern<StoredU64>} dustOutput
*/
/**
@@ -6058,8 +6059,8 @@ function createUnspentPattern(client, acc) {
/**
* @typedef {Object} SeriesTree_Transactions_Fees_Count
* @property {SeriesPattern18<StoredU64>} cpfpParent
* @property {SeriesPattern18<StoredU64>} cpfpChild
* @property {AverageBlockCumulativeSumPattern<StoredU64>} cpfpParent
* @property {AverageBlockCumulativeSumPattern<StoredU64>} cpfpChild
*/
/**
@@ -6072,17 +6073,27 @@ function createUnspentPattern(client, acc) {
/**
* @typedef {Object} SeriesTree_Transactions_Patterns_Count
* @property {SeriesPattern18<StoredU64>} coinjoin
* @property {SeriesPattern18<StoredU64>} consolidation
* @property {SeriesPattern18<StoredU64>} batchPayout
* @property {AverageBlockCumulativeSumPattern<StoredU64>} coinjoin
* @property {AverageBlockCumulativeSumPattern<StoredU64>} consolidation
* @property {AverageBlockCumulativeSumPattern<StoredU64>} batchPayout
*/
/**
* @typedef {Object} SeriesTree_Transactions_Policy
* @property {SeriesPattern18<StoredU64>} count
* @property {SeriesTree_Transactions_Policy_Count} count
* @property {SeriesPattern19<StoredBool>} isNonstandard
*/
/**
* @typedef {Object} SeriesTree_Transactions_Policy_Count
* @property {AverageBlockCumulativeSumPattern<StoredU64>} nonstandard
*/
/**
* @typedef {Object} SeriesTree_Transactions_Sigops
* @property {AverageBlockCumulativeSumPattern<StoredU64>} total
*/
/**
* @typedef {Object} SeriesTree_Transactions_Versions
* @property {AverageBlockCumulativeSumPattern<StoredU64>} 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'),
+27 -14
View File
@@ -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)
+2 -2
View File
@@ -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).
+2 -2
View File
@@ -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)
+2 -96
View File
@@ -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(),
@@ -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<number>} 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,
}),
},
],
};
}
+1
View File
@@ -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" },
+2 -2
View File
@@ -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).
+2 -2
View File
@@ -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)