-
Notifications
You must be signed in to change notification settings - Fork 436
BOLT 12 Offers message handling support #2294
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
BOLT 12 Offers message handling support #2294
Conversation
da21b8d to
64007e7
Compare
Codecov ReportPatch coverage:
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. Additional details and impacted files@@ Coverage Diff @@
## main #2294 +/- ##
==========================================
+ Coverage 90.48% 91.56% +1.07%
==========================================
Files 104 106 +2
Lines 53920 66738 +12818
Branches 53920 66738 +12818
==========================================
+ Hits 48792 61106 +12314
- Misses 5128 5632 +504
☔ View full report in Codecov by Sentry. |
dunxen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked the TLV types against spec and have done a brief review while just brushing up some of the spec again.
Just a nit so far. Will do more review tomorrow.
carlaKC
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New here - hopefully some of what I've said makes sense.
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| pub trait MessageRouter { | ||
| /// Returns a route for sending an [`OnionMessage`] to the given [`Destination`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it clearer here that find_route should return only intermediate_hops to the destination?
I would expect a find_route call to also include the pubkey of the final node (or intro node), but when it's used in the next commit with send_custom_message it's expected to only return the intermediate hops.
Also, empty vector = no path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it clearer here that
find_routeshould return only intermediate_hops to the destination?
Add clarification to the docs.
I would expect a
find_routecall to also include the pubkey of the final node (or intro node), but when it's used in the next commit withsend_custom_messageit's expected to only return the intermediate hops.
Perhaps there's a better name than find_route or at very least a better name for what it is returning. Open to suggestions.
Also, empty vector = no path?
This would be for if there is a direct connection between sender and recipient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_hops?
Sounds like a task for a brewery though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be get_intermediate_hops since that's the wording used in send_custom_message? But no strong feelings if the docs are updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option could be to pass in a new onion_message::Path (or similar name) into send_onion_message, consolidating the intermediate_nodes and destination parameters. Then find_path could return a Path. This'd be in-line with our payment apis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt Would you be ok with that or will this make bindings difficult?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline and at the review club. Renamed to find_path taking a Destination by value and returning a new OnionMessagePath struct. Also, updated OnionMessenger::send_custom_message to take this struct instead of the two parameters as suggested earlier.
Regarding naming, not sure if I should rename MessageRouter to OnionMessageRouter for consistency with the new struct and OnionMessageContents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt Would you be ok with that or will this make bindings difficult?
Not sure why this would make the bindings difficult?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concern was around having two items in different modules named Path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yea, we can't do that for bindings, and really shouldnt do it for rust users either.
64007e7 to
238b72e
Compare
jkczyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review!
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| pub trait MessageRouter { | ||
| /// Returns a route for sending an [`OnionMessage`] to the given [`Destination`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it clearer here that
find_routeshould return only intermediate_hops to the destination?
Add clarification to the docs.
I would expect a
find_routecall to also include the pubkey of the final node (or intro node), but when it's used in the next commit withsend_custom_messageit's expected to only return the intermediate hops.
Perhaps there's a better name than find_route or at very least a better name for what it is returning. Open to suggestions.
Also, empty vector = no path?
This would be for if there is a direct connection between sender and recipient.
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| pub trait MessageRouter { | ||
| /// Returns a route for sending an [`OnionMessage`] to the given [`Destination`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find_hops?
Sounds like a task for a brewery though.
valentinewallace
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like mostly trivial changes? Hope we can land this soon!
|
|
||
| let erroneous_field = match (erroneous_field, suggested_value) { | ||
| (None, None) => None, | ||
| (None, Some(_)) => None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, though the spec is underspecified. I left a comment: https://github.com/lightning/bolts/pull/798/files#r1200840518
I wonder if we should just ignore erroneous_field and suggested_value since they are odd and not very useful at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I added an additional error here. Discussion from the spec meeting indicated the error fields are meant to be generic enough for future uses. So might as well support reading them at least.
| match Self::parse(tlv_type, bytes) { | ||
| Ok(message) => Ok(message), | ||
| Err(ParseError::Decode(e)) => Err(e), | ||
| Err(_) => Err(DecodeError::InvalidValue), // TODO: log ParseError? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be in favor of logging 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... seems I'd have to update all the Readable / ReadableArgs impls back to wire::Message to take a logger to use here. Perhaps it would be better to make a new DecodeError variant wrapping the ParseError to avoid that? That was we could log in PeerManager. Let me know if you have a preference or another idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went ahead and made a couple new DecodeError variants. PTAL
| } | ||
| } | ||
|
|
||
| impl ResponseErrorHandler for TestCustomMessageHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this additional trait, instead of returning a Result from handle_custom_message? We could also add an event for failed OM handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error happens after calling handle_custom_message when OnionMessenger attempts to send the reply. This avoid reentrancy when if instead the user was given a reference to OnionMessenger to directly send the reply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be reasonable to implement EventsProvider for Messenger and generate OM failure events there? Seems in line with what we do for outbound payment path failures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly... @TheBlueMatt is there any concern here? I'd imagine we may want to avoid onion messages from an untrusted source filling our event queue at no cost. At very least the cost for an outbound payment is to tie up liquidity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. For now we'll leave the trait but may want to have the ChannelManager implementation produce an event in the future.
alecchendev
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the stuff I was thinking to comment on while reviewing was already addressed by others so LGTM 👍
| OnionMessageContents::Offers(_) => self.respond_with_onion_message( | ||
| response, path_id, reply_path, &*self.offers_handler | ||
| ), | ||
| OnionMessageContents::Custom(_) => self.respond_with_onion_message( | ||
| response, path_id, reply_path, &*self.custom_handler | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can someone explain the &* here? I've been running into this more lately and haven't quite understood it. Also I notice if I remove the Sized restriction on OMH::Target: OffersMessageHandler + Sized--which is self.offers_handler's type--this gets a warning and I also don't really understand why. I get basic de/referencing and that implementing the Sized trait means having a known constant size at compile time but don't understand it's use/need in this context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can someone explain the
&*here? I've been running into this more lately and haven't quite understood it.
self.custom_handler is a type implementing Deref where the Target implements CustomOnionMessageHandler. respond_with_onion_message wants a reference to a type implementing ResponseErrorHandler, which is a supertrait. It seems just calling deref() would have sufficed.
Also I notice if I remove the
Sizedrestriction onOMH::Target: OffersMessageHandler + Sized--which isself.offers_handler's type--this gets a warning and I also don't really understand why. I get basic de/referencing and that implementing theSizedtrait means having a known constant size at compile time but don't understand it's use/need in this context.
IIUC, since Deref::Target is ?Sized (i.e., optionally sized) and respond_with_onion_message takes a reference which is Sized, we need to restrict its uses to Sized types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.custom_handleris a type implementingDerefwhere theTargetimplementsCustomOnionMessageHandler.respond_with_onion_messagewants a reference to a type implementingResponseErrorHandler, which is a supertrait. It seems just callingderef()would have sufficed.
Oh I see, okay. Reading up a bit more on Deref made me realize there was more happening than I realized. Although, I would've thought Rust would be able to do the type coercion for the supertrait here, hmph.
IIUC, since
Deref::Targetis?Sized(i.e., optionally sized) andrespond_with_onion_messagetakes a reference which isSized, we need to restrict its uses toSizedtypes.
I tried to add ?Sized on the EH type in respond_with_onion_message and that worked too lol, so I guess for some reason Rust needs us to tell it if it's going to be optionally sized or sized, but I still don't really get why... It doesn't make a difference to the actual code here, so whatever, but weird.
238b72e to
5db5cc2
Compare
jkczyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, forgot to send these comments.
fuzz/src/onion_message.rs
Outdated
| let log_entries = logger.lines.lock().unwrap(); | ||
| assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), | ||
| "Received an onion message with path_id None and no reply_path".to_string())), Some(&1)); | ||
| // TODO: Add reply path to one_hop_om instead? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@valentinewallace Does this sound right? How was that input generated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. I just wrote a test with hardcoded keys + printed out the encoded onion message, I think. Here's the branch I used if it helps: https://github.com/valentinewallace/rust-lightning/tree/2022-10-GEN-OM-FUZZ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to get something that manages to reply, indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing a test failure when cherrypicking this code. I pinged @valentinewallace offline but may need to wait for timezones to catch up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked into this. For the test where we're sending directly to a blinded path, we spuriously advance the blinded path on send because in fuzzing all the node pks are the same (so we think we're the intro node to the blinded path).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for helping track this down! I've updated the fuzzer such that each node uses a different public key and was able to generate updated onion messages. Added a separate commit for this.
Also, I updated the fuzzer to test the code path for responding given a reply path. The least intrusive way of doing this was to have the unblinded one-hop case include a one-hop blinded path back. This required modifying BlindedPath::new_for_message to allow such paths. This branch contains the needed modifications for updating the generated onion messages: https://github.com/jkczyz/rust-lightning/tree/2023-02-offer-flow-fuzz
| match Self::parse(tlv_type, bytes) { | ||
| Ok(message) => Ok(message), | ||
| Err(ParseError::Decode(e)) => Err(e), | ||
| Err(_) => Err(DecodeError::InvalidValue), // TODO: log ParseError? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went ahead and made a couple new DecodeError variants. PTAL
|
|
||
| let erroneous_field = match (erroneous_field, suggested_value) { | ||
| (None, None) => None, | ||
| (None, Some(_)) => None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I added an additional error here. Discussion from the spec meeting indicated the error fields are meant to be generic enough for future uses. So might as well support reading them at least.
5db5cc2 to
f22cea5
Compare
lightning/src/ln/msgs.rs
Outdated
| /// The message included zlib-compressed values, which we don't support. | ||
| UnsupportedCompression, | ||
| /// A BOLT 12 Offers message had invalid semantics. | ||
| InvalidSemantics(offers::parse::SemanticError), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this what InvalidValue is for? Is there a reason we need to have an offers specific error variant in the super general purpose decode failure enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for logging purposes. See discussion at #2294 (comment).
I could instead make a more general variant wrapping a string or something like Box<dyn Error> to avoid constructing the string if the log level is not hit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could instead make a more general variant wrapping a string or something like Box to avoid constructing the string if the log level is not hit.
Sure, we could do something like that. I don't think our serialization framework should have offers-specific code/variants in it, but a String (or &'static str) in an error path seems okay. We also might consider including the (optional) specific TLV that failed to parse as Rusty wants to get that back in errors too now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also might consider including the (optional) specific TLV that failed to parse as Rusty wants to get that back in errors too now.
Hmm... is that something outside of InvoiceError? There the TLV in question is not necessarily about a parse failure (i.e., DecodeError) but rather some semantic error, FWIW.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, its not specific to the BOLT12 stuff, we may want it generally, but the invoice_error message has a specific TLV that generated the error - presumably that should be filled in whether the TLV was invalid (eg too many bytes/invalid value) or if it was a semantic error. For that to work it has to be generic in DecodeError and parsing BOLT12 messages have to map InvoiceError to DecodeError.
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| fn find_path( | ||
| &self, sender: &PublicKey, destination: Destination |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need some kind of currently-connected-peers set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... perhaps? Currently, this is called from OnionMessenger::respond_with_onion_message via OnionMessenger::handle_onion_message which is passed the id of the peer who delivered the message. So maybe that id is sufficient? Otherwise, I suppose we'd have to pass all the ids to handle_onion_message from PeerManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we should - better to find a path with the live peer set rather than just guessing based on the public channels we have (which won't work for private nodes anyway?). I don't think we need to pass a "live" set from PeerManager, just maintaining a copy via peer_{dis,}connected in the messenger should suffice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed that we are already maintaining this as the keys in OnionMessenger::pending_messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated find_path to take the peers.
| L::Target: Logger, | ||
| { | ||
| fn handle_message(&self, _message: OffersMessage) -> Option<OffersMessage> { | ||
| todo!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can land this with a panic in it? As it stands you can hook this up and just hit panics left and right. We should instead leave IgnoringMessageHandler as the only implementer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just removed the todo! as it was much simpler than changing SimpleArcOnionMessenger.
| } | ||
| } | ||
|
|
||
| impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use AChannelManager here rather than writing it out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't aware of that, but it looks like it is meant to be test-only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can just make it public. We did the same for APeerManager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved everything into channelmanager.rs given all the utilities are there in the next PR. Also, prevents needing to make a bunch of fields pub(crate) then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that AChannelManager was made public yesterday in a separate PR, so its now already usable.
| /// | ||
| /// Each message handler may choose to reply to an onion message with a response. This handler is | ||
| /// used when an error occurs when responding. | ||
| pub trait ResponseErrorHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is this API not maybe simpler by simply having the OfferMessageHandler take the router itself and it can figure it out from there? Returning a message to send, then getting an immediate callback that it failed seems like a confusing multi-step process that may require storing additional state, vs just getting that feedback from a router and knowing you failed doesn't.
Further, in the future we may want to use onion message routing as a "free boolean probe" - when sending an invoice_request we may use the normal router seeking a route for the amount we want to send and if the request fails we know we shouldn't pay over that route. That kinda implies the routing has to happen in the message handler itself rather than in the messenger/caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is this API not maybe simpler by simply having the OfferMessageHandler take the router itself and it can figure it out from there? Returning a message to send, then getting an immediate callback that it failed seems like a confusing multi-step process that may require storing additional state, vs just getting that feedback from a router and knowing you failed doesn't.
Hmmm... that pushes the onus on the user to not only do the routing but also to send the message. So we'd have to pass the OnionMessenger, too. Or I suppose we could just pass the OnionMessenger so that the handler can call respond_with_onion_message, which would do the routing using the messenger's MessageRouter. We'd have to also pipe the peer(s) through, too.
Either way, it will expose all OnionMessenger's type parameters to the handler traits. 🙁
FYI, another reason I did it this way is so logging was within OnionMessenger.
Further, in the future we may want to use onion message routing as a "free boolean probe" - when sending an invoice_request we may use the normal router seeking a route for the amount we want to send and if the request fails we know we shouldn't pay over that route. That kinda implies the routing has to happen in the message handler itself rather than in the messenger/caller.
Not quite sure I follow how that would even work. Wouldn't the user parameterize with some custom MessageRouter if they want to do such a probe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So handle_custom_message returns Option<Self::CustomMessage, OnionMessagePath> and then the messenger is just responsible for send_onion_message? Agree that would simplify things - eg if you can't find a route to the intro node, your handler can just give up.
IIUC, I think some form of ResponseErrorHandler would still be needed to notify if send_onion_message fails so that your handler knows what to do next?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... that pushes the onus on the user to not only do the routing but also to send the message. So we'd have to pass the OnionMessenger, too. Or I suppose we could just pass the OnionMessenger so that the handler can call respond_with_onion_message, which would do the routing using the messenger's MessageRouter. We'd have to also pipe the peer(s) through, too.
We could still return the message to be sent, just a routed one rather than the contents only. You'd have to pass it a router but not the messenger itself.
FYI, another reason I did it this way is so logging was within OnionMessenger.
I'm not convinced this should be a goal - it costs nothing to add a logger to the downstream implementation (eg the ChannelManager will already have one), and if it leads to avoiding a lot of strange API it seems worth it?
Not quite sure I follow how that would even work. Wouldn't the user parameterize with some custom MessageRouter if they want to do such a probe?
Oh, I take that, back, this is unrelated. I was thinking about the case where a ChannelManager does the initial invoice_request sending - it would like to be able to pick the path based on its own existing Router, but it can via a manual-path message send.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More broadly, the current PR doesn't actually implement handle_response_error anywhere, and its not super clear to me what we can do with the response error but log anyway? Like, if we receive an invoice_request and fail to send the invoice back, it may be worth logging, but we can't actually do anything - the payment may still complete with a second invoice_request received.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, another reason I did it this way is so logging was within OnionMessenger.
I'm not convinced this should be a goal - it costs nothing to add a logger to the downstream implementation (eg the ChannelManager will already have one), and if it leads to avoiding a lot of strange API it seems worth it?
The problems isn't with having a logger, but rather with implementations needing to write the code that logs in five places in respond_with_onion_message across however many handler traits there happen to be going forward.
More broadly, the current PR doesn't actually implement
handle_response_erroranywhere, and its not super clear to me what we can do with the response error but log anyway? Like, if we receive an invoice_request and fail to send the invoice back, it may be worth logging, but we can't actually do anything - the payment may still complete with a second invoice_request received.
Yeah, it was just going to log in the ChannelManager implementation, anyhow. I'm inclined to drop ResponseErrorHandler for now and keep the interface as is. We can always rethink it later if there is some concrete action the user could take.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to drop ResponseErrorHandler for now and keep the interface as is.
Yea, that makes sense toe me, then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed ResponseErrorHandler.
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| pub trait MessageRouter { | ||
| /// Returns a route for sending an [`OnionMessage`] to the given [`Destination`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt Would you be ok with that or will this make bindings difficult?
Not sure why this would make the bindings difficult?
jkczyz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I'm, going to squash the most recent fixups, otherwise subsequent ones will cause many merge conflicts.
| /// | ||
| /// [`OnionMessage`]: msgs::OnionMessage | ||
| fn find_path( | ||
| &self, sender: &PublicKey, destination: Destination |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed that we are already maintaining this as the keys in OnionMessenger::pending_messages.
lightning/src/ln/msgs.rs
Outdated
| /// The message included zlib-compressed values, which we don't support. | ||
| UnsupportedCompression, | ||
| /// A BOLT 12 Offers message had invalid semantics. | ||
| InvalidSemantics(offers::parse::SemanticError), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also might consider including the (optional) specific TLV that failed to parse as Rusty wants to get that back in errors too now.
Hmm... is that something outside of InvoiceError? There the TLV in question is not necessarily about a parse failure (i.e., DecodeError) but rather some semantic error, FWIW.
f22cea5 to
5a1a162
Compare
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conceptually LGTM with one further question about errors.
lightning/src/ln/msgs.rs
Outdated
| /// The message included zlib-compressed values, which we don't support. | ||
| UnsupportedCompression, | ||
| /// The message had invalid semantics. | ||
| InvalidSemantics(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't suffice to generate an InvoiceError, we need the TLV field that failed, is that an issue? Presumably if we fail to read an InvoiceRequest/Invoice we need to be able to map the generic DecodeError (or SemanticError) back into an InvoiceError to send to the peer. I'm okay leaving it as a followup but I'm not entirely clear on the final vision - I assume we'd like to ultimately phase InvalidValue out entirely in favor of a InvalidX { human_readable: String, tlv: Option<u64> }? Can we open an issue and/or leave a comment here describing that? Also, I'm not clear on if this is a good first step towards that, given we don't actually use this yet, does it make more sense to just use InvalidValue for now and then move to something like the above in a followup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... currently this variant is only for logging purposes when failing to parse a message because of invalid semantics. I was thinking InvoiceError would only be used after we have a semantically valid message but couldn't handle it for some other reason (e.g., erroring when verifying a message, building an invoice response, or paying an invoice). The spec isn't really clear on this and, to be honest, the TLV portion doesn't seem that well thought out, IMO. My plan was to only use the error message portion.
Also, note that InvoiceError is sent back via an onion message to the originator, not to our peer. We'd need to re-think our packet parsing / message handling code to handle this as you're suggesting (i.e., for DecodeError::InvalidValue) or at very least only parse the message as TLVs and move semantic checks to the handler (?) in order to reply with an InvoiceError. But then we need to expose those TLV types in the handlers where previously they were private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... currently this variant is only for logging purposes when failing to parse a message because of invalid semantics.
Why does it need to be a DecodeError then? Can't the OnionMessageHandler log the bolt12::ParseError directly rather than munging it into a DecodeError and then logging in peer_handler?
I was thinking InvoiceError would only be used after we have a semantically valid message but couldn't handle it for some other reason (e.g., erroring when verifying a message, building an invoice response, or paying an invoice). The spec isn't really clear on this and, to be honest, the TLV portion doesn't seem that well thought out, IMO. My plan was to only use the error message portion.
Yea, I have no idea what Rusty was really thinking here - the whole "point to the bad TLV" thing isn't really an actionable field, everyone's just gonna implement by passing the human readable string to the user and calling it a day anyway 🤷. Honestly I'd be okay just never filling it in unless someone actually starts using it in a way that makes a difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it need to be a
DecodeErrorthen? Can't theOnionMessageHandlerlog thebolt12::ParseErrordirectly rather than munging it into aDecodeErrorand then logging inpeer_handler?
Ah, so back in #2294 (comment), I think I may have mistaken Payload for Packet and thus thought the Logger would need to be piped through a handful of ReadableArg implementations. But OnionMessenger handler will directly decode the Payload using onion_utils::decode_next_untagged_hop. I should be able to pass the logger through there. Let me give that a try.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that sounds good, thanks. Also feel free to rebase when you do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed and rebased. Should be good for another look now.
| } | ||
| } | ||
|
|
||
| impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that AChannelManager was made public yesterday in a separate PR, so its now already usable.
5a1a162 to
68be6f7
Compare
fuzz/src/onion_message.rs
Outdated
| let log_entries = logger.lines.lock().unwrap(); | ||
| assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), | ||
| "Received an onion message with path_id None and no reply_path".to_string())), Some(&1)); | ||
| // TODO: Add reply path to one_hop_om instead? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to get something that manages to reply, indeed.
68be6f7 to
c0b3380
Compare
TheBlueMatt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commits up to the last two basically LGTM, obv need some kind of resolution on the fuzzing stuff though.
74e8559 to
c1c8316
Compare
|
Added a fixup for addressing the test compilation errors under |
| where | ||
| ES::Target: EntropySource, | ||
| NS::Target: NodeSigner, | ||
| L::Target: Logger + Sized, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sized here is causing problems with a lightning-background-processor doc test with --features=futures that uses Arc<dyn lightning::util::logger::Logger + Send + Sync>.
error[E0277]: the size for values of type `(dyn Logger + Send + std::marker::Sync + 'static)` cannot be known at compilation time
--> src/lib.rs:529:295
|
34 | ...MyChannelManager>, my_gossip_sync: Arc<MyGossipSync>, my_logger: Arc<MyLogger>, my_scorer: Arc<MyScorer>, my_peer_manager: Arc<MyPeerManager>) {
| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn Logger + Send + std::marker::Sync + 'static)`
= note: required because of the requirements on the impl of `OnionMessageHandler` for `OnionMessenger<Arc<KeysManager>, Arc<KeysManager>, Arc<(dyn Logger + Send + std::marker::Sync + 'static)>, IgnoringMessageHandler, IgnoringMessageHandler>`
note: required by a bound in `PeerManager`
--> /Users/jkczyz/src/rust-lightning/lightning/src/ln/peer_handler.rs:683:15
|
683 | OM::Target: OnionMessageHandler,
| ^^^^^^^^^^^^^^^^^^^ required by this bound in `PeerManager`
The problem seems to be that handle_onion_message calls methods that take &L and where L: Logger.
@TheBlueMatt I can fix this by having those methods take &L and where L: Deref, L::Target: Logger. Just seems weird to have a reference to a Deref. Alternatively, keeping L and using where L: Deref + Clone, L::Target: Logger works since both & and Arc implement Clone. Not sure what the canonical way of doing this is, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can drop the sizing bounds, this patch compiles for me:
diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs
index c0d28500f..32e8bb09d 100644
--- a/lightning/src/onion_message/messenger.rs
+++ b/lightning/src/onion_message/messenger.rs
@@ -413,7 +413,7 @@ for OnionMessenger<ES, NS, L, MR, OMH, CMH>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
- L::Target: Logger + Sized,
+ L::Target: Logger,
MR::Target: MessageRouter,
OMH::Target: OffersMessageHandler + Sized,
CMH::Target: CustomOnionMessageHandler + Sized,
diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs
index f208fdda7..f82afdd61 100644
--- a/lightning/src/onion_message/offers.rs
+++ b/lightning/src/onion_message/offers.rs
@@ -91,7 +91,7 @@ impl Writeable for OffersMessage {
}
}
-impl<L: Logger> ReadableArgs<(u64, &L)> for OffersMessage {
+impl<L: Logger + ?Sized> ReadableArgs<(u64, &L)> for OffersMessage {
fn read<R: Read>(r: &mut R, read_args: (u64, &L)) -> Result<Self, DecodeError> {
let (tlv_type, logger) = read_args;
if tlv_type == INVOICE_ERROR_TLV_TYPE {
diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs
index 6f21e9fcd..f532ab585 100644
--- a/lightning/src/onion_message/packet.rs
+++ b/lightning/src/onion_message/packet.rs
@@ -203,7 +203,7 @@ impl<T: CustomOnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
}
// Uses the provided secret to simultaneously decode and decrypt the control TLVs and data TLV.
-impl<H: CustomOnionMessageHandler, L: Logger>
+impl<H: CustomOnionMessageHandler, L: Logger + ?Sized>
ReadableArgs<(SharedSecret, &H, &L)> for Payload<<H as CustomOnionMessageHandler>::CustomMessage> {
fn read<R: Read>(r: &mut R, args: (SharedSecret, &H, &L)) -> Result<Self, DecodeError> {
let (encrypted_tlvs_ss, handler, logger) = args;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that did the trick. Removed the other Sized bounds on the the block while I was at it.
c1c8316 to
d85cccc
Compare
valentinewallace
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will take one more glance tomorrow when the jetlag wears off more, but this looks solid
| assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), | ||
| "Responding to onion message with path_id None".to_string())), Some(&1)); | ||
| assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), | ||
| "Failed responding to onion message with path_id None: TooFewBlindedHops".to_string())), Some(&1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use a valid reply path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an assumption because of the secret key in do_test that the handling node is the second node (i.e., node[1]), so this is the only realistic reply path. In all the other test cases, the handling node isn't the recipient node, so it wouldn't trigger the responding code path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hoping we can keep this as is and just change the log assertion once we support one-hop blinded paths.
3a316cc to
14b1557
Compare
| Ok((Payload::Receive::<<<CMH as Deref>::Target as CustomOnionMessageHandler>::CustomMessage> { | ||
| message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path, | ||
| }, None)) => { | ||
| log_info!(self.logger, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pre-existing, but along the lines of some other comments ISTM we could be spammed by this log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a final commit for this.
|
Feel free to squash! |
14b1557 to
13be0a9
Compare
Done! |
|
Ah, gonna rebase since previous CI failures look to be resolved by #2353. |
If an InvoiceRequest or an Invoice delivered via an onion message cannot be handled, the recipient should reply with an InvoiceError if a reply path was given. Define the message and conversion from SemanticError.
In an upcoming commit, messages for BOLT 12 offers are read from the onion payload. Passing a logger allows for logging semantic errors when parsing the messages.
BOLT 12 Offers makes use of onion messages to request and respond with invoices. Add these types and an error type to OnionMessageContents along with the necessary parsing and encoding.
Add a trait for handling BOLT 12 Offers messages to OnionMessenger and a skeleton implementation of it for ChannelManager. This allows users to either provide their own custom handling Offers messages or rely on a version provided by LDK using stateless verification.
To avoid confusion in the upcoming MessageRouter trait, introduce an OnionMessagePath struct that wraps the intermediate nodes and the destination. Use this in OnionMessenger::send_onion_message.
Add a trait for finding routes for onion messages and parameterize OnionMessenger with it. This allows OnionMessenger to reply to messages that it handles via one of its handlers (e.g., OffersMessageHandler).
Modify onion message handlers to return an optional response message for OnionMessenger to reply with.
This will allow for testing onion message replies.
When generating onion message fuzz data, the same public key was used for each node. However, the code now advances the blinded path if the sender is the introduction node. Use different node secrets for each node to avoid this. Note that the exercised handling code is for the sender's immediate peer.
13be0a9 to
3fd6b44
Compare
Add support for handling BOLT 12 Offers messages and, more generally, replying to onion messages.
InvoiceErrormessageOnionMessageContentswith Offers messagesOnionMessengerwith anOffersMessageHandlertraitOnionMessengerwith anMessageRoutertrait for routing repliesCustomOnionMessageHandlerandOffersMessageHandlerA follow-up (#2371) implements
OffersMessageHandlerforChannelManager.