@@ -68,7 +68,8 @@ impl TrackedSpendableOutput {
68
68
}
69
69
}
70
70
71
- fn is_spent_in ( & self , tx : & Transaction ) -> bool {
71
+ /// Returns whether the output is spent in the given transaction.
72
+ pub fn is_spent_in ( & self , tx : & Transaction ) -> bool {
72
73
let prev_outpoint = match & self . descriptor {
73
74
SpendableOutputDescriptor :: StaticOutput { outpoint, .. } => * outpoint,
74
75
SpendableOutputDescriptor :: DelayedPaymentOutput ( output) => output. outpoint ,
@@ -91,7 +92,10 @@ impl_writeable_tlv_based!(TrackedSpendableOutput, {
91
92
pub enum OutputSpendStatus {
92
93
/// The output is tracked but an initial spending transaction hasn't been generated and
93
94
/// broadcasted yet.
94
- PendingInitialBroadcast ,
95
+ PendingInitialBroadcast {
96
+ /// The height at which we will first generate and broadcast a spending transaction.
97
+ delayed_until_height : Option < u32 > ,
98
+ } ,
95
99
/// A transaction spending the output has been broadcasted but is pending its first confirmation on-chain.
96
100
PendingFirstConfirmation {
97
101
/// The hash of the chain tip when we first broadcast a transaction spending this output.
@@ -120,7 +124,13 @@ pub enum OutputSpendStatus {
120
124
impl OutputSpendStatus {
121
125
fn broadcast ( & mut self , cur_hash : BlockHash , cur_height : u32 , latest_spending_tx : Transaction ) {
122
126
match self {
123
- Self :: PendingInitialBroadcast => {
127
+ Self :: PendingInitialBroadcast { delayed_until_height } => {
128
+ if let Some ( delayed_until_height) = delayed_until_height {
129
+ debug_assert ! (
130
+ cur_height >= * delayed_until_height,
131
+ "We should never broadcast before the required height is reached."
132
+ ) ;
133
+ }
124
134
* self = Self :: PendingFirstConfirmation {
125
135
first_broadcast_hash : cur_hash,
126
136
latest_broadcast_height : cur_height,
@@ -145,7 +155,7 @@ impl OutputSpendStatus {
145
155
latest_spending_tx : Transaction ,
146
156
) {
147
157
match self {
148
- Self :: PendingInitialBroadcast => {
158
+ Self :: PendingInitialBroadcast { .. } => {
149
159
// Generally we can't see any of our transactions confirmed if they haven't been
150
160
// broadcasted yet, so this should never be reachable via `transactions_confirmed`.
151
161
debug_assert ! ( false , "We should never confirm when we haven't broadcasted. This a bug and should never happen, please report." ) ;
@@ -189,7 +199,7 @@ impl OutputSpendStatus {
189
199
190
200
fn unconfirmed ( & mut self ) {
191
201
match self {
192
- Self :: PendingInitialBroadcast => {
202
+ Self :: PendingInitialBroadcast { .. } => {
193
203
debug_assert ! (
194
204
false ,
195
205
"We should only mark a spend as unconfirmed if it used to be confirmed."
@@ -216,9 +226,19 @@ impl OutputSpendStatus {
216
226
}
217
227
}
218
228
229
+ fn is_delayed ( & self , cur_height : u32 ) -> bool {
230
+ match self {
231
+ Self :: PendingInitialBroadcast { delayed_until_height } => {
232
+ delayed_until_height. map_or ( false , |req_height| cur_height < req_height)
233
+ } ,
234
+ Self :: PendingFirstConfirmation { .. } => false ,
235
+ Self :: PendingThresholdConfirmations { .. } => false ,
236
+ }
237
+ }
238
+
219
239
fn first_broadcast_hash ( & self ) -> Option < BlockHash > {
220
240
match self {
221
- Self :: PendingInitialBroadcast => None ,
241
+ Self :: PendingInitialBroadcast { .. } => None ,
222
242
Self :: PendingFirstConfirmation { first_broadcast_hash, .. } => {
223
243
Some ( * first_broadcast_hash)
224
244
} ,
@@ -230,7 +250,7 @@ impl OutputSpendStatus {
230
250
231
251
fn latest_broadcast_height ( & self ) -> Option < u32 > {
232
252
match self {
233
- Self :: PendingInitialBroadcast => None ,
253
+ Self :: PendingInitialBroadcast { .. } => None ,
234
254
Self :: PendingFirstConfirmation { latest_broadcast_height, .. } => {
235
255
Some ( * latest_broadcast_height)
236
256
} ,
@@ -242,7 +262,7 @@ impl OutputSpendStatus {
242
262
243
263
fn confirmation_height ( & self ) -> Option < u32 > {
244
264
match self {
245
- Self :: PendingInitialBroadcast => None ,
265
+ Self :: PendingInitialBroadcast { .. } => None ,
246
266
Self :: PendingFirstConfirmation { .. } => None ,
247
267
Self :: PendingThresholdConfirmations { confirmation_height, .. } => {
248
268
Some ( * confirmation_height)
@@ -252,7 +272,7 @@ impl OutputSpendStatus {
252
272
253
273
fn confirmation_hash ( & self ) -> Option < BlockHash > {
254
274
match self {
255
- Self :: PendingInitialBroadcast => None ,
275
+ Self :: PendingInitialBroadcast { .. } => None ,
256
276
Self :: PendingFirstConfirmation { .. } => None ,
257
277
Self :: PendingThresholdConfirmations { confirmation_hash, .. } => {
258
278
Some ( * confirmation_hash)
@@ -262,7 +282,7 @@ impl OutputSpendStatus {
262
282
263
283
fn latest_spending_tx ( & self ) -> Option < & Transaction > {
264
284
match self {
265
- Self :: PendingInitialBroadcast => None ,
285
+ Self :: PendingInitialBroadcast { .. } => None ,
266
286
Self :: PendingFirstConfirmation { latest_spending_tx, .. } => Some ( latest_spending_tx) ,
267
287
Self :: PendingThresholdConfirmations { latest_spending_tx, .. } => {
268
288
Some ( latest_spending_tx)
@@ -272,15 +292,17 @@ impl OutputSpendStatus {
272
292
273
293
fn is_confirmed ( & self ) -> bool {
274
294
match self {
275
- Self :: PendingInitialBroadcast => false ,
295
+ Self :: PendingInitialBroadcast { .. } => false ,
276
296
Self :: PendingFirstConfirmation { .. } => false ,
277
297
Self :: PendingThresholdConfirmations { .. } => true ,
278
298
}
279
299
}
280
300
}
281
301
282
302
impl_writeable_tlv_based_enum ! ( OutputSpendStatus ,
283
- ( 0 , PendingInitialBroadcast ) => { } ,
303
+ ( 0 , PendingInitialBroadcast ) => {
304
+ ( 0 , delayed_until_height, option) ,
305
+ } ,
284
306
( 2 , PendingFirstConfirmation ) => {
285
307
( 0 , first_broadcast_hash, required) ,
286
308
( 2 , latest_broadcast_height, required) ,
@@ -399,10 +421,13 @@ where
399
421
/// [`SpendableOutputDescriptor::StaticOutput`]s, which may be handled directly by the on-chain
400
422
/// wallet implementation.
401
423
///
424
+ /// If `delay_spend` is configured, we will delay the spending until the respective block
425
+ /// height is reached. This can be used to batch spends, e.g., to reduce on-chain fees.
426
+ ///
402
427
/// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs
403
428
pub fn track_spendable_outputs (
404
429
& self , output_descriptors : Vec < SpendableOutputDescriptor > , channel_id : Option < ChannelId > ,
405
- exclude_static_ouputs : bool ,
430
+ exclude_static_ouputs : bool , delay_spend : Option < SpendingDelay > ,
406
431
) {
407
432
let mut relevant_descriptors = output_descriptors
408
433
. into_iter ( )
@@ -419,11 +444,16 @@ where
419
444
let spending_tx_opt;
420
445
{
421
446
let mut state_lock = self . sweeper_state . lock ( ) . unwrap ( ) ;
447
+ let cur_height = state_lock. best_block . height ;
448
+ let delayed_until_height = delay_spend. map ( |delay| match delay {
449
+ SpendingDelay :: Relative { num_blocks } => cur_height + num_blocks,
450
+ SpendingDelay :: Absolute { height } => height,
451
+ } ) ;
422
452
for descriptor in relevant_descriptors {
423
453
let output_info = TrackedSpendableOutput {
424
454
descriptor,
425
455
channel_id,
426
- status : OutputSpendStatus :: PendingInitialBroadcast ,
456
+ status : OutputSpendStatus :: PendingInitialBroadcast { delayed_until_height } ,
427
457
} ;
428
458
429
459
if state_lock
@@ -470,6 +500,11 @@ where
470
500
return false ;
471
501
}
472
502
503
+ if o. status . is_delayed ( cur_height) {
504
+ // Don't generate and broadcast if still delayed
505
+ return false ;
506
+ }
507
+
473
508
if o. status . latest_broadcast_height ( ) >= Some ( cur_height) {
474
509
// Only broadcast once per block height.
475
510
return false ;
@@ -730,6 +765,23 @@ impl_writeable_tlv_based!(SweeperState, {
730
765
( 2 , best_block, required) ,
731
766
} ) ;
732
767
768
+ /// A `enum` signalling to the [`OutputSweeper`] that it should delay spending an output until a
769
+ /// future block height is reached.
770
+ #[ derive( Debug , Clone ) ]
771
+ pub enum SpendingDelay {
772
+ /// A relative delay indicating we shouldn't spend the output before `cur_height + num_blocks`
773
+ /// is reached.
774
+ Relative {
775
+ /// The number of blocks until we'll generate and broadcast the spending transaction.
776
+ num_blocks : u32 ,
777
+ } ,
778
+ /// An absolute delay indicating we shouldn't spend the output before `height` is reached.
779
+ Absolute {
780
+ /// The height at which we'll generate and broadcast the spending transaction.
781
+ height : u32 ,
782
+ } ,
783
+ }
784
+
733
785
impl < B : Deref , D : Deref , E : Deref , F : Deref , K : Deref , L : Deref , O : Deref >
734
786
ReadableArgs < ( B , E , Option < F > , O , D , K , L ) > for OutputSweeper < B , D , E , F , K , L , O >
735
787
where
0 commit comments