lightning_block_sync/
gossip.rs

1//! When fetching gossip from peers, lightning nodes need to validate that gossip against the
2//! current UTXO set. This module defines an implementation of the LDK API required to do so
3//! against a [`BlockSource`] which implements a few additional methods for accessing the UTXO set.
4
5use crate::{AsyncBlockSourceResult, BlockData, BlockSource, BlockSourceError};
6
7use bitcoin::block::Block;
8use bitcoin::constants::ChainHash;
9use bitcoin::hash_types::BlockHash;
10use bitcoin::transaction::{OutPoint, TxOut};
11
12use lightning::ln::peer_handler::APeerManager;
13
14use lightning::routing::gossip::{NetworkGraph, P2PGossipSync};
15use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoLookupError, UtxoResult};
16
17use lightning::util::logger::Logger;
18
19use std::collections::VecDeque;
20use std::future::Future;
21use std::ops::Deref;
22use std::pin::Pin;
23use std::sync::{Arc, Mutex};
24use std::task::Poll;
25
26/// A trait which extends [`BlockSource`] and can be queried to fetch the block at a given height
27/// as well as whether a given output is unspent (i.e. a member of the current UTXO set).
28///
29/// Note that while this is implementable for a [`BlockSource`] which returns filtered block data
30/// (i.e. [`BlockData::HeaderOnly`] for [`BlockSource::get_block`] requests), such an
31/// implementation will reject all gossip as it is not fully able to verify the UTXOs referenced.
32pub trait UtxoSource: BlockSource + 'static {
33	/// Fetches the block hash of the block at the given height.
34	///
35	/// This will, in turn, be passed to to [`BlockSource::get_block`] to fetch the block needed
36	/// for gossip validation.
37	fn get_block_hash_by_height<'a>(
38		&'a self, block_height: u32,
39	) -> AsyncBlockSourceResult<'a, BlockHash>;
40
41	/// Returns true if the given output has *not* been spent, i.e. is a member of the current UTXO
42	/// set.
43	fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool>;
44}
45
46/// A generic trait which is able to spawn futures in the background.
47///
48/// If the `tokio` feature is enabled, this is implemented on `TokioSpawner` struct which
49/// delegates to `tokio::spawn()`.
50pub trait FutureSpawner: Send + Sync + 'static {
51	/// Spawns the given future as a background task.
52	///
53	/// This method MUST NOT block on the given future immediately.
54	fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T);
55}
56
57#[cfg(feature = "tokio")]
58/// A trivial [`FutureSpawner`] which delegates to `tokio::spawn`.
59pub struct TokioSpawner;
60#[cfg(feature = "tokio")]
61impl FutureSpawner for TokioSpawner {
62	fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
63		tokio::spawn(future);
64	}
65}
66
67/// A trivial future which joins two other futures and polls them at the same time, returning only
68/// once both complete.
69pub(crate) struct Joiner<
70	A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
71	B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
72> {
73	pub a: A,
74	pub b: B,
75	a_res: Option<(BlockHash, Option<u32>)>,
76	b_res: Option<BlockHash>,
77}
78
79impl<
80		A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
81		B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
82	> Joiner<A, B>
83{
84	fn new(a: A, b: B) -> Self {
85		Self { a, b, a_res: None, b_res: None }
86	}
87}
88
89impl<
90		A: Future<Output = Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
91		B: Future<Output = Result<BlockHash, BlockSourceError>> + Unpin,
92	> Future for Joiner<A, B>
93{
94	type Output = Result<((BlockHash, Option<u32>), BlockHash), BlockSourceError>;
95	fn poll(mut self: Pin<&mut Self>, ctx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
96		if self.a_res.is_none() {
97			match Pin::new(&mut self.a).poll(ctx) {
98				Poll::Ready(res) => {
99					if let Ok(ok) = res {
100						self.a_res = Some(ok);
101					} else {
102						return Poll::Ready(Err(res.unwrap_err()));
103					}
104				},
105				Poll::Pending => {},
106			}
107		}
108		if self.b_res.is_none() {
109			match Pin::new(&mut self.b).poll(ctx) {
110				Poll::Ready(res) => {
111					if let Ok(ok) = res {
112						self.b_res = Some(ok);
113					} else {
114						return Poll::Ready(Err(res.unwrap_err()));
115					}
116				},
117				Poll::Pending => {},
118			}
119		}
120		if let Some(b_res) = self.b_res {
121			if let Some(a_res) = self.a_res {
122				return Poll::Ready(Ok((a_res, b_res)));
123			}
124		}
125		Poll::Pending
126	}
127}
128
129/// A struct which wraps a [`UtxoSource`] and a few LDK objects and implements the LDK
130/// [`UtxoLookup`] trait.
131///
132/// Note that if you're using this against a Bitcoin Core REST or RPC server, you likely wish to
133/// increase the `rpcworkqueue` setting in Bitcoin Core as LDK attempts to parallelize requests (a
134/// value of 1024 should more than suffice), and ensure you have sufficient file descriptors
135/// available on both Bitcoin Core and your LDK application for each request to hold its own
136/// connection.
137pub struct GossipVerifier<
138	S: FutureSpawner,
139	Blocks: Deref + Send + Sync + 'static + Clone,
140	L: Deref + Send + Sync + 'static,
141> where
142	Blocks::Target: UtxoSource,
143	L::Target: Logger,
144{
145	source: Blocks,
146	peer_manager_wake: Arc<dyn Fn() + Send + Sync>,
147	gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Arc<Self>, L>>,
148	spawn: S,
149	block_cache: Arc<Mutex<VecDeque<(u32, Block)>>>,
150}
151
152const BLOCK_CACHE_SIZE: usize = 5;
153
154impl<S: FutureSpawner, Blocks: Deref + Send + Sync + Clone, L: Deref + Send + Sync>
155	GossipVerifier<S, Blocks, L>
156where
157	Blocks::Target: UtxoSource,
158	L::Target: Logger,
159{
160	/// Constructs a new [`GossipVerifier`].
161	///
162	/// This is expected to be given to a [`P2PGossipSync`] (initially constructed with `None` for
163	/// the UTXO lookup) via [`P2PGossipSync::add_utxo_lookup`].
164	pub fn new<APM: Deref + Send + Sync + Clone + 'static>(
165		source: Blocks, spawn: S, gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Arc<Self>, L>>,
166		peer_manager: APM,
167	) -> Self
168	where
169		APM::Target: APeerManager,
170	{
171		let peer_manager_wake = Arc::new(move || peer_manager.as_ref().process_events());
172		Self {
173			source,
174			spawn,
175			gossiper,
176			peer_manager_wake,
177			block_cache: Arc::new(Mutex::new(VecDeque::with_capacity(BLOCK_CACHE_SIZE))),
178		}
179	}
180
181	async fn retrieve_utxo(
182		source: Blocks, block_cache: Arc<Mutex<VecDeque<(u32, Block)>>>, short_channel_id: u64,
183	) -> Result<TxOut, UtxoLookupError> {
184		let block_height = (short_channel_id >> 5 * 8) as u32; // block height is most significant three bytes
185		let transaction_index = ((short_channel_id >> 2 * 8) & 0xffffff) as u32;
186		let output_index = (short_channel_id & 0xffff) as u16;
187
188		let (outpoint, output);
189
190		'tx_found: loop {
191			macro_rules! process_block {
192				($block: expr) => {{
193					if transaction_index as usize >= $block.txdata.len() {
194						return Err(UtxoLookupError::UnknownTx);
195					}
196					let transaction = &$block.txdata[transaction_index as usize];
197					if output_index as usize >= transaction.output.len() {
198						return Err(UtxoLookupError::UnknownTx);
199					}
200
201					outpoint = OutPoint::new(transaction.compute_txid(), output_index.into());
202					output = transaction.output[output_index as usize].clone();
203				}};
204			}
205			{
206				let recent_blocks = block_cache.lock().unwrap();
207				for (height, block) in recent_blocks.iter() {
208					if *height == block_height {
209						process_block!(block);
210						break 'tx_found;
211					}
212				}
213			}
214
215			let ((_, tip_height_opt), block_hash) =
216				Joiner::new(source.get_best_block(), source.get_block_hash_by_height(block_height))
217					.await
218					.map_err(|_| UtxoLookupError::UnknownTx)?;
219			if let Some(tip_height) = tip_height_opt {
220				// If the block doesn't yet have five confirmations, error out.
221				//
222				// The BOLT spec requires nodes wait for six confirmations before announcing a
223				// channel, and we give them one block of headroom in case we're delayed seeing a
224				// block.
225				if block_height + 5 > tip_height {
226					return Err(UtxoLookupError::UnknownTx);
227				}
228			}
229			let block_data =
230				source.get_block(&block_hash).await.map_err(|_| UtxoLookupError::UnknownTx)?;
231			let block = match block_data {
232				BlockData::HeaderOnly(_) => return Err(UtxoLookupError::UnknownTx),
233				BlockData::FullBlock(block) => block,
234			};
235			process_block!(block);
236			{
237				let mut recent_blocks = block_cache.lock().unwrap();
238				let mut insert = true;
239				for (height, _) in recent_blocks.iter() {
240					if *height == block_height {
241						insert = false;
242					}
243				}
244				if insert {
245					if recent_blocks.len() >= BLOCK_CACHE_SIZE {
246						recent_blocks.pop_front();
247					}
248					recent_blocks.push_back((block_height, block));
249				}
250			}
251			break 'tx_found;
252		}
253		let outpoint_unspent =
254			source.is_output_unspent(outpoint).await.map_err(|_| UtxoLookupError::UnknownTx)?;
255		if outpoint_unspent {
256			Ok(output)
257		} else {
258			Err(UtxoLookupError::UnknownTx)
259		}
260	}
261}
262
263impl<S: FutureSpawner, Blocks: Deref + Send + Sync + Clone, L: Deref + Send + Sync> Deref
264	for GossipVerifier<S, Blocks, L>
265where
266	Blocks::Target: UtxoSource,
267	L::Target: Logger,
268{
269	type Target = Self;
270	fn deref(&self) -> &Self {
271		self
272	}
273}
274
275impl<S: FutureSpawner, Blocks: Deref + Send + Sync + Clone, L: Deref + Send + Sync> UtxoLookup
276	for GossipVerifier<S, Blocks, L>
277where
278	Blocks::Target: UtxoSource,
279	L::Target: Logger,
280{
281	fn get_utxo(&self, _chain_hash: &ChainHash, short_channel_id: u64) -> UtxoResult {
282		let res = UtxoFuture::new();
283		let fut = res.clone();
284		let source = self.source.clone();
285		let gossiper = Arc::clone(&self.gossiper);
286		let block_cache = Arc::clone(&self.block_cache);
287		let pmw = Arc::clone(&self.peer_manager_wake);
288		self.spawn.spawn(async move {
289			let res = Self::retrieve_utxo(source, block_cache, short_channel_id).await;
290			fut.resolve(gossiper.network_graph(), &*gossiper, res);
291			(pmw)();
292		});
293		UtxoResult::Async(res)
294	}
295}