mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-08-12 02:03:09 -07:00
global: txs add series and charts
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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'),
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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,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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user