mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 06:01:57 -07:00
query + server: more endpoints/methods/helpers
This commit is contained in:
Generated
+3
-3
@@ -4189,7 +4189,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rawdb"
|
||||
version = "0.3.20"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -5370,7 +5370,7 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
|
||||
|
||||
[[package]]
|
||||
name = "vecdb"
|
||||
version = "0.3.20"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"log",
|
||||
@@ -5388,7 +5388,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vecdb_derive"
|
||||
version = "0.3.20"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
|
||||
@@ -63,8 +63,8 @@ export const POOL_ID_TO_POOL_NAME = /** @type {const} */ ({
|
||||
contents += &sorted_pools
|
||||
.iter()
|
||||
.map(|pool| {
|
||||
let id = pool.serialized_id();
|
||||
format!(" {id}: \"{}\",", pool.name)
|
||||
let slug = pool.slug();
|
||||
format!(" {slug}: \"{}\",", pool.name)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
@@ -82,7 +82,6 @@ pub struct Vecs {
|
||||
pub txindex_to_input_count: EagerVec<PcoVec<TxIndex, StoredU64>>,
|
||||
pub txindex_to_output_count: EagerVec<PcoVec<TxIndex, StoredU64>>,
|
||||
pub txindex_to_txindex: LazyVecFrom1<TxIndex, TxIndex, TxIndex, Txid>,
|
||||
pub txinindex_to_txindex: EagerVec<PcoVec<TxInIndex, TxIndex>>,
|
||||
pub txinindex_to_txinindex: LazyVecFrom1<TxInIndex, TxInIndex, TxInIndex, OutPoint>,
|
||||
pub txinindex_to_txoutindex: EagerVec<PcoVec<TxInIndex, TxOutIndex>>,
|
||||
pub txoutindex_to_txoutindex: LazyVecFrom1<TxOutIndex, TxOutIndex, TxOutIndex, Sats>,
|
||||
@@ -122,7 +121,6 @@ impl Vecs {
|
||||
}
|
||||
|
||||
let this = Self {
|
||||
txinindex_to_txindex: eager!("txindex"),
|
||||
txinindex_to_txoutindex: eager!("txoutindex"),
|
||||
txoutindex_to_txoutindex: lazy!("txoutindex", indexer.vecs.txout.txoutindex_to_value),
|
||||
txinindex_to_txinindex: lazy!("txinindex", indexer.vecs.txin.txinindex_to_outpoint),
|
||||
@@ -253,13 +251,6 @@ impl Vecs {
|
||||
// TxInIndex
|
||||
// ---
|
||||
|
||||
self.txinindex_to_txindex.compute_finer(
|
||||
starting_indexes.txinindex,
|
||||
&indexer.vecs.tx.txindex_to_first_txinindex,
|
||||
&indexer.vecs.txin.txinindex_to_outpoint,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let txindex_to_first_txoutindex = &indexer.vecs.tx.txindex_to_first_txoutindex;
|
||||
let txindex_to_first_txoutindex_reader = txindex_to_first_txoutindex.create_reader();
|
||||
self.txinindex_to_txoutindex.compute_transform(
|
||||
|
||||
@@ -4,7 +4,7 @@ use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_store::AnyStore;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Address, AddressBytes, Height, OutputType, PoolId, Pools, TxOutIndex, pools};
|
||||
use brk_types::{Address, AddressBytes, Height, OutputType, PoolSlug, Pools, TxOutIndex, pools};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, BytesVec, Database, Exit, GenericStoredVec, ImportableVec, IterableVec,
|
||||
@@ -24,8 +24,8 @@ pub struct Vecs {
|
||||
db: Database,
|
||||
pools: &'static Pools,
|
||||
|
||||
pub height_to_pool: BytesVec<Height, PoolId>,
|
||||
pub vecs: BTreeMap<PoolId, vecs::Vecs>,
|
||||
pub height_to_pool: BytesVec<Height, PoolSlug>,
|
||||
pub vecs: BTreeMap<PoolSlug, vecs::Vecs>,
|
||||
}
|
||||
|
||||
impl Vecs {
|
||||
@@ -48,13 +48,13 @@ impl Vecs {
|
||||
.map(|pool| {
|
||||
vecs::Vecs::forced_import(
|
||||
&db,
|
||||
pool.id,
|
||||
pool.slug,
|
||||
pools,
|
||||
version + Version::ZERO,
|
||||
indexes,
|
||||
price,
|
||||
)
|
||||
.map(|vecs| (pool.id, vecs))
|
||||
.map(|vecs| (pool.slug, vecs))
|
||||
})
|
||||
.collect::<Result<BTreeMap<_, _>>>()?,
|
||||
pools,
|
||||
@@ -126,20 +126,36 @@ impl Vecs {
|
||||
let mut txindex_to_first_txoutindex_iter =
|
||||
indexer.vecs.tx.txindex_to_first_txoutindex.iter()?;
|
||||
let mut txindex_to_output_count_iter = indexes.txindex_to_output_count.iter();
|
||||
let mut txoutindex_to_outputtype_iter = indexer.vecs.txout.txoutindex_to_outputtype.iter()?;
|
||||
let mut txoutindex_to_outputtype_iter =
|
||||
indexer.vecs.txout.txoutindex_to_outputtype.iter()?;
|
||||
let mut txoutindex_to_typeindex_iter = indexer.vecs.txout.txoutindex_to_typeindex.iter()?;
|
||||
let mut p2pk65addressindex_to_p2pk65bytes_iter =
|
||||
indexer.vecs.address.p2pk65addressindex_to_p2pk65bytes.iter()?;
|
||||
let mut p2pk33addressindex_to_p2pk33bytes_iter =
|
||||
indexer.vecs.address.p2pk33addressindex_to_p2pk33bytes.iter()?;
|
||||
let mut p2pkhaddressindex_to_p2pkhbytes_iter =
|
||||
indexer.vecs.address.p2pkhaddressindex_to_p2pkhbytes.iter()?;
|
||||
let mut p2pk65addressindex_to_p2pk65bytes_iter = indexer
|
||||
.vecs
|
||||
.address
|
||||
.p2pk65addressindex_to_p2pk65bytes
|
||||
.iter()?;
|
||||
let mut p2pk33addressindex_to_p2pk33bytes_iter = indexer
|
||||
.vecs
|
||||
.address
|
||||
.p2pk33addressindex_to_p2pk33bytes
|
||||
.iter()?;
|
||||
let mut p2pkhaddressindex_to_p2pkhbytes_iter = indexer
|
||||
.vecs
|
||||
.address
|
||||
.p2pkhaddressindex_to_p2pkhbytes
|
||||
.iter()?;
|
||||
let mut p2shaddressindex_to_p2shbytes_iter =
|
||||
indexer.vecs.address.p2shaddressindex_to_p2shbytes.iter()?;
|
||||
let mut p2wpkhaddressindex_to_p2wpkhbytes_iter =
|
||||
indexer.vecs.address.p2wpkhaddressindex_to_p2wpkhbytes.iter()?;
|
||||
let mut p2wshaddressindex_to_p2wshbytes_iter =
|
||||
indexer.vecs.address.p2wshaddressindex_to_p2wshbytes.iter()?;
|
||||
let mut p2wpkhaddressindex_to_p2wpkhbytes_iter = indexer
|
||||
.vecs
|
||||
.address
|
||||
.p2wpkhaddressindex_to_p2wpkhbytes
|
||||
.iter()?;
|
||||
let mut p2wshaddressindex_to_p2wshbytes_iter = indexer
|
||||
.vecs
|
||||
.address
|
||||
.p2wshaddressindex_to_p2wshbytes
|
||||
.iter()?;
|
||||
let mut p2traddressindex_to_p2trbytes_iter =
|
||||
indexer.vecs.address.p2traddressindex_to_p2trbytes.iter()?;
|
||||
let mut p2aaddressindex_to_p2abytes_iter =
|
||||
@@ -201,7 +217,7 @@ impl Vecs {
|
||||
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
.unwrap_or(unknown);
|
||||
|
||||
self.height_to_pool.push_if_needed(height, pool.id)?;
|
||||
self.height_to_pool.push_if_needed(height, pool.slug)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, PoolId, Pools, Sats, StoredF32, StoredU16, StoredU32};
|
||||
use brk_types::{Height, PoolSlug, Pools, Sats, StoredF32, StoredU16, StoredU32};
|
||||
use vecdb::{Database, Exit, GenericStoredVec, IterableVec, VecIndex, Version};
|
||||
|
||||
use crate::{
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct Vecs {
|
||||
id: PoolId,
|
||||
slug: PoolSlug,
|
||||
|
||||
pub indexes_to_blocks_mined: ComputedVecsFromHeight<StoredU32>,
|
||||
pub indexes_to_1w_blocks_mined: ComputedVecsFromDateIndex<StoredU32>,
|
||||
@@ -36,15 +36,13 @@ pub struct Vecs {
|
||||
impl Vecs {
|
||||
pub fn forced_import(
|
||||
db: &Database,
|
||||
id: PoolId,
|
||||
pools: &Pools,
|
||||
slug: PoolSlug,
|
||||
_pools: &Pools,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
price: Option<&price::Vecs>,
|
||||
) -> Result<Self> {
|
||||
let pool = pools.get(id);
|
||||
let name = pool.serialized_id();
|
||||
let suffix = |s: &str| format!("{name}_{s}");
|
||||
let suffix = |s: &str| format!("{}_{s}", slug.to_string());
|
||||
let compute_dollars = price.is_some();
|
||||
let version = parent_version + Version::ZERO;
|
||||
|
||||
@@ -65,7 +63,7 @@ impl Vecs {
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
slug,
|
||||
indexes_to_blocks_mined: ComputedVecsFromHeight::forced_import(
|
||||
db,
|
||||
&suffix("blocks_mined"),
|
||||
@@ -118,7 +116,7 @@ impl Vecs {
|
||||
&mut self,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
height_to_pool: &impl IterableVec<Height, PoolId>,
|
||||
height_to_pool: &impl IterableVec<Height, PoolSlug>,
|
||||
chain: &chain::Vecs,
|
||||
price: Option<&price::Vecs>,
|
||||
exit: &Exit,
|
||||
@@ -131,7 +129,7 @@ impl Vecs {
|
||||
|(h, id, ..)| {
|
||||
(
|
||||
h,
|
||||
if id == self.id {
|
||||
if id == self.slug {
|
||||
StoredU32::ONE
|
||||
} else {
|
||||
StoredU32::ZERO
|
||||
|
||||
@@ -595,6 +595,10 @@ impl<'a> BlockProcessor<'a> {
|
||||
.push_if_needed(txindex, txinindex)?;
|
||||
}
|
||||
|
||||
self.vecs
|
||||
.txin
|
||||
.txinindex_to_txindex
|
||||
.push_if_needed(txinindex, txindex)?;
|
||||
self.vecs
|
||||
.txin
|
||||
.txinindex_to_outpoint
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, OutPoint, TxInIndex, Version};
|
||||
use brk_types::{Height, OutPoint, TxInIndex, TxIndex, Version};
|
||||
use vecdb::{AnyStoredVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp};
|
||||
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct TxinVecs {
|
||||
pub height_to_first_txinindex: PcoVec<Height, TxInIndex>,
|
||||
pub txinindex_to_outpoint: PcoVec<TxInIndex, OutPoint>,
|
||||
pub txinindex_to_txindex: PcoVec<TxInIndex, TxIndex>,
|
||||
}
|
||||
|
||||
impl TxinVecs {
|
||||
@@ -14,6 +15,7 @@ impl TxinVecs {
|
||||
Ok(Self {
|
||||
height_to_first_txinindex: PcoVec::forced_import(db, "first_txinindex", version)?,
|
||||
txinindex_to_outpoint: PcoVec::forced_import(db, "outpoint", version)?,
|
||||
txinindex_to_txindex: PcoVec::forced_import(db, "txindex", version)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,6 +24,8 @@ impl TxinVecs {
|
||||
.truncate_if_needed_with_stamp(height, stamp)?;
|
||||
self.txinindex_to_outpoint
|
||||
.truncate_if_needed_with_stamp(txinindex, stamp)?;
|
||||
self.txinindex_to_txindex
|
||||
.truncate_if_needed_with_stamp(txinindex, stamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,6 +33,7 @@ impl TxinVecs {
|
||||
[
|
||||
&mut self.height_to_first_txinindex as &mut dyn AnyStoredVec,
|
||||
&mut self.txinindex_to_outpoint,
|
||||
&mut self.txinindex_to_txindex,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use brk_error::Result;
|
||||
use brk_rpc::Client;
|
||||
use brk_types::{MempoolEntryInfo, MempoolInfo, TxWithHex, Txid, TxidPrefix};
|
||||
use derive_deref::Deref;
|
||||
use log::{error, info};
|
||||
use log::{debug, error};
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -225,7 +225,7 @@ impl MempoolInner {
|
||||
|
||||
let i = Instant::now();
|
||||
self.rebuild_projected_blocks();
|
||||
info!("mempool: rebuild_projected_blocks in {:?}", i.elapsed());
|
||||
debug!("mempool: rebuild_projected_blocks in {:?}", i.elapsed());
|
||||
}
|
||||
|
||||
/// Rebuild projected blocks snapshot.
|
||||
|
||||
@@ -6,8 +6,9 @@ use brk_indexer::Indexer;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_types::{
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, DifficultyAdjustment, Height,
|
||||
Index, IndexInfo, Limit, MempoolBlock, MempoolInfo, Metric, MetricCount, RecommendedFees,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, DifficultyAdjustment,
|
||||
HashrateSummary, Height, Index, IndexInfo, Limit, MempoolBlock, MempoolInfo, Metric,
|
||||
MetricCount, PoolDetail, PoolInfo, PoolSlug, PoolsSummary, RecommendedFees, TimePeriod,
|
||||
Timestamp, Transaction, TreeNode, TxOutspend, TxStatus, Txid, TxidPath, Utxo, Vout,
|
||||
};
|
||||
use tokio::task::spawn_blocking;
|
||||
@@ -117,7 +118,11 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_block_txids(&hash)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_txs(&self, hash: String, start_index: usize) -> Result<Vec<Transaction>> {
|
||||
pub async fn get_block_txs(
|
||||
&self,
|
||||
hash: String,
|
||||
start_index: usize,
|
||||
) -> Result<Vec<Transaction>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_txs(&hash, start_index)).await?
|
||||
}
|
||||
@@ -152,6 +157,70 @@ impl AsyncQuery {
|
||||
spawn_blocking(move || query.get_difficulty_adjustment()).await?
|
||||
}
|
||||
|
||||
pub async fn get_mining_pools(&self, time_period: TimePeriod) -> Result<PoolsSummary> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_mining_pools(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_all_pools(&self) -> Result<Vec<PoolInfo>> {
|
||||
Ok(self.0.get_all_pools())
|
||||
}
|
||||
|
||||
pub async fn get_pool_detail(&self, slug: PoolSlug) -> Result<PoolDetail> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_pool_detail(slug)).await?
|
||||
}
|
||||
|
||||
pub async fn get_hashrate(&self, time_period: Option<TimePeriod>) -> Result<HashrateSummary> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_hashrate(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_difficulty_adjustments(
|
||||
&self,
|
||||
time_period: Option<TimePeriod>,
|
||||
) -> Result<Vec<brk_types::DifficultyAdjustmentEntry>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_difficulty_adjustments(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_fees(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<Vec<brk_types::BlockFeesEntry>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_fees(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_rewards(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<Vec<brk_types::BlockRewardsEntry>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_rewards(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_fee_rates(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<Vec<brk_types::BlockFeeRatesEntry>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_fee_rates(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_block_sizes_weights(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<brk_types::BlockSizesWeights> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_block_sizes_weights(time_period)).await?
|
||||
}
|
||||
|
||||
pub async fn get_reward_stats(&self, block_count: usize) -> Result<brk_types::RewardStats> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || query.get_reward_stats(block_count)).await?
|
||||
}
|
||||
|
||||
pub async fn match_metric(&self, metric: Metric, limit: Limit) -> Result<Vec<&'static str>> {
|
||||
let query = self.0.clone();
|
||||
spawn_blocking(move || Ok(query.match_metric(&metric, limit))).await?
|
||||
|
||||
@@ -41,13 +41,13 @@ pub fn get_address_txids(
|
||||
.rev()
|
||||
.filter(|(key, _): &(AddressIndexTxIndex, Unit)| {
|
||||
if let Some(after) = after_txindex {
|
||||
TxIndex::from(key.txindex()) < after
|
||||
key.txindex() < after
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.take(limit)
|
||||
.map(|(key, _)| TxIndex::from(key.txindex()))
|
||||
.map(|(key, _)| key.txindex())
|
||||
.collect();
|
||||
|
||||
let mut txindex_to_txid_iter = indexer.vecs.tx.txindex_to_txid.iter()?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{BlockTimestamp, Date, DateIndex, Height, Timestamp};
|
||||
use jiff::Timestamp as JiffTimestamp;
|
||||
use vecdb::{AnyVec, GenericStoredVec, TypedVecIterator};
|
||||
use vecdb::{GenericStoredVec, TypedVecIterator};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
@@ -59,7 +59,12 @@ pub fn get_block_by_timestamp(timestamp: Timestamp, query: &Query) -> Result<Blo
|
||||
}
|
||||
|
||||
let height = Height::from(best_height);
|
||||
let blockhash = indexer.vecs.block.height_to_blockhash.iter()?.get_unwrap(height);
|
||||
let blockhash = indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_blockhash
|
||||
.iter()?
|
||||
.get_unwrap(height);
|
||||
|
||||
// Convert timestamp to ISO 8601 format
|
||||
let ts_secs: i64 = (*best_ts).into();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockFeeRatesEntry, FeeRatePercentiles, TimePeriod};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
|
||||
use super::dateindex_iter::DateIndexIter;
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_block_fee_rates(
|
||||
time_period: TimePeriod,
|
||||
query: &Query,
|
||||
) -> Result<Vec<BlockFeeRatesEntry>> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let start = current_height
|
||||
.to_usize()
|
||||
.saturating_sub(time_period.block_count());
|
||||
|
||||
let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
|
||||
let vecs = &computer.chain.indexes_to_fee_rate.dateindex;
|
||||
let mut min = vecs.unwrap_min().iter();
|
||||
let mut pct10 = vecs.unwrap_pct10().iter();
|
||||
let mut pct25 = vecs.unwrap_pct25().iter();
|
||||
let mut median = vecs.unwrap_median().iter();
|
||||
let mut pct75 = vecs.unwrap_pct75().iter();
|
||||
let mut pct90 = vecs.unwrap_pct90().iter();
|
||||
let mut max = vecs.unwrap_max().iter();
|
||||
|
||||
Ok(iter.collect(|di, ts, h| {
|
||||
Some(BlockFeeRatesEntry {
|
||||
avg_height: h.into(),
|
||||
timestamp: *ts as u32,
|
||||
percentiles: FeeRatePercentiles::new(
|
||||
min.get(di).unwrap_or_default(),
|
||||
pct10.get(di).unwrap_or_default(),
|
||||
pct25.get(di).unwrap_or_default(),
|
||||
median.get(di).unwrap_or_default(),
|
||||
pct75.get(di).unwrap_or_default(),
|
||||
pct90.get(di).unwrap_or_default(),
|
||||
max.get(di).unwrap_or_default(),
|
||||
),
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockFeesEntry, TimePeriod};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
|
||||
use super::dateindex_iter::DateIndexIter;
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_block_fees(time_period: TimePeriod, query: &Query) -> Result<Vec<BlockFeesEntry>> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let start = current_height
|
||||
.to_usize()
|
||||
.saturating_sub(time_period.block_count());
|
||||
|
||||
let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
|
||||
let mut fees = computer
|
||||
.chain
|
||||
.indexes_to_fee
|
||||
.sats
|
||||
.dateindex
|
||||
.unwrap_average()
|
||||
.iter();
|
||||
|
||||
Ok(iter.collect(|di, ts, h| {
|
||||
fees.get(di).map(|fee| BlockFeesEntry {
|
||||
avg_height: h.into(),
|
||||
timestamp: *ts as u32,
|
||||
avg_fees: u64::from(*fee),
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockRewardsEntry, TimePeriod};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
|
||||
use super::dateindex_iter::DateIndexIter;
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_block_rewards(time_period: TimePeriod, query: &Query) -> Result<Vec<BlockRewardsEntry>> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let start = current_height
|
||||
.to_usize()
|
||||
.saturating_sub(time_period.block_count());
|
||||
|
||||
let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
|
||||
// coinbase = subsidy + fees
|
||||
let mut rewards = computer
|
||||
.chain
|
||||
.indexes_to_coinbase
|
||||
.sats
|
||||
.dateindex
|
||||
.unwrap_average()
|
||||
.iter();
|
||||
|
||||
Ok(iter.collect(|di, ts, h| {
|
||||
rewards.get(di).map(|reward| BlockRewardsEntry {
|
||||
avg_height: h.into(),
|
||||
timestamp: *ts as u32,
|
||||
avg_rewards: u64::from(*reward),
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockSizeEntry, BlockSizesWeights, BlockWeightEntry, TimePeriod};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
|
||||
use super::dateindex_iter::DateIndexIter;
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_block_sizes_weights(
|
||||
time_period: TimePeriod,
|
||||
query: &Query,
|
||||
) -> Result<BlockSizesWeights> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let start = current_height
|
||||
.to_usize()
|
||||
.saturating_sub(time_period.block_count());
|
||||
|
||||
let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
|
||||
let mut sizes_vec = computer
|
||||
.chain
|
||||
.indexes_to_block_size
|
||||
.dateindex
|
||||
.unwrap_average()
|
||||
.iter();
|
||||
let mut weights_vec = computer
|
||||
.chain
|
||||
.indexes_to_block_weight
|
||||
.dateindex
|
||||
.unwrap_average()
|
||||
.iter();
|
||||
|
||||
let entries: Vec<_> = iter.collect(|di, ts, h| {
|
||||
let size = sizes_vec.get(di).map(|s| u64::from(*s));
|
||||
let weight = weights_vec.get(di).map(|w| u64::from(*w));
|
||||
Some((h.into(), *ts as u32, size, weight))
|
||||
});
|
||||
|
||||
let sizes = entries
|
||||
.iter()
|
||||
.filter_map(|(h, ts, size, _)| {
|
||||
size.map(|s| BlockSizeEntry {
|
||||
avg_height: *h,
|
||||
timestamp: *ts,
|
||||
avg_size: s,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let weights = entries
|
||||
.iter()
|
||||
.filter_map(|(h, ts, _, weight)| {
|
||||
weight.map(|w| BlockWeightEntry {
|
||||
avg_height: *h,
|
||||
timestamp: *ts,
|
||||
avg_weight: w,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(BlockSizesWeights { sizes, weights })
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use brk_computer::Computer;
|
||||
use brk_types::{DateIndex, Height, Timestamp};
|
||||
use vecdb::{GenericStoredVec, IterableVec, VecIndex};
|
||||
|
||||
/// Helper for iterating over dateindex ranges with sampling.
|
||||
pub struct DateIndexIter<'a> {
|
||||
computer: &'a Computer,
|
||||
start_di: DateIndex,
|
||||
end_di: DateIndex,
|
||||
step: usize,
|
||||
}
|
||||
|
||||
impl<'a> DateIndexIter<'a> {
|
||||
pub fn new(computer: &'a Computer, start_height: usize, end_height: usize) -> Self {
|
||||
let start_di = computer
|
||||
.indexes
|
||||
.height_to_dateindex
|
||||
.read_once(Height::from(start_height))
|
||||
.unwrap_or_default();
|
||||
let end_di = computer
|
||||
.indexes
|
||||
.height_to_dateindex
|
||||
.read_once(Height::from(end_height))
|
||||
.unwrap_or_default();
|
||||
|
||||
let total = end_di.to_usize().saturating_sub(start_di.to_usize()) + 1;
|
||||
let step = (total / 200).max(1);
|
||||
|
||||
Self {
|
||||
computer,
|
||||
start_di,
|
||||
end_di,
|
||||
step,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate and collect entries using the provided transform function.
|
||||
pub fn collect<T, F>(&self, mut transform: F) -> Vec<T>
|
||||
where
|
||||
F: FnMut(DateIndex, Timestamp, Height) -> Option<T>,
|
||||
{
|
||||
let total = self.end_di.to_usize().saturating_sub(self.start_di.to_usize()) + 1;
|
||||
let mut timestamps = self
|
||||
.computer
|
||||
.chain
|
||||
.timeindexes_to_timestamp
|
||||
.dateindex_extra
|
||||
.unwrap_first()
|
||||
.iter();
|
||||
let mut heights = self.computer.indexes.dateindex_to_first_height.iter();
|
||||
|
||||
let mut entries = Vec::with_capacity(total / self.step + 1);
|
||||
let mut i = self.start_di.to_usize();
|
||||
|
||||
while i <= self.end_di.to_usize() {
|
||||
let di = DateIndex::from(i);
|
||||
if let (Some(ts), Some(h)) = (timestamps.get(di), heights.get(di)) {
|
||||
if let Some(entry) = transform(di, ts, h) {
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
i += self.step;
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{DifficultyAdjustmentEntry, TimePeriod};
|
||||
use vecdb::VecIndex;
|
||||
|
||||
use crate::Query;
|
||||
|
||||
use super::epochs::iter_difficulty_epochs;
|
||||
|
||||
/// Get historical difficulty adjustments.
|
||||
pub fn get_difficulty_adjustments(
|
||||
time_period: Option<TimePeriod>,
|
||||
query: &Query,
|
||||
) -> Result<Vec<DifficultyAdjustmentEntry>> {
|
||||
let current_height = query.get_height();
|
||||
let end = current_height.to_usize();
|
||||
let start = match time_period {
|
||||
Some(tp) => end.saturating_sub(tp.block_count()),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
let mut entries = iter_difficulty_epochs(query.computer(), start, end);
|
||||
|
||||
// Return in reverse chronological order (newest first)
|
||||
entries.reverse();
|
||||
Ok(entries)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use brk_computer::Computer;
|
||||
use brk_types::{DifficultyAdjustmentEntry, DifficultyEpoch, Height};
|
||||
use vecdb::{GenericStoredVec, IterableVec, VecIndex};
|
||||
|
||||
/// Iterate over difficulty epochs within a height range.
|
||||
pub fn iter_difficulty_epochs(
|
||||
computer: &Computer,
|
||||
start_height: usize,
|
||||
end_height: usize,
|
||||
) -> Vec<DifficultyAdjustmentEntry> {
|
||||
let start_epoch = computer
|
||||
.indexes
|
||||
.height_to_difficultyepoch
|
||||
.read_once(Height::from(start_height))
|
||||
.unwrap_or_default();
|
||||
let end_epoch = computer
|
||||
.indexes
|
||||
.height_to_difficultyepoch
|
||||
.read_once(Height::from(end_height))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut epoch_to_height_iter = computer.indexes.difficultyepoch_to_first_height.iter();
|
||||
let mut epoch_to_timestamp_iter = computer.chain.difficultyepoch_to_timestamp.iter();
|
||||
let mut epoch_to_difficulty_iter = computer
|
||||
.chain
|
||||
.indexes_to_difficulty
|
||||
.difficultyepoch
|
||||
.unwrap_last()
|
||||
.iter();
|
||||
|
||||
let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1);
|
||||
let mut prev_difficulty: Option<f64> = None;
|
||||
|
||||
for epoch_usize in start_epoch.to_usize()..=end_epoch.to_usize() {
|
||||
let epoch = DifficultyEpoch::from(epoch_usize);
|
||||
let epoch_height = epoch_to_height_iter.get(epoch).unwrap_or_default();
|
||||
|
||||
// Skip epochs before our start height but track difficulty
|
||||
if epoch_height.to_usize() < start_height {
|
||||
prev_difficulty = epoch_to_difficulty_iter.get(epoch).map(|d| *d);
|
||||
continue;
|
||||
}
|
||||
|
||||
let epoch_timestamp = epoch_to_timestamp_iter.get(epoch).unwrap_or_default();
|
||||
let epoch_difficulty = *epoch_to_difficulty_iter.get(epoch).unwrap_or_default();
|
||||
|
||||
let change_percent = match prev_difficulty {
|
||||
Some(prev) if prev > 0.0 => ((epoch_difficulty / prev) - 1.0) * 100.0,
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
results.push(DifficultyAdjustmentEntry {
|
||||
timestamp: epoch_timestamp,
|
||||
height: epoch_height,
|
||||
difficulty: epoch_difficulty,
|
||||
change_percent,
|
||||
});
|
||||
|
||||
prev_difficulty = Some(epoch_difficulty);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{DateIndex, DifficultyEntry, HashrateEntry, HashrateSummary, Height, TimePeriod};
|
||||
use vecdb::{GenericStoredVec, IterableVec, VecIndex};
|
||||
|
||||
use super::epochs::iter_difficulty_epochs;
|
||||
use crate::Query;
|
||||
|
||||
/// Get hashrate and difficulty data for a time period.
|
||||
pub fn get_hashrate(time_period: Option<TimePeriod>, query: &Query) -> Result<HashrateSummary> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
|
||||
// Get current difficulty
|
||||
let current_difficulty = *indexer
|
||||
.vecs
|
||||
.block
|
||||
.height_to_difficulty
|
||||
.read_once(current_height)?;
|
||||
|
||||
// Get current hashrate
|
||||
let current_dateindex = computer
|
||||
.indexes
|
||||
.height_to_dateindex
|
||||
.read_once(current_height)?;
|
||||
let current_hashrate = *computer
|
||||
.chain
|
||||
.indexes_to_hash_rate
|
||||
.dateindex
|
||||
.unwrap_last()
|
||||
.read_once(current_dateindex)? as u128;
|
||||
|
||||
// Calculate start height based on time period
|
||||
let end = current_height.to_usize();
|
||||
let start = match time_period {
|
||||
Some(tp) => end.saturating_sub(tp.block_count()),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// Get hashrate entries using iterators for efficiency
|
||||
let start_dateindex = computer
|
||||
.indexes
|
||||
.height_to_dateindex
|
||||
.read_once(Height::from(start))?;
|
||||
let end_dateindex = current_dateindex;
|
||||
|
||||
// Sample at regular intervals to avoid too many data points
|
||||
let total_days = end_dateindex
|
||||
.to_usize()
|
||||
.saturating_sub(start_dateindex.to_usize())
|
||||
+ 1;
|
||||
let step = (total_days / 200).max(1); // Max ~200 data points
|
||||
|
||||
// Create iterators for the loop
|
||||
let mut hashrate_iter = computer
|
||||
.chain
|
||||
.indexes_to_hash_rate
|
||||
.dateindex
|
||||
.unwrap_last()
|
||||
.iter();
|
||||
let mut timestamp_iter = computer
|
||||
.chain
|
||||
.timeindexes_to_timestamp
|
||||
.dateindex_extra
|
||||
.unwrap_first()
|
||||
.iter();
|
||||
|
||||
let mut hashrates = Vec::with_capacity(total_days / step + 1);
|
||||
let mut di = start_dateindex.to_usize();
|
||||
while di <= end_dateindex.to_usize() {
|
||||
let dateindex = DateIndex::from(di);
|
||||
if let (Some(hr), Some(timestamp)) =
|
||||
(hashrate_iter.get(dateindex), timestamp_iter.get(dateindex))
|
||||
{
|
||||
hashrates.push(HashrateEntry {
|
||||
timestamp,
|
||||
avg_hashrate: (*hr) as u128,
|
||||
});
|
||||
}
|
||||
di += step;
|
||||
}
|
||||
|
||||
// Get difficulty adjustments within the period
|
||||
let difficulty: Vec<DifficultyEntry> = iter_difficulty_epochs(computer, start, end)
|
||||
.into_iter()
|
||||
.map(|e| DifficultyEntry {
|
||||
timestamp: e.timestamp,
|
||||
difficulty: e.difficulty,
|
||||
height: e.height,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HashrateSummary {
|
||||
hashrates,
|
||||
difficulty,
|
||||
current_hashrate,
|
||||
current_difficulty,
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,21 @@
|
||||
mod block_fee_rates;
|
||||
mod block_fees;
|
||||
mod block_rewards;
|
||||
mod block_sizes_weights;
|
||||
mod dateindex_iter;
|
||||
mod difficulty;
|
||||
mod difficulty_adjustments;
|
||||
mod epochs;
|
||||
mod hashrate;
|
||||
mod pools;
|
||||
mod reward_stats;
|
||||
|
||||
pub use block_fee_rates::*;
|
||||
pub use block_fees::*;
|
||||
pub use block_rewards::*;
|
||||
pub use block_sizes_weights::*;
|
||||
pub use difficulty::*;
|
||||
pub use difficulty_adjustments::*;
|
||||
pub use hashrate::*;
|
||||
pub use pools::*;
|
||||
pub use reward_stats::*;
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{
|
||||
Height, PoolBlockCounts, PoolBlockShares, PoolDetail, PoolDetailInfo, PoolInfo, PoolSlug,
|
||||
PoolStats, PoolsSummary, TimePeriod, pools,
|
||||
};
|
||||
use vecdb::{AnyVec, GenericStoredVec, IterableVec, VecIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
/// Get mining pool statistics for a time period using pre-computed cumulative counts.
|
||||
pub fn get_mining_pools(time_period: TimePeriod, query: &Query) -> Result<PoolsSummary> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let end = current_height.to_usize();
|
||||
|
||||
// No blocks indexed yet
|
||||
if computer.pools.height_to_pool.len() == 0 {
|
||||
return Ok(PoolsSummary {
|
||||
pools: vec![],
|
||||
block_count: 0,
|
||||
last_estimated_hashrate: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate start height based on time period
|
||||
let start = end.saturating_sub(time_period.block_count());
|
||||
|
||||
let pools = pools();
|
||||
let mut pool_data: Vec<(&'static brk_types::Pool, u32)> = Vec::new();
|
||||
|
||||
// For each pool, get cumulative count at end and start, subtract to get range count
|
||||
for (pool_id, pool_vecs) in &computer.pools.vecs {
|
||||
let mut cumulative = pool_vecs
|
||||
.indexes_to_blocks_mined
|
||||
.height_extra
|
||||
.unwrap_cumulative()
|
||||
.iter();
|
||||
|
||||
let count_at_end: u32 = *cumulative.get(current_height).unwrap_or_default();
|
||||
|
||||
let count_at_start: u32 = if start == 0 {
|
||||
0
|
||||
} else {
|
||||
*cumulative.get(Height::from(start - 1)).unwrap_or_default()
|
||||
};
|
||||
|
||||
let block_count = count_at_end.saturating_sub(count_at_start);
|
||||
|
||||
// Only include pools that mined at least one block in the period
|
||||
if block_count > 0 {
|
||||
pool_data.push((pools.get(*pool_id), block_count));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by block count descending
|
||||
pool_data.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
let total_blocks: u32 = pool_data.iter().map(|(_, count)| count).sum();
|
||||
|
||||
// Build stats with ranks
|
||||
let pool_stats: Vec<PoolStats> = pool_data
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, (pool, block_count))| {
|
||||
let share = if total_blocks > 0 {
|
||||
block_count as f64 / total_blocks as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
PoolStats::new(pool, block_count, (idx + 1) as u32, share)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: Calculate actual hashrate from difficulty
|
||||
let last_estimated_hashrate = 0u128;
|
||||
|
||||
Ok(PoolsSummary {
|
||||
pools: pool_stats,
|
||||
block_count: total_blocks,
|
||||
last_estimated_hashrate,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get list of all known mining pools (no statistics).
|
||||
pub fn get_all_pools() -> Vec<PoolInfo> {
|
||||
pools().iter().map(PoolInfo::from).collect()
|
||||
}
|
||||
|
||||
/// Get detailed information about a specific pool by slug.
|
||||
pub fn get_pool_detail(slug: PoolSlug, query: &Query) -> Result<PoolDetail> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
let end = current_height.to_usize();
|
||||
|
||||
let pools_list = pools();
|
||||
let pool = pools_list.get(slug);
|
||||
|
||||
// Get pool vecs for this specific pool
|
||||
let pool_vecs = computer
|
||||
.pools
|
||||
.vecs
|
||||
.get(&slug)
|
||||
.ok_or_else(|| Error::Str("Pool data not found"))?;
|
||||
|
||||
let mut cumulative = pool_vecs
|
||||
.indexes_to_blocks_mined
|
||||
.height_extra
|
||||
.unwrap_cumulative()
|
||||
.iter();
|
||||
|
||||
// Get total blocks (all time)
|
||||
let total_all: u32 = *cumulative.get(current_height).unwrap_or_default();
|
||||
|
||||
// Get blocks for 24h (144 blocks)
|
||||
let start_24h = end.saturating_sub(144);
|
||||
let count_before_24h: u32 = if start_24h == 0 {
|
||||
0
|
||||
} else {
|
||||
*cumulative
|
||||
.get(Height::from(start_24h - 1))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let total_24h = total_all.saturating_sub(count_before_24h);
|
||||
|
||||
// Get blocks for 1w (1008 blocks)
|
||||
let start_1w = end.saturating_sub(1008);
|
||||
let count_before_1w: u32 = if start_1w == 0 {
|
||||
0
|
||||
} else {
|
||||
*cumulative
|
||||
.get(Height::from(start_1w - 1))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
let total_1w = total_all.saturating_sub(count_before_1w);
|
||||
|
||||
// Calculate total network blocks for share calculation
|
||||
let network_blocks_all = (end + 1) as u32;
|
||||
let network_blocks_24h = (end - start_24h + 1) as u32;
|
||||
let network_blocks_1w = (end - start_1w + 1) as u32;
|
||||
|
||||
let share_all = if network_blocks_all > 0 {
|
||||
total_all as f64 / network_blocks_all as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let share_24h = if network_blocks_24h > 0 {
|
||||
total_24h as f64 / network_blocks_24h as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let share_1w = if network_blocks_1w > 0 {
|
||||
total_1w as f64 / network_blocks_1w as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
Ok(PoolDetail {
|
||||
pool: PoolDetailInfo::from(pool),
|
||||
block_count: PoolBlockCounts {
|
||||
all: total_all,
|
||||
day: total_24h,
|
||||
week: total_1w,
|
||||
},
|
||||
block_share: PoolBlockShares {
|
||||
all: share_all,
|
||||
day: share_24h,
|
||||
week: share_1w,
|
||||
},
|
||||
estimated_hashrate: 0, // TODO: Calculate from share and network hashrate
|
||||
reported_hashrate: None,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Height, RewardStats, Sats};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
|
||||
use crate::Query;
|
||||
|
||||
pub fn get_reward_stats(block_count: usize, query: &Query) -> Result<RewardStats> {
|
||||
let computer = query.computer();
|
||||
let current_height = query.get_height();
|
||||
|
||||
let end_block = current_height;
|
||||
let start_block = Height::from(current_height.to_usize().saturating_sub(block_count - 1));
|
||||
|
||||
let mut coinbase_iter = computer
|
||||
.chain
|
||||
.indexes_to_coinbase
|
||||
.sats
|
||||
.height
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter();
|
||||
let mut fee_iter = computer.chain.indexes_to_fee.sats.height.unwrap_sum().iter();
|
||||
let mut tx_count_iter = computer
|
||||
.chain
|
||||
.indexes_to_tx_count
|
||||
.height
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter();
|
||||
|
||||
let mut total_reward = Sats::ZERO;
|
||||
let mut total_fee = Sats::ZERO;
|
||||
let mut total_tx: u64 = 0;
|
||||
|
||||
for height in start_block.to_usize()..=end_block.to_usize() {
|
||||
let h = Height::from(height);
|
||||
|
||||
if let Some(coinbase) = coinbase_iter.get(h) {
|
||||
total_reward += Sats::from(u64::from(*coinbase));
|
||||
}
|
||||
|
||||
if let Some(fee) = fee_iter.get(h) {
|
||||
total_fee += Sats::from(u64::from(*fee));
|
||||
}
|
||||
|
||||
if let Some(tx_count) = tx_count_iter.get(h) {
|
||||
total_tx += u64::from(*tx_count);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RewardStats {
|
||||
start_block,
|
||||
end_block,
|
||||
total_reward,
|
||||
total_fee,
|
||||
total_tx,
|
||||
})
|
||||
}
|
||||
@@ -47,7 +47,10 @@ pub fn get_tx_outspend(
|
||||
|
||||
// Look up spend status
|
||||
let computer = query.computer();
|
||||
let txinindex = computer.stateful.txoutindex_to_txinindex.read_once(txoutindex)?;
|
||||
let txinindex = computer
|
||||
.stateful
|
||||
.txoutindex_to_txinindex
|
||||
.read_once(txoutindex)?;
|
||||
|
||||
if txinindex == TxInIndex::UNSPENT {
|
||||
return Ok(TxOutspend::UNSPENT);
|
||||
@@ -119,10 +122,13 @@ pub fn get_tx_outspends(TxidPath { txid }: TxidPath, query: &Query) -> Result<Ve
|
||||
/// Get spending transaction details from a txinindex
|
||||
fn get_outspend_details(txinindex: TxInIndex, query: &Query) -> Result<TxOutspend> {
|
||||
let indexer = query.indexer();
|
||||
let computer = query.computer();
|
||||
|
||||
// Look up spending txindex directly
|
||||
let spending_txindex = computer.indexes.txinindex_to_txindex.read_once(txinindex)?;
|
||||
let spending_txindex = indexer
|
||||
.vecs
|
||||
.txin
|
||||
.txinindex_to_txindex
|
||||
.read_once(txinindex)?;
|
||||
|
||||
// Calculate vin
|
||||
let spending_first_txinindex = indexer
|
||||
@@ -133,8 +139,16 @@ fn get_outspend_details(txinindex: TxInIndex, query: &Query) -> Result<TxOutspen
|
||||
let vin = Vin::from(usize::from(txinindex) - usize::from(spending_first_txinindex));
|
||||
|
||||
// Get spending tx details
|
||||
let spending_txid = indexer.vecs.tx.txindex_to_txid.read_once(spending_txindex)?;
|
||||
let spending_height = indexer.vecs.tx.txindex_to_height.read_once(spending_txindex)?;
|
||||
let spending_txid = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_txid
|
||||
.read_once(spending_txindex)?;
|
||||
let spending_height = indexer
|
||||
.vecs
|
||||
.tx
|
||||
.txindex_to_height
|
||||
.read_once(spending_txindex)?;
|
||||
let block_hash = indexer
|
||||
.vecs
|
||||
.block
|
||||
|
||||
@@ -10,9 +10,10 @@ use brk_mempool::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_traversable::TreeNode;
|
||||
use brk_types::{
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, Format, Height, Index,
|
||||
IndexInfo, Limit, MempoolInfo, Metric, MetricCount, RecommendedFees, Timestamp, Transaction,
|
||||
TxOutspend, TxStatus, Txid, TxidPath, Utxo, Vout,
|
||||
Address, AddressStats, BlockInfo, BlockStatus, BlockTimestamp, Format, HashrateSummary,
|
||||
Height, Index, IndexInfo, Limit, MempoolInfo, Metric, MetricCount, PoolDetail, PoolInfo,
|
||||
PoolSlug, PoolsSummary, RecommendedFees, TimePeriod, Timestamp, Transaction, TxOutspend,
|
||||
TxStatus, Txid, TxidPath, Utxo, Vout,
|
||||
};
|
||||
use vecdb::{AnyExportableVec, AnyStoredVec};
|
||||
|
||||
@@ -37,11 +38,12 @@ pub use crate::chain::validate_address;
|
||||
use crate::{
|
||||
chain::{
|
||||
get_address, get_address_mempool_txids, get_address_txids, get_address_utxos,
|
||||
get_block_by_height, get_block_by_timestamp, get_block_raw, get_block_status_by_height,
|
||||
get_block_txid_at_index, get_block_txids, get_block_txs, get_blocks,
|
||||
get_difficulty_adjustment, get_height_by_hash, get_mempool_blocks, get_mempool_info,
|
||||
get_mempool_txids, get_recommended_fees, get_transaction, get_transaction_hex,
|
||||
get_transaction_status, get_tx_outspend, get_tx_outspends,
|
||||
get_all_pools, get_block_by_height, get_block_by_timestamp, get_block_raw,
|
||||
get_block_status_by_height, get_block_txid_at_index, get_block_txids, get_block_txs,
|
||||
get_blocks, get_difficulty_adjustment, get_hashrate, get_height_by_hash,
|
||||
get_mempool_blocks, get_mempool_info, get_mempool_txids, get_mining_pools, get_pool_detail,
|
||||
get_recommended_fees, get_transaction, get_transaction_hex, get_transaction_status,
|
||||
get_tx_outspend, get_tx_outspends,
|
||||
},
|
||||
vecs::{IndexToVec, MetricToVec},
|
||||
};
|
||||
@@ -184,6 +186,58 @@ impl Query {
|
||||
get_difficulty_adjustment(self)
|
||||
}
|
||||
|
||||
pub fn get_mining_pools(&self, time_period: TimePeriod) -> Result<PoolsSummary> {
|
||||
get_mining_pools(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_all_pools(&self) -> Vec<PoolInfo> {
|
||||
get_all_pools()
|
||||
}
|
||||
|
||||
pub fn get_pool_detail(&self, slug: PoolSlug) -> Result<PoolDetail> {
|
||||
get_pool_detail(slug, self)
|
||||
}
|
||||
|
||||
pub fn get_hashrate(&self, time_period: Option<TimePeriod>) -> Result<HashrateSummary> {
|
||||
get_hashrate(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_difficulty_adjustments(
|
||||
&self,
|
||||
time_period: Option<TimePeriod>,
|
||||
) -> Result<Vec<brk_types::DifficultyAdjustmentEntry>> {
|
||||
chain::get_difficulty_adjustments(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_block_fees(&self, time_period: TimePeriod) -> Result<Vec<brk_types::BlockFeesEntry>> {
|
||||
chain::get_block_fees(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_block_rewards(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<Vec<brk_types::BlockRewardsEntry>> {
|
||||
chain::get_block_rewards(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_block_fee_rates(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<Vec<brk_types::BlockFeeRatesEntry>> {
|
||||
chain::get_block_fee_rates(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_block_sizes_weights(
|
||||
&self,
|
||||
time_period: TimePeriod,
|
||||
) -> Result<brk_types::BlockSizesWeights> {
|
||||
chain::get_block_sizes_weights(time_period, self)
|
||||
}
|
||||
|
||||
pub fn get_reward_stats(&self, block_count: usize) -> Result<brk_types::RewardStats> {
|
||||
chain::get_reward_stats(block_count, self)
|
||||
}
|
||||
|
||||
pub fn match_metric(&self, metric: &Metric, limit: Limit) -> Vec<&'static str> {
|
||||
self.vecs().matches(metric, limit)
|
||||
}
|
||||
|
||||
@@ -118,21 +118,22 @@ impl AddressRoutes for ApiRouter<AppState> {
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/address/{address}/txs/chain/{after_txid}",
|
||||
"/api/address/{address}/txs/chain",
|
||||
get_with(async |
|
||||
headers: HeaderMap,
|
||||
Path((address, after_txid)): Path<(Address, Option<Txid>)>,
|
||||
Path(address): Path<Address>,
|
||||
Query(params): Query<AddressTxidsParam>,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let etag = format!("{VERSION}-{}", state.get_height().await);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state.get_address_txids(address, after_txid, 25).await.to_json_response(&etag)
|
||||
state.get_address_txids(address, params.after_txid, 25).await.to_json_response(&etag)
|
||||
}, |op| op
|
||||
.addresses_tag()
|
||||
.summary("Address confirmed transactions")
|
||||
.description("Get confirmed transaction IDs for an address, 25 per page. Use after_txid for pagination.")
|
||||
.description("Get confirmed transaction IDs for an address, 25 per page. Use ?after_txid=<txid> for pagination.")
|
||||
.ok_response::<Vec<Txid>>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use aide::axum::{ApiRouter, routing::get_with};
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Path, State},
|
||||
http::HeaderMap,
|
||||
response::{Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
use brk_types::DifficultyAdjustment;
|
||||
use brk_types::{
|
||||
BlockCountPath, BlockFeeRatesEntry, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
|
||||
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo,
|
||||
PoolSlugPath, PoolsSummary, RewardStats, TimePeriodPath,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
VERSION,
|
||||
@@ -47,5 +51,279 @@ impl MiningRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pools",
|
||||
get_with(
|
||||
async |headers: HeaderMap, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-pools");
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state.get_all_pools().await.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("List all mining pools")
|
||||
.description("Get list of all known mining pools with their identifiers.")
|
||||
.ok_response::<Vec<PoolInfo>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pools/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_mining_pools(path.time_period)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Mining pool statistics")
|
||||
.description("Get mining pool statistics for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<PoolsSummary>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pool/:slug",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<PoolSlugPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-{}-{:?}", state.get_height().await, path.slug);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_pool_detail(path.slug)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Mining pool details")
|
||||
.description("Get detailed information about a specific mining pool including block counts and shares for different time periods.")
|
||||
.ok_response::<PoolDetail>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate",
|
||||
get_with(
|
||||
async |headers: HeaderMap, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-hashrate-{}", state.get_height().await);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_hashrate(None)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Network hashrate (all time)")
|
||||
.description("Get network hashrate and difficulty data for all time.")
|
||||
.ok_response::<HashrateSummary>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-hashrate-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_hashrate(Some(path.time_period))
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Network hashrate")
|
||||
.description("Get network hashrate and difficulty data for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<HashrateSummary>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/difficulty-adjustments",
|
||||
get_with(
|
||||
async |headers: HeaderMap, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-diff-adj-{}", state.get_height().await);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_difficulty_adjustments(None)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Difficulty adjustments (all time)")
|
||||
.description("Get historical difficulty adjustments. Returns array of [timestamp, height, difficulty, change_percent].")
|
||||
.ok_response::<Vec<DifficultyAdjustmentEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/difficulty-adjustments/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-diff-adj-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_difficulty_adjustments(Some(path.time_period))
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Difficulty adjustments")
|
||||
.description("Get historical difficulty adjustments for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y. Returns array of [timestamp, height, difficulty, change_percent].")
|
||||
.ok_response::<Vec<DifficultyAdjustmentEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/blocks/fees/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-fees-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_block_fees(path.time_period)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Block fees")
|
||||
.description("Get average block fees for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<Vec<BlockFeesEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/blocks/rewards/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-rewards-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_block_rewards(path.time_period)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Block rewards")
|
||||
.description("Get average block rewards (coinbase = subsidy + fees) for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<Vec<BlockRewardsEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/blocks/fee-rates/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-feerates-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_block_fee_rates(path.time_period)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Block fee rates")
|
||||
.description("Get block fee rate percentiles (min, 10th, 25th, median, 75th, 90th, max) for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<Vec<BlockFeeRatesEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/blocks/sizes-weights/:time_period",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-sizes-{}-{:?}", state.get_height().await, path.time_period);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_block_sizes_weights(path.time_period)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Block sizes and weights")
|
||||
.description("Get average block sizes and weights for a time period. Valid periods: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y")
|
||||
.ok_response::<BlockSizesWeights>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/reward-stats/:block_count",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<BlockCountPath>, State(state): State<AppState>| {
|
||||
let etag = format!("{VERSION}-reward-stats-{}-{}", state.get_height().await, path.block_count);
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
state
|
||||
.get_reward_stats(path.block_count)
|
||||
.await
|
||||
.to_json_response(&etag)
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
.summary("Mining reward statistics")
|
||||
.description("Get mining reward statistics for the last N blocks including total rewards, fees, and transaction count.")
|
||||
.ok_response::<RewardStats>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct BlockCountPath {
|
||||
/// Number of blocks to include in the stats
|
||||
#[schemars(example = 100)]
|
||||
pub block_count: usize,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A single block fees data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockFeesEntry {
|
||||
pub avg_height: u32,
|
||||
pub timestamp: u32,
|
||||
pub avg_fees: u64,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::FeeRatePercentiles;
|
||||
|
||||
/// A single block fee rates data point with percentiles.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockFeeRatesEntry {
|
||||
pub avg_height: u32,
|
||||
pub timestamp: u32,
|
||||
#[serde(flatten)]
|
||||
pub percentiles: FeeRatePercentiles,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A single block rewards data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockRewardsEntry {
|
||||
pub avg_height: u32,
|
||||
pub timestamp: u32,
|
||||
pub avg_rewards: u64,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A single block size data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockSizeEntry {
|
||||
pub avg_height: u32,
|
||||
pub timestamp: u32,
|
||||
pub avg_size: u64,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{BlockSizeEntry, BlockWeightEntry};
|
||||
|
||||
/// Combined block sizes and weights response.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct BlockSizesWeights {
|
||||
pub sizes: Vec<BlockSizeEntry>,
|
||||
pub weights: Vec<BlockWeightEntry>,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A single block weight data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockWeightEntry {
|
||||
pub avg_height: u32,
|
||||
pub timestamp: u32,
|
||||
pub avg_weight: u64,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::ser::SerializeTuple;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::{Height, Timestamp};
|
||||
|
||||
/// A single difficulty adjustment entry.
|
||||
/// Serializes as array: [timestamp, height, difficulty, change_percent]
|
||||
#[derive(Debug, JsonSchema)]
|
||||
pub struct DifficultyAdjustmentEntry {
|
||||
pub timestamp: Timestamp,
|
||||
pub height: Height,
|
||||
pub difficulty: f64,
|
||||
pub change_percent: f64,
|
||||
}
|
||||
|
||||
impl Serialize for DifficultyAdjustmentEntry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut tup = serializer.serialize_tuple(4)?;
|
||||
tup.serialize_element(&self.timestamp)?;
|
||||
tup.serialize_element(&self.height)?;
|
||||
tup.serialize_element(&self.difficulty)?;
|
||||
tup.serialize_element(&self.change_percent)?;
|
||||
tup.end()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Height, Timestamp};
|
||||
|
||||
/// A single difficulty data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct DifficultyEntry {
|
||||
/// Unix timestamp of the difficulty adjustment.
|
||||
pub timestamp: Timestamp,
|
||||
/// Difficulty value.
|
||||
pub difficulty: f64,
|
||||
/// Block height of the adjustment.
|
||||
pub height: Height,
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::FeeRate;
|
||||
|
||||
/// Fee rate percentiles (min, 10%, 25%, 50%, 75%, 90%, max).
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, JsonSchema)]
|
||||
pub struct FeeRatePercentiles {
|
||||
#[serde(rename = "avgFee_0")]
|
||||
pub min: FeeRate,
|
||||
#[serde(rename = "avgFee_10")]
|
||||
pub pct10: FeeRate,
|
||||
#[serde(rename = "avgFee_25")]
|
||||
pub pct25: FeeRate,
|
||||
#[serde(rename = "avgFee_50")]
|
||||
pub median: FeeRate,
|
||||
#[serde(rename = "avgFee_75")]
|
||||
pub pct75: FeeRate,
|
||||
#[serde(rename = "avgFee_90")]
|
||||
pub pct90: FeeRate,
|
||||
#[serde(rename = "avgFee_100")]
|
||||
pub max: FeeRate,
|
||||
}
|
||||
|
||||
impl FeeRatePercentiles {
|
||||
pub fn new(
|
||||
min: FeeRate,
|
||||
pct10: FeeRate,
|
||||
pct25: FeeRate,
|
||||
median: FeeRate,
|
||||
pct75: FeeRate,
|
||||
pct90: FeeRate,
|
||||
max: FeeRate,
|
||||
) -> Self {
|
||||
Self {
|
||||
min,
|
||||
pct10,
|
||||
pct25,
|
||||
median,
|
||||
pct75,
|
||||
pct90,
|
||||
max,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to array format [min, 10%, 25%, 50%, 75%, 90%, max].
|
||||
pub fn to_array(&self) -> [FeeRate; 7] {
|
||||
[
|
||||
self.min,
|
||||
self.pct10,
|
||||
self.pct25,
|
||||
self.median,
|
||||
self.pct75,
|
||||
self.pct90,
|
||||
self.max,
|
||||
]
|
||||
}
|
||||
|
||||
/// Create from array format [min, 10%, 25%, 50%, 75%, 90%, max].
|
||||
pub fn from_array(arr: [FeeRate; 7]) -> Self {
|
||||
Self {
|
||||
min: arr[0],
|
||||
pct10: arr[1],
|
||||
pct25: arr[2],
|
||||
median: arr[3],
|
||||
pct75: arr[4],
|
||||
pct90: arr[5],
|
||||
max: arr[6],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Timestamp;
|
||||
|
||||
/// A single hashrate data point.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct HashrateEntry {
|
||||
/// Unix timestamp.
|
||||
pub timestamp: Timestamp,
|
||||
/// Average hashrate (H/s).
|
||||
#[serde(rename = "avgHashrate")]
|
||||
pub avg_hashrate: u128,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{DifficultyEntry, HashrateEntry};
|
||||
|
||||
/// Summary of network hashrate and difficulty data.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct HashrateSummary {
|
||||
/// Historical hashrate data points.
|
||||
pub hashrates: Vec<HashrateEntry>,
|
||||
/// Historical difficulty adjustments.
|
||||
pub difficulty: Vec<DifficultyEntry>,
|
||||
/// Current network hashrate (H/s).
|
||||
#[serde(rename = "currentHashrate")]
|
||||
pub current_hashrate: u128,
|
||||
/// Current network difficulty.
|
||||
#[serde(rename = "currentDifficulty")]
|
||||
pub current_difficulty: f64,
|
||||
}
|
||||
+49
-13
@@ -9,21 +9,28 @@ mod addresshash;
|
||||
mod addressindexoutpoint;
|
||||
mod addressindextxindex;
|
||||
mod addressmempoolstats;
|
||||
mod addresstxidsparam;
|
||||
mod addressstats;
|
||||
mod addresstxidsparam;
|
||||
mod addressvalidation;
|
||||
mod anyaddressindex;
|
||||
mod bitcoin;
|
||||
mod blkmetadata;
|
||||
mod blkposition;
|
||||
mod block;
|
||||
mod blockcountpath;
|
||||
mod blockhash;
|
||||
mod blockhashpath;
|
||||
mod blockhashprefix;
|
||||
mod blockhashstartindexpath;
|
||||
mod blockhashtxindexpath;
|
||||
mod blockhashprefix;
|
||||
mod blockfeesentry;
|
||||
mod blockferatesentry;
|
||||
mod blockinfo;
|
||||
mod blockrewardsentry;
|
||||
mod blocksizeentry;
|
||||
mod blocksizesweights;
|
||||
mod blockstatus;
|
||||
mod blockweightentry;
|
||||
mod blocktimestamp;
|
||||
mod bytes;
|
||||
mod cents;
|
||||
@@ -31,14 +38,19 @@ mod date;
|
||||
mod dateindex;
|
||||
mod decadeindex;
|
||||
mod difficultyadjustment;
|
||||
mod difficultyadjustmententry;
|
||||
mod difficultyentry;
|
||||
mod difficultyepoch;
|
||||
mod dollars;
|
||||
mod emptyaddressdata;
|
||||
mod emptyaddressindex;
|
||||
mod emptyoutputindex;
|
||||
mod feerate;
|
||||
mod feeratepercentiles;
|
||||
mod format;
|
||||
mod halvingepoch;
|
||||
mod hashrateentry;
|
||||
mod hashratesummary;
|
||||
mod health;
|
||||
mod height;
|
||||
mod heightpath;
|
||||
@@ -47,10 +59,10 @@ mod indexinfo;
|
||||
mod limit;
|
||||
mod loadedaddressdata;
|
||||
mod loadedaddressindex;
|
||||
mod metric;
|
||||
mod mempoolentryinfo;
|
||||
mod mempoolblock;
|
||||
mod mempoolentryinfo;
|
||||
mod mempoolinfo;
|
||||
mod metric;
|
||||
mod metriccount;
|
||||
mod metrics;
|
||||
mod monthindex;
|
||||
@@ -76,25 +88,31 @@ mod p2wpkhbytes;
|
||||
mod p2wshaddressindex;
|
||||
mod p2wshbytes;
|
||||
mod pool;
|
||||
mod poolid;
|
||||
mod poolsresponse;
|
||||
mod poolstats;
|
||||
mod pooldetail;
|
||||
mod poolinfo;
|
||||
mod pools;
|
||||
mod poolslug;
|
||||
mod poolslugpath;
|
||||
mod poolssummary;
|
||||
mod poolstats;
|
||||
mod quarterindex;
|
||||
mod rawlocktime;
|
||||
mod recommendedfees;
|
||||
mod rewardstats;
|
||||
mod sats;
|
||||
mod semesterindex;
|
||||
mod startheightpath;
|
||||
mod stored_bool;
|
||||
mod stored_f32;
|
||||
mod stored_f64;
|
||||
mod stored_i16;
|
||||
mod startheightpath;
|
||||
mod stored_string;
|
||||
mod stored_u16;
|
||||
mod stored_u32;
|
||||
mod stored_u64;
|
||||
mod stored_u8;
|
||||
mod timeperiod;
|
||||
mod timeperiodpath;
|
||||
mod timestamp;
|
||||
mod timestamppath;
|
||||
mod treenode;
|
||||
@@ -138,13 +156,20 @@ pub use bitcoin::*;
|
||||
pub use blkmetadata::*;
|
||||
pub use blkposition::*;
|
||||
pub use block::*;
|
||||
pub use blockcountpath::*;
|
||||
pub use blockhash::*;
|
||||
pub use blockhashpath::*;
|
||||
pub use blockhashprefix::*;
|
||||
pub use blockhashstartindexpath::*;
|
||||
pub use blockhashtxindexpath::*;
|
||||
pub use blockhashprefix::*;
|
||||
pub use blockfeesentry::*;
|
||||
pub use blockferatesentry::*;
|
||||
pub use blockinfo::*;
|
||||
pub use blockrewardsentry::*;
|
||||
pub use blocksizeentry::*;
|
||||
pub use blocksizesweights::*;
|
||||
pub use blockstatus::*;
|
||||
pub use blockweightentry::*;
|
||||
pub use blocktimestamp::*;
|
||||
pub use bytes::*;
|
||||
pub use cents::*;
|
||||
@@ -152,14 +177,19 @@ pub use date::*;
|
||||
pub use dateindex::*;
|
||||
pub use decadeindex::*;
|
||||
pub use difficultyadjustment::*;
|
||||
pub use difficultyadjustmententry::*;
|
||||
pub use difficultyentry::*;
|
||||
pub use difficultyepoch::*;
|
||||
pub use dollars::*;
|
||||
pub use emptyaddressdata::*;
|
||||
pub use emptyaddressindex::*;
|
||||
pub use emptyoutputindex::*;
|
||||
pub use feerate::*;
|
||||
pub use feeratepercentiles::*;
|
||||
pub use format::*;
|
||||
pub use halvingepoch::*;
|
||||
pub use hashrateentry::*;
|
||||
pub use hashratesummary::*;
|
||||
pub use health::*;
|
||||
pub use height::*;
|
||||
pub use heightpath::*;
|
||||
@@ -168,8 +198,8 @@ pub use indexinfo::*;
|
||||
pub use limit::*;
|
||||
pub use loadedaddressdata::*;
|
||||
pub use loadedaddressindex::*;
|
||||
pub use mempoolentryinfo::*;
|
||||
pub use mempoolblock::*;
|
||||
pub use mempoolentryinfo::*;
|
||||
pub use mempoolinfo::*;
|
||||
pub use metric::*;
|
||||
pub use metriccount::*;
|
||||
@@ -197,25 +227,31 @@ pub use p2wpkhbytes::*;
|
||||
pub use p2wshaddressindex::*;
|
||||
pub use p2wshbytes::*;
|
||||
pub use pool::*;
|
||||
pub use poolid::*;
|
||||
pub use pooldetail::*;
|
||||
pub use poolinfo::*;
|
||||
pub use pools::*;
|
||||
pub use poolsresponse::*;
|
||||
pub use poolslug::*;
|
||||
pub use poolslugpath::*;
|
||||
pub use poolssummary::*;
|
||||
pub use poolstats::*;
|
||||
pub use quarterindex::*;
|
||||
pub use rawlocktime::*;
|
||||
pub use recommendedfees::*;
|
||||
pub use rewardstats::*;
|
||||
pub use sats::*;
|
||||
pub use semesterindex::*;
|
||||
pub use startheightpath::*;
|
||||
pub use stored_bool::*;
|
||||
pub use stored_f32::*;
|
||||
pub use stored_f64::*;
|
||||
pub use stored_i16::*;
|
||||
pub use startheightpath::*;
|
||||
pub use stored_string::*;
|
||||
pub use stored_u8::*;
|
||||
pub use stored_u16::*;
|
||||
pub use stored_u32::*;
|
||||
pub use stored_u64::*;
|
||||
pub use timeperiod::*;
|
||||
pub use timeperiodpath::*;
|
||||
pub use timestamp::*;
|
||||
pub use timestamppath::*;
|
||||
pub use treenode::*;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::PoolId;
|
||||
use super::PoolSlug;
|
||||
|
||||
/// Mining pool information
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct Pool {
|
||||
/// Unique pool identifier
|
||||
pub id: PoolId,
|
||||
pub slug: PoolSlug,
|
||||
|
||||
/// Pool name
|
||||
pub name: &'static str,
|
||||
@@ -30,8 +30,14 @@ pub struct Pool {
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn serialized_id(&self) -> String {
|
||||
self.id.to_string()
|
||||
/// Get slug of pool
|
||||
pub fn slug(&self) -> PoolSlug {
|
||||
self.slug
|
||||
}
|
||||
|
||||
/// Get the pool's unique numeric ID
|
||||
pub fn unique_id(&self) -> u8 {
|
||||
self.slug.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +45,7 @@ impl From<(usize, JSONPool)> for Pool {
|
||||
#[inline]
|
||||
fn from((index, pool): (usize, JSONPool)) -> Self {
|
||||
Self {
|
||||
id: (index as u8).into(),
|
||||
slug: (index as u8).into(),
|
||||
name: pool.name,
|
||||
addresses: pool.addresses,
|
||||
tags_lowercase: pool
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{Pool, PoolSlug};
|
||||
|
||||
/// Detailed pool information with statistics across time periods
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolDetail {
|
||||
/// Pool information
|
||||
pub pool: PoolDetailInfo,
|
||||
|
||||
/// Block counts for different time periods
|
||||
#[serde(rename = "blockCount")]
|
||||
pub block_count: PoolBlockCounts,
|
||||
|
||||
/// Pool's share of total blocks for different time periods
|
||||
#[serde(rename = "blockShare")]
|
||||
pub block_share: PoolBlockShares,
|
||||
|
||||
/// Estimated hashrate based on blocks mined
|
||||
#[serde(rename = "estimatedHashrate")]
|
||||
pub estimated_hashrate: u128,
|
||||
|
||||
/// Self-reported hashrate (if available)
|
||||
#[serde(rename = "reportedHashrate")]
|
||||
pub reported_hashrate: Option<u128>,
|
||||
}
|
||||
|
||||
/// Pool information for detail view
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolDetailInfo {
|
||||
/// Unique pool identifier
|
||||
pub id: u8,
|
||||
|
||||
/// Pool name
|
||||
pub name: &'static str,
|
||||
|
||||
/// Pool website URL
|
||||
pub link: &'static str,
|
||||
|
||||
/// Known payout addresses
|
||||
pub addresses: Vec<&'static str>,
|
||||
|
||||
/// Coinbase tag patterns (regexes)
|
||||
pub regexes: Vec<&'static str>,
|
||||
|
||||
/// URL-friendly pool identifier
|
||||
pub slug: PoolSlug,
|
||||
}
|
||||
|
||||
impl From<&'static Pool> for PoolDetailInfo {
|
||||
fn from(pool: &'static Pool) -> Self {
|
||||
Self {
|
||||
id: pool.unique_id(),
|
||||
name: pool.name,
|
||||
link: pool.link,
|
||||
addresses: pool.addresses.to_vec(),
|
||||
regexes: pool.tags.to_vec(),
|
||||
slug: pool.slug(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block counts for different time periods
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolBlockCounts {
|
||||
/// Total blocks mined (all time)
|
||||
pub all: u32,
|
||||
|
||||
/// Blocks mined in last 24 hours
|
||||
#[serde(rename = "24h")]
|
||||
pub day: u32,
|
||||
|
||||
/// Blocks mined in last week
|
||||
#[serde(rename = "1w")]
|
||||
pub week: u32,
|
||||
}
|
||||
|
||||
/// Pool's share of total blocks for different time periods
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolBlockShares {
|
||||
/// Share of all blocks (0.0 - 1.0)
|
||||
pub all: f64,
|
||||
|
||||
/// Share of blocks in last 24 hours
|
||||
#[serde(rename = "24h")]
|
||||
pub day: f64,
|
||||
|
||||
/// Share of blocks in last week
|
||||
#[serde(rename = "1w")]
|
||||
pub week: f64,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{Pool, PoolSlug};
|
||||
|
||||
/// Basic pool information for listing all pools
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolInfo {
|
||||
/// Pool name
|
||||
pub name: &'static str,
|
||||
|
||||
/// URL-friendly pool identifier
|
||||
pub slug: PoolSlug,
|
||||
|
||||
/// Unique numeric pool identifier
|
||||
pub unique_id: u8,
|
||||
}
|
||||
|
||||
impl From<&'static Pool> for PoolInfo {
|
||||
fn from(pool: &'static Pool) -> Self {
|
||||
Self {
|
||||
name: pool.name,
|
||||
slug: pool.slug(),
|
||||
unique_id: pool.unique_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{slice::Iter, sync::OnceLock};
|
||||
|
||||
use crate::{JSONPool, PoolId};
|
||||
use crate::{JSONPool, PoolSlug};
|
||||
|
||||
use super::Pool;
|
||||
|
||||
@@ -27,8 +27,8 @@ impl Pools {
|
||||
&self.0[0]
|
||||
}
|
||||
|
||||
pub fn get(&self, id: PoolId) -> &Pool {
|
||||
let i: u8 = id.into();
|
||||
pub fn get(&self, slug: PoolSlug) -> &Pool {
|
||||
let i: u8 = slug.into();
|
||||
&self.0[i as usize]
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use vecdb::{Bytes, Formattable};
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[repr(u8)]
|
||||
pub enum PoolId {
|
||||
pub enum PoolSlug {
|
||||
#[default]
|
||||
Unknown,
|
||||
BlockFills,
|
||||
@@ -285,14 +285,14 @@ pub enum PoolId {
|
||||
Dummy255,
|
||||
}
|
||||
|
||||
impl Formattable for PoolId {
|
||||
impl Formattable for PoolSlug {
|
||||
#[inline(always)]
|
||||
fn may_need_escaping() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytes for PoolId {
|
||||
impl Bytes for PoolSlug {
|
||||
type Array = [u8; size_of::<Self>()];
|
||||
|
||||
#[inline]
|
||||
@@ -0,0 +1,11 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::PoolSlug;
|
||||
|
||||
/// Path parameter for pool detail endpoint
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct PoolSlugPath {
|
||||
/// Pool slug (e.g., "foundryusa", "f2pool", "antpool")
|
||||
pub slug: PoolSlug,
|
||||
}
|
||||
@@ -5,11 +5,15 @@ use crate::PoolStats;
|
||||
|
||||
/// Mining pools response for a time period
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolsResponse {
|
||||
pub struct PoolsSummary {
|
||||
/// List of pools sorted by block count descending
|
||||
pub pools: Vec<PoolStats>,
|
||||
|
||||
/// Total blocks in the time period
|
||||
#[serde(rename = "blockCount")]
|
||||
pub block_count: u32,
|
||||
|
||||
/// Estimated network hashrate (hashes per second)
|
||||
#[serde(rename = "lastEstimatedHashrate")]
|
||||
pub last_estimated_hashrate: u128,
|
||||
}
|
||||
@@ -1,19 +1,51 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Pool;
|
||||
use crate::{Pool, PoolSlug};
|
||||
|
||||
/// Mining pool with block statistics for a time period
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct PoolStats {
|
||||
/// Pool information
|
||||
#[serde(flatten)]
|
||||
pub pool: &'static Pool,
|
||||
/// Unique pool identifier
|
||||
#[serde(rename = "poolId")]
|
||||
pub pool_id: u8,
|
||||
|
||||
/// Pool name
|
||||
pub name: &'static str,
|
||||
|
||||
/// Pool website URL
|
||||
pub link: &'static str,
|
||||
|
||||
/// Number of blocks mined in the time period
|
||||
#[serde(rename = "blockCount")]
|
||||
pub block_count: u32,
|
||||
|
||||
/// Pool ranking by block count (1 = most blocks)
|
||||
pub rank: u32,
|
||||
|
||||
/// Number of empty blocks mined
|
||||
#[serde(rename = "emptyBlocks")]
|
||||
pub empty_blocks: u32,
|
||||
|
||||
/// URL-friendly pool identifier
|
||||
pub slug: PoolSlug,
|
||||
|
||||
/// Pool's share of total blocks (0.0 - 1.0)
|
||||
pub share: f64,
|
||||
}
|
||||
|
||||
impl PoolStats {
|
||||
/// Create a new PoolStats from a Pool reference
|
||||
pub fn new(pool: &'static Pool, block_count: u32, rank: u32, share: f64) -> Self {
|
||||
Self {
|
||||
pool_id: pool.unique_id(),
|
||||
name: pool.name,
|
||||
link: pool.link,
|
||||
block_count,
|
||||
rank,
|
||||
empty_blocks: 0, // TODO: track empty blocks if needed
|
||||
slug: pool.slug(),
|
||||
share,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Height, Sats};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RewardStats {
|
||||
pub start_block: Height,
|
||||
pub end_block: Height,
|
||||
#[serde(serialize_with = "sats_as_string")]
|
||||
pub total_reward: Sats,
|
||||
#[serde(serialize_with = "sats_as_string")]
|
||||
pub total_fee: Sats,
|
||||
#[serde(serialize_with = "u64_as_string")]
|
||||
pub total_tx: u64,
|
||||
}
|
||||
|
||||
fn sats_as_string<S>(value: &Sats, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&value.to_string())
|
||||
}
|
||||
|
||||
fn u64_as_string<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&value.to_string())
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Time period for mining statistics.
|
||||
///
|
||||
/// Used to specify the lookback window for pool statistics, hashrate calculations,
|
||||
/// and other time-based mining metrics.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum TimePeriod {
|
||||
/// Last 24 hours (~144 blocks)
|
||||
#[serde(rename = "24h")]
|
||||
Day,
|
||||
/// Last 3 days (~432 blocks)
|
||||
#[serde(rename = "3d")]
|
||||
ThreeDays,
|
||||
/// Last week (~1008 blocks)
|
||||
#[serde(rename = "1w")]
|
||||
Week,
|
||||
/// Last month (~4320 blocks)
|
||||
#[serde(rename = "1m")]
|
||||
Month,
|
||||
/// Last 3 months (~12960 blocks)
|
||||
#[serde(rename = "3m")]
|
||||
ThreeMonths,
|
||||
/// Last 6 months (~25920 blocks)
|
||||
#[serde(rename = "6m")]
|
||||
SixMonths,
|
||||
/// Last year (~52560 blocks)
|
||||
#[serde(rename = "1y")]
|
||||
Year,
|
||||
/// Last 2 years (~105120 blocks)
|
||||
#[serde(rename = "2y")]
|
||||
TwoYears,
|
||||
/// Last 3 years (~157680 blocks)
|
||||
#[serde(rename = "3y")]
|
||||
ThreeYears,
|
||||
}
|
||||
|
||||
impl TimePeriod {
|
||||
/// Approximate number of blocks for this time period (10 min per block average)
|
||||
pub fn block_count(&self) -> usize {
|
||||
match self {
|
||||
TimePeriod::Day => 144,
|
||||
TimePeriod::ThreeDays => 432,
|
||||
TimePeriod::Week => 1008,
|
||||
TimePeriod::Month => 4320,
|
||||
TimePeriod::ThreeMonths => 12960,
|
||||
TimePeriod::SixMonths => 25920,
|
||||
TimePeriod::Year => 52560,
|
||||
TimePeriod::TwoYears => 105120,
|
||||
TimePeriod::ThreeYears => 157680,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse from URL path segment
|
||||
pub fn from_path(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"24h" => Some(TimePeriod::Day),
|
||||
"3d" => Some(TimePeriod::ThreeDays),
|
||||
"1w" => Some(TimePeriod::Week),
|
||||
"1m" => Some(TimePeriod::Month),
|
||||
"3m" => Some(TimePeriod::ThreeMonths),
|
||||
"6m" => Some(TimePeriod::SixMonths),
|
||||
"1y" => Some(TimePeriod::Year),
|
||||
"2y" => Some(TimePeriod::TwoYears),
|
||||
"3y" => Some(TimePeriod::ThreeYears),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::TimePeriod;
|
||||
|
||||
/// Path parameter for mining pool statistics time period
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct TimePeriodPath {
|
||||
/// Time period for statistics.
|
||||
/// Valid values: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y
|
||||
pub time_period: TimePeriod,
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use super::Date;
|
||||
/// Timestamp
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Deref,
|
||||
Clone,
|
||||
Copy,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
OPENSATS*.md
|
||||
DUMP.md
|
||||
*changes.md
|
||||
|
||||
Reference in New Issue
Block a user