Skip to content

Commit 66d48d2

Browse files
committed
Follow reorgs with bitcoind REST
Bitcoin Core's REST headers endpoint cannot serve headers from a stale branch. This prevented REST-backed nodes from finding a common ancestor and following the replacement chain after a reorg. In #1006 we enabled bitcoind REST chain source support in CI, which exposed this failure. Co-Authored-By: HAL 9000
1 parent 5e7250a commit 66d48d2

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

src/chain/bitcoind.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,8 +1280,13 @@ impl BlockSource for BitcoindClient {
12801280
BitcoindClient::Rpc { rpc_client, .. } => {
12811281
rpc_client.get_header(header_hash, height_hint).await
12821282
},
1283-
BitcoindClient::Rest { rest_client, .. } => {
1284-
rest_client.get_header(header_hash, height_hint).await
1283+
BitcoindClient::Rest { rest_client, rpc_client, .. } => {
1284+
match rest_client.get_header(header_hash, height_hint).await {
1285+
Err(e) if e.kind() == BlockSourceErrorKind::Persistent => {
1286+
rpc_client.get_header(header_hash, height_hint).await
1287+
},
1288+
result => result,
1289+
}
12851290
},
12861291
}
12871292
}

tests/reorg_test.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,74 @@ mod common;
22
use std::collections::HashMap;
33

44
use bitcoin::Amount;
5+
use electrsd::corepc_node::mtype::ChainTipsStatus;
56
use ldk_node::payment::{PaymentDirection, PaymentKind};
67
use ldk_node::{Event, LightningBalance, PendingSweepBalance};
78
use proptest::prelude::prop;
89
use proptest::proptest;
10+
use serde_json::json;
911

1012
use crate::common::{
1113
expect_event, exponential_backoff_poll, generate_blocks_and_wait, invalidate_blocks,
1214
open_channel, premine_and_distribute_funds, random_chain_source, random_config,
13-
setup_bitcoind_and_electrsd, setup_node, wait_for_outpoint_spend, wait_for_tx,
15+
setup_bitcoind_and_electrsd, setup_node, wait_for_outpoint_spend, wait_for_tx, TestChainSource,
1416
};
1517

18+
#[test]
19+
fn bitcoind_rest_follows_valid_reorg() {
20+
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();
21+
rt.block_on(async {
22+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
23+
let node = setup_node(&TestChainSource::BitcoindRestSync(&bitcoind), random_config());
24+
let (bitcoind, electrs) = (&bitcoind.client, &electrsd.client);
25+
26+
generate_blocks_and_wait(bitcoind, electrs, 3).await;
27+
node.sync_wallets().unwrap();
28+
let original_tip = node.status().current_best_block;
29+
let fork_block_hash = bitcoind
30+
.get_block_hash((original_tip.height - 1) as u64)
31+
.expect("failed to get fork block hash")
32+
.block_hash()
33+
.expect("fork block hash should be present");
34+
35+
invalidate_blocks(bitcoind, 2);
36+
generate_blocks_and_wait(bitcoind, electrs, 3).await;
37+
let replacement_tip_hash =
38+
bitcoind.best_block_hash().expect("failed to get replacement tip");
39+
let replacement_tip_height =
40+
bitcoind.get_blockchain_info().expect("failed to get replacement tip height").blocks
41+
as u32;
42+
43+
let _: serde_json::Value = bitcoind
44+
.call("reconsiderblock", &[json!(fork_block_hash)])
45+
.expect("failed to reconsider original branch");
46+
let chain_tips = bitcoind
47+
.get_chain_tips()
48+
.expect("failed to get chain tips")
49+
.into_model()
50+
.expect("failed to parse chain tips")
51+
.0;
52+
assert!(chain_tips.iter().any(|tip| {
53+
tip.hash == original_tip.block_hash && tip.status == ChainTipsStatus::ValidFork
54+
}));
55+
assert!(chain_tips.iter().any(|tip| {
56+
tip.hash == replacement_tip_hash && tip.status == ChainTipsStatus::Active
57+
}));
58+
59+
node.sync_wallets()
60+
.expect("REST-backed node did not follow Bitcoin Core's replacement chain");
61+
let synced_tip = node.status().current_best_block;
62+
assert_eq!(
63+
synced_tip.block_hash, replacement_tip_hash,
64+
"REST-backed node did not follow Bitcoin Core's replacement chain"
65+
);
66+
assert_eq!(
67+
synced_tip.height, replacement_tip_height,
68+
"REST-backed node did not follow Bitcoin Core's replacement chain"
69+
);
70+
})
71+
}
72+
1673
async fn wait_for_pending_sweep_balance<F>(
1774
node: &ldk_node::Node, mut matches_balance: F,
1875
) -> PendingSweepBalance

0 commit comments

Comments
 (0)