Skip to content

Commit b8f80f8

Browse files
committed
Allow retrieval of SpendableOutputDescriptors from relevant transactions
Currently, our API will only expose `SpendableOutputDescriptor`s once after they are no longer under reorg risk (see `ANTI_REORG_DELAY`). Users have often requested they'd like the ability to retrieve these in some other way, either for historical purposes, or to handle replaying any in the event of a failure.
1 parent 1ac53ed commit b8f80f8

File tree

2 files changed

+72
-22
lines changed

2 files changed

+72
-22
lines changed

lightning/src/chain/channelmonitor.rs

+42-13
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 descriptor for a relevant output (i.e., one that we can spend) within the
1674+
/// transaction if one exists 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 an
1687+
/// output which can be spent by us is found, a 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_output(&self, tx: &Transaction, confirmation_height: u32) -> Option<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_output(tx)
1695+
} else {
1696+
None
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_output(&tx, height, &block_hash, &logger);
34453472
}
34463473
}
34473474

@@ -3987,9 +4014,7 @@ 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;
4017+
fn get_spendable_output(&self, tx: &Transaction) -> Option<SpendableOutputDescriptor> {
39934018
for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us
39944019
if i > ::core::u16::MAX as usize {
39954020
// While it is possible that an output exists on chain which is greater than the
@@ -4006,15 +4031,14 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40064031
continue;
40074032
}
40084033
if outp.script_pubkey == self.destination_script {
4009-
spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
4034+
return Some(SpendableOutputDescriptor::StaticOutput {
40104035
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40114036
output: outp.clone(),
40124037
});
4013-
break;
40144038
}
40154039
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
40164040
if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
4017-
spendable_output = Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
4041+
return Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
40184042
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40194043
per_commitment_point: broadcasted_holder_revokable_script.1,
40204044
to_self_delay: self.on_holder_tx_csv,
@@ -4023,27 +4047,32 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40234047
channel_keys_id: self.channel_keys_id,
40244048
channel_value_satoshis: self.channel_value_satoshis,
40254049
}));
4026-
break;
40274050
}
40284051
}
40294052
if self.counterparty_payment_script == outp.script_pubkey {
4030-
spendable_output = Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
4053+
return Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
40314054
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40324055
output: outp.clone(),
40334056
channel_keys_id: self.channel_keys_id,
40344057
channel_value_satoshis: self.channel_value_satoshis,
40354058
}));
4036-
break;
40374059
}
40384060
if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) {
4039-
spendable_output = Some(SpendableOutputDescriptor::StaticOutput {
4061+
return Some(SpendableOutputDescriptor::StaticOutput {
40404062
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40414063
output: outp.clone(),
40424064
});
4043-
break;
40444065
}
40454066
}
4046-
if let Some(spendable_output) = spendable_output {
4067+
None
4068+
}
4069+
4070+
/// Checks if the confirmed transaction is paying funds back to some address we can assume to
4071+
/// own.
4072+
fn check_tx_and_push_spendable_output<L: Deref>(
4073+
&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
4074+
) where L::Target: Logger {
4075+
if let Some(spendable_output) = self.get_spendable_output(tx) {
40474076
let entry = OnchainEventEntry {
40484077
txid: tx.txid(),
40494078
transaction: Some(tx.clone()),

lightning/src/ln/monitor_tests.rs

+30-9
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) -> SpendableOutputDescriptor {
9697
let mut spendable = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
9798
assert_eq!(spendable.len(), 1);
98-
if let Event::SpendableOutputs { outputs, .. } = spendable.pop().unwrap() {
99+
if let Event::SpendableOutputs { mut 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.pop().unwrap()
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,35 @@ 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_output(&shutdown_tx[0], shutdown_tx_conf_height_a).is_none());
226+
assert!(get_monitor!(nodes[1], chan_id)
227+
.get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_b).is_none());
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_output_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
238+
assert_eq!(
239+
get_monitor!(nodes[0], chan_id)
240+
.get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_a).unwrap(),
241+
spendable_output_a
242+
);
243+
244+
let spendable_output_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
245+
assert_eq!(
246+
get_monitor!(nodes[1], chan_id)
247+
.get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_b).unwrap(),
248+
spendable_output_b
249+
);
229250

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

0 commit comments

Comments
 (0)