lnwallet: update channel close to use fee estimation interface

This commit switches the channel close workflow to use the lnwallet fee
estimation interface rather than the hardcoded proposedFee.
This commit is contained in:
bryanvu 2017-05-17 16:51:10 -07:00 committed by Olaoluwa Osuntokun
parent 514760f529
commit 408be356fb
3 changed files with 76 additions and 52 deletions

@ -2532,39 +2532,42 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
// //
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs, // TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
// settle any inflight. // settle any inflight.
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, error) { func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, uint64,
error) {
lc.Lock() lc.Lock()
defer lc.Unlock() defer lc.Unlock()
// If we're already closing the channel, then ignore this request. // If we're already closing the channel, then ignore this request.
if lc.status == channelClosing || lc.status == channelClosed { if lc.status == channelClosing || lc.status == channelClosed {
// TODO(roasbeef): check to ensure no pending payments // TODO(roasbeef): check to ensure no pending payments
return nil, ErrChanClosing return nil, 0, ErrChanClosing
} }
// Calculate the fee for the commitment transaction based on its size. // Subtract the proposed fee from the appropriate balance, taking care
// For a cooperative close, there should be no HTLCs. // not to persist the adjusted balance, as the feeRate may change
commitFee := (lc.channelState.FeePerKw * commitWeight) / 1000 // during the channel closing process.
proposedFee := uint64(btcutil.Amount(feeRate) * commitWeight / 1000)
ourBalance := lc.channelState.OurBalance
theirBalance := lc.channelState.TheirBalance
if lc.channelState.IsInitiator { if lc.channelState.IsInitiator {
lc.channelState.OurBalance = lc.channelState.OurBalance - commitFee ourBalance = ourBalance - btcutil.Amount(proposedFee)
} else { } else {
lc.channelState.TheirBalance = lc.channelState.TheirBalance - commitFee theirBalance = theirBalance - btcutil.Amount(proposedFee)
} }
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn, closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit, lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit,
lc.channelState.OurBalance, lc.channelState.TheirBalance, ourBalance, theirBalance, lc.channelState.OurDeliveryScript,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript, lc.channelState.IsInitiator)
lc.channelState.TheirDeliveryScript,
lc.channelState.IsInitiator)
// Ensure that the transaction doesn't explicitly violate any // Ensure that the transaction doesn't explicitly violate any
// consensus rules such as being too big, or having any value with a // consensus rules such as being too big, or having any value with a
// negative output. // negative output.
tx := btcutil.NewTx(closeTx) tx := btcutil.NewTx(closeTx)
if err := blockchain.CheckTransactionSanity(tx); err != nil { if err := blockchain.CheckTransactionSanity(tx); err != nil {
return nil, err return nil, 0, err
} }
// Finally, sign the completed cooperative closure transaction. As the // Finally, sign the completed cooperative closure transaction. As the
@ -2574,14 +2577,14 @@ func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, error)
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx) lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc) sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
// As everything checks out, indicate in the channel status that a // As everything checks out, indicate in the channel status that a
// channel closure has been initiated. // channel closure has been initiated.
lc.status = channelClosing lc.status = channelClosing
return sig, nil return sig, proposedFee, nil
} }
// CompleteCooperativeClose completes the cooperative closure of the target // CompleteCooperativeClose completes the cooperative closure of the target
@ -2590,8 +2593,8 @@ func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, error)
// //
// NOTE: The passed local and remote sigs are expected to be fully complete // NOTE: The passed local and remote sigs are expected to be fully complete
// signatures including the proper sighash byte. // signatures including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(localSig, func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte,
remoteSig []byte, proposedFee uint64) (*wire.MsgTx, error) { feeRate uint64) (*wire.MsgTx, error) {
lc.Lock() lc.Lock()
defer lc.Unlock() defer lc.Unlock()
@ -2601,15 +2604,26 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig,
return nil, ErrChanClosing return nil, ErrChanClosing
} }
// Subtract the proposed fee from the appropriate balance, taking care
// not to persist the adjusted balance, as the feeRate may change
// during the channel closing process.
proposedFee := uint64(btcutil.Amount(feeRate) * commitWeight / 1000)
ourBalance := lc.channelState.OurBalance
theirBalance := lc.channelState.TheirBalance
if lc.channelState.IsInitiator {
ourBalance = ourBalance - btcutil.Amount(proposedFee)
} else {
theirBalance = theirBalance - btcutil.Amount(proposedFee)
}
// Create the transaction used to return the current settled balance // Create the transaction used to return the current settled balance
// on this active channel back to both parties. In this current model, // on this active channel back to both parties. In this current model,
// the initiator pays full fees for the cooperative close transaction. // the initiator pays full fees for the cooperative close transaction.
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn, closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit, lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit,
lc.channelState.OurBalance, lc.channelState.TheirBalance, ourBalance, theirBalance, lc.channelState.OurDeliveryScript,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript, lc.channelState.IsInitiator)
lc.channelState.TheirDeliveryScript,
lc.channelState.IsInitiator)
// Ensure that the transaction doesn't explicitly validate any // Ensure that the transaction doesn't explicitly validate any
// consensus rules such as being too big, or having any value with a // consensus rules such as being too big, or having any value with a

@ -53,12 +53,6 @@ var (
numReqConfs = uint16(1) numReqConfs = uint16(1)
) )
const (
// proposedFee is a hard-coded fee value, intended to be replaced by a
// more robust fee estimation implemention.
proposedFee = 5000
)
type mockSigner struct { type mockSigner struct {
key *btcec.PrivateKey key *btcec.PrivateKey
} }
@ -250,7 +244,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
var obsfucator [StateHintSize]byte var obsfucator [StateHintSize]byte
copy(obsfucator[:], aliceFirstRevoke[:]) copy(obsfucator[:], aliceFirstRevoke[:])
feePerKw := btcutil.Amount(6000) estimator := &StaticFeeEstimator{24, 6}
feePerKw := btcutil.Amount(estimator.EstimateFeePerWeight(1) * 1000)
aliceChannelState := &channeldb.OpenChannel{ aliceChannelState := &channeldb.OpenChannel{
IdentityPub: aliceKeyPub, IdentityPub: aliceKeyPub,
ChanID: prevOut, ChanID: prevOut,
@ -315,7 +310,6 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
bobSigner := &mockSigner{bobKeyPriv} bobSigner := &mockSigner{bobKeyPriv}
notifier := &mockNotfier{} notifier := &mockNotfier{}
estimator := &StaticFeeEstimator{24, 6}
channelAlice, err := NewLightningChannel(aliceSigner, notifier, channelAlice, err := NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState) estimator, aliceChannelState)
@ -711,28 +705,32 @@ func TestCooperativeChannelClosure(t *testing.T) {
} }
defer cleanUp() defer cleanUp()
aliceFeeRate := uint64(aliceChannel.channelState.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.FeePerKw)
// First we test creating of cooperative close proposals. // First we test creating of cooperative close proposals.
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee) aliceSig, _, err := aliceChannel.CreateCloseProposal(
aliceFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to create alice coop close proposal: %v", err) t.Fatalf("unable to create alice coop close proposal: %v", err)
} }
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee) bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to create bob coop close proposal: %v", err) t.Fatalf("unable to create bob coop close proposal: %v", err)
} }
bobCloseSig := append(bobSig, byte(txscript.SigHashAll)) bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
aliceCloseTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig, aliceCloseTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee) aliceCloseSig, bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err) t.Fatalf("unable to complete alice cooperative close: %v", err)
} }
bobCloseSha := aliceCloseTx.TxHash() bobCloseSha := aliceCloseTx.TxHash()
bobCloseTx, err := aliceChannel.CompleteCooperativeClose(aliceCloseSig, bobCloseTx, err := aliceChannel.CompleteCooperativeClose(aliceCloseSig,
bobCloseSig, proposedFee) bobCloseSig, aliceFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err) t.Fatalf("unable to complete bob cooperative close: %v", err)
} }
@ -1555,6 +1553,9 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
} }
defer cleanUp() defer cleanUp()
aliceFeeRate := uint64(aliceChannel.channelState.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.FeePerKw)
setDustLimit := func(dustVal btcutil.Amount) { setDustLimit := func(dustVal btcutil.Amount) {
aliceChannel.channelState.OurDustLimit = dustVal aliceChannel.channelState.OurDustLimit = dustVal
aliceChannel.channelState.TheirDustLimit = dustVal aliceChannel.channelState.TheirDustLimit = dustVal
@ -1582,20 +1583,20 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Both sides currently have over 1 BTC settled as part of their // Both sides currently have over 1 BTC settled as part of their
// balances. As a result, performing a cooperative closure now result // balances. As a result, performing a cooperative closure now result
// in both sides having an output within the closure transaction. // in both sides having an output within the closure transaction.
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee) aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee) bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
bobCloseSig := append(bobSig, byte(txscript.SigHashAll)) bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig, closeTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee) aliceCloseSig, bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to accept channel close: %v", err) t.Fatalf("unable to accept channel close: %v", err)
} }
@ -1617,20 +1618,20 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Attempt another cooperative channel closure. It should succeed // Attempt another cooperative channel closure. It should succeed
// without any issues. // without any issues.
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee) aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee) bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
bobCloseSig = append(bobSig, byte(txscript.SigHashAll)) bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig, closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee) aliceCloseSig, bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to accept channel close: %v", err) t.Fatalf("unable to accept channel close: %v", err)
} }
@ -1653,20 +1654,20 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Our final attempt at another cooperative channel closure. It should // Our final attempt at another cooperative channel closure. It should
// succeed without any issues. // succeed without any issues.
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee) aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee) bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to close channel: %v", err) t.Fatalf("unable to close channel: %v", err)
} }
bobCloseSig = append(bobSig, byte(txscript.SigHashAll)) bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig, closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee) aliceCloseSig, bobFeeRate)
if err != nil { if err != nil {
t.Fatalf("unable to accept channel close: %v", err) t.Fatalf("unable to accept channel close: %v", err)
} }

31
peer.go

@ -39,10 +39,6 @@ const (
// messages to be sent across the wire, requested by objects outside // messages to be sent across the wire, requested by objects outside
// this struct. // this struct.
outgoingQueueLen = 50 outgoingQueueLen = 50
// proposedFee is a hard-coded fee value, intended to be replaced by a
// more robust fee estimation implemention.
proposedFee = 5000
) )
// outgoinMsg packages an lnwire.Message to be sent out on the wire, along with // outgoinMsg packages an lnwire.Message to be sent out on the wire, along with
@ -938,18 +934,26 @@ func (p *peer) handleInitClosingSigned(req *closeLinkReq, msg *lnwire.ClosingSig
channel := p.activeChannels[chanID] channel := p.activeChannels[chanID]
p.activeChanMtx.RUnlock() p.activeChanMtx.RUnlock()
initiatorSig, err := channel.CreateCloseProposal(proposedFee) // Calculate a fee rate that we believe to be fair.
// TODO(bvu): with a dynamic fee implementation, we will compare this to
// the fee proposed by the responder in their ClosingSigned message.
feeRate := p.server.feeEstimator.EstimateFeePerWeight(1) * 1000
// We agree with the proposed channel close transaction and fee rate,
// so generate our signature.
initiatorSig, proposedFee, err := channel.CreateCloseProposal(feeRate)
if err != nil { if err != nil {
req.err <- err req.err <- err
return return
} }
initSig := append(initiatorSig, byte(txscript.SigHashAll)) initSig := append(initiatorSig, byte(txscript.SigHashAll))
// Complete coop close transaction. // Complete coop close transaction with the signatures of the close
// initiator and responder.
responderSig := msg.Signature responderSig := msg.Signature
respSig := append(responderSig.Serialize(), byte(txscript.SigHashAll)) respSig := append(responderSig.Serialize(), byte(txscript.SigHashAll))
closeTx, err := channel.CompleteCooperativeClose(initSig, respSig, closeTx, err := channel.CompleteCooperativeClose(initSig, respSig,
proposedFee) feeRate)
if err != nil { if err != nil {
req.err <- err req.err <- err
// TODO(roasbeef): send ErrorGeneric to other side // TODO(roasbeef): send ErrorGeneric to other side
@ -1063,8 +1067,10 @@ func (p *peer) handleResponseClosingSigned(msg *lnwire.ClosingSigned,
initSig := append(initiatorSig.Serialize(), byte(txscript.SigHashAll)) initSig := append(initiatorSig.Serialize(), byte(txscript.SigHashAll))
chanPoint := channel.ChannelPoint() chanPoint := channel.ChannelPoint()
// Calculate our expected fee rate.
feeRate := p.server.feeEstimator.EstimateFeePerWeight(1) * 1000
closeTx, err := channel.CompleteCooperativeClose(respSig, initSig, closeTx, err := channel.CompleteCooperativeClose(respSig, initSig,
proposedFee) feeRate)
if err != nil { if err != nil {
peerLog.Errorf("unable to complete cooperative "+ peerLog.Errorf("unable to complete cooperative "+
"close for ChannelPoint(%v): %v", "close for ChannelPoint(%v): %v",
@ -1195,9 +1201,12 @@ func (p *peer) sendShutdown(channel *lnwallet.LightningChannel) error {
// sendClosingSigned handles the creation and sending of proposed channel // sendClosingSigned handles the creation and sending of proposed channel
// close transactions. // close transactions.
func (p *peer) sendClosingSigned(channel *lnwallet.LightningChannel) ([]byte, error) { func (p *peer) sendClosingSigned(channel *lnwallet.LightningChannel) ([]byte, error) {
// We agree with the proposed fee, so we send back our signature
// for the proposed transaction. // Calculate an initial proposed fee rate for the close transaction.
closeSig, err := channel.CreateCloseProposal(proposedFee) feeRate := p.server.feeEstimator.EstimateFeePerWeight(1) * 1000
// Create a proposed channel close transaction with our fee proposal.
closeSig, proposedFee, err := channel.CreateCloseProposal(feeRate)
if err != nil { if err != nil {
return nil, err return nil, err
} }