mod+chanfunding: use coin selection strategy for channel funding

The wallet assembler is now aware of the node config level coin
selection strategy, so we can use it when creating new channels.
This commit is contained in:
Oliver Gugger committed 2024-02-23 08:58:09 +01:00
1 parent f7198c4105
commit 9bdddbcc56
8 files changed
+131 -114

No files matched your search

+37 -27
View File
@@ -6,20 +6,20 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
// ErrInsufficientFunds is a type matching the error interface which is
// returned when coin selection for a new funding transaction fails to due
// returned when coin selection for a new funding transaction fails due to
// having an insufficient amount of confirmed funds.
type ErrInsufficientFunds struct {
amountAvailable btcutil.Amount
amountSelected btcutil.Amount
}
// Error returns a human readable string describing the error.
// Error returns a human-readable string describing the error.
func (e *ErrInsufficientFunds) Error() string {
return fmt.Sprintf("not enough witness outputs to create funding "+
"transaction, need %v only have %v available",
@@ -33,33 +33,33 @@ type errUnsupportedInput struct {
PkScript []byte
}
// Error returns a human readable string describing the error.
// Error returns a human-readable string describing the error.
func (e *errUnsupportedInput) Error() string {
return fmt.Sprintf("unsupported address type: %x", e.PkScript)
}
// Coin represents a spendable UTXO which is available for channel funding.
// This UTXO need not reside in our internal wallet as an example, and instead
// may be derived from an existing watch-only wallet. It wraps both the output
// present within the UTXO set, and also the outpoint that generates this coin.
type Coin struct {
wire.TxOut
wire.OutPoint
}
// selectInputs selects a slice of inputs necessary to meet the specified
// selection amount. If input selection is unable to succeed due to insufficient
// funds, a non-nil error is returned. Additionally, the total amount of the
// 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) {
func selectInputs(amt btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy,
feeRate chainfee.SatPerKWeight) (btcutil.Amount, []wallet.Coin, error) {
// All coin selection code in the btcwallet library requires sat/KB.
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
arrangedCoins, err := strategy.ArrangeCoins(coins, feeSatPerKB)
if err != nil {
return 0, nil, err
}
satSelected := btcutil.Amount(0)
for i, coin := range coins {
for i, coin := range arrangedCoins {
satSelected += btcutil.Amount(coin.Value)
if satSelected >= amt {
return satSelected, coins[:i+1], nil
return satSelected, arrangedCoins[:i+1], nil
}
}
@@ -69,8 +69,9 @@ func selectInputs(amt btcutil.Amount, coins []Coin) (btcutil.Amount, []Coin, err
// calculateFees returns for the specified utxos and fee rate two fee
// estimates, one calculated using a change output and one without. The weight
// added to the estimator from a change output is for a P2WKH output.
func calculateFees(utxos []Coin, feeRate chainfee.SatPerKWeight) (btcutil.Amount,
btcutil.Amount, error) {
func calculateFees(utxos []wallet.Coin,
feeRate chainfee.SatPerKWeight) (btcutil.Amount, btcutil.Amount,
error) {
var weightEstimate input.TxWeightEstimator
for _, utxo := range utxos {
@@ -129,13 +130,17 @@ func sanityCheckFee(totalOut, fee btcutil.Amount) error {
// specified fee rate should be expressed in sat/kw for coin selection to
// function properly.
func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount,
coins []Coin) ([]Coin, btcutil.Amount, error) {
coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy) ([]wallet.Coin, btcutil.Amount,
error) {
amtNeeded := amt
for {
// First perform an initial round of coin selection to estimate
// the required fee.
totalSat, selectedUtxos, err := selectInputs(amtNeeded, coins)
totalSat, selectedUtxos, err := selectInputs(
amtNeeded, coins, strategy, feeRate,
)
if err != nil {
return nil, 0, err
}
@@ -198,12 +203,15 @@ func CoinSelect(feeRate chainfee.SatPerKWeight, amt, dustLimit btcutil.Amount,
// amt in total after fees, adhering to the specified fee rate. The selected
// coins, the final output and change values are returned.
func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
dustLimit btcutil.Amount, coins []Coin) ([]Coin, btcutil.Amount,
dustLimit btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy) ([]wallet.Coin, btcutil.Amount,
btcutil.Amount, error) {
// First perform an initial round of coin selection to estimate
// the required fee.
totalSat, selectedUtxos, err := selectInputs(amt, coins)
totalSat, selectedUtxos, err := selectInputs(
amt, coins, strategy, feeRate,
)
if err != nil {
return nil, 0, 0, err
}
@@ -259,8 +267,9 @@ func CoinSelectSubtractFees(feeRate chainfee.SatPerKWeight, amt,
// available. If insufficient funds are available this method selects all
// available coins.
func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
reserved, dustLimit btcutil.Amount, coins []Coin) ([]Coin,
btcutil.Amount, btcutil.Amount, error) {
reserved, dustLimit btcutil.Amount, coins []wallet.Coin,
strategy wallet.CoinSelectionStrategy) ([]wallet.Coin, btcutil.Amount,
btcutil.Amount, error) {
var (
// selectSubtractFee is tracking if our coin selection was
@@ -280,7 +289,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, 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,
feeRate, maxAmount, dustLimit, coins, strategy,
)
var errInsufficientFunds *ErrInsufficientFunds
@@ -320,6 +329,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
if selectSubtractFee {
selected, outputAmount, changeAmt, err = CoinSelectSubtractFees(
feeRate, totalBalance-reserved, dustLimit, coins,
strategy,
)
if err != nil {
return nil, 0, 0, err
@@ -329,7 +339,7 @@ func CoinSelectUpToAmount(feeRate chainfee.SatPerKWeight, minAmount, maxAmount,
// 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 {
sum := func(coins []wallet.Coin) btcutil.Amount {
var sum btcutil.Amount
for _, coin := range coins {
sum += btcutil.Amount(coin.Value)