Skip to main content

lightning_block_sync/
init.rs

1//! Utilities to assist in the initial sync required to initialize or reload Rust-Lightning objects
2//! from disk.
3
4use crate::async_poll::{MultiResultFuturePoller, ResultFuture};
5use crate::poll::{ChainPoller, Poll, Validate, ValidatedBlockHeader};
6use crate::{BlockData, BlockSource, BlockSourceResult, ChainNotifier, HeaderCache};
7
8use bitcoin::block::Header;
9use bitcoin::network::Network;
10
11use lightning::chain;
12use lightning::chain::BlockLocator;
13
14use std::ops::Deref;
15
16/// Returns a validated block header of the source's best chain tip.
17///
18/// Upon success, the returned header can be used to initialize [`SpvClient`]. Useful during a fresh
19/// start when there are no chain listeners to sync yet.
20///
21/// [`SpvClient`]: crate::SpvClient
22pub async fn validate_best_block_header<B: Deref>(
23	block_source: B,
24) -> BlockSourceResult<ValidatedBlockHeader>
25where
26	B::Target: BlockSource,
27{
28	let (best_block_hash, best_block_height) = block_source.get_best_block().await?;
29	block_source.get_header(&best_block_hash, best_block_height).await?.validate(best_block_hash)
30}
31
32/// Performs a one-time sync of chain listeners using a single *trusted* block source, bringing each
33/// listener's view of the chain from its paired block hash to `block_source`'s best chain tip.
34///
35/// Upon success, the returned header and header cache can be used to initialize [`SpvClient`]. In
36/// the case of failure, *each listener may be left at a different block hash than the one it was
37/// originally paired with*.
38///
39/// Thus, in case of errors you likely need to reload each object via deserialization or check its
40/// current tip directly via accessors on the object before trying again.
41///
42/// Useful during startup to bring the [`ChannelManager`] and each [`ChannelMonitor`] in sync before
43/// switching to [`SpvClient`]. For example:
44///
45/// ```
46/// use bitcoin::network::Network;
47///
48/// use lightning::chain;
49/// use lightning::chain::{BlockLocator, Watch};
50/// use lightning::chain::chainmonitor;
51/// use lightning::chain::chainmonitor::ChainMonitor;
52/// use lightning::chain::channelmonitor::ChannelMonitor;
53/// use lightning::chain::chaininterface::BroadcasterInterface;
54/// use lightning::chain::chaininterface::FeeEstimator;
55/// use lightning::ln::channelmanager::{ChannelManager, ChannelManagerReadArgs};
56/// use lightning::onion_message::messenger::MessageRouter;
57/// use lightning::routing::router::Router;
58/// use lightning::sign;
59/// use lightning::sign::{EntropySource, NodeSigner, SignerProvider};
60/// use lightning::util::config::UserConfig;
61/// use lightning::util::logger::Logger;
62/// use lightning::util::ser::ReadableArgs;
63///
64/// use lightning_block_sync::*;
65///
66/// use lightning::io::Cursor;
67///
68/// async fn init_sync<
69/// 	B: BlockSource,
70/// 	ES: EntropySource,
71/// 	NS: NodeSigner,
72/// 	SP: SignerProvider,
73/// 	T: BroadcasterInterface,
74/// 	F: FeeEstimator,
75/// 	R: Router,
76/// 	MR: MessageRouter,
77/// 	L: Logger,
78/// 	C: chain::Filter,
79/// 	P: chainmonitor::Persist<SP::EcdsaSigner>,
80/// >(
81/// 	block_source: &B,
82/// 	chain_monitor: &ChainMonitor<SP::EcdsaSigner, &C, &T, &F, &L, &P, &ES>,
83/// 	config: UserConfig,
84/// 	entropy_source: &ES,
85/// 	node_signer: &NS,
86/// 	signer_provider: &SP,
87/// 	tx_broadcaster: &T,
88/// 	fee_estimator: &F,
89/// 	router: &R,
90/// 	message_router: &MR,
91/// 	logger: &L,
92/// 	persister: &P,
93/// ) {
94/// 	// Read a serialized channel monitor paired with the best block when it was persisted.
95/// 	let serialized_monitor = "...";
96/// 	let (monitor_best_block, mut monitor) = <(BlockLocator, ChannelMonitor<SP::EcdsaSigner>)>::read(
97/// 		&mut Cursor::new(&serialized_monitor), (entropy_source, signer_provider)).unwrap();
98///
99/// 	// Read the channel manager paired with the best block when it was persisted.
100/// 	let serialized_manager = "...";
101/// 	let (manager_best_block, mut manager) = {
102/// 		let read_args = ChannelManagerReadArgs::new(
103/// 			entropy_source,
104/// 			node_signer,
105/// 			signer_provider,
106/// 			fee_estimator,
107/// 			chain_monitor,
108/// 			tx_broadcaster,
109/// 			router,
110/// 			message_router,
111/// 			logger,
112/// 			config,
113/// 			vec![&mut monitor],
114/// 		);
115/// 		<(BlockLocator, ChannelManager<&ChainMonitor<SP::EcdsaSigner, &C, &T, &F, &L, &P, &ES>, &T, &ES, &NS, &SP, &F, &R, &MR, &L>)>::read(
116/// 			&mut Cursor::new(&serialized_manager), read_args).unwrap()
117/// 	};
118///
119/// 	// Synchronize any channel monitors and the channel manager to be on the best block.
120/// 	let mut monitor_listener = (monitor, &*tx_broadcaster, &*fee_estimator, &*logger);
121/// 	let listeners = vec![
122/// 		(monitor_best_block, &monitor_listener as &dyn chain::Listen),
123/// 		(manager_best_block, &manager as &dyn chain::Listen),
124/// 	];
125/// 	let (chain_cache, chain_tip) = init::synchronize_listeners(
126/// 		block_source, Network::Bitcoin, listeners).await.unwrap();
127///
128/// 	// Allow the chain monitor to watch any channels.
129/// 	let monitor = monitor_listener.0;
130/// 	chain_monitor.watch_channel(monitor.channel_id(), monitor);
131///
132/// 	// Create an SPV client to notify the chain monitor and channel manager of block events.
133/// 	let chain_poller = poll::ChainPoller::new(block_source, Network::Bitcoin);
134/// 	let mut chain_listener = (chain_monitor, &manager);
135/// 	let spv_client = SpvClient::new(chain_tip, chain_poller, chain_cache, &chain_listener);
136/// }
137/// ```
138///
139/// [`SpvClient`]: crate::SpvClient
140/// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager
141/// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor
142pub async fn synchronize_listeners<B: Deref + Sized + Send + Sync, L: chain::Listen + ?Sized>(
143	block_source: B, network: Network, mut chain_listeners: Vec<(BlockLocator, &L)>,
144) -> BlockSourceResult<(HeaderCache, ValidatedBlockHeader)>
145where
146	B::Target: BlockSource,
147{
148	let best_header = validate_best_block_header(&*block_source).await?;
149
150	// Find differences and disconnect blocks for each listener individually.
151	let mut chain_poller = ChainPoller::new(block_source, network);
152	let mut chain_listeners_at_height = Vec::new();
153	let mut most_connected_blocks = Vec::new();
154	let mut header_cache = HeaderCache::new();
155	header_cache.retain_on_disconnect = true;
156	for (old_best_block, chain_listener) in chain_listeners.drain(..) {
157		// Disconnect any stale blocks, but keep them in the cache for the next iteration.
158		let (common_ancestor, connected_blocks) = {
159			let chain_listener = &DynamicChainListener(chain_listener);
160			let mut chain_notifier =
161				ChainNotifier { header_cache: &mut header_cache, chain_listener };
162			let difference = chain_notifier
163				.find_difference_from_best_block(best_header, old_best_block, &mut chain_poller)
164				.await?;
165			if difference.common_ancestor.block_hash != old_best_block.block_hash {
166				chain_notifier.disconnect_blocks(difference.common_ancestor);
167			}
168			(difference.common_ancestor, difference.connected_blocks)
169		};
170
171		// Keep track of the most common ancestor and all blocks connected across all listeners.
172		chain_listeners_at_height.push((common_ancestor.height, chain_listener));
173		if connected_blocks.len() > most_connected_blocks.len() {
174			most_connected_blocks = connected_blocks;
175		}
176	}
177
178	while !most_connected_blocks.is_empty() {
179		#[cfg(not(test))]
180		const MAX_BLOCKS_AT_ONCE: usize = 6 * 6; // Six hours of blocks, 144MiB encoded
181		#[cfg(test)]
182		const MAX_BLOCKS_AT_ONCE: usize = 2;
183
184		let mut fetch_block_futures =
185			Vec::with_capacity(core::cmp::min(MAX_BLOCKS_AT_ONCE, most_connected_blocks.len()));
186		for header in most_connected_blocks.iter().rev().take(MAX_BLOCKS_AT_ONCE) {
187			let fetch_future = chain_poller.fetch_block(header);
188			fetch_block_futures
189				.push(ResultFuture::Pending(Box::pin(async move { (header, fetch_future.await) })));
190		}
191		let results = MultiResultFuturePoller::new(fetch_block_futures).await.into_iter();
192
193		const NO_BLOCK: Option<(u32, crate::poll::ValidatedBlock)> = None;
194		let mut fetched_blocks = [NO_BLOCK; MAX_BLOCKS_AT_ONCE];
195		for ((header, block_res), result) in results.into_iter().zip(fetched_blocks.iter_mut()) {
196			let block = block_res?;
197			header_cache.block_connected(header.block_hash, *header);
198			*result = Some((header.height, block));
199		}
200		debug_assert!(fetched_blocks.iter().take(most_connected_blocks.len()).all(|r| r.is_some()));
201		// TODO: When our MSRV is 1.82, use is_sorted_by_key
202		debug_assert!(fetched_blocks.windows(2).all(|blocks| {
203			if let (Some(a), Some(b)) = (&blocks[0], &blocks[1]) {
204				a.0 < b.0
205			} else {
206				// Any non-None blocks have to come before any None entries
207				blocks[1].is_none()
208			}
209		}));
210
211		for (listener_height, listener) in chain_listeners_at_height.iter() {
212			// Connect blocks for this listener.
213			for (height, block_data) in fetched_blocks.iter().flatten() {
214				if *height > *listener_height {
215					match &**block_data {
216						BlockData::FullBlock(block) => {
217							listener.block_connected(&block, *height);
218						},
219						BlockData::HeaderOnly(header_data) => {
220							listener.filtered_block_connected(&header_data, &[], *height);
221						},
222					}
223				}
224			}
225		}
226
227		most_connected_blocks
228			.truncate(most_connected_blocks.len().saturating_sub(MAX_BLOCKS_AT_ONCE));
229	}
230
231	header_cache.retain_on_disconnect = false;
232	Ok((header_cache, best_header))
233}
234
235/// Wrapper for supporting dynamically sized chain listeners.
236struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L);
237
238impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> {
239	fn filtered_block_connected(
240		&self, _header: &Header, _txdata: &chain::transaction::TransactionData, _height: u32,
241	) {
242		unreachable!()
243	}
244
245	fn blocks_disconnected(&self, fork_point: BlockLocator) {
246		self.0.blocks_disconnected(fork_point)
247	}
248}
249
250#[cfg(test)]
251mod tests {
252	use super::*;
253	use crate::test_utils::{Blockchain, MockChainListener};
254
255	#[tokio::test]
256	async fn sync_from_same_chain() {
257		let chain = Blockchain::default().with_height(4);
258
259		let listener_1 = MockChainListener::new()
260			.expect_block_connected(*chain.at_height(2))
261			.expect_block_connected(*chain.at_height(3))
262			.expect_block_connected(*chain.at_height(4));
263		let listener_2 = MockChainListener::new()
264			.expect_block_connected(*chain.at_height(3))
265			.expect_block_connected(*chain.at_height(4));
266		let listener_3 = MockChainListener::new().expect_block_connected(*chain.at_height(4));
267
268		let listeners = vec![
269			(chain.block_locator_at_height(1), &listener_1 as &dyn chain::Listen),
270			(chain.block_locator_at_height(2), &listener_2 as &dyn chain::Listen),
271			(chain.block_locator_at_height(3), &listener_3 as &dyn chain::Listen),
272		];
273		match synchronize_listeners(&chain, Network::Bitcoin, listeners).await {
274			Ok((cache, header)) => {
275				assert_eq!(header, chain.tip());
276				assert!(cache.look_up(&chain.at_height(1).block_hash).is_some());
277				assert!(cache.look_up(&chain.at_height(2).block_hash).is_some());
278				assert!(cache.look_up(&chain.at_height(3).block_hash).is_some());
279				assert!(cache.look_up(&chain.at_height(4).block_hash).is_some());
280			},
281			Err(e) => panic!("Unexpected error: {:?}", e),
282		}
283	}
284
285	#[tokio::test]
286	async fn sync_from_different_chains() {
287		let main_chain = Blockchain::default().with_height(4);
288		let fork_chain_1 = main_chain.fork_at_height(1);
289		let fork_chain_2 = main_chain.fork_at_height(2);
290		let fork_chain_3 = main_chain.fork_at_height(3);
291
292		let listener_1 = MockChainListener::new()
293			.expect_blocks_disconnected(*fork_chain_1.at_height(1))
294			.expect_block_connected(*main_chain.at_height(2))
295			.expect_block_connected(*main_chain.at_height(3))
296			.expect_block_connected(*main_chain.at_height(4));
297		let listener_2 = MockChainListener::new()
298			.expect_blocks_disconnected(*fork_chain_2.at_height(2))
299			.expect_block_connected(*main_chain.at_height(3))
300			.expect_block_connected(*main_chain.at_height(4));
301		let listener_3 = MockChainListener::new()
302			.expect_blocks_disconnected(*fork_chain_3.at_height(3))
303			.expect_block_connected(*main_chain.at_height(4));
304
305		let listeners = vec![
306			(fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
307			(fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
308			(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
309		];
310		match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
311			Ok((cache, header)) => {
312				assert_eq!(header, main_chain.tip());
313				assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
314				assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
315				assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
316				assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
317				assert!(cache.look_up(&fork_chain_2.at_height(3).block_hash).is_none());
318				assert!(cache.look_up(&fork_chain_3.at_height(4).block_hash).is_none());
319			},
320			Err(e) => panic!("Unexpected error: {:?}", e),
321		}
322	}
323
324	#[tokio::test]
325	async fn sync_from_overlapping_chains() {
326		let main_chain = Blockchain::default().with_height(4);
327		let fork_chain_1 = main_chain.fork_at_height(1);
328		let fork_chain_2 = fork_chain_1.fork_at_height(2);
329		let fork_chain_3 = fork_chain_2.fork_at_height(3);
330
331		let listener_1 = MockChainListener::new()
332			.expect_blocks_disconnected(*fork_chain_1.at_height(1))
333			.expect_block_connected(*main_chain.at_height(2))
334			.expect_block_connected(*main_chain.at_height(3))
335			.expect_block_connected(*main_chain.at_height(4));
336		let listener_2 = MockChainListener::new()
337			.expect_blocks_disconnected(*fork_chain_2.at_height(1))
338			.expect_block_connected(*main_chain.at_height(2))
339			.expect_block_connected(*main_chain.at_height(3))
340			.expect_block_connected(*main_chain.at_height(4));
341		let listener_3 = MockChainListener::new()
342			.expect_blocks_disconnected(*fork_chain_3.at_height(1))
343			.expect_block_connected(*main_chain.at_height(2))
344			.expect_block_connected(*main_chain.at_height(3))
345			.expect_block_connected(*main_chain.at_height(4));
346
347		let listeners = vec![
348			(fork_chain_1.best_block(), &listener_1 as &dyn chain::Listen),
349			(fork_chain_2.best_block(), &listener_2 as &dyn chain::Listen),
350			(fork_chain_3.best_block(), &listener_3 as &dyn chain::Listen),
351		];
352		match synchronize_listeners(&main_chain, Network::Bitcoin, listeners).await {
353			Ok((cache, header)) => {
354				assert_eq!(header, main_chain.tip());
355				assert!(cache.look_up(&main_chain.at_height(1).block_hash).is_some());
356				assert!(cache.look_up(&main_chain.at_height(2).block_hash).is_some());
357				assert!(cache.look_up(&main_chain.at_height(3).block_hash).is_some());
358				assert!(cache.look_up(&main_chain.at_height(4).block_hash).is_some());
359				assert!(cache.look_up(&fork_chain_1.at_height(2).block_hash).is_none());
360				assert!(cache.look_up(&fork_chain_1.at_height(3).block_hash).is_none());
361				assert!(cache.look_up(&fork_chain_1.at_height(4).block_hash).is_none());
362			},
363			Err(e) => panic!("Unexpected error: {:?}", e),
364		}
365	}
366}