]> Untitled Git - bdk/commitdiff
Get block hash by its height
authorVladimir Fomene <vladimirfomene@gmail.com>
Thu, 16 Jun 2022 19:42:02 +0000 (20:42 +0100)
committerVladimir Fomene <vladimirfomene@gmail.com>
Wed, 6 Jul 2022 17:03:20 +0000 (18:03 +0100)
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.

CHANGELOG.md
src/blockchain/any.rs
src/blockchain/compact_filters/mod.rs
src/blockchain/electrum.rs
src/blockchain/esplora/reqwest.rs
src/blockchain/esplora/ureq.rs
src/blockchain/mod.rs
src/blockchain/rpc.rs
src/testutils/blockchain_tests.rs

index 4bc8f6bac0f848e483b7cf1e560ccfddaa708544..b8bf8a04d292ece1177b8535b147670368138a4c 100644 (file)
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - 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)
 - Fix hang when `ElectrumBlockchainConfig::stop_gap` is zero.
 - Set coin type in BIP44, BIP49, and BIP84 templates
+- 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. 
 
 ## [v0.19.0] - [v0.18.0]
 
index b669846298b1cd12e1c78b7ceff7712e9f82b4ba..5ef1a338514e393383bb196c84dff749da2af056 100644 (file)
@@ -120,6 +120,13 @@ impl GetTx for AnyBlockchain {
     }
 }
 
+#[maybe_async]
+impl GetBlockHash for AnyBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        maybe_await!(impl_inner_method!(self, get_block_hash, height))
+    }
+}
+
 #[maybe_async]
 impl WalletSync for AnyBlockchain {
     fn wallet_sync<D: BatchDatabase>(
index eb172059266759e0682c2746251b14b0e4dc3feb..7ca78a2c34250cf44dc6161e61d4571a5847d185 100644 (file)
@@ -260,6 +260,16 @@ impl GetTx for CompactFiltersBlockchain {
     }
 }
 
+impl GetBlockHash for CompactFiltersBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        self.headers
+            .get_block_hash(height as usize)?
+            .ok_or(Error::CompactFilters(
+                CompactFiltersError::BlockHashNotFound,
+            ))
+    }
+}
+
 impl WalletSync for CompactFiltersBlockchain {
     #[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
     fn wallet_setup<D: BatchDatabase>(
@@ -536,6 +546,8 @@ pub enum CompactFiltersError {
     InvalidFilter,
     /// The peer is missing a block in the valid chain
     MissingBlock,
+    /// Block hash at specified height not found
+    BlockHashNotFound,
     /// The data stored in the block filters storage are corrupted
     DataCorruption,
 
index b71390cb917b6bd5e398d341b4aaf3cc812b69c8..faf7ea7565906b0cecca1cb3859fdadda73a23a1 100644 (file)
@@ -98,6 +98,13 @@ impl GetTx for ElectrumBlockchain {
     }
 }
 
+impl GetBlockHash for ElectrumBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        let block_header = self.client.block_header(height as usize)?;
+        Ok(block_header.block_hash())
+    }
+}
+
 impl WalletSync for ElectrumBlockchain {
     fn wallet_setup<D: BatchDatabase>(
         &self,
index f68bdd8a1f46d18d503d7c2f4bb689c4dbf463aa..0d40506082a729107d9f20b450e64c45fed76709 100644 (file)
@@ -117,6 +117,14 @@ impl GetTx for EsploraBlockchain {
     }
 }
 
+#[maybe_async]
+impl GetBlockHash for EsploraBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        let block_header = await_or_block!(self.url_client._get_header(height as u32))?;
+        Ok(block_header.block_hash())
+    }
+}
+
 #[maybe_async]
 impl WalletSync for EsploraBlockchain {
     fn wallet_setup<D: BatchDatabase>(
index 7ce57a138e1a5b94b007575a814d369f6cecc10c..9899b90462dc958a2048a623c4b890bb00a94b20 100644 (file)
@@ -112,6 +112,13 @@ impl GetTx for EsploraBlockchain {
     }
 }
 
+impl GetBlockHash for EsploraBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        let block_header = self.url_client._get_header(height as u32)?;
+        Ok(block_header.block_hash())
+    }
+}
+
 impl WalletSync for EsploraBlockchain {
     fn wallet_setup<D: BatchDatabase>(
         &self,
index cf593c3c83fa88226f5ab83ccc28629b8f1c5466..1dc5c95a1c508f3761fb4a41efe322b2a56d0571 100644 (file)
@@ -21,7 +21,7 @@ use std::ops::Deref;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::sync::Arc;
 
-use bitcoin::{Transaction, Txid};
+use bitcoin::{BlockHash, Transaction, Txid};
 
 use crate::database::BatchDatabase;
 use crate::error::Error;
@@ -87,7 +87,7 @@ pub enum Capability {
 
 /// Trait that defines the actions that must be supported by a blockchain backend
 #[maybe_async]
-pub trait Blockchain: WalletSync + GetHeight + GetTx {
+pub trait Blockchain: WalletSync + GetHeight + GetTx + GetBlockHash {
     /// Return the set of [`Capability`] supported by this backend
     fn get_capabilities(&self) -> HashSet<Capability>;
     /// Broadcast a transaction
@@ -110,6 +110,13 @@ pub trait GetTx {
     fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
 }
 
+#[maybe_async]
+/// Trait for getting block hash by block height
+pub trait GetBlockHash {
+    /// fetch block hash given its height
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error>;
+}
+
 /// Trait for blockchains that can sync by updating the database directly.
 #[maybe_async]
 pub trait WalletSync {
@@ -359,6 +366,13 @@ impl<T: GetHeight> GetHeight for Arc<T> {
     }
 }
 
+#[maybe_async]
+impl<T: GetBlockHash> GetBlockHash for Arc<T> {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        maybe_await!(self.deref().get_block_hash(height))
+    }
+}
+
 #[maybe_async]
 impl<T: WalletSync> WalletSync for Arc<T> {
     fn wallet_setup<D: BatchDatabase>(
index d1366368de2f5a0d865412ac55830aee0c304fbc..9ba6f0f33221a7bb17f08443681a22460f6cf1ad 100644 (file)
@@ -169,6 +169,12 @@ impl GetHeight for RpcBlockchain {
     }
 }
 
+impl GetBlockHash for RpcBlockchain {
+    fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
+        Ok(self.client.get_block_hash(height)?)
+    }
+}
+
 impl WalletSync for RpcBlockchain {
     fn wallet_setup<D: BatchDatabase>(
         &self,
index a78e4a893b51cb263e5601f919a364062fc98523..2ae546324b9215205a4c9f2fed005cf37e3e7849 100644 (file)
@@ -1361,6 +1361,35 @@ macro_rules! bdk_blockchain_tests {
                 let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
                 assert_eq!(finalized, true);
             }
+
+            #[test]
+            fn test_get_block_hash() {
+                use bitcoincore_rpc::{ RpcApi };
+                use crate::blockchain::GetBlockHash;
+
+                // create wallet with init_wallet
+                let (_, blockchain, _descriptors, mut test_client) = init_single_sig();
+
+                let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
+                let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
+
+                // use get_block_hash to get best block hash and compare with best_hash above
+                let block_hash = blockchain.get_block_hash(height).unwrap();
+                assert_eq!(best_hash, block_hash);
+
+                // generate blocks to address
+                let node_addr = test_client.get_node_address(None);
+                test_client.generate(10, Some(node_addr));
+
+                let height = test_client.bitcoind.client.get_blockchain_info().unwrap().blocks as u64;
+                let best_hash = test_client.bitcoind.client.get_best_block_hash().unwrap();
+
+                let block_hash = blockchain.get_block_hash(height).unwrap();
+                assert_eq!(best_hash, block_hash);
+
+                // try to get hash for block that has not yet been created.
+                assert!(blockchain.get_block_hash(height + 1).is_err());
+            }
         }
     };