Skip to content
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

Payjoin integration with LDK #2944

Open
jbesraa opened this issue Mar 18, 2024 · 11 comments
Open

Payjoin integration with LDK #2944

jbesraa opened this issue Mar 18, 2024 · 11 comments

Comments

@jbesraa
Copy link
Contributor

jbesraa commented Mar 18, 2024

A Payjoin integration with ldk-node started here lightningdevkit/ldk-node#257 and there are a couple of things I discussed with tnull and we thought it would be good to bring the discussion up to here to see what everyone thinks.

Our goal of integrating Payjoin with lightning is to allow users to create a channel with funds that live outside the node wallet.

In order to achieve the above goal, we need to implement the following functionality:

  1. Allow a lightning node to act as a Payjoin receiver (you can look at the top of this file to understand what does it mean to be Payjoin receiver https://github.com/payjoin/rust-payjoin/blob/master/payjoin/src/receive/mod.rs#L1)
  2. Allow a lightning node runner to schedule a channel for future opening (ie channel scheduler)
  3. Negotiate channel opening with a peer based on the incoming Payjoin request and respond to the Payjoin sender after the negotiating reached the Funding Signed phase.

You can find a detailed diagram of the desired flow here lightningdevkit/ldk-node#257 (comment)

Two questions were raised in offline discussions:

  1. Which of the parts should live in ldk-node/ldk/payjoin/separate crate. This question is raised because while we doing this in LDK-node, maybe we could also enable other node implementations who uses ldk to leverage Payjoin.
  2. We need to get a hold on the PSBT after the FundingSigned msg exchange and and send it back to the Payjoin sender, ie we dont wanna broadcast it ourselves even tho its an outbound channel. The question is how we want to allow that or is it possible already?

cc @DanGould

@TheBlueMatt
Copy link
Collaborator

Which of the parts should live in ldk-node/ldk/payjoin/separate crate. This question is raised because while we doing this in LDK-node, maybe we could also enable other node implementations who uses ldk to leverage Payjoin.

Indeed, IMO it would be nice to have as much common logic in a separate crate as possible.

We need to get a hold on the PSBT after the FundingSigned msg exchange and and send it back to the Payjoin sender, ie we dont wanna broadcast it ourselves even tho its an outbound channel. The question is how we want to allow that or is it possible already?

Not sure I understand this - once we have built the funding tx cooperatively with our payjoin partner, we should be good to go to broadcast, no? Not sure I understand why LDK can't be in control of when the tx gets broadcasted.

@jbesraa
Copy link
Contributor Author

jbesraa commented Mar 26, 2024

Which of the parts should live in ldk-node/ldk/payjoin/separate crate. This question is raised because while we doing this in LDK-node, maybe we could also enable other node implementations who uses ldk to leverage Payjoin.

Indeed, IMO it would be nice to have as much common logic in a separate crate as possible.

We need to get a hold on the PSBT after the FundingSigned msg exchange and and send it back to the Payjoin sender, ie we dont wanna broadcast it ourselves even tho its an outbound channel. The question is how we want to allow that or is it possible already?

Not sure I understand this - once we have built the funding tx cooperatively with our payjoin partner, we should be good to go to broadcast, no? Not sure I understand why LDK can't be in control of when the tx gets broadcasted.

In the payjoin protocol, the "payjoin sender" is the party responsible for broadcasting the transaction and in our use case we will be the "payjoin receiver". See the diagram here lightningdevkit/ldk-node#257 (comment). If we run a lightning node A and we want to open a channel with node B, we can schedule a channel that will be created once we receive a payjoin request into our node(A). So node A is the "payjoin receiver". The reason we cannot broadcast the transaction is we still need the "payjoin sender" to sign the PSBT after we are done with the channel negotiation(ie got PSBT after FundingSigned).

I thought about adding a flag to ChannelContext called broadcast_manually: Option<()> and work my way around the functions to return the PSBT to the user(the node runner) after FundingSigned if the flag is set.

@TheBlueMatt
Copy link
Collaborator

Why not just run the payjoin protocol after accept_channel but before funding_generated/signing?

@tnull
Copy link
Contributor

tnull commented Mar 27, 2024

Why not just run the payjoin protocol after accept_channel but before funding_generated/signing?

Yes, this was also my first question. To quote myself from a previous conversation, where I had proposed the following flow:

  1. create_channel (initiating open/accept handshake)
  2. Get FundingGenerationReady, queue first PJ request to sender with a PayjoinManager object or similar, return from event handling
  3. Once the response from the Sender is received, call funding_transaction_generated, initiating funding_created/signed handshake
  4. Catch the broadcast via a PayjoinBroadcaster implementation that broadcasts in cooperation with the Sender. Need to figure out how to handle rebroadcasts though, i.e., what to intercept and what not to intercept.

However, IIUC, the PJ sender provides a PSBT the funder needs to know/use, so it has to run before open_channel. Is this correct, @jbesraa?

@jbesraa
Copy link
Contributor Author

jbesraa commented Mar 27, 2024

I think coupling both processes (or doing payjoin in the middle) can be tricky in the following scenarios:

  1. If we want to open multiple channels from a single payjoin transaction
  2. Open a channel from a payjoin transaction without predefined channel size(I run a store and receive payjoin onchain tx, I want to configure the node to open a channel if I received a a payment bigger than X)

I think in general we would want some ChannelScheduler where the node runner can schedule multiple channels to be open when funds transferred to the node wallet via payjoin tx.

So the node wallet gets notified about an incoming payjoin tx, we loops through the scheduled channels and try to batch them in the same tx

What kind of limitations do we have once we got accept_channel msg from our peer? are we expected to exchange the following msgs ~instantly?

Why do you prefer the payjoin process to happen after accept_channel?

@jbesraa
Copy link
Contributor Author

jbesraa commented Mar 27, 2024

To meet the payjoin protcol spec, after we are done changing the PSBT outputs and maybe add inputs, we are expected to send whats called payjoin_proposal to the payjoin sender, who then re-verify the tx and re-sign their inputs & broadcast. But at that point we have all the OutPoints we need so we could start some chain monitor to track them.

@TheBlueMatt
Copy link
Collaborator

If we want to open multiple channels from a single payjoin transaction

I don't see why that changes things? You can just wait until all channels are ready for funding and do the payjoin then. Worse, I'm really scared of letting users manually handle broadcast of batch channels, there's a lot that can go wrong.

Open a channel from a payjoin transaction without predefined channel size(I run a store and receive payjoin onchain tx, I want to configure the node to open a channel if I received a a payment bigger than X)

Not quite sure I understand this one - lightning nodes expect to know the channel size after open/accept_channel, it cannot change after that, so after the funding is created its much too late to change that.

One general note, in LDK you can intercept the broadcast and handle it yourself, if you really want - the broadcast goes out via the BroadcasterInterface and you can simply wait until you see the funding tx get broadcasted and then go do whatever you need to finish signing it (you can even let the tx go out normally - presumably if you haven't run the payjoin step its not fully signed so the broadcast will fail anyway). Obviously that's not a great API if payjoin needs to go this way, just mentioning it for completeness.

@jbesraa
Copy link
Contributor Author

jbesraa commented Apr 2, 2024

Why not just run the payjoin protocol after accept_channel but before funding_generated/signing?

I think we could probably run the payjoin protocol in different stages(It will make it tricky but manageable with channel amount) but we cant respond back to the payjoin sender before we receive FundingSigned as that could put the funds in risk because the payjoin sender have the tx and they could broadcast it but we never receive the FundingSigned

@TheBlueMatt
Copy link
Collaborator

Right, that seems like it wouldn't require any LDK changes, I think, and generally be the same implementation complexity on the payjoin side, AFAICT - either way you have to not finish the payjoin protocol until the lightning side advances, you might as well just let the lightning side actually do the broadcast, right?

@jbesraa
Copy link
Contributor Author

jbesraa commented Apr 15, 2024

Regarding the broadcasting, we should abide by the payjoin rules, which goes by the following:

  1. Initiatior sends a payjoin transaction containing the original psbt
  2. Receiver accepts the transaction(http request) and responds back with payjoin_proposal
  3. Initiator validates the payjoin_proposal and broadcast the tx

Also I am not sure if we can sign and broadcast the psbt after we made changes to it and we dont own the inputs?

Currently what I am doing is the following:

  1. Node runner schedule a channel with Node B and gets back a bip21 to pay
    1.1 In the background a channel is created and stopped after channel_accept message
  2. Node runner pays the bip21, the node checks for scheduled channel, if none, it just performs normal payjoin and the funds will be received to the node wallet, if channel is some:
    2.1 change the output of the original_psbt to point to output_script returned from FundingGeneartionReady event.
    2.2 call funding_transaction_generated on channel_manager
    2.3 wait in the tx_broadcaster for the tx to come in(= FundingSigned)
    2.4 return payjoin_proposal to payjoin initiator

This will probably change once I go further in the PR..

@TheBlueMatt
Copy link
Collaborator

I'm still confused why it needs to happen this way. Here's what I had in mind:

We're a node that wants to send payment(s) via payjoin, and in doing so open an outbound channel with some unrelated channel counterparty. This seems like the most sensible case to focus on because its the case of a "client" wallet - if we're receiving payments we probably want to receive channels too.

When we get to the point where we want to do this (ie we're ready to open a channel to the counterparty), we start by doing an open_channel/accept_channel handshake. Once we get the FundingGenerationReady event we start the payjoin protocol - we now have the script we want to pay and how much we need to pay it. Once we get back the payjoin_proposal from the payment recipient we should have a transaction and associated signatures and can feed that back into LDK and let it handle the rest.

Where did we differ - it wasn't entirely clear from your above which side we're talking about in each protocol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants