peer: handling for channel shutdown messages

This commit changes the cooperative channel close workflow to comply
with the latest spec. This adds steps to handle and send shutdown
messages as well as moving responsibility for sending the channel close
message from the initiator to the responder.
This commit is contained in:
bryanvu authored and Olaoluwa Osuntokun committed 2017-05-23 14:31:20 -07:00
1 parent d7bb600c23
commit 514760f529
5 files changed
+336 -215

No files matched your search

+61 -85
View File
@@ -2522,81 +2522,17 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
}, nil
}
// InitCooperativeClose initiates a cooperative closure of an active lightning
// channel. This method should only be executed once all pending HTLCs (if any)
// on the channel have been cleared/removed. Upon completion, the source
// channel will shift into the "closing" state, which indicates that all
// incoming/outgoing HTLC requests should be rejected. A signature for the
// closing transaction, and the txid of the closing transaction are returned.
// The initiator of the channel closure should then watch the blockchain for a
// confirmation of the closing transaction before considering the channel
// terminated. In the case of an unresponsive remote party, the initiator can
// either choose to execute a force closure, or backoff for a period of time,
// and retry the cooperative closure.
// CreateCloseProposal is used by both parties in a cooperative channel close
// workflow to generate proposed close transactions and signatures. This method
// should only be executed once all pending HTLCs (if any) on the channel have
// been cleared/removed. Upon completion, the source channel will shift into
// the "closing" state, which indicates that all incoming/outgoing HTLC
// requests should be rejected. A signature for the closing transaction is
// returned.
//
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
// settle any inflight.
func (lc *LightningChannel) InitCooperativeClose() ([]byte, *chainhash.Hash, 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, nil, 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
if lc.channelState.IsInitiator {
lc.channelState.OurBalance = lc.channelState.OurBalance - commitFee
} else {
lc.channelState.TheirBalance = lc.channelState.TheirBalance - commitFee
}
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)
// 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, nil, err
}
// Finally, sign the completed cooperative closure transaction. As the
// initiator we'll simply send our signature over to the remote party,
// using the generated txid to be notified once the closure transaction
// has been confirmed.
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, nil, err
}
// As everything checks out, indicate in the channel status that a
// channel closure has been initiated.
lc.status = channelClosing
closeTxSha := closeTx.TxHash()
return closeSig, &closeTxSha, nil
}
// CompleteCooperativeClose completes the cooperative closure of the target
// active lightning channel. This method should be called in response to the
// remote node initiating a cooperative channel closure. A fully signed closure
// transaction is returned. It is the duty of the responding node to broadcast
// a signed+valid closure transaction to the network.
//
// NOTE: The passed remote sig is expected to be a fully complete signature
// including the proper sighash byte.
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
func (lc *LightningChannel) CreateCloseProposal(feeRate uint64) ([]byte, error) {
lc.Lock()
defer lc.Unlock()
@@ -2608,7 +2544,6 @@ func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.Ms
// Calculate the fee for the commitment transaction based on its size.
// For a cooperative close, there should be no HTLCs.
// TODO(roasbeef): should be using fee passed in
commitFee := (lc.channelState.FeePerKw * commitWeight) / 1000
if lc.channelState.IsInitiator {
@@ -2617,13 +2552,63 @@ func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.Ms
lc.channelState.TheirBalance = lc.channelState.TheirBalance - commitFee
}
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)
// 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
}
// Finally, sign the completed cooperative closure transaction. As the
// initiator we'll simply send our signature over to the remote party,
// using the generated txid to be notified once the closure transaction
// has been confirmed.
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, err
}
// As everything checks out, indicate in the channel status that a
// channel closure has been initiated.
lc.status = channelClosing
return sig, nil
}
// CompleteCooperativeClose completes the cooperative closure of the target
// active lightning channel. A fully signed closure transaction as well as the
// signature itself are returned.
//
// 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) {
lc.Lock()
defer lc.Unlock()
// If the channel is already closed, then ignore this request.
if lc.status == channelClosed {
// TODO(roasbeef): check to ensure no pending payments
return nil, ErrChanClosing
}
// 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.OurDeliveryScript,
lc.channelState.TheirDeliveryScript,
lc.channelState.IsInitiator)
// Ensure that the transaction doesn't explicitly validate any
@@ -2633,23 +2618,14 @@ func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.Ms
if err := blockchain.CheckTransactionSanity(tx); err != nil {
return nil, err
}
// With the transaction created, we can finally generate our half of
// the 2-of-2 multi-sig needed to redeem the funding output.
hashCache := txscript.NewTxSigHashes(closeTx)
lc.signDesc.SigHashes = hashCache
closeSig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc)
if err != nil {
return nil, err
}
// Finally, construct the witness stack minding the order of the
// pubkeys+sigs on the stack.
ourKey := lc.channelState.OurMultiSigKey.SerializeCompressed()
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
ourSig := append(closeSig, byte(txscript.SigHashAll))
witness := SpendMultiSig(lc.signDesc.WitnessScript, ourKey, ourSig,
theirKey, remoteSig)
witness := SpendMultiSig(lc.signDesc.WitnessScript, ourKey,
localSig, theirKey, remoteSig)
closeTx.TxIn[0].Witness = witness
// Validate the finalized transaction to ensure the output script is
+59 -34
View File
@@ -53,6 +53,12 @@ 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
}
@@ -705,40 +711,35 @@ func TestCooperativeChannelClosure(t *testing.T) {
}
defer cleanUp()
// First we test the channel initiator requesting a cooperative close.
sig, txid, err := aliceChannel.InitCooperativeClose()
// First we test creating of cooperative close proposals.
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to initiate alice cooperative close: %v", err)
t.Fatalf("unable to create alice coop close proposal: %v", err)
}
finalSig := append(sig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(finalSig)
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee)
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)
if err != nil {
t.Fatalf("unable to complete alice cooperative close: %v", err)
}
bobCloseSha := closeTx.TxHash()
if !bobCloseSha.IsEqual(txid) {
t.Fatalf("alice's transactions doesn't match: %x vs %x",
bobCloseSha[:], txid[:])
}
bobCloseSha := aliceCloseTx.TxHash()
aliceChannel.status = channelOpen
bobChannel.status = channelOpen
// Next we test the channel recipient requesting a cooperative closure.
// First we test the channel initiator requesting a cooperative close.
sig, txid, err = bobChannel.InitCooperativeClose()
if err != nil {
t.Fatalf("unable to initiate bob cooperative close: %v", err)
}
finalSig = append(sig, byte(txscript.SigHashAll))
closeTx, err = aliceChannel.CompleteCooperativeClose(finalSig)
bobCloseTx, err := aliceChannel.CompleteCooperativeClose(aliceCloseSig,
bobCloseSig, proposedFee)
if err != nil {
t.Fatalf("unable to complete bob cooperative close: %v", err)
}
aliceCloseSha := closeTx.TxHash()
if !aliceCloseSha.IsEqual(txid) {
t.Fatalf("bob's closure transactions don't match: %x vs %x",
aliceCloseSha[:], txid[:])
aliceCloseSha := bobCloseTx.TxHash()
if bobCloseSha != aliceCloseSha {
t.Fatalf("alice and bob close transactions don't match: %v", err)
}
}
@@ -1581,12 +1582,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.
closeSig, _, err := aliceChannel.InitCooperativeClose()
aliceSig, err := aliceChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
closeSig = append(closeSig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(closeSig)
aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll))
bobSig, err := bobChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig := append(bobSig, byte(txscript.SigHashAll))
closeTx, err := bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
@@ -1608,12 +1617,20 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Attempt another cooperative channel closure. It should succeed
// without any issues.
closeSig, _, err = aliceChannel.InitCooperativeClose()
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
closeSig = append(closeSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(closeSig)
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}
@@ -1636,12 +1653,20 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
// Our final attempt at another cooperative channel closure. It should
// succeed without any issues.
closeSig, _, err = aliceChannel.InitCooperativeClose()
aliceSig, err = aliceChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
closeSig = append(closeSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(closeSig)
aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll))
bobSig, err = bobChannel.CreateCloseProposal(proposedFee)
if err != nil {
t.Fatalf("unable to close channel: %v", err)
}
bobCloseSig = append(bobSig, byte(txscript.SigHashAll))
closeTx, err = bobChannel.CompleteCooperativeClose(bobCloseSig,
aliceCloseSig, proposedFee)
if err != nil {
t.Fatalf("unable to accept channel close: %v", err)
}