current_height, &broadcaster, &fee_estimator, &logger,
);
}
+
+ /// Returns the descriptor for a relevant output (i.e., one that we can spend) within the
+ /// transaction if one exists 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 an
+ /// output which can be spent by us is found, a descriptor is returned.
+ ///
+ /// `confirmation_height` must be the height of the block in which `tx` was included in.
+ pub fn get_spendable_output(&self, tx: &Transaction, confirmation_height: u32) -> Option<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_output(tx)
+ } else {
+ None
+ }
+ }
}
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_output(&tx, height, &block_hash, &logger);
}
}
}
}
- /// 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;
+ fn get_spendable_output(&self, tx: &Transaction) -> Option<SpendableOutputDescriptor> {
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
continue;
}
if outp.script_pubkey == self.destination_script {
- spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
+ return Some(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 {
+ return Some(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,
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 {
+ return Some(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 {
+ return Some(SpendableOutputDescriptor::StaticOutput {
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
output: outp.clone(),
});
- break;
}
}
- if let Some(spendable_output) = spendable_output {
+ None
+ }
+
+ /// Checks if the confirmed transaction is paying funds back to some address we can assume to
+ /// own.
+ fn check_tx_and_push_spendable_output<L: Deref>(
+ &mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
+ ) where L::Target: Logger {
+ if let Some(spendable_output) = self.get_spendable_output(tx) {
let entry = OnchainEventEntry {
txid: tx.txid(),
transaction: Some(tx.clone()),
//! 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};
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;
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) -> 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() {
+ if let Event::SpendableOutputs { mut outputs, .. } = spendable.pop().unwrap() {
assert_eq!(outputs.len(), 1);
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.pop().unwrap()
} else { panic!(); }
}
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());
}],
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_output(&shutdown_tx[0], shutdown_tx_conf_height_a).is_none());
+ assert!(get_monitor!(nodes[1], chan_id)
+ .get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_b).is_none());
+
+ 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_output_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
+ assert_eq!(
+ get_monitor!(nodes[0], chan_id)
+ .get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_a).unwrap(),
+ spendable_output_a
+ );
+
+ let spendable_output_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
+ assert_eq!(
+ get_monitor!(nodes[1], chan_id)
+ .get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_b).unwrap(),
+ spendable_output_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);