Only yield DelayedPaymentOutput descriptors once their delay expires
authorWilmer Paulino <wilmer@wilmerpaulino.com>
Fri, 29 Sep 2023 16:40:34 +0000 (09:40 -0700)
committerWilmer Paulino <wilmer@wilmerpaulino.com>
Fri, 29 Sep 2023 17:56:48 +0000 (10:56 -0700)
Otherwise, we could give users a descriptor ahead of time that will
result in an invalid transaction spend/broadcast.

lightning/src/chain/channelmonitor.rs
lightning/src/ln/monitor_tests.rs

index 61669b784f846cd99f9cf8ebee4a5b1fe170010b..cfd29759bb5058c109e99a2b7ebd55b54b490a07 100644 (file)
@@ -1672,6 +1672,8 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
 
        /// Returns the descriptors for relevant outputs (i.e., those that we can spend) within the
        /// transaction if they exist and the transaction has at least [`ANTI_REORG_DELAY`]
+       /// confirmations. For [`SpendableOutputDescriptor::DelayedPaymentOutput`] descriptors to be
+       /// returned, the transaction must have at least `max(ANTI_REORG_DELAY, to_self_delay)`
        /// confirmations.
        ///
        /// Descriptors returned by this method are primarily exposed via [`Event::SpendableOutputs`]
@@ -1689,11 +1691,16 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
        pub fn get_spendable_outputs(&self, tx: &Transaction, confirmation_height: u32) -> Vec<SpendableOutputDescriptor> {
                let inner = self.inner.lock().unwrap();
                let current_height = inner.best_block.height;
-               if current_height.saturating_sub(ANTI_REORG_DELAY) + 1 >= confirmation_height {
-                       inner.get_spendable_outputs(tx)
-               } else {
-                       Vec::new()
-               }
+               let mut spendable_outputs = inner.get_spendable_outputs(tx);
+               spendable_outputs.retain(|descriptor| {
+                       let mut conf_threshold = current_height.saturating_sub(ANTI_REORG_DELAY) + 1;
+                       if let SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) = descriptor {
+                               conf_threshold = cmp::min(conf_threshold,
+                                       current_height.saturating_sub(descriptor.to_self_delay as u32) + 1);
+                       }
+                       conf_threshold >= confirmation_height
+               });
+               spendable_outputs
        }
 }
 
index f0cc872db0ad6731706cc380f59fd0c78be122f9..c950d13bb01d7d4c63695e4acd3bf9654b7603bc 100644 (file)
@@ -637,7 +637,7 @@ fn test_balances_on_local_commitment_htlcs() {
        // First confirm the commitment transaction on nodes[0], which should leave us with three
        // claimable balances.
        let node_a_commitment_claimable = nodes[0].best_block_info().1 + BREAKDOWN_TIMEOUT as u32;
-       mine_transaction(&nodes[0], &as_txn[0]);
+       let commitment_tx_conf_height_a = block_from_scid(&mine_transaction(&nodes[0], &as_txn[0]));
        check_added_monitors!(nodes[0], 1);
        check_closed_broadcast!(nodes[0], true);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
@@ -729,13 +729,20 @@ fn test_balances_on_local_commitment_htlcs() {
 
        // Connect blocks until the commitment transaction's CSV expires, providing us the relevant
        // `SpendableOutputs` event and removing the claimable balance entry.
-       connect_blocks(&nodes[0], node_a_commitment_claimable - nodes[0].best_block_info().1);
+       connect_blocks(&nodes[0], node_a_commitment_claimable - nodes[0].best_block_info().1 - 1);
+       assert!(get_monitor!(nodes[0], chan_id)
+               .get_spendable_outputs(&as_txn[0], commitment_tx_conf_height_a).is_empty());
+       connect_blocks(&nodes[0], 1);
        assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
                        amount_satoshis: 10_000,
                        confirmation_height: node_a_htlc_claimable,
                }],
                nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
-       test_spendable_output(&nodes[0], &as_txn[0]);
+       let to_self_spendable_output = test_spendable_output(&nodes[0], &as_txn[0]);
+       assert_eq!(
+               get_monitor!(nodes[0], chan_id).get_spendable_outputs(&as_txn[0], commitment_tx_conf_height_a),
+               to_self_spendable_output
+       );
 
        // Connect blocks until the HTLC-Timeout's CSV expires, providing us the relevant
        // `SpendableOutputs` event and removing the claimable balance entry.