@@ -68,7 +68,8 @@ impl TrackedSpendableOutput {
6868 }
6969 }
7070
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 {
7273 let prev_outpoint = match & self . descriptor {
7374 SpendableOutputDescriptor :: StaticOutput { outpoint, .. } => * outpoint,
7475 SpendableOutputDescriptor :: DelayedPaymentOutput ( output) => output. outpoint ,
@@ -91,7 +92,10 @@ impl_writeable_tlv_based!(TrackedSpendableOutput, {
9192pub enum OutputSpendStatus {
9293 /// The output is tracked but an initial spending transaction hasn't been generated and
9394 /// 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+ } ,
9599 /// A transaction spending the output has been broadcasted but is pending its first confirmation on-chain.
96100 PendingFirstConfirmation {
97101 /// The hash of the chain tip when we first broadcast a transaction spending this output.
@@ -120,7 +124,13 @@ pub enum OutputSpendStatus {
120124impl OutputSpendStatus {
121125 fn broadcast ( & mut self , cur_hash : BlockHash , cur_height : u32 , latest_spending_tx : Transaction ) {
122126 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+ }
124134 * self = Self :: PendingFirstConfirmation {
125135 first_broadcast_hash : cur_hash,
126136 latest_broadcast_height : cur_height,
@@ -145,7 +155,7 @@ impl OutputSpendStatus {
145155 latest_spending_tx : Transaction ,
146156 ) {
147157 match self {
148- Self :: PendingInitialBroadcast => {
158+ Self :: PendingInitialBroadcast { .. } => {
149159 // Generally we can't see any of our transactions confirmed if they haven't been
150160 // broadcasted yet, so this should never be reachable via `transactions_confirmed`.
151161 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 {
189199
190200 fn unconfirmed ( & mut self ) {
191201 match self {
192- Self :: PendingInitialBroadcast => {
202+ Self :: PendingInitialBroadcast { .. } => {
193203 debug_assert ! (
194204 false ,
195205 "We should only mark a spend as unconfirmed if it used to be confirmed."
@@ -216,9 +226,19 @@ impl OutputSpendStatus {
216226 }
217227 }
218228
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+
219239 fn first_broadcast_hash ( & self ) -> Option < BlockHash > {
220240 match self {
221- Self :: PendingInitialBroadcast => None ,
241+ Self :: PendingInitialBroadcast { .. } => None ,
222242 Self :: PendingFirstConfirmation { first_broadcast_hash, .. } => {
223243 Some ( * first_broadcast_hash)
224244 } ,
@@ -230,7 +250,7 @@ impl OutputSpendStatus {
230250
231251 fn latest_broadcast_height ( & self ) -> Option < u32 > {
232252 match self {
233- Self :: PendingInitialBroadcast => None ,
253+ Self :: PendingInitialBroadcast { .. } => None ,
234254 Self :: PendingFirstConfirmation { latest_broadcast_height, .. } => {
235255 Some ( * latest_broadcast_height)
236256 } ,
@@ -242,7 +262,7 @@ impl OutputSpendStatus {
242262
243263 fn confirmation_height ( & self ) -> Option < u32 > {
244264 match self {
245- Self :: PendingInitialBroadcast => None ,
265+ Self :: PendingInitialBroadcast { .. } => None ,
246266 Self :: PendingFirstConfirmation { .. } => None ,
247267 Self :: PendingThresholdConfirmations { confirmation_height, .. } => {
248268 Some ( * confirmation_height)
@@ -252,7 +272,7 @@ impl OutputSpendStatus {
252272
253273 fn confirmation_hash ( & self ) -> Option < BlockHash > {
254274 match self {
255- Self :: PendingInitialBroadcast => None ,
275+ Self :: PendingInitialBroadcast { .. } => None ,
256276 Self :: PendingFirstConfirmation { .. } => None ,
257277 Self :: PendingThresholdConfirmations { confirmation_hash, .. } => {
258278 Some ( * confirmation_hash)
@@ -262,7 +282,7 @@ impl OutputSpendStatus {
262282
263283 fn latest_spending_tx ( & self ) -> Option < & Transaction > {
264284 match self {
265- Self :: PendingInitialBroadcast => None ,
285+ Self :: PendingInitialBroadcast { .. } => None ,
266286 Self :: PendingFirstConfirmation { latest_spending_tx, .. } => Some ( latest_spending_tx) ,
267287 Self :: PendingThresholdConfirmations { latest_spending_tx, .. } => {
268288 Some ( latest_spending_tx)
@@ -272,15 +292,17 @@ impl OutputSpendStatus {
272292
273293 fn is_confirmed ( & self ) -> bool {
274294 match self {
275- Self :: PendingInitialBroadcast => false ,
295+ Self :: PendingInitialBroadcast { .. } => false ,
276296 Self :: PendingFirstConfirmation { .. } => false ,
277297 Self :: PendingThresholdConfirmations { .. } => true ,
278298 }
279299 }
280300}
281301
282302impl_writeable_tlv_based_enum ! ( OutputSpendStatus ,
283- ( 0 , PendingInitialBroadcast ) => { } ,
303+ ( 0 , PendingInitialBroadcast ) => {
304+ ( 0 , delayed_until_height, option) ,
305+ } ,
284306 ( 2 , PendingFirstConfirmation ) => {
285307 ( 0 , first_broadcast_hash, required) ,
286308 ( 2 , latest_broadcast_height, required) ,
@@ -399,10 +421,13 @@ where
399421 /// [`SpendableOutputDescriptor::StaticOutput`]s, which may be handled directly by the on-chain
400422 /// wallet implementation.
401423 ///
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+ ///
402427 /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs
403428 pub fn track_spendable_outputs (
404429 & self , output_descriptors : Vec < SpendableOutputDescriptor > , channel_id : Option < ChannelId > ,
405- exclude_static_ouputs : bool ,
430+ exclude_static_ouputs : bool , delay_spend : Option < SpendingDelay > ,
406431 ) {
407432 let mut relevant_descriptors = output_descriptors
408433 . into_iter ( )
@@ -419,11 +444,16 @@ where
419444 let spending_tx_opt;
420445 {
421446 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+ } ) ;
422452 for descriptor in relevant_descriptors {
423453 let output_info = TrackedSpendableOutput {
424454 descriptor,
425455 channel_id,
426- status : OutputSpendStatus :: PendingInitialBroadcast ,
456+ status : OutputSpendStatus :: PendingInitialBroadcast { delayed_until_height } ,
427457 } ;
428458
429459 if state_lock
@@ -470,6 +500,11 @@ where
470500 return false ;
471501 }
472502
503+ if o. status . is_delayed ( cur_height) {
504+ // Don't generate and broadcast if still delayed
505+ return false ;
506+ }
507+
473508 if o. status . latest_broadcast_height ( ) >= Some ( cur_height) {
474509 // Only broadcast once per block height.
475510 return false ;
@@ -730,6 +765,23 @@ impl_writeable_tlv_based!(SweeperState, {
730765 ( 2 , best_block, required) ,
731766} ) ;
732767
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+
733785impl < B : Deref , D : Deref , E : Deref , F : Deref , K : Deref , L : Deref , O : Deref >
734786 ReadableArgs < ( B , E , Option < F > , O , D , K , L ) > for OutputSweeper < B , D , E , F , K , L , O >
735787where
0 commit comments