Commit Graph

13 Commits

Author SHA1 Message Date
Olaoluwa Osuntokun
0ff0ac84f6
protofsm: fix race in state machine executor tests
In this commit, we fix an existing race in the new `protofsm` state
machine executor tests.

The race would appear as such:
```
--- FAIL: TestStateMachineMsgMapper (0.00s)
    state_machine_test.go:165:
        Error Trace:/home/runner/work/lnd/lnd/protofsm/state_machine_test.go:165
                    /home/runner/work/lnd/lnd/protofsm/state_machine_test.go:451
        Error:      Object expected to be of type *protofsm.dummyStateStart, but was *protofsm.dummyStateFin
        Test:       TestStateMachineMsgMapper
FAIL
FAILgithub.com/lightningnetwork/lnd/protofsm0.116s
FAIL
```

This race condition was triggered as before we would start the state
machine _then_ register for notifications. In `Start` we emit the
starting event, then enter the main loop. If that event gets emitted
before our subscription, then we'll miss the event, as the terminal
event will be the only one received.

We fix this by registering for the events before the daemon has started.
2024-11-26 18:58:53 -06:00
Olaoluwa Osuntokun
2e3c0b2a7d
protofsm: use new fn.GoroutineManager to manage goroutines
This fixes an isuse that can occur when we have concurrent calls to
`Stop` while the state machine is driving forward.
2024-11-18 20:49:01 -08:00
Olaoluwa Osuntokun
6de0615cd5
protofsm: allow multiple internal events to be emitted
In this commit, we update the execution logic to allow multiple internal
events to be emitted. This is useful to handle potential out of order
state transitions, as they can be cached, then emitted once the relevant
pre-conditions have been met.
2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
847c1a789d
protofsm: add SpendMapper to craft custom spend events
In this commit, we add the SpendMapper which allows callers to create
custom spent events. Before this commit, the caller would be able to
have an event sent to them in the case a spend happens, but that event
wouldn't have any of the relevant spend details.

With this new addition, the caller can specify how to take a generic
spend event, and transform it into the state machine specific spend
event.
2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
d805c0fc3c
protofsm: add CustomPollInterval for mocking purposes
Adding this makes a state machine easier to unit test, as the caller can
specify a custom polling interval.
2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
35ea05d5dc
protofsm: add ErrorReporter interface
We'll use this to be able to signal to a caller that a critical error
occurred during the state transition.
2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
96a98bc071
protofsm: add logging 2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
44f035330b
protofsm: add a Name() method to the env
This'll be used later to uniquely identify state machines for
routing/dispatch purposes.
2024-11-18 20:49:00 -08:00
Olaoluwa Osuntokun
424ae09631
protofsm: add ability for state machine to consume wire msgs
In this commit, we add the ability for the state machine to consume wire
messages. This'll allow the creation of a new generic message router
that takes the place of the current peer `readHandler` in an upcoming
commit.
2024-11-18 20:48:59 -08:00
Olaoluwa Osuntokun
bf10e31167
protofsm: convert state machine args into config 2024-11-18 20:48:59 -08:00
Olaoluwa Osuntokun
d17e737558
protofsm: add optional daemon event on init
In this commit, we add an optional daemon event that can be specified to
dispatch during init. This is useful for instances where before we
start, we want to make sure we have a registered spend/conf notification
before normal operation starts.

We also add new unit tests to cover this, and the prior spend/conf event
additions.
2024-11-18 20:48:59 -08:00
Olaoluwa Osuntokun
7f69ceb2d4
protofsm: add daemon events for spend+conf registration 2024-11-18 20:48:59 -08:00
Olaoluwa Osuntokun
3bae7f32cd
protofsm: add new package for driving generic protocol FSMs
In this PR, we create a new package, `protofsm` which is intended to
abstract away from something we've done dozens of time in the daemon:
create a new event-drive protocol FSM. One example of this is the co-op
close state machine, and also the channel state machine itself.

This packages picks out the common themes of:

  * clear states and transitions between them
  * calling out to special daemon adapters for I/O such as transaction
    broadcast or sending a message to a peer
  * cleaning up after state machine execution
  * notifying relevant callers of updates to the state machine

The goal of this PR, is that devs can now implement a state machine
based off of this primary interface:
```go
// State defines an abstract state along, namely its state transition function
// that takes as input an event and an environment, and returns a state
// transition (next state, and set of events to emit). As state can also either
// be terminal, or not, a terminal event causes state execution to halt.
type State[Event any, Env Environment] interface {
	// ProcessEvent takes an event and an environment, and returns a new
	// state transition. This will be iteratively called until either a
	// terminal state is reached, or no further internal events are
	// emitted.
	ProcessEvent(event Event, env Env) (*StateTransition[Event, Env], error)

	// IsTerminal returns true if this state is terminal, and false otherwise.
	IsTerminal() bool
}
```

With their focus being _only_ on each state transition, rather than all
the boiler plate involved (processing new events, advancing to
completion, doing I/O, etc, etc).

Instead, they just make their states, then create the state machine
given the starting state and env. The only other custom component needed
is something capable of mapping wire messages or other events from the
"outside world" into the domain of the state machine.

The set of types is based on a pseudo sum type system wherein you
declare an interface, make the sole method private, then create other
instances based on that interface. This restricts call sites (must pass
in that interface) type, and with some tooling, exhaustive matching can
also be enforced via a linter.

The best way to get a hang of the pattern proposed here is to check out
the tests. They make a mock state machine, and then use the new executor
to drive it to completion. You'll also get a view of how the code will
actually look, with the focus being on the: input event, current state,
and output transition (can also emit events to drive itself forward).
2024-11-18 20:48:57 -08:00