mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -782,8 +782,10 @@ name = "brk_monitor"
|
||||
version = "0.0.111"
|
||||
dependencies = [
|
||||
"bitcoin",
|
||||
"brk_error",
|
||||
"brk_rpc",
|
||||
"brk_structs",
|
||||
"derive_deref",
|
||||
"log",
|
||||
"parking_lot 0.12.5",
|
||||
"rustc-hash",
|
||||
|
||||
@@ -40,6 +40,7 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let db = Database::open(&parent_path.join("constants"))?;
|
||||
db.set_min_len(PAGE_SIZE * 10_000_000)?;
|
||||
|
||||
let version = parent_version + Version::ZERO;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ pub struct Vecs {
|
||||
impl Vecs {
|
||||
pub fn forced_import(parent: &Path, fetcher: Fetcher, version: Version) -> Result<Self> {
|
||||
let db = Database::open(&parent.join("fetched"))?;
|
||||
db.set_min_len(PAGE_SIZE * 1_000_000)?;
|
||||
|
||||
let this = Self {
|
||||
fetcher,
|
||||
|
||||
@@ -22,6 +22,7 @@ pub enum Error {
|
||||
BitcoinBip34Error(bitcoin::block::Bip34Error),
|
||||
BitcoinHexError(bitcoin::consensus::encode::FromHexError),
|
||||
BitcoinFromScriptError(bitcoin::address::FromScriptError),
|
||||
BitcoinHexToArrayError(bitcoin::hex::HexToArrayError),
|
||||
SonicRS(sonic_rs::Error),
|
||||
ZeroCopyError,
|
||||
Vecs(vecdb::Error),
|
||||
@@ -60,6 +61,12 @@ impl From<bitcoin::consensus::encode::FromHexError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::hex::HexToArrayError> for Error {
|
||||
fn from(value: bitcoin::hex::HexToArrayError) -> Self {
|
||||
Self::BitcoinHexToArrayError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::address::FromScriptError> for Error {
|
||||
fn from(value: bitcoin::address::FromScriptError) -> Self {
|
||||
Self::BitcoinFromScriptError(value)
|
||||
@@ -151,6 +158,7 @@ impl fmt::Display for Error {
|
||||
Error::BitcoinBip34Error(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinFromScriptError(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinHexError(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinHexToArrayError(error) => Display::fmt(&error, f),
|
||||
Error::BitcoinRPC(error) => Display::fmt(&error, f),
|
||||
Error::FjallV2(error) => Display::fmt(&error, f),
|
||||
// Error::FjallV3(error) => Display::fmt(&error, f),
|
||||
|
||||
@@ -119,6 +119,8 @@ impl Indexer {
|
||||
let mut same_block_output_info: FxHashMap<OutPoint, (OutputType, TypeIndex)> =
|
||||
FxHashMap::default();
|
||||
|
||||
// TODO: CHECK PREV HASH
|
||||
|
||||
for block in reader.read(start, end).iter() {
|
||||
// let i_tot = Instant::now();
|
||||
already_added_addressbyteshash.clear();
|
||||
|
||||
@@ -1,7 +1,38 @@
|
||||
use brk_iterator::BlockIterator;
|
||||
use brk_reader::Reader;
|
||||
use std::{path::Path, time::Instant};
|
||||
|
||||
fn main() {
|
||||
let reader = Reader::new(blocks_dir, rpc);
|
||||
BlockIterator::last(10).reader(reader, client);
|
||||
use brk_error::Result;
|
||||
use brk_iterator::Blocks;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::{Auth, Client};
|
||||
use brk_structs::Height;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let bitcoin_dir = Path::new(&std::env::var("HOME").unwrap())
|
||||
.join("Library")
|
||||
.join("Application Support")
|
||||
.join("Bitcoin");
|
||||
|
||||
let client = Client::new(
|
||||
"http://localhost:8332",
|
||||
Auth::CookieFile(bitcoin_dir.join(".cookie")),
|
||||
)?;
|
||||
|
||||
let reader = Reader::new(bitcoin_dir.join("blocks"), client.clone());
|
||||
|
||||
let blocks = Blocks::new(client, reader);
|
||||
|
||||
let i = Instant::now();
|
||||
blocks
|
||||
.range(Height::new(920040), Height::new(920041))?
|
||||
// .start(Height::new(920040))?
|
||||
// .end(Height::new(10))?
|
||||
// .after(brk_structs::BlockHash::try_from(
|
||||
// "00000000000000000000840d205cac2728740e0e7c5dc92a04c52503017c6241",
|
||||
// )?)?
|
||||
.for_each(|b| {
|
||||
dbg!(b.height());
|
||||
});
|
||||
dbg!(i.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::Client;
|
||||
use brk_structs::Height;
|
||||
|
||||
use crate::{BlockIterator, BlockRange, Source};
|
||||
|
||||
pub struct BlockIteratorBuilder {
|
||||
range: BlockRange,
|
||||
}
|
||||
|
||||
impl BlockIteratorBuilder {
|
||||
pub fn new(range: BlockRange) -> Self {
|
||||
Self { range }
|
||||
}
|
||||
|
||||
/// Build with automatic source selection (≤10 blocks = RPC, >10 = Reader)
|
||||
pub fn smart(self, reader: &Reader, client: Client) -> Result<BlockIterator> {
|
||||
let (start, end) = self.resolve_range(&client)?;
|
||||
let count = end.saturating_sub(*start) + 1;
|
||||
|
||||
let source = if count <= 10 {
|
||||
Source::new_rpc(client, start, end)
|
||||
} else {
|
||||
Source::Reader {
|
||||
receiver: reader.read(Some(start), Some(end)),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(BlockIterator { source })
|
||||
}
|
||||
|
||||
/// Build using RPC source
|
||||
pub fn rpc(self, client: Client) -> Result<BlockIterator> {
|
||||
let (start, end) = self.resolve_range(&client)?;
|
||||
Ok(BlockIterator::from(Source::new_rpc(client, start, end)))
|
||||
}
|
||||
|
||||
/// Build using Reader source
|
||||
pub fn reader(self, reader: &crate::Reader, client: Client) -> Result<BlockIterator> {
|
||||
let (start, end) = self.resolve_range(&client)?;
|
||||
Ok(BlockIterator::from(Source::Reader {
|
||||
receiver: reader.read(Some(start), Some(end)),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Resolve the range to concrete start/end heights
|
||||
fn resolve_range(&self, client: &Client) -> Result<(Height, Height)> {
|
||||
match self.range {
|
||||
BlockRange::Span { start, end } => Ok((start, end)),
|
||||
BlockRange::Start { start } => {
|
||||
let end = Height::new(client.get_block_count()? as u32);
|
||||
Ok((start, end))
|
||||
}
|
||||
BlockRange::End { end } => Ok((Height::ZERO, end)),
|
||||
BlockRange::Last { n } => {
|
||||
let end = Height::new(client.get_block_count()? as u32);
|
||||
let start = Height::new((*end).saturating_sub(n - 1));
|
||||
Ok((start, end))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockRange> for BlockIteratorBuilder {
|
||||
fn from(range: BlockRange) -> Self {
|
||||
Self { range }
|
||||
}
|
||||
}
|
||||
55
crates/brk_iterator/src/iterator.rs
Normal file
55
crates/brk_iterator/src/iterator.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use brk_structs::Block;
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub struct BlockIterator(State);
|
||||
|
||||
impl BlockIterator {
|
||||
pub fn new(state: State) -> Self {
|
||||
Self(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BlockIterator {
|
||||
type Item = Block;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match &mut self.0 {
|
||||
State::Rpc {
|
||||
client,
|
||||
heights,
|
||||
prev_hash,
|
||||
} => {
|
||||
let height = heights.next()?;
|
||||
let hash = client.get_block_hash(height).ok()?;
|
||||
let block = client.get_block(&hash).ok()?;
|
||||
|
||||
if prev_hash
|
||||
.as_ref()
|
||||
.is_some_and(|prev_hash| block.header.prev_blockhash != prev_hash.into())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
prev_hash.replace(hash.clone());
|
||||
|
||||
Some(Block::from((height, hash, block)))
|
||||
}
|
||||
State::Reader {
|
||||
receiver,
|
||||
after_hash,
|
||||
} => {
|
||||
let block = Block::from(receiver.recv().ok()?);
|
||||
|
||||
// Only validate the first block (Reader validates the rest)
|
||||
if let Some(expected_prev) = after_hash.take()
|
||||
&& block.header.prev_blockhash != expected_prev.into()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,119 @@
|
||||
use brk_reader::Reader;
|
||||
use brk_structs::{Block, Height};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod builder;
|
||||
use brk_error::Result;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::Client;
|
||||
use brk_structs::{BlockHash, Height};
|
||||
|
||||
mod iterator;
|
||||
mod range;
|
||||
mod source;
|
||||
mod state;
|
||||
|
||||
use builder::*;
|
||||
use iterator::*;
|
||||
use range::*;
|
||||
use source::*;
|
||||
use state::*;
|
||||
|
||||
/// Block iterator that can use either RPC or Reader
|
||||
pub struct BlockIterator {
|
||||
source: Source,
|
||||
}
|
||||
///
|
||||
/// Block iterator factory
|
||||
///
|
||||
/// Creates iterators over Bitcoin blocks from various sources (RPC/Reader).
|
||||
/// Iterators may end earlier than expected if a chain reorganization occurs.
|
||||
///
|
||||
/// Thread-safe and free to clone.
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct Blocks(Arc<Source>);
|
||||
|
||||
impl From<Source> for BlockIterator {
|
||||
fn from(source: Source) -> Self {
|
||||
Self { source }
|
||||
impl Blocks {
|
||||
/// Create with smart mode (auto-select source based on range size)
|
||||
pub fn new(client: Client, reader: Reader) -> Self {
|
||||
Self::new_inner(Source::Smart { client, reader })
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BlockIterator {
|
||||
type Item = Block;
|
||||
/// Create with RPC-only mode
|
||||
pub fn new_rpc(client: Client) -> Self {
|
||||
Self::new_inner(Source::Rpc { client })
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match &mut self.source {
|
||||
Source::Rpc {
|
||||
client,
|
||||
heights,
|
||||
prev_hash,
|
||||
} => {
|
||||
let height = heights.next()?;
|
||||
/// Create with Reader-only mode
|
||||
pub fn new_reader(reader: Reader) -> Self {
|
||||
Self::new_inner(Source::Reader { reader })
|
||||
}
|
||||
|
||||
let Ok(hash) = client.get_block_hash(height) else {
|
||||
return None;
|
||||
};
|
||||
fn new_inner(source: Source) -> Self {
|
||||
Self(Arc::new(source))
|
||||
}
|
||||
|
||||
let Ok(block) = client.get_block(&hash) else {
|
||||
return None;
|
||||
};
|
||||
/// Iterate over a specific range (start..=end)
|
||||
pub fn range(&self, start: Height, end: Height) -> Result<BlockIterator> {
|
||||
self.iter(BlockRange::Span { start, end })
|
||||
}
|
||||
|
||||
if prev_hash
|
||||
.as_ref()
|
||||
.is_some_and(|prev_hash| block.header.prev_blockhash != prev_hash.into())
|
||||
{
|
||||
return None;
|
||||
/// Iterate from start (inclusive) to chain tip
|
||||
pub fn start(&self, start: Height) -> Result<BlockIterator> {
|
||||
self.iter(BlockRange::Start { start })
|
||||
}
|
||||
|
||||
/// Iterate from genesis to end (inclusive)
|
||||
pub fn end(&self, end: Height) -> Result<BlockIterator> {
|
||||
self.iter(BlockRange::End { end })
|
||||
}
|
||||
|
||||
/// Iterate over last n blocks
|
||||
pub fn last(&self, n: u32) -> Result<BlockIterator> {
|
||||
self.iter(BlockRange::Last { n })
|
||||
}
|
||||
|
||||
/// Iterate after hash
|
||||
pub fn after(&self, hash: BlockHash) -> Result<BlockIterator> {
|
||||
self.iter(BlockRange::After { hash })
|
||||
}
|
||||
|
||||
fn iter(&self, range: BlockRange) -> Result<BlockIterator> {
|
||||
let (start, end, hash_opt) = self.resolve_range(range)?;
|
||||
|
||||
let count = end.saturating_sub(*start) + 1;
|
||||
|
||||
let state = match &*self.0 {
|
||||
Source::Smart { client, reader } => {
|
||||
if count <= 10 {
|
||||
State::new_rpc(client.clone(), start, end, hash_opt)
|
||||
} else {
|
||||
State::new_reader(reader.clone(), start, end, hash_opt)
|
||||
}
|
||||
|
||||
Some(Block::from((height, hash, block)))
|
||||
}
|
||||
Source::Reader { receiver } => receiver.recv().ok().map(|b| b.unwrap()),
|
||||
Source::Rpc { client } => State::new_rpc(client.clone(), start, end, hash_opt),
|
||||
Source::Reader { reader, .. } => {
|
||||
State::new_reader(reader.clone(), start, end, hash_opt)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(BlockIterator::new(state))
|
||||
}
|
||||
|
||||
fn resolve_range(&self, range: BlockRange) -> Result<(Height, Height, Option<BlockHash>)> {
|
||||
let client = self.0.client();
|
||||
|
||||
match range {
|
||||
BlockRange::Span { start, end } => Ok((start, end, None)),
|
||||
BlockRange::Start { start } => {
|
||||
let end = client.get_last_height()?;
|
||||
Ok((start, end, None))
|
||||
}
|
||||
BlockRange::End { end } => Ok((Height::ZERO, end, None)),
|
||||
BlockRange::Last { n } => {
|
||||
let end = client.get_last_height()?;
|
||||
let start = Height::new((*end).saturating_sub(n - 1));
|
||||
Ok((start, end, None))
|
||||
}
|
||||
BlockRange::After { hash } => {
|
||||
let block_info = client.get_block_header_info(&hash)?;
|
||||
let start = (block_info.height + 1).into();
|
||||
let end = client.get_last_height()?;
|
||||
Ok((start, end, Some(hash)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockIterator {
|
||||
pub fn range(start: Height, end: Height) -> BlockIteratorBuilder {
|
||||
BlockIteratorBuilder::from(BlockRange::Span { start, end })
|
||||
}
|
||||
|
||||
pub fn start(start: Height) -> BlockIteratorBuilder {
|
||||
BlockIteratorBuilder::from(BlockRange::Start { start })
|
||||
}
|
||||
|
||||
pub fn end(end: Height) -> BlockIteratorBuilder {
|
||||
BlockIteratorBuilder::from(BlockRange::End { end })
|
||||
}
|
||||
|
||||
pub fn last(n: u32) -> BlockIteratorBuilder {
|
||||
BlockIteratorBuilder::from(BlockRange::Last { n })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use brk_structs::Height;
|
||||
use brk_structs::{BlockHash, Height};
|
||||
|
||||
pub enum BlockRange {
|
||||
Span { start: Height, end: Height },
|
||||
Start { start: Height },
|
||||
End { end: Height },
|
||||
Last { n: u32 },
|
||||
After { hash: BlockHash },
|
||||
}
|
||||
|
||||
@@ -1,44 +1,22 @@
|
||||
use std::vec;
|
||||
|
||||
use brk_reader::Receiver;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::Client;
|
||||
use brk_structs::{BlockHash, Height, ReadBlock};
|
||||
|
||||
/// Source configuration for block iteration
|
||||
pub enum Source {
|
||||
Rpc {
|
||||
client: Client,
|
||||
heights: vec::IntoIter<Height>,
|
||||
prev_hash: Option<BlockHash>,
|
||||
},
|
||||
Reader {
|
||||
receiver: Receiver<ReadBlock>,
|
||||
},
|
||||
/// Automatic selection based on range
|
||||
Smart { client: Client, reader: Reader },
|
||||
/// Always use RPC
|
||||
Rpc { client: Client },
|
||||
/// Always use Reader
|
||||
Reader { reader: Reader },
|
||||
}
|
||||
|
||||
impl Source {
|
||||
pub fn new_rpc(client: Client, start: Height, end: Height) -> Self {
|
||||
let heights = (*start..=*end)
|
||||
.map(Height::new)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter();
|
||||
|
||||
Self::Rpc {
|
||||
client,
|
||||
heights,
|
||||
prev_hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_reader(client: Client, start: Height, end: Height) -> Self {
|
||||
let heights = (*start..=*end)
|
||||
.map(Height::new)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter();
|
||||
|
||||
Self::Rpc {
|
||||
client,
|
||||
heights,
|
||||
prev_hash: None,
|
||||
pub fn client(&self) -> &Client {
|
||||
match self {
|
||||
Source::Smart { client, .. } => client,
|
||||
Source::Rpc { client } => client,
|
||||
Source::Reader { reader } => reader.client(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
crates/brk_iterator/src/state.rs
Normal file
49
crates/brk_iterator/src/state.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::vec;
|
||||
|
||||
use brk_reader::{Reader, Receiver};
|
||||
use brk_rpc::Client;
|
||||
use brk_structs::{BlockHash, Height, ReadBlock};
|
||||
|
||||
pub enum State {
|
||||
Rpc {
|
||||
client: Client,
|
||||
heights: vec::IntoIter<Height>,
|
||||
prev_hash: Option<BlockHash>,
|
||||
},
|
||||
Reader {
|
||||
receiver: Receiver<ReadBlock>,
|
||||
after_hash: Option<BlockHash>,
|
||||
},
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new_rpc(
|
||||
client: Client,
|
||||
start: Height,
|
||||
end: Height,
|
||||
prev_hash: Option<BlockHash>,
|
||||
) -> Self {
|
||||
let heights = (*start..=*end)
|
||||
.map(Height::new)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter();
|
||||
|
||||
Self::Rpc {
|
||||
client,
|
||||
heights,
|
||||
prev_hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_reader(
|
||||
reader: Reader,
|
||||
start: Height,
|
||||
end: Height,
|
||||
after_hash: Option<BlockHash>,
|
||||
) -> Self {
|
||||
State::Reader {
|
||||
receiver: reader.read(Some(start), Some(end)),
|
||||
after_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,10 @@ build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
bitcoin = { workspace = true }
|
||||
brk_error = { workspace = true }
|
||||
brk_rpc = { workspace = true }
|
||||
brk_structs = { workspace = true }
|
||||
derive_deref = { workspace = true }
|
||||
log = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::{path::Path, sync::Arc, thread, time::Duration};
|
||||
use std::{path::Path, thread, time::Duration};
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_monitor::Mempool;
|
||||
use brk_rpc::{Auth, Client};
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<()> {
|
||||
// Connect to Bitcoin Core
|
||||
let bitcoin_dir = Path::new(&std::env::var("HOME").unwrap())
|
||||
.join("Library")
|
||||
@@ -10,18 +12,14 @@ fn main() {
|
||||
.join("Bitcoin");
|
||||
// let bitcoin_dir = Path::new("/Volumes/WD_BLACK/bitcoin");
|
||||
|
||||
let rpc = Box::leak(Box::new(
|
||||
bitcoincore_rpc::Client::new(
|
||||
"http://localhost:8332",
|
||||
bitcoincore_rpc::Auth::CookieFile(bitcoin_dir.join(".cookie")),
|
||||
)
|
||||
.unwrap(),
|
||||
));
|
||||
let client = Client::new(
|
||||
"http://localhost:8332",
|
||||
Auth::CookieFile(bitcoin_dir.join(".cookie")),
|
||||
)?;
|
||||
|
||||
let mempool = Arc::new(Mempool::new(rpc));
|
||||
let mempool = Mempool::new(client);
|
||||
|
||||
// Spawn monitoring thread
|
||||
let mempool_clone = Arc::clone(&mempool);
|
||||
let mempool_clone = mempool.clone();
|
||||
thread::spawn(move || {
|
||||
mempool_clone.start();
|
||||
});
|
||||
@@ -34,4 +32,6 @@ fn main() {
|
||||
let addresses = mempool.get_addresses();
|
||||
println!("mempool_address_count: {}", addresses.len());
|
||||
}
|
||||
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
use std::{thread, time::Duration};
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
|
||||
use bitcoin::consensus::encode;
|
||||
use brk_error::Result;
|
||||
use brk_rpc::Client;
|
||||
use brk_structs::{AddressBytes, AddressMempoolStats, Transaction, Txid};
|
||||
use derive_deref::Deref;
|
||||
use log::error;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
const MAX_FETCHES_PER_CYCLE: usize = 10_000;
|
||||
|
||||
pub struct Mempool {
|
||||
///
|
||||
/// Mempool monitor
|
||||
///
|
||||
/// Thread safe and free to clone
|
||||
///
|
||||
#[derive(Clone, Deref)]
|
||||
pub struct Mempool(Arc<MempoolInner>);
|
||||
|
||||
impl Mempool {
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self(Arc::new(MempoolInner::new(client)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MempoolInner {
|
||||
client: Client,
|
||||
txs: RwLock<FxHashMap<Txid, Transaction>>,
|
||||
addresses: RwLock<FxHashMap<AddressBytes, (AddressMempoolStats, FxHashSet<Txid>)>>,
|
||||
}
|
||||
|
||||
impl Mempool {
|
||||
impl MempoolInner {
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self {
|
||||
client,
|
||||
@@ -34,6 +49,7 @@ impl Mempool {
|
||||
self.addresses.read()
|
||||
}
|
||||
|
||||
/// Start an infinite update loop with a 1 second interval
|
||||
pub fn start(&self) {
|
||||
loop {
|
||||
if let Err(e) = self.update() {
|
||||
@@ -43,12 +59,11 @@ impl Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn update(&self) -> Result<()> {
|
||||
let txids = self
|
||||
.client
|
||||
.get_raw_mempool()?
|
||||
.into_iter()
|
||||
.map(Txid::from)
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let new_txs = {
|
||||
@@ -61,18 +76,12 @@ impl Mempool {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
.into_iter()
|
||||
.filter_map(|txid| {
|
||||
self.client
|
||||
.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.client))
|
||||
.map(|tx| (txid, tx))
|
||||
})
|
||||
.filter_map(|txid| self.client.get_transaction(&txid).ok().map(|tx| (txid, tx)))
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
|
||||
let mut txs = self.txs.write();
|
||||
let mut addresses = self.addresses.write();
|
||||
|
||||
txs.retain(|txid, tx| {
|
||||
if txids.contains(txid) {
|
||||
return true;
|
||||
@@ -98,6 +107,7 @@ impl Mempool {
|
||||
});
|
||||
false
|
||||
});
|
||||
|
||||
new_txs.iter().for_each(|(txid, tx)| {
|
||||
tx.input
|
||||
.iter()
|
||||
|
||||
@@ -32,7 +32,7 @@ impl AnyBlock {
|
||||
|
||||
let header = Header::consensus_decode(&mut cursor)?;
|
||||
|
||||
let hash = header.block_hash().into();
|
||||
let hash = header.block_hash();
|
||||
|
||||
let tx_count = VarInt::consensus_decode(&mut cursor)?.0;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use brk_rpc::Client;
|
||||
use brk_structs::{BlkMetadata, BlkPosition, BlockHash, Height, ReadBlock};
|
||||
pub use crossbeam::channel::Receiver;
|
||||
use crossbeam::channel::bounded;
|
||||
use derive_deref::Deref;
|
||||
use log::error;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use rayon::prelude::*;
|
||||
@@ -35,15 +36,30 @@ pub use xor_index::*;
|
||||
const MAGIC_BYTES: [u8; 4] = [249, 190, 180, 217];
|
||||
const BOUND_CAP: usize = 50;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Reader {
|
||||
///
|
||||
/// Bitcoin BLK file reader
|
||||
///
|
||||
/// Thread safe and free to clone
|
||||
///
|
||||
///
|
||||
#[derive(Debug, Clone, Deref)]
|
||||
pub struct Reader(Arc<ReaderInner>);
|
||||
|
||||
impl Reader {
|
||||
pub fn new(blocks_dir: PathBuf, client: Client) -> Self {
|
||||
Self(Arc::new(ReaderInner::new(blocks_dir, client)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReaderInner {
|
||||
blk_index_to_blk_path: Arc<RwLock<BlkIndexToBlkPath>>,
|
||||
xor_bytes: XORBytes,
|
||||
blocks_dir: PathBuf,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl Reader {
|
||||
impl ReaderInner {
|
||||
pub fn new(blocks_dir: PathBuf, client: Client) -> Self {
|
||||
Self {
|
||||
xor_bytes: XORBytes::from(blocks_dir.as_path()),
|
||||
@@ -55,6 +71,10 @@ impl Reader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.client
|
||||
}
|
||||
|
||||
pub fn blk_index_to_blk_path(&self) -> RwLockReadGuard<'_, BlkIndexToBlkPath> {
|
||||
self.blk_index_to_blk_path.read()
|
||||
}
|
||||
@@ -227,6 +247,10 @@ impl Reader {
|
||||
}
|
||||
|
||||
current_height.increment();
|
||||
|
||||
if end.is_some_and(|end| end == current_height) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
@@ -343,8 +367,4 @@ impl Reader {
|
||||
|
||||
Ok(Height::new(height))
|
||||
}
|
||||
|
||||
pub fn static_clone(&self) -> &'static Self {
|
||||
Box::leak(Box::new(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use inner::ClientInner;
|
||||
///
|
||||
/// Bitcoin Core RPC Client
|
||||
///
|
||||
/// Free to clone (Arc)
|
||||
/// Thread safe and free to clone
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client(Arc<ClientInner>);
|
||||
@@ -39,7 +39,10 @@ impl Client {
|
||||
)?)))
|
||||
}
|
||||
|
||||
pub fn get_block(&self, hash: &BlockHash) -> Result<bitcoin::Block> {
|
||||
pub fn get_block<'a, H>(&self, hash: &'a H) -> Result<bitcoin::Block>
|
||||
where
|
||||
&'a H: Into<&'a bitcoin::BlockHash>,
|
||||
{
|
||||
self.call(|c| c.get_block(hash.into())).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -51,7 +54,10 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Get block hash at a given height
|
||||
pub fn get_block_hash(&self, height: Height) -> Result<BlockHash> {
|
||||
pub fn get_block_hash<H>(&self, height: H) -> Result<BlockHash>
|
||||
where
|
||||
H: Into<u64> + Copy,
|
||||
{
|
||||
self.call(|c| c.get_block_hash(height.into()))
|
||||
.map(BlockHash::from)
|
||||
.map_err(Into::into)
|
||||
@@ -65,13 +71,19 @@ impl Client {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_block_header_info(&self, hash: &BlockHash) -> Result<GetBlockHeaderResult> {
|
||||
pub fn get_block_header_info<'a, H>(&self, hash: &'a H) -> Result<GetBlockHeaderResult>
|
||||
where
|
||||
&'a H: Into<&'a bitcoin::BlockHash>,
|
||||
{
|
||||
self.call(|c| c.get_block_header_info(hash.into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_transaction(&self, txid: Txid) -> Result<Transaction> {
|
||||
let mut tx = self.get_raw_transaction(&txid, None)?;
|
||||
pub fn get_transaction<'a, T>(&self, txid: &'a T) -> Result<Transaction>
|
||||
where
|
||||
&'a T: Into<&'a bitcoin::Txid>,
|
||||
{
|
||||
let mut tx = self.get_raw_transaction(txid, None as Option<&'a BlockHash>)?;
|
||||
|
||||
let input = mem::take(&mut tx.input)
|
||||
.into_iter()
|
||||
@@ -142,21 +154,29 @@ impl Client {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_raw_transaction(
|
||||
pub fn get_raw_transaction<'a, T, H>(
|
||||
&self,
|
||||
txid: &Txid,
|
||||
block_hash: Option<&BlockHash>,
|
||||
) -> brk_error::Result<bitcoin::Transaction> {
|
||||
txid: &'a T,
|
||||
block_hash: Option<&'a H>,
|
||||
) -> brk_error::Result<bitcoin::Transaction>
|
||||
where
|
||||
&'a T: Into<&'a bitcoin::Txid>,
|
||||
&'a H: Into<&'a bitcoin::BlockHash>,
|
||||
{
|
||||
let hex = self.get_raw_transaction_hex(txid, block_hash)?;
|
||||
let tx = encode::deserialize_hex::<bitcoin::Transaction>(&hex)?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
pub fn get_raw_transaction_hex(
|
||||
pub fn get_raw_transaction_hex<'a, T, H>(
|
||||
&self,
|
||||
txid: &Txid,
|
||||
block_hash: Option<&BlockHash>,
|
||||
) -> Result<String> {
|
||||
txid: &'a T,
|
||||
block_hash: Option<&'a H>,
|
||||
) -> Result<String>
|
||||
where
|
||||
&'a T: Into<&'a bitcoin::Txid>,
|
||||
&'a H: Into<&'a bitcoin::BlockHash>,
|
||||
{
|
||||
self.call(|c| c.get_raw_transaction_hex(txid.into(), block_hash.map(|h| h.into())))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ impl From<(Height, BlockHash, bitcoin::Block)> for Block {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReadBlock> for Block {
|
||||
fn from(value: ReadBlock) -> Self {
|
||||
value.block
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Block {
|
||||
type Target = bitcoin::Block;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -89,7 +95,7 @@ impl ReadBlock {
|
||||
&self.tx_metadata
|
||||
}
|
||||
|
||||
pub fn unwrap(self) -> Block {
|
||||
pub fn inner(self) -> Block {
|
||||
self.block
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{fmt, mem};
|
||||
use std::{fmt, mem, str::FromStr};
|
||||
|
||||
use bitcoin::hashes::Hash;
|
||||
use brk_error::Error;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Serialize, Serializer};
|
||||
@@ -13,6 +14,13 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
#[repr(C)]
|
||||
pub struct BlockHash([u8; 32]);
|
||||
|
||||
impl TryFrom<&str> for BlockHash {
|
||||
type Error = Error;
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
Ok(Self::from(bitcoin::BlockHash::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bitcoin::BlockHash> for BlockHash {
|
||||
fn from(value: bitcoin::BlockHash) -> Self {
|
||||
unsafe { mem::transmute(value) }
|
||||
|
||||
Reference in New Issue
Block a user