chanfunding: adds ability to fund up to some maximum amount

Allows to define a maximum amount to provision a channel
opening with using a new field `FundUpToMaxAmt` on the
`Request` struct. Also adds a new coin select function
`CoinSelectUpToAmount` to select coins up to a maximum
amount respecting a minimum amount.
This commit is contained in:
Bjarne Magnussen authored and Slyghtning committed 2023-04-05 23:05:06 +02:00
1 parent f97454d4ff
commit 0c8aa81ed5
6 files changed
+370 -7

No files matched your search

+77
View File
@@ -1,6 +1,7 @@
package chanfunding
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcutil"
@@ -53,6 +54,7 @@ type Coin struct {
// selected coins are returned in order for the caller to properly handle
// change+fees.
func selectInputs(amt btcutil.Amount, coins []Coin) (btcutil.Amount, []Coin, error) {
satSelected := btcutil.Amount(0)
for i, coin := range coins {
satSelected += btcutil.Amount(coin.Value)
@@ -251,3 +253,78 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
return selectedUtxos, outputAmt, changeAmt, nil
}
// CoinSelectUpToAmount attempts to select coins such that we'll select up to
// maxAmount exclusive of fees if sufficient funds are available. If
// insufficient funds are available this method selects all available coins.
func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
dustLimit btcutil.Amount, coins []Coin) ([]Coin, btcutil.Amount,
btcutil.Amount, error) {
var (
// selectSubtractFee is tracking if our coin selection was
// unsuccessful and whether we have to start a new round of
// selecting coins considering fees.
selectSubtractFee = false
outputAmount = maxAmount
)
// 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.
selected, changeAmt, err := CoinSelect(
feeRate, maxAmount, dustLimit, coins,
)
var errInsufficientFunds *ErrInsufficientFunds
if errors.As(err, &errInsufficientFunds) {
// If the initial coin selection fails due to insufficient funds
// we select our total available balance minus fees.
selectSubtractFee = true
} else if err != nil {
return nil, 0, 0, err
}
// If we determined that our local balance is insufficient we check our
// total balance minus fees.
if selectSubtractFee {
// Get balance from coins.
var totalBalance btcutil.Amount
for _, coin := range coins {
totalBalance += btcutil.Amount(coin.Value)
}
selected, outputAmount, changeAmt, err = CoinSelectSubtractFees(
feeRate, totalBalance, dustLimit, coins,
)
if err != nil {
return nil, 0, 0, err
}
}
// Sanity check the resulting output values to make sure we don't burn a
// great part to fees.
totalOut := outputAmount + changeAmt
sum := func(coins []Coin) btcutil.Amount {
var sum btcutil.Amount
for _, coin := range coins {
sum += btcutil.Amount(coin.Value)
}
return sum
}
err = sanityCheckFee(totalOut, sum(selected)-totalOut)
if err != nil {
return nil, 0, 0, err
}
// In case the selected amount is lower than minimum funding amount we
// must return an error. The minimum funding amount is determined
// upstream and denotes either the minimum viable channel size or an
// amount sufficient to cover for the initial remote balance.
if outputAmount < minAmount {
return nil, 0, 0, fmt.Errorf("available funds(%v) below the "+
"minimum amount(%v)", outputAmount, minAmount)
}
return selected, outputAmount, changeAmt, nil
}