Files
lnd/peer/chan_observer.go
Olaoluwa Osuntokun 6364e98f0e peer: conditionally create rbf coop close fsm based on feature bits
In this commit, we fully integrate the new RBF close state machine into
the peer.

For the restart case after shutdown, we can short circuit the existing
logic as the new FSM will handle retransmitting the shutdown message
itself, and doesn't need to delegate that duty to the link.

Unlike the existing state machine, we're able to restart the flow to
sign a coop close with a new higher fee rate. In this case, we can now
send multiple updates to the RPC caller, one for each newly singed coop
close transaction.

To implement the async flush case, we'll launch a new goroutine to wait
until the state machine reaches the `ChannelFlushing` state, then we'll
register the hook. We don't do this at start up, as otherwise the
channel may _already_ be flushed, triggering an invalid state
transition.
2025-03-18 11:44:20 -05:00

175 lines
5.8 KiB
Go

package peer
import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
)
// channelView is a view into the current active/global channel state machine
// for a given link.
type channelView interface {
// OweCommitment returns a boolean value reflecting whether we need to
// send out a commitment signature because there are outstanding local
// updates and/or updates in the local commit tx that aren't reflected
// in the remote commit tx yet.
OweCommitment() bool
// IsChannelClean returns true if neither side has pending commitments,
// neither side has HTLC's, and all updates are locked in irrevocably.
IsChannelClean() bool
// MarkCoopBroadcasted persistently marks that the channel close
// transaction has been broadcast.
MarkCoopBroadcasted(*wire.MsgTx, lntypes.ChannelParty) error
// StateSnapshot returns a snapshot of the current fully committed
// state within the channel.
StateSnapshot() *channeldb.ChannelSnapshot
// MarkShutdownSent persists the given ShutdownInfo. The existence of
// the ShutdownInfo represents the fact that the Shutdown message has
// been sent by us and so should be re-sent on re-establish.
MarkShutdownSent(info *channeldb.ShutdownInfo) error
}
// linkController is capable of controlling the flow out incoming/outgoing
// HTLCs to/from the link.
type linkController interface {
// DisableAdds sets the ChannelUpdateHandler state to allow/reject
// UpdateAddHtlc's in the specified direction. It returns true if the
// state was changed and false if the desired state was already set
// before the method was called.
DisableAdds(outgoing bool) bool
// IsFlushing returns true when UpdateAddHtlc's are disabled in the
// direction of the argument.
IsFlushing(direction bool) bool
}
// linkNetworkController is an interface that represents an object capable of
// managing interactions with the active channel links from the PoV of the
// gossip network.
type linkNetworkController interface {
// RequestDisable disables a channel by its channel point.
RequestDisable(wire.OutPoint, bool) error
}
// chanObserver implements the chancloser.ChanObserver interface for the
// existing LightningChannel struct/instance.
type chanObserver struct {
chanView channelView
link linkController
linkNetwork linkNetworkController
}
// newChanObserver creates a new instance of a chanObserver from an active
// channelView.
func newChanObserver(chanView channelView,
link linkController, linkNetwork linkNetworkController) *chanObserver {
return &chanObserver{
chanView: chanView,
link: link,
linkNetwork: linkNetwork,
}
}
// NoDanglingUpdates returns true if there are no dangling updates in the
// channel. In other words, there are no active update messages that haven't
// already been covered by a commit sig.
func (l *chanObserver) NoDanglingUpdates() bool {
return !l.chanView.OweCommitment()
}
// DisableIncomingAdds instructs the channel link to disable process new
// incoming add messages.
func (l *chanObserver) DisableIncomingAdds() error {
// If there's no link, then we don't need to disable any adds.
if l.link == nil {
return nil
}
disabled := l.link.DisableAdds(htlcswitch.Incoming)
if disabled {
chanPoint := l.chanView.StateSnapshot().ChannelPoint
peerLog.Debugf("ChannelPoint(%v): link already disabled",
chanPoint)
}
return nil
}
// DisableOutgoingAdds instructs the channel link to disable process new
// outgoing add messages.
func (l *chanObserver) DisableOutgoingAdds() error {
// If there's no link, then we don't need to disable any adds.
if l.link == nil {
return nil
}
_ = l.link.DisableAdds(htlcswitch.Outgoing)
return nil
}
// MarkCoopBroadcasted persistently marks that the channel close transaction
// has been broadcast.
func (l *chanObserver) MarkCoopBroadcasted(tx *wire.MsgTx, local bool) error {
return l.chanView.MarkCoopBroadcasted(tx, lntypes.Local)
}
// MarkShutdownSent persists the given ShutdownInfo. The existence of the
// ShutdownInfo represents the fact that the Shutdown message has been sent by
// us and so should be re-sent on re-establish.
func (l *chanObserver) MarkShutdownSent(deliveryAddr []byte,
isInitiator bool) error {
shutdownInfo := channeldb.NewShutdownInfo(deliveryAddr, isInitiator)
return l.chanView.MarkShutdownSent(shutdownInfo)
}
// FinalBalances is the balances of the channel once it has been flushed. If
// Some, then this indicates that the channel is now in a state where it's
// always flushed, so we can accelerate the state transitions.
func (l *chanObserver) FinalBalances() fn.Option[chancloser.ShutdownBalances] {
chanClean := l.chanView.IsChannelClean()
switch {
// If we have a link, then the balances are final if both the incoming
// and outgoing adds are disabled _and_ the channel is clean.
case l.link != nil && l.link.IsFlushing(htlcswitch.Incoming) &&
l.link.IsFlushing(htlcswitch.Outgoing) && chanClean:
fallthrough
// If we don't have a link, then this is a restart case, so the
// balances are final.
case l.link == nil:
snapshot := l.chanView.StateSnapshot()
return fn.Some(chancloser.ShutdownBalances{
LocalBalance: snapshot.LocalBalance,
RemoteBalance: snapshot.RemoteBalance,
})
// Otherwise, the link is still active and not flushed, so the balances
// aren't yet final.
default:
return fn.None[chancloser.ShutdownBalances]()
}
}
// DisableChannel disables the target channel.
func (l *chanObserver) DisableChannel() error {
op := l.chanView.StateSnapshot().ChannelPoint
return l.linkNetwork.RequestDisable(op, false)
}
// A compile-time assertion to ensure that chanObserver meets the
// chancloser.ChanStateObserver interface.
var _ chancloser.ChanStateObserver = (*chanObserver)(nil)