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  }