From 7b08899cdb82b2bcdc071ebf4fa365a3203b0377 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Thu, 18 Jan 2024 14:58:44 -0800 Subject: [PATCH] chancloser: cache remote ClosingSigned to deal with race --- lnwallet/chancloser/chancloser.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index b47308006..5d84c64a7 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -203,6 +203,11 @@ type ChanCloser struct { // locallyInitiated is true if we initiated the channel close. locallyInitiated bool + + // cachedClosingSigned is a cached copy of a received ClosingSigned that + // we use to handle a specific race condition caused by the independent + // message processing queues. + cachedClosingSigned fn.Option[lnwire.ClosingSigned] } // calcCoopCloseFee computes an "ideal" absolute co-op close fee given the @@ -635,7 +640,18 @@ func (c *ChanCloser) BeginNegotiation() ( c.state = closeFeeNegotiation if !c.cfg.Channel.IsInitiator() { - return noClosingSigned, nil + // By default this means we do nothing, but we do want + // to check if we have a cached remote offer to process. + // If we do, we'll process it here. + res := noClosingSigned + err = nil + c.cachedClosingSigned.WhenSome( + func(cs lnwire.ClosingSigned) { + res, err = c.ReceiveClosingSigned(cs) + }, + ) + + return res, err } // We'll craft our initial close proposal in order to keep the @@ -657,6 +673,8 @@ func (c *ChanCloser) BeginNegotiation() ( // ReceiveClosingSigned is a method that should be called whenever we receive a // ClosingSigned message from the wire. It may or may not return a ClosingSigned // of our own to send back to the remote. +// +//nolint:funlen func (c *ChanCloser) ReceiveClosingSigned( msg lnwire.ClosingSigned, ) (fn.Option[lnwire.ClosingSigned], error) { @@ -664,6 +682,13 @@ func (c *ChanCloser) ReceiveClosingSigned( noClosing := fn.None[lnwire.ClosingSigned]() switch c.state { + case closeAwaitingFlush: + // If we hit this case it either means there's a protocol + // violation or that our chanCloser received the remote offer + // before the link finished processing the channel flush. + c.cachedClosingSigned = fn.Some(msg) + return fn.None[lnwire.ClosingSigned](), nil + case closeFeeNegotiation: // If this is a taproot channel, then it MUST have a partial // signature set at this point.