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,
// settle any inflight.
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, error) {
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, uint64,
error) {
lc.Lock()
defer lc.Unlock()
// If we're already closing the channel, then ignore this request.
if lc.status == channelClosing || lc.status == channelClosed {
// 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.
// For a cooperative close, there should be no HTLCs.
commitFee := (lc.channelState.FeePerKw * commitWeight) / 1000
// 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 {
lc.channelState.OurBalance = lc.channelState.OurBalance - commitFee
ourBalance = ourBalance - btcutil.Amount(proposedFee)
} else {
lc.channelState.TheirBalance = lc.channelState.TheirBalance - commitFee
theirBalance = theirBalance - btcutil.Amount(proposedFee)
}
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript,
lc.channelState.TheirDeliveryScript,
lc.channelState.IsInitiator)
ourBalance, theirBalance, lc.channelState.OurDeliveryScript,
lc.channelState.TheirDeliveryScript, lc.channelState.IsInitiator)
// Ensure that the transaction doesn't explicitly violate any
// consensus rules such as being too big, or having any value with a
// negative output.
tx := btcutil.NewTx(closeTx)
if err := blockchain.CheckTransactionSanity(tx); err != nil {
return nil, err
return nil, 0, err
}
// 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)
sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, err
return nil, 0, err
}
// As everything checks out, indicate in the channel status that a
// channel closure has been initiated.
lc.status = channelClosing
return sig, nil
return sig, proposedFee, nil
}
// 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
// signatures including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(localSig,
remoteSig []byte, proposedFee uint64) (*wire.MsgTx, error) {
func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte,
feeRate uint64) (*wire.MsgTx, error) {
lc.Lock()
defer lc.Unlock()
@ -2601,15 +2604,26 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig,
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
// on this active channel back to both parties. In this current model,
// the initiator pays full fees for the cooperative close transaction.
closeTx := CreateCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurDustLimit, lc.channelState.TheirDustLimit,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript,
lc.channelState.TheirDeliveryScript,
lc.channelState.IsInitiator)
ourBalance, theirBalance, lc.channelState.OurDeliveryScript,
lc.channelState.TheirDeliveryScript, lc.channelState.IsInitiator)
// Ensure that the transaction doesn't explicitly validate any
// consensus rules such as being too big, or having any value with a

@ -53,12 +53,6 @@ var (
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 {
key *btcec.PrivateKey
}
@ -250,7 +244,8 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
var obsfucator [StateHintSize]byte
copy(obsfucator[:], aliceFirstRevoke[:])
feePerKw := btcutil.Amount(6000)
estimator := &StaticFeeEstimator{24, 6}
feePerKw := btcutil.Amount(estimator.EstimateFeePerWeight(1) * 1000)
aliceChannelState := &channeldb.OpenChannel{
IdentityPub: aliceKeyPub,
ChanID: prevOut,
@ -315,7 +310,6 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan
bobSigner := &mockSigner{bobKeyPriv}
notifier := &mockNotfier{}
estimator := &StaticFeeEstimator{24, 6}
channelAlice, err := NewLightningChannel(aliceSigner, notifier,
estimator, aliceChannelState)
@ -711,28 +705,32 @@ func TestCooperativeChannelClosure(t *testing.T) {
}
defer cleanUp()
aliceFeeRate := uint64(aliceChannel.channelState.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.FeePerKw)
// First we test creating of cooperative close proposals.
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee)
aliceSig, _, err := aliceChannel.CreateCloseProposal(
aliceFeeRate)
if err != nil {
t.Fatalf("unable to create alice coop close proposal: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee)
bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil {
t.Fatalf("unable to create bob coop close proposal: %v", err)
}
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
aliceCloseTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
aliceCloseSig, bobFeeRate)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
bobCloseSha := aliceCloseTx.TxHash()
bobCloseTx, err := aliceChannel.CompleteCooperativeClose(aliceCloseSig,
bobCloseSig, proposedFee)
bobCloseSig, aliceFeeRate)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}
@ -1555,6 +1553,9 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
}
defer cleanUp()
aliceFeeRate := uint64(aliceChannel.channelState.FeePerKw)
bobFeeRate := uint64(bobChannel.channelState.FeePerKw)
setDustLimit := func(dustVal btcutil.Amount) {
aliceChannel.channelState.OurDustLimit = 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
// balances. As a result, performing a cooperative closure now result
// in both sides having an output within the closure transaction.
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee)
aliceSig, _, err := aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee)
bobSig, _, err := bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
aliceCloseSig, bobFeeRate)
if err != nil {
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
// without any issues.
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee)
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee)
bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
aliceCloseSig, bobFeeRate)
if err != nil {
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
// succeed without any issues.
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee)
aliceSig, _, err = aliceChannel.CreateCloseProposal(aliceFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee)
bobSig, _, err = bobChannel.CreateCloseProposal(bobFeeRate)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
aliceCloseSig, bobFeeRate)
if err != nil {
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
// this struct.
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
@ -938,18 +934,26 @@ func (p *peer) handleInitClosingSigned(req *closeLinkReq, msg *lnwire.ClosingSig
channel := p.activeChannels[chanID]
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 {
req.err <- err
return
}
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
respSig := append(responderSig.Serialize(), byte(txscript.SigHashAll))
closeTx, err := channel.CompleteCooperativeClose(initSig, respSig,
proposedFee)
feeRate)
if err != nil {
req.err <- err
// 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))
chanPoint := channel.ChannelPoint()
// Calculate our expected fee rate.
feeRate := p.server.feeEstimator.EstimateFeePerWeight(1) * 1000
closeTx, err := channel.CompleteCooperativeClose(respSig, initSig,
proposedFee)
feeRate)
if err != nil {
peerLog.Errorf("unable to complete cooperative "+
"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
// close transactions.
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.
closeSig, err := channel.CreateCloseProposal(proposedFee)
// Calculate an initial proposed fee rate for the close transaction.
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 {
return nil, err
}