Skip to content

Commit 31bb3de

Browse files
authored
Merge pull request lightningdevkit#4709 from jkczyz/2026-06-composite-handler-ok-none
Don't panic when a composite sub-handler returns `Ok(None)`
2 parents 8a0464c + 77ac339 commit 31bb3de

1 file changed

Lines changed: 71 additions & 1 deletion

File tree

  • lightning-custom-message/src

lightning-custom-message/src/lib.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,12 @@ macro_rules! composite_custom_message_handler {
358358
match message_type {
359359
$(
360360
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
361-
None => unreachable!(),
361+
// A sub-handler returns `None` for a `message_type` it doesn't
362+
// recognize. The composite's pattern can be broader than the types
363+
// the sub-handler decodes (e.g. a range), and `message_type` is
364+
// peer-provided, so report the message as unknown rather than
365+
// treating this as unreachable and panicking.
366+
None => Ok(None),
362367
Some(message) => Ok(Some($message::$variant(message))),
363368
},
364369
)*
@@ -501,6 +506,71 @@ mod tests {
501506
}
502507
);
503508

509+
struct ReservedBlockHandler;
510+
impl CustomMessageReader for ReservedBlockHandler {
511+
type CustomMessage = Foo;
512+
fn read<R: LengthLimitedRead>(
513+
&self, message_type: u16, _b: &mut R,
514+
) -> Result<Option<Foo>, DecodeError> {
515+
// This build defines only the message at 32768; the rest of the block its
516+
// protocol reserved (32768..=32777) is for types future versions may add.
517+
// A not-yet-defined type is unknown to this build, so per the
518+
// `CustomMessageReader` contract it returns `Ok(None)` -- a newer peer can
519+
// send one and this older node will treat it as an unknown message.
520+
match message_type {
521+
32768 => Ok(Some(Foo)),
522+
_ => Ok(None),
523+
}
524+
}
525+
}
526+
impl CustomMessageHandler for ReservedBlockHandler {
527+
fn handle_custom_message(&self, _msg: Foo, _: PublicKey) -> Result<(), LightningError> {
528+
Ok(())
529+
}
530+
fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Foo)> {
531+
vec![]
532+
}
533+
fn peer_disconnected(&self, _: PublicKey) {}
534+
fn peer_connected(&self, _: PublicKey, _: &Init, _: bool) -> Result<(), ()> {
535+
Ok(())
536+
}
537+
fn provided_node_features(&self) -> NodeFeatures {
538+
NodeFeatures::empty()
539+
}
540+
fn provided_init_features(&self, _: PublicKey) -> InitFeatures {
541+
InitFeatures::empty()
542+
}
543+
}
544+
545+
composite_custom_message_handler!(
546+
struct ReservedBlockComposite {
547+
proto: ReservedBlockHandler,
548+
}
549+
550+
enum ReservedBlockMessage {
551+
Proto(32768..=32777),
552+
}
553+
);
554+
555+
#[test]
556+
fn read_treats_a_reserved_in_range_type_as_unknown() {
557+
// A sub-handler may own a block of type ids (declared here as a range) yet only
558+
// decode the subset its build defines, returning `Ok(None)` for reserved or
559+
// not-yet-defined types in the block -- exactly what a node does on receiving a
560+
// newer peer's message. `read` must surface that as an unknown message, not
561+
// panic.
562+
let composite = ReservedBlockComposite { proto: ReservedBlockHandler };
563+
let mut buffer: &[u8] = &[];
564+
// The message this build defines decodes to its variant.
565+
assert!(matches!(
566+
composite.read(32768, &mut buffer),
567+
Ok(Some(ReservedBlockMessage::Proto(_)))
568+
));
569+
// A reserved type from the same block is reported unknown, not panicked
570+
// (pre-fix the matched arm hit `unreachable!()`).
571+
assert!(matches!(composite.read(32770, &mut buffer), Ok(None)));
572+
}
573+
504574
#[test]
505575
fn peer_connected_failure_does_not_leak_subhandler_state() {
506576
let composite = CompositeHandler {

0 commit comments

Comments
 (0)