bitcoind: handle missing previousblockhash from getblockheader

It seems that internally bitcoind might temporarily not have a pprev
pointer for a block. This will result in the optional
"previousblockhash" field to be null and would previously make us crash.

Handle that gracefully.
This commit is contained in:
Antoine Poinsot 2022-11-09 10:19:59 +01:00
parent 9fa4b95847
commit 80803c78a4
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
4 changed files with 26 additions and 17 deletions

View File

@ -679,12 +679,14 @@ impl BitcoinD {
let previous_blockhash = res
.get("previousblockhash")
.and_then(Json::as_str)
.and_then(|s| bitcoin::BlockHash::from_str(s).ok())
.expect("Invalid previousblockhash in `getblockheader` response");
.map(|s| {
bitcoin::BlockHash::from_str(s)
.expect("Invalid previousblockhash in `getblockheader` response")
});
let height = res
.get("height")
.and_then(Json::as_i64)
.expect("Invalid height in `getblockheader` response: not an u32")
.expect("Invalid height in `getblockheader` response: not an i64")
as i32;
BlockStats {
confirmations,
@ -848,7 +850,7 @@ impl From<Json> for GetTxRes {
#[derive(Debug, Clone)]
pub struct BlockStats {
pub confirmations: i32,
pub previous_blockhash: bitcoin::BlockHash,
pub previous_blockhash: Option<bitcoin::BlockHash>,
pub blockhash: bitcoin::BlockHash,
pub height: i32,
}

View File

@ -84,7 +84,7 @@ pub trait BitcoinInterface: Send {
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)>;
/// Get the common ancestor between the Bitcoin backend's tip and the given tip.
fn common_ancestor(&self, tip: &BlockChainTip) -> BlockChainTip;
fn common_ancestor(&self, tip: &BlockChainTip) -> Option<BlockChainTip>;
/// Broadcast this transaction to the Bitcoin P2P network
fn broadcast_tx(&self, tx: &bitcoin::Transaction) -> Result<(), BitcoinError>;
@ -260,19 +260,19 @@ impl BitcoinInterface for d::BitcoinD {
spent
}
fn common_ancestor(&self, tip: &BlockChainTip) -> BlockChainTip {
fn common_ancestor(&self, tip: &BlockChainTip) -> Option<BlockChainTip> {
let mut stats = self.get_block_stats(tip.hash);
let mut ancestor = *tip;
while stats.confirmations == -1 {
stats = self.get_block_stats(stats.previous_blockhash);
stats = self.get_block_stats(stats.previous_blockhash?);
ancestor = BlockChainTip {
hash: stats.blockhash,
height: stats.height,
};
}
ancestor
Some(ancestor)
}
fn broadcast_tx(&self, tx: &bitcoin::Transaction) -> Result<(), BitcoinError> {
@ -335,7 +335,7 @@ impl BitcoinInterface for sync::Arc<sync::Mutex<dyn BitcoinInterface + 'static>>
self.lock().unwrap().spent_coins(outpoints)
}
fn common_ancestor(&self, tip: &BlockChainTip) -> BlockChainTip {
fn common_ancestor(&self, tip: &BlockChainTip) -> Option<BlockChainTip> {
self.lock().unwrap().common_ancestor(tip)
}

View File

@ -150,13 +150,20 @@ fn new_tip(bit: &impl BitcoinInterface, current_tip: &BlockChainTip) -> TipUpdat
// block chain re-organisation. Find the common ancestor between our current chain and
// the new chain and return that. The caller will take care of rewinding our state.
log::info!("Block chain reorganization detected. Looking for common ancestor.");
let common_ancestor = bit.common_ancestor(current_tip);
log::info!(
"Common ancestor found: '{}'. Starting rescan from there. Old tip was '{}'.",
common_ancestor,
current_tip
);
TipUpdate::Reorged(common_ancestor)
if let Some(common_ancestor) = bit.common_ancestor(current_tip) {
log::info!(
"Common ancestor found: '{}'. Starting rescan from there. Old tip was '{}'.",
common_ancestor,
current_tip
);
TipUpdate::Reorged(common_ancestor)
} else {
log::error!(
"Failed to get common ancestor for tip '{}'. Starting over.",
current_tip
);
new_tip(bit, current_tip)
}
}
fn updates(

View File

@ -67,7 +67,7 @@ impl BitcoinInterface for DummyBitcoind {
Vec::new()
}
fn common_ancestor(&self, _: &BlockChainTip) -> BlockChainTip {
fn common_ancestor(&self, _: &BlockChainTip) -> Option<BlockChainTip> {
todo!()
}