@@ -69,7 +69,8 @@ impl TrackedSpendableOutput {
6969 }
7070 }
7171
72- fn is_spent_in ( & self , tx : & Transaction ) -> bool {
72+ /// Returns whether the output is spent in the given transaction.
73+ pub fn is_spent_in ( & self , tx : & Transaction ) -> bool {
7374 let prev_outpoint = match & self . descriptor {
7475 SpendableOutputDescriptor :: StaticOutput { outpoint, .. } => * outpoint,
7576 SpendableOutputDescriptor :: DelayedPaymentOutput ( output) => output. outpoint ,
@@ -92,7 +93,10 @@ impl_writeable_tlv_based!(TrackedSpendableOutput, {
9293pub enum OutputSpendStatus {
9394 /// The output is tracked but an initial spending transaction hasn't been generated and
9495 /// broadcasted yet.
95- PendingInitialBroadcast ,
96+ PendingInitialBroadcast {
97+ /// The height at which we will first generate and broadcast a spending transaction.
98+ delayed_until_height : Option < u32 > ,
99+ } ,
96100 /// A transaction spending the output has been broadcasted but is pending its first confirmation on-chain.
97101 PendingFirstConfirmation {
98102 /// The hash of the chain tip when we first broadcast a transaction spending this output.
@@ -121,7 +125,13 @@ pub enum OutputSpendStatus {
121125impl OutputSpendStatus {
122126 fn broadcast ( & mut self , cur_hash : BlockHash , cur_height : u32 , latest_spending_tx : Transaction ) {
123127 match self {
124- Self :: PendingInitialBroadcast => {
128+ Self :: PendingInitialBroadcast { delayed_until_height } => {
129+ if let Some ( delayed_until_height) = delayed_until_height {
130+ debug_assert ! (
131+ cur_height >= * delayed_until_height,
132+ "We should never broadcast before the required height is reached."
133+ ) ;
134+ }
125135 * self = Self :: PendingFirstConfirmation {
126136 first_broadcast_hash : cur_hash,
127137 latest_broadcast_height : cur_height,
@@ -146,7 +156,7 @@ impl OutputSpendStatus {
146156 latest_spending_tx : Transaction ,
147157 ) {
148158 match self {
149- Self :: PendingInitialBroadcast => {
159+ Self :: PendingInitialBroadcast { .. } => {
150160 // Generally we can't see any of our transactions confirmed if they haven't been
151161 // broadcasted yet, so this should never be reachable via `transactions_confirmed`.
152162 debug_assert ! ( false , "We should never confirm when we haven't broadcasted. This a bug and should never happen, please report." ) ;
@@ -190,7 +200,7 @@ impl OutputSpendStatus {
190200
191201 fn unconfirmed ( & mut self ) {
192202 match self {
193- Self :: PendingInitialBroadcast => {
203+ Self :: PendingInitialBroadcast { .. } => {
194204 debug_assert ! (
195205 false ,
196206 "We should only mark a spend as unconfirmed if it used to be confirmed."
@@ -217,9 +227,19 @@ impl OutputSpendStatus {
217227 }
218228 }
219229
230+ fn is_delayed ( & self , cur_height : u32 ) -> bool {
231+ match self {
232+ Self :: PendingInitialBroadcast { delayed_until_height } => {
233+ delayed_until_height. map_or ( false , |req_height| cur_height < req_height)
234+ } ,
235+ Self :: PendingFirstConfirmation { .. } => false ,
236+ Self :: PendingThresholdConfirmations { .. } => false ,
237+ }
238+ }
239+
220240 fn first_broadcast_hash ( & self ) -> Option < BlockHash > {
221241 match self {
222- Self :: PendingInitialBroadcast => None ,
242+ Self :: PendingInitialBroadcast { .. } => None ,
223243 Self :: PendingFirstConfirmation { first_broadcast_hash, .. } => {
224244 Some ( * first_broadcast_hash)
225245 } ,
@@ -231,7 +251,7 @@ impl OutputSpendStatus {
231251
232252 fn latest_broadcast_height ( & self ) -> Option < u32 > {
233253 match self {
234- Self :: PendingInitialBroadcast => None ,
254+ Self :: PendingInitialBroadcast { .. } => None ,
235255 Self :: PendingFirstConfirmation { latest_broadcast_height, .. } => {
236256 Some ( * latest_broadcast_height)
237257 } ,
@@ -243,7 +263,7 @@ impl OutputSpendStatus {
243263
244264 fn confirmation_height ( & self ) -> Option < u32 > {
245265 match self {
246- Self :: PendingInitialBroadcast => None ,
266+ Self :: PendingInitialBroadcast { .. } => None ,
247267 Self :: PendingFirstConfirmation { .. } => None ,
248268 Self :: PendingThresholdConfirmations { confirmation_height, .. } => {
249269 Some ( * confirmation_height)
@@ -253,7 +273,7 @@ impl OutputSpendStatus {
253273
254274 fn confirmation_hash ( & self ) -> Option < BlockHash > {
255275 match self {
256- Self :: PendingInitialBroadcast => None ,
276+ Self :: PendingInitialBroadcast { .. } => None ,
257277 Self :: PendingFirstConfirmation { .. } => None ,
258278 Self :: PendingThresholdConfirmations { confirmation_hash, .. } => {
259279 Some ( * confirmation_hash)
@@ -263,7 +283,7 @@ impl OutputSpendStatus {
263283
264284 fn latest_spending_tx ( & self ) -> Option < & Transaction > {
265285 match self {
266- Self :: PendingInitialBroadcast => None ,
286+ Self :: PendingInitialBroadcast { .. } => None ,
267287 Self :: PendingFirstConfirmation { latest_spending_tx, .. } => Some ( latest_spending_tx) ,
268288 Self :: PendingThresholdConfirmations { latest_spending_tx, .. } => {
269289 Some ( latest_spending_tx)
@@ -273,15 +293,17 @@ impl OutputSpendStatus {
273293
274294 fn is_confirmed ( & self ) -> bool {
275295 match self {
276- Self :: PendingInitialBroadcast => false ,
296+ Self :: PendingInitialBroadcast { .. } => false ,
277297 Self :: PendingFirstConfirmation { .. } => false ,
278298 Self :: PendingThresholdConfirmations { .. } => true ,
279299 }
280300 }
281301}
282302
283303impl_writeable_tlv_based_enum ! ( OutputSpendStatus ,
284- ( 0 , PendingInitialBroadcast ) => { } ,
304+ ( 0 , PendingInitialBroadcast ) => {
305+ ( 0 , delayed_until_height, option) ,
306+ } ,
285307 ( 2 , PendingFirstConfirmation ) => {
286308 ( 0 , first_broadcast_hash, required) ,
287309 ( 2 , latest_broadcast_height, required) ,
@@ -372,10 +394,13 @@ where
372394 /// [`SpendableOutputDescriptor::StaticOutput`]s, which may be handled directly by the on-chain
373395 /// wallet implementation.
374396 ///
397+ /// If `delay_until_height` is set, we will delay the spending until the respective block
398+ /// height is reached. This can be used to batch spends, e.g., to reduce on-chain fees.
399+ ///
375400 /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs
376401 pub fn track_spendable_outputs (
377402 & self , output_descriptors : Vec < SpendableOutputDescriptor > , channel_id : Option < ChannelId > ,
378- exclude_static_ouputs : bool ,
403+ exclude_static_ouputs : bool , delay_until_height : Option < u32 > ,
379404 ) {
380405 let mut relevant_descriptors = output_descriptors
381406 . into_iter ( )
@@ -396,7 +421,9 @@ where
396421 let output_info = TrackedSpendableOutput {
397422 descriptor,
398423 channel_id,
399- status : OutputSpendStatus :: PendingInitialBroadcast ,
424+ status : OutputSpendStatus :: PendingInitialBroadcast {
425+ delayed_until_height : delay_until_height,
426+ } ,
400427 } ;
401428
402429 if state_lock
@@ -445,6 +472,11 @@ where
445472 return false ;
446473 }
447474
475+ if o. status . is_delayed ( cur_height) {
476+ // Don't generate and broadcast if still delayed
477+ return false ;
478+ }
479+
448480 if o. status . latest_broadcast_height ( ) >= Some ( cur_height) {
449481 // Only broadcast once per block height.
450482 return false ;
@@ -726,6 +758,23 @@ impl_writeable_tlv_based!(SweeperState, {
726758 ( 2 , best_block, required) ,
727759} ) ;
728760
761+ /// A `enum` signalling to the [`OutputSweeper`] that it should delay spending an output until a
762+ /// future block height is reached.
763+ #[ derive( Debug , Clone ) ]
764+ pub enum SpendingDelay {
765+ /// A relative delay indicating we shouldn't spend the output before `cur_height + num_blocks`
766+ /// is reached.
767+ Relative {
768+ /// The number of blocks until we'll generate and broadcast the spending transaction.
769+ num_blocks : u32 ,
770+ } ,
771+ /// An absolute delay indicating we shouldn't spend the output before `height` is reached.
772+ Absolute {
773+ /// The height at which we'll generate and broadcast the spending transaction.
774+ height : u32 ,
775+ } ,
776+ }
777+
729778impl < B : Deref , D : Deref , E : Deref , F : Deref , K : Deref , L : Deref , O : Deref >
730779 ReadableArgs < ( B , E , Option < F > , O , D , K , L ) > for OutputSweeper < B , D , E , F , K , L , O >
731780where
0 commit comments