X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Futil%2Fsweep.rs;h=dd26a8ef844c1a1d4a7bb8c4351481df6d5fe415;hb=336c77c738c792eb8cc1b2ee0e78ff96f106f753;hp=a8393f70e96c6f6be38bfabea02a69b80a82c291;hpb=071337a1f1ba1ce99e39da86ba6fc6dee1280ff1;p=rust-lightning diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs index a8393f70..dd26a8ef 100644 --- a/lightning/src/util/sweep.rs +++ b/lightning/src/util/sweep.rs @@ -13,8 +13,8 @@ use crate::chain::channelmonitor::ANTI_REORG_DELAY; use crate::chain::{self, BestBlock, Confirm, Filter, Listen, WatchedOutput}; use crate::io; use crate::ln::msgs::DecodeError; -use crate::ln::ChannelId; -use crate::prelude::Vec; +use crate::ln::types::ChannelId; +use crate::prelude::*; use crate::sign::{ChangeDestinationSource, OutputSpender, SpendableOutputDescriptor}; use crate::sync::Mutex; use crate::util::logger::Logger; @@ -69,7 +69,8 @@ impl TrackedSpendableOutput { } } - fn is_spent_in(&self, tx: &Transaction) -> bool { + /// Returns whether the output is spent in the given transaction. + pub fn is_spent_in(&self, tx: &Transaction) -> bool { let prev_outpoint = match &self.descriptor { SpendableOutputDescriptor::StaticOutput { outpoint, .. } => *outpoint, SpendableOutputDescriptor::DelayedPaymentOutput(output) => output.outpoint, @@ -92,7 +93,10 @@ impl_writeable_tlv_based!(TrackedSpendableOutput, { pub enum OutputSpendStatus { /// The output is tracked but an initial spending transaction hasn't been generated and /// broadcasted yet. - PendingInitialBroadcast, + PendingInitialBroadcast { + /// The height at which we will first generate and broadcast a spending transaction. + delayed_until_height: Option, + }, /// A transaction spending the output has been broadcasted but is pending its first confirmation on-chain. PendingFirstConfirmation { /// The hash of the chain tip when we first broadcast a transaction spending this output. @@ -121,7 +125,13 @@ pub enum OutputSpendStatus { impl OutputSpendStatus { fn broadcast(&mut self, cur_hash: BlockHash, cur_height: u32, latest_spending_tx: Transaction) { match self { - Self::PendingInitialBroadcast => { + Self::PendingInitialBroadcast { delayed_until_height } => { + if let Some(delayed_until_height) = delayed_until_height { + debug_assert!( + cur_height >= *delayed_until_height, + "We should never broadcast before the required height is reached." + ); + } *self = Self::PendingFirstConfirmation { first_broadcast_hash: cur_hash, latest_broadcast_height: cur_height, @@ -146,7 +156,7 @@ impl OutputSpendStatus { latest_spending_tx: Transaction, ) { match self { - Self::PendingInitialBroadcast => { + Self::PendingInitialBroadcast { .. } => { // Generally we can't see any of our transactions confirmed if they haven't been // broadcasted yet, so this should never be reachable via `transactions_confirmed`. 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 { fn unconfirmed(&mut self) { match self { - Self::PendingInitialBroadcast => { + Self::PendingInitialBroadcast { .. } => { debug_assert!( false, "We should only mark a spend as unconfirmed if it used to be confirmed." @@ -217,9 +227,19 @@ impl OutputSpendStatus { } } + fn is_delayed(&self, cur_height: u32) -> bool { + match self { + Self::PendingInitialBroadcast { delayed_until_height } => { + delayed_until_height.map_or(false, |req_height| cur_height < req_height) + }, + Self::PendingFirstConfirmation { .. } => false, + Self::PendingThresholdConfirmations { .. } => false, + } + } + fn first_broadcast_hash(&self) -> Option { match self { - Self::PendingInitialBroadcast => None, + Self::PendingInitialBroadcast { .. } => None, Self::PendingFirstConfirmation { first_broadcast_hash, .. } => { Some(*first_broadcast_hash) }, @@ -231,7 +251,7 @@ impl OutputSpendStatus { fn latest_broadcast_height(&self) -> Option { match self { - Self::PendingInitialBroadcast => None, + Self::PendingInitialBroadcast { .. } => None, Self::PendingFirstConfirmation { latest_broadcast_height, .. } => { Some(*latest_broadcast_height) }, @@ -243,7 +263,7 @@ impl OutputSpendStatus { fn confirmation_height(&self) -> Option { match self { - Self::PendingInitialBroadcast => None, + Self::PendingInitialBroadcast { .. } => None, Self::PendingFirstConfirmation { .. } => None, Self::PendingThresholdConfirmations { confirmation_height, .. } => { Some(*confirmation_height) @@ -253,7 +273,7 @@ impl OutputSpendStatus { fn confirmation_hash(&self) -> Option { match self { - Self::PendingInitialBroadcast => None, + Self::PendingInitialBroadcast { .. } => None, Self::PendingFirstConfirmation { .. } => None, Self::PendingThresholdConfirmations { confirmation_hash, .. } => { Some(*confirmation_hash) @@ -263,7 +283,7 @@ impl OutputSpendStatus { fn latest_spending_tx(&self) -> Option<&Transaction> { match self { - Self::PendingInitialBroadcast => None, + Self::PendingInitialBroadcast { .. } => None, Self::PendingFirstConfirmation { latest_spending_tx, .. } => Some(latest_spending_tx), Self::PendingThresholdConfirmations { latest_spending_tx, .. } => { Some(latest_spending_tx) @@ -273,7 +293,7 @@ impl OutputSpendStatus { fn is_confirmed(&self) -> bool { match self { - Self::PendingInitialBroadcast => false, + Self::PendingInitialBroadcast { .. } => false, Self::PendingFirstConfirmation { .. } => false, Self::PendingThresholdConfirmations { .. } => true, } @@ -281,7 +301,9 @@ impl OutputSpendStatus { } impl_writeable_tlv_based_enum!(OutputSpendStatus, - (0, PendingInitialBroadcast) => {}, + (0, PendingInitialBroadcast) => { + (0, delayed_until_height, option), + }, (2, PendingFirstConfirmation) => { (0, first_broadcast_hash, required), (2, latest_broadcast_height, required), @@ -368,35 +390,42 @@ where /// Usually, this should be called based on the values emitted by the /// [`Event::SpendableOutputs`]. /// - /// The given `exclude_static_ouputs` flag controls whether the sweeper will filter out + /// The given `exclude_static_outputs` flag controls whether the sweeper will filter out /// [`SpendableOutputDescriptor::StaticOutput`]s, which may be handled directly by the on-chain /// wallet implementation. /// + /// If `delay_until_height` is set, we will delay the spending until the respective block + /// height is reached. This can be used to batch spends, e.g., to reduce on-chain fees. + /// + /// Returns `Err` on persistence failure, in which case the call may be safely retried. + /// /// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs pub fn track_spendable_outputs( &self, output_descriptors: Vec, channel_id: Option, - exclude_static_ouputs: bool, - ) { + exclude_static_outputs: bool, delay_until_height: Option, + ) -> Result<(), ()> { let mut relevant_descriptors = output_descriptors .into_iter() .filter(|desc| { - !(exclude_static_ouputs + !(exclude_static_outputs && matches!(desc, SpendableOutputDescriptor::StaticOutput { .. })) }) .peekable(); if relevant_descriptors.peek().is_none() { - return; + return Ok(()); } - let mut spending_tx_opt; + let spending_tx_opt; { let mut state_lock = self.sweeper_state.lock().unwrap(); for descriptor in relevant_descriptors { let output_info = TrackedSpendableOutput { descriptor, channel_id, - status: OutputSpendStatus::PendingInitialBroadcast, + status: OutputSpendStatus::PendingInitialBroadcast { + delayed_until_height: delay_until_height, + }, }; if state_lock @@ -411,16 +440,16 @@ where state_lock.outputs.push(output_info); } spending_tx_opt = self.regenerate_spend_if_necessary(&mut *state_lock); - self.persist_state(&*state_lock).unwrap_or_else(|e| { + self.persist_state(&*state_lock).map_err(|e| { log_error!(self.logger, "Error persisting OutputSweeper: {:?}", e); - // Skip broadcasting if the persist failed. - spending_tx_opt = None; - }); + })?; } if let Some(spending_tx) = spending_tx_opt { self.broadcaster.broadcast_transactions(&[&spending_tx]); } + + Ok(()) } /// Returns a list of the currently tracked spendable outputs. @@ -445,6 +474,11 @@ where return false; } + if o.status.is_delayed(cur_height) { + // Don't generate and broadcast if still delayed + return false; + } + if o.status.latest_broadcast_height() >= Some(cur_height) { // Only broadcast once per block height. return false; @@ -726,6 +760,23 @@ impl_writeable_tlv_based!(SweeperState, { (2, best_block, required), }); +/// A `enum` signalling to the [`OutputSweeper`] that it should delay spending an output until a +/// future block height is reached. +#[derive(Debug, Clone)] +pub enum SpendingDelay { + /// A relative delay indicating we shouldn't spend the output before `cur_height + num_blocks` + /// is reached. + Relative { + /// The number of blocks until we'll generate and broadcast the spending transaction. + num_blocks: u32, + }, + /// An absolute delay indicating we shouldn't spend the output before `height` is reached. + Absolute { + /// The height at which we'll generate and broadcast the spending transaction. + height: u32, + }, +} + impl ReadableArgs<(B, E, Option, O, D, K, L)> for OutputSweeper where