}
}
- 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,
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<u32>,
+ },
/// 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.
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,
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.");
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."
}
}
+ 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<BlockHash> {
match self {
- Self::PendingInitialBroadcast => None,
+ Self::PendingInitialBroadcast { .. } => None,
Self::PendingFirstConfirmation { first_broadcast_hash, .. } => {
Some(*first_broadcast_hash)
},
fn latest_broadcast_height(&self) -> Option<u32> {
match self {
- Self::PendingInitialBroadcast => None,
+ Self::PendingInitialBroadcast { .. } => None,
Self::PendingFirstConfirmation { latest_broadcast_height, .. } => {
Some(*latest_broadcast_height)
},
fn confirmation_height(&self) -> Option<u32> {
match self {
- Self::PendingInitialBroadcast => None,
+ Self::PendingInitialBroadcast { .. } => None,
Self::PendingFirstConfirmation { .. } => None,
Self::PendingThresholdConfirmations { confirmation_height, .. } => {
Some(*confirmation_height)
fn confirmation_hash(&self) -> Option<BlockHash> {
match self {
- Self::PendingInitialBroadcast => None,
+ Self::PendingInitialBroadcast { .. } => None,
Self::PendingFirstConfirmation { .. } => None,
Self::PendingThresholdConfirmations { confirmation_hash, .. } => {
Some(*confirmation_hash)
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)
fn is_confirmed(&self) -> bool {
match self {
- Self::PendingInitialBroadcast => false,
+ Self::PendingInitialBroadcast { .. } => false,
Self::PendingFirstConfirmation { .. } => false,
Self::PendingThresholdConfirmations { .. } => true,
}
}
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),
/// [`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.
+ ///
/// [`Event::SpendableOutputs`]: crate::events::Event::SpendableOutputs
pub fn track_spendable_outputs(
&self, output_descriptors: Vec<SpendableOutputDescriptor>, channel_id: Option<ChannelId>,
- exclude_static_ouputs: bool,
+ exclude_static_ouputs: bool, delay_until_height: Option<u32>,
) {
let mut relevant_descriptors = output_descriptors
.into_iter()
let output_info = TrackedSpendableOutput {
descriptor,
channel_id,
- status: OutputSpendStatus::PendingInitialBroadcast,
+ status: OutputSpendStatus::PendingInitialBroadcast {
+ delayed_until_height: delay_until_height,
+ },
};
if state_lock
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;
(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<B: Deref, D: Deref, E: Deref, F: Deref, K: Deref, L: Deref, O: Deref>
ReadableArgs<(B, E, Option<F>, O, D, K, L)> for OutputSweeper<B, D, E, F, K, L, O>
where