-
Notifications
You must be signed in to change notification settings - Fork 367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide Common Base For Custom Message Combinators #1813
Comments
In particular, there is no way, given an object implementing It would be undesirable to have the client code provide this information: if the protocol updates to a new version that has more message types, then the client code can go out of sync with the protocol code. It should be provided by the It is also undesirable to just claim a single message ID range. Consider this situation: protocol A takes the range M..N. It stabilizes for a while. Then protocol B takes the range L..M-1, and later protocol C takes the range N+1..O. Then protocol A wants to upgrade, and needs an additional message type ID; it wants to be back-compatible with older protocol A versions, so messages in the range M..N should still be understood, but it cannot extend the range in either direction. Protocol A then has to define new message types that are not contiguous with the original range. Thus it would be impossible to define a single message ID range. Finally, it is undesirable to force protocol-writers to separately define which messages IDs they use, from the code that parses the message ID. i.e. we do not want separate functions |
So my concrete proposal is this: trait CustomMessageReader {
type CustomMessage: Type;
fn read<R: Read>(&self, message_type: u16) -> Option<Box<dyn FnOnce(R) -> Result<CustomMessage, DecodeError>>>;
}
impl CustomMessageReader for (L, R)
where L: CustomMessageReader, R: CustomMessageReader {
type CustomMessage = Either<L::CustomMessage, R::CustomMessage>;
fn read<R: Read>(&self, message_type: u16) {
let (l, r) = self;
if let Some(lfn) = l.read(message_type) {
Some(Box::new(move |buffer| {
Ok(Left(lfn(buffer)?))
}))
} let Some(rfn) = r.read(message_type) {
Some(Box::new(move |buffer| {
Ok(Right(rfn(buffer)?))
}))
} else {
None
}
}
}
// etc... for other tuple types Then protocol writers would write: impl CustomMessageReader for MyProtocol {
type CustomMessage = MyProtocolMessage;
fn read<R: Read>(&self, message_type: u16) {
match message_type {
MESSAGE_TYPE_FOO => Some(Box::new(|buffer| {
let payload = parse_foo(buffer)?;
Ok(MyProtocolMessage::Foo(payload))
}))
// ... etc.
_ => None
}
}
} and end-clients would write: let custom_message_handler = (
MyProtocol::new(/*whatever*/),
YourProtocol::new(/*whatever*/)
); |
Maybe this can be leveraged instead? https://gist.github.com/kvark/f067ba974446f7c5ce5bd544fe370186#avoid-boxtrait |
Drafted a simple macro approach in #1832 for consideration. User needs to define the message ranges for each inlcuded handler, though. Could add min and max constants to one of the traits, but then the composite would need to define these, too. Not sure how to do that and what that values should be, especially considering arbitrary nesting. Open to feedback on this approach. |
Yeah user needs to define the messages, and that should really be provided by the custom message handler, especially if the custom message handler is upgraded to take a new message ID. Can it handle noncontiguous message ID ranges? e.g. If the point is to avoid pub struct PossibleCustomMessage<H: ComposableCustomMessageHandler, R: Read> {
pub message_id: u16,
// ...rest elided ...
}
impl PossibleCustomMessage<H: ComposableCustomMessageHandler, R: Read> {
/* take self by ownership transfer, so accept is only callable once */
pub fn accept<F: FnOnce(&mut R) -> Result<H::CustomMessage, DecodeError>>(self, function: F) -> () {
// dunno how to implement this
}
}
trait ComposableCustomMessageHandler {
type CustomMessage: Type;
fn read<R:Read>(&self, message: PossibleCustomMessage<Self, R>) -> ();
} Then on the protocl writer end: struct MyProtocol { /* ... */ }
impl ComposableCustomMessageHandler for MyProtocol {
type CustomMessage = MyProtocolMessage;
fn read<R:Read>(&self, message: PossibleCustomMessage<Self, R>) -> () {
match message.message_id {
MESSAGE_TYPE_FOO => message.accept(|buffer| {
let payload = parse_foo(buffer);
Ok(MyProtocolMessage::Foo(payload))
})
_ => ()
}
}
} For the Drawback is that we cannot statically check message ids that are recognized. Thing is, we really need to express a set of messages, not a range. Then we need to do the static check of overlapping sets. I doubt current Rust has this at the compiler level. The nearest is |
I'm not quite sure what problem the code snippet is trying to solve? The existing message handler API seems to accomplish the same thing as the above snippets? If our goal is to just add a wrapper without statically checking the messages that can be done with very minimal overhead on top of the current trait, and without a macro.
Kinda, if its a macro call you can pass a
|
The code snippet does not work, as it turns out, because it cannot be used to actually compose. As you noted it is just the same as the current trait anyway, which is not composable.
mycrate::use_our_messenger! IntermediateMessenger {
}
yourcrate::use_our_messenger! FinalMessenger {
(IntermediateMessenger, /* what goes here??? */ )
} |
Perhaps the simplest solution is just to add a Drawback is absolutely no compile-time checks. Maybe fine since the expected structure of any |
Is something like the below possible with Rust macros? So for example in lightning::define_composable_tag! pub tag {
(MyProtocolHandler, MY_PROTOCOL_FOO | MY_PROTOCOL_BAR),
} then in lightning::define_composable_tag! pub tag {
(YourProtocolHandler, YOUR_PROTOCOL_FOO | YOUR_PROTOCOL_BAR )
} then in the user wallet: lightning::compose_handler! pub(crate) CombinedProtocolHandler {
mycrate::tag, yourcrate::tag
} Secretly, the above desugars to: mycrate::tag! CombinedProtocolHandler {
{ // macros that still need to be expanded.
yourcrate::tag
}
{ // types and patterns already expanded.
}
} which desugars to: yourcrate::tag! CombinedProtocolHandler {
{ // macros that still need to be expanded.
}
{ // types and patterns already expanded.
(MyProtocolHandler, MY_PROTOCOL_FOO | MY_PROTOCOL_BAR),
}
} which finally desugars to: lightning::composite_custom_message_handler! CombinedProtocolHandler {
(MyProtocolHandler, MY_PROTOCOL_FOO | MY_PROTOCOL_BAR),
(YourProtocolHandler, YOUR_PROTOCOL_FOO | YOUR_PROTOCOL_BAR)
} That way the patterns are provided by the individual crates, and the crate-exported macros are composable. |
I'm not 100% sure on the rules, but most attempts with nested macro calls haven't worked with me. However, I did manage to get this to work with a single pattern in #1832. --- a/lightning/src/ln/peer_handler.rs
+++ b/lightning/src/ln/peer_handler.rs
@@ -155,6 +155,10 @@ macro_rules! composite_custom_message_handler {
}
}
+macro_rules! some_pattern {
+ () => { 1..=10 }
+}
+
composite_custom_message_handler!(
pub struct CompositeMessageHandler {
ignoring: IgnoringMessageHandler,
@@ -163,7 +167,7 @@ composite_custom_message_handler!(
pub enum CompositeMessage {
Infallible(0 | 2 | 4),
- Infallible2(1..=10 | 99),
+ Infallible2(some_pattern!()),
}
);
But it fails with multiple patterns. --- a/lightning/src/ln/peer_handler.rs
+++ b/lightning/src/ln/peer_handler.rs
@@ -155,6 +155,10 @@ macro_rules! composite_custom_message_handler {
}
}
+macro_rules! some_pattern {
+ () => { 1..=10 | 99 }
+}
+
composite_custom_message_handler!(
pub struct CompositeMessageHandler {
ignoring: IgnoringMessageHandler,
@@ -163,7 +167,7 @@ composite_custom_message_handler!(
pub enum CompositeMessage {
Infallible(0 | 2 | 4),
- Infallible2(1..=10 | 99),
+ Infallible2(some_pattern!()),
}
);
Looks to be resolved in edition 2021: https://doc.rust-lang.org/edition-guide/rust-2021/or-patterns-macro-rules.html So we could possibly publish a separate crate with the composite macro, in that case. |
What I proposed would be a "typical, everyday" pattern for Scheme macros, and Rust macros look awfully suspiciously like Scheme macros re-applied to a "true" AST rather than s-expressions, but really it does depend on how Rust macros work. I guess having a macro like |
Yes, I'm not sure I understand why that is an issue? Its pretty clean, if we can do it. One thing we could do to get there is having the |
Tried a workaround for edition 2018: diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs
index 08ea5535..1a926d9c 100644
--- a/lightning/src/ln/peer_handler.rs
+++ b/lightning/src/ln/peer_handler.rs
@@ -156,9 +156,28 @@ macro_rules! composite_custom_message_handler {
}
macro_rules! some_pattern {
+ () => { zero_pattern!() | two_pattern!() | four_pattern!() }
+}
+
+macro_rules! zero_pattern {
+ () => { 0 }
+}
+
+macro_rules! two_pattern {
+ () => { 2 }
+}
+
+macro_rules! four_pattern {
+ () => { 4 }
+}
+
+macro_rules! some_pattern1 {
() => { 1..=10 }
}
+macro_rules! some_pattern2 {
+ () => { 11..=20 }
+}
composite_custom_message_handler!(
pub struct CompositeMessageHandler {
ignoring: IgnoringMessageHandler,
@@ -166,8 +185,8 @@ composite_custom_message_handler!(
}
pub enum CompositeMessage {
- Infallible(0 | 2 | 4),
- Infallible2(some_pattern!()),
+ Infallible(some_pattern!()),
+ Infallible2(some_pattern1!() | some_pattern2!()),
}
); This only works for
@TheBlueMatt Was that what you had in mind? |
Oddly, it doesn't work there but is fine if I replace |
Here's the nested version exhibiting the same problem: diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs
index 08ea5535..70feacb0 100644
--- a/lightning/src/ln/peer_handler.rs
+++ b/lightning/src/ln/peer_handler.rs
@@ -155,10 +155,30 @@ macro_rules! composite_custom_message_handler {
}
}
-macro_rules! some_pattern {
+macro_rules! zero_pattern {
+ () => { 0 }
+}
+
+macro_rules! two_pattern {
+ () => { 2 }
+}
+
+macro_rules! four_pattern {
+ () => { 4 }
+}
+
+macro_rules! some_pattern1 {
() => { 1..=10 }
}
+macro_rules! some_pattern2 {
+ () => { 11..=20 }
+}
+
+macro_rules! all_pattern {
+ () => { zero_pattern!() | two_pattern!() | four_pattern!() | some_pattern1!() | some_pattern2!() }
+}
+
composite_custom_message_handler!(
pub struct CompositeMessageHandler {
ignoring: IgnoringMessageHandler,
@@ -166,8 +186,18 @@ composite_custom_message_handler!(
}
pub enum CompositeMessage {
- Infallible(0 | 2 | 4),
- Infallible2(some_pattern!()),
+ Infallible(zero_pattern!() | two_pattern!() | four_pattern!()),
+ Infallible2(some_pattern1!() | some_pattern2!()),
+ }
+);
+
+composite_custom_message_handler!(
+ pub struct OuterCompositeMessageHandler {
+ inner: CompositeMessageHandler,
+ }
+
+ pub enum OuterCompositeMessage {
+ Inner(all_pattern!()),
}
); |
Slipping to 114, sadly it doesn't look like there's any good way to actually do this with current Rust. |
I took some time over the holidays to update and clean up #1832. The macro is defined in a separate crate using Rust edition 2021. This allows nesting without needing to explicitly repeat the type ids from the reused handler. This is important as otherwise updating to a new version of the reused handler may not work properly if the updated version covered more type ids. @ZmnSCPxj-jr could you see if this covers your concerns. The docs in the PR has an example, but essentially someone may use the macro to publish their handler along with a macro giving the type pattern. If each message has its own handler, then it should be DRY. Then anyone can use these (i.e., the published handler and type id macro) to compose it with their own custom handler in an arbitrarily nested manner. |
The changes there basically LGTM. Would like to see if @ZmnSCPxj-jr agrees and then we can ship it 🎉 |
We've had some users ask about publishing standalone crates that use the LDK Custom Message API to expose some functionality that users can adopt. Sadly, our API isn't super conducive to this - we don't have "combine two custom message handlers into one" API, so the end-user wishing to use the functionality and their own custom messages would have to build a combinator themselves, which is doable but annoying.
It should be trivial to expose a simple custom message combinator (as long as it doesnt worry about duplicate message IDs), so we should probably Just Do It (tm).
The text was updated successfully, but these errors were encountered: