@@ -18,10 +18,14 @@ use bitcoin::hashes::hex::ToHex;
18
18
19
19
use crate :: ln:: chan_utils:: make_funding_redeemscript_from_slices;
20
20
use crate :: ln:: msgs:: { self , LightningError , ErrorAction } ;
21
+ use crate :: routing:: gossip:: { NetworkGraph , NodeId } ;
22
+ use crate :: util:: logger:: { Level , Logger } ;
21
23
use crate :: util:: ser:: Writeable ;
22
24
23
25
use crate :: prelude:: * ;
24
26
27
+ use alloc:: sync:: { Arc , Weak } ;
28
+ use crate :: sync:: Mutex ;
25
29
use core:: ops:: Deref ;
26
30
27
31
/// An error when accessing the chain via [`UtxoLookup`].
@@ -34,26 +38,117 @@ pub enum UtxoLookupError {
34
38
UnknownTx ,
35
39
}
36
40
41
+ /// The result of a [`UtxoLookup::get_utxo`] call. A call may resolve either synchronously,
42
+ /// returning the `Sync` variant, or asynchronously, returning an [`UtxoFuture`] in the `Async`
43
+ /// variant.
44
+ pub enum UtxoResult {
45
+ /// A result which was resolved synchronously. It either includes a [`TxOut`] for the output
46
+ /// requested or a [`UtxoLookupError`].
47
+ Sync ( Result < TxOut , UtxoLookupError > ) ,
48
+ /// A result which will be resolved asynchronously. It includes a [`UtxoFuture`], a `clone` of
49
+ /// which you must keep locally and call [`UtxoFuture::resolve`] on once the lookup completes.
50
+ ///
51
+ /// Note that in order to avoid runaway memory usage, the number of parallel checks is limited,
52
+ /// but only fairly loosely. Because a pending checks block all message processing, leaving
53
+ /// checks pending for an extended time may cause DoS of other functions. It is recommended you
54
+ /// keep a tight timeout on lookups, on the order of a few seconds.
55
+ Async ( UtxoFuture ) ,
56
+ }
57
+
37
58
/// The `UtxoLookup` trait defines behavior for accessing on-chain UTXOs.
38
59
pub trait UtxoLookup {
39
60
/// Returns the transaction output of a funding transaction encoded by [`short_channel_id`].
40
61
/// Returns an error if `genesis_hash` is for a different chain or if such a transaction output
41
62
/// is unknown.
42
63
///
43
64
/// [`short_channel_id`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#definition-of-short_channel_id
44
- fn get_utxo ( & self , genesis_hash : & BlockHash , short_channel_id : u64 ) -> Result < TxOut , UtxoLookupError > ;
65
+ fn get_utxo ( & self , genesis_hash : & BlockHash , short_channel_id : u64 ) -> UtxoResult ;
66
+ }
67
+
68
+ enum ChannelAnnouncement {
69
+ Full ( msgs:: ChannelAnnouncement ) ,
70
+ Unsigned ( msgs:: UnsignedChannelAnnouncement ) ,
71
+ }
72
+
73
+ struct UtxoMessages {
74
+ complete : Option < Result < TxOut , UtxoLookupError > > ,
75
+ channel_announce : Option < ChannelAnnouncement > ,
76
+ }
77
+
78
+ /// Represents a future resolution of a [`UtxoLookup::get_utxo`] query resolving async.
79
+ ///
80
+ /// See [`UtxoResult::Async`] and [`UtxoFuture::resolve`] for more info.
81
+ #[ derive( Clone ) ]
82
+ pub struct UtxoFuture {
83
+ state : Arc < Mutex < UtxoMessages > > ,
84
+ }
85
+
86
+ /// A trivial implementation of [`UtxoLookup`] which is used to call back into the network graph
87
+ /// once we have a concrete resolution of a request.
88
+ struct UtxoResolver ( Result < TxOut , UtxoLookupError > ) ;
89
+ impl UtxoLookup for UtxoResolver {
90
+ fn get_utxo ( & self , _genesis_hash : & BlockHash , _short_channel_id : u64 ) -> UtxoResult {
91
+ UtxoResult :: Sync ( self . 0 . clone ( ) )
92
+ }
93
+ }
94
+
95
+ impl UtxoFuture {
96
+ /// Builds a new future for later resolution.
97
+ pub fn new ( ) -> Self {
98
+ Self { state : Arc :: new ( Mutex :: new ( UtxoMessages {
99
+ complete : None ,
100
+ channel_announce : None ,
101
+ } ) ) }
102
+ }
103
+
104
+ /// Resolves this future against the given `graph` and with the given `result`.
105
+ pub fn resolve < L : Deref > ( & self , graph : & NetworkGraph < L > , result : Result < TxOut , UtxoLookupError > )
106
+ where L :: Target : Logger {
107
+ let announcement = {
108
+ let mut async_messages = self . state . lock ( ) . unwrap ( ) ;
109
+
110
+ if async_messages. channel_announce . is_none ( ) {
111
+ // We raced returning to `check_channel_announcement` which hasn't updated
112
+ // `channel_announce` yet. That's okay, we can set the `complete` field which it will
113
+ // check once it gets control again.
114
+ async_messages. complete = Some ( result) ;
115
+ return ;
116
+ }
117
+
118
+ async_messages. channel_announce . take ( ) . unwrap ( )
119
+ } ;
120
+
121
+ // Now that we've updated our internal state, pass the pending messages back through the
122
+ // network graph with a different `UtxoLookup` which will resolve immediately.
123
+ // Note that we ignore errors as we don't disconnect peers anyway, so there's nothing to do
124
+ // with them.
125
+ let resolver = UtxoResolver ( result) ;
126
+ match announcement {
127
+ ChannelAnnouncement :: Full ( signed_msg) => {
128
+ let _ = graph. update_channel_from_announcement ( & signed_msg, & Some ( & resolver) ) ;
129
+ } ,
130
+ ChannelAnnouncement :: Unsigned ( msg) => {
131
+ let _ = graph. update_channel_from_unsigned_announcement ( & msg, & Some ( & resolver) ) ;
132
+ } ,
133
+ }
134
+ }
135
+ }
136
+
137
+ /// A set of messages which are pending UTXO lookups for processing.
138
+ pub ( super ) struct PendingChecks {
45
139
}
46
140
47
- pub ( crate ) fn check_channel_announcement < U : Deref > (
48
- utxo_lookup : & Option < U > , msg : & msgs:: UnsignedChannelAnnouncement
49
- ) -> Result < Option < u64 > , msgs:: LightningError > where U :: Target : UtxoLookup {
50
- match utxo_lookup {
51
- & None => {
52
- // Tentatively accept, potentially exposing us to DoS attacks
53
- Ok ( None )
54
- } ,
55
- & Some ( ref utxo_lookup) => {
56
- match utxo_lookup. get_utxo ( & msg. chain_hash , msg. short_channel_id ) {
141
+ impl PendingChecks {
142
+ pub ( super ) fn new ( ) -> Self {
143
+ PendingChecks { }
144
+ }
145
+
146
+ pub ( super ) fn check_channel_announcement < U : Deref > ( & self ,
147
+ utxo_lookup : & Option < U > , msg : & msgs:: UnsignedChannelAnnouncement ,
148
+ full_msg : Option < & msgs:: ChannelAnnouncement >
149
+ ) -> Result < Option < u64 > , msgs:: LightningError > where U :: Target : UtxoLookup {
150
+ let handle_result = |res| {
151
+ match res {
57
152
Ok ( TxOut { value, script_pubkey } ) => {
58
153
let expected_script =
59
154
make_funding_redeemscript_from_slices ( msg. bitcoin_key_1 . as_slice ( ) , msg. bitcoin_key_2 . as_slice ( ) ) . to_v0_p2wsh ( ) ;
@@ -80,6 +175,34 @@ pub(crate) fn check_channel_announcement<U: Deref>(
80
175
} )
81
176
} ,
82
177
}
178
+ } ;
179
+
180
+ match utxo_lookup {
181
+ & None => {
182
+ // Tentatively accept, potentially exposing us to DoS attacks
183
+ Ok ( None )
184
+ } ,
185
+ & Some ( ref utxo_lookup) => {
186
+ match utxo_lookup. get_utxo ( & msg. chain_hash , msg. short_channel_id ) {
187
+ UtxoResult :: Sync ( res) => handle_result ( res) ,
188
+ UtxoResult :: Async ( future) => {
189
+ let mut async_messages = future. state . lock ( ) . unwrap ( ) ;
190
+ if let Some ( res) = async_messages. complete . take ( ) {
191
+ // In the unlikely event the future resolved before we managed to get it,
192
+ // handle the result in-line.
193
+ handle_result ( res)
194
+ } else {
195
+ async_messages. channel_announce = Some (
196
+ if let Some ( msg) = full_msg { ChannelAnnouncement :: Full ( msg. clone ( ) ) }
197
+ else { ChannelAnnouncement :: Unsigned ( msg. clone ( ) ) } ) ;
198
+ Err ( LightningError {
199
+ err : "Channel being checked async" . to_owned ( ) ,
200
+ action : ErrorAction :: IgnoreAndLog ( Level :: Gossip ) ,
201
+ } )
202
+ }
203
+ } ,
204
+ }
205
+ }
83
206
}
84
207
}
85
208
}
0 commit comments