Skip to content

Commit 6016101

Browse files
authored
Merge pull request #2609 from wpaulino/monitor-get-spendable-output
Allow retrieval of SpendableOutputDescriptors from relevant transactions
2 parents db41b87 + ffec24b commit 6016101

File tree

2 files changed

+71
-36
lines changed

2 files changed

+71
-36
lines changed

lightning/src/chain/channelmonitor.rs

+44-28
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,33 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16691669
current_height, &broadcaster, &fee_estimator, &logger,
16701670
);
16711671
}
1672+
1673+
/// Returns the descriptors for relevant outputs (i.e., those that we can spend) within the
1674+
/// transaction if they exist and the transaction has at least [`ANTI_REORG_DELAY`]
1675+
/// confirmations.
1676+
///
1677+
/// Descriptors returned by this method are primarily exposed via [`Event::SpendableOutputs`]
1678+
/// once they are no longer under reorg risk. This method serves as a way to retrieve these
1679+
/// descriptors at a later time, either for historical purposes, or to replay any
1680+
/// missed/unhandled descriptors. For the purpose of gathering historical records, if the
1681+
/// channel close has fully resolved (i.e., [`ChannelMonitor::get_claimable_balances`] returns
1682+
/// an empty set), you can retrieve all spendable outputs by providing all descendant spending
1683+
/// transactions starting from the channel's funding or closing transaction that have at least
1684+
/// [`ANTI_REORG_DELAY`] confirmations.
1685+
///
1686+
/// `tx` is a transaction we'll scan the outputs of. Any transaction can be provided. If any
1687+
/// outputs which can be spent by us are found, at least one descriptor is returned.
1688+
///
1689+
/// `confirmation_height` must be the height of the block in which `tx` was included in.
1690+
pub fn get_spendable_outputs(&self, tx: &Transaction, confirmation_height: u32) -> Vec<SpendableOutputDescriptor> {
1691+
let inner = self.inner.lock().unwrap();
1692+
let current_height = inner.best_block.height;
1693+
if current_height.saturating_sub(ANTI_REORG_DELAY) + 1 >= confirmation_height {
1694+
inner.get_spendable_outputs(tx)
1695+
} else {
1696+
Vec::new()
1697+
}
1698+
}
16721699
}
16731700

16741701
impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
@@ -3441,7 +3468,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34413468
}
34423469
self.is_resolving_htlc_output(&tx, height, &block_hash, &logger);
34433470

3444-
self.is_paying_spendable_output(&tx, height, &block_hash, &logger);
3471+
self.check_tx_and_push_spendable_outputs(&tx, height, &block_hash, &logger);
34453472
}
34463473
}
34473474

@@ -3987,34 +4014,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
39874014
}
39884015
}
39894016

3990-
/// Check if any transaction broadcasted is paying fund back to some address we can assume to own
3991-
fn is_paying_spendable_output<L: Deref>(&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L) where L::Target: Logger {
3992-
let mut spendable_output = None;
3993-
for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us
3994-
if i > ::core::u16::MAX as usize {
3995-
// While it is possible that an output exists on chain which is greater than the
3996-
// 2^16th output in a given transaction, this is only possible if the output is not
3997-
// in a lightning transaction and was instead placed there by some third party who
3998-
// wishes to give us money for no reason.
3999-
// Namely, any lightning transactions which we pre-sign will never have anywhere
4000-
// near 2^16 outputs both because such transactions must have ~2^16 outputs who's
4001-
// scripts are not longer than one byte in length and because they are inherently
4002-
// non-standard due to their size.
4003-
// Thus, it is completely safe to ignore such outputs, and while it may result in
4004-
// us ignoring non-lightning fund to us, that is only possible if someone fills
4005-
// nearly a full block with garbage just to hit this case.
4006-
continue;
4007-
}
4017+
fn get_spendable_outputs(&self, tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
4018+
let mut spendable_outputs = Vec::new();
4019+
for (i, outp) in tx.output.iter().enumerate() {
40084020
if outp.script_pubkey == self.destination_script {
4009-
spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
4021+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40104022
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40114023
output: outp.clone(),
40124024
});
4013-
break;
40144025
}
40154026
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
40164027
if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
4017-
spendable_output = Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
4028+
spendable_outputs.push(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
40184029
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40194030
per_commitment_point: broadcasted_holder_revokable_script.1,
40204031
to_self_delay: self.on_holder_tx_csv,
@@ -4023,27 +4034,32 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40234034
channel_keys_id: self.channel_keys_id,
40244035
channel_value_satoshis: self.channel_value_satoshis,
40254036
}));
4026-
break;
40274037
}
40284038
}
40294039
if self.counterparty_payment_script == outp.script_pubkey {
4030-
spendable_output = Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
4040+
spendable_outputs.push(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
40314041
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40324042
output: outp.clone(),
40334043
channel_keys_id: self.channel_keys_id,
40344044
channel_value_satoshis: self.channel_value_satoshis,
40354045
}));
4036-
break;
40374046
}
40384047
if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) {
4039-
spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
4048+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40404049
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40414050
output: outp.clone(),
40424051
});
4043-
break;
40444052
}
40454053
}
4046-
if let Some(spendable_output) = spendable_output {
4054+
spendable_outputs
4055+
}
4056+
4057+
/// Checks if the confirmed transaction is paying funds back to some address we can assume to
4058+
/// own.
4059+
fn check_tx_and_push_spendable_outputs<L: Deref>(
4060+
&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
4061+
) where L::Target: Logger {
4062+
for spendable_output in self.get_spendable_outputs(tx) {
40474063
let entry = OnchainEventEntry {
40484064
txid: tx.txid(),
40494065
transaction: Some(tx.clone()),

lightning/src/ln/monitor_tests.rs

+27-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
//! Further functional tests which test blockchain reorganizations.
1111
12-
use crate::sign::EcdsaChannelSigner;
12+
use crate::sign::{EcdsaChannelSigner, SpendableOutputDescriptor};
1313
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
1414
use crate::chain::transaction::OutPoint;
1515
use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
@@ -21,6 +21,7 @@ use crate::ln::msgs::ChannelMessageHandler;
2121
use crate::util::config::UserConfig;
2222
use crate::util::crypto::sign;
2323
use crate::util::ser::Writeable;
24+
use crate::util::scid_utils::block_from_scid;
2425
use crate::util::test_utils;
2526

2627
use bitcoin::blockdata::transaction::EcdsaSighashType;
@@ -92,14 +93,15 @@ fn chanmon_fail_from_stale_commitment() {
9293
expect_payment_failed_with_update!(nodes[0], payment_hash, false, update_a.contents.short_channel_id, true);
9394
}
9495

95-
fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) {
96+
fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
9697
let mut spendable = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
9798
assert_eq!(spendable.len(), 1);
9899
if let Event::SpendableOutputs { outputs, .. } = spendable.pop().unwrap() {
99100
assert_eq!(outputs.len(), 1);
100101
let spend_tx = node.keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
101102
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
102103
check_spends!(spend_tx, spendable_tx);
104+
outputs
103105
} else { panic!(); }
104106
}
105107

@@ -196,8 +198,8 @@ fn chanmon_claim_value_coop_close() {
196198
assert_eq!(shutdown_tx, nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0));
197199
assert_eq!(shutdown_tx.len(), 1);
198200

199-
mine_transaction(&nodes[0], &shutdown_tx[0]);
200-
mine_transaction(&nodes[1], &shutdown_tx[0]);
201+
let shutdown_tx_conf_height_a = block_from_scid(&mine_transaction(&nodes[0], &shutdown_tx[0]));
202+
let shutdown_tx_conf_height_b = block_from_scid(&mine_transaction(&nodes[1], &shutdown_tx[0]));
201203

202204
assert!(nodes[0].node.list_channels().is_empty());
203205
assert!(nodes[1].node.list_channels().is_empty());
@@ -216,16 +218,33 @@ fn chanmon_claim_value_coop_close() {
216218
}],
217219
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
218220

219-
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
220-
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
221+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 2);
222+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
223+
224+
assert!(get_monitor!(nodes[0], chan_id)
225+
.get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a).is_empty());
226+
assert!(get_monitor!(nodes[1], chan_id)
227+
.get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b).is_empty());
228+
229+
connect_blocks(&nodes[0], 1);
230+
connect_blocks(&nodes[1], 1);
221231

222232
assert_eq!(Vec::<Balance>::new(),
223233
nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
224234
assert_eq!(Vec::<Balance>::new(),
225235
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
226236

227-
test_spendable_output(&nodes[0], &shutdown_tx[0]);
228-
test_spendable_output(&nodes[1], &shutdown_tx[0]);
237+
let spendable_outputs_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
238+
assert_eq!(
239+
get_monitor!(nodes[0], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a),
240+
spendable_outputs_a
241+
);
242+
243+
let spendable_outputs_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
244+
assert_eq!(
245+
get_monitor!(nodes[1], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b),
246+
spendable_outputs_b
247+
);
229248

230249
check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure, [nodes[1].node.get_our_node_id()], 1000000);
231250
check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure, [nodes[0].node.get_our_node_id()], 1000000);

0 commit comments

Comments
 (0)