lnwallet: extract coin selection to distinct method

This is required since for single funder channels, we don’t contribute
any funds so we don’t need to select any change or coins for input into
the funding transaction.
This commit is contained in:
Olaoluwa Osuntokun 2016-06-21 10:33:14 -07:00
parent e62fc414cb
commit d52955b146
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -452,104 +452,21 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
ourContribution.CsvDelay = req.csvDelay
reservation.partialState.LocalCsvDelay = req.csvDelay
// We hold the coin select mutex while querying for outputs, and
// performing coin selection in order to avoid inadvertent double spends
// accross funding transactions.
// NOTE: We don't use defer her so we can properly release the lock
// when we encounter an error condition.
l.coinSelectMtx.Lock()
// TODO(roasbeef): check if balance is insufficient, if so then select
// on two channels, one is a time.After that will bail out with
// insuffcient funds, the other is a notification that the balance has
// been updated make(chan struct{}, 1).
// Find all unlocked unspent witness outputs with greater than 6
// confirmations.
// TODO(roasbeef): make 6 a config paramter?
unspentOutputs, err := l.ListUnspentWitness(6)
if err != nil {
l.coinSelectMtx.Unlock()
req.err <- err
req.resp <- nil
return
}
// Convert the outputs to coins for coin selection below.
coins, err := outputsToCoins(unspentOutputs)
if err != nil {
l.coinSelectMtx.Unlock()
req.err <- err
req.resp <- nil
return
}
// Peform coin selection over our available, unlocked unspent outputs
// in order to find enough coins to meet the funding amount requirements.
//
// TODO(roasbeef): Should extend coinset with optimal coin selection
// heuristics for our use case.
// TODO(roasbeef): factor in fees..
// TODO(roasbeef): possibly integrate the fee prediction project? if
// results hold up...
// NOTE: this current selection assumes "priority" is still a thing.
selector := &coinset.MaxValueAgeCoinSelector{
MaxInputs: 10,
MinChangeAmount: 10000,
}
selectedCoins, err := selector.CoinSelect(req.fundingAmount, coins)
if err != nil {
l.coinSelectMtx.Unlock()
req.err <- err
req.resp <- nil
return
}
// Lock the selected coins. These coins are now "reserved", this
// prevents concurrent funding requests from referring to and this
// double-spending the same set of coins.
ourContribution.Inputs = make([]*wire.TxIn, len(selectedCoins.Coins()))
for i, coin := range selectedCoins.Coins() {
txout := wire.NewOutPoint(coin.Hash(), coin.Index())
l.LockOutpoint(*txout)
// Empty sig script, we'll actually sign if this reservation is
// queued up to be completed (the other side accepts).
outPoint := wire.NewOutPoint(coin.Hash(), coin.Index())
ourContribution.Inputs[i] = wire.NewTxIn(outPoint, nil, nil)
}
l.coinSelectMtx.Unlock()
// Create some possibly neccessary change outputs.
selectedTotalValue := coinset.NewCoinSet(selectedCoins.Coins()).TotalValue()
if selectedTotalValue > req.fundingAmount {
ourContribution.ChangeOutputs = make([]*wire.TxOut, 1)
// Change is necessary. Query for an available change address to
// send the remainder to.
changeAddr, err := l.NewChangeAddress(waddrmgr.DefaultAccountNum,
waddrmgr.WitnessPubKey)
if err != nil {
// If we're on the receiving end of a single funder channel then we
// don't need to perform any coin selection. Otherwise, attempt to
// obtain enough coins to meet the required funding amount.
if req.fundingAmount != 0 {
if err := l.selectCoinsAndChange(req.fundingAmount, ourContribution); err != nil {
req.err <- err
req.resp <- nil
return
}
changeAddrScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
req.err <- err
req.resp <- nil
return
}
changeAmount := selectedTotalValue - req.fundingAmount
ourContribution.ChangeOutputs[0] = wire.NewTxOut(int64(changeAmount),
changeAddrScript)
}
// TODO(roasbeef): re-calculate fees here to minFeePerKB, may need more inputs
// Grab two fresh keys from out HD chain, one will be used for the
// Grab two fresh keys from our HD chain, one will be used for the
// multi-sig funding transaction, and the other for the commitment
// transaction.
multiSigKey, err := l.getNextRawKey()
@ -1094,6 +1011,103 @@ func (l *LightningWallet) ListUnspentWitness(minConfs int32) ([]*btcjson.ListUns
return witnessOutputs, nil
}
// selectCoinsAndChange performs coin selection in order to obtain witness
// outputs which sum to at least 'numCoins' amount of satoshis. If coin
// selection is succesful/possible, then the selected coins are available within
// the passed contribution's inputs. If necessary, a change address will also be
// generated.
// TODO(roasbeef): remove hardcoded fees and req'd confs for outputs.
func (l *LightningWallet) selectCoinsAndChange(numCoins btcutil.Amount,
contribution *ChannelContribution) error {
// We hold the coin select mutex while querying for outputs, and
// performing coin selection in order to avoid inadvertent double spends
// accross funding transactions.
// NOTE: We don't use defer her so we can properly release the lock
// when we encounter an error condition.
l.coinSelectMtx.Lock()
// TODO(roasbeef): check if balance is insufficient, if so then select
// on two channels, one is a time.After that will bail out with
// insuffcient funds, the other is a notification that the balance has
// been updated make(chan struct{}, 1).
// Find all unlocked unspent witness outputs with greater than 1
// confirmation.
// TODO(roasbeef): make num confs a configuration paramter
unspentOutputs, err := l.ListUnspentWitness(1)
if err != nil {
l.coinSelectMtx.Unlock()
return err
}
// Convert the outputs to coins for coin selection below.
coins, err := outputsToCoins(unspentOutputs)
if err != nil {
l.coinSelectMtx.Unlock()
return err
}
// Peform coin selection over our available, unlocked unspent outputs
// in order to find enough coins to meet the funding amount requirements.
//
// TODO(roasbeef): Should extend coinset with optimal coin selection
// heuristics for our use case.
// NOTE: this current selection assumes "priority" is still a thing.
selector := &coinset.MaxValueAgeCoinSelector{
MaxInputs: 10,
MinChangeAmount: 10000,
}
// TODO(roasbeef): don't hardcode fee...
totalWithFee := numCoins + 10000
selectedCoins, err := selector.CoinSelect(totalWithFee, coins)
if err != nil {
l.coinSelectMtx.Unlock()
return err
}
// Lock the selected coins. These coins are now "reserved", this
// prevents concurrent funding requests from referring to and this
// double-spending the same set of coins.
contribution.Inputs = make([]*wire.TxIn, len(selectedCoins.Coins()))
for i, coin := range selectedCoins.Coins() {
txout := wire.NewOutPoint(coin.Hash(), coin.Index())
l.LockOutpoint(*txout)
// Empty sig script, we'll actually sign if this reservation is
// queued up to be completed (the other side accepts).
outPoint := wire.NewOutPoint(coin.Hash(), coin.Index())
contribution.Inputs[i] = wire.NewTxIn(outPoint, nil, nil)
}
l.coinSelectMtx.Unlock()
// Create some possibly neccessary change outputs.
selectedTotalValue := coinset.NewCoinSet(selectedCoins.Coins()).TotalValue()
if selectedTotalValue > totalWithFee {
// Change is necessary. Query for an available change address to
// send the remainder to.
contribution.ChangeOutputs = make([]*wire.TxOut, 1)
changeAddr, err := l.NewChangeAddress(waddrmgr.DefaultAccountNum,
waddrmgr.WitnessPubKey)
if err != nil {
return err
}
changeAddrScript, err := txscript.PayToAddrScript(changeAddr)
if err != nil {
return err
}
changeAmount := selectedTotalValue - totalWithFee
contribution.ChangeOutputs[0] = wire.NewTxOut(int64(changeAmount),
changeAddrScript)
}
// TODO(roasbeef): re-calculate fees here to minFeePerKB, may need more inputs
return nil
}
type WaddrmgrEncryptorDecryptor struct {
M *waddrmgr.Manager
}