Skip to content

Commit c0880d9

Browse files
committed
Try to poll chain tip on initialization
Previously, we couldn't poll the chain tip in `Builder::build` as we wouldn't have a runtime available. Since we now do, we can at least attempt to poll for the chain tip before initializing objects, avoiding that fresh nodes need to re-validate everything from genesis.
1 parent 5f1a872 commit c0880d9

File tree

3 files changed

+111
-71
lines changed

3 files changed

+111
-71
lines changed

src/builder.rs

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,10 +1195,10 @@ fn build_with_store_internal(
11951195
},
11961196
};
11971197

1198-
let chain_source = match chain_data_source_config {
1198+
let (chain_source, chain_tip_opt) = match chain_data_source_config {
11991199
Some(ChainDataSourceConfig::Esplora { server_url, headers, sync_config }) => {
12001200
let sync_config = sync_config.unwrap_or(EsploraSyncConfig::default());
1201-
Arc::new(ChainSource::new_esplora(
1201+
ChainSource::new_esplora(
12021202
server_url.clone(),
12031203
headers.clone(),
12041204
sync_config,
@@ -1208,11 +1208,11 @@ fn build_with_store_internal(
12081208
Arc::clone(&config),
12091209
Arc::clone(&logger),
12101210
Arc::clone(&node_metrics),
1211-
))
1211+
)
12121212
},
12131213
Some(ChainDataSourceConfig::Electrum { server_url, sync_config }) => {
12141214
let sync_config = sync_config.unwrap_or(ElectrumSyncConfig::default());
1215-
Arc::new(ChainSource::new_electrum(
1215+
ChainSource::new_electrum(
12161216
server_url.clone(),
12171217
sync_config,
12181218
Arc::clone(&fee_estimator),
@@ -1221,7 +1221,7 @@ fn build_with_store_internal(
12211221
Arc::clone(&config),
12221222
Arc::clone(&logger),
12231223
Arc::clone(&node_metrics),
1224-
))
1224+
)
12251225
},
12261226
Some(ChainDataSourceConfig::Bitcoind {
12271227
rpc_host,
@@ -1230,38 +1230,44 @@ fn build_with_store_internal(
12301230
rpc_password,
12311231
rest_client_config,
12321232
}) => match rest_client_config {
1233-
Some(rest_client_config) => Arc::new(ChainSource::new_bitcoind_rest(
1234-
rpc_host.clone(),
1235-
*rpc_port,
1236-
rpc_user.clone(),
1237-
rpc_password.clone(),
1238-
Arc::clone(&fee_estimator),
1239-
Arc::clone(&tx_broadcaster),
1240-
Arc::clone(&kv_store),
1241-
Arc::clone(&config),
1242-
rest_client_config.clone(),
1243-
Arc::clone(&logger),
1244-
Arc::clone(&node_metrics),
1245-
)),
1246-
None => Arc::new(ChainSource::new_bitcoind_rpc(
1247-
rpc_host.clone(),
1248-
*rpc_port,
1249-
rpc_user.clone(),
1250-
rpc_password.clone(),
1251-
Arc::clone(&fee_estimator),
1252-
Arc::clone(&tx_broadcaster),
1253-
Arc::clone(&kv_store),
1254-
Arc::clone(&config),
1255-
Arc::clone(&logger),
1256-
Arc::clone(&node_metrics),
1257-
)),
1233+
Some(rest_client_config) => runtime.block_on(async {
1234+
ChainSource::new_bitcoind_rest(
1235+
rpc_host.clone(),
1236+
*rpc_port,
1237+
rpc_user.clone(),
1238+
rpc_password.clone(),
1239+
Arc::clone(&fee_estimator),
1240+
Arc::clone(&tx_broadcaster),
1241+
Arc::clone(&kv_store),
1242+
Arc::clone(&config),
1243+
rest_client_config.clone(),
1244+
Arc::clone(&logger),
1245+
Arc::clone(&node_metrics),
1246+
)
1247+
.await
1248+
}),
1249+
None => runtime.block_on(async {
1250+
ChainSource::new_bitcoind_rpc(
1251+
rpc_host.clone(),
1252+
*rpc_port,
1253+
rpc_user.clone(),
1254+
rpc_password.clone(),
1255+
Arc::clone(&fee_estimator),
1256+
Arc::clone(&tx_broadcaster),
1257+
Arc::clone(&kv_store),
1258+
Arc::clone(&config),
1259+
Arc::clone(&logger),
1260+
Arc::clone(&node_metrics),
1261+
)
1262+
.await
1263+
}),
12581264
},
12591265

12601266
None => {
12611267
// Default to Esplora client.
12621268
let server_url = DEFAULT_ESPLORA_SERVER_URL.to_string();
12631269
let sync_config = EsploraSyncConfig::default();
1264-
Arc::new(ChainSource::new_esplora(
1270+
ChainSource::new_esplora(
12651271
server_url.clone(),
12661272
HashMap::new(),
12671273
sync_config,
@@ -1271,9 +1277,10 @@ fn build_with_store_internal(
12711277
Arc::clone(&config),
12721278
Arc::clone(&logger),
12731279
Arc::clone(&node_metrics),
1274-
))
1280+
)
12751281
},
12761282
};
1283+
let chain_source = Arc::new(chain_source);
12771284

12781285
// Initialize the on-chain wallet and chain access
12791286
let xprv = bitcoin::bip32::Xpriv::new_master(config.network, &seed_bytes).map_err(|e| {
@@ -1313,13 +1320,31 @@ fn build_with_store_internal(
13131320
})?;
13141321
let bdk_wallet = match wallet_opt {
13151322
Some(wallet) => wallet,
1316-
None => BdkWallet::create(descriptor, change_descriptor)
1317-
.network(config.network)
1318-
.create_wallet(&mut wallet_persister)
1319-
.map_err(|e| {
1320-
log_error!(logger, "Failed to set up wallet: {}", e);
1321-
BuildError::WalletSetupFailed
1322-
})?,
1323+
None => {
1324+
let mut wallet = BdkWallet::create(descriptor, change_descriptor)
1325+
.network(config.network)
1326+
.create_wallet(&mut wallet_persister)
1327+
.map_err(|e| {
1328+
log_error!(logger, "Failed to set up wallet: {}", e);
1329+
BuildError::WalletSetupFailed
1330+
})?;
1331+
1332+
if let Some(best_block) = chain_tip_opt {
1333+
// Insert the first checkpoint if we have it, to avoid resyncing from genesis.
1334+
// TODO: Use a proper wallet birthday once BDK supports it.
1335+
let mut latest_checkpoint = wallet.latest_checkpoint();
1336+
let block_id =
1337+
bdk_chain::BlockId { height: best_block.height, hash: best_block.block_hash };
1338+
latest_checkpoint = latest_checkpoint.insert(block_id);
1339+
let update =
1340+
bdk_wallet::Update { chain: Some(latest_checkpoint), ..Default::default() };
1341+
wallet.apply_update(update).map_err(|e| {
1342+
log_error!(logger, "Failed to apply checkpoint during wallet setup: {}", e);
1343+
BuildError::WalletSetupFailed
1344+
})?;
1345+
}
1346+
wallet
1347+
},
13231348
};
13241349

13251350
let wallet = Arc::new(Wallet::new(
@@ -1499,13 +1524,10 @@ fn build_with_store_internal(
14991524
channel_manager
15001525
} else {
15011526
// We're starting a fresh node.
1502-
let genesis_block_hash =
1503-
bitcoin::blockdata::constants::genesis_block(config.network).block_hash();
1527+
let best_block =
1528+
chain_tip_opt.unwrap_or_else(|| BestBlock::from_network(config.network));
15041529

1505-
let chain_params = ChainParameters {
1506-
network: config.network.into(),
1507-
best_block: BestBlock::new(genesis_block_hash, 0),
1508-
};
1530+
let chain_params = ChainParameters { network: config.network.into(), best_block };
15091531
channelmanager::ChannelManager::new(
15101532
Arc::clone(&fee_estimator),
15111533
Arc::clone(&chain_monitor),

src/chain/bitcoind.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use base64::prelude::BASE64_STANDARD;
1414
use base64::Engine;
1515
use bitcoin::{BlockHash, FeeRate, Network, Transaction, Txid};
1616
use lightning::chain::chaininterface::ConfirmationTarget as LdkConfirmationTarget;
17-
use lightning::chain::Listen;
17+
use lightning::chain::{BestBlock, Listen};
1818
use lightning::util::ser::Writeable;
1919
use lightning_block_sync::gossip::UtxoSource;
2020
use lightning_block_sync::http::{HttpEndpoint, JsonResponse};
@@ -42,6 +42,7 @@ use crate::types::{ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet};
4242
use crate::{Error, NodeMetrics};
4343

4444
const CHAIN_POLLING_INTERVAL_SECS: u64 = 2;
45+
const CHAIN_POLLING_TIMEOUT_SECS: u64 = 10;
4546

4647
pub(super) struct BitcoindChainSource {
4748
api_client: Arc<BitcoindClient>,
@@ -329,6 +330,33 @@ impl BitcoindChainSource {
329330
}
330331
}
331332

333+
pub(super) async fn poll_best_block(&self) -> Result<BestBlock, Error> {
334+
self.poll_chain_tip().await.map(|tip| tip.to_best_block())
335+
}
336+
337+
async fn poll_chain_tip(&self) -> Result<ValidatedBlockHeader, Error> {
338+
let validate_res = tokio::time::timeout(
339+
Duration::from_secs(CHAIN_POLLING_TIMEOUT_SECS),
340+
validate_best_block_header(self.api_client.as_ref()),
341+
)
342+
.await
343+
.map_err(|e| {
344+
log_error!(self.logger, "Failed to poll for chain data: {:?}", e);
345+
Error::TxSyncTimeout
346+
})?;
347+
348+
match validate_res {
349+
Ok(tip) => {
350+
*self.latest_chain_tip.write().unwrap() = Some(tip);
351+
Ok(tip)
352+
},
353+
Err(e) => {
354+
log_error!(self.logger, "Failed to poll for chain data: {:?}", e);
355+
return Err(Error::TxSyncFailed);
356+
},
357+
}
358+
}
359+
332360
pub(super) async fn poll_and_update_listeners(
333361
&self, onchain_wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
334362
chain_monitor: Arc<ChainMonitor>, output_sweeper: Arc<Sweeper>,
@@ -366,20 +394,8 @@ impl BitcoindChainSource {
366394
chain_monitor: Arc<ChainMonitor>, output_sweeper: Arc<Sweeper>,
367395
) -> Result<(), Error> {
368396
let latest_chain_tip_opt = self.latest_chain_tip.read().unwrap().clone();
369-
let chain_tip = if let Some(tip) = latest_chain_tip_opt {
370-
tip
371-
} else {
372-
match validate_best_block_header(self.api_client.as_ref()).await {
373-
Ok(tip) => {
374-
*self.latest_chain_tip.write().unwrap() = Some(tip);
375-
tip
376-
},
377-
Err(e) => {
378-
log_error!(self.logger, "Failed to poll for chain data: {:?}", e);
379-
return Err(Error::TxSyncFailed);
380-
},
381-
}
382-
};
397+
let chain_tip =
398+
if let Some(tip) = latest_chain_tip_opt { tip } else { self.poll_chain_tip().await? };
383399

384400
let mut locked_header_cache = self.header_cache.lock().await;
385401
let chain_poller = ChainPoller::new(Arc::clone(&self.api_client), self.config.network);

src/chain/mod.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::sync::{Arc, RwLock};
1414
use std::time::Duration;
1515

1616
use bitcoin::{Script, Txid};
17-
use lightning::chain::Filter;
17+
use lightning::chain::{BestBlock, Filter};
1818
use lightning_block_sync::gossip::UtxoSource;
1919

2020
use crate::chain::bitcoind::BitcoindChainSource;
@@ -102,7 +102,7 @@ impl ChainSource {
102102
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
103103
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
104104
node_metrics: Arc<RwLock<NodeMetrics>>,
105-
) -> Self {
105+
) -> (Self, Option<BestBlock>) {
106106
let esplora_chain_source = EsploraChainSource::new(
107107
server_url,
108108
headers,
@@ -114,15 +114,15 @@ impl ChainSource {
114114
node_metrics,
115115
);
116116
let kind = ChainSourceKind::Esplora(esplora_chain_source);
117-
Self { kind, tx_broadcaster, logger }
117+
(Self { kind, tx_broadcaster, logger }, None)
118118
}
119119

120120
pub(crate) fn new_electrum(
121121
server_url: String, sync_config: ElectrumSyncConfig,
122122
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
123123
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
124124
node_metrics: Arc<RwLock<NodeMetrics>>,
125-
) -> Self {
125+
) -> (Self, Option<BestBlock>) {
126126
let electrum_chain_source = ElectrumChainSource::new(
127127
server_url,
128128
sync_config,
@@ -133,15 +133,15 @@ impl ChainSource {
133133
node_metrics,
134134
);
135135
let kind = ChainSourceKind::Electrum(electrum_chain_source);
136-
Self { kind, tx_broadcaster, logger }
136+
(Self { kind, tx_broadcaster, logger }, None)
137137
}
138138

139-
pub(crate) fn new_bitcoind_rpc(
139+
pub(crate) async fn new_bitcoind_rpc(
140140
rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String,
141141
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
142142
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
143143
node_metrics: Arc<RwLock<NodeMetrics>>,
144-
) -> Self {
144+
) -> (Self, Option<BestBlock>) {
145145
let bitcoind_chain_source = BitcoindChainSource::new_rpc(
146146
rpc_host,
147147
rpc_port,
@@ -153,16 +153,17 @@ impl ChainSource {
153153
Arc::clone(&logger),
154154
node_metrics,
155155
);
156+
let best_block = bitcoind_chain_source.poll_best_block().await.ok();
156157
let kind = ChainSourceKind::Bitcoind(bitcoind_chain_source);
157-
Self { kind, tx_broadcaster, logger }
158+
(Self { kind, tx_broadcaster, logger }, best_block)
158159
}
159160

160-
pub(crate) fn new_bitcoind_rest(
161+
pub(crate) async fn new_bitcoind_rest(
161162
rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String,
162163
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
163164
kv_store: Arc<DynStore>, config: Arc<Config>, rest_client_config: BitcoindRestClientConfig,
164165
logger: Arc<Logger>, node_metrics: Arc<RwLock<NodeMetrics>>,
165-
) -> Self {
166+
) -> (Self, Option<BestBlock>) {
166167
let bitcoind_chain_source = BitcoindChainSource::new_rest(
167168
rpc_host,
168169
rpc_port,
@@ -175,8 +176,9 @@ impl ChainSource {
175176
Arc::clone(&logger),
176177
node_metrics,
177178
);
179+
let best_block = bitcoind_chain_source.poll_best_block().await.ok();
178180
let kind = ChainSourceKind::Bitcoind(bitcoind_chain_source);
179-
Self { kind, tx_broadcaster, logger }
181+
(Self { kind, tx_broadcaster, logger }, best_block)
180182
}
181183

182184
pub(crate) fn start(&self, runtime: Arc<Runtime>) -> Result<(), Error> {

0 commit comments

Comments
 (0)