lnwallet: use distinct fee rate for funding transaction coin selection

This commit modifies the coin selection logic around selecting inputs
for a funding transaction to query the fee estimator directly (and use
fee-per-byte), rather than use the fee estimate which was passed into
the context.

We also use the value passed into the InitChannelReservation method
directly rather than make a call to the fee estimator. With this
change, the responder to a funding workflow will now properly adopt the
fee-per-kw suggested by the funder of the channel.
This commit is contained in:
Olaoluwa Osuntokun committed 2017-05-16 19:00:27 -07:00
1 parent 222385bf73
commit c324b11b95
1 file changed
+45 -34
+45 -34
View File
@@ -74,31 +74,31 @@ func (e *ErrInsufficientFunds) Error() string {
// //
// TODO(roasbeef): zombie reservation sweeper goroutine. // TODO(roasbeef): zombie reservation sweeper goroutine.
type initFundingReserveMsg struct { type initFundingReserveMsg struct {
// The ID of the remote node we would like to open a channel with. // nodeId is the ID of the remote node we would like to open a channel
// with.
nodeID *btcec.PublicKey nodeID *btcec.PublicKey
// The IP address plus port that we used to either establish or accept // nodeAddr is the IP address plus port that we used to either
// the connection which led to the negotiation of this funding // establish or accept the connection which led to the negotiation of
// workflow. // this funding workflow.
nodeAddr *net.TCPAddr nodeAddr *net.TCPAddr
// The number of confirmations required before the channel is considered // numConfs is the number of confirmations required before the channel
// open. // is considered open.
numConfs uint16 numConfs uint16
// The amount of funds requested for this channel. // fundingAmount is the amount of funds requested for this channel.
fundingAmount btcutil.Amount fundingAmount btcutil.Amount
// The total capacity of the channel which includes the amount of funds // capacity is the total capacity of the channel which includes the
// the remote party contributes (if any). // amount of funds the remote party contributes (if any).
capacity btcutil.Amount capacity btcutil.Amount
// The minimum accepted satoshis/KB fee for the funding transaction. In // feePerKw is the accepted satoshis/Kw fee for the funding
// order to ensure timely confirmation, it is recomened that this fee // transaction. In order to ensure timely confirmation, it is
// should be generous, paying some multiple of the accepted base fee // recommended that this fee should be generous, paying some multiple
// rate of the network. // of the accepted base fee rate of the network.
// TODO(roasbeef): integrate fee estimation project... feePerKw btcutil.Amount
minFeeRate btcutil.Amount
// ourDustLimit is the threshold below which no HTLC output should be // ourDustLimit is the threshold below which no HTLC output should be
// generated for our commitment transaction; ie. HTLCs below // generated for our commitment transaction; ie. HTLCs below
@@ -109,17 +109,21 @@ type initFundingReserveMsg struct {
// responder as part of the initial channel creation. // responder as part of the initial channel creation.
pushSat btcutil.Amount pushSat btcutil.Amount
// The delay on the "pay-to-self" output(s) of the commitment transaction. // csvDelay is the delay on the "pay-to-self" output(s) of the
// commitment transaction.
csvDelay uint32 csvDelay uint32
// A channel in which all errors will be sent accross. Will be nil if // err is a channel in which all errors will be sent across. Will be
// this initial set is succesful. // nil if this initial set is successful.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered. // NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error err chan error
// A ChannelReservation with our contributions filled in will be sent // resp is channel in which a ChannelReservation with our contributions
// accross this channel in the case of a succesfully reservation // filled in will be sent across this channel in the case of a
// initiation. In the case of an error, this will read a nil pointer. // successfully reservation initiation. In the case of an error, this
// will read a nil pointer.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered. // NOTE: In order to avoid deadlocks, this channel MUST be buffered.
resp chan *ChannelReservation resp chan *ChannelReservation
} }
@@ -487,16 +491,15 @@ out:
// commitment transaction is valid. // commitment transaction is valid.
func (l *LightningWallet) InitChannelReservation(capacity, func (l *LightningWallet) InitChannelReservation(capacity,
ourFundAmt btcutil.Amount, theirID *btcec.PublicKey, ourFundAmt btcutil.Amount, theirID *btcec.PublicKey,
theirAddr *net.TCPAddr, numConfs uint16, theirAddr *net.TCPAddr, numConfs uint16, csvDelay uint32,
csvDelay uint32, ourDustLimit btcutil.Amount, ourDustLimit btcutil.Amount, pushSat btcutil.Amount,
pushSat btcutil.Amount) (*ChannelReservation, error) { feePerKw btcutil.Amount) (*ChannelReservation, error) {
// TODO(roasbeef): make the above into an initial config as part of the // TODO(roasbeef): make the above into an initial config as part of the
// refactor to implement spec compliant funding flow // refactor to implement spec compliant funding flow
errChan := make(chan error, 1) errChan := make(chan error, 1)
respChan := make(chan *ChannelReservation, 1) respChan := make(chan *ChannelReservation, 1)
minFeeRate := btcutil.Amount(l.FeeEstimator.EstimateFeePerWeight(1))
l.msgChan <- &initFundingReserveMsg{ l.msgChan <- &initFundingReserveMsg{
capacity: capacity, capacity: capacity,
@@ -504,7 +507,7 @@ func (l *LightningWallet) InitChannelReservation(capacity,
fundingAmount: ourFundAmt, fundingAmount: ourFundAmt,
csvDelay: csvDelay, csvDelay: csvDelay,
ourDustLimit: ourDustLimit, ourDustLimit: ourDustLimit,
minFeeRate: minFeeRate, feePerKw: feePerKw,
pushSat: pushSat, pushSat: pushSat,
nodeID: theirID, nodeID: theirID,
nodeAddr: theirAddr, nodeAddr: theirAddr,
@@ -528,7 +531,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
id := atomic.AddUint64(&l.nextFundingID, 1) id := atomic.AddUint64(&l.nextFundingID, 1)
reservation := NewChannelReservation(req.capacity, req.fundingAmount, reservation := NewChannelReservation(req.capacity, req.fundingAmount,
req.minFeeRate, l, id, req.numConfs, req.pushSat) req.feePerKw, l, id, req.numConfs, req.pushSat)
// Grab the mutex on the ChannelReservation to ensure thread-safety // Grab the mutex on the ChannelReservation to ensure thread-safety
reservation.Lock() reservation.Lock()
@@ -549,10 +552,14 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
// don't need to perform any coin selection. Otherwise, attempt to // don't need to perform any coin selection. Otherwise, attempt to
// obtain enough coins to meet the required funding amount. // obtain enough coins to meet the required funding amount.
if req.fundingAmount != 0 { if req.fundingAmount != 0 {
// TODO(roasbeef): consult model for proper fee rate on funding // Coin selection is done on the basis of sat-per-byte, so
// tx // we'll query the fee estimator for a fee to use to ensure the
err := l.selectCoinsAndChange(uint64(req.minFeeRate), // funding transaction gets into the _next_ block.
req.fundingAmount, ourContribution) //
// TODO(roasbeef): shouldn't be targeting next block
satPerByte := l.FeeEstimator.EstimateFeePerByte(1)
err := l.selectCoinsAndChange(satPerByte, req.fundingAmount,
ourContribution)
if err != nil { if err != nil {
req.err <- err req.err <- err
req.resp <- nil req.resp <- nil
@@ -1212,7 +1219,8 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
// Add the complete funding transaction to the DB, in it's open bucket // Add the complete funding transaction to the DB, in it's open bucket
// which will be used for the lifetime of this channel. // which will be used for the lifetime of this channel.
if err := pendingReservation.partialState.SyncPending(pendingReservation.nodeAddr); err != nil { err = pendingReservation.partialState.SyncPending(pendingReservation.nodeAddr)
if err != nil {
req.err <- err req.err <- err
req.completeChan <- nil req.completeChan <- nil
return return
@@ -1241,9 +1249,12 @@ func (l *LightningWallet) selectCoinsAndChange(feeRate uint64, amt btcutil.Amoun
l.coinSelectMtx.Lock() l.coinSelectMtx.Lock()
defer l.coinSelectMtx.Unlock() defer l.coinSelectMtx.Unlock()
walletLog.Infof("Performing coin selection using %v sat/byte as fee "+
"rate", feeRate)
// Find all unlocked unspent witness outputs with greater than 1 // Find all unlocked unspent witness outputs with greater than 1
// confirmation. // confirmation.
// TODO(roasbeef): make num confs a configuration paramter // TODO(roasbeef): make num confs a configuration parameter
coins, err := l.ListUnspentWitness(1) coins, err := l.ListUnspentWitness(1)
if err != nil { if err != nil {
return err return err
@@ -1442,7 +1453,7 @@ func coinSelect(feeRate uint64, amt btcutil.Amount,
} }
// StaticFeeEstimator will return a static value for all fee calculation // StaticFeeEstimator will return a static value for all fee calculation
// requests. It is designed to be replaced by a proper fee calcuation // requests. It is designed to be replaced by a proper fee calculation
// implementation. // implementation.
type StaticFeeEstimator struct { type StaticFeeEstimator struct {
FeeRate uint64 FeeRate uint64