chanfunding: adds ability to fund a selected set of coins

This commit is contained in:
Slyghtning 2023-06-10 21:26:50 +02:00
parent c3cd93c98a
commit ac523f93ee
No known key found for this signature in database
GPG Key ID: F82D456EA023C9BF
2 changed files with 106 additions and 2 deletions

View File

@ -84,6 +84,13 @@ type Request struct {
// e.g. anchor channels.
WalletReserve btcutil.Amount
// Outpoints is a list of client-selected outpoints that should be used
// for funding a channel. If LocalAmt is specified then this amount is
// allocated from the sum of outpoints towards funding. If the
// FundUpToMaxAmt is specified the entirety of selected funds is
// allocated towards channel funding.
Outpoints []wire.OutPoint
// 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
// set to zero, then zero conf outputs may be spent.

View File

@ -260,23 +260,64 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
log.Infof("Performing funding tx coin selection using %v "+
"sat/kw as fee rate", int64(r.FeeRate))
var (
// allCoins refers to the entirety of coins in our
// wallet that are available for funding a channel.
allCoins []Coin
// manuallySelectedCoins refers to the client-side
// selected coins that should be considered available
// for funding a channel.
manuallySelectedCoins []Coin
err error
)
// Convert manually selected outpoints to coins.
manuallySelectedCoins, err = outpointsToCoins(
r.Outpoints, w.cfg.CoinSource.CoinFromOutPoint,
)
if err != nil {
return err
}
// Find all unlocked unspent witness outputs that satisfy the
// minimum number of confirmations required. Coin selection in
// this function currently ignores the configured coin selection
// strategy.
coins, err := w.cfg.CoinSource.ListCoins(
allCoins, err = w.cfg.CoinSource.ListCoins(
r.MinConfs, math.MaxInt32,
)
if err != nil {
return err
}
// Ensure that all manually selected coins remain unspent.
unspent := make(map[wire.OutPoint]struct{})
for _, coin := range allCoins {
unspent[coin.OutPoint] = struct{}{}
}
for _, coin := range manuallySelectedCoins {
if _, ok := unspent[coin.OutPoint]; !ok {
return fmt.Errorf("outpoint already spent: %v",
coin.OutPoint)
}
}
var (
coins []Coin
selectedCoins []Coin
localContributionAmt btcutil.Amount
changeAmt btcutil.Amount
)
// If outputs were specified manually then we'll take the
// corresponding coins as basis for coin selection. Otherwise,
// all available coins from our wallet are used.
coins = allCoins
if len(manuallySelectedCoins) > 0 {
coins = manuallySelectedCoins
}
// Perform coin selection over our available, unlocked unspent
// outputs in order to find enough coins to meet the funding
// amount requirements.
@ -305,10 +346,45 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
// we will call the specialized coin selection function for
// that.
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
// We need to ensure that manually selected coins, which
// are spent entirely on the channel funding, leave
// enough funds in the wallet to cover for a reserve.
reserve := r.WalletReserve
if len(manuallySelectedCoins) > 0 {
sumCoins := func(coins []Coin) btcutil.Amount {
var sum btcutil.Amount
for _, coin := range coins {
sum += btcutil.Amount(
coin.Value,
)
}
return sum
}
sumManual := sumCoins(manuallySelectedCoins)
sumAll := sumCoins(allCoins)
// If sufficient reserve funds are available we
// don't have to provide for it during coin
// selection. The manually selected coins can be
// spent entirely on the channel funding. If
// the excess of coins cover the reserve
// partially then we have to provide for the
// rest during coin selection.
excess := sumAll - sumManual
if excess >= reserve {
reserve = 0
} else {
reserve -= excess
}
}
selectedCoins, localContributionAmt, changeAmt,
err = CoinSelectUpToAmount(
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
r.WalletReserve, w.cfg.DustLimit, coins,
reserve, w.cfg.DustLimit, coins,
)
if err != nil {
return err
@ -422,6 +498,27 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
return intent, nil
}
// outpointsToCoins maps outpoints to coins in our wallet iff these coins are
// existent and returns an error otherwise.
func outpointsToCoins(outpoints []wire.OutPoint,
coinFromOutPoint func(wire.OutPoint) (*Coin, error)) ([]Coin, error) {
var selectedCoins []Coin
for _, outpoint := range outpoints {
coin, err := coinFromOutPoint(
outpoint,
)
if err != nil {
return nil, err
}
selectedCoins = append(
selectedCoins, *coin,
)
}
return selectedCoins, nil
}
// FundingTxAvailable is an empty method that an assembler can implement to
// signal to callers that its able to provide the funding transaction for the
// channel via the intent it returns.