heatmaps: part 13

This commit is contained in:
nym21
2026-06-01 11:17:00 +02:00
parent e64ffac8d1
commit 102933b406
10 changed files with 162 additions and 142 deletions
+20 -20
View File
@@ -9865,40 +9865,40 @@ impl BrkClient {
self.base.get_json(&format!("/api/oracle/price"))
}
/// Live EMA histogram
/// Live payment output histogram
///
/// Smoothed round-dollar payment histogram at the live tip: the committed EMA with the forming mempool block blended in. A flat array of log-scale bins.
/// Live smoothed histogram of oracle-eligible payment outputs, binned by output value on the oracle log scale. It combines the committed oracle window with the forming mempool block. A flat array of log-scale bins.
///
/// Endpoint: `GET /api/oracle/histogram/ema/live`
pub fn get_oracle_histogram_ema_live(&self) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/ema/live"))
/// Endpoint: `GET /api/oracle/histogram/payments/live`
pub fn get_oracle_histogram_payments_live(&self) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/payments/live"))
}
/// EMA histogram at height or day
/// Payment output histogram at height or day
///
/// Smoothed round-dollar payment histogram for a confirmed point: a block height (`840000`) gives that block's EMA, a calendar date (`YYYY-MM-DD`) gives the average of that day's per-block EMAs. A flat array of log-scale bins.
/// Smoothed histogram of oracle-eligible payment outputs for a confirmed point. A block height (`840000`) gives that block's oracle payment histogram; a calendar date (`YYYY-MM-DD`) gives the average of that day's per-block payment histograms. A flat array of log-scale bins.
///
/// Endpoint: `GET /api/oracle/histogram/ema/{point}`
pub fn get_oracle_histogram_ema(&self, point: &str) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/ema/{point}"))
/// Endpoint: `GET /api/oracle/histogram/payments/{point}`
pub fn get_oracle_histogram_payments(&self, point: &str) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/payments/{point}"))
}
/// Live raw histogram
/// Live output value histogram
///
/// Unfiltered output histogram for the forming mempool block: every live output binned by value, with none of the round-dollar payment filters applied. A flat array of log-scale bins, all zero when no mempool is configured.
/// Live unfiltered output value histogram for the forming mempool block. Every live output is binned by value on the oracle log scale; no oracle payment filters are applied. A flat array of log-scale bins, all zero when no mempool is configured.
///
/// Endpoint: `GET /api/oracle/histogram/raw/live`
pub fn get_oracle_histogram_raw_live(&self) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/raw/live"))
/// Endpoint: `GET /api/oracle/histogram/outputs/live`
pub fn get_oracle_histogram_outputs_live(&self) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/outputs/live"))
}
/// Raw histogram at height or day
/// Output value histogram at height or day
///
/// Unfiltered output histogram for a confirmed point: a block height (`840000`) gives that block's outputs, coinbase included, binned by value with no payment filtering; a calendar date (`YYYY-MM-DD`) sums every block that day. A flat array of log-scale bins.
/// Unfiltered output value histogram for a confirmed point. A block height (`840000`) gives every output in that block, coinbase included, binned by value on the oracle log scale; a calendar date (`YYYY-MM-DD`) sums every block that day. A flat array of log-scale bins.
///
/// Endpoint: `GET /api/oracle/histogram/raw/{point}`
pub fn get_oracle_histogram_raw(&self, point: &str) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/raw/{point}"))
/// Endpoint: `GET /api/oracle/histogram/outputs/{point}`
pub fn get_oracle_histogram_outputs(&self, point: &str) -> Result<Vec<i64>> {
self.base.get_json(&format!("/api/oracle/histogram/outputs/{point}"))
}
/// Txid by index
+19 -10
View File
@@ -3,8 +3,8 @@ use std::ops::Range;
use brk_error::Result;
use brk_indexer::{Indexer, Lengths};
use brk_oracle::{
Config, HistogramRaw, Oracle, START_HEIGHT_FAST, START_HEIGHT_SLOW, bin_to_cents, cents_to_bin,
for_each_round_dollar_bin,
bin_to_cents, cents_to_bin, for_each_round_dollar_bin, Config, HistogramRaw, Oracle,
START_HEIGHT_FAST, START_HEIGHT_SLOW,
};
use brk_types::{Cents, OutputType, Sats, TxIndex, TxOutIndex};
use tracing::info;
@@ -115,13 +115,7 @@ impl Vecs {
let seed_bin = cents_to_bin(prev_cents.inner() as f64);
let warmup = config.window_size.min(committed - START_HEIGHT_SLOW);
let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
Self::feed_blocks_with(
o,
indexer,
(committed - warmup)..committed,
None,
|_, _, _| {},
);
Self::feed_blocks_for_warmup(o, indexer, (committed - warmup)..committed, None);
});
let num_new = total_heights - committed;
@@ -136,7 +130,12 @@ impl Vecs {
let mut ref_bins = Vec::with_capacity(num_new);
if committed < START_HEIGHT_FAST {
let slow_end = START_HEIGHT_FAST.min(total_heights);
ref_bins.extend(Self::feed_blocks(&mut oracle, indexer, committed..slow_end, None));
ref_bins.extend(Self::feed_blocks(
&mut oracle,
indexer,
committed..slow_end,
None,
));
if slow_end == START_HEIGHT_FAST {
oracle.reconfigure(Config::default());
}
@@ -197,6 +196,16 @@ impl Vecs {
ref_bins
}
/// Feed blocks into an Oracle when callers only need the warmed EMA/window state.
pub fn feed_blocks_for_warmup<IM: StorageMode>(
oracle: &mut Oracle,
indexer: &Indexer<IM>,
range: Range<usize>,
cap: Option<&Lengths>,
) {
Self::feed_blocks_with(oracle, indexer, range, cap, |_, _, _| {});
}
/// Feed a range of blocks into an Oracle and call `on_block` after each
/// processed block. This lets callers observe derived state such as EMA
/// without duplicating the histogram extraction path.
+1 -1
View File
@@ -66,7 +66,7 @@ impl Config {
/// Split a block range into sub-ranges with a single EMA configuration.
pub fn segments_for_range(range: Range<usize>) -> impl Iterator<Item = Range<usize>> {
let split = START_HEIGHT_FAST.clamp(range.start, range.end);
let split = START_HEIGHT_FAST.max(range.start).min(range.end);
[range.start..split, split..range.end]
.into_iter()
.filter(|range| !range.is_empty())
+26 -17
View File
@@ -18,30 +18,32 @@ impl Query {
Ok(self.live_oracle()?.price_dollars())
}
/// Smoothed EMA histogram at the live tip, quantized for the wire.
pub fn live_histogram_ema(&self) -> Result<HistogramEmaCompact> {
/// Smoothed payment output histogram at the live tip, quantized for the wire.
pub fn live_payment_histogram(&self) -> Result<HistogramEmaCompact> {
Ok(self.live_oracle()?.ema().to_compact())
}
/// Smoothed EMA histogram for a confirmed `height`, deterministically
/// Smoothed payment output histogram for a confirmed `height`, deterministically
/// reconstructed by replaying the window ending at `height`. EMA values are
/// seed-independent, so the result is exact.
pub fn confirmed_histogram_ema(&self, height: usize) -> Result<HistogramEmaCompact> {
pub fn confirmed_payment_histogram(&self, height: usize) -> Result<HistogramEmaCompact> {
let safe = self.check_histogram_height(height)?;
Ok(self.ema_oracle_at(height, &safe)?.ema().to_compact())
}
/// Smoothed EMA histogram for a calendar `day`: the bin-by-bin average of
/// Smoothed payment output histogram for a calendar `day`: the bin-by-bin average of
/// every confirmed block's per-block EMA. Each block's EMA is reconstructed
/// independently (seed-independent, so exact); averaging keeps the result an
/// intensive per-block rate rather than letting a busy day dominate.
pub fn confirmed_histogram_ema_day(&self, day: Day1) -> Result<HistogramEmaCompact> {
pub fn confirmed_payment_histogram_day(&self, day: Day1) -> Result<HistogramEmaCompact> {
let safe = self.safe_lengths();
let range = self.day_block_range(day, &safe)?;
Ok(self.average_histogram_ema_range(range, &safe)?.to_compact())
Ok(self
.average_payment_histogram_range(range, &safe)?
.to_compact())
}
fn average_histogram_ema_range(
fn average_payment_histogram_range(
&self,
range: Range<usize>,
safe: &Lengths,
@@ -72,7 +74,7 @@ impl Query {
/// Unfiltered per-bin output counts at the live tip: every forming-block
/// mempool output binned by value, with none of the round-dollar payment
/// filters applied. Zeros when no mempool is configured.
pub fn live_histogram_raw(&self) -> Result<HistogramRaw> {
pub fn live_output_histogram(&self) -> Result<HistogramRaw> {
Ok(match self.mempool() {
Some(mempool) => mempool.live_raw_histogram(),
None => HistogramRaw::zeros(),
@@ -81,18 +83,18 @@ impl Query {
/// Unfiltered per-bin output counts for a confirmed `height`: every output
/// in the block binned by value, with no payment filtering.
pub fn confirmed_histogram_raw(&self, height: usize) -> Result<HistogramRaw> {
pub fn confirmed_output_histogram(&self, height: usize) -> Result<HistogramRaw> {
let safe = self.check_histogram_height(height)?;
Ok(self.raw_histogram_for_blocks(height..height + 1, &safe))
Ok(self.output_histogram_for_blocks(height..height + 1, &safe))
}
/// Unfiltered per-bin output counts for a calendar `day`: every block's raw
/// Unfiltered per-bin output counts for a calendar `day`: every block's output
/// histogram summed bin-by-bin. Raw counts are additive, so the day total is
/// just the sum across its confirmed blocks.
pub fn confirmed_histogram_raw_day(&self, day: Day1) -> Result<HistogramRaw> {
pub fn confirmed_output_histogram_day(&self, day: Day1) -> Result<HistogramRaw> {
let safe = self.safe_lengths();
let range = self.day_block_range(day, &safe)?;
Ok(self.raw_histogram_for_blocks(range, &safe))
Ok(self.output_histogram_for_blocks(range, &safe))
}
/// The live tip oracle: the cached committed base, with the forming block's
@@ -124,7 +126,14 @@ impl Query {
return Ok(oracle);
}
let last = self.computer().prices.spot.cents.height.len().saturating_sub(1);
let last = self
.computer()
.prices
.spot
.cents
.height
.len()
.saturating_sub(1);
let seed_bin = self.seed_bin_at(last)?;
let oracle = Arc::new(self.warm_oracle(seed_bin, height.to_usize(), &safe));
@@ -150,7 +159,7 @@ impl Query {
let config = Config::for_height(end.saturating_sub(1));
let start = end.saturating_sub(config.window_size);
Oracle::from_checkpoint(seed_bin, config, |o| {
PricesVecs::feed_blocks_with(o, self.indexer(), start..end, Some(safe), |_, _, _| {});
PricesVecs::feed_blocks_for_warmup(o, self.indexer(), start..end, Some(safe));
})
}
@@ -222,7 +231,7 @@ impl Query {
/// coinbase included, binned by value via `sats_to_bin` with no payment
/// filtering. Raw counts are additive, so a day can be read as one output
/// range instead of one block at a time.
fn raw_histogram_for_blocks(&self, range: Range<usize>, safe: &Lengths) -> HistogramRaw {
fn output_histogram_for_blocks(&self, range: Range<usize>, safe: &Lengths) -> HistogramRaw {
let indexer = self.indexer();
let total_outputs = safe.txout_index.to_usize();
let collect_end = (range.end + 1).min(safe.height.to_usize());
+36 -33
View File
@@ -43,23 +43,24 @@ impl OracleRoutes for ApiRouter<AppState> {
),
)
.api_route(
"/api/oracle/histogram/ema/live",
"/api/oracle/histogram/payments/live",
get_with(
async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state
.respond_json(&headers, state.mempool_strategy(), &uri, |q| {
q.live_histogram_ema()
q.live_payment_histogram()
})
.await
},
|op| {
op.id("get_oracle_histogram_ema_live")
op.id("get_oracle_histogram_payments_live")
.oracle_tag()
.summary("Live EMA histogram")
.summary("Live payment output histogram")
.description(
"Smoothed round-dollar payment histogram at the live tip: the \
committed EMA with the forming mempool block blended in. \
A flat array of log-scale bins.",
"Live smoothed histogram of oracle-eligible payment outputs, binned \
by output value on the oracle log scale. It combines the committed \
oracle window with the forming mempool block. A flat array of \
log-scale bins.",
)
.json_response::<HistogramEmaCompact>()
.not_modified()
@@ -68,7 +69,7 @@ impl OracleRoutes for ApiRouter<AppState> {
),
)
.api_route(
"/api/oracle/histogram/ema/{point}",
"/api/oracle/histogram/payments/{point}",
get_with(
async |uri: Uri,
headers: HeaderMap,
@@ -81,7 +82,7 @@ impl OracleRoutes for ApiRouter<AppState> {
let strategy = state.date_strategy(version, date);
state
.respond_json(&headers, strategy, &uri, move |q| {
q.confirmed_histogram_ema_day(Day1::try_from(date)?)
q.confirmed_payment_histogram_day(Day1::try_from(date)?)
})
.await
}
@@ -89,7 +90,7 @@ impl OracleRoutes for ApiRouter<AppState> {
let strategy = state.height_strategy(version, height);
state
.respond_json(&headers, strategy, &uri, move |q| {
q.confirmed_histogram_ema(usize::from(height))
q.confirmed_payment_histogram(usize::from(height))
})
.await
}
@@ -97,14 +98,15 @@ impl OracleRoutes for ApiRouter<AppState> {
}
},
|op| {
op.id("get_oracle_histogram_ema")
op.id("get_oracle_histogram_payments")
.oracle_tag()
.summary("EMA histogram at height or day")
.summary("Payment output histogram at height or day")
.description(
"Smoothed round-dollar payment histogram for a confirmed point: a \
block height (`840000`) gives that block's EMA, a calendar date \
(`YYYY-MM-DD`) gives the average of that day's per-block EMAs. \
A flat array of log-scale bins.",
"Smoothed histogram of oracle-eligible payment outputs for a \
confirmed point. A block height (`840000`) gives that block's oracle \
payment histogram; a calendar date (`YYYY-MM-DD`) gives the average \
of that day's per-block payment histograms. A flat array of log-scale \
bins.",
)
.json_response::<HistogramEmaCompact>()
.not_modified()
@@ -115,24 +117,24 @@ impl OracleRoutes for ApiRouter<AppState> {
),
)
.api_route(
"/api/oracle/histogram/raw/live",
"/api/oracle/histogram/outputs/live",
get_with(
async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state
.respond_json(&headers, state.mempool_strategy(), &uri, |q| {
q.live_histogram_raw()
q.live_output_histogram()
})
.await
},
|op| {
op.id("get_oracle_histogram_raw_live")
op.id("get_oracle_histogram_outputs_live")
.oracle_tag()
.summary("Live raw histogram")
.summary("Live output value histogram")
.description(
"Unfiltered output histogram for the forming mempool block: every \
live output binned by value, with none of the round-dollar payment \
filters applied. A flat array of log-scale bins, all zero when no \
mempool is configured.",
"Live unfiltered output value histogram for the forming mempool \
block. Every live output is binned by value on the oracle log scale; \
no oracle payment filters are applied. A flat array of log-scale \
bins, all zero when no mempool is configured.",
)
.json_response::<HistogramRaw>()
.not_modified()
@@ -141,7 +143,7 @@ impl OracleRoutes for ApiRouter<AppState> {
),
)
.api_route(
"/api/oracle/histogram/raw/{point}",
"/api/oracle/histogram/outputs/{point}",
get_with(
async |uri: Uri,
headers: HeaderMap,
@@ -155,7 +157,7 @@ impl OracleRoutes for ApiRouter<AppState> {
let strategy = state.date_strategy(version, date);
state
.respond_json(&headers, strategy, &uri, move |q| {
q.confirmed_histogram_raw_day(Day1::try_from(date)?)
q.confirmed_output_histogram_day(Day1::try_from(date)?)
})
.await
}
@@ -163,7 +165,7 @@ impl OracleRoutes for ApiRouter<AppState> {
let strategy = state.height_strategy(version, height);
state
.respond_json(&headers, strategy, &uri, move |q| {
q.confirmed_histogram_raw(usize::from(height))
q.confirmed_output_histogram(usize::from(height))
})
.await
}
@@ -171,14 +173,15 @@ impl OracleRoutes for ApiRouter<AppState> {
}
},
|op| {
op.id("get_oracle_histogram_raw")
op.id("get_oracle_histogram_outputs")
.oracle_tag()
.summary("Raw histogram at height or day")
.summary("Output value histogram at height or day")
.description(
"Unfiltered output histogram for a confirmed point: a block height \
(`840000`) gives that block's outputs, coinbase included, binned by \
value with no payment filtering; a calendar date (`YYYY-MM-DD`) sums \
every block that day. A flat array of log-scale bins.",
"Unfiltered output value histogram for a confirmed point. A block \
height (`840000`) gives every output in that block, coinbase \
included, binned by value on the oracle log scale; a calendar date \
(`YYYY-MM-DD`) sums every block that day. A flat array of log-scale \
bins.",
)
.json_response::<HistogramRaw>()
.not_modified()