Write DiscardFunding contributions as an odd event type
What changed, and why it matters
This commit fixes a backward-compatibility bug in how a particular wallet event is saved to disk. A new type of funding contribution introduced in version 0.3 could not be read by version 0.2, which would crash or fail when loading a wallet that had been saved while a 'DiscardFunding' event was pending. The fix writes that specific case under a new, odd-numbered event type that older versions safely ignore, allowing users to downgrade from 0.3 to 0.2 without their wallet becoming unloadable. There is no attacker-controlled exploit here; it is a reliability/downgrade fix.
Treat as a normal bug-fix / compatibility patch. Review the follow-up commit that adds discarded UTXOs to Event::SpliceNegotiationFailed, since this commit alone causes downgraded nodes to lose the event. No urgent security deployment is required.
Security signals we found
Backward-compatibility / downgrade safety fix
Serialization format change to prevent load failures in older versions
Odd TLV type used to ensure older parsers ignore unknown event variant
No input validation, memory safety, or cryptographic weakness addressed
Evidence from the diff
The patch changes serialization of Event::DiscardFunding in lightning/src/events/mod.rs. When funding_info is FundingInfo::Contribution (added in 0.3), the event is now written with event type 53 (odd, ignored by older parsers) using TLV fields 1 and 3. Other variants still use event type 11 with fields 0, 2, and 4, preserving 0.2 compatibility. A corresponding reader branch for type 53 is added, and a unit test verifies both serialization paths. The commit notes this is a partial fix for issue #4919 and that a follow-up will recover discarded UTXOs in Event::SpliceNegotiationFailed after downgrade.
Changed components
lightning/src/events/mod.rsEvent::DiscardFunding serialization/deserializationFundingInfo::Contribution handlingInspect captured patch +73 / −11
### lightning/src/events/mod.rs
@@ -2279,18 +2279,28 @@ impl Writeable for Event {
});
},
&Event::DiscardFunding { ref channel_id, ref funding_info } => {
- 11u8.write(writer)?;
-
- let transaction = if let FundingInfo::Tx { transaction } = funding_info {
- Some(transaction)
+ if let FundingInfo::Contribution { .. } = funding_info {
+ // 0.2 requires a transaction or outpoint when reading event type 11, so write
+ // `FundingInfo::Contribution` under an odd event type it will ignore instead.
+ 53u8.write(writer)?;
+ write_tlv_fields!(writer, {
+ (1, channel_id, required),
+ (3, funding_info, required),
+ })
} else {
- None
- };
- write_tlv_fields!(writer, {
- (0, channel_id, required),
- (2, transaction, option),
- (4, funding_info, required),
- })
+ 11u8.write(writer)?;
+
+ let transaction = if let FundingInfo::Tx { transaction } = funding_info {
+ Some(transaction)
+ } else {
+ None
+ };
+ write_tlv_fields!(writer, {
+ (0, channel_id, required),
+ (2, transaction, option),
+ (4, funding_info, required),
+ })
+ }
},
&Event::PaymentPathSuccessful {
ref payment_id,
@@ -3229,6 +3239,20 @@ impl MaybeReadable for Event {
};
f()
},
+ 53u8 => {
+ let mut f = || {
+ _init_and_read_len_prefixed_tlv_fields!(reader, {
+ (1, channel_id, required),
+ (3, funding_info, required),
+ });
+
+ Ok(Some(Event::DiscardFunding {
+ channel_id: channel_id.0.unwrap(),
+ funding_info: funding_info.0.unwrap(),
+ }))
+ };
+ f()
+ },
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
// reads.
@@ -3409,6 +3433,44 @@ mod tests {
let decoded = Event::read(&mut &encoded[..]).unwrap().unwrap();
assert_eq!(event, decoded);
}
+
+ #[test]
+ fn discard_funding_event_type_depends_on_funding_info() {
+ let channel_id = ChannelId::from_bytes([2; 32]);
+
+ // `FundingInfo::Contribution` is written under an odd event type, which 0.2 skips as it
+ // cannot read the enum variant.
+ let event = Event::DiscardFunding {
+ channel_id,
+ funding_info: FundingInfo::Contribution {
+ inputs: vec![OutPoint {
+ txid: bitcoin::Txid::from_slice(&[9; 32]).unwrap(),
+ vout: 1,
+ }],
+ outputs: vec![ScriptBuf::new_p2wpkh(
+ &bitcoin::WPubkeyHash::from_slice(&[7; 20]).unwrap(),
+ )],
+ },
+ };
+ let encoded = event.encode();
+ assert_eq!(encoded[0], 53);
+ let decoded = Event::read(&mut &encoded[..]).unwrap().unwrap();
+ assert_eq!(event, decoded);
+
+ // Other variants remain under event type 11, which 0.2 can read.
+ let transaction = Transaction {
+ version: bitcoin::transaction::Version::TWO,
+ lock_time: bitcoin::absolute::LockTime::ZERO,
+ input: vec![],
+ output: vec![],
+ };
+ let event =
+ Event::DiscardFunding { channel_id, funding_info: FundingInfo::Tx { transaction } };
+ let encoded = event.encode();
+ assert_eq!(encoded[0], 11);
+ let decoded = Event::read(&mut &encoded[..]).unwrap().unwrap();
+ assert_eq!(event, decoded);
+ }
}
/// A trait indicating an object may generate events.Why this scored 35/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.