Skip to content

Commit 2af678a

Browse files
Get block hash by its height
Create blockchain::GetBlockHash trait with a method to get block hash given a block height. Then, implement this trait for all backends (Electrum, RPC , Esplora, CBF). Referenced in issue 603.
1 parent 1c94108 commit 2af678a

File tree

9 files changed

+93
-2
lines changed

9 files changed

+93
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
- Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced)
1010
- Fix hang when `ElectrumBlockchainConfig::stop_gap` is zero.
1111
- Set coin type in BIP44, BIP49, and BIP84 templates
12+
- Get block hash given a block height - A `get_block_hash` method is now defined on the `GetBlockHash` trait and implemented on every blockchain backend. This method expects a block height and returns the corresponding block hash.
1213

1314
## [v0.19.0] - [v0.18.0]
1415

src/blockchain/any.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ impl GetTx for AnyBlockchain {
120120
}
121121
}
122122

123+
#[maybe_async]
124+
impl GetBlockHash for AnyBlockchain {
125+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
126+
maybe_await!(impl_inner_method!(self, get_block_hash, height))
127+
}
128+
}
129+
123130
#[maybe_async]
124131
impl WalletSync for AnyBlockchain {
125132
fn wallet_sync<D: BatchDatabase>(

src/blockchain/compact_filters/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ impl GetTx for CompactFiltersBlockchain {
260260
}
261261
}
262262

263+
impl GetBlockHash for CompactFiltersBlockchain {
264+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
265+
self.headers
266+
.get_block_hash(height as usize)?
267+
.ok_or(Error::CompactFilters(
268+
CompactFiltersError::BlockHashNotFound,
269+
))
270+
}
271+
}
272+
263273
impl WalletSync for CompactFiltersBlockchain {
264274
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
265275
fn wallet_setup<D: BatchDatabase>(
@@ -536,6 +546,8 @@ pub enum CompactFiltersError {
536546
InvalidFilter,
537547
/// The peer is missing a block in the valid chain
538548
MissingBlock,
549+
/// Block hash at specified height not found
550+
BlockHashNotFound,
539551
/// The data stored in the block filters storage are corrupted
540552
DataCorruption,
541553

src/blockchain/electrum.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ impl GetTx for ElectrumBlockchain {
9898
}
9999
}
100100

101+
impl GetBlockHash for ElectrumBlockchain {
102+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
103+
let block_header = self.client.block_header(height as usize)?;
104+
Ok(block_header.block_hash())
105+
}
106+
}
107+
101108
impl WalletSync for ElectrumBlockchain {
102109
fn wallet_setup<D: BatchDatabase>(
103110
&self,

src/blockchain/esplora/reqwest.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ impl GetTx for EsploraBlockchain {
117117
}
118118
}
119119

120+
#[maybe_async]
121+
impl GetBlockHash for EsploraBlockchain {
122+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
123+
let block_header = await_or_block!(self.url_client._get_header(height as u32))?;
124+
Ok(block_header.block_hash())
125+
}
126+
}
127+
120128
#[maybe_async]
121129
impl WalletSync for EsploraBlockchain {
122130
fn wallet_setup<D: BatchDatabase>(

src/blockchain/esplora/ureq.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ impl GetTx for EsploraBlockchain {
112112
}
113113
}
114114

115+
impl GetBlockHash for EsploraBlockchain {
116+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
117+
let block_header = self.url_client._get_header(height as u32)?;
118+
Ok(block_header.block_hash())
119+
}
120+
}
121+
115122
impl WalletSync for EsploraBlockchain {
116123
fn wallet_setup<D: BatchDatabase>(
117124
&self,

src/blockchain/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::ops::Deref;
2121
use std::sync::mpsc::{channel, Receiver, Sender};
2222
use std::sync::Arc;
2323

24-
use bitcoin::{Transaction, Txid};
24+
use bitcoin::{BlockHash, Transaction, Txid};
2525

2626
use crate::database::BatchDatabase;
2727
use crate::error::Error;
@@ -87,7 +87,7 @@ pub enum Capability {
8787

8888
/// Trait that defines the actions that must be supported by a blockchain backend
8989
#[maybe_async]
90-
pub trait Blockchain: WalletSync + GetHeight + GetTx {
90+
pub trait Blockchain: WalletSync + GetHeight + GetTx + GetBlockHash {
9191
/// Return the set of [`Capability`] supported by this backend
9292
fn get_capabilities(&self) -> HashSet<Capability>;
9393
/// Broadcast a transaction
@@ -110,6 +110,13 @@ pub trait GetTx {
110110
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
111111
}
112112

113+
#[maybe_async]
114+
/// Trait for getting block hash by block height
115+
pub trait GetBlockHash {
116+
/// fetch block hash given its height
117+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error>;
118+
}
119+
113120
/// Trait for blockchains that can sync by updating the database directly.
114121
#[maybe_async]
115122
pub trait WalletSync {
@@ -359,6 +366,13 @@ impl<T: GetHeight> GetHeight for Arc<T> {
359366
}
360367
}
361368

369+
#[maybe_async]
370+
impl<T: GetBlockHash> GetBlockHash for Arc<T> {
371+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
372+
maybe_await!(self.deref().get_block_hash(height))
373+
}
374+
}
375+
362376
#[maybe_async]
363377
impl<T: WalletSync> WalletSync for Arc<T> {
364378
fn wallet_setup<D: BatchDatabase>(

src/blockchain/rpc.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ impl GetHeight for RpcBlockchain {
169169
}
170170
}
171171

172+
impl GetBlockHash for RpcBlockchain {
173+
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
174+
Ok(self.client.get_block_hash(height)?)
175+
}
176+
}
177+
172178
impl WalletSync for RpcBlockchain {
173179
fn wallet_setup<D: BatchDatabase>(
174180
&self,

src/testutils/blockchain_tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,35 @@ macro_rules! bdk_blockchain_tests {
13611361
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
13621362
assert_eq!(finalized, true);
13631363
}
1364+
1365+
#[test]
1366+
fn test_get_block_hash() {
1367+
use bitcoincore_rpc::{ RpcApi };
1368+
use crate::blockchain::GetBlockHash;
1369+
1370+
// create wallet with init_wallet
1371+
let (_, blockchain, _descriptors, mut test_client) = init_single_sig();
1372+
1373+
let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
1374+
let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
1375+
1376+
// use get_block_hash to get best block hash and compare with best_hash above
1377+
let block_hash = blockchain.get_block_hash(height).unwrap();
1378+
assert_eq!(best_hash, block_hash);
1379+
1380+
// generate blocks to address
1381+
let node_addr = test_client.get_node_address(None);
1382+
test_client.generate(10, Some(node_addr));
1383+
1384+
let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
1385+
let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
1386+
1387+
let block_hash = blockchain.get_block_hash(height).unwrap();
1388+
assert_eq!(best_hash, block_hash);
1389+
1390+
// try to get hash for block that has not yet been created.
1391+
assert!(blockchain.get_block_hash(height + 1).is_err());
1392+
}
13641393
}
13651394
};
13661395

0 commit comments

Comments
 (0)