diff --git a/Cargo.lock b/Cargo.lock index 7cf837a67..1f0ba272d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/brk_computer/src/constants.rs b/crates/brk_computer/src/constants.rs index 9ac3a050e..5de350f88 100644 --- a/crates/brk_computer/src/constants.rs +++ b/crates/brk_computer/src/constants.rs @@ -40,6 +40,7 @@ impl Vecs { indexes: &indexes::Vecs, ) -> Result { let db = Database::open(&parent_path.join("constants"))?; + db.set_min_len(PAGE_SIZE * 10_000_000)?; let version = parent_version + Version::ZERO; diff --git a/crates/brk_computer/src/fetched.rs b/crates/brk_computer/src/fetched.rs index 63890a934..4ce31d97d 100644 --- a/crates/brk_computer/src/fetched.rs +++ b/crates/brk_computer/src/fetched.rs @@ -24,6 +24,7 @@ pub struct Vecs { impl Vecs { pub fn forced_import(parent: &Path, fetcher: Fetcher, version: Version) -> Result { let db = Database::open(&parent.join("fetched"))?; + db.set_min_len(PAGE_SIZE * 1_000_000)?; let this = Self { fetcher, diff --git a/crates/brk_error/src/lib.rs b/crates/brk_error/src/lib.rs index c8c3db891..32207b57c 100644 --- a/crates/brk_error/src/lib.rs +++ b/crates/brk_error/src/lib.rs @@ -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 for Error { } } +impl From for Error { + fn from(value: bitcoin::hex::HexToArrayError) -> Self { + Self::BitcoinHexToArrayError(value) + } +} + impl From 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), diff --git a/crates/brk_indexer/src/lib.rs b/crates/brk_indexer/src/lib.rs index 07dcb8373..36dcf470e 100644 --- a/crates/brk_indexer/src/lib.rs +++ b/crates/brk_indexer/src/lib.rs @@ -119,6 +119,8 @@ impl Indexer { let mut same_block_output_info: FxHashMap = FxHashMap::default(); + // TODO: CHECK PREV HASH + for block in reader.read(start, end).iter() { // let i_tot = Instant::now(); already_added_addressbyteshash.clear(); diff --git a/crates/brk_iterator/examples/iterator.rs b/crates/brk_iterator/examples/iterator.rs index d79b22606..067147a5e 100644 --- a/crates/brk_iterator/examples/iterator.rs +++ b/crates/brk_iterator/examples/iterator.rs @@ -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(()) } diff --git a/crates/brk_iterator/src/builder.rs b/crates/brk_iterator/src/builder.rs deleted file mode 100644 index b2be39ef9..000000000 --- a/crates/brk_iterator/src/builder.rs +++ /dev/null @@ -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 { - 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 { - 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 { - 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 for BlockIteratorBuilder { - fn from(range: BlockRange) -> Self { - Self { range } - } -} diff --git a/crates/brk_iterator/src/iterator.rs b/crates/brk_iterator/src/iterator.rs new file mode 100644 index 000000000..f5e8d953c --- /dev/null +++ b/crates/brk_iterator/src/iterator.rs @@ -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 { + 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) + } + } + } +} diff --git a/crates/brk_iterator/src/lib.rs b/crates/brk_iterator/src/lib.rs index 17ac56526..96f0df0d8 100644 --- a/crates/brk_iterator/src/lib.rs +++ b/crates/brk_iterator/src/lib.rs @@ -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); -impl From 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 { - 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 { + 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 { + self.iter(BlockRange::Start { start }) + } + + /// Iterate from genesis to end (inclusive) + pub fn end(&self, end: Height) -> Result { + self.iter(BlockRange::End { end }) + } + + /// Iterate over last n blocks + pub fn last(&self, n: u32) -> Result { + self.iter(BlockRange::Last { n }) + } + + /// Iterate after hash + pub fn after(&self, hash: BlockHash) -> Result { + self.iter(BlockRange::After { hash }) + } + + fn iter(&self, range: BlockRange) -> Result { + 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)> { + 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 }) - } -} diff --git a/crates/brk_iterator/src/range.rs b/crates/brk_iterator/src/range.rs index c957e5348..19413be41 100644 --- a/crates/brk_iterator/src/range.rs +++ b/crates/brk_iterator/src/range.rs @@ -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 }, } diff --git a/crates/brk_iterator/src/source.rs b/crates/brk_iterator/src/source.rs index ef0711bb7..88f51848e 100644 --- a/crates/brk_iterator/src/source.rs +++ b/crates/brk_iterator/src/source.rs @@ -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, - prev_hash: Option, - }, - Reader { - receiver: Receiver, - }, + /// 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::>() - .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::>() - .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(), } } } diff --git a/crates/brk_iterator/src/state.rs b/crates/brk_iterator/src/state.rs new file mode 100644 index 000000000..6d55f207d --- /dev/null +++ b/crates/brk_iterator/src/state.rs @@ -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, + prev_hash: Option, + }, + Reader { + receiver: Receiver, + after_hash: Option, + }, +} + +impl State { + pub fn new_rpc( + client: Client, + start: Height, + end: Height, + prev_hash: Option, + ) -> Self { + let heights = (*start..=*end) + .map(Height::new) + .collect::>() + .into_iter(); + + Self::Rpc { + client, + heights, + prev_hash, + } + } + + pub fn new_reader( + reader: Reader, + start: Height, + end: Height, + after_hash: Option, + ) -> Self { + State::Reader { + receiver: reader.read(Some(start), Some(end)), + after_hash, + } + } +} diff --git a/crates/brk_monitor/Cargo.toml b/crates/brk_monitor/Cargo.toml index 81313f498..242f8050f 100644 --- a/crates/brk_monitor/Cargo.toml +++ b/crates/brk_monitor/Cargo.toml @@ -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 } diff --git a/crates/brk_monitor/examples/mempool.rs b/crates/brk_monitor/examples/mempool.rs index 0ed69bc5a..cb3550c0b 100644 --- a/crates/brk_monitor/examples/mempool.rs +++ b/crates/brk_monitor/examples/mempool.rs @@ -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(()) } diff --git a/crates/brk_monitor/src/lib.rs b/crates/brk_monitor/src/lib.rs index a0436ed53..45e130f9d 100644 --- a/crates/brk_monitor/src/lib.rs +++ b/crates/brk_monitor/src/lib.rs @@ -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); + +impl Mempool { + pub fn new(client: Client) -> Self { + Self(Arc::new(MempoolInner::new(client))) + } +} + +pub struct MempoolInner { client: Client, txs: RwLock>, addresses: RwLock)>>, } -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> { + pub fn update(&self) -> Result<()> { let txids = self .client .get_raw_mempool()? .into_iter() - .map(Txid::from) .collect::>(); let new_txs = { @@ -61,18 +76,12 @@ impl Mempool { .collect::>() } .into_iter() - .filter_map(|txid| { - self.client - .get_raw_transaction_hex(&bitcoin::Txid::from(&txid), None) - .ok() - .and_then(|hex| encode::deserialize_hex::(&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::>(); 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() diff --git a/crates/brk_reader/src/any_block.rs b/crates/brk_reader/src/any_block.rs index 989f1c561..b956a5683 100644 --- a/crates/brk_reader/src/any_block.rs +++ b/crates/brk_reader/src/any_block.rs @@ -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; diff --git a/crates/brk_reader/src/lib.rs b/crates/brk_reader/src/lib.rs index 85a26a0a4..db6563234 100644 --- a/crates/brk_reader/src/lib.rs +++ b/crates/brk_reader/src/lib.rs @@ -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); + +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>, 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())) - } } diff --git a/crates/brk_rpc/src/lib.rs b/crates/brk_rpc/src/lib.rs index 1e925cd2a..7dc09ba5f 100644 --- a/crates/brk_rpc/src/lib.rs +++ b/crates/brk_rpc/src/lib.rs @@ -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); @@ -39,7 +39,10 @@ impl Client { )?))) } - pub fn get_block(&self, hash: &BlockHash) -> Result { + pub fn get_block<'a, H>(&self, hash: &'a H) -> Result + 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 { + pub fn get_block_hash(&self, height: H) -> Result + where + H: Into + 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 { + pub fn get_block_header_info<'a, H>(&self, hash: &'a H) -> Result + 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 { - let mut tx = self.get_raw_transaction(&txid, None)?; + pub fn get_transaction<'a, T>(&self, txid: &'a T) -> Result + 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 { + txid: &'a T, + block_hash: Option<&'a H>, + ) -> brk_error::Result + 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::(&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 { + txid: &'a T, + block_hash: Option<&'a H>, + ) -> Result + 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) } diff --git a/crates/brk_structs/src/block.rs b/crates/brk_structs/src/block.rs index 37e5521c4..048ba6b71 100644 --- a/crates/brk_structs/src/block.rs +++ b/crates/brk_structs/src/block.rs @@ -56,6 +56,12 @@ impl From<(Height, BlockHash, bitcoin::Block)> for Block { } } +impl From 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 } } diff --git a/crates/brk_structs/src/blockhash.rs b/crates/brk_structs/src/blockhash.rs index 800fa445e..804cc25be 100644 --- a/crates/brk_structs/src/blockhash.rs +++ b/crates/brk_structs/src/blockhash.rs @@ -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 { + Ok(Self::from(bitcoin::BlockHash::from_str(s)?)) + } +} + impl From for BlockHash { fn from(value: bitcoin::BlockHash) -> Self { unsafe { mem::transmute(value) }