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  }