mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-27 16:19:59 -07:00
global: snapshot
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user