global: opreturn part 4

This commit is contained in:
nym21
2026-07-19 09:34:03 +02:00
parent 5161ae2012
commit 0fc61d7932
19 changed files with 510 additions and 108 deletions
+32 -8
View File
@@ -2291,6 +2291,28 @@ impl CapLossMvrvPriceProfitPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct CarrierDataOutputPostPattern {
pub carrier_tx_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub carrier_vsize: AverageBlockCumulativeSumPattern<VSize>,
pub data_share: BpsPercentRatioPattern2,
pub output_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub post_op_return_bytes: AverageBlockCumulativeSumPattern<StoredU64>,
}
impl CarrierDataOutputPostPattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
carrier_tx_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "carrier_tx_count")),
carrier_vsize: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "carrier_vsize")),
data_share: BpsPercentRatioPattern2::new(client.clone(), _m(&acc, "data_share")),
output_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "output_count")),
post_op_return_bytes: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "post_op_return_bytes")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct CentsToUsdPattern4 {
pub cents: SeriesPattern1<Cents>,
@@ -5388,6 +5410,7 @@ pub struct SeriesTree_OpReturn_Total {
pub post_op_return_bytes: AverageBlockCumulativeSumPattern<StoredU64>,
pub carrier_tx_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub carrier_vsize: AverageBlockCumulativeSumPattern<VSize>,
pub data_share: BpsPercentRatioPattern2,
}
impl SeriesTree_OpReturn_Total {
@@ -5396,6 +5419,7 @@ impl SeriesTree_OpReturn_Total {
post_op_return_bytes: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_post_op_return_bytes".to_string()),
carrier_tx_count: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_carrier_tx_count".to_string()),
carrier_vsize: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_carrier_vsize".to_string()),
data_share: BpsPercentRatioPattern2::new(client.clone(), "op_return_data_share".to_string()),
}
}
}
@@ -5459,19 +5483,19 @@ impl SeriesTree_OpReturn_ByKind {
/// Series tree node.
pub struct SeriesTree_OpReturn_Policy {
pub standard: CarrierOutputPostPattern,
pub oversized: CarrierOutputPostPattern,
pub multiple: CarrierOutputPostPattern,
pub pre_v30_nonstandard: CarrierOutputPostPattern,
pub standard: CarrierDataOutputPostPattern,
pub oversized: CarrierDataOutputPostPattern,
pub multiple: CarrierDataOutputPostPattern,
pub pre_v30_nonstandard: CarrierDataOutputPostPattern,
}
impl SeriesTree_OpReturn_Policy {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
standard: CarrierOutputPostPattern::new(client.clone(), "op_return_policy_standard".to_string()),
oversized: CarrierOutputPostPattern::new(client.clone(), "op_return_policy_oversized".to_string()),
multiple: CarrierOutputPostPattern::new(client.clone(), "op_return_policy_multiple".to_string()),
pre_v30_nonstandard: CarrierOutputPostPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard".to_string()),
standard: CarrierDataOutputPostPattern::new(client.clone(), "op_return_policy_standard".to_string()),
oversized: CarrierDataOutputPostPattern::new(client.clone(), "op_return_policy_oversized".to_string()),
multiple: CarrierDataOutputPostPattern::new(client.clone(), "op_return_policy_multiple".to_string()),
pre_v30_nonstandard: CarrierDataOutputPostPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard".to_string()),
}
}
}
+1 -1
View File
@@ -362,7 +362,7 @@ impl Computer {
let op_return = scope.spawn(|| {
timed("Computed OP_RETURN", || {
self.op_return.compute(indexer, exit)
self.op_return.compute(indexer, &self.blocks, exit)
})
});
+38 -2
View File
@@ -1,9 +1,13 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{OpReturnKind, VSize};
use brk_types::{BasisPoints16, Height, OpReturnKind, StoredU64, VSize};
use vecdb::{AnyVec, Exit, ReadableVec, VecIndex};
use super::{Vecs, vecs::Totals};
use crate::{
blocks,
internal::{PercentPerBlock, RatioU64Bp16},
};
const KIND_COUNT: usize = OpReturnKind::Unknown as usize + 1;
const OLD_STANDARD_MAX_POST_OP_RETURN_BYTES: u64 = 82;
@@ -40,7 +44,12 @@ impl Carrier {
}
impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
blocks: &blocks::Vecs,
exit: &Exit,
) -> Result<()> {
self.db.sync_bg_tasks()?;
let starting_lengths = indexer.safe_lengths();
@@ -132,6 +141,23 @@ impl Vecs {
}
self.compute_cumulative(starting_lengths.height, exit)?;
let block_size = &blocks.size.size.cumulative.height;
compute_data_share(
starting_lengths.height,
&mut self.total.data_share,
&self.total.metrics.post_op_return_bytes.cumulative.height,
block_size,
exit,
)?;
for policy in self.policy.iter_mut() {
compute_data_share(
starting_lengths.height,
&mut policy.data_share,
&policy.metrics.total.post_op_return_bytes.cumulative.height,
block_size,
exit,
)?;
}
let exit = exit.clone();
self.db.run_bg(move |db| {
@@ -142,6 +168,16 @@ impl Vecs {
}
}
fn compute_data_share(
max_from: Height,
target: &mut PercentPerBlock<BasisPoints16>,
data: &impl ReadableVec<Height, StoredU64>,
block_size: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
target.compute_binary::<StoredU64, StoredU64, RatioU64Bp16>(max_from, data, block_size, exit)
}
fn finalize_transaction(
total: &mut Totals,
by_kind: &mut [Totals; KIND_COUNT],
+2 -2
View File
@@ -3,7 +3,7 @@ use std::path::Path;
use brk_error::Result;
use brk_types::Version;
use super::{ByKind, Metrics, Policy, TotalMetrics, Vecs};
use super::{ByKind, Metrics, Policy, Total, Vecs};
use crate::{
indexes,
internal::{
@@ -20,7 +20,7 @@ impl Vecs {
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let db = open_db(parent_path, super::DB_NAME, 1_000_000)?;
let total = TotalMetrics::forced_import(&db, "op_return", version, indexes, cached_starts)?;
let total = Total::forced_import(&db, "op_return", version, indexes, cached_starts)?;
let by_kind = ByKind::try_new(|_, name| {
Metrics::forced_import(
&db,
+1 -1
View File
@@ -4,6 +4,6 @@ mod import;
mod vecs;
pub use by_kind::ByKind;
pub use vecs::{Metrics, Policy, TotalMetrics, Vecs};
pub use vecs::{Metrics, Policy, Total, Vecs};
pub const DB_NAME: &str = "op_return";
+76 -13
View File
@@ -1,12 +1,13 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, VSize, Version};
use brk_types::{BasisPoints16, Height, StoredU64, VSize, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use super::ByKind;
use crate::{
indexes,
internal::{PerBlockCumulativeRolling, WindowStartVec, Windows},
internal::{PerBlockCumulativeRolling, PercentPerBlock, WindowStartVec, Windows},
};
pub type Series<T, M = Rw> = PerBlockCumulativeRolling<T, T, M>;
@@ -112,6 +113,35 @@ impl TotalMetrics {
}
}
#[derive(Deref, DerefMut, Traversable)]
pub struct Total<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub metrics: TotalMetrics<M>,
pub data_share: PercentPerBlock<BasisPoints16, M>,
}
impl Total {
pub(crate) fn forced_import(
db: &Database,
prefix: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self {
metrics: TotalMetrics::forced_import(db, prefix, version, indexes, cached_starts)?,
data_share: PercentPerBlock::forced_import(
db,
&format!("{prefix}_data_share"),
version,
indexes,
)?,
})
}
}
#[derive(Traversable)]
pub struct Metrics<M: StorageMode = Rw> {
pub output_count: Series<StoredU64, M>,
@@ -175,12 +205,41 @@ impl Metrics {
}
}
#[derive(Deref, DerefMut, Traversable)]
pub struct PolicyMetrics<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub metrics: Metrics<M>,
pub data_share: PercentPerBlock<BasisPoints16, M>,
}
impl PolicyMetrics {
fn forced_import(
db: &Database,
prefix: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self {
metrics: Metrics::forced_import(db, prefix, version, indexes, cached_starts)?,
data_share: PercentPerBlock::forced_import(
db,
&format!("{prefix}_data_share"),
version,
indexes,
)?,
})
}
}
#[derive(Traversable)]
pub struct Policy<M: StorageMode = Rw> {
pub standard: Metrics<M>,
pub oversized: Metrics<M>,
pub multiple: Metrics<M>,
pub pre_v30_nonstandard: Metrics<M>,
pub standard: PolicyMetrics<M>,
pub oversized: PolicyMetrics<M>,
pub multiple: PolicyMetrics<M>,
pub pre_v30_nonstandard: PolicyMetrics<M>,
}
impl Policy {
@@ -191,7 +250,7 @@ impl Policy {
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let import = |name| {
Metrics::forced_import(
PolicyMetrics::forced_import(
db,
&format!("op_return_policy_{name}"),
version,
@@ -208,7 +267,7 @@ impl Policy {
})
}
fn iter(&self) -> impl Iterator<Item = &Metrics> {
fn iter(&self) -> impl Iterator<Item = &PolicyMetrics> {
[
&self.standard,
&self.oversized,
@@ -218,7 +277,7 @@ impl Policy {
.into_iter()
}
fn iter_mut(&mut self) -> impl Iterator<Item = &mut Metrics> {
pub(super) fn iter_mut(&mut self) -> impl Iterator<Item = &mut PolicyMetrics> {
[
&mut self.standard,
&mut self.oversized,
@@ -233,18 +292,22 @@ impl Policy {
pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub(crate) db: Database,
pub total: TotalMetrics<M>,
pub total: Total<M>,
pub by_kind: ByKind<Metrics<M>>,
pub policy: Policy<M>,
}
impl Vecs {
pub(crate) fn min_len(&self) -> usize {
self.by_kind
let len = self
.by_kind
.iter()
.chain(self.policy.iter())
.map(Metrics::len)
.fold(self.total.len(), usize::min)
.fold(self.total.len(), usize::min);
self.policy
.iter()
.map(|metrics| metrics.len())
.fold(len, usize::min)
}
pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> {
+35 -8
View File
@@ -3820,6 +3820,31 @@ function createCapLossMvrvPriceProfitPattern(client, acc) {
};
}
/**
* @typedef {Object} CarrierDataOutputPostPattern
* @property {AverageBlockCumulativeSumPattern<StoredU64>} carrierTxCount
* @property {AverageBlockCumulativeSumPattern<VSize>} carrierVsize
* @property {BpsPercentRatioPattern2} dataShare
* @property {AverageBlockCumulativeSumPattern<StoredU64>} outputCount
* @property {AverageBlockCumulativeSumPattern<StoredU64>} postOpReturnBytes
*/
/**
* Create a CarrierDataOutputPostPattern pattern node
* @param {BrkClient} client
* @param {string} acc - Accumulated series name
* @returns {CarrierDataOutputPostPattern}
*/
function createCarrierDataOutputPostPattern(client, acc) {
return {
carrierTxCount: createAverageBlockCumulativeSumPattern(client, _m(acc, 'carrier_tx_count')),
carrierVsize: createAverageBlockCumulativeSumPattern(client, _m(acc, 'carrier_vsize')),
dataShare: createBpsPercentRatioPattern2(client, _m(acc, 'data_share')),
outputCount: createAverageBlockCumulativeSumPattern(client, _m(acc, 'output_count')),
postOpReturnBytes: createAverageBlockCumulativeSumPattern(client, _m(acc, 'post_op_return_bytes')),
};
}
/**
* @typedef {Object} CentsToUsdPattern4
* @property {SeriesPattern1<Cents>} cents
@@ -6192,6 +6217,7 @@ function createTransferPattern(client, acc) {
* @property {AverageBlockCumulativeSumPattern<StoredU64>} postOpReturnBytes
* @property {AverageBlockCumulativeSumPattern<StoredU64>} carrierTxCount
* @property {AverageBlockCumulativeSumPattern<VSize>} carrierVsize
* @property {BpsPercentRatioPattern2} dataShare
*/
/**
@@ -6223,10 +6249,10 @@ function createTransferPattern(client, acc) {
/**
* @typedef {Object} SeriesTree_OpReturn_Policy
* @property {CarrierOutputPostPattern} standard
* @property {CarrierOutputPostPattern} oversized
* @property {CarrierOutputPostPattern} multiple
* @property {CarrierOutputPostPattern} preV30Nonstandard
* @property {CarrierDataOutputPostPattern} standard
* @property {CarrierDataOutputPostPattern} oversized
* @property {CarrierDataOutputPostPattern} multiple
* @property {CarrierDataOutputPostPattern} preV30Nonstandard
*/
/**
@@ -9988,6 +10014,7 @@ class BrkClient extends BrkClientBase {
postOpReturnBytes: createAverageBlockCumulativeSumPattern(this, 'op_return_post_op_return_bytes'),
carrierTxCount: createAverageBlockCumulativeSumPattern(this, 'op_return_carrier_tx_count'),
carrierVsize: createAverageBlockCumulativeSumPattern(this, 'op_return_carrier_vsize'),
dataShare: createBpsPercentRatioPattern2(this, 'op_return_data_share'),
},
byKind: {
runes: createCarrierOutputPostPattern(this, 'op_return_runes'),
@@ -10015,10 +10042,10 @@ class BrkClient extends BrkClientBase {
unknown: createCarrierOutputPostPattern(this, 'op_return_unknown'),
},
policy: {
standard: createCarrierOutputPostPattern(this, 'op_return_policy_standard'),
oversized: createCarrierOutputPostPattern(this, 'op_return_policy_oversized'),
multiple: createCarrierOutputPostPattern(this, 'op_return_policy_multiple'),
preV30Nonstandard: createCarrierOutputPostPattern(this, 'op_return_policy_pre_v30_nonstandard'),
standard: createCarrierDataOutputPostPattern(this, 'op_return_policy_standard'),
oversized: createCarrierDataOutputPostPattern(this, 'op_return_policy_oversized'),
multiple: createCarrierDataOutputPostPattern(this, 'op_return_policy_multiple'),
preV30Nonstandard: createCarrierDataOutputPostPattern(this, 'op_return_policy_pre_v30_nonstandard'),
},
},
mining: {
+16 -4
View File
@@ -3577,6 +3577,17 @@ class CapLossMvrvPriceProfitPattern:
self.price: BpsCentsRatioSatsUsdPattern = BpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price'))
self.profit: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, _m(acc, 'realized_profit'))
class CarrierDataOutputPostPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClient, acc: str):
"""Create pattern node with accumulated series name."""
self.carrier_tx_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'carrier_tx_count'))
self.carrier_vsize: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, _m(acc, 'carrier_vsize'))
self.data_share: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, _m(acc, 'data_share'))
self.output_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'output_count'))
self.post_op_return_bytes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'post_op_return_bytes'))
class CentsToUsdPattern4:
"""Pattern struct for repeated tree structure."""
@@ -5064,6 +5075,7 @@ class SeriesTree_OpReturn_Total:
self.post_op_return_bytes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_post_op_return_bytes')
self.carrier_tx_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_carrier_tx_count')
self.carrier_vsize: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_carrier_vsize')
self.data_share: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'op_return_data_share')
class SeriesTree_OpReturn_ByKind:
"""Series tree node."""
@@ -5097,10 +5109,10 @@ class SeriesTree_OpReturn_Policy:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.standard: CarrierOutputPostPattern = CarrierOutputPostPattern(client, 'op_return_policy_standard')
self.oversized: CarrierOutputPostPattern = CarrierOutputPostPattern(client, 'op_return_policy_oversized')
self.multiple: CarrierOutputPostPattern = CarrierOutputPostPattern(client, 'op_return_policy_multiple')
self.pre_v30_nonstandard: CarrierOutputPostPattern = CarrierOutputPostPattern(client, 'op_return_policy_pre_v30_nonstandard')
self.standard: CarrierDataOutputPostPattern = CarrierDataOutputPostPattern(client, 'op_return_policy_standard')
self.oversized: CarrierDataOutputPostPattern = CarrierDataOutputPostPattern(client, 'op_return_policy_oversized')
self.multiple: CarrierDataOutputPostPattern = CarrierDataOutputPostPattern(client, 'op_return_policy_multiple')
self.pre_v30_nonstandard: CarrierDataOutputPostPattern = CarrierDataOutputPostPattern(client, 'op_return_policy_pre_v30_nonstandard')
class SeriesTree_OpReturn:
"""Series tree node."""
@@ -164,7 +164,7 @@ export function createCohortFolderWithNupl(cohort) {
createValuationSectionFull({ cohort, title }),
createPricesSectionFull({ cohort, title }),
createCostBasisSectionWithPercentiles({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createProfitabilitySectionFull({ cohort, title }),
createActivitySection({ cohort, title }),
],
};
@@ -808,8 +808,8 @@ export function createProfitabilitySectionAll({ cohort, title }) {
}
/**
* Section for Full cohorts (STH)
* @param {{ cohort: CohortFull, title: (name: string) => string }} args
* Section for cohorts with full realized and unrealized profitability data.
* @param {{ cohort: { tree: { unrealized: FullRelativePattern, realized: RealizedPattern } }, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionFull({ cohort, title }) {
+18 -6
View File
@@ -34,6 +34,7 @@ import {
exposedSubtree,
reusedSubtree,
} from "./shared.js";
import { createOpReturnSection } from "./network/op-return.js";
/**
* Create Network section
@@ -829,8 +830,8 @@ export function createNetworkSection() {
* @template {string} K
* @param {Object} args
* @param {string} args.label - Singular noun for count/tree labels ("Output" / "Prev-Out")
* @param {Readonly<Record<K, CountPattern<number>>>} args.count
* @param {Readonly<Record<K, CountPattern<number>>>} args.txCount
* @param {Readonly<Record<K | "all", CountPattern<number>>>} args.count
* @param {Readonly<Record<K | "all", CountPattern<number>>>} args.txCount
* @param {Readonly<Record<K, PercentRatioCumulativePattern>>} args.share
* @param {Readonly<Record<K, PercentRatioCumulativePattern>>} args.txShare
* @param {ReadonlyArray<{key: K, name: string, color: Color, defaultActive: boolean}>} args.types
@@ -845,6 +846,15 @@ export function createNetworkSection() {
types,
}) => {
const lowerLabel = label.toLowerCase();
const countTypes = /** @type {const} */ ([
...types,
{
key: "all",
name: "All",
color: colors.default,
defaultActive: false,
},
]);
return [
{
name: "Compare",
@@ -855,7 +865,7 @@ export function createNetworkSection() {
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} ${label} Count by Type`,
bottom: types.map((t) =>
bottom: countTypes.map((t) =>
line({
series: count[t.key].sum[w.key],
name: t.name,
@@ -868,7 +878,7 @@ export function createNetworkSection() {
{
name: "Cumulative",
title: `Cumulative ${label} Count by Type`,
bottom: types.map((t) =>
bottom: countTypes.map((t) =>
line({
series: count[t.key].cumulative,
name: t.name,
@@ -898,7 +908,7 @@ export function createNetworkSection() {
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${w.title} Transactions by ${label} Type`,
bottom: types.map((t) =>
bottom: countTypes.map((t) =>
line({
series: txCount[t.key].sum[w.key],
name: t.name,
@@ -911,7 +921,7 @@ export function createNetworkSection() {
{
name: "Cumulative",
title: `Cumulative Transactions by ${label} Type`,
bottom: types.map((t) =>
bottom: countTypes.map((t) =>
line({
series: txCount[t.key].cumulative,
name: t.name,
@@ -1231,6 +1241,8 @@ export function createNetworkSection() {
],
},
createOpReturnSection(),
// UTXOs
{
name: "UTXOs",
@@ -0,0 +1,192 @@
import { colors } from "../../utils/colors.js";
import { brk } from "../../utils/client.js";
import { Unit } from "../../utils/units.js";
import { chartsFromCount, line } from "../series.js";
import { groupedWindowsCumulative } from "../shared.js";
const TYPE_DEFINITIONS = /** @type {const} */ ([
{ key: "runes", name: "Runes", defaultActive: true },
{ key: "veriBlock", name: "VeriBlock", defaultActive: true },
{ key: "omni", name: "Omni", defaultActive: true },
{ key: "stacks", name: "Stacks", defaultActive: true },
{ key: "blockstack", name: "Blockstack", defaultActive: false },
{ key: "colu", name: "Colu", defaultActive: false },
{ key: "openAssets", name: "Open Assets", defaultActive: false },
{ key: "komodo", name: "Komodo", defaultActive: false },
{ key: "coinSpark", name: "CoinSpark", defaultActive: false },
{ key: "poet", name: "Po.et", defaultActive: false },
{ key: "docproof", name: "Docproof", defaultActive: false },
{ key: "openTimestamps", name: "OpenTimestamps", defaultActive: true },
{ key: "factom", name: "Factom", defaultActive: false },
{ key: "eternityWall", name: "Eternity Wall", defaultActive: false },
{ key: "memo", name: "Memo", defaultActive: false },
{ key: "bitproof", name: "Bitproof", defaultActive: false },
{ key: "ascribe", name: "Ascribe", defaultActive: false },
{ key: "stampery", name: "Stampery", defaultActive: false },
{ key: "epobc", name: "EPOBC", defaultActive: false },
{ key: "bareHash", name: "Bare Hash", defaultActive: false },
{ key: "text", name: "Text", defaultActive: true },
{ key: "empty", name: "Empty", defaultActive: false },
{ key: "unknown", name: "Unknown", defaultActive: true },
]);
const METRICS = /** @type {const} */ ([
{
key: "carrierTxCount",
name: "Transactions",
title: "Carrier Transaction Count",
unit: Unit.count,
},
{
key: "outputCount",
name: "Outputs",
title: "Output Count",
unit: Unit.count,
},
{
key: "carrierVsize",
name: "vBytes",
title: "Carrier Transaction vBytes",
unit: Unit.vb,
},
{
key: "postOpReturnBytes",
name: "Data",
title: "Data Bytes",
unit: Unit.bytes,
},
]);
/** @typedef {(typeof brk.series.opReturn.byKind)[keyof typeof brk.series.opReturn.byKind]} OpReturnMetrics */
/**
* @param {Object} args
* @param {readonly { name: string, color: Color, defaultActive: boolean, pattern: OpReturnMetrics }[]} args.list
* @param {string} args.category
* @returns {PartialOptionsTree}
*/
function createComparisons({ list, category }) {
return METRICS.map((metric) => ({
name: metric.name,
tree: groupedWindowsCumulative({
list,
title: (title) => `OP_RETURN ${title}`,
metricTitle: `${metric.title} by ${category}`,
getWindowSeries: (entry, window) =>
entry.pattern[metric.key].sum[window],
getCumulativeSeries: (entry) =>
entry.pattern[metric.key].cumulative,
seriesFn: line,
unit: metric.unit,
}),
}));
}
/** @returns {PartialOptionsGroup} */
export function createOpReturnSection() {
const opReturn = brk.series.opReturn;
const types = TYPE_DEFINITIONS.map((type, index) => ({
...type,
color: colors.at(index, TYPE_DEFINITIONS.length),
pattern: opReturn.byKind[type.key],
}));
const policies = [
{
name: "Standard",
color: colors.profit,
defaultActive: true,
pattern: opReturn.policy.standard,
},
{
name: "Oversized",
color: colors.loss,
defaultActive: true,
pattern: opReturn.policy.oversized,
},
{
name: "Multiple Outputs",
color: colors.bitcoin,
defaultActive: true,
pattern: opReturn.policy.multiple,
},
{
name: "Pre-v30 Nonstandard",
color: colors.gray,
defaultActive: true,
pattern: opReturn.policy.preV30Nonstandard,
},
];
return {
name: "OP_RETURN",
tree: [
{
name: "Total",
tree: [
{
name: "Transactions",
tree: chartsFromCount({
pattern: opReturn.total.carrierTxCount,
metric: "OP_RETURN Carrier Transaction Count",
unit: Unit.count,
color: colors.scriptType.opReturn,
}),
},
{
name: "vBytes",
tree: chartsFromCount({
pattern: opReturn.total.carrierVsize,
metric: "OP_RETURN Carrier Transaction vBytes",
unit: Unit.vb,
color: colors.scriptType.opReturn,
}),
},
{
name: "Data",
tree: chartsFromCount({
pattern: opReturn.total.postOpReturnBytes,
metric: "OP_RETURN Data Bytes",
unit: Unit.bytes,
color: colors.scriptType.opReturn,
}),
},
{
name: "Share",
title: "Cumulative OP_RETURN Data Share of Blockchain Size",
bottom: [
line({
series: opReturn.total.dataShare.percent,
name: "OP_RETURN Data",
color: colors.scriptType.opReturn,
unit: Unit.percentage,
}),
],
},
],
},
{
name: "Type",
tree: createComparisons({ list: types, category: "Type" }),
},
{
name: "Policy",
tree: [
{
name: "Share",
title: "Cumulative OP_RETURN Data Share of Blockchain Size by Policy",
bottom: policies.map(({ name, color, defaultActive, pattern }) =>
line({
series: pattern.dataShare.percent,
name,
color,
defaultActive,
unit: Unit.percentage,
}),
),
},
...createComparisons({ list: policies, category: "Policy" }),
],
},
],
};
}
+26
View File
@@ -3,6 +3,15 @@ export const HALVING_EPOCH_BLOCKS = 210_000;
export const MAX_BLOCK_WEIGHT = 4_000_000;
const RELATIVE_TIME = new Intl.RelativeTimeFormat(undefined);
const RELATIVE_UNITS = /** @type {const} */ ([
["year", 31_557_600],
["month", 2_629_800],
["day", 86_400],
["hour", 3_600],
["minute", 60],
]);
/** @param {number} value */
export function formatNumber(value) {
return value.toLocaleString();
@@ -22,6 +31,23 @@ export function formatDateTime(unixSeconds) {
});
}
/** @param {number} unixSeconds */
export function formatDateAndAge(unixSeconds) {
const date = new Date(unixSeconds * 1_000).toLocaleDateString(undefined, {
dateStyle: "medium",
});
const difference = unixSeconds - Date.now() / 1_000;
const absolute = Math.abs(difference);
if (absolute < 60) return `${date} · just now`;
const [unit, seconds] = RELATIVE_UNITS.find(([, duration]) => {
return absolute >= duration;
}) ?? RELATIVE_UNITS.at(-1);
return `${date} · ${RELATIVE_TIME.format(Math.trunc(difference / seconds), unit)}`;
}
/** @param {number} bytes */
export function formatBytes(bytes) {
return bytes >= 1_000_000
+14 -15
View File
@@ -1,5 +1,5 @@
import { createUsdAmount, renderUsdAmount } from "../../../usd/index.js";
import { formatDateTime } from "../format.js";
import { formatDateAndAge } from "../format.js";
import { createBlockTitle } from "../title/index.js";
/** @typedef {import("../../../modules/brk-client/index.js").BlockInfoV1} Block */
@@ -24,38 +24,37 @@ function createHashElement(hash) {
/** @param {Node[]} [actions] */
export function createBlockHeader(actions = []) {
const element = document.createElement("header");
const titleRow = document.createElement("div");
const main = document.createElement("div");
const title = document.createElement("h1");
const side = document.createElement("div");
const actionList = document.createElement("div");
const date = document.createElement("time");
const hash = document.createElement("p");
const price = createUsdAmount("output", 0, {
size: "title",
tone: "positive",
});
let timestamp = 0;
let dateTimer = 0;
element.dataset.blockHeader = "";
title.dataset.blockTitleText = "";
main.dataset.blockMain = "";
titleRow.dataset.blockTitle = "";
side.dataset.blockSide = "";
actionList.dataset.blockActions = "";
date.dataset.blockDate = "";
hash.dataset.blockHashLine = "";
actionList.append(...actions);
side.append(date, price, actionList);
main.append(title, hash);
titleRow.append(main, side);
element.append(titleRow);
element.append(title, price, hash, actionList, date);
function refreshDate() {
date.textContent = formatDateAndAge(timestamp);
dateTimer = window.setTimeout(refreshDate, 60_000);
}
/** @param {Block} block */
function update(block) {
title.replaceChildren(...createBlockTitle(block.height));
date.dateTime = new Date(block.timestamp * 1_000).toISOString();
date.textContent = formatDateTime(block.timestamp);
timestamp = block.timestamp;
window.clearTimeout(dateTimer);
refreshDate();
hash.replaceChildren(createHashElement(block.id));
renderUsdAmount(price, block.extras.price, {
size: "title",
tone: "positive",
});
}
+16 -26
View File
@@ -5,44 +5,41 @@
--block-title-label-size: 2.5rem;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.15rem 1rem;
align-items: baseline;
padding-bottom: 1.25rem;
[data-dim] {
opacity: 0.5;
}
[data-block-title] {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.35rem 1rem;
align-items: start;
> h1 {
min-width: 0;
}
[data-block-main] {
display: grid;
gap: 0.15rem;
min-width: 0;
> output {
justify-self: end;
white-space: nowrap;
}
[data-block-date],
[data-block-hash-line] {
> p,
> time {
color: var(--gray);
}
[data-block-date] {
> p {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
[data-block-side] {
display: grid;
gap: 0.15rem;
justify-items: end;
min-width: max-content;
> time {
white-space: nowrap;
}
[data-block-actions] {
> div {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
@@ -60,11 +57,4 @@
padding: 0.25rem 0.375rem;
}
}
[data-block-hash-line] {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@@ -34,12 +34,8 @@ export function orderTransactions(weights, feeRates) {
* @param {number} rows
*/
export function packTransactions(cells, columns, rows) {
let layouts = packCells(cells, columns, rows);
while (layouts === null) {
rows += 1;
layouts = packCells(cells, columns, rows);
}
return { layouts, resolvedCells: cells };
return {
layouts: packCells(cells, columns, rows),
resolvedCells: cells,
};
}
@@ -2,7 +2,7 @@
* @param {readonly PackCell[]} cells
* @param {number} columns
* @param {number} rows
* @returns {PackLayout[] | null}
* @returns {PackLayout[]}
*/
export function packCells(cells, columns, rows) {
const occupied = new Uint8Array(columns * rows);
@@ -10,8 +10,8 @@ export function packCells(cells, columns, rows) {
const layouts = [];
for (const cell of cells) {
const span = Math.min(cell.span, columns);
const position = findPosition(
let span = Math.min(cell.span, columns);
let position = findPosition(
occupied,
columns,
rows,
@@ -19,7 +19,16 @@ export function packCells(cells, columns, rows) {
cursors[span],
);
if (position === null) return null;
while (position === null) {
span -= 1;
position = findPosition(
occupied,
columns,
rows,
span,
cursors[span],
);
}
fillCells(occupied, columns, position.x, position.y, span);
cursors[span] = position.index + span;
+4 -2
View File
@@ -76,6 +76,7 @@ function createReceiptBrand() {
/** @param {Block} block */
function openReceiptDialog(block) {
const dialog = document.createElement("dialog");
const content = document.createElement("main");
const paper = document.createElement("article");
const controls = document.createElement("footer");
const print = document.createElement("button");
@@ -104,14 +105,15 @@ function openReceiptDialog(block) {
createReceiptQr(block, url),
createReceiptBrand(),
);
dialog.append(paper, controls);
content.append(paper);
dialog.append(content, controls);
print.addEventListener("click", () => {
window.print();
});
openDialog(dialog, document.body);
dialog.focus({ preventScroll: true });
dialog.scrollTop = 0;
content.scrollTop = 0;
}
export function createBlockReceipt() {
+19 -5
View File
@@ -10,18 +10,25 @@ dialog[data-block-receipt] {
position: fixed;
inset: 0 0 auto;
width: min(100% - 2rem, 28rem);
height: 100dvh;
max-height: 100dvh;
margin: 0 auto;
border-radius: 0;
overflow-y: auto;
padding-block: var(--receipt-dialog-padding-block);
overflow: hidden;
padding: 0;
color: var(--black);
background: transparent;
font-family: var(--font-mono);
overscroll-behavior: contain;
scrollbar-width: none;
translate: none;
> main {
height: 100%;
overflow-y: auto;
padding-block: var(--receipt-dialog-padding-block);
overscroll-behavior: contain;
scrollbar-width: none;
}
:is(
[data-receipt-section] > h3,
[data-receipt-row] > span,
@@ -190,7 +197,7 @@ dialog[data-block-receipt] {
}
[data-receipt-controls] {
position: fixed;
position: absolute;
bottom: 0.5rem;
left: 50%;
z-index: 1;
@@ -265,6 +272,7 @@ dialog[data-block-receipt] {
position: static;
display: block;
width: var(--receipt-page-width);
height: auto;
max-width: none;
max-height: none;
margin: 0;
@@ -275,6 +283,12 @@ dialog[data-block-receipt] {
overscroll-behavior: auto;
}
dialog[data-block-receipt] > main {
height: auto;
overflow: visible;
padding: 0;
}
dialog[data-block-receipt]::backdrop {
display: none;
}