Skip to content

Commit a574f8b

Browse files
committed
Allow delaying generation and broadcasting of spending txs
.. which enables users to batch output spends.
1 parent 071337a commit a574f8b

File tree

1 file changed

+63
-14
lines changed

1 file changed

+63
-14
lines changed

lightning/src/util/sweep.rs

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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, {
9293
pub 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 {
121125
impl 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

283303
impl_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+
729778
impl<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>
731780
where

0 commit comments

Comments
 (0)