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