chanfunding: adds ability to fund a selected set of coins
This commit is contained in:
2 files changed
+106
-2
No files matched your search
@@ -84,6 +84,13 @@ type Request struct {
|
|||||||
// e.g. anchor channels.
|
// e.g. anchor channels.
|
||||||
WalletReserve btcutil.Amount
|
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
|
// 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.
|
||||||
|
|||||||
@@ -260,23 +260,64 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
log.Infof("Performing funding tx coin selection using %v "+
|
log.Infof("Performing funding tx coin selection using %v "+
|
||||||
"sat/kw as fee rate", int64(r.FeeRate))
|
"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
|
// Find all unlocked unspent witness outputs that satisfy the
|
||||||
// minimum number of confirmations required. Coin selection in
|
// minimum number of confirmations required. Coin selection in
|
||||||
// this function currently ignores the configured coin selection
|
// this function currently ignores the configured coin selection
|
||||||
// strategy.
|
// strategy.
|
||||||
coins, err := w.cfg.CoinSource.ListCoins(
|
allCoins, err = w.cfg.CoinSource.ListCoins(
|
||||||
r.MinConfs, math.MaxInt32,
|
r.MinConfs, math.MaxInt32,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 (
|
var (
|
||||||
|
coins []Coin
|
||||||
selectedCoins []Coin
|
selectedCoins []Coin
|
||||||
localContributionAmt btcutil.Amount
|
localContributionAmt btcutil.Amount
|
||||||
changeAmt 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
|
// Perform coin selection over our available, unlocked unspent
|
||||||
// outputs in order to find enough coins to meet the funding
|
// outputs in order to find enough coins to meet the funding
|
||||||
// amount requirements.
|
// amount requirements.
|
||||||
@@ -305,10 +346,45 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
// we will call the specialized coin selection function for
|
// we will call the specialized coin selection function for
|
||||||
// that.
|
// that.
|
||||||
case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
|
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,
|
selectedCoins, localContributionAmt, changeAmt,
|
||||||
err = CoinSelectUpToAmount(
|
err = CoinSelectUpToAmount(
|
||||||
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
|
r.FeeRate, r.MinFundAmt, r.FundUpToMaxAmt,
|
||||||
r.WalletReserve, w.cfg.DustLimit, coins,
|
reserve, w.cfg.DustLimit, coins,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -422,6 +498,27 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
|
|||||||
return intent, nil
|
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
|
// FundingTxAvailable is an empty method that an assembler can implement to
|
||||||
// signal to callers that its able to provide the funding transaction for the
|
// signal to callers that its able to provide the funding transaction for the
|
||||||
// channel via the intent it returns.
|
// channel via the intent it returns.
|
||||||
|
|||||||
Reference in new issue
Block a user