mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-14 18:30:52 +02:00
chanfunding: allow to set a reserved amount not used for funding
This commit is contained in:
committed by
Slyghtning
parent
d7f578b0d9
commit
8c1cf21707
@@ -78,6 +78,12 @@ type Request struct {
|
|||||||
// responder as part of the initial channel creation.
|
// responder as part of the initial channel creation.
|
||||||
PushAmt btcutil.Amount
|
PushAmt btcutil.Amount
|
||||||
|
|
||||||
|
// WalletReserve is a reserved amount that is not used to fund the
|
||||||
|
// channel when a maximum amount defined by FundUpToMaxAmt is set. This
|
||||||
|
// is useful when a reserved wallet balance must stay available due to
|
||||||
|
// e.g. anchor channels.
|
||||||
|
WalletReserve btcutil.Amount
|
||||||
|
|
||||||
// MinConfs controls how many confirmations a coin need to be eligible
|
// MinConfs controls how many confirmations a coin need to be eligible
|
||||||
// to be used as an input to the funding transaction. If this value is
|
// to be used as an input to the funding transaction. If this value is
|
||||||
// set to zero, then zero conf outputs may be spent.
|
// set to zero, then zero conf outputs may be spent.
|
||||||
|
@@ -255,11 +255,12 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CoinSelectUpToAmount attempts to select coins such that we'll select up to
|
// CoinSelectUpToAmount attempts to select coins such that we'll select up to
|
||||||
// maxAmount exclusive of fees if sufficient funds are available. If
|
// maxAmount exclusive of fees and optional reserve if sufficient funds are
|
||||||
// insufficient funds are available this method selects all available coins.
|
// available. If insufficient funds are available this method selects all
|
||||||
|
// available coins.
|
||||||
func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
|
func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
|
||||||
dustLimit btcutil.Amount, coins []Coin) ([]Coin, btcutil.Amount,
|
reserved, dustLimit btcutil.Amount, coins []Coin) ([]Coin,
|
||||||
btcutil.Amount, error) {
|
btcutil.Amount, btcutil.Amount, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// selectSubtractFee is tracking if our coin selection was
|
// selectSubtractFee is tracking if our coin selection was
|
||||||
@@ -269,6 +270,13 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
|
|||||||
outputAmount = maxAmount
|
outputAmount = maxAmount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Get total balance from coins which we need for reserve considerations
|
||||||
|
// and fee santiy checks.
|
||||||
|
var totalBalance btcutil.Amount
|
||||||
|
for _, coin := range coins {
|
||||||
|
totalBalance += btcutil.Amount(coin.Value)
|
||||||
|
}
|
||||||
|
|
||||||
// First we try to select coins to create an output of the specified
|
// First we try to select coins to create an output of the specified
|
||||||
// maxAmount with or without a change output that covers the miner fee.
|
// maxAmount with or without a change output that covers the miner fee.
|
||||||
selected, changeAmt, err := CoinSelect(
|
selected, changeAmt, err := CoinSelect(
|
||||||
@@ -276,25 +284,42 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
|
|||||||
)
|
)
|
||||||
|
|
||||||
var errInsufficientFunds *ErrInsufficientFunds
|
var errInsufficientFunds *ErrInsufficientFunds
|
||||||
if errors.As(err, &errInsufficientFunds) {
|
if err == nil { //nolint:gocritic,ifElseChain
|
||||||
|
// If the coin selection succeeds we check if our total balance
|
||||||
|
// covers the selected set of coins including fees plus an
|
||||||
|
// optional anchor reserve.
|
||||||
|
|
||||||
|
// First we sum up the value of all selected coins.
|
||||||
|
var sumSelected btcutil.Amount
|
||||||
|
for _, coin := range selected {
|
||||||
|
sumSelected += btcutil.Amount(coin.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We then subtract the change amount from the value of all
|
||||||
|
// selected coins to obtain the actual amount that is selected.
|
||||||
|
sumSelected -= changeAmt
|
||||||
|
|
||||||
|
// Next we check if our total balance can cover for the selected
|
||||||
|
// output plus the optional anchor reserve.
|
||||||
|
if totalBalance-sumSelected < reserved {
|
||||||
|
// If our local balance is insufficient to cover for the
|
||||||
|
// reserve we try to select an output amount that uses
|
||||||
|
// our total balance minus reserve and fees.
|
||||||
|
selectSubtractFee = true
|
||||||
|
}
|
||||||
|
} else if errors.As(err, &errInsufficientFunds) {
|
||||||
// If the initial coin selection fails due to insufficient funds
|
// If the initial coin selection fails due to insufficient funds
|
||||||
// we select our total available balance minus fees.
|
// we select our total available balance minus fees.
|
||||||
selectSubtractFee = true
|
selectSubtractFee = true
|
||||||
} else if err != nil {
|
} else {
|
||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we determined that our local balance is insufficient we check our
|
// If we determined that our local balance is insufficient we check
|
||||||
// total balance minus fees.
|
// our total balance minus fees and optional reserve.
|
||||||
if selectSubtractFee {
|
if selectSubtractFee {
|
||||||
// Get balance from coins.
|
|
||||||
var totalBalance btcutil.Amount
|
|
||||||
for _, coin := range coins {
|
|
||||||
totalBalance += btcutil.Amount(coin.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
selected, outputAmount, changeAmt, err = CoinSelectSubtractFees(
|
selected, outputAmount, changeAmt, err = CoinSelectSubtractFees(
|
||||||
feeRate, totalBalance, dustLimit, coins,
|
feeRate, totalBalance-reserved, dustLimit, coins,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
|
@@ -550,6 +550,7 @@ func TestCoinSelectUpToAmount(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
minValue btcutil.Amount
|
minValue btcutil.Amount
|
||||||
maxValue btcutil.Amount
|
maxValue btcutil.Amount
|
||||||
|
reserved btcutil.Amount
|
||||||
coins []Coin
|
coins []Coin
|
||||||
|
|
||||||
expectedInput []btcutil.Amount
|
expectedInput []btcutil.Amount
|
||||||
@@ -688,6 +689,26 @@ func TestCoinSelectUpToAmount(t *testing.T) {
|
|||||||
expectedInput: []btcutil.Amount{1 * coin},
|
expectedInput: []btcutil.Amount{1 * coin},
|
||||||
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false) - 1,
|
expectedFundingAmt: 1*coin - fundingFee(feeRate, 1, false) - 1,
|
||||||
expectedChange: 0,
|
expectedChange: 0,
|
||||||
|
}, {
|
||||||
|
// This test makes sure that if a reserved value is required
|
||||||
|
// then it is handled correctly by leaving exactly the reserved
|
||||||
|
// value as change and still maxing out the funding amount.
|
||||||
|
name: "sanity check for correct reserved amount subtract " +
|
||||||
|
"from total",
|
||||||
|
coins: []Coin{{
|
||||||
|
TxOut: wire.TxOut{
|
||||||
|
PkScript: p2wkhScript,
|
||||||
|
Value: 1 * coin,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
minValue: minValue,
|
||||||
|
maxValue: 1*coin - 9000,
|
||||||
|
reserved: 10000,
|
||||||
|
|
||||||
|
expectedInput: []btcutil.Amount{1 * coin},
|
||||||
|
expectedFundingAmt: 1*coin -
|
||||||
|
fundingFee(feeRate, 1, true) - 10000,
|
||||||
|
expectedChange: 10000,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
@@ -698,7 +719,7 @@ func TestCoinSelectUpToAmount(t *testing.T) {
|
|||||||
selected, localFundingAmt, changeAmt,
|
selected, localFundingAmt, changeAmt,
|
||||||
err := CoinSelectUpToAmount(
|
err := CoinSelectUpToAmount(
|
||||||
feeRate, test.minValue, test.maxValue,
|
feeRate, test.minValue, test.maxValue,
|
||||||
dustLimit, test.coins,
|
test.reserved, dustLimit, test.coins,
|
||||||
)
|
)
|
||||||
if len(test.expectErr) == 0 && err != nil {
|
if len(test.expectErr) == 0 && err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
|
@@ -308,7 +308,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
selectedCoins, localContributionAmt, changeAmt,
|
selectedCoins, localContributionAmt, changeAmt,
|
||||||
err = CoinSelectUpToAmount(
|
err = CoinSelectUpToAmount(
|
||||||
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
|
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
|
||||||
w.cfg.DustLimit, coins,
|
r.WalletReserve, w.cfg.DustLimit, coins,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -836,6 +836,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
|
|
||||||
localFundingAmt := req.LocalFundingAmt
|
localFundingAmt := req.LocalFundingAmt
|
||||||
remoteFundingAmt := req.RemoteFundingAmt
|
remoteFundingAmt := req.RemoteFundingAmt
|
||||||
|
hasAnchors := req.CommitType.HasAnchors()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fundingIntent chanfunding.Intent
|
fundingIntent chanfunding.Intent
|
||||||
@@ -855,9 +856,33 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
// funder in the attached request to provision the inputs/outputs
|
// funder in the attached request to provision the inputs/outputs
|
||||||
// that'll ultimately be used to construct the funding transaction.
|
// that'll ultimately be used to construct the funding transaction.
|
||||||
if !ok {
|
if !ok {
|
||||||
|
var err error
|
||||||
|
var numAnchorChans int
|
||||||
|
|
||||||
|
// Get the number of anchor channels to determine if there is a
|
||||||
|
// reserved value that must be respected when funding up to the
|
||||||
|
// maximum amount. Since private channels (most likely) won't be
|
||||||
|
// used for routing other than the last hop, they bear a smaller
|
||||||
|
// risk that we must force close them in order to resolve a HTLC
|
||||||
|
// up/downstream. Hence we exclude them from the count of anchor
|
||||||
|
// channels in order to attribute the respective anchor amount
|
||||||
|
// to the channel capacity.
|
||||||
|
if req.FundUpToMaxAmt > 0 && req.MinFundAmt > 0 {
|
||||||
|
numAnchorChans, err = l.CurrentNumAnchorChans()
|
||||||
|
if err != nil {
|
||||||
|
req.err <- err
|
||||||
|
req.resp <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
|
||||||
|
if hasAnchors && isPublic {
|
||||||
|
numAnchorChans++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Coin selection is done on the basis of sat/kw, so we'll use
|
// Coin selection is done on the basis of sat/kw, so we'll use
|
||||||
// the fee rate passed in to perform coin selection.
|
// the fee rate passed in to perform coin selection.
|
||||||
var err error
|
|
||||||
fundingReq := &chanfunding.Request{
|
fundingReq := &chanfunding.Request{
|
||||||
RemoteAmt: req.RemoteFundingAmt,
|
RemoteAmt: req.RemoteFundingAmt,
|
||||||
LocalAmt: req.LocalFundingAmt,
|
LocalAmt: req.LocalFundingAmt,
|
||||||
@@ -867,6 +892,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
PushAmt: lnwire.MilliSatoshi.ToSatoshis(
|
PushAmt: lnwire.MilliSatoshi.ToSatoshis(
|
||||||
req.PushMSat,
|
req.PushMSat,
|
||||||
),
|
),
|
||||||
|
WalletReserve: l.RequiredReserve(
|
||||||
|
uint32(numAnchorChans),
|
||||||
|
),
|
||||||
MinConfs: req.MinConfs,
|
MinConfs: req.MinConfs,
|
||||||
SubtractFees: req.SubtractFees,
|
SubtractFees: req.SubtractFees,
|
||||||
FeeRate: req.FundingFeePerKw,
|
FeeRate: req.FundingFeePerKw,
|
||||||
@@ -939,7 +967,6 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
|||||||
// funding tx ready, so this will always pass. We'll do another check
|
// funding tx ready, so this will always pass. We'll do another check
|
||||||
// when the PSBT has been verified.
|
// when the PSBT has been verified.
|
||||||
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
|
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
|
||||||
hasAnchors := req.CommitType.HasAnchors()
|
|
||||||
if enforceNewReservedValue {
|
if enforceNewReservedValue {
|
||||||
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
|
err = l.enforceNewReservedValue(fundingIntent, isPublic, hasAnchors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1160,10 +1187,7 @@ func (l *LightningWallet) CheckReservedValue(in []wire.OutPoint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We reserve a given amount for each anchor channel.
|
// We reserve a given amount for each anchor channel.
|
||||||
reserved := btcutil.Amount(numAnchorChans) * AnchorChanReservedValue
|
reserved := l.RequiredReserve(uint32(numAnchorChans))
|
||||||
if reserved > MaxAnchorChanReservedValue {
|
|
||||||
reserved = MaxAnchorChanReservedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if walletBalance < reserved {
|
if walletBalance < reserved {
|
||||||
walletLog.Debugf("Reserved value=%v above final "+
|
walletLog.Debugf("Reserved value=%v above final "+
|
||||||
|
Reference in New Issue
Block a user