@@ -5,7 +5,7 @@ use crate::wallet::{num_blocks_from_conf_target, Wallet};
5
5
use crate :: { Error , KeysManager } ;
6
6
7
7
use lightning:: chain:: chaininterface:: { BroadcasterInterface , ConfirmationTarget , FeeEstimator } ;
8
- use lightning:: chain:: BestBlock ;
8
+ use lightning:: chain:: { self , BestBlock , Confirm , Filter , Listen } ;
9
9
use lightning:: impl_writeable_tlv_based;
10
10
use lightning:: sign:: { EntropySource , SpendableOutputDescriptor } ;
11
11
use lightning:: util:: ser:: Writeable ;
@@ -16,6 +16,8 @@ use bitcoin::{BlockHash, BlockHeader, LockTime, PackedLockTime, Script, Transact
16
16
use std:: ops:: Deref ;
17
17
use std:: sync:: { Arc , Mutex } ;
18
18
19
+ const CONSIDERED_SPENT_THRESHOLD_CONF : u32 = 6 ;
20
+
19
21
#[ derive( Clone , Debug , PartialEq , Eq ) ]
20
22
pub ( crate ) struct SpendableOutputInfo {
21
23
id : [ u8 ; 32 ] ,
@@ -33,29 +35,39 @@ impl_writeable_tlv_based!(SpendableOutputInfo, {
33
35
( 8 , confirmed_in_block, option) ,
34
36
} ) ;
35
37
36
- pub ( crate ) struct OutputSweeper < K : KVStore + Sync + Send , L : Deref >
38
+ pub ( crate ) struct OutputSweeper < K : KVStore + Sync + Send , F : Deref , L : Deref >
37
39
where
40
+ F :: Target : Filter ,
38
41
L :: Target : Logger ,
39
42
{
40
43
outputs : Mutex < Vec < SpendableOutputInfo > > ,
41
44
wallet : Arc < Wallet < bdk:: database:: SqliteDatabase , L > > ,
42
45
keys_manager : Arc < KeysManager > ,
43
46
kv_store : Arc < K > ,
44
47
best_block : Mutex < BestBlock > ,
48
+ chain_source : Option < F > ,
45
49
logger : L ,
46
50
}
47
51
48
- impl < K : KVStore + Sync + Send , L : Deref > OutputSweeper < K , L >
52
+ impl < K : KVStore + Sync + Send , F : Deref , L : Deref > OutputSweeper < K , F , L >
49
53
where
54
+ F :: Target : Filter ,
50
55
L :: Target : Logger ,
51
56
{
52
57
pub ( crate ) fn new (
53
58
outputs : Vec < SpendableOutputInfo > , wallet : Arc < Wallet < bdk:: database:: SqliteDatabase , L > > ,
54
- keys_manager : Arc < KeysManager > , kv_store : Arc < K > , best_block : BestBlock , logger : L ,
59
+ keys_manager : Arc < KeysManager > , kv_store : Arc < K > , best_block : BestBlock ,
60
+ chain_source : Option < F > , logger : L ,
55
61
) -> Self {
62
+ for o in & outputs {
63
+ if let Some ( filter) = chain_source. as_ref ( ) {
64
+ filter. register_tx ( & o. spending_tx . txid ( ) , & Script :: new ( ) )
65
+ }
66
+ }
67
+
56
68
let outputs = Mutex :: new ( outputs) ;
57
69
let best_block = Mutex :: new ( best_block) ;
58
- Self { outputs, wallet, keys_manager, kv_store, best_block, logger }
70
+ Self { outputs, wallet, keys_manager, kv_store, best_block, chain_source , logger }
59
71
}
60
72
61
73
pub ( crate ) fn add_outputs ( & self , output_descriptors : Vec < SpendableOutputDescriptor > ) {
64
76
let spending_tx = match self . get_spending_tx ( & output_descriptors) {
65
77
Ok ( Some ( spending_tx) ) => {
66
78
self . wallet . broadcast_transactions ( & [ & spending_tx] ) ;
79
+ if let Some ( filter) = self . chain_source . as_ref ( ) {
80
+ filter. register_tx ( & spending_tx. txid ( ) , & Script :: new ( ) )
81
+ }
67
82
spending_tx
68
83
}
69
84
Ok ( None ) => {
@@ -138,3 +153,145 @@ where
138
153
} )
139
154
}
140
155
}
156
+
157
+ impl < K : KVStore + Sync + Send , F : Deref , L : Deref > Listen for OutputSweeper < K , F , L >
158
+ where
159
+ F :: Target : Filter ,
160
+ L :: Target : Logger ,
161
+ {
162
+ fn filtered_block_connected (
163
+ & self , header : & BlockHeader , txdata : & chain:: transaction:: TransactionData , height : u32 ,
164
+ ) {
165
+ {
166
+ let best_block = self . best_block . lock ( ) . unwrap ( ) ;
167
+ assert_eq ! ( best_block. block_hash( ) , header. prev_blockhash,
168
+ "Blocks must be connected in chain-order - the connected header must build on the last connected header" ) ;
169
+ assert_eq ! ( best_block. height( ) , height - 1 ,
170
+ "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height" ) ;
171
+ }
172
+
173
+ self . transactions_confirmed ( header, txdata, height) ;
174
+ self . best_block_updated ( header, height) ;
175
+ }
176
+
177
+ fn block_disconnected ( & self , header : & BlockHeader , height : u32 ) {
178
+ let new_height = height - 1 ;
179
+ {
180
+ let mut best_block = self . best_block . lock ( ) . unwrap ( ) ;
181
+ assert_eq ! ( best_block. block_hash( ) , header. block_hash( ) ,
182
+ "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header" ) ;
183
+ assert_eq ! ( best_block. height( ) , height,
184
+ "Blocks must be disconnected in chain-order - the disconnected block must have the correct height" ) ;
185
+ * best_block = BestBlock :: new ( header. prev_blockhash , new_height)
186
+ }
187
+
188
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
189
+ for output_info in locked_outputs. iter_mut ( ) {
190
+ if output_info. confirmed_in_block == Some ( ( height, header. block_hash ( ) ) ) {
191
+ output_info. confirmed_in_block = None ;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ impl < K : KVStore + Sync + Send , F : Deref , L : Deref > Confirm for OutputSweeper < K , F , L >
198
+ where
199
+ F :: Target : Filter ,
200
+ L :: Target : Logger ,
201
+ {
202
+ fn transactions_confirmed (
203
+ & self , header : & BlockHeader , txdata : & chain:: transaction:: TransactionData , height : u32 ,
204
+ ) {
205
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
206
+ for ( _, tx) in txdata {
207
+ locked_outputs
208
+ . iter_mut ( )
209
+ . filter ( |o| o. spending_tx . txid ( ) == tx. txid ( ) )
210
+ . for_each ( |o| o. confirmed_in_block = Some ( ( height, header. block_hash ( ) ) ) ) ;
211
+ }
212
+ }
213
+
214
+ fn transaction_unconfirmed ( & self , txid : & Txid ) {
215
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
216
+
217
+ // Get what height was unconfirmed.
218
+ let unconf_height = locked_outputs
219
+ . iter ( )
220
+ . find ( |o| o. spending_tx . txid ( ) == * txid)
221
+ . and_then ( |o| o. confirmed_in_block )
222
+ . map ( |t| t. 0 ) ;
223
+
224
+ // Unconfirm all >= this height.
225
+ locked_outputs
226
+ . iter_mut ( )
227
+ . filter ( |o| o. confirmed_in_block . map ( |t| t. 0 ) >= unconf_height)
228
+ . for_each ( |o| o. confirmed_in_block = None ) ;
229
+ }
230
+
231
+ fn best_block_updated ( & self , header : & BlockHeader , height : u32 ) {
232
+ * self . best_block . lock ( ) . unwrap ( ) = BestBlock :: new ( header. block_hash ( ) , height) ;
233
+
234
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
235
+
236
+ // Rebroadcast all outputs that didn't get confirmed by now.
237
+ for output_info in locked_outputs. iter_mut ( ) . filter ( |o| o. confirmed_in_block . is_none ( ) ) {
238
+ if height
239
+ >= output_info. broadcast_height
240
+ + num_blocks_from_conf_target ( ConfirmationTarget :: Background )
241
+ {
242
+ let output_descriptors = vec ! [ output_info. descriptor. clone( ) ] ;
243
+ match self . get_spending_tx ( & output_descriptors) {
244
+ Ok ( Some ( spending_tx) ) => {
245
+ self . wallet . broadcast_transactions ( & [ & spending_tx] ) ;
246
+ if let Some ( filter) = self . chain_source . as_ref ( ) {
247
+ filter. register_tx ( & spending_tx. txid ( ) , & Script :: new ( ) )
248
+ }
249
+ output_info. spending_tx = spending_tx;
250
+ output_info. broadcast_height = height;
251
+ }
252
+ Ok ( None ) => {
253
+ log_debug ! (
254
+ self . logger,
255
+ "Omitted spending static outputs: {:?}" ,
256
+ output_descriptors
257
+ ) ;
258
+ }
259
+ Err ( err) => {
260
+ log_error ! ( self . logger, "Error spending outputs: {:?}" , err) ;
261
+ }
262
+ } ;
263
+ }
264
+ }
265
+
266
+ // Prune all outputs that have sufficient depth by now.
267
+ locked_outputs. retain ( |o| {
268
+ if let Some ( ( conf_height, _) ) = o. confirmed_in_block {
269
+ if height >= conf_height + CONSIDERED_SPENT_THRESHOLD_CONF {
270
+ let key = hex_utils:: to_string ( & o. id ) ;
271
+ match self . kv_store . remove ( SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE , & key) {
272
+ Ok ( _) => return false ,
273
+ Err ( e) => {
274
+ log_error ! (
275
+ self . logger,
276
+ "Removal of key {}/{} failed due to: {}" ,
277
+ SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE ,
278
+ key,
279
+ e
280
+ ) ;
281
+ return true ;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ true
287
+ } ) ;
288
+ }
289
+
290
+ fn get_relevant_txids ( & self ) -> Vec < ( Txid , Option < BlockHash > ) > {
291
+ let locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
292
+ locked_outputs
293
+ . iter ( )
294
+ . map ( |o| ( o. spending_tx . txid ( ) , o. confirmed_in_block . map ( |c| c. 1 ) ) )
295
+ . collect :: < Vec < _ > > ( )
296
+ }
297
+ }
0 commit comments