Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Macro for composing custom message handlers by jkczyz · Pull Request #1832 · lightningdevkit/rust-lightning · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: stable
test-custom-message: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
Expand All@@ -54,6 +56,8 @@ jobs:
build-no-std: true
build-futures: true
build-tx-sync: true
- toolchain: beta
test-custom-message: true
- toolchain: 1.41.1
build-no-std: false
test-log-variants: true
Expand DownExpand Up@@ -226,6 +230,11 @@ jobs:
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client
RUSTFLAGS="-C link-dead-code" cargo test --verbose --color always --features rpc-client,rest-client,tokio
- name: Test Custom Message Macros on Rust ${{ matrix.toolchain }}
if: "matrix.test-custom-message"
run: |
cd lightning-custom-message
cargo test --verbose --color always
- name: Install deps for kcov
if: matrix.coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,5 +9,5 @@ Cargo.lock
.idea
lightning/target
lightning/ldk-net_graph-*.bin
lightning-custom-message/target
no-std-check/target

1 change: 1 addition & 0 deletions Cargo.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ members = [
]

exclude = [
"lightning-custom-message",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason it's excluded?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to compile using cargo +1.41.1 check gives:

error: failed to parse manifest at `/Users/jkczyz/src/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018`, but `2021` is unknown

But after rebasing, it looks like that's also a problem for one one of lightning-transaction-sync's dependencies, as well. I guess we just can't build the entire workspace with an older rust version now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess CI doesn't try to build the workspace at all then since it's passing without excluding lightning-transaction-sync. We probably shouldn't break the default way to build for users on our MSRV, so we should exclude lightning-transaction-sync as well.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, correction, the above command also doesn't work because lightning-net-tokio requires a higher version. So moving lightning-custom-message back into the members should be fine.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to soon. CI is unhappy because of the edition:

error: failed to parse manifest at `/home/runner/work/rust-lightning/rust-lightning/lightning-custom-message/Cargo.toml`
Caused by:
failed to parse the `edition` key
Caused by:
supported edition values are `2015` or `2018, but `2021` is unknown

"no-std-check",
]

Expand Down
18 changes: 18 additions & 0 deletions lightning-custom-message/Cargo.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
[package]
name = "lightning-custom-message"
version = "0.0.113"
authors = ["Jeffrey Czyz"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
description = """
Utilities for supporting custom peer-to-peer messages in LDK.
"""
edition = "2021"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitcoin = "0.29.0"
lightning = { version = "0.0.113", path = "../lightning" }
310 changes: 310 additions & 0 deletions lightning-custom-message/src/lib.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
//! Utilities for supporting custom peer-to-peer messages in LDK.
//!
//! [BOLT 1] specifies a custom message type range for use with experimental or application-specific
//! messages. While a [`CustomMessageHandler`] can be defined to support more than one message type,
//! defining such a handler requires a significant amount of boilerplate and can be error prone.
//!
//! This crate provides the [`composite_custom_message_handler`] macro for easily composing
//! pre-defined custom message handlers into one handler. The resulting handler can be further
//! composed with other custom message handlers using the same macro.
//!
//! The following example demonstrates defining a `FooBarHandler` to compose separate handlers for
//! `Foo` and `Bar` messages, and further composing it with a handler for `Baz` messages.
//!
//!```
//! # extern crate bitcoin;
//! extern crate lightning;
//! #[macro_use]
//! extern crate lightning_custom_message;
//!
//! # use bitcoin::secp256k1::PublicKey;
//! # use lightning::io;
//! # use lightning::ln::msgs::{DecodeError, LightningError};
//! use lightning::ln::peer_handler::CustomMessageHandler;
//! use lightning::ln::wire::{CustomMessageReader, self};
//! use lightning::util::ser::Writeable;
//! # use lightning::util::ser::Writer;
//!
//! // Assume that `FooHandler` and `BarHandler` are defined in one crate and `BazHandler` is
//! // defined in another crate, handling messages `Foo`, `Bar`, and `Baz`, respectively.
//!
//! #[derive(Debug)]
//! pub struct Foo;
//!
//! macro_rules! foo_type_id {
//! () => { 32768 }
//! }
//!
//! impl wire::Type for Foo {
//! fn type_id(&self) -> u16 { foo_type_id!() }
//! }
//! impl Writeable for Foo {
//! // ...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: tabs not spaces

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I believe all (most?) of our doctests use spaces. IIRC, there was some weirdness around the leading "//! " and the first tab not indenting enough. Especially, if the space after the comment was skipped in lieu of the first tab.

//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct FooHandler;
//!
//! impl CustomMessageReader for FooHandler {
//! // ...
//! # type CustomMessage = Foo;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for FooHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Bar;
//!
//! macro_rules! bar_type_id {
//! () => { 32769 }
//! }
//!
//! impl wire::Type for Bar {
//! fn type_id(&self) -> u16 { bar_type_id!() }
//! }
//! impl Writeable for Bar {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BarHandler;
//!
//! impl CustomMessageReader for BarHandler {
//! // ...
//! # type CustomMessage = Bar;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BarHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! #[derive(Debug)]
//! pub struct Baz;
//!
//! macro_rules! baz_type_id {
//! () => { 32770 }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to demonstrate baz supporting two different message ids internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better left for CustomMessageHandler docs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well with the macro_rules-specific stuff for this crate.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like what is done using the FooBarHandler? I thought you had meant showing a leaf handler handling more than one message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I did mean a leaf handler handling more than one message feeding into one MessageHandler. I guess it doesn't matter, though, as long as the documentation mentions that you can do any pattern (which it does) and maybe mention in the example that they don't have to be in different crate(s).

//! }
//!
//! impl wire::Type for Baz {
//! fn type_id(&self) -> u16 { baz_type_id!() }
//! }
//! impl Writeable for Baz {
//! // ...
//! # fn write<W: Writer>(&self, _: &mut W) -> Result<(), io::Error> {
//! # unimplemented!()
//! # }
//! }
//!
//! pub struct BazHandler;
//!
//! impl CustomMessageReader for BazHandler {
//! // ...
//! # type CustomMessage = Baz;
//! # fn read<R: io::Read>(
//! # &self, _message_type: u16, _buffer: &mut R
//! # ) -> Result<Option<Self::CustomMessage>, DecodeError> {
//! # unimplemented!()
//! # }
//! }
//! impl CustomMessageHandler for BazHandler {
//! // ...
//! # fn handle_custom_message(
//! # &self, _msg: Self::CustomMessage, _sender_node_id: &PublicKey
//! # ) -> Result<(), LightningError> {
//! # unimplemented!()
//! # }
//! # fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> {
//! # unimplemented!()
//! # }
//! }
//!
//! # fn main() {
//! // The first crate may define a handler composing `FooHandler` and `BarHandler` and export the
//! // corresponding message type ids as a macro to use in further composition.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarHandler {
//! foo: FooHandler,
//! bar: BarHandler,
//! }
//!
//! pub enum FooBarMessage {
//! Foo(foo_type_id!()),
//! Bar(bar_type_id!()),
//! }
//! );
//!
//! #[macro_export]
//! macro_rules! foo_bar_type_ids {
//! () => { foo_type_id!() | bar_type_id!() }
//! }
//!
//! // Another crate can then define a handler further composing `FooBarHandler` with `BazHandler`
//! // and similarly export the composition of message type ids as a macro.
//!
//! composite_custom_message_handler!(
//! pub struct FooBarBazHandler {
//! foo_bar: FooBarHandler,
//! baz: BazHandler,
//! }
//!
//! pub enum FooBarBazMessage {
//! FooBar(foo_bar_type_ids!()),
//! Baz(baz_type_id!()),
//! }
//! );
Comment on lines +175 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

@jkczyzjkczyzJan 31, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason someone would opt for using the macro multiple times like this as opposed to just listing each message + handler in one? i.e.

composite_custom_message_handler!(
pub struct FooBarBazHandler {
foo: FooHandler,
bar: BarHandler,
baz: BazHandler,
}
...
}

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Also is there significance to the foo_bar_type_ids being the | of the two type ids or was that mostly arbitrary? Otherwise, I thought the FooBar example was very helpful/straightforward!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

https://doc.rust-lang.org/reference/patterns.html

Someone using the macro could use a range of type ids instead, e.g., 32768..=32769. Though the | approach is preferred for the reason given in the macro's docs.

@alecchendevalecchendevJan 31, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is also possible, though the example is meant to demonstrate arbitrary composition. This may be useful when FooBarHandler is defined in one crate and you want to use it along with BazHandler in your own crate. I've added some comments to the example to clarify this use case.

Ah makes sense!

Yes, the macro matcher $pattern uses a pat specifier, meaning it can be anything rust considers a pattern, including the composition of patterns using |.

Ohh I see I didn't realize it was being used as a pattern I thought it was doing a bitwise OR of the two type ids, that's cool.

//!
//! #[macro_export]
//! macro_rules! foo_bar_baz_type_ids {
//! () => { foo_bar_type_ids!() | baz_type_id!() }
//! }
//! # }
//!```
//!
//! [BOLT 1]: https://github.com/lightning/bolts/blob/master/01-messaging.md
//! [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler

#![doc(test(no_crate_inject, attr(deny(warnings))))]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a no_crate_inject and why are we denying compilation warnings?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no_crate_inject is needed otherwise we can't add #[macro_use] on the crate in the doc test.

For warnings, I was testing out unreachable patterns in the doc test, so needed that for it fail. I can remove it now, I suppose.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I just generally worry about deny(warnings) cause that means CI will start failing on a new rustc without us touching anything. We'll fix warnings when we find them.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, this only affects doc tests.


pub extern crate bitcoin;
pub extern crate lightning;

/// Defines a composite type implementing [`CustomMessageHandler`] (and therefore also implementing
/// [`CustomMessageReader`]), along with a corresponding enumerated custom message [`Type`], from
/// one or more previously defined custom message handlers.
///
/// Useful for parameterizing [`PeerManager`] with custom message handling for one or more sets of
/// custom messages. Message type ids may be given as a valid `match` pattern, including ranges,
/// though using OR-ed literal patterns is preferred in order to catch unreachable code for
/// conflicting handlers.
///
/// See [crate documentation] for example usage.
///
/// [`CustomMessageHandler`]: crate::lightning::ln::peer_handler::CustomMessageHandler
/// [`CustomMessageReader`]: crate::lightning::ln::wire::CustomMessageReader
/// [`Type`]: crate::lightning::ln::wire::Type
/// [`PeerManager`]: crate::lightning::ln::peer_handler::PeerManager
/// [crate documentation]: self
#[macro_export]
macro_rules! composite_custom_message_handler {
(
$handler_visibility:vis struct $handler:ident {
$($field_visibility:vis $field:ident: $type:ty),* $(,)*
}

$message_visibility:vis enum $message:ident {
$($variant:ident($pattern:pat)),* $(,)*
}
) => {
#[allow(missing_docs)]
$handler_visibility struct $handler {
$(
$field_visibility $field: $type,
)*
}

#[allow(missing_docs)]
#[derive(Debug)]
$message_visibility enum $message {
$(
$variant(<$type as $crate::lightning::ln::wire::CustomMessageReader>::CustomMessage),
)*
}

impl $crate::lightning::ln::peer_handler::CustomMessageHandler for $handler {
fn handle_custom_message(
&self, msg: Self::CustomMessage, sender_node_id: &$crate::bitcoin::secp256k1::PublicKey
) -> Result<(), $crate::lightning::ln::msgs::LightningError> {
match msg {
$(
$message::$variant(message) => {
$crate::lightning::ln::peer_handler::CustomMessageHandler::handle_custom_message(
&self.$field, message, sender_node_id
)
},
)*
}
}

fn get_and_clear_pending_msg(&self) -> Vec<($crate::bitcoin::secp256k1::PublicKey, Self::CustomMessage)> {
vec![].into_iter()
$(
.chain(
self.$field
.get_and_clear_pending_msg()
.into_iter()
.map(|(pubkey, message)| (pubkey, $message::$variant(message)))
)
)*
.collect()
}
}

impl $crate::lightning::ln::wire::CustomMessageReader for $handler {
type CustomMessage = $message;
fn read<R: $crate::lightning::io::Read>(
&self, message_type: u16, buffer: &mut R
) -> Result<Option<Self::CustomMessage>, $crate::lightning::ln::msgs::DecodeError> {
match message_type {
$(
$pattern => match <$type>::read(&self.$field, message_type, buffer)? {
None => unreachable!(),
Some(message) => Ok(Some($message::$variant(message))),
},
)*
_ => Ok(None),
}
}
}

impl $crate::lightning::ln::wire::Type for $message {
fn type_id(&self) -> u16 {
match self {
$(
Self::$variant(message) => message.type_id(),
)*
}
}
}

impl $crate::lightning::util::ser::Writeable for $message {
fn write<W: $crate::lightning::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::lightning::io::Error> {
match self {
$(
Self::$variant(message) => message.write(writer),
)*
}
}
}
}
}
Loading