Skip to content

Commit 8cf0271

Browse files
committed
Fail UTXO lookups if the block doesn't have five confirmations
The BOLT spec mandates that channels not be announced until they have at least six confirmations. This is important to enforce not because we particularly care about any specific DoS concerns, but because if we do not we may have to handle reorgs of channel funding transactions which change their SCID or have conflicting SCIDs.
1 parent 3e99307 commit 8cf0271

File tree

1 file changed

+75
-2
lines changed

1 file changed

+75
-2
lines changed

lightning-block-sync/src/gossip.rs

+75-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! current UTXO set. This module defines an implementation of the LDK API required to do so
33
//! against a [`BlockSource`] which implements a few additional methods for accessing the UTXO set.
44
5-
use crate::{AsyncBlockSourceResult, BlockData, BlockSource};
5+
use crate::{AsyncBlockSourceResult, BlockData, BlockSource, BlockSourceError};
66

77
use bitcoin::blockdata::block::Block;
88
use bitcoin::blockdata::transaction::{TxOut, OutPoint};
@@ -22,6 +22,8 @@ use std::sync::{Arc, Mutex};
2222
use std::collections::VecDeque;
2323
use std::future::Future;
2424
use std::ops::Deref;
25+
use std::pin::Pin;
26+
use std::task::Poll;
2527

2628
/// A trait which extends [`BlockSource`] and can be queried to fetch the block at a given height
2729
/// as well as whether a given output is unspent (i.e. a member of the current UTXO set).
@@ -62,6 +64,65 @@ impl FutureSpawner for TokioSpawner {
6264
}
6365
}
6466

67+
/// A trivial future which joins two other futures and polls them at the same time, returning only
68+
/// once both complete.
69+
pub(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+
79+
impl<
80+
A: Future<Output=Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
81+
B: Future<Output=Result<BlockHash, BlockSourceError>> + Unpin,
82+
> Joiner<A, B> {
83+
fn new(a: A, b: B) -> Self { Self { a, b, a_res: None, b_res: None } }
84+
}
85+
86+
impl<
87+
A: Future<Output=Result<(BlockHash, Option<u32>), BlockSourceError>> + Unpin,
88+
B: Future<Output=Result<BlockHash, BlockSourceError>> + Unpin,
89+
> Future for Joiner<A, B> {
90+
type Output = Result<((BlockHash, Option<u32>), BlockHash), BlockSourceError>;
91+
fn poll(mut self: Pin<&mut Self>, ctx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
92+
if self.a_res.is_none() {
93+
match Pin::new(&mut self.a).poll(ctx) {
94+
Poll::Ready(res) => {
95+
if let Ok(ok) = res {
96+
self.a_res = Some(ok);
97+
} else {
98+
return Poll::Ready(Err(res.unwrap_err()));
99+
}
100+
},
101+
Poll::Pending => {},
102+
}
103+
}
104+
if self.b_res.is_none() {
105+
match Pin::new(&mut self.b).poll(ctx) {
106+
Poll::Ready(res) => {
107+
if let Ok(ok) = res {
108+
self.b_res = Some(ok);
109+
} else {
110+
return Poll::Ready(Err(res.unwrap_err()));
111+
}
112+
113+
},
114+
Poll::Pending => {},
115+
}
116+
}
117+
if let Some(b_res) = self.b_res {
118+
if let Some(a_res) = self.a_res {
119+
return Poll::Ready(Ok((a_res, b_res)))
120+
}
121+
}
122+
Poll::Pending
123+
}
124+
}
125+
65126
/// A struct which wraps a [`UtxoSource`] and a few LDK objects and implements the LDK
66127
/// [`UtxoLookup`] trait.
67128
///
@@ -156,8 +217,20 @@ impl<S: FutureSpawner,
156217
}
157218
}
158219

159-
let block_hash = source.get_block_hash_by_height(block_height).await
220+
let ((_, tip_height_opt), block_hash) =
221+
Joiner::new(source.get_best_block(), source.get_block_hash_by_height(block_height))
222+
.await
160223
.map_err(|_| UtxoLookupError::UnknownTx)?;
224+
if let Some(tip_height) = tip_height_opt {
225+
// If the block doesn't yet have five confirmations, error out.
226+
//
227+
// The BOLT spec requires nodes wait for six confirmations before announcing a
228+
// channel, and we give them one block of headroom in case we're delayed seeing a
229+
// block.
230+
if block_height + 5 > tip_height {
231+
return Err(UtxoLookupError::UnknownTx);
232+
}
233+
}
161234
let block_data = source.get_block(&block_hash).await
162235
.map_err(|_| UtxoLookupError::UnknownTx)?;
163236
let block = match block_data {

0 commit comments

Comments
 (0)