Skip to content

Commit 5daf449

Browse files
committed
callbacks: provide access to block headers
For some callbacks access to only the header may be sufficient, at which point deserializing the whole block would be wasteful. We thus provide a new hook that gets called with the header. If the `on_block()` hook remains unused, the `block` variable being deserialized should be optimized away by the compiler.
1 parent 108edaf commit 5daf449

File tree

11 files changed

+109
-1
lines changed

11 files changed

+109
-1
lines changed

src/callbacks/balances.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ impl Callback for Balances {
4343
Ok(())
4444
}
4545

46+
fn on_header(
47+
&mut self,
48+
_header: &bitcoin::blockdata::block::Header,
49+
_block_height: u64,
50+
) -> anyhow::Result<()> {
51+
Ok(())
52+
}
53+
4654
/// For each transaction in the block
4755
/// 1. apply input transactions (remove (TxID == prevTxIDOut and prevOutID == spentOutID))
4856
/// 2. apply output transactions (add (TxID + curOutID -> HashMapVal))

src/callbacks/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ pub trait Callback {
1717
/// Gets called shortly before the blocks are parsed.
1818
fn on_start(&mut self, block_height: u64) -> anyhow::Result<()>;
1919

20+
/// Gets called if a new header is available.
21+
fn on_header(
22+
&mut self,
23+
_header: &bitcoin::blockdata::block::Header,
24+
_block_height: u64,
25+
) -> anyhow::Result<()>;
26+
2027
/// Gets called if a new block is available.
2128
fn on_block(&mut self, block: &bitcoin::Block, block_height: u64) -> anyhow::Result<()>;
2229

src/callbacks/opreturn.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ impl Callback for OpReturn {
1818
Ok(())
1919
}
2020

21+
fn on_header(
22+
&mut self,
23+
_header: &bitcoin::blockdata::block::Header,
24+
_block_height: u64,
25+
) -> anyhow::Result<()> {
26+
Ok(())
27+
}
28+
2129
fn on_block(&mut self, block: &bitcoin::Block, block_height: u64) -> anyhow::Result<()> {
2230
for tx in &block.txdata {
2331
for out in &tx.output {

src/callbacks/simplestats.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ impl Callback for SimpleStats {
186186
Ok(())
187187
}
188188

189+
fn on_header(
190+
&mut self,
191+
_header: &bitcoin::blockdata::block::Header,
192+
_block_height: u64,
193+
) -> anyhow::Result<()> {
194+
Ok(())
195+
}
196+
189197
fn on_block(&mut self, block: &bitcoin::Block, block_height: u64) -> anyhow::Result<()> {
190198
self.n_valid_blocks += 1;
191199
self.n_tx += block.txdata.len();

src/callbacks/unspentcsvdump.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ impl crate::callbacks::Callback for UnspentCsvDump {
4545
Ok(())
4646
}
4747

48+
fn on_header(
49+
&mut self,
50+
_header: &bitcoin::blockdata::block::Header,
51+
_block_height: u64,
52+
) -> anyhow::Result<()> {
53+
Ok(())
54+
}
55+
4856
/// For each transaction in the block
4957
/// 1. apply input transactions (remove (TxID == prevTxIDOut and prevOutID == spentOutID))
5058
/// 2. apply output transactions (add (TxID + curOutID -> HashMapVal))

src/parser/blkfile.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ impl BlkFile {
4141
}
4242
}
4343

44+
pub fn read_header(
45+
&mut self,
46+
offset: u64,
47+
) -> anyhow::Result<bitcoin::blockdata::block::Header> {
48+
let reader = self.open()?;
49+
reader.seek(SeekFrom::Start(offset))?;
50+
reader.read_header()
51+
}
52+
4453
pub fn read_block(&mut self, offset: u64) -> anyhow::Result<bitcoin::Block> {
4554
let reader = self.open()?;
4655
reader.seek(SeekFrom::Start(offset))?;

src/parser/chain.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ impl ChainStorage {
2121
})
2222
}
2323

24+
/// Returns the header of a given height.
25+
#[must_use]
26+
pub fn get_header(&mut self, height: u64) -> Option<bitcoin::blockdata::block::Header> {
27+
// Read block
28+
let block_meta = self.chain_index.get(height)?;
29+
let blk_file = self.blk_files.get_mut(&block_meta.blk_index)?;
30+
let header = blk_file.read_header(block_meta.data_offset).ok()?;
31+
32+
// Check if blk file can be closed
33+
if height == self.chain_index.max_height_by_blk(block_meta.blk_index) {
34+
blk_file.close();
35+
}
36+
Some(header)
37+
}
38+
2439
/// Returns the block of a given height.
2540
#[must_use]
2641
pub fn get_block(&mut self, height: u64) -> Option<bitcoin::Block> {

src/parser/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ impl BlockchainParser {
5353
tracing::debug!(target: "parser", "Starting worker ...");
5454

5555
self.on_start(self.cur_height)?;
56-
while let Some(block) = self.chain_storage.get_block(self.cur_height) {
56+
while let Some(header) = self.chain_storage.get_header(self.cur_height) {
57+
self.on_header(&header, self.cur_height)?;
58+
let block = self.chain_storage.get_block(self.cur_height).unwrap();
5759
self.on_block(&block, self.cur_height)?;
5860
self.cur_height += 1;
5961
}
@@ -79,6 +81,17 @@ impl BlockchainParser {
7981
Ok(())
8082
}
8183

84+
/// Triggers the on_block() callback and updates statistics.
85+
fn on_header(
86+
&mut self,
87+
header: &bitcoin::blockdata::block::Header,
88+
height: u64,
89+
) -> anyhow::Result<()> {
90+
self.callback.on_header(header, height)?;
91+
tracing::trace!(target: "parser", "on_header(height={}) called", height);
92+
Ok(())
93+
}
94+
8295
/// Triggers the on_block() callback and updates statistics.
8396
fn on_block(&mut self, block: &bitcoin::Block, height: u64) -> anyhow::Result<()> {
8497
self.callback.on_block(block, height)?;

src/parser/reader.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ pub trait BlockchainRead: std::io::Read {
66
fn read_block(&mut self) -> anyhow::Result<bitcoin::Block> {
77
Ok(bitcoin::Block::consensus_decode(self).unwrap())
88
}
9+
10+
/// Reads a block as specified here: https://en.bitcoin.it/wiki/Protocol_specification#block
11+
fn read_header(&mut self) -> anyhow::Result<bitcoin::blockdata::block::Header> {
12+
Ok(bitcoin::blockdata::block::Header::consensus_decode(self).unwrap())
13+
}
914
}
1015

1116
/// All types that implement `Read` get methods defined in `BlockchainRead`

tests/bitcoin.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ fn test_bitcoin_genesis() {
7979
assert_eq!(0x0000_0000, genesis.txdata[0].lock_time.to_consensus_u32());
8080
}
8181

82+
#[test]
83+
fn test_genesis_header() {
84+
let header = STORAGE.lock().unwrap().get_header(0).unwrap();
85+
assert_eq!(
86+
header,
87+
bitcoin::blockdata::constants::genesis_block(bitcoin::network::constants::Network::Bitcoin)
88+
.header
89+
);
90+
}
91+
8292
#[test]
8393
fn test_blockdata_parsing() {
8494
for height in 0..=169 {
@@ -97,3 +107,10 @@ fn test_blockdata_parsing() {
97107
40 * bitcoin::Amount::ONE_BTC.to_sat()
98108
);
99109
}
110+
111+
#[test]
112+
fn test_timing() {
113+
for height in 0..=169 {
114+
STORAGE.lock().unwrap().get_header(height).unwrap();
115+
}
116+
}

0 commit comments

Comments
 (0)