bitcoin: make get_block_stats fallible

The call to getblockheader may fail, for instance when bitcoind is
rolling forward its blocks after a crash. It's a pretty edgy case but
instead of crashing restart over and over again.
This commit is contained in:
Antoine Poinsot 2023-10-27 18:38:14 +02:00
parent 869779dd94
commit 663fbd2c28
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 30 additions and 19 deletions

View File

@ -957,11 +957,17 @@ impl BitcoinD {
None
}
pub fn get_block_stats(&self, blockhash: bitcoin::BlockHash) -> BlockStats {
let res = self.make_node_request(
pub fn get_block_stats(&self, blockhash: bitcoin::BlockHash) -> Option<BlockStats> {
let res = match self.make_fallible_node_request(
"getblockheader",
&params!(Json::String(blockhash.to_string()),),
);
) {
Ok(res) => res,
Err(e) => {
log::warn!("Error when fetching block header {}: {}", &blockhash, e);
return None;
}
};
let confirmations = res
.get("confirmations")
.and_then(Json::as_i64)
@ -989,14 +995,14 @@ impl BitcoinD {
.and_then(Json::as_u64)
.expect("Invalid median timestamp in `getblockheader` response: not an u64")
as u32;
BlockStats {
Some(BlockStats {
confirmations,
previous_blockhash,
height,
blockhash,
time,
median_time_past,
}
})
}
pub fn broadcast_tx(&self, tx: &bitcoin::Transaction) -> Result<(), BitcoindError> {

View File

@ -28,15 +28,15 @@ pub fn block_before_date<Fh, Fs>(
) -> Option<BlockChainTip>
where
Fh: FnMut(i32) -> Option<bitcoin::BlockHash>,
Fs: FnMut(bitcoin::BlockHash) -> BlockStats,
Fs: FnMut(bitcoin::BlockHash) -> Option<BlockStats>,
{
log::debug!("Looking for the first block before {}", target_timestamp);
let mut start_height = 0;
let mut end_height = chain_tip.height;
let genesis_stats = get_stats(get_hash(0).expect("Genesis hash"));
let tip_stats = get_stats(chain_tip.hash);
let genesis_stats = get_stats(get_hash(0).expect("Genesis hash"))?;
let tip_stats = get_stats(chain_tip.hash)?;
if !(genesis_stats.time..tip_stats.time).contains(&target_timestamp) {
return None;
}
@ -47,7 +47,7 @@ where
let current_height = start_height + delta.checked_div(2).unwrap();
// We want the last block with a timestamp below, not the first with a higher one.
let next_height = current_height.checked_add(1).unwrap();
let next_stats = get_stats(get_hash(next_height)?);
let next_stats = get_stats(get_hash(next_height)?)?;
log::debug!("Current next block: {:?}", next_stats);
if target_timestamp > next_stats.time {
@ -85,13 +85,18 @@ mod tests {
}
// Inefficient dummy implementation of BitcoinD's self.get_block_stats
fn get_stats(chain: &[(BlockChainTip, BlockStats)], hash: bitcoin::BlockHash) -> BlockStats {
chain
.iter()
.find(|(tip, _)| tip.hash == hash)
.unwrap()
.1
.clone()
fn get_stats(
chain: &[(BlockChainTip, BlockStats)],
hash: bitcoin::BlockHash,
) -> Option<BlockStats> {
Some(
chain
.iter()
.find(|(tip, _)| tip.hash == hash)
.unwrap()
.1
.clone(),
)
}
macro_rules! bh {

View File

@ -272,11 +272,11 @@ impl BitcoinInterface for d::BitcoinD {
}
fn common_ancestor(&self, tip: &BlockChainTip) -> Option<BlockChainTip> {
let mut stats = self.get_block_stats(tip.hash);
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,
@ -318,7 +318,7 @@ impl BitcoinInterface for d::BitcoinD {
fn tip_time(&self) -> Option<u32> {
let tip = self.chain_tip();
Some(self.get_block_stats(tip.hash).time)
Some(self.get_block_stats(tip.hash)?.time)
}
fn wallet_transaction(