mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -751,7 +751,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brk_mempool"
|
||||
name = "brk_monitor"
|
||||
version = "0.0.111"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
@@ -1241,6 +1241,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"vecdb",
|
||||
"zerocopy",
|
||||
@@ -2113,9 +2114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
checksum = "1dc8f7d2ded5f9209535e4b3fd4d39c002f30902ff5ce9f64e2c33d549576500"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
@@ -4410,12 +4411,12 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -57,7 +57,7 @@ brk_indexer = { version = "0.0.111", path = "crates/brk_indexer" }
|
||||
brk_interface = { version = "0.0.111", path = "crates/brk_interface" }
|
||||
brk_logger = { version = "0.0.111", path = "crates/brk_logger" }
|
||||
brk_mcp = { version = "0.0.111", path = "crates/brk_mcp" }
|
||||
brk_mempool = { version = "0.0.111", path = "crates/brk_mempool" }
|
||||
brk_monitor = { version = "0.0.111", path = "crates/brk_monitor" }
|
||||
brk_parser = { version = "0.0.111", path = "crates/brk_parser" }
|
||||
brk_server = { version = "0.0.111", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.0.111", path = "crates/brk_store" }
|
||||
|
||||
@@ -4,7 +4,7 @@ use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_fetcher::Fetcher;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_structs::{AddressBytes, OutputIndex, OutputType, pools};
|
||||
use brk_structs::{Address, AddressBytes, OutputIndex, OutputType, pools};
|
||||
use vecdb::{AnyIterableVec, Exit, VecIterator};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -68,7 +68,7 @@ fn main() -> Result<()> {
|
||||
let typeindex =
|
||||
outputindex_to_typeindex_iter.unwrap_get_inner(outputindex);
|
||||
|
||||
let address = match outputtype {
|
||||
match outputtype {
|
||||
OutputType::P2PK65 => Some(AddressBytes::from(
|
||||
p2pk65addressindex_to_p2pk65bytes_iter
|
||||
.unwrap_get_inner(typeindex.into()),
|
||||
@@ -102,10 +102,9 @@ fn main() -> Result<()> {
|
||||
.unwrap_get_inner(typeindex.into()),
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
address
|
||||
.and_then(|address| pools.find_from_address(&address.to_string()))
|
||||
}
|
||||
.map(|bytes| Address::try_from(bytes).unwrap())
|
||||
.and_then(|address| pools.find_from_address(&address))
|
||||
})
|
||||
.or_else(|| pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
.unwrap_or(unknown);
|
||||
|
||||
@@ -4,7 +4,7 @@ use allocative::Allocative;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_store::AnyStore;
|
||||
use brk_structs::{AddressBytes, Height, OutputIndex, OutputType, PoolId, Pools, pools};
|
||||
use brk_structs::{Address, AddressBytes, Height, OutputIndex, OutputType, PoolId, Pools, pools};
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
@@ -167,7 +167,7 @@ impl Vecs {
|
||||
outputindex_to_outputtype_iter.unwrap_get_inner(outputindex);
|
||||
let typeindex = outputindex_to_typeindex_iter.unwrap_get_inner(outputindex);
|
||||
|
||||
let address = match outputtype {
|
||||
match outputtype {
|
||||
OutputType::P2PK65 => Some(AddressBytes::from(
|
||||
p2pk65addressindex_to_p2pk65bytes_iter
|
||||
.unwrap_get_inner(typeindex.into()),
|
||||
@@ -200,10 +200,9 @@ impl Vecs {
|
||||
p2aaddressindex_to_p2abytes_iter.unwrap_get_inner(typeindex.into()),
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
address
|
||||
.and_then(|address| self.pools.find_from_address(&address.to_string()))
|
||||
}
|
||||
.map(|bytes| Address::try_from(bytes).unwrap())
|
||||
.and_then(|address| self.pools.find_from_address(&address))
|
||||
})
|
||||
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
.unwrap_or(unknown);
|
||||
|
||||
@@ -19,6 +19,7 @@ pub enum Error {
|
||||
SystemTimeError(time::SystemTimeError),
|
||||
BitcoinConsensusEncode(bitcoin::consensus::encode::Error),
|
||||
BitcoinBip34Error(bitcoin::block::Bip34Error),
|
||||
BitcoinFromScriptError(bitcoin::address::FromScriptError),
|
||||
SonicRS(sonic_rs::Error),
|
||||
ZeroCopyError,
|
||||
Vecs(vecdb::Error),
|
||||
@@ -51,6 +52,12 @@ impl From<bitcoin::consensus::encode::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::address::FromScriptError> for Error {
|
||||
fn from(value: bitcoin::address::FromScriptError) -> Self {
|
||||
Self::BitcoinFromScriptError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::SystemTimeError> for Error {
|
||||
fn from(value: time::SystemTimeError) -> Self {
|
||||
Self::SystemTimeError(value)
|
||||
@@ -128,6 +135,7 @@ impl fmt::Display for Error {
|
||||
match self {
|
||||
Error::BitcoinConsensusEncode(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinBip34Error(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinFromScriptError(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinRPC(error) => Display::fmt(&error, f),
|
||||
Error::Fjall(error) => Display::fmt(&error, f),
|
||||
Error::IO(error) => Display::fmt(&error, f),
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::{Address, Network, PublicKey, ScriptBuf};
|
||||
use bitcoin::{Network, PublicKey, ScriptBuf};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_structs::{
|
||||
AddressBytes, AddressBytesHash, AddressInfo, AddressPath, AnyAddressDataIndexEnum, Bitcoin,
|
||||
Address, AddressBytes, AddressBytesHash, AddressStats, AnyAddressDataIndexEnum, Bitcoin,
|
||||
OutputType,
|
||||
};
|
||||
use vecdb::{AnyIterableVec, VecIterator};
|
||||
|
||||
use crate::Interface;
|
||||
|
||||
pub fn get_address_info(
|
||||
AddressPath { address }: AddressPath,
|
||||
interface: &Interface,
|
||||
) -> Result<AddressInfo> {
|
||||
pub fn get_address(Address { address }: Address, interface: &Interface) -> Result<AddressStats> {
|
||||
let indexer = interface.indexer();
|
||||
let computer = interface.computer();
|
||||
let stores = &indexer.stores;
|
||||
|
||||
let script = if let Ok(address) = Address::from_str(&address) {
|
||||
let script = if let Ok(address) = bitcoin::Address::from_str(&address) {
|
||||
if !address.is_valid_for_network(Network::Bitcoin) {
|
||||
return Err(Error::InvalidNetwork);
|
||||
}
|
||||
@@ -109,16 +106,18 @@ pub fn get_address_info(
|
||||
|
||||
let balance = address_data.balance();
|
||||
|
||||
Ok(AddressInfo {
|
||||
address: address.to_string(),
|
||||
r#type: type_,
|
||||
type_index,
|
||||
utxo_count: address_data.utxo_count,
|
||||
total_sent: address_data.sent,
|
||||
total_received: address_data.received,
|
||||
balance,
|
||||
balance_usd: price.map(|p| p * Bitcoin::from(balance)),
|
||||
estimated_total_invested: price.map(|_| address_data.realized_cap),
|
||||
estimated_avg_entry_price: price.map(|_| address_data.realized_price()),
|
||||
})
|
||||
todo!();
|
||||
|
||||
// Ok(Address {
|
||||
// address: address.to_string(),
|
||||
// r#type: type_,
|
||||
// type_index,
|
||||
// utxo_count: address_data.utxo_count,
|
||||
// total_sent: address_data.sent,
|
||||
// total_received: address_data.received,
|
||||
// balance,
|
||||
// balance_usd: price.map(|p| p * Bitcoin::from(balance)),
|
||||
// estimated_total_invested: price.map(|_| address_data.realized_cap),
|
||||
// estimated_avg_entry_price: price.map(|_| address_data.realized_price()),
|
||||
// })
|
||||
}
|
||||
|
||||
@@ -7,15 +7,12 @@ use std::{
|
||||
use bitcoin::{Transaction, consensus::Decodable};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_parser::XORIndex;
|
||||
use brk_structs::{TransactionInfo, Txid, TxidPath, TxidPrefix};
|
||||
use brk_structs::{Tx, Txid, TxidPath, TxidPrefix};
|
||||
use vecdb::VecIterator;
|
||||
|
||||
use crate::Interface;
|
||||
|
||||
pub fn get_transaction_info(
|
||||
TxidPath { txid }: TxidPath,
|
||||
interface: &Interface,
|
||||
) -> Result<TransactionInfo> {
|
||||
pub fn get_transaction_info(TxidPath { txid }: TxidPath, interface: &Interface) -> Result<Tx> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
@@ -79,9 +76,11 @@ pub fn get_transaction_info(
|
||||
return Err(Error::Str("Failed decode the transaction"));
|
||||
};
|
||||
|
||||
Ok(TransactionInfo {
|
||||
txid,
|
||||
index,
|
||||
// tx
|
||||
})
|
||||
todo!();
|
||||
|
||||
// Ok(TxInfo {
|
||||
// txid,
|
||||
// index,
|
||||
// // tx
|
||||
// })
|
||||
}
|
||||
|
||||
@@ -7,26 +7,26 @@ use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_structs::{
|
||||
AddressInfo, AddressPath, Format, Height, Index, IndexInfo, MetricCount, MetricSearchQuery,
|
||||
TransactionInfo, TxidPath,
|
||||
Address, AddressStats, Format, Height, Index, IndexInfo, Limit, Metric, MetricCount, Tx,
|
||||
TxidPath,
|
||||
};
|
||||
use brk_traversable::TreeNode;
|
||||
use vecdb::{AnyCollectableVec, AnyStoredVec};
|
||||
|
||||
mod chain;
|
||||
mod deser;
|
||||
mod metrics;
|
||||
mod output;
|
||||
mod pagination;
|
||||
mod params;
|
||||
mod vecs;
|
||||
|
||||
pub use metrics::{Output, Value};
|
||||
pub use output::{Output, Value};
|
||||
pub use pagination::{PaginatedIndexParam, PaginatedMetrics, PaginationParam};
|
||||
pub use params::{Params, ParamsDeprec, ParamsOpt};
|
||||
use vecs::Vecs;
|
||||
|
||||
use crate::{
|
||||
chain::{get_address_info, get_transaction_info},
|
||||
chain::{get_address, get_transaction_info},
|
||||
vecs::{IndexToVec, MetricToVec},
|
||||
};
|
||||
|
||||
@@ -57,16 +57,16 @@ impl<'a> Interface<'a> {
|
||||
Height::from(self.indexer.vecs.height_to_blockhash.stamp())
|
||||
}
|
||||
|
||||
pub fn get_address_info(&self, address: AddressPath) -> Result<AddressInfo> {
|
||||
get_address_info(address, self)
|
||||
pub fn get_address(&self, address: Address) -> Result<AddressStats> {
|
||||
get_address(address, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction_info(&self, txid: TxidPath) -> Result<TransactionInfo> {
|
||||
pub fn get_transaction_info(&self, txid: TxidPath) -> Result<Tx> {
|
||||
get_transaction_info(txid, self)
|
||||
}
|
||||
|
||||
pub fn match_metric(&self, query: MetricSearchQuery) -> Vec<&str> {
|
||||
self.vecs.matches(query)
|
||||
pub fn match_metric(&self, metric: &Metric, limit: Limit) -> Vec<&str> {
|
||||
self.vecs.matches(metric, limit)
|
||||
}
|
||||
|
||||
pub fn search_metric_with_index(
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use brk_structs::{Format, Index};
|
||||
use brk_structs::{Format, Index, Metric, Metrics};
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
deser::{de_unquote_i64, de_unquote_usize},
|
||||
metrics::MaybeMetrics,
|
||||
};
|
||||
use crate::deser::{de_unquote_i64, de_unquote_usize};
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
pub struct Params {
|
||||
/// Requested metrics
|
||||
#[serde(alias = "m")]
|
||||
pub metrics: MaybeMetrics,
|
||||
pub metrics: Metrics,
|
||||
|
||||
/// Requested index
|
||||
#[serde(alias = "i")]
|
||||
pub index: Index,
|
||||
|
||||
@@ -30,11 +27,21 @@ impl Deref for Params {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<((Index, String), ParamsOpt)> for Params {
|
||||
fn from(((index, metric), rest): ((Index, String), ParamsOpt)) -> Self {
|
||||
impl From<(Index, Metric, ParamsOpt)> for Params {
|
||||
fn from((index, metric, rest): (Index, Metric, ParamsOpt)) -> Self {
|
||||
Self {
|
||||
index,
|
||||
metrics: MaybeMetrics::from(metric),
|
||||
metrics: Metrics::from(metric),
|
||||
rest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Index, Metrics, ParamsOpt)> for Params {
|
||||
fn from((index, metrics, rest): (Index, Metrics, ParamsOpt)) -> Self {
|
||||
Self {
|
||||
index,
|
||||
metrics,
|
||||
rest,
|
||||
}
|
||||
}
|
||||
@@ -105,7 +112,7 @@ pub struct ParamsDeprec {
|
||||
#[serde(alias = "i")]
|
||||
pub index: Index,
|
||||
#[serde(alias = "v")]
|
||||
pub ids: MaybeMetrics,
|
||||
pub ids: Metrics,
|
||||
#[serde(flatten)]
|
||||
pub rest: ParamsOpt,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use brk_computer::Computer;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_structs::{Index, IndexInfo, MetricSearchQuery};
|
||||
use brk_structs::{Index, IndexInfo, Limit, Metric};
|
||||
use brk_traversable::{Traversable, TreeNode};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use quickmatch::{QuickMatch, QuickMatchConfig};
|
||||
@@ -172,9 +172,9 @@ impl<'a> Vecs<'a> {
|
||||
self.catalog.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn matches(&self, query: MetricSearchQuery) -> Vec<&'_ str> {
|
||||
pub fn matches(&self, metric: &Metric, limit: Limit) -> Vec<&'_ str> {
|
||||
self.matcher()
|
||||
.matches_with(&query.q, &QuickMatchConfig::new().with_limit(query.limit))
|
||||
.matches_with(metric, &QuickMatchConfig::new().with_limit(*limit))
|
||||
}
|
||||
|
||||
fn matcher(&self) -> &QuickMatch<'_> {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# brk_mempool
|
||||
@@ -1,71 +0,0 @@
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
use bitcoin::{Transaction, consensus::encode};
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use log::error;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
const MAX_FETCHES_PER_CYCLE: usize = 10_000;
|
||||
|
||||
pub struct Mempool {
|
||||
rpc: &'static Client,
|
||||
txs: RwLock<FxHashMap<String, Transaction>>,
|
||||
}
|
||||
|
||||
impl Mempool {
|
||||
pub fn new(rpc: &'static Client) -> Self {
|
||||
Self {
|
||||
rpc,
|
||||
txs: RwLock::new(FxHashMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_txs(&self) -> RwLockReadGuard<'_, FxHashMap<String, Transaction>> {
|
||||
self.txs.read()
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
loop {
|
||||
if let Err(e) = self.update() {
|
||||
error!("Error updating mempool: {}", e);
|
||||
}
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let current_txids = self.rpc.get_raw_mempool()?;
|
||||
|
||||
let current_set: std::collections::HashSet<String> =
|
||||
current_txids.iter().map(|t| t.to_string()).collect();
|
||||
|
||||
// Fetch new transactions
|
||||
let mut new_txs = FxHashMap::default();
|
||||
let mut fetched = 0;
|
||||
|
||||
for txid in current_txids {
|
||||
if fetched >= MAX_FETCHES_PER_CYCLE {
|
||||
break;
|
||||
}
|
||||
|
||||
let txid_str = txid.to_string();
|
||||
if !self.txs.read().contains_key(&txid_str)
|
||||
&& let Ok(hex) = self.rpc.get_raw_transaction_hex(&txid, None)
|
||||
{
|
||||
let tx: Transaction = encode::deserialize_hex(&hex)?;
|
||||
|
||||
new_txs.insert(txid_str, tx);
|
||||
fetched += 1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut mempool = self.txs.write();
|
||||
mempool.retain(|txid, _| current_set.contains(txid));
|
||||
mempool.extend(new_txs);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "brk_mempool"
|
||||
description = "A Bitcoin mempool reader"
|
||||
name = "brk_monitor"
|
||||
description = "A Bitcoin mempool monitor with real-time synchronization"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -10,8 +10,8 @@ rust-version.workspace = true
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { version = "0.32.7", features = ["serde"] }
|
||||
bitcoincore-rpc = "0.19.0"
|
||||
bitcoin = { workspace = true }
|
||||
bitcoincore-rpc = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
30
crates/brk_monitor/README.md
Normal file
30
crates/brk_monitor/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# brk_monitor
|
||||
|
||||
A lightweight, thread-safe Rust library for maintaining a live, in-memory snapshot of the Bitcoin mempool.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Real-time synchronization**: Polls Bitcoin Core RPC every second to track mempool state
|
||||
- **Thread-safe access**: Uses `RwLock` for concurrent reads with minimal contention
|
||||
- **Efficient updates**: Only fetches new transactions, with configurable rate limiting (10,000 tx/cycle)
|
||||
- **Zero-copy reads**: Exposes mempool via read guards for lock-free iteration
|
||||
- **Optimized data structures**: Uses `FxHashMap` for fast lookups and minimal hashing overhead
|
||||
- **Automatic cleanup**: Removes confirmed/dropped transactions on each update
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **Minimal lock duration**: Lock held only during HashSet operations, never during I/O
|
||||
- **Memory efficient**: Stores only missing txids during fetch phase
|
||||
- **Simple API**: Just `new()`, `start()`, and `get_txs()`
|
||||
- **Production-ready**: Error handling with logging, graceful degradation
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Fee estimation and mempool analysis
|
||||
- Transaction monitoring and alerts
|
||||
- Block template prediction
|
||||
- Network research and statistics
|
||||
|
||||
## Description
|
||||
|
||||
A clean, performant way to keep Bitcoin's mempool state available in your Rust application without repeatedly querying RPC. Perfect for applications that need frequent mempool access with low latency.
|
||||
70
crates/brk_monitor/src/lib.rs
Normal file
70
crates/brk_monitor/src/lib.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
use bitcoin::{Transaction, Txid, consensus::encode};
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use log::error;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
const MAX_FETCHES_PER_CYCLE: usize = 10_000;
|
||||
|
||||
pub struct Mempool {
|
||||
rpc: &'static Client,
|
||||
txs: RwLock<FxHashMap<Txid, Transaction>>,
|
||||
}
|
||||
|
||||
impl Mempool {
|
||||
pub fn new(rpc: &'static Client) -> Self {
|
||||
Self {
|
||||
rpc,
|
||||
txs: RwLock::new(FxHashMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_txs(&self) -> RwLockReadGuard<'_, FxHashMap<Txid, Transaction>> {
|
||||
self.txs.read()
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
loop {
|
||||
if let Err(e) = self.update() {
|
||||
error!("Error updating mempool: {}", e);
|
||||
}
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let txids = self
|
||||
.rpc
|
||||
.get_raw_mempool()?
|
||||
.into_iter()
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let missing_txids = {
|
||||
let txs = self.txs.read();
|
||||
txids
|
||||
.iter()
|
||||
.filter(|txid| !txs.contains_key(*txid))
|
||||
.take(MAX_FETCHES_PER_CYCLE)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let new_txs = missing_txids
|
||||
.into_iter()
|
||||
.filter_map(|txid| {
|
||||
self.rpc
|
||||
.get_raw_transaction_hex(txid, None)
|
||||
.ok()
|
||||
.and_then(|hex| encode::deserialize_hex(&hex).ok())
|
||||
.map(|tx| (*txid, tx))
|
||||
})
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
|
||||
let mut txs = self.txs.write();
|
||||
txs.retain(|txid, _| txids.contains(txid));
|
||||
txs.extend(new_txs);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{path::Path, sync::Arc, thread, time::Duration};
|
||||
|
||||
use brk_mempool::Mempool;
|
||||
use brk_monitor::Mempool;
|
||||
|
||||
fn main() {
|
||||
// Connect to Bitcoin Core
|
||||
@@ -5,7 +5,7 @@ use axum::{
|
||||
response::{Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
use brk_structs::{AddressInfo, AddressPath};
|
||||
use brk_structs::{Address, AddressStats};
|
||||
|
||||
use crate::{
|
||||
VERSION,
|
||||
@@ -27,14 +27,14 @@ impl AddressRoutes for ApiRouter<AppState> {
|
||||
"/api/address/{address}",
|
||||
get_with(async |
|
||||
headers: HeaderMap,
|
||||
Path(address): Path<AddressPath>,
|
||||
Path(address): Path<Address>,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let etag = format!("{VERSION}-{}", state.get_height());
|
||||
if headers.has_etag(&etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
match state.get_address_info(address).with_status() {
|
||||
match state.get_address(address).with_status() {
|
||||
Ok(value) => Response::new_json(&value, &etag),
|
||||
Err((status, message)) => Response::new_json_with(status, &message, &etag)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ impl AddressRoutes for ApiRouter<AppState> {
|
||||
.addresses_tag()
|
||||
.summary("Address information")
|
||||
.description("Retrieve comprehensive information about a Bitcoin address including balance, transaction history, UTXOs, and estimated investment metrics. Supports all standard Bitcoin address types (P2PKH, P2SH, P2WPKH, P2WSH, P2TR, etc.).")
|
||||
.ok_response::<AddressInfo>()
|
||||
.ok_response::<AddressStats>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
|
||||
@@ -6,7 +6,7 @@ use axum::{
|
||||
routing::get,
|
||||
};
|
||||
use brk_interface::{PaginatedMetrics, PaginationParam, Params, ParamsDeprec, ParamsOpt};
|
||||
use brk_structs::{Index, IndexInfo, MetricCount, MetricPath, MetricSearchQuery};
|
||||
use brk_structs::{Index, IndexInfo, Limit, Metric, MetricCount, Metrics};
|
||||
use brk_traversable::TreeNode;
|
||||
|
||||
use crate::{
|
||||
@@ -114,18 +114,19 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/metrics/search",
|
||||
"/api/metrics/search/{metric}",
|
||||
get_with(
|
||||
async |
|
||||
headers: HeaderMap,
|
||||
State(state): State<AppState>,
|
||||
Query(query): Query<MetricSearchQuery>
|
||||
Path(metric): Path<Metric>,
|
||||
Query(limit): Query<Limit>
|
||||
| {
|
||||
let etag = VERSION;
|
||||
if headers.has_etag(etag) {
|
||||
return Response::new_not_modified();
|
||||
}
|
||||
Response::new_json(state.match_metric(query), etag)
|
||||
Response::new_json(state.match_metric(&metric, limit), etag)
|
||||
},
|
||||
|op| op
|
||||
.metrics_tag()
|
||||
@@ -141,7 +142,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
async |
|
||||
headers: HeaderMap,
|
||||
State(state): State<AppState>,
|
||||
Path(MetricPath { metric }): Path<MetricPath>
|
||||
Path(metric): Path<Metric>
|
||||
| {
|
||||
let etag = VERSION;
|
||||
if headers.has_etag(etag) {
|
||||
@@ -150,10 +151,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
if let Some(indexes) = state.metric_to_indexes(metric.clone()) {
|
||||
return Response::new_json(indexes, etag)
|
||||
}
|
||||
let value = if let Some(first) = state.match_metric(MetricSearchQuery {
|
||||
q: metric.clone(),
|
||||
limit: 1,
|
||||
}).first() {
|
||||
let value = if let Some(first) = state.match_metric(&metric, Limit::MIN).first() {
|
||||
format!("Could not find '{metric}', did you mean '{first}' ?")
|
||||
} else {
|
||||
format!("Could not find '{metric}'.")
|
||||
@@ -181,7 +179,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
async |uri: Uri,
|
||||
headers: HeaderMap,
|
||||
state: State<AppState>,
|
||||
Path((metric, index)): Path<(MetricPath, Index)>,
|
||||
Path((metric, index)): Path<(Metric, Index)>,
|
||||
Query(params_opt): Query<ParamsOpt>|
|
||||
-> Response {
|
||||
todo!();
|
||||
@@ -232,7 +230,8 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
};
|
||||
|
||||
let params = Params::from((
|
||||
(index, split.collect::<Vec<_>>().join(separator)),
|
||||
index,
|
||||
Metrics::from(split.collect::<Vec<_>>().join(separator)),
|
||||
params_opt,
|
||||
));
|
||||
data::handler(uri, headers, Query(params), state).await
|
||||
|
||||
@@ -5,7 +5,7 @@ use axum::{
|
||||
response::{Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
use brk_structs::{TransactionInfo, TxidPath};
|
||||
use brk_structs::{Tx, TxidPath};
|
||||
|
||||
use crate::{
|
||||
VERSION,
|
||||
@@ -46,7 +46,7 @@ impl TxRoutes for ApiRouter<AppState> {
|
||||
.description(
|
||||
"Retrieve complete transaction data by transaction ID (txid). Returns the full transaction details including inputs, outputs, and metadata. The transaction data is read directly from the blockchain data files.",
|
||||
)
|
||||
.ok_response::<TransactionInfo>()
|
||||
.ok_response::<Tx>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
|
||||
@@ -23,6 +23,7 @@ rapidhash = "4.1.0"
|
||||
ryu = "1.0.20"
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_bytes = { workspace = true }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
112
crates/brk_structs/src/address.rs
Normal file
112
crates/brk_structs/src/address.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use std::fmt;
|
||||
|
||||
use bitcoin::{Network, ScriptBuf, opcodes, script::Builder};
|
||||
use brk_error::Error;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::AddressBytes;
|
||||
|
||||
use super::OutputType;
|
||||
|
||||
#[derive(Debug, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Address {
|
||||
/// Bitcoin address string
|
||||
#[schemars(example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")]
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.address)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Address {
|
||||
fn from(address: String) -> Self {
|
||||
Self { address }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptBuf> for Address {
|
||||
type Error = Error;
|
||||
fn try_from(script: ScriptBuf) -> Result<Self, Self::Error> {
|
||||
Ok(Self::from(bitcoin::Address::from_script(
|
||||
&script,
|
||||
Network::Bitcoin,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::Address> for Address {
|
||||
fn from(address: bitcoin::Address) -> Self {
|
||||
Self {
|
||||
address: address.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(&ScriptBuf, OutputType)> for Address {
|
||||
type Error = Error;
|
||||
fn try_from(tuple: (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
|
||||
Self::try_from(AddressBytes::try_from(tuple)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBytes> for Address {
|
||||
type Error = Error;
|
||||
fn try_from(bytes: AddressBytes) -> Result<Self, Self::Error> {
|
||||
let address = match bytes {
|
||||
AddressBytes::P2PK65(b) => Self::from(bytes_to_hex(&**b)),
|
||||
AddressBytes::P2PK33(b) => Self::from(bytes_to_hex(&**b)),
|
||||
AddressBytes::P2PKH(b) => Self::try_from(
|
||||
Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(**b)
|
||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||
.push_opcode(opcodes::all::OP_CHECKSIG)
|
||||
.into_script(),
|
||||
)?,
|
||||
AddressBytes::P2SH(b) => Self::try_from(
|
||||
Builder::new()
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(**b)
|
||||
.push_opcode(opcodes::all::OP_EQUAL)
|
||||
.into_script(),
|
||||
)?,
|
||||
AddressBytes::P2WPKH(b) => {
|
||||
Self::try_from(Builder::new().push_int(0).push_slice(**b).into_script())?
|
||||
}
|
||||
AddressBytes::P2WSH(b) => {
|
||||
Self::try_from(Builder::new().push_int(0).push_slice(**b).into_script())?
|
||||
}
|
||||
AddressBytes::P2TR(b) => {
|
||||
Self::try_from(Builder::new().push_int(1).push_slice(**b).into_script())?
|
||||
}
|
||||
AddressBytes::P2A(b) => {
|
||||
Self::try_from(Builder::new().push_int(1).push_slice(**b).into_script())?
|
||||
}
|
||||
};
|
||||
Ok(address)
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_to_hex(bytes: &[u8]) -> String {
|
||||
let mut hex_string = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
use std::fmt::Write;
|
||||
write!(&mut hex_string, "{:02x}", byte).unwrap();
|
||||
}
|
||||
hex_string
|
||||
}
|
||||
|
||||
impl Serialize for Address {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.address)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
use std::fmt;
|
||||
|
||||
use bitcoin::{
|
||||
Address, Network, ScriptBuf,
|
||||
hex::{Case, DisplayHex},
|
||||
opcodes,
|
||||
script::Builder,
|
||||
};
|
||||
use bitcoin::ScriptBuf;
|
||||
use brk_error::Error;
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use serde::{Serialize, Serializer};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::OutputType;
|
||||
use super::{
|
||||
OutputType, P2ABytes, P2PK33Bytes, P2PK65Bytes, P2PKHBytes, P2SHBytes, P2TRBytes, P2WPKHBytes,
|
||||
P2WSHBytes,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum AddressBytes {
|
||||
@@ -25,21 +18,6 @@ pub enum AddressBytes {
|
||||
P2A(P2ABytes),
|
||||
}
|
||||
|
||||
impl fmt::Display for AddressBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&match self {
|
||||
AddressBytes::P2PK65(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2PK33(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2PKH(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2SH(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2WPKH(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2WSH(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2TR(bytes) => bytes.to_string(),
|
||||
AddressBytes::P2A(bytes) => bytes.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBytes {
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
match self {
|
||||
@@ -70,7 +48,7 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
return Err(Error::WrongLength);
|
||||
}
|
||||
};
|
||||
Ok(Self::P2PK65(P2PK65Bytes(U8x65::from(bytes))))
|
||||
Ok(Self::P2PK65(P2PK65Bytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2PK33 => {
|
||||
let bytes = script.as_bytes();
|
||||
@@ -81,31 +59,31 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
return Err(Error::WrongLength);
|
||||
}
|
||||
};
|
||||
Ok(Self::P2PK33(P2PK33Bytes(U8x33::from(bytes))))
|
||||
Ok(Self::P2PK33(P2PK33Bytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2PKH => {
|
||||
let bytes = &script.as_bytes()[3..23];
|
||||
Ok(Self::P2PKH(P2PKHBytes(U8x20::from(bytes))))
|
||||
Ok(Self::P2PKH(P2PKHBytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2SH => {
|
||||
let bytes = &script.as_bytes()[2..22];
|
||||
Ok(Self::P2SH(P2SHBytes(U8x20::from(bytes))))
|
||||
Ok(Self::P2SH(P2SHBytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2WPKH => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2WPKH(P2WPKHBytes(U8x20::from(bytes))))
|
||||
Ok(Self::P2WPKH(P2WPKHBytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2WSH => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2WSH(P2WSHBytes(U8x32::from(bytes))))
|
||||
Ok(Self::P2WSH(P2WSHBytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2TR => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2TR(P2TRBytes(U8x32::from(bytes))))
|
||||
Ok(Self::P2TR(P2TRBytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2A => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2A(P2ABytes(U8x2::from(bytes))))
|
||||
Ok(Self::P2A(P2ABytes::from(bytes)))
|
||||
}
|
||||
OutputType::P2MS => Err(Error::WrongAddressType),
|
||||
OutputType::Unknown => Err(Error::WrongAddressType),
|
||||
@@ -116,348 +94,50 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2PK65Bytes(U8x65);
|
||||
|
||||
impl fmt::Display for P2PK65Bytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_hex_string(Case::Lower))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2PK65Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK65Bytes> for AddressBytes {
|
||||
fn from(value: P2PK65Bytes) -> Self {
|
||||
Self::P2PK65(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2PK33Bytes(U8x33);
|
||||
|
||||
impl fmt::Display for P2PK33Bytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_hex_string(Case::Lower))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2PK33Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33Bytes> for AddressBytes {
|
||||
fn from(value: P2PK33Bytes) -> Self {
|
||||
Self::P2PK33(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2PKHBytes(U8x20);
|
||||
|
||||
impl fmt::Display for P2PKHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(*self.0)
|
||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||
.push_opcode(opcodes::all::OP_CHECKSIG)
|
||||
.into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2PKHBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHBytes> for AddressBytes {
|
||||
fn from(value: P2PKHBytes) -> Self {
|
||||
Self::P2PKH(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2SHBytes(U8x20);
|
||||
|
||||
impl fmt::Display for P2SHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new()
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(*self.0)
|
||||
.push_opcode(opcodes::all::OP_EQUAL)
|
||||
.into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2SHBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHBytes> for AddressBytes {
|
||||
fn from(value: P2SHBytes) -> Self {
|
||||
Self::P2SH(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2WPKHBytes(U8x20);
|
||||
|
||||
impl fmt::Display for P2WPKHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new().push_int(0).push_slice(*self.0).into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2WPKHBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHBytes> for AddressBytes {
|
||||
fn from(value: P2WPKHBytes) -> Self {
|
||||
Self::P2WPKH(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2WSHBytes(U8x32);
|
||||
|
||||
impl fmt::Display for P2WSHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new().push_int(0).push_slice(*self.0).into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2WSHBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHBytes> for AddressBytes {
|
||||
fn from(value: P2WSHBytes) -> Self {
|
||||
Self::P2WSH(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2TRBytes(U8x32);
|
||||
|
||||
impl fmt::Display for P2TRBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new().push_int(1).push_slice(*self.0).into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2TRBytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRBytes> for AddressBytes {
|
||||
fn from(value: P2TRBytes) -> Self {
|
||||
Self::P2TR(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
pub struct P2ABytes(U8x2);
|
||||
|
||||
impl fmt::Display for P2ABytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let script = Builder::new().push_int(1).push_slice(*self.0).into_script();
|
||||
let address = Address::from_script(&script, Network::Bitcoin).unwrap();
|
||||
write!(f, "{address}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for P2ABytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2ABytes> for AddressBytes {
|
||||
fn from(value: P2ABytes) -> Self {
|
||||
Self::P2A(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x2([u8; 2]);
|
||||
impl From<&[u8]> for U8x2 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 2];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x20([u8; 20]);
|
||||
impl From<&[u8]> for U8x20 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 20];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x32([u8; 32]);
|
||||
impl From<&[u8]> for U8x32 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 32];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]);
|
||||
impl From<&[u8]> for U8x33 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 33];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x64(#[serde(with = "serde_bytes")] [u8; 64]);
|
||||
impl From<&[u8]> for U8x64 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 64];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]);
|
||||
impl From<&[u8]> for U8x65 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 65];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
33
crates/brk_structs/src/addresschainstats.rs
Normal file
33
crates/brk_structs/src/addresschainstats.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::{Sats, TypeIndex};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Address statistics on the blockchain (confirmed transactions only)
|
||||
///
|
||||
/// Based on mempool.space's format with type_index extension.
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct AddressChainStats {
|
||||
/// Total number of transaction outputs that funded this address
|
||||
#[schemars(example = 5)]
|
||||
pub funded_txo_count: u64,
|
||||
|
||||
/// Total amount in satoshis received by this address across all funded outputs
|
||||
#[schemars(example = Sats::new(15007599040))]
|
||||
pub funded_txo_sum: Sats,
|
||||
|
||||
/// Total number of transaction outputs spent from this address
|
||||
#[schemars(example = 5)]
|
||||
pub spent_txo_count: u64,
|
||||
|
||||
/// Total amount in satoshis spent from this address
|
||||
#[schemars(example = Sats::new(15007599040))]
|
||||
pub spent_txo_sum: Sats,
|
||||
|
||||
/// Total number of confirmed transactions involving this address
|
||||
#[schemars(example = 10)]
|
||||
pub tx_count: Option<u64>,
|
||||
|
||||
/// Index of this address within its type on the blockchain
|
||||
#[schemars(example = TypeIndex::new(0))]
|
||||
pub type_index: TypeIndex,
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{Dollars, OutputType, Sats, TypeIndex};
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Address information
|
||||
pub struct AddressInfo {
|
||||
/// Bitcoin address string
|
||||
#[schemars(example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")]
|
||||
pub address: String,
|
||||
|
||||
#[schemars(example = OutputType::P2PK65)]
|
||||
pub r#type: OutputType,
|
||||
|
||||
#[schemars(example = TypeIndex::new(0))]
|
||||
pub type_index: TypeIndex,
|
||||
|
||||
/// Total satoshis ever sent from this address
|
||||
#[schemars(example = Sats::new(0))]
|
||||
pub total_sent: Sats,
|
||||
|
||||
/// Total satoshis ever received by this address
|
||||
#[schemars(example = Sats::new(5001008380))]
|
||||
pub total_received: Sats,
|
||||
|
||||
/// Number of unspent transaction outputs (UTXOs)
|
||||
#[schemars(example = 10)]
|
||||
pub utxo_count: u32,
|
||||
|
||||
/// Current spendable balance in satoshis (total_received - total_sent)
|
||||
#[schemars(example = Sats::new(5001008380))]
|
||||
pub balance: Sats,
|
||||
|
||||
/// Current balance value in USD at current market price
|
||||
#[schemars(example = Some(Dollars::mint(6_157_891.64)))]
|
||||
pub balance_usd: Option<Dollars>,
|
||||
|
||||
/// Estimated total USD value at time of deposit for coins currently in this address (not including coins that were later sent out). Not suitable for tax calculations
|
||||
#[schemars(example = Some(Dollars::mint(6.2)))]
|
||||
pub estimated_total_invested: Option<Dollars>,
|
||||
|
||||
/// Estimated average BTC price at time of deposit for coins currently in this address (USD). Not suitable for tax calculations
|
||||
#[schemars(example = Some(Dollars::mint(0.12)))]
|
||||
pub estimated_avg_entry_price: Option<Dollars>,
|
||||
//
|
||||
// Transaction count?
|
||||
// First/last activity timestamps?
|
||||
// Realized/unrealized gains?
|
||||
// Current value (balance × current price)?
|
||||
// "address": address,
|
||||
// "type": output_type,
|
||||
// "index": addri,
|
||||
// "chain_stats": {
|
||||
// "funded_txo_count": null,
|
||||
// "funded_txo_sum": addr_data.received,
|
||||
// "spent_txo_count": null,
|
||||
// "spent_txo_sum": addr_data.sent,
|
||||
// "utxo_count": addr_data.utxos,
|
||||
// "balance": amount,
|
||||
// "balance_usd": price.map_or(Value::new(), |p| {
|
||||
// Value::from(Number::from_f64(*(p * Bitcoin::from(amount))).unwrap())
|
||||
// }),
|
||||
// "realized_value": addr_data.realized_cap,
|
||||
// "tx_count": null,
|
||||
// "avg_cost_basis": addr_data.realized_price()
|
||||
// },
|
||||
// "mempool_stats": null
|
||||
}
|
||||
31
crates/brk_structs/src/addressmempoolstats.rs
Normal file
31
crates/brk_structs/src/addressmempoolstats.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::Sats;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// Address statistics in the mempool (unconfirmed transactions only)
|
||||
///
|
||||
/// Based on mempool.space's format.
|
||||
///
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
pub struct AddressMempoolStats {
|
||||
/// Number of unconfirmed transaction outputs funding this address
|
||||
#[schemars(example = 0)]
|
||||
pub funded_txo_count: u32,
|
||||
|
||||
/// Total amount in satoshis being received in unconfirmed transactions
|
||||
#[schemars(example = Sats::new(0))]
|
||||
pub funded_txo_sum: Sats,
|
||||
|
||||
/// Number of unconfirmed transaction inputs spending from this address
|
||||
#[schemars(example = 0)]
|
||||
pub spent_txo_count: u32,
|
||||
|
||||
/// Total amount in satoshis being spent in unconfirmed transactions
|
||||
#[schemars(example = Sats::new(0))]
|
||||
pub spent_txo_sum: Sats,
|
||||
|
||||
/// Number of unconfirmed transactions involving this address
|
||||
#[schemars(example = 0)]
|
||||
pub tx_count: u32,
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct AddressPath {
|
||||
/// Bitcoin address string
|
||||
#[schemars(example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")]
|
||||
pub address: String,
|
||||
}
|
||||
19
crates/brk_structs/src/addressstats.rs
Normal file
19
crates/brk_structs/src/addressstats.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::{AddressChainStats, AddressMempoolStats};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Address information compatible with mempool.space API format
|
||||
pub struct AddressStats {
|
||||
/// Bitcoin address string
|
||||
#[schemars(
|
||||
example = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
|
||||
)]
|
||||
pub address: String,
|
||||
|
||||
/// Statistics for confirmed transactions on the blockchain
|
||||
pub chain_stats: AddressChainStats,
|
||||
|
||||
/// Statistics for unconfirmed transactions in the mempool
|
||||
pub mempool_stats: AddressMempoolStats,
|
||||
}
|
||||
@@ -3,12 +3,16 @@ use std::{fmt, mem};
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::Height;
|
||||
|
||||
#[derive(Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes)]
|
||||
/// Block hash
|
||||
#[derive(
|
||||
Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, JsonSchema,
|
||||
)]
|
||||
pub struct BlockHash([u8; 32]);
|
||||
|
||||
impl From<bitcoin::BlockHash> for BlockHash {
|
||||
|
||||
113
crates/brk_structs/src/bytes.rs
Normal file
113
crates/brk_structs/src/bytes.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x2([u8; 2]);
|
||||
impl From<&[u8]> for U8x2 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 2];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x20([u8; 20]);
|
||||
impl From<&[u8]> for U8x20 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 20];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x32([u8; 32]);
|
||||
impl From<&[u8]> for U8x32 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 32];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]);
|
||||
impl From<&[u8]> for U8x33 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 33];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
)]
|
||||
pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]);
|
||||
impl From<&[u8]> for U8x65 {
|
||||
fn from(slice: &[u8]) -> Self {
|
||||
let mut arr = [0; 65];
|
||||
arr.copy_from_slice(slice);
|
||||
Self(arr)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use allocative::Allocative;
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use byteview::ByteView;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, PrintableIndex, Stamp, StoredCompressed};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
@@ -15,6 +16,7 @@ use crate::{BLOCKS_PER_DIFF_EPOCHS, BLOCKS_PER_HALVING, copy_first_4bytes};
|
||||
|
||||
use super::StoredU64;
|
||||
|
||||
/// Block height
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
@@ -33,6 +35,7 @@ use super::StoredU64;
|
||||
KnownLayout,
|
||||
StoredCompressed,
|
||||
Allocative,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct Height(u32);
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ use super::{
|
||||
SemesterIndex, TxIndex, UnknownOutputIndex, WeekIndex, YearIndex,
|
||||
};
|
||||
|
||||
/// Aggregation dimension for querying Bitcoin blockchain data
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[schemars(example = Index::DateIndex)]
|
||||
/// Aggregation dimension for querying Bitcoin blockchain data
|
||||
pub enum Index {
|
||||
/// Date/day index
|
||||
DateIndex,
|
||||
|
||||
@@ -4,10 +4,12 @@ pub use vecdb::{CheckedSub, Exit, PrintableIndex, Version};
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
|
||||
mod address;
|
||||
mod addressbytes;
|
||||
mod addressbyteshash;
|
||||
mod addressinfo;
|
||||
mod addresspath;
|
||||
mod addresschainstats;
|
||||
mod addressmempoolstats;
|
||||
mod addressstats;
|
||||
mod anyaddressindex;
|
||||
mod bitcoin;
|
||||
mod blkmetadata;
|
||||
@@ -15,6 +17,7 @@ mod blkposition;
|
||||
mod block;
|
||||
mod blockhash;
|
||||
mod blockhashprefix;
|
||||
mod bytes;
|
||||
mod cents;
|
||||
mod date;
|
||||
mod dateindex;
|
||||
@@ -32,25 +35,34 @@ mod height;
|
||||
mod index;
|
||||
mod indexinfo;
|
||||
mod inputindex;
|
||||
mod limit;
|
||||
mod loadedaddressdata;
|
||||
mod loadedaddressindex;
|
||||
mod metric;
|
||||
mod metriccount;
|
||||
mod metricpath;
|
||||
mod metricsearchquery;
|
||||
mod metrics;
|
||||
mod monthindex;
|
||||
mod ohlc;
|
||||
mod opreturnindex;
|
||||
mod outputindex;
|
||||
mod outputtype;
|
||||
mod p2aaddressindex;
|
||||
mod p2abytes;
|
||||
mod p2msoutputindex;
|
||||
mod p2pk33addressindex;
|
||||
mod p2pk33bytes;
|
||||
mod p2pk65addressindex;
|
||||
mod p2pk65bytes;
|
||||
mod p2pkhaddressindex;
|
||||
mod p2pkhbytes;
|
||||
mod p2shaddressindex;
|
||||
mod p2shbytes;
|
||||
mod p2traddressindex;
|
||||
mod p2trbytes;
|
||||
mod p2wpkhaddressindex;
|
||||
mod p2wpkhbytes;
|
||||
mod p2wshaddressindex;
|
||||
mod p2wshbytes;
|
||||
mod pool;
|
||||
mod poolid;
|
||||
mod pools;
|
||||
@@ -69,11 +81,15 @@ mod stored_u64;
|
||||
mod stored_u8;
|
||||
mod timestamp;
|
||||
mod treenode;
|
||||
mod tx;
|
||||
mod txid;
|
||||
mod txidpath;
|
||||
mod txidprefix;
|
||||
mod txindex;
|
||||
mod txinfo;
|
||||
mod txinput;
|
||||
mod txoutput;
|
||||
mod txprevout;
|
||||
mod txstatus;
|
||||
mod txversion;
|
||||
mod typeindex;
|
||||
mod typeindex_with_outputindex;
|
||||
@@ -85,10 +101,12 @@ mod weekindex;
|
||||
mod weight;
|
||||
mod yearindex;
|
||||
|
||||
pub use address::*;
|
||||
pub use addressbytes::*;
|
||||
pub use addressbyteshash::*;
|
||||
pub use addressinfo::*;
|
||||
pub use addresspath::*;
|
||||
pub use addresschainstats::*;
|
||||
pub use addressmempoolstats::*;
|
||||
pub use addressstats::*;
|
||||
pub use anyaddressindex::*;
|
||||
pub use bitcoin::*;
|
||||
pub use blkmetadata::*;
|
||||
@@ -96,6 +114,7 @@ pub use blkposition::*;
|
||||
pub use block::*;
|
||||
pub use blockhash::*;
|
||||
pub use blockhashprefix::*;
|
||||
pub use bytes::*;
|
||||
pub use cents::*;
|
||||
pub use date::*;
|
||||
pub use dateindex::*;
|
||||
@@ -113,25 +132,34 @@ pub use height::*;
|
||||
pub use index::*;
|
||||
pub use indexinfo::*;
|
||||
pub use inputindex::*;
|
||||
pub use limit::*;
|
||||
pub use loadedaddressdata::*;
|
||||
pub use loadedaddressindex::*;
|
||||
pub use metric::*;
|
||||
pub use metriccount::*;
|
||||
pub use metricpath::*;
|
||||
pub use metricsearchquery::*;
|
||||
pub use metrics::*;
|
||||
pub use monthindex::*;
|
||||
pub use ohlc::*;
|
||||
pub use opreturnindex::*;
|
||||
pub use outputindex::*;
|
||||
pub use outputtype::*;
|
||||
pub use p2aaddressindex::*;
|
||||
pub use p2abytes::*;
|
||||
pub use p2msoutputindex::*;
|
||||
pub use p2pk33addressindex::*;
|
||||
pub use p2pk33bytes::*;
|
||||
pub use p2pk65addressindex::*;
|
||||
pub use p2pk65bytes::*;
|
||||
pub use p2pkhaddressindex::*;
|
||||
pub use p2pkhbytes::*;
|
||||
pub use p2shaddressindex::*;
|
||||
pub use p2shbytes::*;
|
||||
pub use p2traddressindex::*;
|
||||
pub use p2trbytes::*;
|
||||
pub use p2wpkhaddressindex::*;
|
||||
pub use p2wpkhbytes::*;
|
||||
pub use p2wshaddressindex::*;
|
||||
pub use p2wshbytes::*;
|
||||
pub use pool::*;
|
||||
pub use poolid::*;
|
||||
pub use pools::*;
|
||||
@@ -150,11 +178,15 @@ pub use stored_u32::*;
|
||||
pub use stored_u64::*;
|
||||
pub use timestamp::*;
|
||||
pub use treenode::*;
|
||||
pub use tx::*;
|
||||
pub use txid::*;
|
||||
pub use txidpath::*;
|
||||
pub use txidprefix::*;
|
||||
pub use txindex::*;
|
||||
pub use txinfo::*;
|
||||
pub use txinput::*;
|
||||
pub use txoutput::*;
|
||||
pub use txprevout::*;
|
||||
pub use txstatus::*;
|
||||
pub use txversion::*;
|
||||
pub use typeindex::*;
|
||||
pub use typeindex_with_outputindex::*;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
/// Search query parameters for finding metrics by name
|
||||
pub struct MetricSearchQuery {
|
||||
/// Search query string. Supports fuzzy matching, partial matches, and typos.
|
||||
#[schemars(example = &"price", example = &"low", example = &"sth", example = &"realized", example = &"pric")]
|
||||
pub q: String,
|
||||
|
||||
#[derive(Debug, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Limit {
|
||||
/// Maximum number of results to return. Defaults to 100 if not specified.
|
||||
#[serde(default = "default_search_limit")]
|
||||
#[schemars(
|
||||
@@ -18,7 +14,11 @@ pub struct MetricSearchQuery {
|
||||
example = "10000",
|
||||
example = "100000"
|
||||
)]
|
||||
pub limit: usize,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl Limit {
|
||||
pub const MIN: Self = Self { limit: 1 };
|
||||
}
|
||||
|
||||
fn default_search_limit() -> usize {
|
||||
32
crates/brk_structs/src/metric.rs
Normal file
32
crates/brk_structs/src/metric.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Metric {
|
||||
/// Metric name
|
||||
#[schemars(example = &"price_close", example = &"market_cap", example = &"realized_price")]
|
||||
metric: String,
|
||||
}
|
||||
|
||||
impl From<String> for Metric {
|
||||
fn from(metric: String) -> Self {
|
||||
Self { metric }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Metric {
|
||||
fn from(metric: &str) -> Self {
|
||||
Self {
|
||||
metric: metric.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Metric {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.metric)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct MetricPath {
|
||||
/// Metric name
|
||||
#[schemars(example = &"price_close", example = &"market_cap", example = &"realized_price")]
|
||||
pub metric: String,
|
||||
}
|
||||
@@ -4,34 +4,43 @@ use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
mod output;
|
||||
|
||||
pub use output::*;
|
||||
use super::Metric;
|
||||
|
||||
#[derive(Debug, Deref, JsonSchema)]
|
||||
pub struct MaybeMetrics(Vec<String>);
|
||||
pub struct Metrics {
|
||||
/// A list of metrics
|
||||
metrics: Vec<Metric>,
|
||||
}
|
||||
|
||||
const MAX_VECS: usize = 32;
|
||||
const MAX_STRING_SIZE: usize = 64 * MAX_VECS;
|
||||
|
||||
impl From<String> for MaybeMetrics {
|
||||
impl From<Metric> for Metrics {
|
||||
fn from(metric: Metric) -> Self {
|
||||
Self {
|
||||
metrics: vec![metric],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Metrics {
|
||||
fn from(value: String) -> Self {
|
||||
Self(vec![value.replace("-", "_").to_lowercase()])
|
||||
Self::from(Metric::from(value.replace("-", "_").to_lowercase()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<&'a str>> for MaybeMetrics {
|
||||
impl<'a> From<Vec<&'a str>> for Metrics {
|
||||
fn from(value: Vec<&'a str>) -> Self {
|
||||
Self(
|
||||
value
|
||||
Self {
|
||||
metrics: value
|
||||
.iter()
|
||||
.map(|s| s.replace("-", "_").to_lowercase())
|
||||
.map(|s| Metric::from(s.replace("-", "_").to_lowercase()))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for MaybeMetrics {
|
||||
impl<'de> Deserialize<'de> for Metrics {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
@@ -40,17 +49,23 @@ impl<'de> Deserialize<'de> for MaybeMetrics {
|
||||
|
||||
if let Some(str) = value.as_str() {
|
||||
if str.len() <= MAX_STRING_SIZE {
|
||||
Ok(MaybeMetrics(sanitize_metrics(
|
||||
str.split(",").map(|s| s.to_string()),
|
||||
)))
|
||||
Ok(Self {
|
||||
metrics: sanitize(str.split(",").map(|s| s.to_string()))
|
||||
.into_iter()
|
||||
.map(Metric::from)
|
||||
.collect(),
|
||||
})
|
||||
} else {
|
||||
Err(serde::de::Error::custom("Given parameter is too long"))
|
||||
}
|
||||
} else if let Some(vec) = value.as_array() {
|
||||
if vec.len() <= MAX_VECS {
|
||||
Ok(MaybeMetrics(sanitize_metrics(
|
||||
vec.iter().map(|s| s.as_str().unwrap().to_string()),
|
||||
)))
|
||||
Ok(Self {
|
||||
metrics: sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
|
||||
.into_iter()
|
||||
.map(Metric::from)
|
||||
.collect(),
|
||||
})
|
||||
} else {
|
||||
Err(serde::de::Error::custom("Given parameter is too long"))
|
||||
}
|
||||
@@ -60,22 +75,27 @@ impl<'de> Deserialize<'de> for MaybeMetrics {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MaybeMetrics {
|
||||
impl fmt::Display for Metrics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = self.0.join(",");
|
||||
let s = self
|
||||
.metrics
|
||||
.iter()
|
||||
.map(|m| m.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_metrics(raw_ids: impl Iterator<Item = String>) -> Vec<String> {
|
||||
let mut results = Vec::new();
|
||||
raw_ids.for_each(|s| {
|
||||
fn sanitize(dirty: impl Iterator<Item = String>) -> Vec<String> {
|
||||
let mut clean = Vec::new();
|
||||
dirty.for_each(|s| {
|
||||
let mut current = String::new();
|
||||
for c in s.to_lowercase().chars() {
|
||||
match c {
|
||||
' ' | ',' | '+' => {
|
||||
if !current.is_empty() {
|
||||
results.push(std::mem::take(&mut current));
|
||||
clean.push(std::mem::take(&mut current));
|
||||
}
|
||||
}
|
||||
'-' => current.push('_'),
|
||||
@@ -84,8 +104,8 @@ fn sanitize_metrics(raw_ids: impl Iterator<Item = String>) -> Vec<String> {
|
||||
}
|
||||
}
|
||||
if !current.is_empty() {
|
||||
results.push(current);
|
||||
clean.push(current);
|
||||
}
|
||||
});
|
||||
results
|
||||
clean
|
||||
}
|
||||
30
crates/brk_structs/src/p2abytes.rs
Normal file
30
crates/brk_structs/src/p2abytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x2;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2ABytes(U8x2);
|
||||
|
||||
impl From<&[u8]> for P2ABytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x2::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x2> for P2ABytes {
|
||||
fn from(value: U8x2) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2ABytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2pk33bytes.rs
Normal file
30
crates/brk_structs/src/p2pk33bytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x33;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2PK33Bytes(U8x33);
|
||||
|
||||
impl From<&[u8]> for P2PK33Bytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x33::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x33> for P2PK33Bytes {
|
||||
fn from(value: U8x33) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2PK33Bytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2pk65bytes.rs
Normal file
30
crates/brk_structs/src/p2pk65bytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x65;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2PK65Bytes(U8x65);
|
||||
|
||||
impl From<&[u8]> for P2PK65Bytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x65::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x65> for P2PK65Bytes {
|
||||
fn from(value: U8x65) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2PK65Bytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2pkhbytes.rs
Normal file
30
crates/brk_structs/src/p2pkhbytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2PKHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2PKHBytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x20::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x20> for P2PKHBytes {
|
||||
fn from(value: U8x20) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2PKHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2shbytes.rs
Normal file
30
crates/brk_structs/src/p2shbytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2SHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2SHBytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x20::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x20> for P2SHBytes {
|
||||
fn from(value: U8x20) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2SHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2trbytes.rs
Normal file
30
crates/brk_structs/src/p2trbytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2TRBytes(U8x32);
|
||||
|
||||
impl From<&[u8]> for P2TRBytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x32::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x32> for P2TRBytes {
|
||||
fn from(value: U8x32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2TRBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2wpkhbytes.rs
Normal file
30
crates/brk_structs/src/p2wpkhbytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2WPKHBytes(U8x20);
|
||||
|
||||
impl From<&[u8]> for P2WPKHBytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x20::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x20> for P2WPKHBytes {
|
||||
fn from(value: U8x20) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2WPKHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
30
crates/brk_structs/src/p2wshbytes.rs
Normal file
30
crates/brk_structs/src/p2wshbytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::fmt;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
)]
|
||||
pub struct P2WSHBytes(U8x32);
|
||||
|
||||
impl From<&[u8]> for P2WSHBytes {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(U8x32::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<U8x32> for P2WSHBytes {
|
||||
fn from(value: U8x32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for P2WSHBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,21 @@
|
||||
use bitcoin::{absolute::LockTime, locktime::absolute::LOCK_TIME_THRESHOLD};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::StoredCompressed;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
/// Transaction locktime
|
||||
#[derive(
|
||||
Debug, Immutable, Clone, Copy, IntoBytes, KnownLayout, FromBytes, Serialize, StoredCompressed,
|
||||
Debug,
|
||||
Immutable,
|
||||
Clone,
|
||||
Copy,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
StoredCompressed,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct RawLockTime(u32);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::StoredF64;
|
||||
|
||||
use super::{Bitcoin, Cents, Dollars, Height};
|
||||
|
||||
/// Satoshis
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
|
||||
@@ -3,12 +3,14 @@ use std::ops::{Add, AddAssign, Div};
|
||||
use allocative::Allocative;
|
||||
use derive_deref::Deref;
|
||||
use jiff::{civil::date, tz::TimeZone};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{CheckedSub, StoredCompressed};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::Date;
|
||||
|
||||
/// Timestamp
|
||||
#[derive(
|
||||
Debug,
|
||||
Deref,
|
||||
@@ -25,6 +27,7 @@ use super::Date;
|
||||
Serialize,
|
||||
StoredCompressed,
|
||||
Allocative,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct Timestamp(u32);
|
||||
|
||||
|
||||
43
crates/brk_structs/src/tx.rs
Normal file
43
crates/brk_structs/src/tx.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::{RawLockTime, Sats, TxIndex, TxInput, TxOutput, TxStatus, TxVersion, Txid};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Transaction information compatible with mempool.space API format
|
||||
pub struct Tx {
|
||||
#[schemars(example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba")]
|
||||
pub txid: Txid,
|
||||
|
||||
#[schemars(example = TxIndex::new(0))]
|
||||
pub index: TxIndex,
|
||||
|
||||
#[schemars(example = 2)]
|
||||
pub version: TxVersion,
|
||||
|
||||
#[schemars(example = 0)]
|
||||
pub locktime: RawLockTime,
|
||||
|
||||
/// Transaction size in bytes
|
||||
#[schemars(example = 222)]
|
||||
pub size: u32,
|
||||
|
||||
/// Transaction weight (for SegWit transactions)
|
||||
#[schemars(example = 558)]
|
||||
pub weight: u32,
|
||||
|
||||
/// Number of signature operations
|
||||
#[schemars(example = 1)]
|
||||
pub sigops: u32,
|
||||
|
||||
/// Transaction fee in satoshis
|
||||
#[schemars(example = Sats::new(31))]
|
||||
pub fee: Sats,
|
||||
|
||||
/// Transaction inputs
|
||||
pub vin: Vec<TxInput>,
|
||||
|
||||
/// Transaction outputs
|
||||
pub vout: Vec<TxOutput>,
|
||||
|
||||
pub status: TxStatus,
|
||||
}
|
||||
@@ -6,9 +6,15 @@ use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer};
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
/// Transaction ID (hash)
|
||||
#[derive(
|
||||
Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, JsonSchema,
|
||||
)]
|
||||
#[schemars(
|
||||
example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
||||
example = "2bb85f4b004be6da54f766c17c1e855187327112c231ef2ff35ebad0ea67c69e",
|
||||
example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba"
|
||||
)]
|
||||
pub struct Txid([u8; 32]);
|
||||
|
||||
impl From<bitcoin::Txid> for Txid {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{TxIndex, Txid};
|
||||
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
/// Transaction Information
|
||||
pub struct TransactionInfo {
|
||||
#[schemars(
|
||||
with = "String",
|
||||
example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
|
||||
)]
|
||||
pub txid: Txid,
|
||||
#[schemars(example = TxIndex::new(0))]
|
||||
pub index: TxIndex,
|
||||
// #[serde(flatten)]
|
||||
// #[schemars(with = "serde_json::Value")]
|
||||
// pub tx: Transaction,
|
||||
}
|
||||
40
crates/brk_structs/src/txinput.rs
Normal file
40
crates/brk_structs/src/txinput.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::{TxPrevout, Txid, Vout};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Transaction input
|
||||
pub struct TxInput {
|
||||
/// Transaction ID of the output being spent
|
||||
pub txid: Txid,
|
||||
|
||||
#[schemars(example = 0)]
|
||||
pub vout: Vout,
|
||||
|
||||
/// Information about the previous output being spent
|
||||
pub prevout: Option<TxPrevout>,
|
||||
|
||||
/// Signature script (for non-SegWit inputs)
|
||||
#[schemars(example = "")]
|
||||
pub scriptsig: String,
|
||||
|
||||
/// Signature script in assembly format
|
||||
#[schemars(example = "")]
|
||||
pub scriptsig_asm: String,
|
||||
|
||||
/// Witness data (for SegWit inputs)
|
||||
#[schemars(example = vec!["3045022100d0c9936990bf00bdba15f425f0f360a223d5cbf81f4bf8477fe6c6d838fb5fae02207e42a8325a4dd41702bf065aa6e0a1b7b0b8ee92a5e6c182da018b0afc82c40601".to_string()])]
|
||||
pub witness: Vec<String>,
|
||||
|
||||
/// Whether this input is a coinbase (block reward) input
|
||||
#[schemars(example = false)]
|
||||
pub is_coinbase: bool,
|
||||
|
||||
/// Input sequence number
|
||||
#[schemars(example = 429496729)]
|
||||
pub sequence: u32,
|
||||
|
||||
/// Inner redeemscript in assembly format (for P2SH-wrapped SegWit)
|
||||
#[schemars(example = Some("OP_0 OP_PUSHBYTES_20 992a1f7420fc5285070d19c71ff2efb1e356ad2f".to_string()))]
|
||||
pub inner_redeemscript_asm: Option<String>,
|
||||
}
|
||||
27
crates/brk_structs/src/txoutput.rs
Normal file
27
crates/brk_structs/src/txoutput.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::Sats;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Transaction output
|
||||
pub struct TxOutput {
|
||||
/// Script pubkey (locking script)
|
||||
#[schemars(example = "00143b064c595a95f977f00352d6e917501267cacdc6")]
|
||||
pub scriptpubkey: String,
|
||||
|
||||
/// Script pubkey in assembly format
|
||||
#[schemars(example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6")]
|
||||
pub scriptpubkey_asm: String,
|
||||
|
||||
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, etc.)
|
||||
#[schemars(example = &"v0_p2wpkh")]
|
||||
pub scriptpubkey_type: String,
|
||||
|
||||
/// Bitcoin address (if applicable, None for OP_RETURN)
|
||||
#[schemars(example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
|
||||
pub scriptpubkey_address: Option<String>,
|
||||
|
||||
/// Value of the output in satoshis
|
||||
#[schemars(example = Sats::new(7782))]
|
||||
pub value: Sats,
|
||||
}
|
||||
27
crates/brk_structs/src/txprevout.rs
Normal file
27
crates/brk_structs/src/txprevout.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::Sats;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Information about a previous transaction output being spent
|
||||
pub struct TxPrevout {
|
||||
/// Script pubkey (locking script)
|
||||
#[schemars(example = "00143b064c595a95f977f00352d6e917501267cacdc6")]
|
||||
pub scriptpubkey: String,
|
||||
|
||||
/// Script pubkey in assembly format
|
||||
#[schemars(example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6")]
|
||||
pub scriptpubkey_asm: String,
|
||||
|
||||
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, etc.)
|
||||
#[schemars(example = &"v0_p2wpkh")]
|
||||
pub scriptpubkey_type: String,
|
||||
|
||||
/// Bitcoin address (if applicable)
|
||||
#[schemars(example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
|
||||
pub scriptpubkey_address: Option<String>,
|
||||
|
||||
/// Value of the output in satoshis
|
||||
#[schemars(example = Sats::new(7813))]
|
||||
pub value: Sats,
|
||||
}
|
||||
24
crates/brk_structs/src/txstatus.rs
Normal file
24
crates/brk_structs/src/txstatus.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{BlockHash, Height, Timestamp};
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Transaction confirmation status
|
||||
pub struct TxStatus {
|
||||
/// Whether the transaction is confirmed
|
||||
#[schemars(example = true)]
|
||||
pub confirmed: bool,
|
||||
|
||||
/// Block height (only present if confirmed)
|
||||
#[schemars(example = Some(916656))]
|
||||
pub block_height: Option<Height>,
|
||||
|
||||
/// Block hash (only present if confirmed)
|
||||
#[schemars(example = Some("000000000000000000012711f7e0d13e586752a42c66e25faf75f159b3d04911".to_string()))]
|
||||
pub block_hash: Option<BlockHash>,
|
||||
|
||||
/// Block timestamp (only present if confirmed)
|
||||
#[schemars(example = Some(1759000868))]
|
||||
pub block_time: Option<Timestamp>,
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::StoredCompressed;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::StoredU16;
|
||||
|
||||
/// Transaction version number
|
||||
#[derive(
|
||||
Debug,
|
||||
Deref,
|
||||
@@ -20,6 +22,7 @@ use super::StoredU16;
|
||||
FromBytes,
|
||||
Serialize,
|
||||
StoredCompressed,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct TxVersion(u16);
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Index of the output being spent in the previous transaction
|
||||
#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, JsonSchema)]
|
||||
pub struct Vout(u32);
|
||||
|
||||
impl Vout {
|
||||
|
||||
Reference in New Issue
Block a user