Merge pull request #2609 from wpaulino/monitor-get-spendable-output
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Fri, 29 Sep 2023 01:29:47 +0000 (01:29 +0000)
committerGitHub <noreply@github.com>
Fri, 29 Sep 2023 01:29:47 +0000 (01:29 +0000)
Allow retrieval of SpendableOutputDescriptors from relevant transactions

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

index 3f9c83bb54393290c40b9a6784bcce7dd48c53da..7088d32d1607e1f4fa0137d6ceda9182d32ca3b2 100644 (file)
@@ -1669,6 +1669,33 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
                        current_height, &broadcaster, &fee_estimator, &logger,
                );
        }
+
+       /// 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.
+       ///
+       /// Descriptors returned by this method are primarily exposed via [`Event::SpendableOutputs`]
+       /// once they are no longer under reorg risk. This method serves as a way to retrieve these
+       /// descriptors at a later time, either for historical purposes, or to replay any
+       /// missed/unhandled descriptors. For the purpose of gathering historical records, if the
+       /// channel close has fully resolved (i.e., [`ChannelMonitor::get_claimable_balances`] returns
+       /// an empty set), you can retrieve all spendable outputs by providing all descendant spending
+       /// transactions starting from the channel's funding or closing transaction that have at least
+       /// [`ANTI_REORG_DELAY`] confirmations.
+       ///
+       /// `tx` is a transaction we'll scan the outputs of. Any transaction can be provided. If any
+       /// outputs which can be spent by us are found, at least one descriptor is returned.
+       ///
+       /// `confirmation_height` must be the height of the block in which `tx` was included in.
+       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()
+               }
+       }
 }
 
 impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
@@ -3441,7 +3468,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
                                }
                                self.is_resolving_htlc_output(&tx, height, &block_hash, &logger);
 
-                               self.is_paying_spendable_output(&tx, height, &block_hash, &logger);
+                               self.check_tx_and_push_spendable_outputs(&tx, height, &block_hash, &logger);
                        }
                }
 
@@ -3987,34 +4014,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
                }
        }
 
-       /// Check if any transaction broadcasted is paying fund back to some address we can assume to own
-       fn is_paying_spendable_output<L: Deref>(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) where L::Target: Logger {
-               let mut spendable_output = None;
-               for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us
-                       if i > ::core::u16::MAX as usize {
-                               // While it is possible that an output exists on chain which is greater than the
-                               // 2^16th output in a given transaction, this is only possible if the output is not
-                               // in a lightning transaction and was instead placed there by some third party who
-                               // wishes to give us money for no reason.
-                               // Namely, any lightning transactions which we pre-sign will never have anywhere
-                               // near 2^16 outputs both because such transactions must have ~2^16 outputs who's
-                               // scripts are not longer than one byte in length and because they are inherently
-                               // non-standard due to their size.
-                               // Thus, it is completely safe to ignore such outputs, and while it may result in
-                               // us ignoring non-lightning fund to us, that is only possible if someone fills
-                               // nearly a full block with garbage just to hit this case.
-                               continue;
-                       }
+       fn get_spendable_outputs(&self, tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
+               let mut spendable_outputs = Vec::new();
+               for (i, outp) in tx.output.iter().enumerate() {
                        if outp.script_pubkey == self.destination_script {
-                               spendable_output =  Some(SpendableOutputDescriptor::StaticOutput {
+                               spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
                                        outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
                                        output: outp.clone(),
                                });
-                               break;
                        }
                        if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
                                if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
-                                       spendable_output =  Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
+                                       spendable_outputs.push(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
                                                outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
                                                per_commitment_point: broadcasted_holder_revokable_script.1,
                                                to_self_delay: self.on_holder_tx_csv,
@@ -4023,27 +4034,32 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
                                                channel_keys_id: self.channel_keys_id,
                                                channel_value_satoshis: self.channel_value_satoshis,
                                        }));
-                                       break;
                                }
                        }
                        if self.counterparty_payment_script == outp.script_pubkey {
-                               spendable_output = Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
+                               spendable_outputs.push(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
                                        outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
                                        output: outp.clone(),
                                        channel_keys_id: self.channel_keys_id,
                                        channel_value_satoshis: self.channel_value_satoshis,
                                }));
-                               break;
                        }
                        if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) {
-                               spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
+                               spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
                                        outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
                                        output: outp.clone(),
                                });
-                               break;
                        }
                }
-               if let Some(spendable_output) = spendable_output {
+               spendable_outputs
+       }
+
+       /// Checks if the confirmed transaction is paying funds back to some address we can assume to
+       /// own.
+       fn check_tx_and_push_spendable_outputs<L: Deref>(
+               &mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
+       ) where L::Target: Logger {
+               for spendable_output in self.get_spendable_outputs(tx) {
                        let entry = OnchainEventEntry {
                                txid: tx.txid(),
                                transaction: Some(tx.clone()),
index 9cf3f4ca29a323b16f5d76114a9a413138f2633f..f0cc872db0ad6731706cc380f59fd0c78be122f9 100644 (file)
@@ -9,7 +9,7 @@
 
 //! Further functional tests which test blockchain reorganizations.
 
-use crate::sign::EcdsaChannelSigner;
+use crate::sign::{EcdsaChannelSigner, SpendableOutputDescriptor};
 use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
 use crate::chain::transaction::OutPoint;
 use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
@@ -21,6 +21,7 @@ use crate::ln::msgs::ChannelMessageHandler;
 use crate::util::config::UserConfig;
 use crate::util::crypto::sign;
 use crate::util::ser::Writeable;
+use crate::util::scid_utils::block_from_scid;
 use crate::util::test_utils;
 
 use bitcoin::blockdata::transaction::EcdsaSighashType;
@@ -92,7 +93,7 @@ fn chanmon_fail_from_stale_commitment() {
        expect_payment_failed_with_update!(nodes[0], payment_hash, false, update_a.contents.short_channel_id, true);
 }
 
-fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) {
+fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
        let mut spendable = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
        assert_eq!(spendable.len(), 1);
        if let Event::SpendableOutputs { outputs, .. } = spendable.pop().unwrap() {
@@ -100,6 +101,7 @@ fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_t
                let spend_tx = node.keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
                        Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
                check_spends!(spend_tx, spendable_tx);
+               outputs
        } else { panic!(); }
 }
 
@@ -196,8 +198,8 @@ fn chanmon_claim_value_coop_close() {
        assert_eq!(shutdown_tx, nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0));
        assert_eq!(shutdown_tx.len(), 1);
 
-       mine_transaction(&nodes[0], &shutdown_tx[0]);
-       mine_transaction(&nodes[1], &shutdown_tx[0]);
+       let shutdown_tx_conf_height_a = block_from_scid(&mine_transaction(&nodes[0], &shutdown_tx[0]));
+       let shutdown_tx_conf_height_b = block_from_scid(&mine_transaction(&nodes[1], &shutdown_tx[0]));
 
        assert!(nodes[0].node.list_channels().is_empty());
        assert!(nodes[1].node.list_channels().is_empty());
@@ -216,16 +218,33 @@ fn chanmon_claim_value_coop_close() {
                }],
                nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
 
-       connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
-       connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
+       connect_blocks(&nodes[0], ANTI_REORG_DELAY - 2);
+       connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
+
+       assert!(get_monitor!(nodes[0], chan_id)
+               .get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a).is_empty());
+       assert!(get_monitor!(nodes[1], chan_id)
+               .get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b).is_empty());
+
+       connect_blocks(&nodes[0], 1);
+       connect_blocks(&nodes[1], 1);
 
        assert_eq!(Vec::<Balance>::new(),
                nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
        assert_eq!(Vec::<Balance>::new(),
                nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
 
-       test_spendable_output(&nodes[0], &shutdown_tx[0]);
-       test_spendable_output(&nodes[1], &shutdown_tx[0]);
+       let spendable_outputs_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
+       assert_eq!(
+               get_monitor!(nodes[0], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a),
+               spendable_outputs_a
+       );
+
+       let spendable_outputs_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
+       assert_eq!(
+               get_monitor!(nodes[1], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b),
+               spendable_outputs_b
+       );
 
        check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000);
        check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000);