Move FundingTxInput::sequence to Utxo
What changed, and why it matters
This commit is a straightforward internal code reorganization in the Lightning Dev Kit's Rust library. It moves the 'sequence' field (a Bitcoin transaction detail that controls things like replace-by-fee) from one internal data structure called FundingTxInput into another called Utxo. The change is preparation for future work on splicing (a way to resize a Lightning channel) and avoids unnecessary lookups of previous transactions. There is no indication this fixes a security vulnerability or introduces a new attack path.
No security action required. Treat as a normal refactoring/code-quality change during review.
Security signals we found
No security-relevant behavioral change: default sequence remains ENABLE_RBF_NO_LOCKTIME
Serialization migration is defensive: legacy sequence is preserved if present
No new input validation or parsing of untrusted data
No memory-safety, cryptographic, or authorization changes
Evidence from the diff
The patch refactors where the nSequence value is stored. Previously FundingTxInput held both a Utxo and a separate sequence field; now Utxo carries sequence directly. FundingTxInput keeps backward-compatible serialization by reading any legacy sequence field and copying it into Utxo if Utxo was deserialized with the default value. All call sites that constructed TxIn objects now read sequence from utxo.sequence instead of a separate field. The default sequence used across constructors remains Sequence::ENABLE_RBF_NO_LOCKTIME.
Changed components
lightning/src/events/bump_transaction/mod.rslightning/src/ln/funding.rslightning/src/ln/interactivetxs.rslightning/src/util/anchor_channel_reserves.rsInspect captured patch +33 / −13
diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs
index ff03417..a79e927 100644
--- a/lightning/src/events/bump_transaction/mod.rs
+++ b/lightning/src/events/bump_transaction/mod.rs
@@ -284,12 +284,15 @@ pub struct Utxo {
/// with their lengths included, required to satisfy the output's script. The weight consumed by
/// the input's `script_sig` must account for [`WITNESS_SCALE_FACTOR`].
pub satisfaction_weight: u64,
+ /// The sequence number to use in the [`TxIn`] when spending the UTXO.
+ pub sequence: Sequence,
}
impl_writeable_tlv_based!(Utxo, {
(1, outpoint, required),
(3, output, required),
(5, satisfaction_weight, required),
+ (7, sequence, (default_value, Sequence::ENABLE_RBF_NO_LOCKTIME)),
});
impl Utxo {
@@ -304,6 +307,7 @@ impl Utxo {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2pkh(pubkey_hash) },
satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64 + 1, /* empty witness */
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
@@ -323,6 +327,7 @@ impl Utxo {
},
satisfaction_weight: script_sig_size * WITNESS_SCALE_FACTOR as u64
+ P2WPKH_WITNESS_WEIGHT,
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
@@ -332,6 +337,7 @@ impl Utxo {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2wpkh(pubkey_hash) },
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + P2WPKH_WITNESS_WEIGHT,
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
@@ -343,6 +349,7 @@ impl Utxo {
outpoint,
output: TxOut { value, script_pubkey: ScriptBuf::new_p2tr_tweaked(tweaked_public_key) },
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + P2TR_KEY_PATH_WITNESS_WEIGHT,
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
}
@@ -737,7 +744,7 @@ where
tx.input.push(TxIn {
previous_output: utxo.outpoint,
script_sig: ScriptBuf::new(),
- sequence: Sequence::ZERO,
+ sequence: utxo.sequence,
witness: Witness::new(),
});
}
@@ -1392,6 +1399,7 @@ mod tests {
script_pubkey: ScriptBuf::new(),
},
satisfaction_weight: 5, // Just the script_sig and witness lengths
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}],
change_output: None,
},
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index 8092a0e..50e0938 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -108,11 +108,6 @@ pub struct FundingTxInput {
/// [`TxOut`]: bitcoin::TxOut
pub(super) utxo: Utxo,
- /// The sequence number to use in the [`TxIn`].
- ///
- /// [`TxIn`]: bitcoin::TxIn
- pub(super) sequence: Sequence,
-
/// The transaction containing the unspent [`TxOut`] referenced by [`utxo`].
///
/// [`TxOut`]: bitcoin::TxOut
@@ -122,7 +117,19 @@ pub struct FundingTxInput {
impl_writeable_tlv_based!(FundingTxInput, {
(1, utxo, required),
- (3, sequence, required),
+ (3, _sequence, (legacy, Sequence,
+ |read_val: Option<&Sequence>| {
+ if let Some(sequence) = read_val {
+ // Utxo contains sequence now, so update it if the value read here differs since
+ // this indicates Utxo::sequence was read with default_value
+ let utxo: &mut Utxo = utxo.0.as_mut().expect("utxo is required");
+ if utxo.sequence != *sequence {
+ utxo.sequence = *sequence;
+ }
+ }
+ Ok(())
+ },
+ |input: &FundingTxInput| Some(input.utxo.sequence))),
(5, prevtx, required),
});
@@ -140,8 +147,8 @@ impl FundingTxInput {
.ok_or(())?
.clone(),
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + witness_weight.to_wu(),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
},
- sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
prevtx,
})
}
@@ -234,14 +241,14 @@ impl FundingTxInput {
///
/// [`TxIn`]: bitcoin::TxIn
pub fn sequence(&self) -> Sequence {
- self.sequence
+ self.utxo.sequence
}
/// Sets the sequence number to use in the [`TxIn`].
///
/// [`TxIn`]: bitcoin::TxIn
pub fn set_sequence(&mut self, sequence: Sequence) {
- self.sequence = sequence;
+ self.utxo.sequence = sequence;
}
/// Converts the [`FundingTxInput`] into a [`Utxo`] for coin selection.
diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs
index a004f6e..3c47658 100644
--- a/lightning/src/ln/interactivetxs.rs
+++ b/lightning/src/ln/interactivetxs.rs
@@ -2054,9 +2054,13 @@ impl InteractiveTxConstructor {
let mut inputs_to_contribute: Vec<(SerialId, InputOwned)> = inputs_to_contribute
.into_iter()
- .map(|FundingTxInput { utxo, sequence, prevtx: prev_tx }| {
+ .map(|FundingTxInput { utxo, prevtx: prev_tx }| {
let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
- let txin = TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() };
+ let txin = TxIn {
+ previous_output: utxo.outpoint,
+ sequence: utxo.sequence,
+ ..Default::default()
+ };
let prev_output = utxo.output;
let input = InputOwned::Single(SingleOwnedInput {
input: txin,
diff --git a/lightning/src/util/anchor_channel_reserves.rs b/lightning/src/util/anchor_channel_reserves.rs
index 8026af0..25a0e7c 100644
--- a/lightning/src/util/anchor_channel_reserves.rs
+++ b/lightning/src/util/anchor_channel_reserves.rs
@@ -315,7 +315,7 @@ where
#[cfg(test)]
mod test {
use super::*;
- use bitcoin::{OutPoint, ScriptBuf, TxOut, Txid};
+ use bitcoin::{OutPoint, ScriptBuf, Sequence, TxOut, Txid};
use std::str::FromStr;
#[test]
@@ -343,6 +343,7 @@ mod test {
},
output: TxOut { value: amount, script_pubkey: ScriptBuf::new() },
satisfaction_weight: 1 * 4 + (1 + 1 + 72 + 1 + 33),
+ sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
}
}
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.