Skip to content

Commit 2ab9fcc

Browse files
committed
Allow delaying generation and broadcasting of spending txs
.. which enables users to batch output spends.
1 parent 3c9bc5b commit 2ab9fcc

File tree

1 file changed

+66
-14
lines changed

1 file changed

+66
-14
lines changed

lightning/src/util/sweep.rs

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

282302
impl_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+
733785
impl<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>
735787
where

0 commit comments

Comments
 (0)