global: snapshot

This commit is contained in:
nym21
2025-10-22 12:36:35 +02:00
parent 8072c4670c
commit 6cd60a064b
20 changed files with 385 additions and 214 deletions

2
Cargo.lock generated
View File

@@ -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",

View File

@@ -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;

View File

@@ -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,

View File

@@ -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),

View File

@@ -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();

View File

@@ -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(())
}

View File

@@ -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 }
}
}

View 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)
}
}
}
}

View File

@@ -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 })
}
}

View File

@@ -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 },
}

View File

@@ -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(),
}
}
}

View 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,
}
}
}

View File

@@ -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 }

View File

@@ -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(())
}

View File

@@ -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()

View File

@@ -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;

View File

@@ -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()))
}
}

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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) }