Skip to content

Commit dcca939

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 740ebb1 commit dcca939

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]),
@@ -1362,7 +1372,14 @@ impl<State> FundingBuilderInner<State> {
13621372
)?;
13631373

13641374
let mut value_removed = Amount::ZERO;
1365-
for output in self.outputs.iter() {
1375+
for (idx, output) in self.outputs.iter().enumerate() {
1376+
if self.outputs[..idx]
1377+
.iter()
1378+
.any(|existing_output| existing_output.script_pubkey == output.script_pubkey)
1379+
{
1380+
return Err(FundingContributionError::InvalidSpliceValue);
1381+
}
1382+
13661383
value_removed = match value_removed.checked_add(output.value) {
13671384
Some(sum) if sum <= Amount::MAX_MONEY => sum,
13681385
_ => return Err(FundingContributionError::InvalidSpliceValue),
@@ -2325,6 +2342,36 @@ mod tests {
23252342
);
23262343
}
23272344

2345+
#[test]
2346+
fn test_funding_builder_rejects_duplicate_inputs() {
2347+
let feerate = FeeRate::from_sat_per_kwu(2000);
2348+
let input = funding_input_sats(100_000);
2349+
2350+
let result = FundingTemplate::new(None, None, None, Amount::ZERO)
2351+
.without_prior_contribution(feerate, FeeRate::MAX)
2352+
.add_inputs(vec![input.clone(), input])
2353+
.unwrap()
2354+
.build();
2355+
2356+
assert!(matches!(result, Err(FundingContributionError::InvalidSpliceValue),));
2357+
}
2358+
2359+
#[test]
2360+
fn test_funding_builder_rejects_duplicate_outputs() {
2361+
let feerate = FeeRate::from_sat_per_kwu(2000);
2362+
let first_output = funding_output_sats(25_000);
2363+
let second_output = funding_output_sats(30_000);
2364+
assert_ne!(first_output, second_output);
2365+
assert_eq!(first_output.script_pubkey, second_output.script_pubkey);
2366+
2367+
let result = FundingTemplate::new(None, None, None, Amount::MAX_MONEY)
2368+
.without_prior_contribution(feerate, FeeRate::MAX)
2369+
.add_outputs(vec![first_output, second_output])
2370+
.build();
2371+
2372+
assert!(matches!(result, Err(FundingContributionError::InvalidSpliceValue),));
2373+
}
2374+
23282375
#[test]
23292376
fn test_funding_builder_remove_input_updates_manual_input_request() {
23302377
let feerate = FeeRate::from_sat_per_kwu(2000);

0 commit comments

Comments
 (0)