lnwallet: implement cooperative closure for LightningChannel

A cooperative closure of a LightningChannel proceeds in two steps.
First, the party who wishes to close the channel sends a signature for
the closing transaction. Next, the responder reconstructs the closing
transaction identically as the initiator did using a canonical
input/output ordering, and the currently settled balance within the
channel. At this point, the responder then broadcasts the closure
transaction. It is the responsibility of the initiator to watch for
this transaction broadcast within the network to clean up any resources
they committed to the active channel.
This commit is contained in:
Olaoluwa Osuntokun 2016-06-20 22:09:42 -07:00
parent 27e6839060
commit 45236fa092
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -237,6 +237,7 @@ func (c *ChannelUpdate) VerifyNewCommitmentSigs(ourSig, theirSig []byte) error {
// then validate that the scriptSig executes correctly.
commitTx := c.ourPendingCommitTx
commitTx.TxIn[0].Witness = witness
// TODO(roasbeef): need hashcache and value here
vm, err := txscript.NewEngine(c.lnChannel.fundingP2SH, commitTx, 0,
txscript.StandardVerifyFlags, nil, nil, 0)
if err != nil {
@ -535,30 +536,6 @@ func (lc *LightningChannel) SettleHTLC(rValue [20]byte, newRevocation [20]byte)
return chanUpdate, nil
}
// createNewCommitmentTxns....
// NOTE: This MUST be called with stateMtx held.
func createNewCommitmentTxns(fundingTxIn *wire.TxIn, state *channeldb.OpenChannel,
chanUpdate *ChannelUpdate, amountToUs, amountToThem btcutil.Amount) (*wire.MsgTx, *wire.MsgTx, error) {
ourNewCommitTx, err := createCommitTx(fundingTxIn,
state.OurCommitKey.PubKey(), state.TheirCommitKey,
chanUpdate.pendingDesc.OurRevocation[:], state.LocalCsvDelay,
amountToUs, amountToThem)
if err != nil {
return nil, nil, err
}
theirNewCommitTx, err := createCommitTx(fundingTxIn,
state.TheirCommitKey, state.OurCommitKey.PubKey(),
chanUpdate.pendingDesc.TheirRevocation[:], state.RemoteCsvDelay,
amountToThem, amountToUs)
if err != nil {
return nil, nil, err
}
return ourNewCommitTx, theirNewCommitTx, nil
}
// CancelHTLC...
func (lc *LightningChannel) CancelHTLC() error {
return nil
@ -583,6 +560,106 @@ func (lc *LightningChannel) ForceClose() error {
return 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.
// TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs,
// settle any inflight.
func (lc *LightningChannel) InitCooperativeClose() ([]byte, *wire.ShaHash, error) {
lc.Lock()
defer lc.Unlock() // TODO(roasbeef): coarser graiend locking
// 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
}
// Otherwise, indicate in the channel status that a channel closure has
// been initiated.
lc.status = channelClosing
// TODO(roasbeef): assumes initiator pays fees
closeTx := createCooperativeCloseTx(lc.fundingTxIn,
lc.channelState.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
true)
closeTxSha := closeTx.TxSha()
// Finally, sign the completed cooperative closure transaction. As the
// initiator we'll simply send our signature over the the remote party,
// using the generated txid to be notified once the closure transaction
// has been confirmed.
hashCache := txscript.NewTxSigHashes(closeTx)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, int64(lc.channelState.Capacity),
lc.channelState.FundingRedeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
if err != nil {
return nil, nil, err
}
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 initating 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.
func (lc *LightningChannel) CompleteCooperativeClose(remoteSig []byte) (*wire.MsgTx, error) {
lc.Lock()
defer lc.Unlock() // TODO(roasbeef): coarser graiend locking
// 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
}
lc.status = channelClosed
// 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.OurBalance, lc.channelState.TheirBalance,
lc.channelState.OurDeliveryScript, lc.channelState.TheirDeliveryScript,
false)
// With the transaction created, we can finally generate our half of
// the 2-of-2 multi-sig needed to redeem the funding output.
redeemScript := lc.channelState.FundingRedeemScript
hashCache := txscript.NewTxSigHashes(closeTx)
closeSig, err := txscript.RawTxInWitnessSignature(closeTx,
hashCache, 0, int64(lc.channelState.Capacity),
redeemScript, txscript.SigHashAll,
lc.channelState.OurMultiSigKey)
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.PubKey().SerializeCompressed()
theirKey := lc.channelState.TheirMultiSigKey.SerializeCompressed()
witness := spendMultiSig(redeemScript, ourKey, closeSig,
theirKey, remoteSig)
closeTx.TxIn[0].Witness = witness
// TODO(roasbeef): VALIDATE
return closeTx, nil
}
// RequestPayment...
func (lc *LightningChannel) RequestPayment(amount btcutil.Amount) error {
// Validate amount
@ -597,6 +674,30 @@ type PaymentRequest struct {
Value btcutil.Amount
}
// createNewCommitmentTxns....
// NOTE: This MUST be called with stateMtx held.
func createNewCommitmentTxns(fundingTxIn *wire.TxIn, state *channeldb.OpenChannel,
chanUpdate *ChannelUpdate, amountToUs, amountToThem btcutil.Amount) (*wire.MsgTx, *wire.MsgTx, error) {
ourNewCommitTx, err := createCommitTx(fundingTxIn,
state.OurCommitKey.PubKey(), state.TheirCommitKey,
chanUpdate.pendingDesc.OurRevocation[:], state.LocalCsvDelay,
amountToUs, amountToThem)
if err != nil {
return nil, nil, err
}
theirNewCommitTx, err := createCommitTx(fundingTxIn,
state.TheirCommitKey, state.OurCommitKey.PubKey(),
chanUpdate.pendingDesc.TheirRevocation[:], state.RemoteCsvDelay,
amountToThem, amountToUs)
if err != nil {
return nil, nil, err
}
return ourNewCommitTx, theirNewCommitTx, nil
}
// createCommitTx...
// TODO(roasbeef): fix inconsistency of 32 vs 20 byte revocation hashes everywhere...
func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
@ -637,3 +738,50 @@ func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey
return commitTx, nil
}
// createCooperativeCloseTx creates a transaction which if signed by both
// parties, then broadcast cooperatively closes an active channel. The creation
// of the closure transaction is modified by a boolean indicating if the party
// constructing the channel is the initiator of the closure. Currently it is
// expected that the initiator pays the transaction fees for the closing
// transaction in full.
func createCooperativeCloseTx(fundingTxIn *wire.TxIn,
ourBalance, theirBalance btcutil.Amount,
ourDeliveryScript, theirDeliveryScript []byte,
initiator bool) *wire.MsgTx {
// Construct the transaction to perform a cooperative closure of the
// channel. In the event that one side doesn't have any settled funds
// within the channel then a refund output for that particular side can
// be omitted.
closeTx := wire.NewMsgTx()
closeTx.AddTxIn(fundingTxIn)
// The initiator the a cooperative closure pays the fee in entirety.
// Determine if we're the initiator so we can compute fees properly.
if initiator {
// TODO(roasbeef): take sat/byte here instead of properly calc
ourBalance -= 5000
} else {
theirBalance -= 5000
}
// TODO(roasbeef): dust check...
// * although upper layers should prevent
if ourBalance != 0 {
closeTx.AddTxOut(&wire.TxOut{
PkScript: ourDeliveryScript,
Value: int64(ourBalance),
})
}
if theirBalance != 0 {
closeTx.AddTxOut(&wire.TxOut{
PkScript: theirDeliveryScript,
Value: int64(theirBalance),
})
}
txsort.InPlaceSort(closeTx)
return closeTx
}