@@ -54,6 +54,20 @@ type registerConf struct {
54
54
func (r * registerConf ) dummy () {
55
55
}
56
56
57
+ type spendDetailsEvent struct {
58
+ spenderTxHash chainhash.Hash
59
+ spendingHeight int32
60
+ }
61
+
62
+ func (s * spendDetailsEvent ) dummy () {
63
+ }
64
+
65
+ type registerSpend struct {
66
+ }
67
+
68
+ func (r * registerSpend ) dummy () {
69
+ }
70
+
57
71
type dummyEnv struct {
58
72
mock.Mock
59
73
}
88
102
func (d * dummyStateStart ) ProcessEvent (event dummyEvents , env * dummyEnv ,
89
103
) (* StateTransition [dummyEvents , * dummyEnv ], error ) {
90
104
91
- switch event .(type ) {
105
+ switch newEvent := event .(type ) {
92
106
case * goToFin :
93
107
return & StateTransition [dummyEvents , * dummyEnv ]{
94
108
NextState : & dummyStateFin {},
@@ -180,14 +194,59 @@ func (d *dummyStateStart) ProcessEvent(event dummyEvents, env *dummyEnv,
180
194
// This event contains details from the confirmation and signals us to
181
195
// transition to the final state.
182
196
case * confDetailsEvent :
183
- eventDetails := event .(* confDetailsEvent )
184
-
185
- // We received the mapped confirmation details, transition to the
186
- // confirmed state.
197
+ // We received the mapped confirmation details, transition to
198
+ // the confirmed state.
187
199
return & StateTransition [dummyEvents , * dummyEnv ]{
188
200
NextState : & dummyStateConfirmed {
189
- blockHash : eventDetails .blockHash ,
190
- blockHeight : eventDetails .blockHeight ,
201
+ blockHash : newEvent .blockHash ,
202
+ blockHeight : newEvent .blockHeight ,
203
+ },
204
+ }, nil
205
+
206
+ // This state will emit a RegisterSpend event which uses a mapper to
207
+ // transition to the spent state upon spend detection.
208
+ case * registerSpend :
209
+ spendMapper := func (
210
+ spend * chainntnfs.SpendDetail ) dummyEvents {
211
+
212
+ // Map the spend details into our custom event.
213
+ return & spendDetailsEvent {
214
+ spenderTxHash : * spend .SpenderTxHash ,
215
+ spendingHeight : spend .SpendingHeight ,
216
+ }
217
+ }
218
+
219
+ regSpendEvent := & RegisterSpend [dummyEvents ]{
220
+ OutPoint : wire.OutPoint {Hash : chainhash.Hash {3 }},
221
+ PkScript : []byte {0x03 },
222
+ HeightHint : 300 ,
223
+ PostSpendEvent : fn.Some [SpendMapper [dummyEvents ]](
224
+ spendMapper ,
225
+ ),
226
+ }
227
+
228
+ return & StateTransition [dummyEvents , * dummyEnv ]{
229
+ // Stay in the start state until the spend event is
230
+ // received and mapped.
231
+ NextState : & dummyStateStart {
232
+ canSend : d .canSend ,
233
+ },
234
+ NewEvents : fn .Some (EmittedEvent [dummyEvents ]{
235
+ ExternalEvents : DaemonEventSet {
236
+ regSpendEvent ,
237
+ },
238
+ }),
239
+ }, nil
240
+
241
+ // This event contains details from the spend notification and signals
242
+ // us to transition to the spent state.
243
+ case * spendDetailsEvent :
244
+ // We received the mapped spend details, transition to the
245
+ // spent state.
246
+ return & StateTransition [dummyEvents , * dummyEnv ]{
247
+ NextState : & dummyStateSpent {
248
+ spenderTxHash : newEvent .spenderTxHash ,
249
+ spendingHeight : newEvent .spendingHeight ,
191
250
},
192
251
}, nil
193
252
}
@@ -240,6 +299,28 @@ func (d *dummyStateConfirmed) IsTerminal() bool {
240
299
return true
241
300
}
242
301
302
+ type dummyStateSpent struct {
303
+ spenderTxHash chainhash.Hash
304
+ spendingHeight int32
305
+ }
306
+
307
+ func (d * dummyStateSpent ) String () string {
308
+ return "dummyStateSpent"
309
+ }
310
+
311
+ func (d * dummyStateSpent ) ProcessEvent (event dummyEvents , env * dummyEnv ,
312
+ ) (* StateTransition [dummyEvents , * dummyEnv ], error ) {
313
+
314
+ // This is a terminal state, no further transitions.
315
+ return & StateTransition [dummyEvents , * dummyEnv ]{
316
+ NextState : d ,
317
+ }, nil
318
+ }
319
+
320
+ func (d * dummyStateSpent ) IsTerminal () bool {
321
+ return true
322
+ }
323
+
243
324
func assertState [Event any , Env Environment ](t * testing.T ,
244
325
m * StateMachine [Event , Env ], expectedState State [Event , Env ]) {
245
326
@@ -565,8 +646,92 @@ func TestStateMachineConfMapper(t *testing.T) {
565
646
// Assert that the details from the confirmation event were correctly
566
647
// propagated to the final state.
567
648
finalStateDetails := finalState .(* dummyStateConfirmed )
568
- require .Equal (t , simulatedConf .BlockHash , & finalStateDetails .blockHash )
569
- require .Equal (t , simulatedConf .BlockHeight , finalStateDetails .blockHeight )
649
+ require .Equal (t ,
650
+ * simulatedConf .BlockHash , finalStateDetails .blockHash ,
651
+ )
652
+ require .Equal (t ,
653
+ simulatedConf .BlockHeight , finalStateDetails .blockHeight ,
654
+ )
655
+
656
+ adapters .AssertExpectations (t )
657
+ env .AssertExpectations (t )
658
+ }
659
+
660
+ // TestStateMachineSpendMapper tests that the state machine is able to properly
661
+ // map the spend event into a custom event that can be used to trigger a state
662
+ // transition.
663
+ func TestStateMachineSpendMapper (t * testing.T ) {
664
+ t .Parallel ()
665
+ ctx := context .Background ()
666
+
667
+ // Create the state machine.
668
+ env := & dummyEnv {}
669
+ startingState := & dummyStateStart {}
670
+ adapters := newDaemonAdapters ()
671
+
672
+ cfg := StateMachineCfg [dummyEvents , * dummyEnv ]{
673
+ Daemon : adapters ,
674
+ InitialState : startingState ,
675
+ Env : env ,
676
+ }
677
+ stateMachine := NewStateMachine (cfg )
678
+
679
+ stateSub := stateMachine .RegisterStateEvents ()
680
+ defer stateMachine .RemoveStateSub (stateSub )
681
+
682
+ stateMachine .Start (ctx )
683
+ defer stateMachine .Stop ()
684
+
685
+ // Expect the RegisterSpendNtfn call when we send the event.
686
+ targetOutpoint := & wire.OutPoint {Hash : chainhash.Hash {3 }}
687
+ targetPkScript := []byte {0x03 }
688
+ targetHeightHint := uint32 (300 )
689
+ adapters .On (
690
+ "RegisterSpendNtfn" , targetOutpoint , targetPkScript ,
691
+ targetHeightHint ,
692
+ ).Return (nil )
693
+
694
+ // Send the event that triggers RegisterSpend emission.
695
+ stateMachine .SendEvent (ctx , & registerSpend {})
696
+
697
+ // We should transition back to the starting state initially.
698
+ expectedStates := []State [dummyEvents , * dummyEnv ]{
699
+ & dummyStateStart {}, & dummyStateStart {},
700
+ }
701
+ assertStateTransitions (t , stateSub , expectedStates )
702
+
703
+ // Assert the registration call was made.
704
+ adapters .AssertExpectations (t )
705
+
706
+ // Now, simulate the spend event coming back from the notifier. Populate
707
+ // it with some data to be mapped.
708
+ simulatedSpend := & chainntnfs.SpendDetail {
709
+ SpentOutPoint : targetOutpoint ,
710
+ SpenderTxHash : & chainhash.Hash {4 },
711
+ SpendingTx : & wire.MsgTx {},
712
+ SpendingHeight : 456 ,
713
+ }
714
+ adapters .spendChan <- simulatedSpend
715
+
716
+ // This should trigger the mapper and send the spendDetailsEvent,
717
+ // transitioning us to the spent state.
718
+ expectedStates = []State [dummyEvents , * dummyEnv ]{& dummyStateSpent {}}
719
+ assertStateTransitions (t , stateSub , expectedStates )
720
+
721
+ // Final state assertion.
722
+ finalState , err := stateMachine .CurrentState ()
723
+ require .NoError (t , err )
724
+ require .IsType (t , & dummyStateSpent {}, finalState )
725
+
726
+ // Assert that the details from the spend event were correctly
727
+ // propagated to the final state.
728
+ finalStateDetails := finalState .(* dummyStateSpent )
729
+ require .Equal (t ,
730
+ * simulatedSpend .SpenderTxHash , finalStateDetails .spenderTxHash ,
731
+ )
732
+ require .Equal (t ,
733
+ simulatedSpend .SpendingHeight , finalStateDetails .spendingHeight ,
734
+ )
570
735
571
736
adapters .AssertExpectations (t )
572
737
env .AssertExpectations (t )
0 commit comments