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

funding: fund channel with selected utxos #7516

Merged
merged 8 commits into from
Jul 25, 2023

Conversation

hieblmi
Copy link
Collaborator

@hieblmi hieblmi commented Mar 15, 2023

This PR implements feature #6955.

Background

It extends the ways in which the user can instruct the OpenChannel RPC to allocate funds when opening a channel. Specifically, it adds the ability to select individual utxos from the user's wallet as the basis for funding. Besides funding via psbts, there are currently two ways in which the user can define the amount a channel is funded with, local_amt and fundmax. The former selects coins until the amount is reached and creates the funding output and a change output with the excess. The later uses up all available funds towards a channel opening. Both source from lnd's internal wallet.

This PR allows both these funding options to instead source from a set of defined utxos.

Examples

To see how this works lets consider two examples:

  1. lncli openchannel --utxo=A --utxo=B --local_amt 100000
    Instead of considering the entire available wallet balance only two utxos from our wallet are taken into account for funding a 100k cap channel.

  2. lncli openchannel --utxo=A --utxo=B --fundmax
    Instead of spending the entire wallet balance on a channel opening fundmax only consumes the specified utxos for it.

Wallet reserve considerations

In both examples we have to ensure that our wallet keeps enough funds to cover for the anchor reserve requirement.
For example, in the fundmax case, if the remaining wallet balance is less than the anchor reserve requirement, we have to carve out the remaining sats from the specified utxo set. Similarly this has to be handled for the local_amt case.

@hieblmi hieblmi marked this pull request as draft March 15, 2023 20:59
@hieblmi hieblmi changed the title funding: pass selected utxos to the OpenChannel RPC funding: fund channel with selected utxos Mar 15, 2023
@saubyk saubyk linked an issue Mar 15, 2023 that may be closed by this pull request
3 tasks
@hieblmi hieblmi force-pushed the utxo-funding branch 4 times, most recently from 0d9ac8b to 625234d Compare March 20, 2023 20:53
@guggero guggero changed the base branch from master to 0-16-1-staging March 28, 2023 07:35
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Super useful feature to make it easier to do coin control/selection funding on the CLI.

Did an initial pass with a few comments that jumped to mind.

@hieblmi hieblmi changed the base branch from 0-16-1-staging to master April 7, 2023 08:38
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid contribution 🤓, good structure, very nice itests.

Keep up the good work :)

localAmt: btcutil.Amount(210_337),
chanOpenShouldFail: true,
expectedErrStr: "not enough witness outputs to " +
"create funding transaction, need 0.00210337 " +
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: does this error msg account for the reserve it the wallet tho, doesn't look like it ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it doesn't, in this test case the user chooses too much local_amt for what they have in their wallet. And it's not an anchor but a static remote key channel, so no reserve.

// manually selected coins can be spent
// entirely on the channel funding.
reserve = 0
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we decrease the reserve requirement by the reserve = reserve - (sumAll-sumManuel) amount here to let non selected coins also account for the reserve ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah right, but only if sumAll-sumManual < reserve. I thought about something like:

excess := sumAll - sumManual
if excess >= reserve {
	reserve = 0
} else {
	reserve -= excess
}

@hieblmi
Copy link
Collaborator Author

hieblmi commented May 25, 2023

Appreciate the review @ziggie1984, I will get to your comments in a bit.

Copy link
Contributor

@shaurya947 shaurya947 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice feature! I was wondering if reordering the commits would make reviewing easier:
cli -> RPC(protobuf) -> rpcserver.go -> chanfunding -> lnwallet -> itest

@hieblmi hieblmi force-pushed the utxo-funding branch 5 times, most recently from b03a52e to 50b2658 Compare June 10, 2023 20:56
@hieblmi hieblmi marked this pull request as ready for review June 10, 2023 21:12
@hieblmi hieblmi requested review from Roasbeef and ziggie1984 June 10, 2023 21:12
@hieblmi hieblmi force-pushed the utxo-funding branch 2 times, most recently from 8dbdd41 to b18cd83 Compare June 12, 2023 09:05
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost ready had a final question regarding concurrent safety when manual utxo selection and allCoins selection happens in different places.

Some other remarks also in regards of the discussion during the review club:

  1. We went by the idea, if we selected more than what is needed we just neglect the other inputs? No consolidation option?
  2. Is the coin-selection strategy now part of this PR or is this still not influenced?

if excess >= reserve {
reserve = 0
} else {
reserve -= excess
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// the excess of coins cover the reserve
// partially then we have to provide for the
// rest during coin selection.
excess := sumAll - sumManual
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we be sure that sumAll and sumManuel don't run in any race condition so that this always stays positive ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that the selection happens in one place it is guaranteed that we do not run into a race condition so this is fine here 👍

@saubyk saubyk added this to the v0.17.1 milestone Jun 15, 2023
@hieblmi hieblmi requested a review from ziggie1984 July 8, 2023 07:06
@hieblmi
Copy link
Collaborator Author

hieblmi commented Jul 12, 2023

@ziggie1984 thanks for your review. I placed the coin availability checks within the coin lock so to avoid a race here.

As to the coin selection and consolidation strategy discussed in the review club - this seems like an optimization that I want to address in a separate PR, once the basic functionality here is merged.

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

Had some minor nits but this looks very good, tested it locally and works perfect ! This feature is really cool.

rpcserver.go Outdated
@@ -2145,6 +2145,15 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
"exceeds %d", in.Memo, len(in.Memo), maxMemoLength)
}

// Check if manually selected outpoints are present to fund a channel.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is not what the below code does. It does not really check if outpoints are present imo? Just converts these to types ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^

@@ -84,6 +84,13 @@ type Request struct {
// e.g. anchor channels.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Commit Msg: adds ability to fund a channel with a selected set of coins?

// selected coins that should be considered available
// for funding a channel.
manuallySelectedCoins []Coin
err error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We could still use := in the line and it would not redeclare the manuallySelectedCoins variable, or is this the style we have to adhere in such a case ? If so maybe an entry in the code_formatting_rules.md file

r.MinConfs, math.MaxInt32,
)
if err != nil {
return err
}

// Ensure that all manually selected coins remain unspent.
unspent := make(map[wire.OutPoint]struct{})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// the excess of coins cover the reserve
// partially then we have to provide for the
// rest during coin selection.
excess := sumAll - sumManual
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that the selection happens in one place it is guaranteed that we do not run into a race condition so this is fine here 👍

reserve = 0
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate more, do you mean adding new external inputs as well when the internal ones do not suffice?

@lightninglabs-deploy
Copy link

@Roasbeef: review reminder
@hieblmi, remember to re-request review from reviewers when ready

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🍾

for _, coin := range allCoins {
unspent[coin.OutPoint] = struct{}{}
}
for _, coin := range manuallySelectedCoins {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could also assert that the coins are locked as well at this stage. Though, if things proceed as expected here (final transaction actually broadcast), then this'll happen eventually.

}

// runTestCase runs a single test case asserting that test conditions are met.
func runUtxoSelectionTestCase(ht *lntest.HarnessTest, t *testing.T, alice,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mismatch in godoc name: runTestCase -> runUtxoSelectionTestCase

rpcserver.go Outdated
@@ -2145,6 +2145,15 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
"exceeds %d", in.Memo, len(in.Memo), maxMemoLength)
}

// Check if manually selected outpoints are present to fund a channel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^

@Roasbeef Roasbeef modified the milestones: v0.17.1, v0.17.0 Jul 23, 2023
@Roasbeef Roasbeef merged commit 9ed064b into lightningnetwork:master Jul 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Add argument to OpenChannel rpc to input UTXOs
6 participants