github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/protocol_state/epochs/happy_path_statemachine_test.go (about)

     1  package epochs
     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/model/flow/filter"
    11  	"github.com/onflow/flow-go/state/protocol"
    12  	"github.com/onflow/flow-go/utils/unittest"
    13  )
    14  
    15  func TestProtocolStateMachine(t *testing.T) {
    16  	suite.Run(t, new(ProtocolStateMachineSuite))
    17  }
    18  
    19  // BaseStateMachineSuite is a base test suite that holds common functionality for testing protocol state machines.
    20  // It reflects the portion of data which is present in baseStateMachine.
    21  type BaseStateMachineSuite struct {
    22  	suite.Suite
    23  
    24  	parentProtocolState *flow.RichProtocolStateEntry
    25  	parentBlock         *flow.Header
    26  	candidate           *flow.Header
    27  }
    28  
    29  func (s *BaseStateMachineSuite) SetupTest() {
    30  	s.parentProtocolState = unittest.EpochStateFixture()
    31  	s.parentBlock = unittest.BlockHeaderFixture(unittest.HeaderWithView(s.parentProtocolState.CurrentEpochSetup.FirstView + 1))
    32  	s.candidate = unittest.BlockHeaderWithParentFixture(s.parentBlock)
    33  }
    34  
    35  // ProtocolStateMachineSuite is a dedicated test suite for testing happy path state machine.
    36  type ProtocolStateMachineSuite struct {
    37  	BaseStateMachineSuite
    38  	stateMachine *HappyPathStateMachine
    39  }
    40  
    41  func (s *ProtocolStateMachineSuite) SetupTest() {
    42  	s.BaseStateMachineSuite.SetupTest()
    43  	var err error
    44  	s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
    45  	require.NoError(s.T(), err)
    46  }
    47  
    48  // TestNewstateMachine tests if the constructor correctly setups invariants for HappyPathStateMachine.
    49  func (s *ProtocolStateMachineSuite) TestNewstateMachine() {
    50  	require.NotSame(s.T(), s.stateMachine.parentState, s.stateMachine.state, "except to take deep copy of parent state")
    51  	require.Nil(s.T(), s.stateMachine.parentState.NextEpoch)
    52  	require.Nil(s.T(), s.stateMachine.state.NextEpoch)
    53  	require.Equal(s.T(), s.candidate.View, s.stateMachine.View())
    54  	require.Equal(s.T(), s.parentProtocolState, s.stateMachine.ParentState())
    55  }
    56  
    57  // TestTransitionToNextEpoch tests a scenario where the HappyPathStateMachine processes first block from next epoch.
    58  // It has to discard the parent state and build a new state with data from next epoch.
    59  func (s *ProtocolStateMachineSuite) TestTransitionToNextEpoch() {
    60  	// update protocol state with next epoch information
    61  	unittest.WithNextEpochProtocolState()(s.parentProtocolState)
    62  
    63  	candidate := unittest.BlockHeaderFixture(
    64  		unittest.HeaderWithView(s.parentProtocolState.CurrentEpochSetup.FinalView + 1))
    65  	var err error
    66  	// since the candidate block is from next epoch, HappyPathStateMachine should transition to next epoch
    67  	s.stateMachine, err = NewHappyPathStateMachine(candidate.View, s.parentProtocolState.Copy())
    68  	require.NoError(s.T(), err)
    69  	err = s.stateMachine.TransitionToNextEpoch()
    70  	require.NoError(s.T(), err)
    71  	updatedState, stateID, hasChanges := s.stateMachine.Build()
    72  	require.True(s.T(), hasChanges)
    73  	require.NotEqual(s.T(), s.parentProtocolState.ID(), updatedState.ID())
    74  	require.Equal(s.T(), updatedState.ID(), stateID)
    75  	require.Equal(s.T(), s.parentProtocolState.ID(), s.stateMachine.ParentState().ID(), "should not modify parent protocol state")
    76  	require.Equal(s.T(), updatedState.CurrentEpoch.ID(), s.parentProtocolState.NextEpoch.ID(), "should transition into next epoch")
    77  	require.Nil(s.T(), updatedState.NextEpoch, "next epoch protocol state should be nil")
    78  }
    79  
    80  // TestTransitionToNextEpochNotAllowed tests different scenarios where transition to next epoch is not allowed.
    81  func (s *ProtocolStateMachineSuite) TestTransitionToNextEpochNotAllowed() {
    82  	s.Run("no next epoch protocol state", func() {
    83  		protocolState := unittest.EpochStateFixture()
    84  		candidate := unittest.BlockHeaderFixture(
    85  			unittest.HeaderWithView(protocolState.CurrentEpochSetup.FinalView + 1))
    86  		stateMachine, err := NewHappyPathStateMachine(candidate.View, protocolState)
    87  		require.NoError(s.T(), err)
    88  		err = stateMachine.TransitionToNextEpoch()
    89  		require.Error(s.T(), err, "should not allow transition to next epoch if there is no next epoch protocol state")
    90  	})
    91  	s.Run("next epoch not committed", func() {
    92  		protocolState := unittest.EpochStateFixture(unittest.WithNextEpochProtocolState(), func(entry *flow.RichProtocolStateEntry) {
    93  			entry.NextEpoch.CommitID = flow.ZeroID
    94  			entry.NextEpochCommit = nil
    95  		})
    96  		candidate := unittest.BlockHeaderFixture(
    97  			unittest.HeaderWithView(protocolState.CurrentEpochSetup.FinalView + 1))
    98  		stateMachine, err := NewHappyPathStateMachine(candidate.View, protocolState)
    99  		require.NoError(s.T(), err)
   100  		err = stateMachine.TransitionToNextEpoch()
   101  		require.Error(s.T(), err, "should not allow transition to next epoch if it is not committed")
   102  	})
   103  	s.Run("candidate block is not from next epoch", func() {
   104  		protocolState := unittest.EpochStateFixture(unittest.WithNextEpochProtocolState())
   105  		candidate := unittest.BlockHeaderFixture(
   106  			unittest.HeaderWithView(protocolState.CurrentEpochSetup.FinalView))
   107  		stateMachine, err := NewHappyPathStateMachine(candidate.View, protocolState)
   108  		require.NoError(s.T(), err)
   109  		err = stateMachine.TransitionToNextEpoch()
   110  		require.Error(s.T(), err, "should not allow transition to next epoch if next block is not first block from next epoch")
   111  	})
   112  }
   113  
   114  // TestBuild tests if the HappyPathStateMachine returns correct protocol state.
   115  func (s *ProtocolStateMachineSuite) TestBuild() {
   116  	updatedState, stateID, hasChanges := s.stateMachine.Build()
   117  	require.Equal(s.T(), stateID, s.parentProtocolState.ID(), "should return same protocol state")
   118  	require.False(s.T(), hasChanges, "should not have changes")
   119  	require.NotSame(s.T(), updatedState, s.stateMachine.state, "should return a copy of protocol state")
   120  	require.Equal(s.T(), updatedState.ID(), stateID, "should return correct ID")
   121  	require.Equal(s.T(), s.parentProtocolState.ID(), s.stateMachine.ParentState().ID(), "should not modify parent protocol state")
   122  
   123  	updatedDynamicIdentity := s.parentProtocolState.CurrentEpochIdentityTable[0].NodeID
   124  	err := s.stateMachine.EjectIdentity(updatedDynamicIdentity)
   125  	require.NoError(s.T(), err)
   126  	updatedState, stateID, hasChanges = s.stateMachine.Build()
   127  	require.True(s.T(), hasChanges, "should have changes")
   128  	require.NotEqual(s.T(), stateID, s.parentProtocolState.ID(), "protocol state was modified but still has same ID")
   129  	require.Equal(s.T(), updatedState.ID(), stateID, "should return correct ID")
   130  	require.Equal(s.T(), s.parentProtocolState.ID(), s.stateMachine.ParentState().ID(), "should not modify parent protocol state")
   131  }
   132  
   133  // TestCreateStateMachineAfterInvalidStateTransitionAttempted tests if creating state machine after observing invalid state transition
   134  // results in error .
   135  func (s *ProtocolStateMachineSuite) TestCreateStateMachineAfterInvalidStateTransitionAttempted() {
   136  	s.parentProtocolState.InvalidEpochTransitionAttempted = true
   137  	var err error
   138  	// create new HappyPathStateMachine with next epoch information
   139  	s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   140  	require.Error(s.T(), err)
   141  }
   142  
   143  // TestProcessEpochCommit tests if processing epoch commit event correctly updates internal state of HappyPathStateMachine and
   144  // correctly behaves when invariants are violated.
   145  func (s *ProtocolStateMachineSuite) TestProcessEpochCommit() {
   146  	var err error
   147  	s.Run("invalid counter", func() {
   148  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   149  		require.NoError(s.T(), err)
   150  		commit := unittest.EpochCommitFixture(func(commit *flow.EpochCommit) {
   151  			commit.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 10 // set invalid counter for next epoch
   152  		})
   153  		_, err := s.stateMachine.ProcessEpochCommit(commit)
   154  		require.Error(s.T(), err)
   155  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   156  	})
   157  	s.Run("no next epoch protocol state", func() {
   158  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   159  		require.NoError(s.T(), err)
   160  		commit := unittest.EpochCommitFixture(func(commit *flow.EpochCommit) {
   161  			commit.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1
   162  		})
   163  		_, err := s.stateMachine.ProcessEpochCommit(commit)
   164  		require.Error(s.T(), err)
   165  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   166  	})
   167  	s.Run("conflicting epoch commit", func() {
   168  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   169  		require.NoError(s.T(), err)
   170  		setup := unittest.EpochSetupFixture(
   171  			unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   172  			unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   173  			unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   174  		)
   175  		// processing setup event results in creating next epoch protocol state
   176  		_, err := s.stateMachine.ProcessEpochSetup(setup)
   177  		require.NoError(s.T(), err)
   178  
   179  		updatedState, _, _ := s.stateMachine.Build()
   180  
   181  		parentState, err := flow.NewRichProtocolStateEntry(updatedState,
   182  			s.parentProtocolState.PreviousEpochSetup,
   183  			s.parentProtocolState.PreviousEpochCommit,
   184  			s.parentProtocolState.CurrentEpochSetup,
   185  			s.parentProtocolState.CurrentEpochCommit,
   186  			setup,
   187  			nil,
   188  		)
   189  		require.NoError(s.T(), err)
   190  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View+1, parentState)
   191  		require.NoError(s.T(), err)
   192  		commit := unittest.EpochCommitFixture(
   193  			unittest.CommitWithCounter(setup.Counter),
   194  			unittest.WithDKGFromParticipants(setup.Participants),
   195  		)
   196  
   197  		_, err = s.stateMachine.ProcessEpochCommit(commit)
   198  		require.NoError(s.T(), err)
   199  
   200  		// processing another epoch commit has to be an error since we have already processed one
   201  		_, err = s.stateMachine.ProcessEpochCommit(commit)
   202  		require.Error(s.T(), err)
   203  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   204  
   205  		newState, _, _ := s.stateMachine.Build()
   206  		require.Equal(s.T(), commit.ID(), newState.NextEpoch.CommitID, "next epoch should be committed since we have observed, a valid event")
   207  	})
   208  	s.Run("happy path processing", func() {
   209  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   210  		require.NoError(s.T(), err)
   211  		setup := unittest.EpochSetupFixture(
   212  			unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   213  			unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   214  			unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   215  		)
   216  		// processing setup event results in creating next epoch protocol state
   217  		_, err := s.stateMachine.ProcessEpochSetup(setup)
   218  		require.NoError(s.T(), err)
   219  
   220  		updatedState, stateID, hasChanges := s.stateMachine.Build()
   221  		require.True(s.T(), hasChanges)
   222  		require.NotEqual(s.T(), s.parentProtocolState.ID(), updatedState.ID())
   223  		require.Equal(s.T(), updatedState.ID(), stateID)
   224  		require.Equal(s.T(), s.parentProtocolState.ID(), s.stateMachine.ParentState().ID(), "should not modify parent protocol state")
   225  
   226  		parentState, err := flow.NewRichProtocolStateEntry(updatedState,
   227  			s.parentProtocolState.PreviousEpochSetup,
   228  			s.parentProtocolState.PreviousEpochCommit,
   229  			s.parentProtocolState.CurrentEpochSetup,
   230  			s.parentProtocolState.CurrentEpochCommit,
   231  			setup,
   232  			nil,
   233  		)
   234  		require.NoError(s.T(), err)
   235  		s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View+1, parentState.Copy())
   236  		require.NoError(s.T(), err)
   237  		commit := unittest.EpochCommitFixture(
   238  			unittest.CommitWithCounter(setup.Counter),
   239  			unittest.WithDKGFromParticipants(setup.Participants),
   240  		)
   241  
   242  		_, err = s.stateMachine.ProcessEpochCommit(commit)
   243  		require.NoError(s.T(), err)
   244  
   245  		newState, newStateID, newStateHasChanges := s.stateMachine.Build()
   246  		require.True(s.T(), newStateHasChanges)
   247  		require.Equal(s.T(), commit.ID(), newState.NextEpoch.CommitID, "next epoch should be committed")
   248  		require.Equal(s.T(), newState.ID(), newStateID)
   249  		require.NotEqual(s.T(), s.parentProtocolState.ID(), newState.ID())
   250  		require.NotEqual(s.T(), updatedState.ID(), newState.ID())
   251  		require.Equal(s.T(), parentState.ID(), s.stateMachine.ParentState().ID(),
   252  			"should not modify parent protocol state")
   253  	})
   254  }
   255  
   256  // TestUpdateIdentityUnknownIdentity tests if updating the identity of unknown node results in an error.
   257  func (s *ProtocolStateMachineSuite) TestUpdateIdentityUnknownIdentity() {
   258  	err := s.stateMachine.EjectIdentity(unittest.IdentifierFixture())
   259  	require.Error(s.T(), err, "should not be able to update data of unknown identity")
   260  	require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   261  
   262  	updatedState, updatedStateID, hasChanges := s.stateMachine.Build()
   263  	require.False(s.T(), hasChanges, "should not have changes")
   264  	require.Equal(s.T(), updatedState.ID(), s.parentProtocolState.ID())
   265  	require.Equal(s.T(), updatedState.ID(), updatedStateID)
   266  }
   267  
   268  // TestUpdateIdentityHappyPath tests if identity updates are correctly processed and reflected in the resulting protocol state.
   269  func (s *ProtocolStateMachineSuite) TestUpdateIdentityHappyPath() {
   270  	// update protocol state to have next epoch protocol state
   271  	unittest.WithNextEpochProtocolState()(s.parentProtocolState)
   272  	var err error
   273  	s.stateMachine, err = NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   274  	require.NoError(s.T(), err)
   275  
   276  	currentEpochParticipants := s.parentProtocolState.CurrentEpochIdentityTable.Copy()
   277  	ejectedChanges, err := currentEpochParticipants.Sample(2)
   278  	require.NoError(s.T(), err)
   279  
   280  	for _, update := range ejectedChanges {
   281  		err := s.stateMachine.EjectIdentity(update.NodeID)
   282  		require.NoError(s.T(), err)
   283  	}
   284  	updatedState, updatedStateID, hasChanges := s.stateMachine.Build()
   285  	require.True(s.T(), hasChanges, "should have changes")
   286  	require.Equal(s.T(), updatedState.ID(), updatedStateID)
   287  	require.NotEqual(s.T(), s.parentProtocolState.ID(), updatedState.ID())
   288  	require.Equal(s.T(), s.parentProtocolState.ID(), s.stateMachine.ParentState().ID(),
   289  		"should not modify parent protocol state")
   290  
   291  	// assert that all changes made in the previous epoch are preserved
   292  	currentEpochLookup := updatedState.CurrentEpoch.ActiveIdentities.Lookup()
   293  	nextEpochLookup := updatedState.NextEpoch.ActiveIdentities.Lookup()
   294  
   295  	for _, updated := range ejectedChanges {
   296  		currentEpochIdentity, foundInCurrentEpoch := currentEpochLookup[updated.NodeID]
   297  		if foundInCurrentEpoch {
   298  			require.Equal(s.T(), updated.NodeID, currentEpochIdentity.NodeID)
   299  			require.True(s.T(), currentEpochIdentity.Ejected)
   300  		}
   301  
   302  		nextEpochIdentity, foundInNextEpoch := nextEpochLookup[updated.NodeID]
   303  		if foundInNextEpoch {
   304  			require.Equal(s.T(), updated.NodeID, nextEpochIdentity.NodeID)
   305  			require.True(s.T(), nextEpochIdentity.Ejected)
   306  		}
   307  		require.True(s.T(), foundInCurrentEpoch || foundInNextEpoch, "identity should be found in either current or next epoch")
   308  	}
   309  }
   310  
   311  // TestProcessEpochSetupInvariants tests if processing epoch setup when invariants are violated doesn't update internal structures.
   312  func (s *ProtocolStateMachineSuite) TestProcessEpochSetupInvariants() {
   313  	s.Run("invalid counter", func() {
   314  		setup := unittest.EpochSetupFixture(func(setup *flow.EpochSetup) {
   315  			setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 10 // set invalid counter for next epoch
   316  		})
   317  		_, err := s.stateMachine.ProcessEpochSetup(setup)
   318  		require.Error(s.T(), err)
   319  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   320  	})
   321  	s.Run("processing second epoch setup", func() {
   322  		stateMachine, err := NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   323  		require.NoError(s.T(), err)
   324  		setup := unittest.EpochSetupFixture(
   325  			unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   326  			unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   327  			unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   328  		)
   329  		_, err = stateMachine.ProcessEpochSetup(setup)
   330  		require.NoError(s.T(), err)
   331  
   332  		_, err = stateMachine.ProcessEpochSetup(setup)
   333  		require.Error(s.T(), err)
   334  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   335  	})
   336  	s.Run("participants not sorted", func() {
   337  		stateMachine, err := NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   338  		require.NoError(s.T(), err)
   339  		setup := unittest.EpochSetupFixture(func(setup *flow.EpochSetup) {
   340  			setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1
   341  			var err error
   342  			setup.Participants, err = setup.Participants.Shuffle()
   343  			require.NoError(s.T(), err)
   344  		})
   345  		_, err = stateMachine.ProcessEpochSetup(setup)
   346  		require.Error(s.T(), err)
   347  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   348  	})
   349  	s.Run("epoch setup state conflicts with protocol state", func() {
   350  		conflictingIdentity := s.parentProtocolState.ProtocolStateEntry.CurrentEpoch.ActiveIdentities[0]
   351  		conflictingIdentity.Ejected = true
   352  
   353  		stateMachine, err := NewHappyPathStateMachine(s.candidate.View, s.parentProtocolState.Copy())
   354  		require.NoError(s.T(), err)
   355  		setup := unittest.EpochSetupFixture(func(setup *flow.EpochSetup) {
   356  			setup.Counter = s.parentProtocolState.CurrentEpochSetup.Counter + 1
   357  			// using same identities as in previous epoch should result in an error since
   358  			// we have ejected conflicting identity but it was added back in epoch setup
   359  			// such epoch setup event is invalid.
   360  			setup.Participants = s.parentProtocolState.CurrentEpochSetup.Participants
   361  		})
   362  
   363  		_, err = stateMachine.ProcessEpochSetup(setup)
   364  		require.Error(s.T(), err)
   365  		require.True(s.T(), protocol.IsInvalidServiceEventError(err))
   366  	})
   367  }
   368  
   369  // TestProcessEpochSetupHappyPath tests if processing epoch setup when invariants are not violated updates internal structures.
   370  // We test correct construction of the *active identities* for the current and next epoch. Specifically, observing an EpochSetup
   371  // event should leave `PreviousEpoch` and `CurrentEpoch`'s EpochStateContainer unchanged.
   372  // The next epoch's EpochStateContainer should reference the EpochSetup event and hold the respective ActiveIdentities.
   373  func (s *ProtocolStateMachineSuite) TestProcessEpochSetupHappyPath() {
   374  	setupParticipants := unittest.IdentityListFixture(5, unittest.WithAllRoles()).Sort(flow.Canonical[flow.Identity])
   375  	setupParticipants[0].InitialWeight = 13
   376  	setup := unittest.EpochSetupFixture(
   377  		unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   378  		unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   379  		unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   380  		unittest.WithParticipants(setupParticipants.ToSkeleton()),
   381  	)
   382  
   383  	// for next epoch we will have all the identities from setup event
   384  	expectedNextEpochActiveIdentities := flow.DynamicIdentityEntryListFromIdentities(setupParticipants)
   385  
   386  	// process actual event
   387  	_, err := s.stateMachine.ProcessEpochSetup(setup)
   388  	require.NoError(s.T(), err)
   389  
   390  	updatedState, _, hasChanges := s.stateMachine.Build()
   391  	require.True(s.T(), hasChanges, "should have changes")
   392  	require.Equal(s.T(), s.parentProtocolState.PreviousEpoch, updatedState.PreviousEpoch, "previous epoch's EpochStateContainer should not change")
   393  	require.Equal(s.T(), s.parentProtocolState.CurrentEpoch, updatedState.CurrentEpoch, "current epoch's EpochStateContainer should not change")
   394  	nextEpoch := updatedState.NextEpoch
   395  	require.NotNil(s.T(), nextEpoch, "should have next epoch protocol state")
   396  	require.Equal(s.T(), nextEpoch.SetupID, setup.ID(),
   397  		"should have correct setup ID for next protocol state")
   398  	require.Equal(s.T(), nextEpoch.CommitID, flow.ZeroID, "ID for EpochCommit event should still be nil")
   399  	require.Equal(s.T(), expectedNextEpochActiveIdentities, nextEpoch.ActiveIdentities,
   400  		"should have filled active identities for next epoch")
   401  }
   402  
   403  // TestProcessEpochSetupWithSameParticipants tests that processing epoch setup with overlapping participants results in correctly
   404  // built updated protocol state. It should build a union of participants from current and next epoch for current and
   405  // next epoch protocol states respectively.
   406  func (s *ProtocolStateMachineSuite) TestProcessEpochSetupWithSameParticipants() {
   407  	participantsFromCurrentEpochSetup, err := flow.ComposeFullIdentities(
   408  		s.parentProtocolState.CurrentEpochSetup.Participants,
   409  		s.parentProtocolState.CurrentEpoch.ActiveIdentities,
   410  		flow.EpochParticipationStatusActive,
   411  	)
   412  	require.NoError(s.T(), err)
   413  	// Function `ComposeFullIdentities` verified that `Participants` and `ActiveIdentities` have identical ordering w.r.t nodeID.
   414  	// By construction, `participantsFromCurrentEpochSetup` lists the full Identities in the same ordering as `Participants` and
   415  	// `ActiveIdentities`. By confirming that `participantsFromCurrentEpochSetup` follows canonical ordering, we can conclude that
   416  	// also `Participants` and `ActiveIdentities` are canonically ordered.
   417  	require.True(s.T(), participantsFromCurrentEpochSetup.Sorted(flow.Canonical[flow.Identity]), "participants in current epoch's setup event are not in canonical order")
   418  
   419  	overlappingNodes, err := participantsFromCurrentEpochSetup.Sample(2)
   420  	require.NoError(s.T(), err)
   421  	setupParticipants := append(unittest.IdentityListFixture(len(s.parentProtocolState.CurrentEpochIdentityTable), unittest.WithAllRoles()),
   422  		overlappingNodes...).Sort(flow.Canonical[flow.Identity])
   423  	setup := unittest.EpochSetupFixture(
   424  		unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   425  		unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   426  		unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   427  		unittest.WithParticipants(setupParticipants.ToSkeleton()),
   428  	)
   429  	_, err = s.stateMachine.ProcessEpochSetup(setup)
   430  	require.NoError(s.T(), err)
   431  	updatedState, _, _ := s.stateMachine.Build()
   432  
   433  	require.Equal(s.T(), s.parentProtocolState.CurrentEpoch.ActiveIdentities,
   434  		updatedState.CurrentEpoch.ActiveIdentities,
   435  		"should not change active identities for current epoch")
   436  
   437  	expectedNextEpochActiveIdentities := flow.DynamicIdentityEntryListFromIdentities(setupParticipants)
   438  	require.Equal(s.T(), expectedNextEpochActiveIdentities, updatedState.NextEpoch.ActiveIdentities,
   439  		"should have filled active identities for next epoch")
   440  }
   441  
   442  // TestEpochSetupAfterIdentityChange tests that after processing epoch an setup event, all previously made changes to the identity table
   443  // are preserved and reflected in the resulting protocol state.
   444  func (s *ProtocolStateMachineSuite) TestEpochSetupAfterIdentityChange() {
   445  	participantsFromCurrentEpochSetup := s.parentProtocolState.CurrentEpochIdentityTable.Filter(func(i *flow.Identity) bool {
   446  		_, exists := s.parentProtocolState.CurrentEpochSetup.Participants.ByNodeID(i.NodeID)
   447  		return exists
   448  	}).Sort(flow.Canonical[flow.Identity])
   449  	ejectedChanges, err := participantsFromCurrentEpochSetup.Sample(2)
   450  	require.NoError(s.T(), err)
   451  	for _, update := range ejectedChanges {
   452  		err := s.stateMachine.EjectIdentity(update.NodeID)
   453  		require.NoError(s.T(), err)
   454  	}
   455  	updatedState, _, _ := s.stateMachine.Build()
   456  
   457  	// Construct a valid flow.RichProtocolStateEntry for next block
   458  	// We do this by copying the parent protocol state and updating the identities manually
   459  	updatedRichProtocolState := &flow.RichProtocolStateEntry{
   460  		ProtocolStateEntry:        updatedState,
   461  		PreviousEpochSetup:        s.parentProtocolState.PreviousEpochSetup,
   462  		PreviousEpochCommit:       s.parentProtocolState.PreviousEpochCommit,
   463  		CurrentEpochSetup:         s.parentProtocolState.CurrentEpochSetup,
   464  		CurrentEpochCommit:        s.parentProtocolState.CurrentEpochCommit,
   465  		NextEpochSetup:            nil,
   466  		NextEpochCommit:           nil,
   467  		CurrentEpochIdentityTable: s.parentProtocolState.CurrentEpochIdentityTable.Copy(),
   468  		NextEpochIdentityTable:    flow.IdentityList{},
   469  	}
   470  	// Update enriched data with the changes made to the low-level updated table
   471  	for _, identity := range ejectedChanges {
   472  		toBeUpdated, _ := updatedRichProtocolState.CurrentEpochIdentityTable.ByNodeID(identity.NodeID)
   473  		toBeUpdated.EpochParticipationStatus = flow.EpochParticipationStatusEjected
   474  	}
   475  
   476  	// now we can use it to construct HappyPathStateMachine for next block, which will process epoch setup event.
   477  	nextBlock := unittest.BlockHeaderWithParentFixture(s.candidate)
   478  	s.stateMachine, err = NewHappyPathStateMachine(nextBlock.View, updatedRichProtocolState)
   479  	require.NoError(s.T(), err)
   480  
   481  	setup := unittest.EpochSetupFixture(
   482  		unittest.SetupWithCounter(s.parentProtocolState.CurrentEpochSetup.Counter+1),
   483  		unittest.WithFirstView(s.parentProtocolState.CurrentEpochSetup.FinalView+1),
   484  		unittest.WithFinalView(s.parentProtocolState.CurrentEpochSetup.FinalView+1000),
   485  		func(setup *flow.EpochSetup) {
   486  			// add those nodes that were changed in the previous epoch, but not those that were ejected
   487  			// it's important to exclude ejected nodes, since we expect that service smart contract has emitted ejection operation
   488  			// and service events are delivered (asynchronously) in an *order-preserving* manner meaning if ejection has happened before
   489  			// epoch setup then there is no possible way that it will include ejected node unless there is a severe bug in the service contract.
   490  			setup.Participants = setup.Participants.Filter(
   491  				filter.Not(filter.In(ejectedChanges.ToSkeleton()))).Sort(flow.Canonical[flow.IdentitySkeleton])
   492  		},
   493  	)
   494  
   495  	_, err = s.stateMachine.ProcessEpochSetup(setup)
   496  	require.NoError(s.T(), err)
   497  
   498  	updatedState, _, _ = s.stateMachine.Build()
   499  
   500  	// assert that all changes made in previous epoch are preserved
   501  	currentEpochLookup := updatedState.CurrentEpoch.ActiveIdentities.Lookup()
   502  	nextEpochLookup := updatedState.NextEpoch.ActiveIdentities.Lookup()
   503  
   504  	for _, updated := range ejectedChanges {
   505  		currentEpochIdentity := currentEpochLookup[updated.NodeID]
   506  		require.Equal(s.T(), updated.NodeID, currentEpochIdentity.NodeID)
   507  		require.True(s.T(), currentEpochIdentity.Ejected)
   508  
   509  		_, foundInNextEpoch := nextEpochLookup[updated.NodeID]
   510  		require.False(s.T(), foundInNextEpoch)
   511  	}
   512  }