mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot + monitor: add addresses to mempool
This commit is contained in:
86
Cargo.lock
generated
86
Cargo.lock
generated
@@ -509,7 +509,7 @@ dependencies = [
|
||||
"brk_interface",
|
||||
"brk_logger",
|
||||
"brk_mcp",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_server",
|
||||
"brk_store",
|
||||
"brk_structs",
|
||||
@@ -615,7 +615,7 @@ dependencies = [
|
||||
"brk_indexer",
|
||||
"brk_interface",
|
||||
"brk_logger",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_server",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
@@ -640,7 +640,7 @@ dependencies = [
|
||||
"brk_grouper",
|
||||
"brk_indexer",
|
||||
"brk_logger",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_store",
|
||||
"brk_structs",
|
||||
"brk_traversable",
|
||||
@@ -697,7 +697,7 @@ dependencies = [
|
||||
"brk_error",
|
||||
"brk_grouper",
|
||||
"brk_logger",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_store",
|
||||
"brk_structs",
|
||||
"brk_traversable",
|
||||
@@ -716,7 +716,7 @@ dependencies = [
|
||||
"brk_computer",
|
||||
"brk_error",
|
||||
"brk_indexer",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_structs",
|
||||
"brk_traversable",
|
||||
"derive_deref",
|
||||
@@ -763,7 +763,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brk_parser"
|
||||
name = "brk_reader"
|
||||
version = "0.0.111"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
@@ -1183,7 +1183,7 @@ dependencies = [
|
||||
"brk_interface",
|
||||
"brk_logger",
|
||||
"brk_mcp",
|
||||
"brk_parser",
|
||||
"brk_reader",
|
||||
"brk_structs",
|
||||
"brk_traversable",
|
||||
"jiff",
|
||||
@@ -1373,9 +1373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1383,9 +1383,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1395,9 +1395,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.47"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -1407,9 +1407,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
@@ -1856,9 +1856,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
@@ -2114,9 +2114,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dc8f7d2ded5f9209535e4b3fd4d39c002f30902ff5ce9f64e2c33d549576500"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
@@ -2159,9 +2159,9 @@ checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e54c115d4f30f52c67202f079c5f9d8b49db4691f460fdb0b4c2e838261b2ba5"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
@@ -2500,17 +2500,6 @@ dependencies = [
|
||||
"compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@@ -3964,9 +3953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.1"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3976,9 +3965,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3987,9 +3976,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.7"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
@@ -4695,27 +4684,24 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4939,9 +4925,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
|
||||
checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs-macros",
|
||||
@@ -4949,9 +4935,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
|
||||
checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -58,7 +58,7 @@ 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_monitor = { version = "0.0.111", path = "crates/brk_monitor" }
|
||||
brk_parser = { version = "0.0.111", path = "crates/brk_parser" }
|
||||
brk_reader = { version = "0.0.111", path = "crates/brk_reader" }
|
||||
brk_server = { version = "0.0.111", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.0.111", path = "crates/brk_store" }
|
||||
brk_structs = { version = "0.0.111", path = "crates/brk_structs" }
|
||||
@@ -78,7 +78,7 @@ serde_bytes = "0.11.19"
|
||||
serde_derive = "1.0.228"
|
||||
serde_json = { version = "1.0.145", features = ["float_roundtrip"] }
|
||||
sonic-rs = "0.5.5"
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
|
||||
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
|
||||
vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"] }
|
||||
# vecdb = { version = "0.2.17", features = ["derive"] }
|
||||
zerocopy = { version = "0.8.27", features = ["derive"] }
|
||||
|
||||
@@ -40,7 +40,7 @@ indexer = ["brk_indexer"]
|
||||
interface = ["brk_interface"]
|
||||
logger = ["brk_logger"]
|
||||
mcp = ["brk_mcp"]
|
||||
parser = ["brk_parser"]
|
||||
parser = ["brk_reader"]
|
||||
server = ["brk_server"]
|
||||
store = ["brk_store"]
|
||||
structs = ["brk_structs"]
|
||||
@@ -58,7 +58,7 @@ brk_indexer = { workspace = true, optional = true }
|
||||
brk_interface = { workspace = true, optional = true }
|
||||
brk_logger = { workspace = true, optional = true }
|
||||
brk_mcp = { workspace = true, optional = true }
|
||||
brk_parser = { workspace = true, optional = true }
|
||||
brk_reader = { workspace = true, optional = true }
|
||||
brk_server = { workspace = true, optional = true }
|
||||
brk_store = { workspace = true, optional = true }
|
||||
brk_structs = { workspace = true, optional = true }
|
||||
|
||||
@@ -248,7 +248,7 @@ This pattern ensures:
|
||||
| `interface` | `brk_interface` | Data query interface |
|
||||
| `logger` | `brk_logger` | Enhanced logging |
|
||||
| `mcp` | `brk_mcp` | Model Context Protocol |
|
||||
| `parser` | `brk_parser` | Block parsing |
|
||||
| `parser` | `brk_reader` | Block parsing |
|
||||
| `server` | `brk_server` | HTTP server |
|
||||
| `store` | `brk_store` | Key-value storage |
|
||||
| `structs` | `brk_structs` | Data structures |
|
||||
@@ -270,4 +270,4 @@ Documentation is aggregated from all components with `#![doc = include_str!("../
|
||||
|
||||
---
|
||||
|
||||
_This README was generated by Claude Code_
|
||||
_This README was generated by Claude Code_
|
||||
|
||||
@@ -46,7 +46,7 @@ pub use brk_mcp as mcp;
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
#[doc(inline)]
|
||||
pub use brk_parser as parser;
|
||||
pub use brk_reader as parser;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -19,10 +19,10 @@ brk_fetcher = { workspace = true }
|
||||
brk_indexer = { workspace = true }
|
||||
brk_interface = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_reader = { workspace = true }
|
||||
brk_server = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
clap = { version = "4.5.48", features = ["derive", "string"] }
|
||||
clap = { version = "4.5.49", features = ["derive", "string"] }
|
||||
color-eyre = "0.6.5"
|
||||
log = { workspace = true }
|
||||
minreq = { workspace = true }
|
||||
|
||||
@@ -15,7 +15,7 @@ use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_interface::Interface;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_server::{Server, VERSION};
|
||||
use log::info;
|
||||
use vecdb::Exit;
|
||||
@@ -48,7 +48,7 @@ pub fn run() -> color_eyre::Result<()> {
|
||||
let exit = Exit::new();
|
||||
exit.set_ctrlc_handler();
|
||||
|
||||
let parser = Parser::new(config.blocksdir(), rpc);
|
||||
let parser = Reader::new(config.blocksdir(), rpc);
|
||||
|
||||
let mut indexer = Indexer::forced_import(&config.brkdir())?;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ brk_fetcher = { workspace = true }
|
||||
brk_grouper = { workspace = true }
|
||||
brk_indexer = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_reader = { workspace = true }
|
||||
brk_store = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
brk_traversable = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_fetcher::Fetcher;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use vecdb::Exit;
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
@@ -34,7 +34,7 @@ pub fn main() -> Result<()> {
|
||||
let outputs_dir = Path::new(&std::env::var("HOME").unwrap()).join(".brk");
|
||||
// let outputs_dir = Path::new("../../_outputs");
|
||||
|
||||
let parser = Parser::new(bitcoin_dir.join("blocks"), rpc);
|
||||
let parser = Reader::new(bitcoin_dir.join("blocks"), rpc);
|
||||
|
||||
let mut indexer = Indexer::forced_import(&outputs_dir)?;
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ fn main() -> Result<()> {
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
.map(|bytes| Address::try_from(bytes).unwrap())
|
||||
.map(|bytes| Address::try_from(&bytes).unwrap())
|
||||
.and_then(|address| pools.find_from_address(&address))
|
||||
})
|
||||
.or_else(|| pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::{BlkPosition, Height, TxIndex, Version};
|
||||
use brk_traversable::Traversable;
|
||||
use vecdb::{
|
||||
@@ -56,10 +56,10 @@ impl Vecs {
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
parser: &Parser,
|
||||
reader: &Reader,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.compute_(indexer, indexes, starting_indexes, parser, exit)?;
|
||||
self.compute_(indexer, indexes, starting_indexes, reader, exit)?;
|
||||
self.db.flush_then_punch()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -69,7 +69,7 @@ impl Vecs {
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
parser: &Parser,
|
||||
parser: &Reader,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let min_txindex =
|
||||
@@ -87,7 +87,7 @@ impl Vecs {
|
||||
let mut height_to_first_txindex_iter = indexer.vecs.height_to_first_txindex.iter();
|
||||
|
||||
parser
|
||||
.parse(
|
||||
.read(
|
||||
Some(min_height),
|
||||
Some((indexer.vecs.height_to_first_txindex.len() - 1).into()),
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::Path;
|
||||
use brk_error::Result;
|
||||
use brk_fetcher::Fetcher;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::Version;
|
||||
use brk_traversable::Traversable;
|
||||
use log::info;
|
||||
@@ -114,7 +114,7 @@ impl Computer {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
starting_indexes: brk_indexer::Indexes,
|
||||
parser: &Parser,
|
||||
parser: &Reader,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
info!("Computing indexes...");
|
||||
|
||||
@@ -201,7 +201,7 @@ impl Vecs {
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
.map(|bytes| Address::try_from(bytes).unwrap())
|
||||
.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))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "brk_indexer"
|
||||
description = "A Bitcoin indexer built on top of brk_parser"
|
||||
description = "A Bitcoin indexer built on top of brk_reader"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -15,7 +15,7 @@ bitcoincore-rpc = { workspace = true }
|
||||
brk_error = { workspace = true }
|
||||
brk_grouper = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_reader = { workspace = true }
|
||||
brk_store = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
brk_traversable = { workspace = true }
|
||||
|
||||
@@ -7,7 +7,7 @@ High-performance Bitcoin blockchain indexer with parallel processing and dual st
|
||||
|
||||
## Overview
|
||||
|
||||
This crate provides a comprehensive Bitcoin blockchain indexer built on top of `brk_parser`. It processes raw Bitcoin blocks in parallel, extracting and indexing transactions, addresses, inputs, outputs, and metadata into optimized storage structures. The indexer maintains two complementary storage systems: columnar vectors for analytics and key-value stores for fast lookups.
|
||||
This crate provides a comprehensive Bitcoin blockchain indexer built on top of `brk_reader`. It processes raw Bitcoin blocks in parallel, extracting and indexing transactions, addresses, inputs, outputs, and metadata into optimized storage structures. The indexer maintains two complementary storage systems: columnar vectors for analytics and key-value stores for fast lookups.
|
||||
|
||||
**Key Features:**
|
||||
|
||||
@@ -36,7 +36,7 @@ cargo add brk_indexer
|
||||
|
||||
```rust
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
use bitcoincore_rpc::{Client, Auth};
|
||||
use vecdb::Exit;
|
||||
use std::path::Path;
|
||||
@@ -114,7 +114,7 @@ Complete coverage of Bitcoin script types:
|
||||
|
||||
```rust
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
use std::path::Path;
|
||||
|
||||
// Initialize components
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use vecdb::Exit;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -33,7 +33,7 @@ fn main() -> Result<()> {
|
||||
let exit = Exit::new();
|
||||
exit.set_ctrlc_handler();
|
||||
|
||||
let parser = Parser::new(blocks_dir, rpc);
|
||||
let parser = Reader::new(blocks_dir, rpc);
|
||||
|
||||
fs::create_dir_all(&outputs_dir)?;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::{collections::BTreeMap, path::Path, str::FromStr, time::Instant};
|
||||
|
||||
use bitcoin::{Transaction, TxIn, TxOut};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_parser::Parser;
|
||||
use brk_store::AnyStore;
|
||||
use brk_structs::{
|
||||
AddressBytes, AddressBytesHash, BlockHashPrefix, Height, InputIndex, OutputIndex, OutputType,
|
||||
@@ -51,7 +50,7 @@ impl Indexer {
|
||||
|
||||
pub fn index(
|
||||
&mut self,
|
||||
parser: &Parser,
|
||||
reader: &brk_reader::Reader,
|
||||
rpc: &'static bitcoincore_rpc::Client,
|
||||
exit: &Exit,
|
||||
check_collisions: bool,
|
||||
@@ -163,7 +162,7 @@ impl Indexer {
|
||||
&mut p2aaddressindex_to_p2abytes_reader_opt,
|
||||
);
|
||||
|
||||
parser.parse(start, end).iter().try_for_each(
|
||||
reader.read(start, end).iter().try_for_each(
|
||||
|block| -> Result<()> {
|
||||
let height = block.height();
|
||||
let blockhash = block.hash();
|
||||
|
||||
@@ -320,28 +320,28 @@ impl Vecs {
|
||||
match bytes {
|
||||
AddressBytes::P2PK65(bytes) => self
|
||||
.p2pk65addressindex_to_p2pk65bytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2PK33(bytes) => self
|
||||
.p2pk33addressindex_to_p2pk33bytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2PKH(bytes) => self
|
||||
.p2pkhaddressindex_to_p2pkhbytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2SH(bytes) => self
|
||||
.p2shaddressindex_to_p2shbytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2WPKH(bytes) => self
|
||||
.p2wpkhaddressindex_to_p2wpkhbytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2WSH(bytes) => self
|
||||
.p2wshaddressindex_to_p2wshbytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2TR(bytes) => self
|
||||
.p2traddressindex_to_p2trbytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
AddressBytes::P2A(bytes) => self
|
||||
.p2aaddressindex_to_p2abytes
|
||||
.push_if_needed(index.into(), bytes)?,
|
||||
.push_if_needed(index.into(), *bytes)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ bitcoincore-rpc = { workspace = true }
|
||||
brk_computer = { workspace = true }
|
||||
brk_error = { workspace = true }
|
||||
brk_indexer = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_reader = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
brk_traversable = { workspace = true }
|
||||
derive_deref = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@ use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_interface::{Interface, Params, ParamsOpt};
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::Index;
|
||||
use vecdb::Exit;
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn main() -> Result<()> {
|
||||
let exit = Exit::new();
|
||||
exit.set_ctrlc_handler();
|
||||
|
||||
let parser = Parser::new(blocks_dir, rpc);
|
||||
let parser = Reader::new(blocks_dir, rpc);
|
||||
|
||||
let indexer = Indexer::forced_import(&outputs_dir)?;
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::str::FromStr;
|
||||
use bitcoin::{Network, PublicKey, ScriptBuf};
|
||||
use brk_error::{Error, Result};
|
||||
use brk_structs::{
|
||||
Address, AddressBytes, AddressBytesHash, AddressStats, AnyAddressDataIndexEnum, Bitcoin,
|
||||
OutputType,
|
||||
Address, AddressBytes, AddressBytesHash, AddressChainStats, AddressMempoolStats, AddressStats,
|
||||
AnyAddressDataIndexEnum, Bitcoin, OutputType,
|
||||
};
|
||||
use vecdb::{AnyIterableVec, VecIterator};
|
||||
|
||||
@@ -104,9 +104,11 @@ pub fn get_address(Address { address }: Address, interface: &Interface) -> Resul
|
||||
.into(),
|
||||
};
|
||||
|
||||
let balance = address_data.balance();
|
||||
|
||||
todo!();
|
||||
Ok(AddressStats {
|
||||
address: address.into(),
|
||||
chain_stats: AddressChainStats::default(),
|
||||
mempool_stats: AddressMempoolStats::default(),
|
||||
})
|
||||
|
||||
// Ok(Address {
|
||||
// address: address.to_string(),
|
||||
|
||||
@@ -4,15 +4,18 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use bitcoin::{Transaction, consensus::Decodable};
|
||||
use bitcoin::consensus::Decodable;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_parser::XORIndex;
|
||||
use brk_structs::{Tx, Txid, TxidPath, TxidPrefix};
|
||||
use brk_reader::XORIndex;
|
||||
use brk_structs::{Transaction, Txid, TxidPath, TxidPrefix};
|
||||
use vecdb::VecIterator;
|
||||
|
||||
use crate::Interface;
|
||||
|
||||
pub fn get_transaction_info(TxidPath { txid }: TxidPath, interface: &Interface) -> Result<Tx> {
|
||||
pub fn get_transaction_info(
|
||||
TxidPath { txid }: TxidPath,
|
||||
interface: &Interface,
|
||||
) -> Result<Transaction> {
|
||||
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
|
||||
return Err(Error::InvalidTxid);
|
||||
};
|
||||
@@ -72,7 +75,7 @@ pub fn get_transaction_info(TxidPath { txid }: TxidPath, interface: &Interface)
|
||||
xori.bytes(&mut buffer, parser.xor_bytes());
|
||||
|
||||
let mut reader = Cursor::new(buffer);
|
||||
let Ok(_) = Transaction::consensus_decode(&mut reader) else {
|
||||
let Ok(_) = bitcoin::Transaction::consensus_decode(&mut reader) else {
|
||||
return Err(Error::Str("Failed decode the transaction"));
|
||||
};
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ use std::collections::BTreeMap;
|
||||
use brk_computer::Computer;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::{
|
||||
Address, AddressStats, Format, Height, Index, IndexInfo, Limit, Metric, MetricCount, Tx,
|
||||
TxidPath,
|
||||
Address, AddressStats, Format, Height, Index, IndexInfo, Limit, Metric, MetricCount,
|
||||
Transaction, TxidPath,
|
||||
};
|
||||
use brk_traversable::TreeNode;
|
||||
use vecdb::{AnyCollectableVec, AnyStoredVec};
|
||||
@@ -33,13 +33,13 @@ use crate::{
|
||||
#[allow(dead_code)]
|
||||
pub struct Interface<'a> {
|
||||
vecs: Vecs<'a>,
|
||||
parser: &'a Parser,
|
||||
parser: &'a Reader,
|
||||
indexer: &'a Indexer,
|
||||
computer: &'a Computer,
|
||||
}
|
||||
|
||||
impl<'a> Interface<'a> {
|
||||
pub fn build(parser: &Parser, indexer: &Indexer, computer: &Computer) -> Self {
|
||||
pub fn build(parser: &Reader, indexer: &Indexer, computer: &Computer) -> Self {
|
||||
let parser = parser.static_clone();
|
||||
let indexer = indexer.static_clone();
|
||||
let computer = computer.static_clone();
|
||||
@@ -61,7 +61,7 @@ impl<'a> Interface<'a> {
|
||||
get_address(address, self)
|
||||
}
|
||||
|
||||
pub fn get_transaction_info(&self, txid: TxidPath) -> Result<Tx> {
|
||||
pub fn get_transaction_info(&self, txid: TxidPath) -> Result<Transaction> {
|
||||
get_transaction_info(txid, self)
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ impl<'a> Interface<'a> {
|
||||
self.vecs.metric_to_indexes(metric)
|
||||
}
|
||||
|
||||
pub fn parser(&self) -> &Parser {
|
||||
pub fn parser(&self) -> &Reader {
|
||||
self.parser
|
||||
}
|
||||
|
||||
|
||||
@@ -31,5 +31,7 @@ fn main() {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
let txs = mempool.get_txs();
|
||||
println!("mempool_tx_count: {}", txs.len());
|
||||
let addresses = mempool.get_addresses();
|
||||
println!("mempool_address_count: {}", addresses.len());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
use bitcoin::{Transaction, Txid, consensus::encode};
|
||||
use bitcoin::consensus::encode;
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use brk_structs::{AddressBytes, AddressMempoolStats, Transaction, Txid};
|
||||
use log::error;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
@@ -11,6 +12,7 @@ const MAX_FETCHES_PER_CYCLE: usize = 10_000;
|
||||
pub struct Mempool {
|
||||
rpc: &'static Client,
|
||||
txs: RwLock<FxHashMap<Txid, Transaction>>,
|
||||
addresses: RwLock<FxHashMap<AddressBytes, (AddressMempoolStats, FxHashSet<Txid>)>>,
|
||||
}
|
||||
|
||||
impl Mempool {
|
||||
@@ -18,6 +20,7 @@ impl Mempool {
|
||||
Self {
|
||||
rpc,
|
||||
txs: RwLock::new(FxHashMap::default()),
|
||||
addresses: RwLock::new(FxHashMap::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +28,12 @@ impl Mempool {
|
||||
self.txs.read()
|
||||
}
|
||||
|
||||
pub fn get_addresses(
|
||||
&self,
|
||||
) -> RwLockReadGuard<'_, FxHashMap<AddressBytes, (AddressMempoolStats, FxHashSet<Txid>)>> {
|
||||
self.addresses.read()
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
loop {
|
||||
if let Err(e) = self.update() {
|
||||
@@ -34,35 +43,82 @@ impl Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn update(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let txids = self
|
||||
.rpc
|
||||
.get_raw_mempool()?
|
||||
.into_iter()
|
||||
.map(Txid::from)
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let missing_txids = {
|
||||
let new_txs = {
|
||||
let txs = self.txs.read();
|
||||
txids
|
||||
.iter()
|
||||
.filter(|txid| !txs.contains_key(*txid))
|
||||
.take(MAX_FETCHES_PER_CYCLE)
|
||||
.cloned()
|
||||
.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<_, _>>();
|
||||
}
|
||||
.into_iter()
|
||||
.filter_map(|txid| {
|
||||
self.rpc
|
||||
.get_raw_transaction_hex(&bitcoin::Txid::from(&txid), None)
|
||||
.ok()
|
||||
.and_then(|hex| encode::deserialize_hex::<bitcoin::Transaction>(&hex).ok())
|
||||
.map(|tx| Transaction::from_mempool(tx, self.rpc))
|
||||
.map(|tx| (txid, tx))
|
||||
})
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
|
||||
let mut txs = self.txs.write();
|
||||
txs.retain(|txid, _| txids.contains(txid));
|
||||
let mut addresses = self.addresses.write();
|
||||
txs.retain(|txid, tx| {
|
||||
if txids.contains(txid) {
|
||||
return true;
|
||||
}
|
||||
tx.input
|
||||
.iter()
|
||||
.flat_map(|txin| txin.prevout.as_ref())
|
||||
.flat_map(|txout| txout.address_bytes().map(|bytes| (txout, bytes)))
|
||||
.for_each(|(txout, bytes)| {
|
||||
let (stats, set) = addresses.entry(bytes).or_default();
|
||||
set.remove(txid);
|
||||
stats.sent(txout);
|
||||
stats.update_tx_count(set.len() as u32);
|
||||
});
|
||||
tx.output
|
||||
.iter()
|
||||
.flat_map(|txout| txout.address_bytes().map(|bytes| (txout, bytes)))
|
||||
.for_each(|(txout, bytes)| {
|
||||
let (stats, set) = addresses.entry(bytes).or_default();
|
||||
set.remove(txid);
|
||||
stats.received(txout);
|
||||
stats.update_tx_count(set.len() as u32);
|
||||
});
|
||||
false
|
||||
});
|
||||
new_txs.iter().for_each(|(txid, tx)| {
|
||||
tx.input
|
||||
.iter()
|
||||
.flat_map(|txin| txin.prevout.as_ref())
|
||||
.flat_map(|txout| txout.address_bytes().map(|bytes| (txout, bytes)))
|
||||
.for_each(|(txout, bytes)| {
|
||||
let (stats, set) = addresses.entry(bytes).or_default();
|
||||
set.insert(txid.clone());
|
||||
stats.sending(txout);
|
||||
stats.update_tx_count(set.len() as u32);
|
||||
});
|
||||
tx.output
|
||||
.iter()
|
||||
.flat_map(|txout| txout.address_bytes().map(|bytes| (txout, bytes)))
|
||||
.for_each(|(txout, bytes)| {
|
||||
let (stats, set) = addresses.entry(bytes).or_default();
|
||||
set.insert(txid.clone());
|
||||
stats.receiving(txout);
|
||||
stats.update_tx_count(set.len() as u32);
|
||||
});
|
||||
});
|
||||
txs.extend(new_txs);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "brk_parser"
|
||||
name = "brk_reader"
|
||||
description = "A very fast Bitcoin block parser and iterator built on top of bitcoin-rust"
|
||||
keywords = ["bitcoin", "block", "iterator"]
|
||||
categories = ["cryptography::cryptocurrencies", "encoding"]
|
||||
@@ -1,9 +1,9 @@
|
||||
# brk_parser
|
||||
# brk_reader
|
||||
|
||||
High-performance Bitcoin block parser for raw Bitcoin Core block files with XOR encryption support.
|
||||
|
||||
[](https://crates.io/crates/brk_parser)
|
||||
[](https://docs.rs/brk_parser)
|
||||
[](https://crates.io/crates/brk_reader)
|
||||
[](https://docs.rs/brk_reader)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -29,13 +29,13 @@ This crate provides a multi-threaded Bitcoin block parser that processes raw Bit
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cargo add brk_parser
|
||||
cargo add brk_reader
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
use bitcoincore_rpc::{Client, Auth, RpcApi};
|
||||
use brk_structs::Height;
|
||||
use std::path::PathBuf;
|
||||
@@ -94,7 +94,7 @@ The parser implements a three-stage pipeline:
|
||||
### Basic Block Iteration
|
||||
|
||||
```rust
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
|
||||
let parser = Parser::new(blocks_dir, Some(output_dir), rpc);
|
||||
|
||||
@@ -115,7 +115,7 @@ for (height, block, hash) in receiver.iter() {
|
||||
### Range-Based Processing
|
||||
|
||||
```rust
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
|
||||
let parser = Parser::new(blocks_dir, Some(output_dir), rpc);
|
||||
|
||||
@@ -139,7 +139,7 @@ println!("Processed 1000 blocks with {} total transactions", total_tx_count);
|
||||
### Incremental Processing with Metadata
|
||||
|
||||
```rust
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Parser;
|
||||
|
||||
let parser = Parser::new(blocks_dir, Some(output_dir), rpc);
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::Path;
|
||||
|
||||
use bitcoincore_rpc::{Auth, Client};
|
||||
use brk_error::Result;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
fn main() -> Result<()> {
|
||||
@@ -18,12 +18,12 @@ fn main() -> Result<()> {
|
||||
Auth::CookieFile(bitcoin_dir.join(".cookie")),
|
||||
)?));
|
||||
|
||||
let parser = Parser::new(bitcoin_dir.join("blocks"), rpc);
|
||||
let reader = Reader::new(bitcoin_dir.join("blocks"), rpc);
|
||||
|
||||
let start = None;
|
||||
// let start = Some(916037_u32.into());
|
||||
let end = None;
|
||||
parser.parse(start, end).iter().for_each(|block| {
|
||||
reader.read(start, end).iter().for_each(|block| {
|
||||
println!("{}: {}", block.height(), block.hash());
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use bitcoincore_rpc::{Auth, Client};
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::{Height, OutputType};
|
||||
|
||||
fn main() {
|
||||
@@ -20,7 +20,7 @@ fn main() {
|
||||
// let start = None;
|
||||
// let end = None;
|
||||
|
||||
let parser = Parser::new(bitcoin_dir.join("blocks"), rpc);
|
||||
let parser = Reader::new(bitcoin_dir.join("blocks"), rpc);
|
||||
|
||||
// parser
|
||||
// .parse(start, end)
|
||||
@@ -34,14 +34,14 @@ const MAGIC_BYTES: [u8; 4] = [249, 190, 180, 217];
|
||||
const BOUND_CAP: usize = 50;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Parser {
|
||||
pub struct Reader {
|
||||
blk_index_to_blk_path: Arc<RwLock<BlkIndexToBlkPath>>,
|
||||
xor_bytes: XORBytes,
|
||||
blocks_dir: PathBuf,
|
||||
rpc: &'static bitcoincore_rpc::Client,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
impl Reader {
|
||||
pub fn new(blocks_dir: PathBuf, rpc: &'static bitcoincore_rpc::Client) -> Self {
|
||||
Self {
|
||||
xor_bytes: XORBytes::from(blocks_dir.as_path()),
|
||||
@@ -75,7 +75,7 @@ impl Parser {
|
||||
///
|
||||
/// For an example checkout `./main.rs`
|
||||
///
|
||||
pub fn parse(&self, start: Option<Height>, end: Option<Height>) -> Receiver<ParsedBlock> {
|
||||
pub fn read(&self, start: Option<Height>, end: Option<Height>) -> Receiver<ParsedBlock> {
|
||||
let rpc = self.rpc;
|
||||
|
||||
let (send_bytes, recv_bytes) = bounded(BOUND_CAP / 2);
|
||||
@@ -21,7 +21,7 @@ brk_indexer = { workspace = true }
|
||||
brk_interface = { workspace = true }
|
||||
brk_logger = { workspace = true }
|
||||
brk_mcp = { workspace = true }
|
||||
brk_parser = { workspace = true }
|
||||
brk_reader = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
brk_traversable = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
@@ -7,7 +7,7 @@ use brk_error::Result;
|
||||
use brk_fetcher::Fetcher;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_interface::Interface;
|
||||
use brk_parser::Parser;
|
||||
use brk_reader::Reader;
|
||||
use brk_server::Server;
|
||||
use vecdb::Exit;
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn main() -> Result<()> {
|
||||
let exit = Exit::new();
|
||||
exit.set_ctrlc_handler();
|
||||
|
||||
let parser = Parser::new(bitcoin_dir.join("blocks"), rpc);
|
||||
let parser = Reader::new(bitcoin_dir.join("blocks"), rpc);
|
||||
|
||||
let outputs_dir = Path::new("../../_outputs");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use axum::{
|
||||
response::{Redirect, Response},
|
||||
routing::get,
|
||||
};
|
||||
use brk_structs::{Tx, TxidPath};
|
||||
use brk_structs::{Transaction, 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::<Tx>()
|
||||
.ok_response::<Transaction>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use bitcoin::{Network, ScriptBuf, opcodes, script::Builder};
|
||||
use bitcoin::{ScriptBuf, opcodes, script::Builder};
|
||||
use brk_error::Error;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
@@ -29,64 +29,53 @@ impl From<String> for Address {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ScriptBuf> for 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(),
|
||||
}
|
||||
fn try_from(script: &ScriptBuf) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&AddressBytes::try_from(script)?)
|
||||
}
|
||||
}
|
||||
|
||||
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)?)
|
||||
Self::try_from(&AddressBytes::try_from(tuple)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AddressBytes> for Address {
|
||||
impl TryFrom<&AddressBytes> for Address {
|
||||
type Error = Error;
|
||||
fn try_from(bytes: AddressBytes) -> Result<Self, Self::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::P2PK65(_) => Self::from(bytes_to_hex(bytes.as_slice())),
|
||||
AddressBytes::P2PK33(_) => Self::from(bytes_to_hex(bytes.as_slice())),
|
||||
AddressBytes::P2PKH(b) => Self::try_from(
|
||||
Builder::new()
|
||||
&Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(**b)
|
||||
.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()
|
||||
&Builder::new()
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(**b)
|
||||
.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())?
|
||||
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())?
|
||||
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())?
|
||||
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())?
|
||||
Self::try_from(&Builder::new().push_int(1).push_slice(****b).into_script())?
|
||||
}
|
||||
};
|
||||
Ok(address)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
use bitcoin::ScriptBuf;
|
||||
use brk_error::Error;
|
||||
|
||||
@@ -6,16 +8,16 @@ use super::{
|
||||
P2WSHBytes,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum AddressBytes {
|
||||
P2PK65(P2PK65Bytes),
|
||||
P2PK33(P2PK33Bytes),
|
||||
P2PKH(P2PKHBytes),
|
||||
P2SH(P2SHBytes),
|
||||
P2WPKH(P2WPKHBytes),
|
||||
P2WSH(P2WSHBytes),
|
||||
P2TR(P2TRBytes),
|
||||
P2A(P2ABytes),
|
||||
P2PK65(Box<P2PK65Bytes>), // 65
|
||||
P2PK33(Box<P2PK33Bytes>), // 33
|
||||
P2PKH(Box<P2PKHBytes>), // 20
|
||||
P2SH(Box<P2SHBytes>), // 20
|
||||
P2WPKH(Box<P2WPKHBytes>), // 20
|
||||
P2WSH(Box<P2WSHBytes>), // 32
|
||||
P2TR(Box<P2TRBytes>), // 32
|
||||
P2A(Box<P2ABytes>), // 2
|
||||
}
|
||||
|
||||
impl AddressBytes {
|
||||
@@ -33,6 +35,19 @@ impl AddressBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AddressBytes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&super::Address::try_from(self).unwrap().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ScriptBuf> for AddressBytes {
|
||||
type Error = Error;
|
||||
fn try_from(script: &ScriptBuf) -> Result<Self, Self::Error> {
|
||||
Self::try_from((script, OutputType::from(script)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
type Error = Error;
|
||||
fn try_from(tuple: (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
|
||||
@@ -48,7 +63,7 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
return Err(Error::WrongLength);
|
||||
}
|
||||
};
|
||||
Ok(Self::P2PK65(P2PK65Bytes::from(bytes)))
|
||||
Ok(Self::P2PK65(Box::new(P2PK65Bytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2PK33 => {
|
||||
let bytes = script.as_bytes();
|
||||
@@ -59,31 +74,31 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
return Err(Error::WrongLength);
|
||||
}
|
||||
};
|
||||
Ok(Self::P2PK33(P2PK33Bytes::from(bytes)))
|
||||
Ok(Self::P2PK33(Box::new(P2PK33Bytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2PKH => {
|
||||
let bytes = &script.as_bytes()[3..23];
|
||||
Ok(Self::P2PKH(P2PKHBytes::from(bytes)))
|
||||
Ok(Self::P2PKH(Box::new(P2PKHBytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2SH => {
|
||||
let bytes = &script.as_bytes()[2..22];
|
||||
Ok(Self::P2SH(P2SHBytes::from(bytes)))
|
||||
Ok(Self::P2SH(Box::new(P2SHBytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2WPKH => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2WPKH(P2WPKHBytes::from(bytes)))
|
||||
Ok(Self::P2WPKH(Box::new(P2WPKHBytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2WSH => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2WSH(P2WSHBytes::from(bytes)))
|
||||
Ok(Self::P2WSH(Box::new(P2WSHBytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2TR => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2TR(P2TRBytes::from(bytes)))
|
||||
Ok(Self::P2TR(Box::new(P2TRBytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2A => {
|
||||
let bytes = &script.as_bytes()[2..];
|
||||
Ok(Self::P2A(P2ABytes::from(bytes)))
|
||||
Ok(Self::P2A(Box::new(P2ABytes::from(bytes))))
|
||||
}
|
||||
OutputType::P2MS => Err(Error::WrongAddressType),
|
||||
OutputType::Unknown => Err(Error::WrongAddressType),
|
||||
@@ -96,48 +111,48 @@ impl TryFrom<(&ScriptBuf, OutputType)> for AddressBytes {
|
||||
|
||||
impl From<P2PK65Bytes> for AddressBytes {
|
||||
fn from(value: P2PK65Bytes) -> Self {
|
||||
Self::P2PK65(value)
|
||||
Self::P2PK65(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PK33Bytes> for AddressBytes {
|
||||
fn from(value: P2PK33Bytes) -> Self {
|
||||
Self::P2PK33(value)
|
||||
Self::P2PK33(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2PKHBytes> for AddressBytes {
|
||||
fn from(value: P2PKHBytes) -> Self {
|
||||
Self::P2PKH(value)
|
||||
Self::P2PKH(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2SHBytes> for AddressBytes {
|
||||
fn from(value: P2SHBytes) -> Self {
|
||||
Self::P2SH(value)
|
||||
Self::P2SH(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WPKHBytes> for AddressBytes {
|
||||
fn from(value: P2WPKHBytes) -> Self {
|
||||
Self::P2WPKH(value)
|
||||
Self::P2WPKH(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2WSHBytes> for AddressBytes {
|
||||
fn from(value: P2WSHBytes) -> Self {
|
||||
Self::P2WSH(value)
|
||||
Self::P2WSH(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2TRBytes> for AddressBytes {
|
||||
fn from(value: P2TRBytes) -> Self {
|
||||
Self::P2TR(value)
|
||||
Self::P2TR(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P2ABytes> for AddressBytes {
|
||||
fn from(value: P2ABytes) -> Self {
|
||||
Self::P2A(value)
|
||||
Self::P2A(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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)]
|
||||
#[derive(Debug, Default, Serialize, JsonSchema)]
|
||||
pub struct AddressChainStats {
|
||||
/// Total number of transaction outputs that funded this address
|
||||
#[schemars(example = 5)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::Sats;
|
||||
use crate::{Sats, TxOut};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde::Serialize;
|
||||
///
|
||||
/// Based on mempool.space's format.
|
||||
///
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
#[derive(Debug, Default, Serialize, JsonSchema)]
|
||||
pub struct AddressMempoolStats {
|
||||
/// Number of unconfirmed transaction outputs funding this address
|
||||
#[schemars(example = 0)]
|
||||
@@ -29,3 +29,29 @@ pub struct AddressMempoolStats {
|
||||
#[schemars(example = 0)]
|
||||
pub tx_count: u32,
|
||||
}
|
||||
|
||||
impl AddressMempoolStats {
|
||||
pub fn receiving(&mut self, txout: &TxOut) {
|
||||
self.funded_txo_count += 1;
|
||||
self.funded_txo_sum += txout.value;
|
||||
}
|
||||
|
||||
pub fn received(&mut self, txout: &TxOut) {
|
||||
self.funded_txo_count -= 1;
|
||||
self.funded_txo_sum -= txout.value;
|
||||
}
|
||||
|
||||
pub fn sending(&mut self, txout: &TxOut) {
|
||||
self.spent_txo_count += 1;
|
||||
self.spent_txo_sum += txout.value;
|
||||
}
|
||||
|
||||
pub fn sent(&mut self, txout: &TxOut) {
|
||||
self.spent_txo_count -= 1;
|
||||
self.spent_txo_sum -= txout.value;
|
||||
}
|
||||
|
||||
pub fn update_tx_count(&mut self, tx_count: u32) {
|
||||
self.tx_count = tx_count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{AddressChainStats, AddressMempoolStats};
|
||||
use crate::{Address, AddressChainStats, AddressMempoolStats};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct AddressStats {
|
||||
#[schemars(
|
||||
example = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
|
||||
)]
|
||||
pub address: String,
|
||||
pub address: Address,
|
||||
|
||||
/// Statistics for confirmed transactions on the blockchain
|
||||
pub chain_stats: AddressChainStats,
|
||||
|
||||
@@ -9,11 +9,14 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct U8x2([u8; 2]);
|
||||
impl From<&[u8]> for U8x2 {
|
||||
@@ -31,11 +34,14 @@ impl From<&[u8]> for U8x2 {
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct U8x20([u8; 20]);
|
||||
impl From<&[u8]> for U8x20 {
|
||||
@@ -53,11 +59,14 @@ impl From<&[u8]> for U8x20 {
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct U8x32([u8; 32]);
|
||||
impl From<&[u8]> for U8x32 {
|
||||
@@ -75,11 +84,14 @@ impl From<&[u8]> for U8x32 {
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct U8x33(#[serde(with = "serde_bytes")] [u8; 33]);
|
||||
impl From<&[u8]> for U8x33 {
|
||||
@@ -97,11 +109,14 @@ impl From<&[u8]> for U8x33 {
|
||||
DerefMut,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct U8x65(#[serde(with = "serde_bytes")] [u8; 65]);
|
||||
impl From<&[u8]> for U8x65 {
|
||||
|
||||
@@ -85,10 +85,9 @@ mod tx;
|
||||
mod txid;
|
||||
mod txidpath;
|
||||
mod txidprefix;
|
||||
mod txin;
|
||||
mod txindex;
|
||||
mod txinput;
|
||||
mod txoutput;
|
||||
mod txprevout;
|
||||
mod txout;
|
||||
mod txstatus;
|
||||
mod txversion;
|
||||
mod typeindex;
|
||||
@@ -182,10 +181,9 @@ pub use tx::*;
|
||||
pub use txid::*;
|
||||
pub use txidpath::*;
|
||||
pub use txidprefix::*;
|
||||
pub use txin::*;
|
||||
pub use txindex::*;
|
||||
pub use txinput::*;
|
||||
pub use txoutput::*;
|
||||
pub use txprevout::*;
|
||||
pub use txout::*;
|
||||
pub use txstatus::*;
|
||||
pub use txversion::*;
|
||||
pub use typeindex::*;
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x2;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2ABytes(U8x2);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x33;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2PK33Bytes(U8x33);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x65;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2PK65Bytes(U8x65);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2PKHBytes(U8x20);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2SHBytes(U8x20);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2TRBytes(U8x32);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x20;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2WPKHBytes(U8x20);
|
||||
|
||||
|
||||
@@ -7,7 +7,19 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
use crate::U8x32;
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Deref, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, Serialize,
|
||||
Debug,
|
||||
Clone,
|
||||
Deref,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
Serialize,
|
||||
Hash,
|
||||
)]
|
||||
pub struct P2WSHBytes(U8x32);
|
||||
|
||||
|
||||
@@ -1,43 +1,86 @@
|
||||
use crate::{RawLockTime, Sats, TxIndex, TxInput, TxOutput, TxStatus, TxVersion, Txid};
|
||||
use crate::{RawLockTime, Sats, TxIn, TxIndex, TxOut, TxStatus, TxVersion, Txid, Weight};
|
||||
use bitcoincore_rpc::Client;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::CheckedSub;
|
||||
|
||||
#[derive(Debug, Serialize, JsonSchema)]
|
||||
/// Transaction information compatible with mempool.space API format
|
||||
pub struct Tx {
|
||||
#[schemars(example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba")]
|
||||
pub txid: Txid,
|
||||
|
||||
pub struct Transaction {
|
||||
#[schemars(example = TxIndex::new(0))]
|
||||
pub index: TxIndex,
|
||||
pub index: Option<TxIndex>,
|
||||
|
||||
#[schemars(example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")]
|
||||
pub txid: Txid,
|
||||
|
||||
#[schemars(example = 2)]
|
||||
pub version: TxVersion,
|
||||
|
||||
#[schemars(example = 0)]
|
||||
pub locktime: RawLockTime,
|
||||
#[serde(rename = "locktime")]
|
||||
pub lock_time: RawLockTime,
|
||||
|
||||
/// Transaction size in bytes
|
||||
#[schemars(example = 222)]
|
||||
pub size: u32,
|
||||
#[serde(rename = "size")]
|
||||
pub total_size: usize,
|
||||
|
||||
/// Transaction weight (for SegWit transactions)
|
||||
/// Transaction weight
|
||||
#[schemars(example = 558)]
|
||||
pub weight: u32,
|
||||
pub weight: Weight,
|
||||
|
||||
/// Number of signature operations
|
||||
#[schemars(example = 1)]
|
||||
pub sigops: u32,
|
||||
#[serde(rename = "sigops")]
|
||||
pub total_sigop_cost: usize,
|
||||
|
||||
/// Transaction fee in satoshis
|
||||
#[schemars(example = Sats::new(31))]
|
||||
pub fee: Sats,
|
||||
|
||||
/// Transaction inputs
|
||||
pub vin: Vec<TxInput>,
|
||||
#[serde(rename = "vin")]
|
||||
pub input: Vec<TxIn>,
|
||||
|
||||
/// Transaction outputs
|
||||
pub vout: Vec<TxOutput>,
|
||||
#[serde(rename = "vout")]
|
||||
pub output: Vec<TxOut>,
|
||||
|
||||
pub status: TxStatus,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn fee(&self) -> Option<Sats> {
|
||||
let in_ = self
|
||||
.input
|
||||
.iter()
|
||||
.map(|txin| txin.prevout.as_ref().map(|txout| txout.value))
|
||||
.sum::<Option<Sats>>()?;
|
||||
let out = self.output.iter().map(|txout| txout.value).sum::<Sats>();
|
||||
Some(in_.checked_sub(out).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_mempool(tx: bitcoin::Transaction, rpc: &Client) -> Self {
|
||||
let mut this = Self {
|
||||
index: None,
|
||||
txid: tx.compute_txid().into(),
|
||||
version: tx.version.into(),
|
||||
total_sigop_cost: tx.total_sigop_cost(|_| None),
|
||||
weight: tx.weight().into(),
|
||||
lock_time: tx.lock_time.into(),
|
||||
total_size: tx.total_size(),
|
||||
fee: Sats::default(),
|
||||
input: tx
|
||||
.input
|
||||
.into_iter()
|
||||
.map(|txin| TxIn::from((txin, rpc)))
|
||||
.collect(),
|
||||
output: tx.output.into_iter().map(TxOut::from).collect(),
|
||||
status: TxStatus::UNCOMFIRMED,
|
||||
};
|
||||
this.fee = this.fee().unwrap_or_default();
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
/// Transaction ID (hash)
|
||||
#[derive(
|
||||
Debug, Deref, Clone, PartialEq, Eq, Immutable, IntoBytes, KnownLayout, FromBytes, JsonSchema,
|
||||
Debug,
|
||||
Deref,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Immutable,
|
||||
IntoBytes,
|
||||
KnownLayout,
|
||||
FromBytes,
|
||||
JsonSchema,
|
||||
Hash,
|
||||
)]
|
||||
#[schemars(
|
||||
example = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
|
||||
|
||||
118
crates/brk_structs/src/txin.rs
Normal file
118
crates/brk_structs/src/txin.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::{TxOut, Txid, Vout};
|
||||
use bitcoin::{Script, ScriptBuf};
|
||||
use bitcoincore_rpc::{Client, RpcApi};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
|
||||
#[derive(Debug, JsonSchema)]
|
||||
/// Transaction input
|
||||
pub struct TxIn {
|
||||
/// Transaction ID of the output being spent
|
||||
#[schemars(example = "0000000000000000000000000000000000000000000000000000000000000000")]
|
||||
pub txid: Txid,
|
||||
|
||||
#[schemars(example = 0)]
|
||||
pub vout: Vout,
|
||||
|
||||
/// Information about the previous output being spent
|
||||
#[schemars(example = None as Option<TxOut>)]
|
||||
pub prevout: Option<TxOut>,
|
||||
|
||||
/// Signature script (for non-SegWit inputs)
|
||||
#[schemars(
|
||||
rename = "scriptsig",
|
||||
with = "String",
|
||||
example = "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
|
||||
)]
|
||||
pub script_sig: ScriptBuf,
|
||||
|
||||
/// Signature script in assembly format
|
||||
#[allow(dead_code)]
|
||||
#[schemars(
|
||||
rename = "scriptsig_asm",
|
||||
with = "String",
|
||||
example = "OP_PUSHBYTES_4 ffff001d OP_PUSHBYTES_1 04 OP_PUSHBYTES_69 5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
|
||||
)]
|
||||
script_sig_asm: (),
|
||||
|
||||
// /// 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)
|
||||
#[allow(dead_code)]
|
||||
#[schemars(
|
||||
rename = "inner_redeemscript_asm",
|
||||
with = "Option<String>",
|
||||
example = Some("OP_0 OP_PUSHBYTES_20 992a1f7420fc5285070d19c71ff2efb1e356ad2f".to_string())
|
||||
)]
|
||||
inner_redeem_script_asm: (),
|
||||
}
|
||||
|
||||
impl TxIn {
|
||||
pub fn script_sig_asm(&self) -> String {
|
||||
self.script_sig.to_asm_string()
|
||||
}
|
||||
|
||||
pub fn redeem_script(&self) -> Option<&Script> {
|
||||
self.script_sig.redeem_script()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(bitcoin::TxIn, &Client)> for TxIn {
|
||||
fn from((txin, rpc): (bitcoin::TxIn, &Client)) -> Self {
|
||||
let txout_result = rpc
|
||||
.get_tx_out(
|
||||
&txin.previous_output.txid,
|
||||
txin.previous_output.vout,
|
||||
Some(true),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let is_coinbase = txout_result.as_ref().is_none_or(|r| r.coinbase);
|
||||
|
||||
// txin.witness
|
||||
|
||||
// txin.script_sig.redeem_script()
|
||||
|
||||
Self {
|
||||
is_coinbase,
|
||||
prevout: txout_result.map(TxOut::from),
|
||||
txid: txin.previous_output.txid.into(),
|
||||
vout: txin.previous_output.vout.into(),
|
||||
script_sig: txin.script_sig,
|
||||
script_sig_asm: (),
|
||||
sequence: txin.sequence.into(),
|
||||
// witness:
|
||||
inner_redeem_script_asm: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for TxIn {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("TxIn", 8)?;
|
||||
|
||||
state.serialize_field("txid", &self.txid)?;
|
||||
state.serialize_field("vout", &self.vout)?;
|
||||
state.serialize_field("prevout", &self.prevout)?;
|
||||
state.serialize_field("scriptsig", &self.script_sig.to_hex_string())?;
|
||||
state.serialize_field("scriptsig_asm", &self.script_sig_asm())?;
|
||||
state.serialize_field("is_coinbase", &self.is_coinbase)?;
|
||||
state.serialize_field("sequence", &self.sequence)?;
|
||||
state.serialize_field("inner_redeemscript_asm", &self.redeem_script())?;
|
||||
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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>,
|
||||
}
|
||||
102
crates/brk_structs/src/txout.rs
Normal file
102
crates/brk_structs/src/txout.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::{Address, AddressBytes, OutputType, Sats};
|
||||
use bitcoin::ScriptBuf;
|
||||
use bitcoincore_rpc::json::GetTxOutResult;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
|
||||
#[derive(Debug, JsonSchema)]
|
||||
/// Transaction output
|
||||
pub struct TxOut {
|
||||
/// Script pubkey (locking script)
|
||||
#[serde(
|
||||
rename = "scriptpubkey",
|
||||
serialize_with = "serialize_with_script_pubkey"
|
||||
)]
|
||||
#[schemars(
|
||||
with = "String",
|
||||
example = "00143b064c595a95f977f00352d6e917501267cacdc6"
|
||||
)]
|
||||
pub script_pubkey: ScriptBuf,
|
||||
|
||||
/// Script pubkey in assembly format
|
||||
#[allow(dead_code)]
|
||||
#[serde(skip, rename = "scriptpubkey_asm")]
|
||||
#[schemars(
|
||||
with = "String",
|
||||
example = "OP_0 OP_PUSHBYTES_20 3b064c595a95f977f00352d6e917501267cacdc6"
|
||||
)]
|
||||
script_pubkey_asm: (),
|
||||
|
||||
/// Script type (p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, etc.)
|
||||
#[allow(dead_code)]
|
||||
#[serde(skip, rename = "scriptpubkey_type")]
|
||||
#[schemars(with = "OutputType", example = &"v0_p2wpkh")]
|
||||
script_pubkey_type: (),
|
||||
|
||||
/// Bitcoin address (if applicable, None for OP_RETURN)
|
||||
#[allow(dead_code)]
|
||||
#[serde(skip, rename = "scriptpubkey_address")]
|
||||
#[schemars(with = "Option<Address>", example = Some("bc1q8vryck26jhuh0uqr2ttwj96szfnu4nwxfmu39y".to_string()))]
|
||||
script_pubkey_address: (),
|
||||
|
||||
/// Value of the output in satoshis
|
||||
#[schemars(example = Sats::new(7782))]
|
||||
pub value: Sats,
|
||||
}
|
||||
|
||||
impl TxOut {
|
||||
pub fn address(&self) -> Option<Address> {
|
||||
Address::try_from(&self.script_pubkey).ok()
|
||||
}
|
||||
|
||||
pub fn address_bytes(&self) -> Option<AddressBytes> {
|
||||
AddressBytes::try_from(&self.script_pubkey).ok()
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> OutputType {
|
||||
OutputType::from(&self.script_pubkey)
|
||||
}
|
||||
|
||||
pub fn script_pubkey_asm(&self) -> String {
|
||||
self.script_pubkey.to_asm_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::TxOut> for TxOut {
|
||||
fn from(txout: bitcoin::TxOut) -> Self {
|
||||
Self {
|
||||
script_pubkey: txout.script_pubkey,
|
||||
value: txout.value.into(),
|
||||
script_pubkey_asm: (),
|
||||
script_pubkey_address: (),
|
||||
script_pubkey_type: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetTxOutResult> for TxOut {
|
||||
fn from(value: GetTxOutResult) -> Self {
|
||||
Self {
|
||||
script_pubkey: value.script_pub_key.script().unwrap(),
|
||||
script_pubkey_address: (),
|
||||
script_pubkey_asm: (),
|
||||
script_pubkey_type: (),
|
||||
value: value.value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for TxOut {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("TxOut", 5)?;
|
||||
state.serialize_field("scriptpubkey", &self.script_pubkey.to_hex_string())?;
|
||||
state.serialize_field("scriptpubkey_asm", &self.script_pubkey_asm())?;
|
||||
state.serialize_field("scriptpubkey_type", &self.type_())?;
|
||||
state.serialize_field("scriptpubkey_address", &self.address())?;
|
||||
state.serialize_field("value", &self.value)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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,
|
||||
}
|
||||
@@ -22,3 +22,12 @@ pub struct TxStatus {
|
||||
#[schemars(example = Some(1759000868))]
|
||||
pub block_time: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl TxStatus {
|
||||
pub const UNCOMFIRMED: Self = Self {
|
||||
confirmed: false,
|
||||
block_hash: None,
|
||||
block_height: None,
|
||||
block_time: None,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use allocative::Allocative;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::StoredCompressed;
|
||||
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
@@ -22,6 +23,7 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
Serialize,
|
||||
StoredCompressed,
|
||||
Allocative,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct Weight(u64);
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ Contributions from the Bitcoin community are welcome! Here's how to get involved
|
||||
| [`brk_cli`](https://crates.io/crates/brk_cli) | Command line interface for running BRK instances |
|
||||
| [`brk_server`](https://crates.io/crates/brk_server) | REST API server for data access |
|
||||
| [`brk_mcp`](https://crates.io/crates/brk_mcp) | Model Context Protocol bridge for AI integration |
|
||||
| [`brk_parser`](https://crates.io/crates/brk_parser) | High-performance Bitcoin block parser |
|
||||
| [`brk_reader`](https://crates.io/crates/brk_reader) | High-performance Bitcoin block parser |
|
||||
| [`brk_indexer`](https://crates.io/crates/brk_indexer) | Blockchain data indexing engine |
|
||||
| [`brk_computer`](https://crates.io/crates/brk_computer) | Bitcoin metrics and dataset computation |
|
||||
| [`brk_interface`](https://crates.io/crates/brk_interface) | Data formatting and query interface |
|
||||
|
||||
Reference in New Issue
Block a user