-
Notifications
You must be signed in to change notification settings - Fork 385
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
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. |
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.
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_route
should return only intermediate_hops to the destination?
Add clarification to the docs.
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 withsend_custom_message
it'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
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_route
should return only intermediate_hops to the destination?
Add clarification to the docs.
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 withsend_custom_message
it'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.
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
@@ -115,6 +122,12 @@ impl CustomOnionMessageHandler for TestCustomMessageHandler { | |||
} | |||
} | |||
|
|||
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.
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
Sized
restriction 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 theSized
trait 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_handler
is a type implementingDeref
where theTarget
implementsCustomOnionMessageHandler
.respond_with_onion_message
wants 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::Target
is?Sized
(i.e., optionally sized) andrespond_with_onion_message
takes a reference which isSized
, we need to restrict its uses toSized
types.
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
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
@@ -209,6 +207,9 @@ mod tests { | |||
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
@@ -79,6 +80,10 @@ pub enum DecodeError { | |||
Io(io::ErrorKind), | |||
/// 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_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.
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?
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
@@ -79,6 +80,10 @@ pub enum DecodeError { | |||
Io(io::ErrorKind), | |||
/// 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
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
@@ -79,6 +79,8 @@ pub enum DecodeError { | |||
Io(io::ErrorKind), | |||
/// 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
DecodeError
then? Can't theOnionMessageHandler
log thebolt12::ParseError
directly rather than munging it into aDecodeError
and 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
@@ -209,6 +207,9 @@ mod tests { | |||
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
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
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
match onion_utils::decode_next_untagged_hop( | ||
onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, | ||
(control_tlvs_ss, &*self.custom_handler, &*self.logger) | ||
) { | ||
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.
InvoiceError
messageOnionMessageContents
with Offers messagesOnionMessenger
with anOffersMessageHandler
traitOnionMessenger
with anMessageRouter
trait for routing repliesCustomOnionMessageHandler
andOffersMessageHandler
A follow-up (#2371) implements
OffersMessageHandler
forChannelManager
.