-
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
Introduce Flow utilities and OffersMessageFlow implementation #3639
base: main
Are you sure you want to change the base?
Conversation
👋 Hi! I see this is a draft PR. |
Right now, this PR is a proof of concept for the approach—it introduces a basic set of utilities to move a significant chunk of Offers-related code out of What's in the current version:
About the CI failures:
If this approach looks good, I'll expand it to cover Offer handlers next! |
@TheBlueMatt, @jkczyz |
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.
Let's iterate a bit more on this. We should have the MessageContext
creation and thus the BlindedPath
creation be the domain of OffersMessageFlow
, IMO. This means passing a closure to the utility functions to create the blinded paths. Likewise with BlindedPaymentPath
s.
message_router: MR, | ||
|
||
our_network_pubkey: PublicKey, | ||
highest_seen_timestamp: AtomicUsize, |
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'd need to update this any time a block is processed. So either we need an Arc
to share with ChannelManager
or have Flow
require chain::Listen
such that ChannelManager
(or whoever owns the Flow
) can call block_connected
.
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.
That makes sense! I’ve implemented the chain::Listen
trait for OffersMessageFlow
in pr3639.02. This allows block_connected
to be called as needed.
Let me know if this approach aligns with what you had in mind! Thanks a lot for the pointer!
#[cfg(not(any(test, feature = "_test_utils")))] | ||
pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>, | ||
#[cfg(any(test, feature = "_test_utils"))] | ||
pub(crate) pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>, | ||
|
||
pending_async_payments_messages: Mutex<Vec<(AsyncPaymentsMessage, MessageSendInstructions)>>, | ||
|
||
#[cfg(feature = "dnssec")] | ||
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>, |
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 the idea here was to have the owner such as ChannelManager
own the message queues since it would implement the message handlers.
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 was deciding between two designs: having ChannelManager
hold the queues or having OffersMessageFlow
hold them. I chose the latter because:
- The long-term goal is to fully separate offers-related code from
ChannelManager
, and moving the queues out will be a major step in that direction. - Keeping the queues within
OffersMessageFlow
helps maintain cleaner utility function signatures, as users don’t need to explicitly pass the queues as parameters.
Let me know your thoughts on this approach!
fn create_offer_builder( | ||
&self, nonce: Nonce, | ||
) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> { | ||
let node_id = self.get_our_node_id(); | ||
let expanded_key = &self.inbound_payment_key; | ||
let secp_ctx = &self.secp_ctx; | ||
|
||
let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) | ||
.chain_hash(self.chain_hash); | ||
|
||
Ok(builder) | ||
} |
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 question what value utilities like these are really providing to the the user.
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 idea I was exploring was to create a mid-level API that remains modular enough to be used in multiple contexts.
For example, self.flow.create_offer_builder
simplifies both create_offer_builder
and create_async_receive_offer_builder
, making the API more reusable and reducing duplication.
Would love to hear your thoughts on whether this with indented design approach.
MR::Target: MessageRouter, | ||
{ | ||
fn create_offer_builder( | ||
&self, nonce: Nonce, |
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 the Nonce
could be generated from the EntropySource
. Though I imagine we need the Nonce
for the MessageContext
. Don't we want OffersMessageFlow
to determine the correct MessageContext
to give to the BlindedPath
s? Otherwise, the onus is on the user to pass the correct context. So this implies the blinded paths should be created within this method.
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.
Makes sense! My initial reasoning was to keep offer building and blinded path creation separate, as this allowed us to use the same self.flow.create_offer_builder
for both create_offer_builder
(where we create & append the path later) and create_async_receive_offer_builder
(where we already have a vector of paths to append).
That said, I think we can take a middle ground by passing an optional closure as input—if present, it would handle the creation and appending of paths. This keeps things flexible while reducing the onus on the user to manually pass the correct context.
Let me know what you think of this approach. Thanks!
fn create_invoice_builder<'a>( | ||
&'a self, refund: &'a Refund, payment_paths: Vec<BlindedPaymentPath>, | ||
payment_hash: PaymentHash, | ||
) -> Result<InvoiceBuilder<'a, DerivedSigningPubkey>, Bolt12SemanticError> { |
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 we need this not only for refunds but also for handling invoice requests. I'm wondering though if we'd rather want a method that does more similar to request_refund_payment
and pay_for_offer
?
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.
Looking through the section of code where we create an invoice from an invoice request, it seems significantly different from invoice creation from a refund. Given our current trait design, introducing a separate function feels like a cleaner solution.
On the other hand, we could design the Flow
trait to be more similar to request_refund_payment
and pay_for_offer
, but wouldn’t that make it too high-level of an API?
@TheBlueMatt, would love to hear your thoughts on this!
} | ||
|
||
fn create_blinded_paths( | ||
&self, peers: Vec<MessageForwardNode>, context: MessageContext, |
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.
Again, it seems OffersMessageFlow
should be the one determining which context to use.
lightning/src/offers/flow.rs
Outdated
|
||
#[cfg(feature = "dnssec")] | ||
use crate::onion_message::dns_resolution::DNSResolverMessage; | ||
|
||
pub trait Flow { |
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.
After going through the PR, I'm not sure how useful the Flow
trait is. Either the caller of the utilities can do any processing between calls or we can parameterize OffersMessageFlow
with traits (e.g., currency conversion).
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 I agree with your point. We could make OffersMessageFlow
a direct parameter of ChannelManager
without needing the Flow
trait as an intermediary. This way, we still remove Bolt12
responsibilities from ChannelManager
while giving users the flexibility to implement their own handlers using OffersMessageFlow
utilities.
@TheBlueMatt, I’d love to hear your thoughts on this approach!
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.
Were we going to move most of the OffersMessageHandler
logic into flow.rs
?
Yes, that’s the goal! For this version, I’ve primarily abstracted out the |
This PR presents an alternative approach to #3412.
It introduces the
Flow
trait and its implementation,OffersMessageFlow
, as a mid-level API that abstracts most Offers-related logic out ofChannelManager
, while allowingChannelManager
to be parameterized overFlow
.This improves modularity and separation of concerns, making the Offers-related code more maintainable. Additionally, it provides a set of functional utilities for users who do not use
ChannelManager
in their workflow.