diff --git a/lnwallet/chancloser/rbf_coop_states.go b/lnwallet/chancloser/rbf_coop_states.go new file mode 100644 index 000000000..764cdecb2 --- /dev/null +++ b/lnwallet/chancloser/rbf_coop_states.go @@ -0,0 +1,756 @@ +package chancloser + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/protofsm" +) + +var ( + // ErrInvalidStateTransition is returned when we receive an unexpected + // event for a given state. + ErrInvalidStateTransition = fmt.Errorf("invalid state transition") + + // ErrTooManySigs is returned when we receive too many sigs from the + // remote party in the ClosingSigs message. + ErrTooManySigs = fmt.Errorf("too many sigs received") + + // ErrNoSig is returned when we receive no sig from the remote party. + ErrNoSig = fmt.Errorf("no sig received") + + // ErrUnknownFinalBalance is returned if we're unable to determine the + // final channel balance after a flush. + ErrUnknownFinalBalance = fmt.Errorf("unknown final balance") + + // ErrRemoteCannotPay is returned if the remote party cannot pay the + // pay for the fees when it sends a signature. + ErrRemoteCannotPay = fmt.Errorf("remote cannot pay fees") + + // ErrNonFinalSequence is returned if we receive a non-final sequence + // from the remote party for their signature. + ErrNonFinalSequence = fmt.Errorf("received non-final sequence") + + // ErrCloserNoClosee is returned if our balance is dust, but the remote + // party includes our output. + ErrCloserNoClosee = fmt.Errorf("expected CloserNoClosee sig") + + // ErrCloserAndClosee is returned when we expect a sig covering both + // outputs, it isn't present. + ErrCloserAndClosee = fmt.Errorf("expected CloserAndClosee sig") +) + +// ProtocolEvent is a special interface used to create the equivalent of a +// sum-type, but using a "sealed" interface. Protocol events can be used as +// input to trigger a state transition, and also as output to trigger a new set +// of events into the very same state machine. +type ProtocolEvent interface { + protocolSealed() +} + +// ProtocolEvents is a special type constraint that enumerates all the possible +// protocol events. This is used mainly as type-level documentation, and may +// also be useful to constraint certain state transition functions. +type ProtocolEvents interface { + SendShutdown | ShutdownReceived | ShutdownComplete | ChannelFlushed | + SendOfferEvent | OfferReceivedEvent | LocalSigReceived | + SpendEvent +} + +// SpendEvent indicates that a transaction spending the funding outpoint has +// been confirmed in the main chain. +type SpendEvent struct { + // Tx is the spending transaction that has been confirmed. + Tx *wire.MsgTx + + // BlockHeight is the height of the block that confirmed the + // transaction. + BlockHeight uint32 +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *SpendEvent) protocolSealed() {} + +// SendShutdown indicates that the user wishes to co-op close the channel, so we +// should send a new shutdown message to the remote party. +// +// transition: +// - fromState: ChannelActive +// - toState: ChannelFlushing +type SendShutdown struct { + // DeliveryAddr is the address we'd like to receive the funds to. If + // None, then a new addr will be generated. + DeliveryAddr fn.Option[lnwire.DeliveryAddress] + + // IdealFeeRate is the ideal fee rate we'd like to use for the closing + // attempt. + IdealFeeRate chainfee.SatPerVByte +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *SendShutdown) protocolSealed() {} + +// ShutdownReceived indicates that we received a shutdown event so we need to +// enter the flushing state. +// +// transition: +// - fromState: ChannelActive +// - toState: ChannelFlushing +type ShutdownReceived struct { + // ShutdownScript is the script the remote party wants to use to + // shutdown. + ShutdownScript lnwire.DeliveryAddress + + // BlockHeight is the height at which the shutdown message was + // received. This is used for channel leases to determine if a co-op + // close can occur. + BlockHeight uint32 +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *ShutdownReceived) protocolSealed() {} + +// ShutdownComplete is an event that indicates the channel has been fully +// shutdown. At this point, we'll go to the ChannelFlushing state so we can +// wait for all pending updates to be gone from the channel. +// +// transition: +// - fromState: ShutdownPending +// - toState: ChannelFlushing +type ShutdownComplete struct { +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *ShutdownComplete) protocolSealed() {} + +// ShutdownBalances holds the local+remote balance once the channel has been +// fully flushed. +type ShutdownBalances struct { + // LocalBalance is the local balance of the channel. + LocalBalance lnwire.MilliSatoshi + + // RemoteBalance is the remote balance of the channel. + RemoteBalance lnwire.MilliSatoshi +} + +// unknownBalance is a special variable used to denote an unknown channel +// balance (channel not fully flushed yet). +var unknownBalance = ShutdownBalances{} + +// ChannelFlushed is an event that indicates the channel has been fully flushed +// can we can now start closing negotiation. +// +// transition: +// - fromState: ChannelFlushing +// - toState: ClosingNegotiation +type ChannelFlushed struct { + // FreshFlush indicates if this is the first time the channel has been + // flushed, or if this is a flush as part of an RBF iteration. + FreshFlush bool + + // ShutdownBalances is the balances of the channel once it has been + // flushed. We tie this to the ChannelFlushed state as this may not be + // the same as the starting value. + ShutdownBalances +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (c *ChannelFlushed) protocolSealed() {} + +// SendOfferEvent is a self-triggered event that transitions us from the +// LocalCloseStart state to the LocalOfferSent state. This kicks off the new +// signing process for the co-op close process. +// +// transition: +// - fromState: LocalCloseStart +// - toState: LocalOfferSent +type SendOfferEvent struct { + // TargetFeeRate is the fee rate we'll use for the closing transaction. + TargetFeeRate chainfee.SatPerVByte +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *SendOfferEvent) protocolSealed() {} + +// LocalSigReceived is an event that indicates we've received a signature from +// the remote party, which signs our the co-op close transaction at our +// specified fee rate. +// +// transition: +// - fromState: LocalOfferSent +// - toState: ClosePending +type LocalSigReceived struct { + // SigMsg is the sig message we received from the remote party. + SigMsg lnwire.ClosingSig +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *LocalSigReceived) protocolSealed() {} + +// OfferReceivedEvent is an event that indicates we've received an offer from +// the remote party. This applies to the RemoteCloseStart state. +// +// transition: +// - fromState: RemoteCloseStart +// - toState: ClosePending +type OfferReceivedEvent struct { + // SigMsg is the signature message we received from the remote party. + SigMsg lnwire.ClosingComplete +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (s *OfferReceivedEvent) protocolSealed() {} + +// CloseSigner is an interface that abstracts away the details of the signing +// new coop close transactions. +type CloseSigner interface { + // CreateCloseProposal creates a new co-op close proposal in the form + // of a valid signature, the chainhash of the final txid, and our final + // balance in the created state. + CreateCloseProposal(proposedFee btcutil.Amount, + localDeliveryScript []byte, remoteDeliveryScript []byte, + closeOpt ...lnwallet.ChanCloseOpt, + ) ( + input.Signature, *chainhash.Hash, btcutil.Amount, error) + + // CompleteCooperativeClose persistently "completes" the cooperative + // close by producing a fully signed co-op close transaction. + CompleteCooperativeClose(localSig, remoteSig input.Signature, + localDeliveryScript, remoteDeliveryScript []byte, + proposedFee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt, + ) (*wire.MsgTx, btcutil.Amount, error) +} + +// ChanStateObserver is an interface used to observe state changes that occur +// in a channel. This can be used to figure out if we're able to send a +// shutdown message or not. +type ChanStateObserver interface { + // 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. + NoDanglingUpdates() bool + + // DisableIncomingAdds instructs the channel link to disable process new + // incoming add messages. + DisableIncomingAdds() error + + // DisableOutgoingAdds instructs the channel link to disable process + // new outgoing add messages. + DisableOutgoingAdds() error + + // MarkCoopBroadcasted persistently marks that the channel close + // transaction has been broadcast. + MarkCoopBroadcasted(*wire.MsgTx, bool) error + + // 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(deliveryAddr []byte, isInitiator bool) error + + // 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. + FinalBalances() fn.Option[ShutdownBalances] +} + +// Environment is a set of dependencies that a state machine may need to carry +// out the logic for a given state transition. All fields are to be considered +// immutable, and will be fixed for the lifetime of the state machine. +type Environment struct { + // ChainParams is the chain parameters for the channel. + ChainParams chaincfg.Params + + // ChanPeer is the peer we're attempting to close the channel with. + ChanPeer btcec.PublicKey + + // ChanPoint is the channel point of the active channel. + ChanPoint wire.OutPoint + + // ChanID is the channel ID of the channel we're attempting to close. + ChanID lnwire.ChannelID + + // ShortChanID is the short channel ID of the channel we're attempting + // to close. + Scid lnwire.ShortChannelID + + // ChanType is the type of channel we're attempting to close. + ChanType channeldb.ChannelType + + // DefaultFeeRate is the fee we'll use for the closing transaction if + // the user didn't specify an ideal fee rate. This may happen if the + // remote party is the one that initiates the co-op close. + DefaultFeeRate chainfee.SatPerVByte + + // ThawHeight is the height at which the channel will be thawed. If + // this is None, then co-op close can occur at any moment. + ThawHeight fn.Option[uint32] + + // RemoteUprontShutdown is the upfront shutdown addr of the remote + // party. We'll use this to validate if the remote peer is authorized to + // close the channel with the sent addr or not. + RemoteUpfrontShutdown fn.Option[lnwire.DeliveryAddress] + + // LocalUprontShutdown is our upfront shutdown address. If Some, then + // we'll default to using this. + LocalUpfrontShutdown fn.Option[lnwire.DeliveryAddress] + + // NewDeliveryScript is a function that returns a new delivery script. + // This is used if we don't have an upfront shutdown addr, and no addr + // was specified at closing time. + NewDeliveryScript func() (lnwire.DeliveryAddress, error) + + // FeeEstimator is the fee estimator we'll use to determine the fee in + // satoshis we'll pay given a local and/or remote output. + FeeEstimator CoopFeeEstimator + + // ChanObserver is an interface used to observe state changes to the + // channel. We'll use this to figure out when/if we can send certain + // messages. + ChanObserver ChanStateObserver + + // CloseSigner is the signer we'll use to sign the close transaction. + // This is a part of the ChannelFlushed state, as the channel state + // we'll be signing can only be determined once the channel has been + // flushed. + CloseSigner CloseSigner +} + +// Name returns the name of the environment. This is used to uniquely identify +// the environment of related state machines. For this state machine, the name +// is based on the channel ID. +func (e *Environment) Name() string { + return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint) +} + +// CloseStateTransition is the StateTransition type specific to the coop close +// state machine. +// +//nolint:ll +type CloseStateTransition = protofsm.StateTransition[ProtocolEvent, *Environment] + +// ProtocolState is our sum-type ish interface that represents the current +// protocol state. +type ProtocolState interface { + // protocolStateSealed is a special method that is used to seal the + // interface (only types in this package can implement it). + protocolStateSealed() + + // IsTerminal returns true if the target state is a terminal state. + IsTerminal() bool + + // ProcessEvent takes a protocol event, and implements a state + // transition for the state. + ProcessEvent(ProtocolEvent, *Environment) (*CloseStateTransition, error) +} + +// AsymmetricPeerState is an extension of the normal ProtocolState interface +// that gives a caller a hit on if the target state should process an incoming +// event or not. +type AsymmetricPeerState interface { + ProtocolState + + // ShouldRouteTo returns true if the target state should process the + // target event. + ShouldRouteTo(ProtocolEvent) bool +} + +// ProtocolStates is a special type constraint that enumerates all the possible +// protocol states. +type ProtocolStates interface { + ChannelActive | ShutdownPending | ChannelFlushing | ClosingNegotiation | + LocalCloseStart | LocalOfferSent | RemoteCloseStart | + ClosePending | CloseFin +} + +// ChannelActive is the base state for the channel closer state machine. In +// this state, we haven't begun the shutdown process yet, so the channel is +// still active. Receiving the ShutdownSent or ShutdownReceived events will +// transition us to the ChannelFushing state. +// +// When we transition to this state, we emit a DaemonEvent to send the shutdown +// message if we received one ourselves. Alternatively, we may send out a new +// shutdown if we're initiating it for the very first time. +// +// transition: +// - fromState: None +// - toState: ChannelFlushing +// +// input events: +// - SendShutdown +// - ShutdownReceived +type ChannelActive struct { +} + +// IsTerminal returns true if the target state is a terminal state. +func (c *ChannelActive) IsTerminal() bool { + return false +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (c *ChannelActive) protocolStateSealed() {} + +// ShutdownScripts is a set of scripts that we'll use to co-op close the +// channel. +type ShutdownScripts struct { + // LocalDeliveryScript is the script that we'll send our settled + // channel funds to. + LocalDeliveryScript lnwire.DeliveryAddress + + // RemoteDeliveryScript is the script that we'll send the remote + // party's settled channel funds to. + RemoteDeliveryScript lnwire.DeliveryAddress +} + +// ShutdownPending is the state we enter into after we've sent or received the +// shutdown message. If we sent the shutdown, then we'll wait for the remote +// party to send a shutdown. Otherwise, if we received it, then we'll send our +// shutdown then go to the next state. +// +// transition: +// - fromState: ChannelActive +// - toState: ChannelFlushing +// +// input events: +// - SendShutdown +// - ShutdownReceived +type ShutdownPending struct { + // ShutdownScripts store the set of scripts we'll use to initiate a coop + // close. + ShutdownScripts + + // IdealFeeRate is the ideal fee rate we'd like to use for the closing + // attempt. + IdealFeeRate fn.Option[chainfee.SatPerVByte] +} + +// IsTerminal returns true if the target state is a terminal state. +func (s *ShutdownPending) IsTerminal() bool { + return false +} + +// protocolStateSealed indicates that this struct is a ProtocolEvent instance. +func (s *ShutdownPending) protocolStateSealed() {} + +// ChannelFlushing is the state we enter into after we've received or sent a +// shutdown message. In this state, we wait the ChannelFlushed event, after +// which we'll transition to the CloseReady state. +// +// transition: +// - fromState: ShutdownPending +// - toState: ClosingNegotiation +// +// input events: +// - ShutdownComplete +// - ShutdownReceived +type ChannelFlushing struct { + // EarlyRemoteOffer is the offer we received from the remote party + // before we obtained the local channel flushed event. We'll stash this + // to process later. + EarlyRemoteOffer fn.Option[OfferReceivedEvent] + + // ShutdownScripts store the set of scripts we'll use to initiate a coop + // close. + ShutdownScripts + + // IdealFeeRate is the ideal fee rate we'd like to use for the closing + // transaction. Once the channel has been flushed, we'll use this as + // our target fee rate. + IdealFeeRate fn.Option[chainfee.SatPerVByte] +} + +// protocolStateSealed indicates that this struct is a ProtocolEvent instance. +func (c *ChannelFlushing) protocolStateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (c *ChannelFlushing) IsTerminal() bool { + return false +} + +// ClosingNegotiation is the state we transition to once the channel has been +// flushed. This is actually a composite state that contains one for each side +// of the channel, as the closing process is asymmetric. Once either of the +// peer states reaches the CloseFin state, then the channel is fully closed, +// and we'll transition to that terminal state. +// +// transition: +// - fromState: ChannelFlushing +// - toState: CloseFin +// +// input events: +// - ChannelFlushed +type ClosingNegotiation struct { + // PeerStates is a composite state that contains the state for both the + // local and remote parties. Our usage of Dual makes this a special + // state that allows us to treat two states as a single state. We'll use + // the ShouldRouteTo method to determine which state route incoming + // events to. + PeerState lntypes.Dual[AsymmetricPeerState] +} + +// IsTerminal returns true if the target state is a terminal state. +func (c *ClosingNegotiation) IsTerminal() bool { + return false +} + +// protocolSealed indicates that this struct is a ProtocolEvent instance. +func (c *ClosingNegotiation) protocolStateSealed() {} + +// CloseChannelTerms is a set of terms that we'll use to close the channel. This +// includes the balances of the channel, and the scripts we'll use to send each +// party's funds to. +type CloseChannelTerms struct { + ShutdownScripts + + ShutdownBalances +} + +// DeriveCloseTxOuts takes the close terms, and returns the local and remote tx +// out for the close transaction. If an output is dust, then it'll be nil. +// +// TODO(roasbeef): add func for w/e heuristic to not manifest own output? +func (c *CloseChannelTerms) DeriveCloseTxOuts() (*wire.TxOut, *wire.TxOut) { + //nolint:ll + deriveTxOut := func(balance btcutil.Amount, pkScript []byte) *wire.TxOut { + dustLimit := lnwallet.DustLimitForSize(len(pkScript)) + if balance >= dustLimit { + return &wire.TxOut{ + PkScript: pkScript, + Value: int64(balance), + } + } + + return nil + } + + localTxOut := deriveTxOut( + c.LocalBalance.ToSatoshis(), + c.LocalDeliveryScript, + ) + remoteTxOut := deriveTxOut( + c.RemoteBalance.ToSatoshis(), + c.RemoteDeliveryScript, + ) + + return localTxOut, remoteTxOut +} + +// RemoteAmtIsDust returns true if the remote output is dust. +func (c *CloseChannelTerms) RemoteAmtIsDust() bool { + return c.RemoteBalance.ToSatoshis() < lnwallet.DustLimitForSize( + len(c.RemoteDeliveryScript), + ) +} + +// LocalAmtIsDust returns true if the local output is dust. +func (c *CloseChannelTerms) LocalAmtIsDust() bool { + return c.LocalBalance.ToSatoshis() < lnwallet.DustLimitForSize( + len(c.LocalDeliveryScript), + ) +} + +// LocalCanPayFees returns true if the local party can pay the absolute fee +// from their local settled balance. +func (c *CloseChannelTerms) LocalCanPayFees(absoluteFee btcutil.Amount) bool { + return c.LocalBalance.ToSatoshis() >= absoluteFee +} + +// RemoteCanPayFees returns true if the remote party can pay the absolute fee +// from their remote settled balance. +func (c *CloseChannelTerms) RemoteCanPayFees(absoluteFee btcutil.Amount) bool { + return c.RemoteBalance.ToSatoshis() >= absoluteFee +} + +// LocalCloseStart is the state we enter into after we've received or sent +// shutdown, and the channel has been flushed. In this state, we'll emit a new +// event to send our offer to drive the rest of the process. +// +// transition: +// - fromState: ChannelFlushing +// - toState: LocalOfferSent +// +// input events: +// - SendOfferEvent +type LocalCloseStart struct { + CloseChannelTerms +} + +// ShouldRouteTo returns true if the target state should process the target +// event. +func (l *LocalCloseStart) ShouldRouteTo(event ProtocolEvent) bool { + switch event.(type) { + case *SendOfferEvent: + return true + default: + return false + } +} + +// IsTerminal returns true if the target state is a terminal state. +func (l *LocalCloseStart) IsTerminal() bool { + return false +} + +// protocolStateaSealed indicates that this struct is a ProtocolEvent instance. +func (l *LocalCloseStart) protocolStateSealed() {} + +// LocalOfferSent is the state we transition to after we reveiver the +// SendOfferEvent in the LocalCloseStart state. With this state we send our +// offer to the remote party, then await a sig from them which concludes the +// local cooperative close process. +// +// transition: +// - fromState: LocalCloseStart +// - toState: ClosePending +// +// input events: +// - LocalSigReceived +type LocalOfferSent struct { + CloseChannelTerms + + // ProposedFee is the fee we proposed to the remote party. + ProposedFee btcutil.Amount + + // LocalSig is the signature we sent to the remote party. + LocalSig lnwire.Sig +} + +// ShouldRouteTo returns true if the target state should process the target +// event. +func (l *LocalOfferSent) ShouldRouteTo(event ProtocolEvent) bool { + switch event.(type) { + case *LocalSigReceived: + return true + default: + return false + } +} + +// protocolStateaSealed indicates that this struct is a ProtocolEvent instance. +func (l *LocalOfferSent) protocolStateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (l *LocalOfferSent) IsTerminal() bool { + return false +} + +// ClosePending is the state we enter after concluding the negotiation for the +// remote or local state. At this point, given a confirmation notification we +// can terminate the process. Otherwise, we can receive a fresh CoopCloseReq to +// go back to the very start. +// +// transition: +// - fromState: LocalOfferSent || RemoteCloseStart +// - toState: CloseFin +// +// input events: +// - LocalSigReceived +// - OfferReceivedEvent +type ClosePending struct { + // CloseTx is the pending close transaction. + CloseTx *wire.MsgTx +} + +// ShouldRouteTo returns true if the target state should process the target +// event. +func (c *ClosePending) ShouldRouteTo(event ProtocolEvent) bool { + switch event.(type) { + case *SpendEvent: + return true + default: + return false + } +} + +// protocolStateSealed indicates that this struct is a ProtocolEvent instance. +func (c *ClosePending) protocolStateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (c *ClosePending) IsTerminal() bool { + return true +} + +// CloseFin is the terminal state for the channel closer state machine. At this +// point, the close tx has been confirmed on chain. +type CloseFin struct { + // ConfirmedTx is the transaction that confirmed the channel close. + ConfirmedTx *wire.MsgTx +} + +// protocolStateSealed indicates that this struct is a ProtocolEvent instance. +func (c *CloseFin) protocolStateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (c *CloseFin) IsTerminal() bool { + return true +} + +// RemoteCloseStart is similar to the LocalCloseStart, but is used to drive the +// process of signing an offer for the remote party +// +// transition: +// - fromState: ChannelFlushing +// - toState: ClosePending +type RemoteCloseStart struct { + CloseChannelTerms +} + +// ShouldRouteTo returns true if the target state should process the target +// event. +func (l *RemoteCloseStart) ShouldRouteTo(event ProtocolEvent) bool { + switch event.(type) { + case *OfferReceivedEvent: + return true + default: + return false + } +} + +// protocolStateSealed indicates that this struct is a ProtocolEvent instance. +func (l *RemoteCloseStart) protocolStateSealed() {} + +// IsTerminal returns true if the target state is a terminal state. +func (l *RemoteCloseStart) IsTerminal() bool { + return false +} + +// RbfChanCloser is a state machine that handles the RBF-enabled cooperative +// channel close protocol. +type RbfChanCloser = protofsm.StateMachine[ProtocolEvent, *Environment] + +// RbfChanCloserCfg is a configuration struct that is used to initialize a new +// RBF chan closer state machine. +type RbfChanCloserCfg = protofsm.StateMachineCfg[ProtocolEvent, *Environment] + +// RbfSpendMapper is a type used to map the generic spend event to one specific +// to this package. +type RbfSpendMapper = protofsm.SpendMapper[ProtocolEvent] + +func SpendMapper(spendEvent *chainntnfs.SpendDetail) ProtocolEvent { + return &SpendEvent{ + Tx: spendEvent.SpendingTx, + BlockHeight: uint32(spendEvent.SpendingHeight), + } +} + +// RbfMsgMapperT is a type used to map incoming wire messages to protocol +// events. +type RbfMsgMapperT = protofsm.MsgMapper[ProtocolEvent] + +// RbfState is a type alias for the state of the RBF channel closer. +type RbfState = protofsm.State[ProtocolEvent, *Environment] + +// RbfEvent is a type alias for the event type of the RBF channel closer. +type RbfEvent = protofsm.EmittedEvent[ProtocolEvent]