Skip to content

Commit d09d457

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 130abf8 commit d09d457

File tree

2 files changed

+25
-40
lines changed

2 files changed

+25
-40
lines changed

lightning/src/chain/channelmonitor.rs

+14-27
Original file line numberDiff line numberDiff line change
@@ -1682,13 +1682,13 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16821682
/// `tx` is a transaction we'll scan the outputs of. If an output which can be spent by us is
16831683
/// found, a descriptor is returned. `confirmation_height` must be the height of the block in
16841684
/// which `tx` was included in.
1685-
pub fn get_spendable_output(&self, tx: &Transaction, confirmation_height: u32) -> Option<SpendableOutputDescriptor> {
1685+
pub fn get_spendable_outputs(&self, tx: &Transaction, confirmation_height: u32) -> Vec<SpendableOutputDescriptor> {
16861686
let inner = self.inner.lock().unwrap();
16871687
let current_height = inner.best_block.height;
16881688
if current_height.saturating_sub(ANTI_REORG_DELAY) + 1 >= confirmation_height {
1689-
inner.get_spendable_output(tx)
1689+
inner.get_spendable_outputs(tx)
16901690
} else {
1691-
None
1691+
Vec::new()
16921692
}
16931693
}
16941694
}
@@ -3463,7 +3463,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34633463
}
34643464
self.is_resolving_htlc_output(&tx, height, &block_hash, &logger);
34653465

3466-
self.check_tx_and_push_spendable_output(&tx, height, &block_hash, &logger);
3466+
self.check_tx_and_push_spendable_outputs(&tx, height, &block_hash, &logger);
34673467
}
34683468
}
34693469

@@ -4009,31 +4009,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40094009
}
40104010
}
40114011

4012-
fn get_spendable_output(&self, tx: &Transaction) -> Option<SpendableOutputDescriptor> {
4013-
for (i, outp) in tx.output.iter().enumerate() { // There is max one spendable output for any channel tx, including ones generated by us
4014-
if i > ::core::u16::MAX as usize {
4015-
// While it is possible that an output exists on chain which is greater than the
4016-
// 2^16th output in a given transaction, this is only possible if the output is not
4017-
// in a lightning transaction and was instead placed there by some third party who
4018-
// wishes to give us money for no reason.
4019-
// Namely, any lightning transactions which we pre-sign will never have anywhere
4020-
// near 2^16 outputs both because such transactions must have ~2^16 outputs who's
4021-
// scripts are not longer than one byte in length and because they are inherently
4022-
// non-standard due to their size.
4023-
// Thus, it is completely safe to ignore such outputs, and while it may result in
4024-
// us ignoring non-lightning fund to us, that is only possible if someone fills
4025-
// nearly a full block with garbage just to hit this case.
4026-
continue;
4027-
}
4012+
fn get_spendable_outputs(&self, tx: &Transaction) -> Vec<SpendableOutputDescriptor> {
4013+
let mut spendable_outputs = Vec::new();
4014+
for (i, outp) in tx.output.iter().enumerate() {
40284015
if outp.script_pubkey == self.destination_script {
4029-
return Some(SpendableOutputDescriptor::StaticOutput {
4016+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40304017
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40314018
output: outp.clone(),
40324019
});
40334020
}
40344021
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script {
40354022
if broadcasted_holder_revokable_script.0 == outp.script_pubkey {
4036-
return Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
4023+
spendable_outputs.push(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor {
40374024
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40384025
per_commitment_point: broadcasted_holder_revokable_script.1,
40394026
to_self_delay: self.on_holder_tx_csv,
@@ -4045,29 +4032,29 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
40454032
}
40464033
}
40474034
if self.counterparty_payment_script == outp.script_pubkey {
4048-
return Some(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
4035+
spendable_outputs.push(SpendableOutputDescriptor::StaticPaymentOutput(StaticPaymentOutputDescriptor {
40494036
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40504037
output: outp.clone(),
40514038
channel_keys_id: self.channel_keys_id,
40524039
channel_value_satoshis: self.channel_value_satoshis,
40534040
}));
40544041
}
40554042
if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) {
4056-
return Some(SpendableOutputDescriptor::StaticOutput {
4043+
spendable_outputs.push(SpendableOutputDescriptor::StaticOutput {
40574044
outpoint: OutPoint { txid: tx.txid(), index: i as u16 },
40584045
output: outp.clone(),
40594046
});
40604047
}
40614048
}
4062-
None
4049+
spendable_outputs
40634050
}
40644051

40654052
/// Checks if the confirmed transaction is paying funds back to some address we can assume to
40664053
/// own.
4067-
fn check_tx_and_push_spendable_output<L: Deref>(
4054+
fn check_tx_and_push_spendable_outputs<L: Deref>(
40684055
&mut self, tx: &Transaction, height: u32, block_hash: &BlockHash, logger: &L,
40694056
) where L::Target: Logger {
4070-
if let Some(spendable_output) = self.get_spendable_output(tx) {
4057+
for spendable_output in self.get_spendable_outputs(tx) {
40714058
let entry = OnchainEventEntry {
40724059
txid: tx.txid(),
40734060
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)