github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/protocol_state/kvstore/upgrade_statemachine_test.go (about) 1 package kvstore_test 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/require" 7 "github.com/stretchr/testify/suite" 8 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/state/protocol" 11 mockprotocol "github.com/onflow/flow-go/state/protocol/mock" 12 "github.com/onflow/flow-go/state/protocol/protocol_state/kvstore" 13 "github.com/onflow/flow-go/state/protocol/protocol_state/mock" 14 "github.com/onflow/flow-go/utils/unittest" 15 ) 16 17 func TestStateMachine(t *testing.T) { 18 suite.Run(t, new(StateMachineSuite)) 19 } 20 21 // StateMachineSuite is a dedicated test suite for testing KV store state machine. 22 type StateMachineSuite struct { 23 suite.Suite 24 25 view uint64 26 parentState *mockprotocol.KVStoreReader 27 mutator *mock.KVStoreMutator 28 params *mockprotocol.GlobalParams 29 30 stateMachine *kvstore.PSVersionUpgradeStateMachine 31 } 32 33 func (s *StateMachineSuite) SetupTest() { 34 s.parentState = mockprotocol.NewKVStoreReader(s.T()) 35 s.mutator = mock.NewKVStoreMutator(s.T()) 36 s.params = mockprotocol.NewGlobalParams(s.T()) 37 s.view = 1000 38 39 s.params.On("EpochCommitSafetyThreshold").Return(uint64(100)).Maybe() 40 41 s.stateMachine = kvstore.NewPSVersionUpgradeStateMachine(s.view, s.params, s.parentState, s.mutator) 42 require.NotNil(s.T(), s.stateMachine) 43 } 44 45 // TestInitialInvariants ensures that initial state machine invariants are met. 46 // It checks that state machine has correct candidateView and parent state. 47 func (s *StateMachineSuite) TestInitialInvariants() { 48 require.Equal(s.T(), s.view, s.stateMachine.View()) 49 require.Equal(s.T(), s.parentState, s.stateMachine.ParentState()) 50 } 51 52 // TestEvolveState_ProtocolStateVersionUpgrade ensures that state machine can process protocol state version upgrade event. 53 // It checks several cases including 54 // * happy path - valid upgrade version and activation view 55 // * invalid upgrade version - has to return sentinel error since version is invalid 56 // * invalid activation view - has to return sentinel error since activation view doesn't meet threshold. 57 func (s *StateMachineSuite) TestEvolveState_ProtocolStateVersionUpgrade() { 58 s.Run("happy-path", func() { 59 oldVersion := uint64(0) 60 s.parentState.On("GetProtocolStateVersion").Return(oldVersion) 61 62 upgrade := unittest.ProtocolStateVersionUpgradeFixture() 63 upgrade.ActiveView = s.view + s.params.EpochCommitSafetyThreshold() + 1 64 upgrade.NewProtocolStateVersion = oldVersion + 1 65 66 s.mutator.On("GetVersionUpgrade").Return(nil) 67 s.mutator.On("SetVersionUpgrade", &protocol.ViewBasedActivator[uint64]{ 68 Data: upgrade.NewProtocolStateVersion, 69 ActivationView: upgrade.ActiveView, 70 }).Return() 71 72 err := s.stateMachine.EvolveState([]flow.ServiceEvent{upgrade.ServiceEvent()}) 73 require.NoError(s.T(), err) 74 }) 75 s.Run("invalid-protocol-state-version", func() { 76 s.mutator = mock.NewKVStoreMutator(s.T()) 77 oldVersion := uint64(0) 78 s.parentState.On("GetProtocolStateVersion").Return(oldVersion) 79 80 upgrade := unittest.ProtocolStateVersionUpgradeFixture() 81 upgrade.ActiveView = s.view + s.params.EpochCommitSafetyThreshold() + 1 82 upgrade.NewProtocolStateVersion = oldVersion 83 84 _ = s.stateMachine.EvolveState([]flow.ServiceEvent{upgrade.ServiceEvent()}) 85 86 // TODO: this needs to be fixed to consume error for consumer, since sentinels are handled internally 87 //require.ErrorIs(s.T(), err, ErrInvalidUpgradeVersion, "has to be expected sentinel") 88 //require.True(s.T(), protocol.IsInvalidServiceEventError(err), "has to be expected sentinel") 89 s.mutator.AssertNumberOfCalls(s.T(), "SetVersionUpgrade", 0) 90 }) 91 s.Run("skipping-protocol-state-version", func() { 92 s.mutator = mock.NewKVStoreMutator(s.T()) 93 oldVersion := uint64(0) 94 s.parentState.On("GetProtocolStateVersion").Return(oldVersion) 95 96 upgrade := unittest.ProtocolStateVersionUpgradeFixture() 97 upgrade.ActiveView = s.view + s.params.EpochCommitSafetyThreshold() + 1 98 upgrade.NewProtocolStateVersion = oldVersion + 2 // has to be exactly +1 99 100 _ = s.stateMachine.EvolveState([]flow.ServiceEvent{upgrade.ServiceEvent()}) 101 102 // TODO: this needs to be fixed to consume error for consumer, since sentinels are handled internally 103 //require.ErrorIs(s.T(), err, ErrInvalidUpgradeVersion, "has to be expected sentinel") 104 //require.True(s.T(), protocol.IsInvalidServiceEventError(err), "has to be expected sentinel") 105 s.mutator.AssertNumberOfCalls(s.T(), "SetVersionUpgrade", 0) 106 }) 107 s.Run("invalid-activation-view", func() { 108 s.mutator = mock.NewKVStoreMutator(s.T()) 109 upgrade := unittest.ProtocolStateVersionUpgradeFixture() 110 upgrade.ActiveView = s.view + s.params.EpochCommitSafetyThreshold() 111 112 _ = s.stateMachine.EvolveState([]flow.ServiceEvent{upgrade.ServiceEvent()}) 113 114 // TODO: this needs to be fixed to consume error for consumer, since sentinels are handled internally 115 //require.ErrorIs(s.T(), err, ErrInvalidActivationView, "has to be expected sentinel") 116 //require.True(s.T(), protocol.IsInvalidServiceEventError(err), "has to be expected sentinel") 117 s.mutator.AssertNumberOfCalls(s.T(), "SetVersionUpgrade", 0) 118 }) 119 } 120 121 // TestBuild ensures that state machine returns empty list of deferred operations. 122 func (s *StateMachineSuite) TestBuild() { 123 dbOps, err := s.stateMachine.Build() 124 require.NoError(s.T(), err) 125 require.True(s.T(), dbOps.IsEmpty()) 126 }