Skip to content

Commit 2e4a2ac

Browse files
authored
Merge pull request #4390 from jkczyz/2026-02-legacy-tlv-read
Add custom TLV read/write
2 parents 2424c4a + e71ad81 commit 2e4a2ac

6 files changed

Lines changed: 73 additions & 26 deletions

File tree

lightning-macros/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ fn process_fields(group: Group) -> proc_macro::TokenStream {
138138
if let TokenTree::Group(group) = ty_info {
139139
let first_group_tok = group.stream().into_iter().next().unwrap();
140140
if let TokenTree::Ident(ident) = first_group_tok {
141-
if ident.to_string() == "legacy" {
141+
if ident.to_string() == "legacy" || ident.to_string() == "custom" {
142142
continue;
143143
}
144144
}
@@ -155,13 +155,13 @@ fn process_fields(group: Group) -> proc_macro::TokenStream {
155155
computed_fields
156156
}
157157

158-
/// Scans a match statement for legacy fields which should be skipped.
158+
/// Scans a match statement for legacy or custom fields which should be skipped.
159159
///
160160
/// This is used internally in LDK's TLV serialization logic and is not expected to be used by
161161
/// other crates.
162162
///
163163
/// Wraps a `match self {..}` statement and scans the fields in the match patterns (in the form
164-
/// `ref $field_name: $field_ty`) for types marked `legacy`, skipping those fields.
164+
/// `ref $field_name: $field_ty`) for types marked `legacy` or `custom`, skipping those fields.
165165
///
166166
/// Specifically, it expects input like the following, simply dropping `field3` and the
167167
/// `: $field_ty` after each field name.

lightning/src/chain/package.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl_writeable_tlv_based!(RevokedOutput, {
183183
(12, on_counterparty_tx_csv, required),
184184
// Unused since 0.1, this setting causes downgrades to before 0.1 to refuse to
185185
// aggregate `RevokedOutput` claims, which is the more conservative stance.
186-
(14, is_counterparty_balance_on_anchors, (legacy, (), |_| Some(()))),
186+
(14, is_counterparty_balance_on_anchors, (legacy, (), |_| Ok(()), |_| Some(()))),
187187
(15, channel_parameters, (option: ReadableArgs, None)), // Added in 0.2.
188188
});
189189

lightning/src/ln/channel_state.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,9 @@ impl_writeable_tlv_based!(ChannelDetails, {
607607
(10, channel_value_satoshis, required),
608608
(12, unspendable_punishment_reserve, option),
609609
// Note that _user_channel_id_low is used below, but rustc warns anyway
610-
(14, _user_channel_id_low, (legacy, u64,
610+
(14, _user_channel_id_low, (legacy, u64, |_| Ok(()),
611611
|us: &ChannelDetails| Some(us.user_channel_id as u64))),
612-
(16, _balance_msat, (legacy, u64, |us: &ChannelDetails| Some(us.next_outbound_htlc_limit_msat))),
612+
(16, _balance_msat, (legacy, u64, |_| Ok(()), |us: &ChannelDetails| Some(us.next_outbound_htlc_limit_msat))),
613613
(18, outbound_capacity_msat, required),
614614
(19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat)),
615615
(20, inbound_capacity_msat, required),
@@ -623,7 +623,7 @@ impl_writeable_tlv_based!(ChannelDetails, {
623623
(33, inbound_htlc_minimum_msat, option),
624624
(35, inbound_htlc_maximum_msat, option),
625625
// Note that _user_channel_id_high is used below, but rustc warns anyway
626-
(37, _user_channel_id_high, (legacy, u64,
626+
(37, _user_channel_id_high, (legacy, u64, |_| Ok(()),
627627
|us: &ChannelDetails| Some((us.user_channel_id >> 64) as u64))),
628628
(39, feerate_sat_per_1000_weight, option),
629629
(41, channel_shutdown_state, option),

lightning/src/ln/onion_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,14 +1943,14 @@ impl Readable for HTLCFailReason {
19431943

19441944
impl_writeable_tlv_based_enum!(HTLCFailReasonRepr,
19451945
(0, LightningError) => {
1946-
(0, data, (legacy, Vec<u8>, |us|
1946+
(0, data, (legacy, Vec<u8>, |_| Ok(()), |us|
19471947
if let &HTLCFailReasonRepr::LightningError { err: msgs::OnionErrorPacket { ref data, .. }, .. } = us {
19481948
Some(data)
19491949
} else {
19501950
None
19511951
})
19521952
),
1953-
(1, attribution_data, (legacy, AttributionData, |us|
1953+
(1, attribution_data, (legacy, AttributionData, |_| Ok(()), |us|
19541954
if let &HTLCFailReasonRepr::LightningError { err: msgs::OnionErrorPacket { ref attribution_data, .. }, .. } = us {
19551955
attribution_data.as_ref()
19561956
} else {
@@ -1961,7 +1961,7 @@ impl_writeable_tlv_based_enum!(HTLCFailReasonRepr,
19611961
(_unused, err, (static_value, msgs::OnionErrorPacket { data: data.ok_or(DecodeError::InvalidValue)?, attribution_data })),
19621962
},
19631963
(1, Reason) => {
1964-
(0, _failure_code, (legacy, u16,
1964+
(0, _failure_code, (legacy, u16, |_| Ok(()),
19651965
|r: &HTLCFailReasonRepr| match r {
19661966
HTLCFailReasonRepr::LightningError{ .. } => None,
19671967
HTLCFailReasonRepr::Reason{ failure_reason, .. } => Some(failure_reason.failure_code())

lightning/src/ln/outbound_payment.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,7 +2731,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27312731
(5, AwaitingInvoice) => {
27322732
(0, expiration, required),
27332733
(2, retry_strategy, required),
2734-
(4, _max_total_routing_fee_msat, (legacy, u64,
2734+
(4, _max_total_routing_fee_msat, (legacy, u64, |_| Ok(()),
27352735
|us: &PendingOutboundPayment| match us {
27362736
PendingOutboundPayment::AwaitingInvoice { route_params_config, .. } => route_params_config.max_total_routing_fee_msat,
27372737
_ => None,
@@ -2748,7 +2748,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27482748
(7, InvoiceReceived) => {
27492749
(0, payment_hash, required),
27502750
(2, retry_strategy, required),
2751-
(4, _max_total_routing_fee_msat, (legacy, u64,
2751+
(4, _max_total_routing_fee_msat, (legacy, u64, |_| Ok(()),
27522752
|us: &PendingOutboundPayment| match us {
27532753
PendingOutboundPayment::InvoiceReceived { route_params_config, .. } => route_params_config.max_total_routing_fee_msat,
27542754
_ => None,
@@ -2779,7 +2779,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
27792779
(11, AwaitingOffer) => {
27802780
(0, expiration, required),
27812781
(2, retry_strategy, required),
2782-
(4, _max_total_routing_fee_msat, (legacy, u64,
2782+
(4, _max_total_routing_fee_msat, (legacy, u64, |_| Ok(()),
27832783
|us: &PendingOutboundPayment| match us {
27842784
PendingOutboundPayment::AwaitingOffer { route_params_config, .. } => route_params_config.max_total_routing_fee_msat,
27852785
_ => None,

lightning/src/util/ser_macros.rs

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ macro_rules! _encode_tlv {
4545
field.write($stream)?;
4646
}
4747
};
48-
($stream: expr, $optional_type: expr, $optional_field: expr, (legacy, $fieldty: ty, $write: expr) $(, $self: ident)?) => { {
48+
($stream: expr, $optional_type: expr, $optional_field: expr, (legacy, $fieldty: ty, $read: expr, $write: expr) $(, $self: ident)?) => { {
4949
let value: Option<_> = $write($($self)?);
5050
#[cfg(debug_assertions)]
5151
{
@@ -63,6 +63,9 @@ macro_rules! _encode_tlv {
6363
}
6464
$crate::_encode_tlv!($stream, $optional_type, value, option);
6565
} };
66+
($stream: expr, $optional_type: expr, $optional_field: expr, (custom, $fieldty: ty, $read: expr, $write: expr) $(, $self: ident)?) => { {
67+
$crate::_encode_tlv!($stream, $optional_type, $optional_field, (legacy, $fieldty, $read, $write) $(, $self)?);
68+
} };
6669
($stream: expr, $type: expr, $field: expr, optional_vec $(, $self: ident)?) => {
6770
if !$field.is_empty() {
6871
$crate::_encode_tlv!($stream, $type, $field, required_vec);
@@ -229,9 +232,12 @@ macro_rules! _get_varint_length_prefixed_tlv_length {
229232
$len.0 += field_len;
230233
}
231234
};
232-
($len: expr, $optional_type: expr, $optional_field: expr, (legacy, $fieldty: ty, $write: expr) $(, $self: ident)?) => {
235+
($len: expr, $optional_type: expr, $optional_field: expr, (legacy, $fieldty: ty, $read: expr, $write: expr) $(, $self: ident)?) => {
233236
$crate::_get_varint_length_prefixed_tlv_length!($len, $optional_type, $write($($self)?), option);
234237
};
238+
($len: expr, $optional_type: expr, $optional_field: expr, (custom, $fieldty: ty, $read: expr, $write: expr) $(, $self: ident)?) => {
239+
$crate::_get_varint_length_prefixed_tlv_length!($len, $optional_type, $optional_field, (legacy, $fieldty, $read, $write) $(, $self)?);
240+
};
235241
($len: expr, $type: expr, $field: expr, optional_vec $(, $self: ident)?) => {
236242
if !$field.is_empty() {
237243
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required_vec);
@@ -314,9 +320,19 @@ macro_rules! _check_decoded_tlv_order {
314320
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (option, explicit_type: $fieldty: ty)) => {{
315321
// no-op
316322
}};
317-
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (legacy, $fieldty: ty, $write: expr)) => {{
323+
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (legacy, $fieldty: ty, $read: expr, $write: expr)) => {{
318324
// no-op
319325
}};
326+
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (custom, $fieldty: ty, $read: expr, $write: expr) $(, $self: ident)?) => {{
327+
// Note that $type may be 0 making the second comparison always false
328+
#[allow(unused_comparisons)]
329+
let invalid_order =
330+
($last_seen_type.is_none() || $last_seen_type.unwrap() < $type) && $typ.0 > $type;
331+
if invalid_order {
332+
let read_result: Result<_, DecodeError> = $read(None);
333+
$field = read_result?.into();
334+
}
335+
}};
320336
($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (required, explicit_type: $fieldty: ty)) => {{
321337
_check_decoded_tlv_order!($last_seen_type, $typ, $type, $field, required);
322338
}};
@@ -382,8 +398,19 @@ macro_rules! _check_missing_tlv {
382398
($last_seen_type: expr, $type: expr, $field: ident, (option, explicit_type: $fieldty: ty)) => {{
383399
// no-op
384400
}};
385-
($last_seen_type: expr, $type: expr, $field: ident, (legacy, $fieldty: ty, $write: expr)) => {{
386-
// no-op
401+
($last_seen_type: expr, $type: expr, $field: ident, (legacy, $fieldty: ty, $read: expr, $write: expr)) => {{
402+
use $crate::ln::msgs::DecodeError;
403+
let read_result: Result<(), DecodeError> = $read($field.as_ref());
404+
read_result?;
405+
}};
406+
($last_seen_type: expr, $type: expr, $field: ident, (custom, $fieldty: ty, $read: expr, $write: expr)) => {{
407+
// Note that $type may be 0 making the second comparison always false
408+
#[allow(unused_comparisons)]
409+
let missing_req_type = $last_seen_type.is_none() || $last_seen_type.unwrap() < $type;
410+
if missing_req_type {
411+
let read_result: Result<_, DecodeError> = $read(None);
412+
$field = read_result?.into();
413+
}
387414
}};
388415
($last_seen_type: expr, $type: expr, $field: ident, (required, explicit_type: $fieldty: ty)) => {{
389416
_check_missing_tlv!($last_seen_type, $type, $field, required);
@@ -438,9 +465,15 @@ macro_rules! _decode_tlv {
438465
let _field: &Option<$fieldty> = &$field;
439466
$crate::_decode_tlv!($outer_reader, $reader, $field, option);
440467
}};
441-
($outer_reader: expr, $reader: expr, $field: ident, (legacy, $fieldty: ty, $write: expr)) => {{
468+
($outer_reader: expr, $reader: expr, $field: ident, (legacy, $fieldty: ty, $read: expr, $write: expr)) => {{
442469
$crate::_decode_tlv!($outer_reader, $reader, $field, (option, explicit_type: $fieldty));
443470
}};
471+
($outer_reader: expr, $reader: expr, $field: ident, (custom, $fieldty: ty, $read: expr, $write: expr)) => {{
472+
let read_field: $fieldty;
473+
$crate::_decode_tlv!($outer_reader, $reader, read_field, required);
474+
let read_result: Result<_, DecodeError> = $read(Some(read_field));
475+
$field = read_result?.into();
476+
}};
444477
($outer_reader: expr, $reader: expr, $field: ident, (required, explicit_type: $fieldty: ty)) => {{
445478
let _field: &$fieldty = &$field;
446479
_decode_tlv!($outer_reader, $reader, $field, required);
@@ -827,9 +860,12 @@ macro_rules! _init_tlv_based_struct_field {
827860
($field: ident, option) => {
828861
$field
829862
};
830-
($field: ident, (legacy, $fieldty: ty, $write: expr)) => {
863+
($field: ident, (legacy, $fieldty: ty, $read: expr, $write: expr)) => {
831864
$crate::_init_tlv_based_struct_field!($field, option)
832865
};
866+
($field: ident, (custom, $fieldty: ty, $read: expr, $write: expr)) => {
867+
$crate::_init_tlv_based_struct_field!($field, required)
868+
};
833869
($field: ident, (option: $trait: ident $(, $read_arg: expr)?)) => {
834870
$crate::_init_tlv_based_struct_field!($field, option)
835871
};
@@ -893,9 +929,12 @@ macro_rules! _init_tlv_field_var {
893929
($field: ident, (option, explicit_type: $fieldty: ty)) => {
894930
let mut $field: Option<$fieldty> = None;
895931
};
896-
($field: ident, (legacy, $fieldty: ty, $write: expr)) => {
932+
($field: ident, (legacy, $fieldty: ty, $read: expr, $write: expr)) => {
897933
$crate::_init_tlv_field_var!($field, (option, explicit_type: $fieldty));
898934
};
935+
($field: ident, (custom, $fieldty: ty, $read: expr, $write: expr)) => {
936+
$crate::_init_tlv_field_var!($field, required);
937+
};
899938
($field: ident, (required, explicit_type: $fieldty: ty)) => {
900939
let mut $field = $crate::util::ser::RequiredWrapper::<$fieldty>(None);
901940
};
@@ -975,10 +1014,18 @@ macro_rules! _decode_and_build {
9751014
/// [`MaybeReadable`], requiring the TLV to be present.
9761015
/// If `$fieldty` is `optional_vec`, then `$field` is a [`Vec`], which needs to have its individual elements serialized.
9771016
/// Note that for `optional_vec` no bytes are written if the vec is empty
978-
/// If `$fieldty` is `(legacy, $ty, $write)` then, when writing, the function $write will be
1017+
/// If `$fieldty` is `(legacy, $ty, $read, $write)` then, when writing, the function $write will be
9791018
/// called with the object being serialized and a returned `Option` and is written as a TLV if
980-
/// `Some`. When reading, an optional field of type `$ty` is read (which can be used in later
981-
/// `default_value` or `static_value` fields by referring to the value by name).
1019+
/// `Some`. When reading, an optional field of type `$ty` is read, and after all TLV fields are
1020+
/// read, the `$read` closure is called with the `Option<&$ty>` value. The `$read` closure should
1021+
/// return a `Result<(), DecodeError>`. Legacy field values can be used in later
1022+
/// `default_value` or `static_value` fields by referring to the value by name.
1023+
/// If `$fieldty` is `(custom, $ty, $read, $write)` then, when writing, the same behavior as
1024+
/// `legacy`, above is used. When reading, if a TLV is present, it is read as `$ty` and the
1025+
/// `$read` method is called with `Some(decoded_$ty_object)`. If no TLV is present, the field
1026+
/// will be initialized by calling `$read(None)`. `$read` should return a
1027+
/// `Result<field type, DecodeError>` (note that the processed field type may differ from `$ty`;
1028+
/// `$ty` is the type as de/serialized, not necessarily the actual field type).
9821029
///
9831030
/// For example,
9841031
/// ```
@@ -996,7 +1043,7 @@ macro_rules! _decode_and_build {
9961043
/// (1, tlv_default_integer, (default_value, 7)),
9971044
/// (2, tlv_optional_integer, option),
9981045
/// (3, tlv_vec_type_integer, optional_vec),
999-
/// (4, unwritten_type, (legacy, u32, |us: &LightningMessage| Some(us.tlv_integer))),
1046+
/// (4, unwritten_type, (legacy, u32, |_| Ok(()), |us: &LightningMessage| Some(us.tlv_integer))),
10001047
/// (_unused, tlv_upgraded_integer, (static_value, unwritten_type.unwrap_or(0) * 2))
10011048
/// });
10021049
/// ```
@@ -1888,7 +1935,7 @@ mod tests {
18881935
new_field: (u8, u8),
18891936
}
18901937
impl_writeable_tlv_based!(ExpandedField, {
1891-
(0, old_field, (legacy, u8, |us: &ExpandedField| Some(us.new_field.0))),
1938+
(0, old_field, (legacy, u8, |_| Ok(()), |us: &ExpandedField| Some(us.new_field.0))),
18921939
(1, new_field, (default_value, (old_field.ok_or(DecodeError::InvalidValue)?, 0))),
18931940
});
18941941

0 commit comments

Comments
 (0)