Skip to content

Commit c240f1b

Browse files
committed
Detect duplicate inputs and outputs upon building FundingBuilder
While this is already enforced when we get to the interactive negotiation phase, we choose to fail early anyway.
1 parent 7ff5eb7 commit c240f1b

1 file changed

Lines changed: 52 additions & 5 deletions

File tree

lightning/src/ln/funding.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ pub enum FundingContributionError {
132132
/// The minimum RBF feerate.
133133
min_rbf_feerate: FeeRate,
134134
},
135-
/// The splice value is invalid (zero, empty outputs, exceeds the maximum money supply, or
136-
/// splices out more than the available channel balance).
135+
/// The splice value is invalid (zero, empty outputs, duplicate inputs or outputs, exceeds the
136+
/// maximum money supply, or splices out more than the available channel balance).
137137
InvalidSpliceValue,
138138
/// An input's `prevtx` is too large to fit in a `tx_add_input` message.
139139
PrevTxTooLarge,
@@ -164,7 +164,10 @@ impl core::fmt::Display for FundingContributionError {
164164
write!(f, "Feerate {} is below minimum RBF feerate {}", feerate, min_rbf_feerate)
165165
},
166166
FundingContributionError::InvalidSpliceValue => {
167-
write!(f, "Invalid splice value (zero, empty, exceeds limit, or overdraws balance)")
167+
write!(
168+
f,
169+
"Invalid splice value (zero, empty, duplicate, exceeds limit, or overdraws balance)"
170+
)
168171
},
169172
FundingContributionError::PrevTxTooLarge => {
170173
write!(f, "Input prevtx is too large to fit in a tx_add_input message")
@@ -514,7 +517,14 @@ fn estimate_transaction_fee(
514517

515518
fn validate_inputs(inputs: &[FundingTxInput]) -> Result<(), FundingContributionError> {
516519
let mut total_value = Amount::ZERO;
517-
for input in inputs {
520+
for (idx, input) in inputs.iter().enumerate() {
521+
if inputs[..idx]
522+
.iter()
523+
.any(|existing_input| existing_input.utxo.outpoint == input.utxo.outpoint)
524+
{
525+
return Err(FundingContributionError::InvalidSpliceValue);
526+
}
527+
518528
use crate::util::ser::Writeable;
519529
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
520530
channel_id: ChannelId([0; 32]),
@@ -1360,7 +1370,14 @@ impl<State> FundingBuilderInner<State> {
13601370
)?;
13611371

13621372
let mut value_removed = Amount::ZERO;
1363-
for output in self.outputs.iter() {
1373+
for (idx, output) in self.outputs.iter().enumerate() {
1374+
if self.outputs[..idx]
1375+
.iter()
1376+
.any(|existing_output| existing_output.script_pubkey == output.script_pubkey)
1377+
{
1378+
return Err(FundingContributionError::InvalidSpliceValue);
1379+
}
1380+
13641381
value_removed = match value_removed.checked_add(output.value) {
13651382
Some(sum) if sum <= Amount::MAX_MONEY => sum,
13661383
_ => return Err(FundingContributionError::InvalidSpliceValue),
@@ -2339,6 +2356,36 @@ mod tests {
23392356
);
23402357
}
23412358

2359+
#[test]
2360+
fn test_funding_builder_rejects_duplicate_inputs() {
2361+
let feerate = FeeRate::from_sat_per_kwu(2000);
2362+
let input = funding_input_sats(100_000);
2363+
2364+
let result = FundingTemplate::new(None, None, None, Amount::ZERO)
2365+
.without_prior_contribution(feerate, FeeRate::MAX)
2366+
.add_inputs(vec![input.clone(), input])
2367+
.unwrap()
2368+
.build();
2369+
2370+
assert!(matches!(result, Err(FundingContributionError::InvalidSpliceValue),));
2371+
}
2372+
2373+
#[test]
2374+
fn test_funding_builder_rejects_duplicate_outputs() {
2375+
let feerate = FeeRate::from_sat_per_kwu(2000);
2376+
let first_output = funding_output_sats(25_000);
2377+
let second_output = funding_output_sats(30_000);
2378+
assert_ne!(first_output, second_output);
2379+
assert_eq!(first_output.script_pubkey, second_output.script_pubkey);
2380+
2381+
let result = FundingTemplate::new(None, None, None, Amount::MAX_MONEY)
2382+
.without_prior_contribution(feerate, FeeRate::MAX)
2383+
.add_outputs(vec![first_output, second_output])
2384+
.build();
2385+
2386+
assert!(matches!(result, Err(FundingContributionError::InvalidSpliceValue),));
2387+
}
2388+
23422389
#[test]
23432390
fn test_funding_builder_remove_input_updates_manual_input_request() {
23442391
let feerate = FeeRate::from_sat_per_kwu(2000);

0 commit comments

Comments
 (0)