1
1
use crate :: hex_utils;
2
2
use crate :: io:: { KVStore , SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE } ;
3
3
use crate :: logger:: { log_debug, log_error, Logger } ;
4
- use crate :: wallet:: Wallet ;
4
+ 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 ;
12
12
13
13
use bitcoin:: secp256k1:: Secp256k1 ;
14
- use bitcoin:: { BlockHash , LockTime , PackedLockTime , Transaction } ;
14
+ use bitcoin:: { BlockHash , BlockHeader , LockTime , PackedLockTime , Script , Transaction , Txid } ;
15
15
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,42 @@ 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
+ if let Some ( filter) = chain_source. as_ref ( ) {
63
+ for o in & outputs {
64
+ if let Some ( tx) = o. spending_tx . as_ref ( ) {
65
+ // TODO: can we give something better than the empty script here?
66
+ filter. register_tx ( & tx. txid ( ) , & Script :: new ( ) )
67
+ }
68
+ }
69
+ }
70
+
56
71
let outputs = Mutex :: new ( outputs) ;
57
72
let best_block = Mutex :: new ( best_block) ;
58
- Self { outputs, wallet, keys_manager, kv_store, best_block, logger }
73
+ Self { outputs, wallet, keys_manager, kv_store, best_block, chain_source , logger }
59
74
}
60
75
61
76
pub ( crate ) fn add_outputs ( & self , output_descriptors : Vec < SpendableOutputDescriptor > ) {
66
81
let ( spending_tx, broadcast_height) = match self . get_spending_tx ( & output_descriptors, cur_height) {
67
82
Ok ( Some ( spending_tx) ) => {
68
83
self . wallet . broadcast_transactions ( & [ & spending_tx] ) ;
84
+ if let Some ( filter) = self . chain_source . as_ref ( ) {
85
+ filter. register_tx ( & spending_tx. txid ( ) , & Script :: new ( ) )
86
+ }
69
87
( Some ( spending_tx) , Some ( cur_height) )
70
88
}
71
89
Ok ( None ) => {
@@ -139,3 +157,189 @@ where
139
157
} )
140
158
}
141
159
}
160
+
161
+ impl < K : KVStore + Sync + Send , F : Deref , L : Deref > Listen for OutputSweeper < K , F , L >
162
+ where
163
+ F :: Target : Filter ,
164
+ L :: Target : Logger ,
165
+ {
166
+ fn filtered_block_connected (
167
+ & self , header : & BlockHeader , txdata : & chain:: transaction:: TransactionData , height : u32 ,
168
+ ) {
169
+ {
170
+ let best_block = self . best_block . lock ( ) . unwrap ( ) ;
171
+ assert_eq ! ( best_block. block_hash( ) , header. prev_blockhash,
172
+ "Blocks must be connected in chain-order - the connected header must build on the last connected header" ) ;
173
+ assert_eq ! ( best_block. height( ) , height - 1 ,
174
+ "Blocks must be connected in chain-order - the connected block height must be one greater than the previous height" ) ;
175
+ }
176
+
177
+ self . transactions_confirmed ( header, txdata, height) ;
178
+ self . best_block_updated ( header, height) ;
179
+ }
180
+
181
+ fn block_disconnected ( & self , header : & BlockHeader , height : u32 ) {
182
+ let new_height = height - 1 ;
183
+ {
184
+ let mut best_block = self . best_block . lock ( ) . unwrap ( ) ;
185
+ assert_eq ! ( best_block. block_hash( ) , header. block_hash( ) ,
186
+ "Blocks must be disconnected in chain-order - the disconnected header must be the last connected header" ) ;
187
+ assert_eq ! ( best_block. height( ) , height,
188
+ "Blocks must be disconnected in chain-order - the disconnected block must have the correct height" ) ;
189
+ * best_block = BestBlock :: new ( header. prev_blockhash , new_height)
190
+ }
191
+
192
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
193
+ for output_info in locked_outputs. iter_mut ( ) {
194
+ if output_info. confirmed_in_block == Some ( ( height, header. block_hash ( ) ) ) {
195
+ output_info. confirmed_in_block = None ;
196
+ match self . persist_info ( output_info) {
197
+ Ok ( ( ) ) => { }
198
+ Err ( e) => {
199
+ log_error ! ( self . logger, "Error persisting spendable output info: {:?}" , e)
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ impl < K : KVStore + Sync + Send , F : Deref , L : Deref > Confirm for OutputSweeper < K , F , L >
208
+ where
209
+ F :: Target : Filter ,
210
+ L :: Target : Logger ,
211
+ {
212
+ fn transactions_confirmed (
213
+ & self , header : & BlockHeader , txdata : & chain:: transaction:: TransactionData , height : u32 ,
214
+ ) {
215
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
216
+ for ( _, tx) in txdata {
217
+ locked_outputs
218
+ . iter_mut ( )
219
+ . filter ( |o| o. spending_tx . as_ref ( ) . map ( |t| t. txid ( ) ) == Some ( tx. txid ( ) ) )
220
+ . for_each ( |o| {
221
+ o. confirmed_in_block = Some ( ( height, header. block_hash ( ) ) ) ;
222
+ match self . persist_info ( o) {
223
+ Ok ( ( ) ) => { }
224
+ Err ( e) => {
225
+ log_error ! (
226
+ self . logger,
227
+ "Error persisting spendable output info: {:?}" ,
228
+ e
229
+ )
230
+ }
231
+ }
232
+ } ) ;
233
+ }
234
+ }
235
+
236
+ fn transaction_unconfirmed ( & self , txid : & Txid ) {
237
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
238
+
239
+ // Get what height was unconfirmed.
240
+ let unconf_height = locked_outputs
241
+ . iter ( )
242
+ . find ( |o| o. spending_tx . as_ref ( ) . map ( |t| t. txid ( ) ) == Some ( * txid) )
243
+ . and_then ( |o| o. confirmed_in_block )
244
+ . map ( |t| t. 0 ) ;
245
+
246
+ // Unconfirm all >= this height.
247
+ locked_outputs
248
+ . iter_mut ( )
249
+ . filter ( |o| o. confirmed_in_block . map ( |t| t. 0 ) >= unconf_height)
250
+ . for_each ( |o| {
251
+ o. confirmed_in_block = None ;
252
+ match self . persist_info ( o) {
253
+ Ok ( ( ) ) => { }
254
+ Err ( e) => {
255
+ log_error ! ( self . logger, "Error persisting spendable output info: {:?}" , e)
256
+ }
257
+ }
258
+ } ) ;
259
+ }
260
+
261
+ fn best_block_updated ( & self , header : & BlockHeader , height : u32 ) {
262
+ * self . best_block . lock ( ) . unwrap ( ) = BestBlock :: new ( header. block_hash ( ) , height) ;
263
+
264
+ let mut locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
265
+
266
+ // Rebroadcast all outputs that didn't get confirmed by now.
267
+ for output_info in locked_outputs. iter_mut ( ) . filter ( |o| o. confirmed_in_block . is_none ( ) ) {
268
+ let should_broadcast = if let Some ( bcast_height) = output_info. broadcast_height {
269
+ height >= bcast_height + num_blocks_from_conf_target ( ConfirmationTarget :: Background )
270
+ } else {
271
+ true
272
+ } ;
273
+ if should_broadcast {
274
+ let output_descriptors = vec ! [ output_info. descriptor. clone( ) ] ;
275
+ match self . get_spending_tx ( & output_descriptors) {
276
+ Ok ( Some ( spending_tx) ) => {
277
+ self . wallet . broadcast_transactions ( & [ & spending_tx] ) ;
278
+ if let Some ( filter) = self . chain_source . as_ref ( ) {
279
+ filter. register_tx ( & spending_tx. txid ( ) , & Script :: new ( ) )
280
+ }
281
+ output_info. spending_tx = Some ( spending_tx) ;
282
+ output_info. broadcast_height = Some ( height) ;
283
+ match self . persist_info ( output_info) {
284
+ Ok ( ( ) ) => { }
285
+ Err ( e) => {
286
+ log_error ! (
287
+ self . logger,
288
+ "Error persisting spendable output info: {:?}" ,
289
+ e
290
+ )
291
+ }
292
+ }
293
+ }
294
+ Ok ( None ) => {
295
+ log_debug ! (
296
+ self . logger,
297
+ "Omitted spending static outputs: {:?}" ,
298
+ output_descriptors
299
+ ) ;
300
+ }
301
+ Err ( err) => {
302
+ log_error ! ( self . logger, "Error spending outputs: {:?}" , err) ;
303
+ }
304
+ } ;
305
+ }
306
+ }
307
+
308
+ // Prune all outputs that have sufficient depth by now.
309
+ locked_outputs. retain ( |o| {
310
+ if let Some ( ( conf_height, _) ) = o. confirmed_in_block {
311
+ if height >= conf_height + CONSIDERED_SPENT_THRESHOLD_CONF {
312
+ let key = hex_utils:: to_string ( & o. id ) ;
313
+ match self . kv_store . remove ( SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE , & key) {
314
+ Ok ( _) => return false ,
315
+ Err ( e) => {
316
+ log_error ! (
317
+ self . logger,
318
+ "Removal of key {}/{} failed due to: {}" ,
319
+ SPENDABLE_OUTPUT_INFO_PERSISTENCE_NAMESPACE ,
320
+ key,
321
+ e
322
+ ) ;
323
+ return true ;
324
+ }
325
+ }
326
+ }
327
+ }
328
+ true
329
+ } ) ;
330
+ }
331
+
332
+ fn get_relevant_txids ( & self ) -> Vec < ( Txid , Option < BlockHash > ) > {
333
+ let locked_outputs = self . outputs . lock ( ) . unwrap ( ) ;
334
+ locked_outputs
335
+ . iter ( )
336
+ . filter_map ( |o| {
337
+ if let Some ( tx) = o. spending_tx . as_ref ( ) {
338
+ Some ( ( tx. txid ( ) , o. confirmed_in_block . map ( |c| c. 1 ) ) )
339
+ } else {
340
+ None
341
+ }
342
+ } )
343
+ . collect :: < Vec < _ > > ( )
344
+ }
345
+ }
0 commit comments