Skip to content

Commit

Permalink
lnwallet: update channel close to use fee estimation interface
Browse files Browse the repository at this point in the history
This commit switches the channel close workflow to use the lnwallet fee
estimation interface rather than the hardcoded proposedFee.
  • Loading branch information
bryanvu authored and Roasbeef committed May 23, 2017
1 parent 514760f commit 408be35
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 52 deletions.
54 changes: 34 additions & 20 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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
Expand Down
43 changes: 22 additions & 21 deletions lnwallet/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
31 changes: 20 additions & 11 deletions peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 408be35

Please sign in to comment.