Skip to content

Commit ffec24b

Browse files
committed
Retrieve all possible spendable outputs from transactions
Assuming our keys haven't been compromised, and that random transactions aren't learning of these scripts somehow and sending funds to them, it was only possible for one spendable output to exist within a transaction. - `shutdown_script` can only exist in co-op close transactions. - `counterparty_payment_script` can only exist in counterparty commitment transactions. - `broadcasted_holder_revokable_script` can only exist in holder commitment/HTLC transactions. - `destination_script` can exist in any other type of claim we support. Now that we're exposing this API to users such that they can rescan any relevant transactions, there's no harm in allowing them to claim more funds from spendable outputs than we expected.
1 parent b8f80f8 commit ffec24b

File tree

2 files changed

+29
-44
lines changed

2 files changed

+29
-44
lines changed

lightning/src/chain/channelmonitor.rs

+18-31
Original file line numberDiff line numberDiff line change
@@ -1670,8 +1670,8 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16701670
);
16711671
}
16721672

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`]
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`]
16751675
/// confirmations.
16761676
///
16771677
/// Descriptors returned by this method are primarily exposed via [`Event::SpendableOutputs`]
@@ -1683,17 +1683,17 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16831683
/// transactions starting from the channel's funding or closing transaction that have at least
16841684
/// [`ANTI_REORG_DELAY`] confirmations.
16851685
///
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.
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.
16881688
///
16891689
/// `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> {
1690+
pub fn get_spendable_outputs(&self, tx: &Transaction, confirmation_height: u32) -> Vec<SpendableOutputDescriptor> {
16911691
let inner = self.inner.lock().unwrap();
16921692
let current_height = inner.best_block.height;
16931693
if current_height.saturating_sub(ANTI_REORG_DELAY) + 1 >= confirmation_height {
1694-
inner.get_spendable_output(tx)
1694+
inner.get_spendable_outputs(tx)
16951695
} else {
1696-
None
1696+
Vec::new()
16971697
}
16981698
}
16991699
}
@@ -3468,7 +3468,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34683468
}
34693469
self.is_resolving_htlc_output(&tx, height, &block_hash, &logger);
34703470

3471-
self.check_tx_and_push_spendable_output(&tx, height, &block_hash, &logger);
3471+
self.check_tx_and_push_spendable_outputs(&tx, height, &block_hash, &logger);
34723472
}
34733473
}
34743474

@@ -4014,31 +4014,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40144014
}
40154015
}
40164016

4017-
fn get_spendable_output(&self, tx: &Transaction) -> Option<SpendableOutputDescriptor> {
4018-
for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us
4019-
if i > ::core::u16::MAX as usize {
4020-
// While it is possible that an output exists on chain which is greater than the
4021-
// 2^16th output in a given transaction, this is only possible if the output is not
4022-
// in a lightning transaction and was instead placed there by some third party who
4023-
// wishes to give us money for no reason.
4024-
// Namely, any lightning transactions which we pre-sign will never have anywhere
4025-
// near 2^16 outputs both because such transactions must have ~2^16 outputs who's
4026-
// scripts are not longer than one byte in length and because they are inherently
4027-
// non-standard due to their size.
4028-
// Thus, it is completely safe to ignore such outputs, and while it may result in
4029-
// us ignoring non-lightning fund to us, that is only possible if someone fills
4030-
// nearly a full block with garbage just to hit this case.
4031-
continue;
4032-
}
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() {
40334020
if outp.script_pubkey == self.destination_script {
4034-
return Some(SpendableOutputDescriptor::StaticOutput {
4021+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40354022
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40364023
output: outp.clone(),
40374024
});
40384025
}
40394026
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
40404027
if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
4041-
return Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
4028+
spendable_outputs.push(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
40424029
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40434030
per_commitment_point: broadcasted_holder_revokable_script.1,
40444031
to_self_delay: self.on_holder_tx_csv,
@@ -4050,29 +4037,29 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40504037
}
40514038
}
40524039
if self.counterparty_payment_script == outp.script_pubkey {
4053-
return Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
4040+
spendable_outputs.push(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
40544041
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40554042
output: outp.clone(),
40564043
channel_keys_id: self.channel_keys_id,
40574044
channel_value_satoshis: self.channel_value_satoshis,
40584045
}));
40594046
}
40604047
if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) {
4061-
return Some(SpendableOutputDescriptor::StaticOutput {
4048+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40624049
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40634050
output: outp.clone(),
40644051
});
40654052
}
40664053
}
4067-
None
4054+
spendable_outputs
40684055
}
40694056

40704057
/// Checks if the confirmed transaction is paying funds back to some address we can assume to
40714058
/// own.
4072-
fn check_tx_and_push_spendable_output<L: Deref>(
4059+
fn check_tx_and_push_spendable_outputs<L: Deref>(
40734060
&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
40744061
) where L::Target: Logger {
4075-
if let Some(spendable_output) = self.get_spendable_output(tx) {
4062+
for spendable_output in self.get_spendable_outputs(tx) {
40764063
let entry = OnchainEventEntry {
40774064
txid: tx.txid(),
40784065
transaction: Some(tx.clone()),

lightning/src/ln/monitor_tests.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ fn chanmon_fail_from_stale_commitment() {
9393
expect_payment_failed_with_update!(nodes[0], payment_hash, false, update_a.contents.short_channel_id, true);
9494
}
9595

96-
fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) -> SpendableOutputDescriptor {
96+
fn test_spendable_output<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, spendable_tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
9797
let mut spendable = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
9898
assert_eq!(spendable.len(), 1);
99-
if let Event::SpendableOutputs { mut outputs, .. } = spendable.pop().unwrap() {
99+
if let Event::SpendableOutputs { outputs, .. } = spendable.pop().unwrap() {
100100
assert_eq!(outputs.len(), 1);
101101
let spend_tx = node.keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
102102
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, None, &Secp256k1::new()).unwrap();
103103
check_spends!(spend_tx, spendable_tx);
104-
outputs.pop().unwrap()
104+
outputs
105105
} else { panic!(); }
106106
}
107107

@@ -222,9 +222,9 @@ fn chanmon_claim_value_coop_close() {
222222
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
223223

224224
assert!(get_monitor!(nodes[0], chan_id)
225-
.get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_a).is_none());
225+
.get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a).is_empty());
226226
assert!(get_monitor!(nodes[1], chan_id)
227-
.get_spendable_output(&shutdown_tx[0], shutdown_tx_conf_height_b).is_none());
227+
.get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b).is_empty());
228228

229229
connect_blocks(&nodes[0], 1);
230230
connect_blocks(&nodes[1], 1);
@@ -234,18 +234,16 @@ fn chanmon_claim_value_coop_close() {
234234
assert_eq!(Vec::<Balance>::new(),
235235
nodes[1].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
236236

237-
let spendable_output_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
237+
let spendable_outputs_a = test_spendable_output(&nodes[0], &shutdown_tx[0]);
238238
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
239+
get_monitor!(nodes[0], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_a),
240+
spendable_outputs_a
242241
);
243242

244-
let spendable_output_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
243+
let spendable_outputs_b = test_spendable_output(&nodes[1], &shutdown_tx[0]);
245244
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
245+
get_monitor!(nodes[1], chan_id).get_spendable_outputs(&shutdown_tx[0], shutdown_tx_conf_height_b),
246+
spendable_outputs_b
249247
);
250248

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

0 commit comments

Comments
 (0)