chancloser: new package for cooperative channel closure

Introduces a new chancloser package which exposes a ChanCloser
struct that handles the cooperative channel closure negotiation
and is meant to replace chancloser.go in the lnd package. Updates
all references to chancloser.go to instead use chancloser package.
This commit is contained in:
nsa 2020-06-16 20:33:06 -04:00
parent 8528ec4568
commit 2d68a64a5b
7 changed files with 97 additions and 50 deletions

@ -1,4 +1,4 @@
package lnd
package chancloser
import (
"bytes"
@ -70,13 +70,13 @@ const (
closeFinished
)
// ChanCloseCfg holds all the items that a channelCloser requires to carry out its
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
// duties.
type ChanCloseCfg struct {
// Channel is the channel that should be closed.
Channel *lnwallet.LightningChannel
// UnregisterChannel is a function closure that allows the channelCloser to
// UnregisterChannel is a function closure that allows the ChanCloser to
// unregister a channel. Once this has been done, no further HTLC's should
// be routed through the channel.
UnregisterChannel func(lnwire.ChannelID)
@ -96,15 +96,15 @@ type ChanCloseCfg struct {
Quit chan struct{}
}
// channelCloser is a state machine that handles the cooperative channel closure
// ChanCloser is a state machine that handles the cooperative channel closure
// procedure. This includes shutting down a channel, marking it ineligible for
// routing HTLC's, negotiating fees with the remote party, and finally
// broadcasting the fully signed closure transaction to the network.
type channelCloser struct {
type ChanCloser struct {
// state is the current state of the state machine.
state closeState
// cfg holds the configuration for this channelCloser instance.
// cfg holds the configuration for this ChanCloser instance.
cfg ChanCloseCfg
// chanPoint is the full channel point of the target channel.
@ -162,7 +162,7 @@ type channelCloser struct {
// be populated iff, we're the initiator of this closing request.
func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
closeReq *htlcswitch.ChanClose, locallyInitiated bool) *channelCloser {
closeReq *htlcswitch.ChanClose, locallyInitiated bool) *ChanCloser {
// Given the target fee-per-kw, we'll compute what our ideal _total_ fee
// will be starting at for this fee negotiation.
@ -177,17 +177,17 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
// TODO(roasbeef): clamp fee func?
channelCommitFee := cfg.Channel.StateSnapshot().CommitFee
if idealFeeSat > channelCommitFee {
peerLog.Infof("Ideal starting fee of %v is greater than commit "+
chancloserLog.Infof("Ideal starting fee of %v is greater than commit "+
"fee of %v, clamping", int64(idealFeeSat), int64(channelCommitFee))
idealFeeSat = channelCommitFee
}
peerLog.Infof("Ideal fee for closure of ChannelPoint(%v) is: %v sat",
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) is: %v sat",
cfg.Channel.ChannelPoint(), int64(idealFeeSat))
cid := lnwire.NewChanIDFromOutPoint(cfg.Channel.ChannelPoint())
return &channelCloser{
return &ChanCloser{
closeReq: closeReq,
state: closeIdle,
chanPoint: *cfg.Channel.ChannelPoint(),
@ -203,7 +203,7 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
// initChanShutdown begins the shutdown process by un-registering the channel,
// and creating a valid shutdown message to our target delivery address.
func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) {
func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// With both items constructed we'll now send the shutdown message for this
// particular channel, advertising a shutdown request to our desired
// closing script.
@ -215,7 +215,7 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// We do so before closing the channel as otherwise the current edge policy
// won't be retrievable from the graph.
if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
peerLog.Warnf("Unable to disable channel %v on close: %v",
chancloserLog.Warnf("Unable to disable channel %v on close: %v",
c.chanPoint, err)
}
@ -233,7 +233,8 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) {
return nil, err
}
peerLog.Infof("ChannelPoint(%v): sending shutdown message", c.chanPoint)
chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
c.chanPoint)
return shutdown, nil
}
@ -242,14 +243,14 @@ func (c *channelCloser) initChanShutdown() (*lnwire.Shutdown, error) {
// cooperative channel closure. This message returns the shutdown message to
// send to the remote party. Upon completion, we enter the
// closeShutdownInitiated phase as we await a response.
func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) {
func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
// If we attempt to shutdown the channel for the first time, and we're not
// in the closeIdle state, then the caller made an error.
if c.state != closeIdle {
return nil, ErrChanAlreadyClosing
}
peerLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
shutdownMsg, err := c.initChanShutdown()
if err != nil {
@ -270,7 +271,7 @@ func (c *channelCloser) ShutdownChan() (*lnwire.Shutdown, error) {
//
// NOTE: This transaction is only available if the state machine is in the
// closeFinished state.
func (c *channelCloser) ClosingTx() (*wire.MsgTx, error) {
func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
// If the state machine hasn't finished closing the channel, then we'll
// return an error as we haven't yet computed the closing tx.
if c.state != closeFinished {
@ -285,17 +286,17 @@ func (c *channelCloser) ClosingTx() (*wire.MsgTx, error) {
//
// NOTE: This will only return a non-nil pointer if we were the initiator of
// the cooperative closure workflow.
func (c *channelCloser) CloseRequest() *htlcswitch.ChanClose {
func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
return c.closeReq
}
// Channel returns the channel stored in the config.
func (c *channelCloser) Channel() *lnwallet.LightningChannel {
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
return c.cfg.Channel
}
// NegotiationHeight returns the negotiation height.
func (c *channelCloser) NegotiationHeight() uint32 {
func (c *ChanCloser) NegotiationHeight() uint32 {
return c.negotiationHeight
}
@ -317,7 +318,7 @@ func maybeMatchScript(disconnect func() error, upfrontScript,
// If an upfront shutdown script was provided, disconnect from the peer, as
// per BOLT 2, and return an error.
if !bytes.Equal(upfrontScript, peerScript) {
peerLog.Warnf("peer's script: %x does not match upfront "+
chancloserLog.Warnf("peer's script: %x does not match upfront "+
"shutdown script: %x", peerScript, upfrontScript)
// Disconnect from the peer because they have violated option upfront
@ -336,8 +337,8 @@ func maybeMatchScript(disconnect func() error, upfrontScript,
// This method will update the state accordingly and return two primary values:
// the next set of messages to be sent, and a bool indicating if the fee
// negotiation process has completed. If the second value is true, then this
// means the channelCloser can be garbage collected.
func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// means the ChanCloser can be garbage collected.
func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
bool, error) {
switch c.state {
@ -390,7 +391,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
return nil, false, err
}
peerLog.Infof("ChannelPoint(%v): responding to shutdown",
chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
c.chanPoint)
msgsToSend := make([]lnwire.Message, 0, 2)
@ -445,7 +446,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// closing transaction should look like.
c.state = closeFeeNegotiation
peerLog.Infof("ChannelPoint(%v): shutdown response received, "+
chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+
"entering fee negotiation", c.chanPoint)
// Starting with our ideal fee rate, we'll create an initial closing
@ -482,9 +483,8 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// We'll now attempt to ratchet towards a fee deemed acceptable by
// both parties, factoring in our ideal fee rate, and the last
// proposed fee by both sides.
feeProposal := calcCompromiseFee(c.chanPoint,
c.idealFeeSat, c.lastFeeProposal,
remoteProposedFee,
feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat,
c.lastFeeProposal, remoteProposedFee,
)
// With our new fee proposal calculated, we'll craft a new close
@ -499,13 +499,13 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// we'll return this latest close signed message so we can continue
// negotiation.
if feeProposal != remoteProposedFee {
peerLog.Debugf("ChannelPoint(%v): close tx fee "+
chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+
"disagreement, continuing negotiation", c.chanPoint)
return []lnwire.Message{closeSigned}, false, nil
}
}
peerLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+
chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+
"negotiation", c.chanPoint, remoteProposedFee)
// Otherwise, we've agreed on a fee for the closing transaction! We'll
@ -540,7 +540,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// With the closing transaction crafted, we'll now broadcast it to the
// network.
peerLog.Infof("Broadcasting cooperative close tx: %v",
chancloserLog.Infof("Broadcasting cooperative close tx: %v",
newLogClosure(func() string {
return spew.Sdump(closeTx)
}),
@ -579,7 +579,7 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
// proposeCloseSigned attempts to propose a new signature for the closing
// transaction for a channel based on the prior fee negotiations and our current
// compromise fee.
func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) {
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) {
rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
fee, c.localDeliveryScript, c.remoteDeliveryScript,
)
@ -595,7 +595,7 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS
return nil, err
}
peerLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+
chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+
"chan", c.chanPoint, int64(fee))
// We'll assemble a ClosingSigned message using this information and return
@ -650,7 +650,7 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
// TODO(roasbeef): take in number of rounds as well?
peerLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+
chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+
"%v, last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee),
int64(lastSentFee), int64(remoteFee))
@ -676,7 +676,7 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
// If the fee is lower, but still acceptable, then we'll just return
// this fee and end the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) {
peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+
chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
"close enough, capitulating", chanPoint)
return remoteFee
}
@ -690,13 +690,12 @@ func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
// If the fee is greater, but still acceptable, then we'll just return
// this fee in order to put an end to the negotiation.
if feeInAcceptableRange(lastSentFee, remoteFee) {
peerLog.Infof("ChannelPoint(%v): proposed remote fee is "+
chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
"close enough, capitulating", chanPoint)
return remoteFee
}
// Otherwise, we'll rachet the fee up using our current
// algorithm.
// Otherwise, we'll ratchet the fee up using our current algorithm.
return ratchetFee(lastSentFee, true)
default:

@ -1,4 +1,4 @@
package lnd
package chancloser
import (
"crypto/rand"

@ -0,0 +1,41 @@
package chancloser
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// chancloserLog is a logger that is initialized with the btclog.Disabled
// logger.
var chancloserLog btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("CHCL", nil))
}
// DisableLog disables all logging output.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
chancloserLog = logger
}
// logClosure is used to provide a closure over expensive logging operations
// so they aren't performed when the logging level doesn't warrant it.
type logClosure func() string
// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

2
log.go

@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/monitoring"
"github.com/lightningnetwork/lnd/netann"
@ -121,6 +122,7 @@ func SetupLoggers(root *build.RotatingLogWriter) {
AddSubLogger(root, "WTCL", wtclient.UseLogger)
AddSubLogger(root, "PRNF", peernotifier.UseLogger)
AddSubLogger(root, "CHFD", chanfunding.UseLogger)
AddSubLogger(root, "CHCL", chancloser.UseLogger)
AddSubLogger(root, routing.Subsystem, routing.UseLogger, localchans.UseLogger)
AddSubLogger(root, routerrpc.Subsystem, routerrpc.UseLogger)

21
peer.go

@ -27,6 +27,7 @@ import (
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/queue"
@ -190,7 +191,7 @@ type peer struct {
// messages are directed to one of these active state machines. Once
// the channel has been closed, the state machine will be delete from
// the map.
activeChanCloses map[lnwire.ChannelID]*channelCloser
activeChanCloses map[lnwire.ChannelID]*chancloser.ChanCloser
// localCloseChanReqs is a channel in which any local requests to close
// a particular channel are sent over.
@ -302,7 +303,7 @@ func newPeer(cfg *Config, conn net.Conn, connReq *connmgr.ConnReq, server *serve
activeMsgStreams: make(map[lnwire.ChannelID]*msgStream),
activeChanCloses: make(map[lnwire.ChannelID]*channelCloser),
activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser),
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
linkFailures: make(chan linkFailureReport),
chanCloseMsgs: make(chan *closeMsg),
@ -2232,7 +2233,9 @@ func (p *peer) reenableActiveChannels() {
// for the target channel ID. If the channel isn't active an error is returned.
// Otherwise, either an existing state machine will be returned, or a new one
// will be created.
func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, error) {
func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (
*chancloser.ChanCloser, error) {
// First, we'll ensure that we actually know of the target channel. If
// not, we'll ignore this message.
p.activeChanMtx.RLock()
@ -2288,8 +2291,8 @@ func (p *peer) fetchActiveChanCloser(chanID lnwire.ChannelID) (*channelCloser, e
return nil, fmt.Errorf("cannot obtain best block")
}
chanCloser = NewChanCloser(
ChanCloseCfg{
chanCloser = chancloser.NewChanCloser(
chancloser.ChanCloseCfg{
Channel: channel,
UnregisterChannel: p.server.htlcSwitch.RemoveLink,
BroadcastTx: p.server.cc.wallet.PublishTransaction,
@ -2334,7 +2337,7 @@ func chooseDeliveryScript(upfront,
// the upfront shutdown script (because closing out to a different script
// would violate upfront shutdown).
if !bytes.Equal(upfront, requested) {
return nil, ErrUpfrontShutdownScriptMismatch
return nil, chancloser.ErrUpfrontShutdownScriptMismatch
}
// The user requested script matches the upfront shutdown script, so we
@ -2404,8 +2407,8 @@ func (p *peer) handleLocalCloseReq(req *htlcswitch.ChanClose) {
return
}
chanCloser := NewChanCloser(
ChanCloseCfg{
chanCloser := chancloser.NewChanCloser(
chancloser.ChanCloseCfg{
Channel: channel,
UnregisterChannel: p.server.htlcSwitch.RemoveLink,
BroadcastTx: p.server.cc.wallet.PublishTransaction,
@ -2520,7 +2523,7 @@ func (p *peer) handleLinkFailure(failure linkFailureReport) {
// machine should be passed in. Once the transaction has been sufficiently
// confirmed, the channel will be marked as fully closed within the database,
// and any clients will be notified of updates to the closing state.
func (p *peer) finalizeChanClosure(chanCloser *channelCloser) {
func (p *peer) finalizeChanClosure(chanCloser *chancloser.ChanCloser) {
closeReq := chanCloser.CloseRequest()
// First, we'll clear all indexes related to the channel in question.

@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
)
@ -642,7 +643,7 @@ func TestChooseDeliveryScript(t *testing.T) {
userScript: script1,
shutdownScript: script2,
expectedScript: nil,
expectedError: ErrUpfrontShutdownScriptMismatch,
expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
},
{
name: "Only upfront script",
@ -733,7 +734,7 @@ func TestCustomShutdownScript(t *testing.T) {
name: "Shutdown set, user script different",
update: setShutdown,
userCloseScript: []byte("different addr"),
expectedError: ErrUpfrontShutdownScriptMismatch,
expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
},
}

@ -24,6 +24,7 @@ import (
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/shachain"
@ -441,7 +442,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
activeChannels: make(map[lnwire.ChannelID]*lnwallet.LightningChannel),
newChannels: make(chan *newChannelMsg, 1),
activeChanCloses: make(map[lnwire.ChannelID]*channelCloser),
activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser),
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
chanCloseMsgs: make(chan *closeMsg),