github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/fsm_test.go (about) 1 package initialsync 2 3 import ( 4 "errors" 5 "fmt" 6 "testing" 7 8 types "github.com/prysmaticlabs/eth2-types" 9 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 10 "github.com/prysmaticlabs/prysm/shared/testutil/require" 11 ) 12 13 func TestStateMachineManager_String(t *testing.T) { 14 tests := []struct { 15 name string 16 machines map[types.Slot]*stateMachine 17 want string 18 }{ 19 { 20 "empty epoch state list", 21 map[types.Slot]*stateMachine{}, 22 "map[]", 23 }, 24 { 25 "newly created state machine", 26 map[types.Slot]*stateMachine{ 27 0: {start: 64 * 0, state: stateNew}, 28 64: {start: 64 * 1, state: stateScheduled}, 29 128: {start: 64 * 2, state: stateDataParsed}, 30 196: {start: 64 * 3, state: stateSkipped}, 31 256: {start: 64 * 4, state: stateSent}, 32 }, 33 "map[0:{0:new} 64:{2:scheduled} 128:{4:dataParsed} 196:{6:skipped} 256:{8:sent}]", 34 }, 35 } 36 for _, tt := range tests { 37 t.Run(tt.name, func(t *testing.T) { 38 smm := &stateMachineManager{ 39 machines: tt.machines, 40 } 41 assert.Equal(t, tt.want, smm.String()) 42 }) 43 } 44 } 45 46 func TestStateMachine_StateIDString(t *testing.T) { 47 stateIDs := []stateID{stateNew, stateScheduled, stateDataParsed, stateSkipped, stateSent} 48 assert.Equal(t, "[new scheduled dataParsed skipped sent]", fmt.Sprintf("%v", stateIDs)) 49 assert.Equal(t, "stateUnknown", stateID(15).String()) 50 } 51 52 func TestStateMachine_EventIDString(t *testing.T) { 53 eventIDs := []eventID{eventTick, eventDataReceived} 54 assert.Equal(t, "[tick dataReceived]", fmt.Sprintf("%v", eventIDs)) 55 assert.Equal(t, "eventUnknown", eventID(15).String()) 56 } 57 58 func TestStateMachineManager_addEventHandler(t *testing.T) { 59 smm := newStateMachineManager() 60 61 smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, i interface{}) (id stateID, err error) { 62 return stateScheduled, nil 63 }) 64 assert.Equal(t, 1, len(smm.handlers[stateNew]), "Unexpected size") 65 state, err := smm.handlers[stateNew][eventTick](nil, nil) 66 assert.NoError(t, err) 67 assert.Equal(t, stateScheduled, state, "Unexpected state") 68 69 // Add second handler to the same event 70 smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i interface{}) (id stateID, err error) { 71 return stateDataParsed, nil 72 }) 73 assert.Equal(t, 1, len(smm.handlers[stateSent]), "Unexpected size") 74 state, err = smm.handlers[stateSent][eventTick](nil, nil) 75 assert.NoError(t, err) 76 assert.Equal(t, stateDataParsed, state, "Unexpected state") 77 78 // Add another handler to existing event/state pair. Should have no effect. 79 smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i interface{}) (id stateID, err error) { 80 return stateSkipped, nil 81 }) 82 assert.Equal(t, 1, len(smm.handlers[stateSent]), "Unexpected size") 83 state, err = smm.handlers[stateSent][eventTick](nil, nil) 84 assert.NoError(t, err) 85 // No effect, previous handler worked. 86 assert.Equal(t, stateDataParsed, state, "Unexpected state") 87 } 88 89 func TestStateMachine_trigger(t *testing.T) { 90 type event struct { 91 state stateID 92 event eventID 93 returnState stateID 94 err bool 95 } 96 type args struct { 97 name eventID 98 returnState stateID 99 epoch types.Epoch 100 data interface{} 101 } 102 tests := []struct { 103 name string 104 events []event 105 epochs []types.Epoch 106 args args 107 err error 108 }{ 109 { 110 name: "event not found", 111 events: []event{}, 112 epochs: []types.Epoch{12, 13}, 113 args: args{name: eventTick, epoch: 12, data: nil, returnState: stateNew}, 114 err: errors.New("no event handlers registered for event: tick, state: new"), 115 }, 116 { 117 name: "single action", 118 events: []event{ 119 {stateNew, eventTick, stateScheduled, false}, 120 }, 121 epochs: []types.Epoch{12, 13}, 122 args: args{name: eventTick, epoch: 12, data: nil, returnState: stateScheduled}, 123 err: nil, 124 }, 125 { 126 name: "multiple actions, has error", 127 events: []event{ 128 {stateNew, eventTick, stateScheduled, false}, 129 {stateScheduled, eventTick, stateSent, true}, 130 {stateSent, eventTick, stateSkipped, false}, 131 }, 132 epochs: []types.Epoch{12, 13}, 133 args: args{name: eventTick, epoch: 12, data: nil, returnState: stateScheduled}, 134 err: nil, 135 }, 136 { 137 name: "multiple actions, no error, can cascade", 138 events: []event{ 139 {stateNew, eventTick, stateScheduled, false}, 140 {stateScheduled, eventTick, stateSent, false}, 141 {stateSent, eventTick, stateSkipped, false}, 142 }, 143 epochs: []types.Epoch{12, 13}, 144 args: args{name: eventTick, epoch: 12, data: nil, returnState: stateScheduled}, 145 err: nil, 146 }, 147 { 148 name: "multiple actions, no error, no cascade", 149 events: []event{ 150 {stateNew, eventTick, stateScheduled, false}, 151 {stateScheduled, eventTick, stateSent, false}, 152 {stateNew, eventTick, stateSkipped, false}, 153 }, 154 epochs: []types.Epoch{12, 13}, 155 args: args{name: eventTick, epoch: 12, data: nil, returnState: stateScheduled}, 156 err: nil, 157 }, 158 } 159 fn := func(e event) eventHandlerFn { 160 return func(m *stateMachine, in interface{}) (stateID, error) { 161 if e.err { 162 return m.state, errors.New("invalid") 163 } 164 return e.returnState, nil 165 } 166 } 167 for _, tt := range tests { 168 t.Run(tt.name, func(t *testing.T) { 169 smm := newStateMachineManager() 170 expectHandlerError := false 171 for _, event := range tt.events { 172 smm.addEventHandler(event.event, event.state, fn(event)) 173 if event.err { 174 expectHandlerError = true 175 } 176 } 177 for _, epoch := range tt.epochs { 178 smm.addStateMachine(types.Slot(epoch * 32)) 179 } 180 state := smm.machines[types.Slot(tt.args.epoch*32)] 181 err := state.trigger(tt.args.name, tt.args.data) 182 if tt.err != nil && (err == nil || tt.err.Error() != err.Error()) { 183 t.Errorf("unexpected error = '%v', want '%v'", err, tt.err) 184 } 185 if tt.err == nil { 186 if err != nil && !expectHandlerError { 187 t.Error(err) 188 } 189 ind := types.Slot(tt.args.epoch * 32) 190 if smm.machines[ind].state != tt.args.returnState { 191 t.Errorf("unexpected final state: %v, want: %v (%v)", 192 smm.machines[ind].state, tt.args.returnState, smm.machines) 193 } 194 } 195 }) 196 } 197 } 198 199 func TestStateMachineManager_QueueLoop(t *testing.T) { 200 smm := newStateMachineManager() 201 smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, data interface{}) (stateID, error) { 202 return stateScheduled, nil 203 }) 204 smm.addEventHandler(eventTick, stateScheduled, func(m *stateMachine, data interface{}) (stateID, error) { 205 if m.start < 256 { 206 return stateDataParsed, nil 207 } 208 return stateSkipped, nil 209 }) 210 smm.addEventHandler(eventTick, stateDataParsed, func(m *stateMachine, data interface{}) (stateID, error) { 211 return stateSent, nil 212 }) 213 smm.addEventHandler(eventTick, stateSkipped, func(m *stateMachine, data interface{}) (stateID, error) { 214 dataParsed, ok := data.(int) 215 if !ok { 216 return m.state, errors.New("invalid data type") 217 } 218 if dataParsed > 41 { 219 return stateNew, nil 220 } 221 222 return stateScheduled, nil 223 }) 224 assert.Equal(t, 4, len(smm.handlers), "Unexpected number of state events") 225 smm.addStateMachine(64) 226 smm.addStateMachine(512) 227 228 assertState := func(startSlot types.Slot, state stateID) { 229 fsm, ok := smm.findStateMachine(startSlot) 230 require.Equal(t, true, ok, "State machine not found") 231 assert.Equal(t, state, fsm.state, "Unexpected state machine state") 232 } 233 234 triggerTickEvent := func() { 235 for _, fsm := range smm.machines { 236 data := 42 237 assert.NoError(t, fsm.trigger(eventTick, data)) 238 } 239 } 240 241 assertState(64, stateNew) 242 assertState(512, stateNew) 243 244 triggerTickEvent() 245 assertState(64, stateScheduled) 246 assertState(512, stateScheduled) 247 248 triggerTickEvent() 249 assertState(64, stateDataParsed) 250 assertState(512, stateSkipped) 251 252 triggerTickEvent() 253 assertState(64, stateSent) 254 assertState(512, stateNew) 255 } 256 257 func TestStateMachineManager_removeStateMachine(t *testing.T) { 258 smm := newStateMachineManager() 259 if _, ok := smm.findStateMachine(64); ok { 260 t.Error("unexpected machine found") 261 } 262 smm.addStateMachine(64) 263 if _, ok := smm.findStateMachine(64); !ok { 264 t.Error("expected machine not found") 265 } 266 expectedError := fmt.Sprintf("state for machine %v is not found", 65) 267 assert.ErrorContains(t, expectedError, smm.removeStateMachine(65)) 268 assert.NoError(t, smm.removeStateMachine(64)) 269 if _, ok := smm.findStateMachine(64); ok { 270 t.Error("unexpected machine found") 271 } 272 } 273 274 func TestStateMachineManager_removeAllStateMachines(t *testing.T) { 275 smm := newStateMachineManager() 276 smm.addStateMachine(64) 277 smm.addStateMachine(128) 278 smm.addStateMachine(196) 279 keys := []types.Slot{64, 128, 196} 280 assert.DeepEqual(t, smm.keys, keys, "Keys not sorted") 281 assert.Equal(t, 3, len(smm.machines), "Unexpected list size") 282 assert.NoError(t, smm.removeAllStateMachines()) 283 284 keys = []types.Slot{} 285 assert.DeepEqual(t, smm.keys, keys, "Unexpected keys") 286 assert.Equal(t, 0, len(smm.machines), "Expected empty list") 287 } 288 289 func TestStateMachineManager_findStateMachine(t *testing.T) { 290 smm := newStateMachineManager() 291 if _, ok := smm.findStateMachine(64); ok { 292 t.Errorf("unexpected returned value: want: %v, got: %v", false, ok) 293 } 294 smm.addStateMachine(64) 295 if fsm, ok := smm.findStateMachine(64); !ok || fsm == nil { 296 t.Errorf("unexpected returned value: want: %v, got: %v", true, ok) 297 } 298 smm.addStateMachine(512) 299 smm.addStateMachine(196) 300 smm.addStateMachine(256) 301 smm.addStateMachine(128) 302 if fsm, ok := smm.findStateMachine(128); !ok || fsm.start != 128 { 303 t.Errorf("unexpected start slot: %v, want: %v", fsm.start, 122) 304 } 305 if fsm, ok := smm.findStateMachine(512); !ok || fsm.start != 512 { 306 t.Errorf("unexpected start slot: %v, want: %v", fsm.start, 512) 307 } 308 keys := []types.Slot{64, 128, 196, 256, 512} 309 assert.DeepEqual(t, smm.keys, keys, "Keys not sorted") 310 } 311 312 func TestStateMachineManager_highestStartSlot(t *testing.T) { 313 smm := newStateMachineManager() 314 _, err := smm.highestStartSlot() 315 assert.ErrorContains(t, "no state machine exist", err) 316 smm.addStateMachine(64) 317 smm.addStateMachine(128) 318 smm.addStateMachine(196) 319 start, err := smm.highestStartSlot() 320 assert.NoError(t, err) 321 assert.Equal(t, types.Slot(196), start, "Incorrect highest start slot") 322 assert.NoError(t, smm.removeStateMachine(196)) 323 start, err = smm.highestStartSlot() 324 assert.NoError(t, err) 325 assert.Equal(t, types.Slot(128), start, "Incorrect highest start slot") 326 } 327 328 func TestStateMachineManager_allMachinesInState(t *testing.T) { 329 tests := []struct { 330 name string 331 smmGen func() *stateMachineManager 332 expectedStates []stateID 333 unexpectedStates []stateID 334 }{ 335 { 336 name: "empty manager", 337 smmGen: newStateMachineManager, 338 expectedStates: []stateID{}, 339 unexpectedStates: []stateID{stateNew, stateScheduled, stateDataParsed, stateSkipped, stateSent}, 340 }, 341 { 342 name: "single machine default state", 343 smmGen: func() *stateMachineManager { 344 smm := newStateMachineManager() 345 smm.addStateMachine(64) 346 return smm 347 }, 348 expectedStates: []stateID{stateNew}, 349 unexpectedStates: []stateID{stateScheduled, stateDataParsed, stateSkipped, stateSent}, 350 }, 351 { 352 name: "single machine updated state", 353 smmGen: func() *stateMachineManager { 354 smm := newStateMachineManager() 355 m1 := smm.addStateMachine(64) 356 m1.setState(stateSkipped) 357 return smm 358 }, 359 expectedStates: []stateID{stateSkipped}, 360 unexpectedStates: []stateID{stateNew, stateScheduled, stateDataParsed, stateSent}, 361 }, 362 { 363 name: "multiple machines false", 364 smmGen: func() *stateMachineManager { 365 smm := newStateMachineManager() 366 smm.addStateMachine(64) 367 smm.addStateMachine(128) 368 smm.addStateMachine(196) 369 for _, fsm := range smm.machines { 370 fsm.setState(stateSkipped) 371 } 372 smm.addStateMachine(256) 373 return smm 374 }, 375 expectedStates: []stateID{}, 376 unexpectedStates: []stateID{stateNew, stateScheduled, stateDataParsed, stateSkipped, stateSent}, 377 }, 378 { 379 name: "multiple machines true", 380 smmGen: func() *stateMachineManager { 381 smm := newStateMachineManager() 382 smm.addStateMachine(64) 383 smm.addStateMachine(128) 384 smm.addStateMachine(196) 385 for _, fsm := range smm.machines { 386 fsm.setState(stateSkipped) 387 } 388 return smm 389 }, 390 expectedStates: []stateID{stateSkipped}, 391 unexpectedStates: []stateID{stateNew, stateScheduled, stateDataParsed, stateSent}, 392 }, 393 } 394 for _, tt := range tests { 395 t.Run(tt.name, func(t *testing.T) { 396 smm := tt.smmGen() 397 for _, state := range tt.expectedStates { 398 if !smm.allMachinesInState(state) { 399 t.Errorf("expected all machines be in state: %v", state) 400 } 401 } 402 for _, state := range tt.unexpectedStates { 403 if smm.allMachinesInState(state) { 404 t.Errorf("unexpected state: %v", state) 405 } 406 } 407 }) 408 } 409 } 410 411 func TestStateMachine_isFirstLast(t *testing.T) { 412 checkFirst := func(m *stateMachine, want bool) { 413 assert.Equal(t, want, m.isFirst(), "isFirst() returned unexpected value") 414 } 415 checkLast := func(m *stateMachine, want bool) { 416 assert.Equal(t, want, m.isLast(), "isLast() returned unexpected value") 417 } 418 smm := newStateMachineManager() 419 m1 := smm.addStateMachine(64) 420 checkFirst(m1, true) 421 checkLast(m1, true) 422 423 m2 := smm.addStateMachine(128) 424 checkFirst(m1, true) 425 checkLast(m1, false) 426 checkFirst(m2, false) 427 checkLast(m2, true) 428 429 m3 := smm.addStateMachine(512) 430 checkFirst(m1, true) 431 checkLast(m1, false) 432 checkFirst(m2, false) 433 checkLast(m2, false) 434 checkFirst(m3, false) 435 checkLast(m3, true) 436 437 // Add machine with lower start slot - shouldn't be marked as last. 438 m4 := smm.addStateMachine(196) 439 checkFirst(m1, true) 440 checkLast(m1, false) 441 checkFirst(m2, false) 442 checkLast(m2, false) 443 checkFirst(m3, false) 444 checkLast(m3, true) 445 checkFirst(m4, false) 446 checkLast(m4, false) 447 448 // Add machine with lowest start slot - should be marked as first. 449 m5 := smm.addStateMachine(32) 450 checkFirst(m1, false) 451 checkLast(m1, false) 452 checkFirst(m2, false) 453 checkLast(m2, false) 454 checkFirst(m3, false) 455 checkLast(m3, true) 456 checkFirst(m4, false) 457 checkLast(m4, false) 458 checkFirst(m5, true) 459 checkLast(m5, false) 460 461 keys := []types.Slot{32, 64, 128, 196, 512} 462 assert.DeepEqual(t, smm.keys, keys, "Keys not sorted") 463 }