peer: modify channel closing negotiation to create new delivery scripts

This commit modifies the channel close negotiation workflow to instead
take not of the fat that with the new funding workflow, the delivery
scripts are no longer pre-committed to at the start of the funding
workflow. Instead, both sides present their delivery addresses at the
start of the shutdown process, then use those to create the final
cooperative closure transaction.

To accommodate for this new change, we now have an intermediate staging
area where we store the delivery scripts for both sides.
This commit is contained in:
Olaoluwa Osuntokun 2017-07-30 14:21:21 -07:00
parent 01fe9adff0
commit cd7b3290a8
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

203
peer.go

@ -750,6 +750,15 @@ func (p *peer) ChannelSnapshots() []*channeldb.ChannelSnapshot {
return <-resp return <-resp
} }
// closingScripts are the set of clsoign deslivery scripts for each party. This
// intermediate state is maintained for each active close negotiation, as the
// final signatures sent must cover the specified delivery scripts for each
// party.
type closingScripts struct {
localScript []byte
remoteScript []byte
}
// channelManager is goroutine dedicated to handling all requests/signals // channelManager is goroutine dedicated to handling all requests/signals
// pertaining to the opening, cooperative closing, and force closing of all // pertaining to the opening, cooperative closing, and force closing of all
// channels maintained with the remote peer. // channels maintained with the remote peer.
@ -762,11 +771,27 @@ func (p *peer) channelManager() {
// workflow. // workflow.
chanShutdowns := make(map[lnwire.ChannelID]*htlcswitch.ChanClose) chanShutdowns := make(map[lnwire.ChannelID]*htlcswitch.ChanClose)
deliveryAddrs := make(map[lnwire.ChannelID]*closingScripts)
// shutdownSigs is a map of signatures maintained by the responder in a // shutdownSigs is a map of signatures maintained by the responder in a
// cooperative channel close. This map enables us to respond to // cooperative channel close. This map enables us to respond to
// subsequent steps in the workflow without having to recalculate our // subsequent steps in the workflow without having to recalculate our
// signature for the channel close transaction. // signature for the channel close transaction.
shutdownSigs := make(map[lnwire.ChannelID][]byte) shutdownSigs := make(map[lnwire.ChannelID][]byte)
// TODO(roasbeef): move to cfg closure func
genDeliveryScript := func() ([]byte, error) {
deliveryAddr, err := p.server.cc.wallet.NewAddress(
lnwallet.WitnessPubKey, false,
)
if err != nil {
return nil, err
}
peerLog.Infof("Delivery addr for channel close: %v",
deliveryAddr)
return txscript.PayToAddrScript(deliveryAddr)
}
out: out:
for { for {
select { select {
@ -781,33 +806,43 @@ out:
p.activeChanMtx.RUnlock() p.activeChanMtx.RUnlock()
req.resp <- snapshots req.resp <- snapshots
// A new channel has arrived which means we've just completed a
// funding workflow. We'll initialize the necessary local
// state, and notify the htlc switch of a new link.
case newChanReq := <-p.newChannels: case newChanReq := <-p.newChannels:
chanPoint := newChanReq.channel.ChannelPoint() chanPoint := newChanReq.channel.ChannelPoint()
chanID := lnwire.NewChanIDFromOutPoint(chanPoint) chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
newChan := newChanReq.channel
// First, we'll add this channel to the set of active
// channels, so we can look it up later easily
// according to its channel ID.
p.activeChanMtx.Lock() p.activeChanMtx.Lock()
p.activeChannels[chanID] = newChanReq.channel p.activeChannels[chanID] = newChan
p.activeChanMtx.Unlock() p.activeChanMtx.Unlock()
peerLog.Infof("New channel active ChannelPoint(%v) "+ peerLog.Infof("New channel active ChannelPoint(%v) "+
"with peerId(%v)", chanPoint, p.id) "with peerId(%v)", chanPoint, p.id)
link := htlcswitch.NewChannelLink( // Next, we'll assemble a ChannelLink along with the
htlcswitch.ChannelLinkConfig{ // necessary items it needs to function.
Peer: p, linkConfig := htlcswitch.ChannelLinkConfig{
DecodeHopIterator: p.server.sphinx.DecodeHopIterator, Peer: p,
DecodeOnionObfuscator: p.server.sphinx.DecodeOnionObfuscator, DecodeHopIterator: p.server.sphinx.DecodeHopIterator,
GetLastChannelUpdate: createGetLastUpdate(p.server.chanRouter, DecodeOnionObfuscator: p.server.sphinx.DecodeOnionObfuscator,
p.PubKey(), newChanReq.channel.ShortChanID()), GetLastChannelUpdate: createGetLastUpdate(p.server.chanRouter,
SettledContracts: p.server.breachArbiter.settledContracts, p.PubKey(), newChanReq.channel.ShortChanID()),
DebugHTLC: cfg.DebugHTLC, SettledContracts: p.server.breachArbiter.settledContracts,
Registry: p.server.invoices, DebugHTLC: cfg.DebugHTLC,
Switch: p.server.htlcSwitch, Registry: p.server.invoices,
FwrdingPolicy: p.server.cc.routingPolicy, Switch: p.server.htlcSwitch,
}, FwrdingPolicy: p.server.cc.routingPolicy,
newChanReq.channel, }
) link := htlcswitch.NewChannelLink(linkConfig, newChan)
// With the channel link created, we'll now notify the
// htlc switch so this channel can be used to dispatch
// local payments and also passively forward payments.
err := p.server.htlcSwitch.AddLink(link) err := p.server.htlcSwitch.AddLink(link)
if err != nil { if err != nil {
peerLog.Errorf("can't register new channel "+ peerLog.Errorf("can't register new channel "+
@ -826,58 +861,117 @@ out:
// We'll only track this shutdown request if this is a // We'll only track this shutdown request if this is a
// regular close request, and not in response to a // regular close request, and not in response to a
// channel breach. // channel breach.
var (
deliveryScript []byte
err error
)
if req.CloseType == htlcswitch.CloseRegular { if req.CloseType == htlcswitch.CloseRegular {
chanShutdowns[chanID] = req chanShutdowns[chanID] = req
// As we need to close out the channel and
// claim our funds on-chain, we'll request a
// new delivery address from the wallet, and
// turn that into it corresponding output
// script.
deliveryScript, err = genDeliveryScript()
if err != nil {
cErr := fmt.Errorf("Unable to generate "+
"delivery address: %v", err)
peerLog.Errorf(cErr.Error())
req.Err <- cErr
continue
}
// We'll also track this delivery script, as
// we'll need it to reconstruct the cooperative
// closure transaction during our closing fee
// negotiation ratchet.
deliveryAddrs[chanID] = &closingScripts{
localScript: deliveryScript,
}
} }
// With the state marked as shutting down, we can now // With the state marked as shutting down, we can now
// proceed with the channel close workflow. If this is // proceed with the channel close workflow. If this is
// regular close, we'll send a shutdown. Otherwise, // regular close, we'll send a shutdown. Otherwise,
// we'll simply be clearing our indexes. // we'll simply be clearing our indexes.
p.handleLocalClose(req) p.handleLocalClose(req, deliveryScript)
// A receipt of a message over this channel indicates that // A receipt of a message over this channel indicates that
// either a shutdown proposal has been initiated, or a prior // either a shutdown proposal has been initiated, or a prior
// one has been completed, advancing to the next state of // one has been completed, advancing to the next state of
// channel closure. // channel closure.
case req := <-p.shutdownChanReqs: case req := <-p.shutdownChanReqs:
// We've just received a shutdown request. First, we'll // If we don't have a channel that matches this channel
// check in the shutdown map to see if we're the // ID, then we'll ignore this message.
// initiator or not. If we don't have an entry for chanID := req.ChannelID
// this channel, then this means that we're the p.activeChanMtx.Lock()
// responder to the workflow. _, ok := p.activeChannels[chanID]
p.activeChanMtx.Unlock()
if !ok {
peerLog.Warnf("Received unsolicited shutdown msg: %v",
spew.Sdump(req))
continue
}
// First, we'll track their delivery script for when we
// ultimately create the cooperative closure
// transaction.
deliveryScripts, ok := deliveryAddrs[chanID]
if !ok {
deliveryAddrs[chanID] = &closingScripts{}
deliveryScripts = deliveryAddrs[chanID]
}
deliveryScripts.remoteScript = req.Address
// Next, we'll check in the shutdown map to see if
// we're the initiator or not. If we don't have an
// entry for this channel, then this means that we're
// the responder to the workflow.
if _, ok := chanShutdowns[req.ChannelID]; !ok { if _, ok := chanShutdowns[req.ChannelID]; !ok {
// As we're the responder, we'll need to
// generate a delivery script of our own.
deliveryScript, err := genDeliveryScript()
if err != nil {
peerLog.Errorf("Unable to generate "+
"delivery address: %v", err)
continue
}
deliveryScripts.localScript = deliveryScript
// In this case, we'll send a shutdown message, // In this case, we'll send a shutdown message,
// and also prep our closing signature for the // and also prep our closing signature for the
// case they fees are immediately agreed upon. // case they fees are immediately agreed upon.
closeSig := p.handleShutdownResponse(req) closeSig := p.handleShutdownResponse(req,
deliveryScript)
if closeSig != nil { if closeSig != nil {
shutdownSigs[req.ChannelID] = closeSig shutdownSigs[chanID] = closeSig
} }
} }
// TODO(roasbeef): should also save their delivery
// address within close request after funding change.
// * modify complete to include delivery address
// A receipt of a message over this channel indicates that the // A receipt of a message over this channel indicates that the
// final stage of a channel shutdown workflow has been // final stage of a channel shutdown workflow has been
// completed. // completed.
case req := <-p.closingSignedChanReqs: case req := <-p.closingSignedChanReqs:
// First we'll check if this has an entry in the local // First we'll check if this has an entry in the local
// shutdown map. // shutdown map.
localCloseReq, ok := chanShutdowns[req.ChannelID] chanID := req.ChannelID
localCloseReq, ok := chanShutdowns[chanID]
// If it does, then this means we were the initiator of // If it does, then this means we were the initiator of
// the channel shutdown procedure. // the channel shutdown procedure.
if ok { if ok {
// To finalize this shtudown, we'll now send a // To finalize this shutdown, we'll now send a
// matching close signed message to the other // matching close signed message to the other
// party, and broadcast the closing transaction // party, and broadcast the closing transaction
// to the network. // to the network.
p.handleInitClosingSigned(localCloseReq, req) p.handleInitClosingSigned(localCloseReq, req,
deliveryAddrs[chanID])
delete(chanShutdowns, req.ChannelID) delete(chanShutdowns, req.ChannelID)
delete(deliveryAddrs, req.ChannelID)
continue continue
} }
@ -886,10 +980,13 @@ out:
// channel as pending close, and watch the network for // channel as pending close, and watch the network for
// the ultimate confirmation of the closing // the ultimate confirmation of the closing
// transaction. // transaction.
responderSig := append(shutdownSigs[req.ChannelID], responderSig := append(shutdownSigs[chanID],
byte(txscript.SigHashAll)) byte(txscript.SigHashAll))
p.handleResponseClosingSigned(req, responderSig) p.handleResponseClosingSigned(req, responderSig,
delete(shutdownSigs, req.ChannelID) deliveryAddrs[chanID])
delete(shutdownSigs, chanID)
delete(deliveryAddrs, chanID)
case <-p.quit: case <-p.quit:
break out break out
@ -904,7 +1001,7 @@ out:
// //
// TODO(roasbeef): if no more active channels with peer call Remove on connMgr // TODO(roasbeef): if no more active channels with peer call Remove on connMgr
// with peerID // with peerID
func (p *peer) handleLocalClose(req *htlcswitch.ChanClose) { func (p *peer) handleLocalClose(req *htlcswitch.ChanClose, deliveryScript []byte) {
chanID := lnwire.NewChanIDFromOutPoint(req.ChanPoint) chanID := lnwire.NewChanIDFromOutPoint(req.ChanPoint)
p.activeChanMtx.RLock() p.activeChanMtx.RLock()
@ -919,11 +1016,12 @@ func (p *peer) handleLocalClose(req *htlcswitch.ChanClose) {
} }
switch req.CloseType { switch req.CloseType {
// A type of CloseRegular indicates that the user has opted to close // A type of CloseRegular indicates that the user has opted to close
// out this channel on-chain, so we execute the cooperative channel // out this channel on-chain, so we execute the cooperative channel
// closure workflow. // closure workflow.
case htlcswitch.CloseRegular: case htlcswitch.CloseRegular:
err := p.sendShutdown(channel) err := p.sendShutdown(channel, deliveryScript)
if err != nil { if err != nil {
req.Err <- err req.Err <- err
return return
@ -932,6 +1030,7 @@ func (p *peer) handleLocalClose(req *htlcswitch.ChanClose) {
// A type of CloseBreach indicates that the counterparty has breached // A type of CloseBreach indicates that the counterparty has breached
// the channel therefore we need to clean up our local state. // the channel therefore we need to clean up our local state.
case htlcswitch.CloseBreach: case htlcswitch.CloseBreach:
// TODO(roasbeef): no longer need with newer beach logic?
peerLog.Infof("ChannelPoint(%v) has been breached, wiping "+ peerLog.Infof("ChannelPoint(%v) has been breached, wiping "+
"channel", req.ChanPoint) "channel", req.ChanPoint)
if err := p.WipeChannel(channel); err != nil { if err := p.WipeChannel(channel); err != nil {
@ -948,7 +1047,9 @@ func (p *peer) handleLocalClose(req *htlcswitch.ChanClose) {
// close workflow receives a Shutdown message. This is the second step in the // close workflow receives a Shutdown message. This is the second step in the
// cooperative close workflow. This function generates a close transaction with // cooperative close workflow. This function generates a close transaction with
// a proposed fee amount and sends the signed transaction to the initiator. // a proposed fee amount and sends the signed transaction to the initiator.
func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown) []byte { func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown,
localDeliveryScript []byte) []byte {
p.activeChanMtx.RLock() p.activeChanMtx.RLock()
channel, ok := p.activeChannels[msg.ChannelID] channel, ok := p.activeChannels[msg.ChannelID]
p.activeChanMtx.RUnlock() p.activeChanMtx.RUnlock()
@ -960,7 +1061,8 @@ func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown) []byte {
// As we just received a shutdown message, we'll also send a shutdown // As we just received a shutdown message, we'll also send a shutdown
// message with our desired fee so we can start the negotiation. // message with our desired fee so we can start the negotiation.
if err := p.sendShutdown(channel); err != nil { err := p.sendShutdown(channel, localDeliveryScript)
if err != nil {
peerLog.Errorf("error while sending shutdown message: %v", err) peerLog.Errorf("error while sending shutdown message: %v", err)
return nil return nil
} }
@ -973,8 +1075,9 @@ func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown) []byte {
// Once both sides agree on a fee, we'll create a signature that closes // Once both sides agree on a fee, we'll create a signature that closes
// the channel using the agree upon fee rate. // the channel using the agree upon fee rate.
// TODO(roasbeef): remove encoding redundancy closeSig, proposedFee, err := channel.CreateCloseProposal(
closeSig, proposedFee, err := channel.CreateCloseProposal(feeRate) feeRate, localDeliveryScript, msg.Address,
)
if err != nil { if err != nil {
peerLog.Errorf("unable to create close proposal: %v", err) peerLog.Errorf("unable to create close proposal: %v", err)
return nil return nil
@ -1008,7 +1111,9 @@ func (p *peer) handleShutdownResponse(msg *lnwire.Shutdown) []byte {
// of an unresponsive remote party, the initiator can either choose to execute // 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 // a force closure, or backoff for a period of time, and retry the cooperative
// closure. // closure.
func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose, msg *lnwire.ClosingSigned) { func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose,
msg *lnwire.ClosingSigned, deliveryScripts *closingScripts) {
chanID := lnwire.NewChanIDFromOutPoint(req.ChanPoint) chanID := lnwire.NewChanIDFromOutPoint(req.ChanPoint)
p.activeChanMtx.RLock() p.activeChanMtx.RLock()
channel, ok := p.activeChannels[chanID] channel, ok := p.activeChannels[chanID]
@ -1030,7 +1135,9 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose, msg *lnwire.Cl
// We agree with the proposed channel close transaction and fee rate, // We agree with the proposed channel close transaction and fee rate,
// so generate our signature. // so generate our signature.
initiatorSig, proposedFee, err := channel.CreateCloseProposal(feeRate) initiatorSig, proposedFee, err := channel.CreateCloseProposal(
feeRate, deliveryScripts.localScript, deliveryScripts.remoteScript,
)
if err != nil { if err != nil {
req.Err <- err req.Err <- err
return return
@ -1042,6 +1149,7 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose, msg *lnwire.Cl
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,
deliveryScripts.localScript, deliveryScripts.remoteScript,
feeRate) feeRate)
if err != nil { if err != nil {
req.Err <- err req.Err <- err
@ -1150,7 +1258,8 @@ func (p *peer) handleInitClosingSigned(req *htlcswitch.ChanClose, msg *lnwire.Cl
// close workflow receives a ClosingSigned message. This function handles the // close workflow receives a ClosingSigned message. This function handles the
// finalization of the cooperative close from the perspective of the responder. // finalization of the cooperative close from the perspective of the responder.
func (p *peer) handleResponseClosingSigned(msg *lnwire.ClosingSigned, func (p *peer) handleResponseClosingSigned(msg *lnwire.ClosingSigned,
respSig []byte) { respSig []byte, deliveryScripts *closingScripts) {
p.activeChanMtx.RLock() p.activeChanMtx.RLock()
channel, ok := p.activeChannels[msg.ChannelID] channel, ok := p.activeChannels[msg.ChannelID]
p.activeChanMtx.RUnlock() p.activeChanMtx.RUnlock()
@ -1171,6 +1280,7 @@ func (p *peer) handleResponseClosingSigned(msg *lnwire.ClosingSigned,
// TODO(roasbeef): should instead use the fee within the message // TODO(roasbeef): should instead use the fee within the message
feeRate := p.server.cc.feeEstimator.EstimateFeePerWeight(1) * 1000 feeRate := p.server.cc.feeEstimator.EstimateFeePerWeight(1) * 1000
closeTx, err := channel.CompleteCooperativeClose(respSig, initSig, closeTx, err := channel.CompleteCooperativeClose(respSig, initSig,
deliveryScripts.localScript, deliveryScripts.remoteScript,
feeRate) feeRate)
if err != nil { if err != nil {
peerLog.Errorf("unable to complete cooperative "+ peerLog.Errorf("unable to complete cooperative "+
@ -1279,17 +1389,18 @@ func waitForChanToClose(bestHeight uint32, notifier chainntnfs.ChainNotifier,
// between peers to initiate the cooperative channel close workflow. In // between peers to initiate the cooperative channel close workflow. In
// addition, sendShutdown also signals to the HTLC switch to stop accepting // addition, sendShutdown also signals to the HTLC switch to stop accepting
// HTLCs for the specified channel. // HTLCs for the specified channel.
func (p *peer) sendShutdown(channel *lnwallet.LightningChannel) error { func (p *peer) sendShutdown(channel *lnwallet.LightningChannel,
deliveryScript []byte) error {
// In order to construct the shutdown message, we'll need to // In order to construct the shutdown message, we'll need to
// reconstruct the channelID, and the current set delivery script for // reconstruct the channelID, and the current set delivery script for
// the channel closure. // the channel closure.
chanID := lnwire.NewChanIDFromOutPoint(channel.ChannelPoint()) chanID := lnwire.NewChanIDFromOutPoint(channel.ChannelPoint())
addr := lnwire.DeliveryAddress(channel.LocalDeliveryScript)
// With both items constructed we'll now send the shutdown message for // With both items constructed we'll now send the shutdown message for
// this particular channel, advertising a shutdown request to our // this particular channel, advertising a shutdown request to our
// desired closing script. // desired closing script.
shutdown := lnwire.NewShutdown(chanID, addr) shutdown := lnwire.NewShutdown(chanID, deliveryScript)
p.queueMsg(shutdown, nil) p.queueMsg(shutdown, nil)
// Finally, we'll unregister the link from the switch in order to // Finally, we'll unregister the link from the switch in order to