github.com/onflow/flow-go@v0.33.17/engine/consensus/dkg/reactor_engine_test.go (about)

     1  package dkg_test
     2  
     3  import (
     4  	"math/rand"
     5  	"os"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/rs/zerolog"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/mock"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	"github.com/onflow/flow-go/crypto"
    16  	"github.com/onflow/flow-go/engine/consensus/dkg"
    17  	"github.com/onflow/flow-go/model/flow"
    18  	dkgmodule "github.com/onflow/flow-go/module/dkg"
    19  	module "github.com/onflow/flow-go/module/mock"
    20  	"github.com/onflow/flow-go/state/protocol/events/gadgets"
    21  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    22  	storerr "github.com/onflow/flow-go/storage"
    23  	storage "github.com/onflow/flow-go/storage/mock"
    24  	"github.com/onflow/flow-go/utils/unittest"
    25  	"github.com/onflow/flow-go/utils/unittest/mocks"
    26  )
    27  
    28  // ReactorEngineSuite_SetupPhase is a test suite for the Reactor engine which encompasses
    29  // test cases from when the DKG is instantiated to when it terminates locally.
    30  //
    31  // For tests of the Reactor engine's reaction to the global end of the DKG, see
    32  // ReactorEngineSuite_CommittedPhase.
    33  type ReactorEngineSuite_SetupPhase struct {
    34  	suite.Suite
    35  
    36  	// config
    37  	dkgStartView       uint64
    38  	dkgPhase1FinalView uint64
    39  	dkgPhase2FinalView uint64
    40  	dkgPhase3FinalView uint64
    41  
    42  	epochCounter       uint64            // current epoch counter
    43  	myIndex            int               // my index in the DKG
    44  	committee          flow.IdentityList // the DKG committee
    45  	expectedPrivateKey crypto.PrivateKey
    46  	firstBlock         *flow.Header
    47  	blocksByView       map[uint64]*flow.Header
    48  
    49  	// track how many warn-level logs are logged
    50  	warnsLogged int
    51  	logger      zerolog.Logger
    52  
    53  	local        *module.Local
    54  	currentEpoch *protocol.Epoch
    55  	nextEpoch    *protocol.Epoch
    56  	epochQuery   *mocks.EpochQuery
    57  	snapshot     *protocol.Snapshot
    58  	state        *protocol.State
    59  	viewEvents   *gadgets.Views
    60  
    61  	dkgState   *storage.DKGState
    62  	controller *module.DKGController
    63  	factory    *module.DKGControllerFactory
    64  
    65  	engine *dkg.ReactorEngine
    66  }
    67  
    68  func (suite *ReactorEngineSuite_SetupPhase) NextEpochCounter() uint64 {
    69  	return suite.epochCounter + 1
    70  }
    71  
    72  func TestReactorEngineSuite_SetupPhase(t *testing.T) {
    73  	suite.Run(t, new(ReactorEngineSuite_SetupPhase))
    74  }
    75  
    76  // SetupTest prepares the DKG test.
    77  //
    78  // The EpochSetup event is received at view 100.
    79  // The current epoch is configured with DKG phase transitions at views 150, 200,
    80  // and 250. In between phase transitions, the controller calls the DKG
    81  // smart-contract every 10 views.
    82  //
    83  // VIEWS
    84  // setup      : 100
    85  // polling    : 110 120 130 140 150
    86  // Phase1Final: 150
    87  // polling    : 160 170 180 190 200
    88  // Phase2Final: 200
    89  // polling    : 210 220 230 240 250
    90  // Phase3Final: 250
    91  func (suite *ReactorEngineSuite_SetupPhase) SetupTest() {
    92  
    93  	suite.dkgStartView = 100
    94  	suite.dkgPhase1FinalView = 150
    95  	suite.dkgPhase2FinalView = 200
    96  	suite.dkgPhase3FinalView = 250
    97  
    98  	suite.epochCounter = rand.Uint64()
    99  	suite.committee = unittest.IdentityListFixture(10)
   100  	suite.myIndex = 5
   101  
   102  	suite.local = new(module.Local)
   103  	suite.local.On("NodeID").Return(suite.committee[suite.myIndex].NodeID)
   104  
   105  	// create a block for each view of interest
   106  	suite.blocksByView = make(map[uint64]*flow.Header)
   107  	for view := suite.dkgStartView; view <= suite.dkgPhase3FinalView; view += dkg.DefaultPollStep {
   108  		header := unittest.BlockHeaderFixture(unittest.HeaderWithView(view))
   109  		suite.blocksByView[view] = header
   110  	}
   111  	suite.firstBlock = suite.blocksByView[100]
   112  
   113  	// expectedPrivKey is the expected private share produced by the dkg run. We
   114  	// will mock the controller to return this value, and we will check it
   115  	// against the value that gets inserted in the DB at the end.
   116  	suite.expectedPrivateKey = unittest.PrivateKeyFixture(crypto.BLSBLS12381, 48)
   117  
   118  	// mock protocol state
   119  	suite.currentEpoch = new(protocol.Epoch)
   120  	suite.currentEpoch.On("Counter").Return(suite.epochCounter, nil)
   121  	suite.currentEpoch.On("DKGPhase1FinalView").Return(suite.dkgPhase1FinalView, nil)
   122  	suite.currentEpoch.On("DKGPhase2FinalView").Return(suite.dkgPhase2FinalView, nil)
   123  	suite.currentEpoch.On("DKGPhase3FinalView").Return(suite.dkgPhase3FinalView, nil)
   124  	suite.nextEpoch = new(protocol.Epoch)
   125  	suite.nextEpoch.On("Counter").Return(suite.NextEpochCounter(), nil)
   126  	suite.nextEpoch.On("InitialIdentities").Return(suite.committee, nil)
   127  
   128  	suite.epochQuery = mocks.NewEpochQuery(suite.T(), suite.epochCounter)
   129  	suite.epochQuery.Add(suite.currentEpoch)
   130  	suite.epochQuery.Add(suite.nextEpoch)
   131  	suite.snapshot = new(protocol.Snapshot)
   132  	suite.snapshot.On("Epochs").Return(suite.epochQuery)
   133  	suite.snapshot.On("Head").Return(suite.firstBlock, nil)
   134  	suite.state = new(protocol.State)
   135  	suite.state.On("AtBlockID", suite.firstBlock.ID()).Return(suite.snapshot)
   136  	suite.state.On("Final").Return(suite.snapshot)
   137  
   138  	// ensure that an attempt is made to insert the expected dkg private share
   139  	// for the next epoch.
   140  	suite.dkgState = new(storage.DKGState)
   141  	suite.dkgState.On("SetDKGStarted", suite.NextEpochCounter()).Return(nil).Once()
   142  	suite.dkgState.On("InsertMyBeaconPrivateKey", mock.Anything, mock.Anything).Run(
   143  		func(args mock.Arguments) {
   144  			epochCounter := args.Get(0).(uint64)
   145  			require.Equal(suite.T(), suite.NextEpochCounter(), epochCounter)
   146  			dkgPriv := args.Get(1).(crypto.PrivateKey)
   147  			require.Equal(suite.T(), suite.expectedPrivateKey, dkgPriv)
   148  		}).
   149  		Return(nil).
   150  		Once()
   151  
   152  	// we will ensure that the controller state transitions get called appropriately
   153  	suite.controller = new(module.DKGController)
   154  	suite.controller.On("Run").Return(nil).Once()
   155  	suite.controller.On("EndPhase1").Return(nil).Once()
   156  	suite.controller.On("EndPhase2").Return(nil).Once()
   157  	suite.controller.On("End").Return(nil).Once()
   158  	suite.controller.On("Poll", mock.Anything).Return(nil).Times(15)
   159  	suite.controller.On("GetArtifacts").Return(suite.expectedPrivateKey, nil, nil).Once()
   160  	suite.controller.On("SubmitResult").Return(nil).Once()
   161  
   162  	suite.factory = new(module.DKGControllerFactory)
   163  	suite.factory.On("Create",
   164  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   165  		suite.committee,
   166  		mock.Anything,
   167  	).Return(suite.controller, nil)
   168  
   169  	suite.warnsLogged = 0
   170  	suite.logger = hookedLogger(&suite.warnsLogged)
   171  
   172  	suite.viewEvents = gadgets.NewViews()
   173  	suite.engine = dkg.NewReactorEngine(
   174  		suite.logger,
   175  		suite.local,
   176  		suite.state,
   177  		suite.dkgState,
   178  		suite.factory,
   179  		suite.viewEvents,
   180  	)
   181  }
   182  
   183  // TestRunDKG_PhaseTransition tests that the DKG is started and completed successfully
   184  // after a phase transition from StakingPhase->SetupPhase.
   185  func (suite *ReactorEngineSuite_SetupPhase) TestRunDKG_PhaseTransition() {
   186  
   187  	// the dkg for this epoch has not been started
   188  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(false, nil).Once()
   189  	// protocol event indicating the setup phase is starting
   190  	suite.engine.EpochSetupPhaseStarted(suite.epochCounter, suite.firstBlock)
   191  
   192  	for view := uint64(100); view <= 250; view += dkg.DefaultPollStep {
   193  		suite.viewEvents.BlockFinalized(suite.blocksByView[view])
   194  	}
   195  
   196  	// check that the appropriate callbacks were registered
   197  	time.Sleep(50 * time.Millisecond)
   198  	suite.controller.AssertExpectations(suite.T())
   199  	suite.dkgState.AssertExpectations(suite.T())
   200  	// happy path - no warn logs expected
   201  	suite.Assert().Equal(0, suite.warnsLogged)
   202  }
   203  
   204  // TestRunDKG_StartupInSetupPhase tests that the DKG is started and completed
   205  // successfully when the engine starts up during the EpochSetup phase, and the
   206  // DKG for this epoch has not been started previously. This is the case for
   207  // consensus nodes joining the network at an epoch boundary.
   208  func (suite *ReactorEngineSuite_SetupPhase) TestRunDKG_StartupInSetupPhase() {
   209  
   210  	// we are in the EpochSetup phase
   211  	suite.snapshot.On("Phase").Return(flow.EpochPhaseSetup, nil).Once()
   212  	// the dkg for this epoch has not been started
   213  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(false, nil).Once()
   214  
   215  	// start up the engine
   216  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   217  
   218  	// keyStorage := new(storage.DKGKeys)
   219  	// keyStorage.On("RetrieveMyDKGPrivateInfo", currentCounter+1).Return(dkgParticipantPrivInfo, true, nil)
   220  	// factory := new(module.DKGControllerFactory)
   221  	for view := uint64(100); view <= 250; view += dkg.DefaultPollStep {
   222  		suite.viewEvents.BlockFinalized(suite.blocksByView[view])
   223  	}
   224  
   225  	// check that the appropriate callbacks were registered
   226  	time.Sleep(50 * time.Millisecond)
   227  	suite.controller.AssertExpectations(suite.T())
   228  	suite.dkgState.AssertExpectations(suite.T())
   229  	// happy path - no warn logs expected
   230  	suite.Assert().Equal(0, suite.warnsLogged)
   231  }
   232  
   233  // TestRunDKG_StartupInSetupPhase_DKGAlreadyStarted tests that the DKG is NOT
   234  // started, when the engine starts up during the EpochSetup phase, and the DKG
   235  // for this epoch HAS been started previously. This will be the case for
   236  // consensus nodes which restart during the DKG.
   237  func (suite *ReactorEngineSuite_SetupPhase) TestRunDKG_StartupInSetupPhase_DKGAlreadyStarted() {
   238  
   239  	// we are in the EpochSetup phase
   240  	suite.snapshot.On("Phase").Return(flow.EpochPhaseSetup, nil).Once()
   241  	// the dkg for this epoch has been started
   242  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(true, nil).Once()
   243  
   244  	// start up the engine
   245  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   246  
   247  	// we should not have instantiated the DKG
   248  	suite.factory.AssertNotCalled(suite.T(), "Create",
   249  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   250  		suite.committee,
   251  		mock.Anything,
   252  	)
   253  
   254  	// we should log a warning that the DKG has already started
   255  	suite.Assert().Equal(1, suite.warnsLogged)
   256  }
   257  
   258  // ReactorEngineSuite_CommittedPhase tests the Reactor engine's operation
   259  // during the transition to the EpochCommitted phase, after the DKG has
   260  // completed locally, and we are comparing our local results to the
   261  // canonical DKG results.
   262  type ReactorEngineSuite_CommittedPhase struct {
   263  	suite.Suite
   264  
   265  	epochCounter         uint64            // current epoch counter
   266  	myLocalBeaconKey     crypto.PrivateKey // my locally computed beacon key
   267  	myGlobalBeaconPubKey crypto.PublicKey  // my public key, as dictated by global DKG
   268  	dkgEndState          flow.DKGEndState  // backend for DGKState.
   269  	firstBlock           *flow.Header      // first block of EpochCommitted phase
   270  	warnsLogged          int               // count # of warn-level logs
   271  
   272  	me       *module.Local
   273  	dkgState *storage.DKGState
   274  	state    *protocol.State
   275  	snap     *protocol.Snapshot
   276  	factory  *module.DKGControllerFactory
   277  
   278  	engine *dkg.ReactorEngine
   279  }
   280  
   281  func TestReactorEngineSuite_CommittedPhase(t *testing.T) {
   282  	suite.Run(t, new(ReactorEngineSuite_CommittedPhase))
   283  }
   284  
   285  func (suite *ReactorEngineSuite_CommittedPhase) NextEpochCounter() uint64 {
   286  	return suite.epochCounter + 1
   287  }
   288  
   289  func (suite *ReactorEngineSuite_CommittedPhase) SetupTest() {
   290  
   291  	suite.epochCounter = rand.Uint64()
   292  	suite.dkgEndState = flow.DKGEndStateUnknown
   293  	suite.me = new(module.Local)
   294  
   295  	id := unittest.IdentifierFixture()
   296  	suite.me.On("NodeID").Return(id)
   297  
   298  	// by default we seed the test suite with consistent keys
   299  	suite.myLocalBeaconKey = unittest.RandomBeaconPriv().PrivateKey
   300  	suite.myGlobalBeaconPubKey = suite.myLocalBeaconKey.PublicKey()
   301  
   302  	suite.dkgState = new(storage.DKGState)
   303  	suite.dkgState.On("RetrieveMyBeaconPrivateKey", suite.NextEpochCounter()).Return(
   304  		func(_ uint64) crypto.PrivateKey { return suite.myLocalBeaconKey },
   305  		func(_ uint64) error {
   306  			if suite.myLocalBeaconKey == nil {
   307  				return storerr.ErrNotFound
   308  			}
   309  			return nil
   310  		},
   311  	)
   312  	suite.dkgState.On("SetDKGEndState", suite.NextEpochCounter(), mock.Anything).
   313  		Run(func(args mock.Arguments) {
   314  			assert.Equal(suite.T(), flow.DKGEndStateUnknown, suite.dkgEndState) // must be unset
   315  			endState := args[1].(flow.DKGEndState)
   316  			suite.dkgEndState = endState
   317  		}).
   318  		Return(nil)
   319  	suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return(
   320  		func(_ uint64) flow.DKGEndState { return suite.dkgEndState },
   321  		func(_ uint64) error {
   322  			if suite.dkgEndState == flow.DKGEndStateUnknown {
   323  				return storerr.ErrNotFound
   324  			}
   325  			return nil
   326  		},
   327  	)
   328  
   329  	currentEpoch := new(protocol.Epoch)
   330  	currentEpoch.On("Counter").Return(suite.epochCounter, nil)
   331  
   332  	nextDKG := new(protocol.DKG)
   333  	nextDKG.On("KeyShare", id).Return(
   334  		func(_ flow.Identifier) crypto.PublicKey { return suite.myGlobalBeaconPubKey },
   335  		func(_ flow.Identifier) error { return nil },
   336  	)
   337  
   338  	nextEpoch := new(protocol.Epoch)
   339  	nextEpoch.On("Counter").Return(suite.NextEpochCounter(), nil)
   340  	nextEpoch.On("DKG").Return(nextDKG, nil)
   341  
   342  	epochQuery := mocks.NewEpochQuery(suite.T(), suite.epochCounter)
   343  	epochQuery.Add(currentEpoch)
   344  	epochQuery.Add(nextEpoch)
   345  
   346  	firstBlock := unittest.BlockHeaderFixture(unittest.HeaderWithView(100))
   347  	suite.firstBlock = firstBlock
   348  
   349  	suite.snap = new(protocol.Snapshot)
   350  	suite.snap.On("Epochs").Return(epochQuery)
   351  	suite.snap.On("Head").Return(firstBlock, nil)
   352  
   353  	suite.state = new(protocol.State)
   354  	suite.state.On("AtBlockID", firstBlock.ID()).Return(suite.snap)
   355  	suite.state.On("Final").Return(suite.snap)
   356  
   357  	// count number of warn-level logs
   358  	suite.warnsLogged = 0
   359  	logger := hookedLogger(&suite.warnsLogged)
   360  
   361  	suite.factory = new(module.DKGControllerFactory)
   362  	viewEvents := gadgets.NewViews()
   363  
   364  	suite.engine = dkg.NewReactorEngine(
   365  		logger,
   366  		suite.me,
   367  		suite.state,
   368  		suite.dkgState,
   369  		suite.factory,
   370  		viewEvents,
   371  	)
   372  }
   373  
   374  // TestDKGSuccess tests the path where we are checking the global DKG
   375  // results and observe our key is consistent.
   376  // We should:
   377  // * set the DKG end state to Success
   378  func (suite *ReactorEngineSuite_CommittedPhase) TestDKGSuccess() {
   379  
   380  	// no change to suite - this is the happy path
   381  
   382  	suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock)
   383  	suite.Require().Equal(0, suite.warnsLogged)
   384  	suite.Assert().Equal(flow.DKGEndStateSuccess, suite.dkgEndState)
   385  }
   386  
   387  // TestInconsistentKey tests the path where we are checking the global DKG
   388  // results and observe that our locally computed key is inconsistent.
   389  // We should:
   390  // * log a warning
   391  // * set the DKG end state accordingly
   392  func (suite *ReactorEngineSuite_CommittedPhase) TestInconsistentKey() {
   393  
   394  	// set our global pub key to a random value
   395  	suite.myGlobalBeaconPubKey = unittest.RandomBeaconPriv().PublicKey()
   396  
   397  	suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock)
   398  	suite.Require().Equal(1, suite.warnsLogged)
   399  	suite.Assert().Equal(flow.DKGEndStateInconsistentKey, suite.dkgEndState)
   400  }
   401  
   402  // TestMissingKey tests the path where we are checking the global DKG results
   403  // and observe that we have not stored a locally computed key.
   404  // We should:
   405  // * log a warning
   406  // * set the DKG end state accordingly
   407  func (suite *ReactorEngineSuite_CommittedPhase) TestMissingKey() {
   408  
   409  	// remove our key
   410  	suite.myLocalBeaconKey = nil
   411  
   412  	suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock)
   413  	suite.Require().Equal(1, suite.warnsLogged)
   414  	suite.Assert().Equal(flow.DKGEndStateNoKey, suite.dkgEndState)
   415  }
   416  
   417  // TestLocalDKGFailure tests the path where we are checking the global DKG
   418  // results and observe that we have already set the DKG end state as a failure.
   419  // We should:
   420  // * log a warning
   421  // * keep the dkg end state as it is
   422  func (suite *ReactorEngineSuite_CommittedPhase) TestLocalDKGFailure() {
   423  
   424  	// set dkg end state as failure
   425  	suite.dkgEndState = flow.DKGEndStateDKGFailure
   426  
   427  	suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock)
   428  	suite.Require().Equal(1, suite.warnsLogged)
   429  	suite.Assert().Equal(flow.DKGEndStateDKGFailure, suite.dkgEndState)
   430  }
   431  
   432  // TestStartupInCommittedPhase_DKGSuccess tests that the dkg end state is correctly
   433  // set when starting in EpochCommitted phase and a successful DKG
   434  func (suite *ReactorEngineSuite_CommittedPhase) TestStartupInCommittedPhase_DKGSuccess() {
   435  
   436  	// we are in the EpochSetup phase
   437  	suite.snap.On("Phase").Return(flow.EpochPhaseCommitted, nil).Once()
   438  	// the dkg for this epoch has been started but not ended
   439  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(true, nil).Once()
   440  	suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return(flow.DKGEndStateUnknown, storerr.ErrNotFound).Once()
   441  
   442  	// start up the engine
   443  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   444  
   445  	// we should not have instantiated the DKG
   446  	suite.factory.AssertNotCalled(suite.T(), "Create",
   447  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   448  		mock.Anything,
   449  		mock.Anything,
   450  	)
   451  	// should set DKG end state
   452  	suite.Assert().Equal(flow.DKGEndStateSuccess, suite.dkgEndState)
   453  }
   454  
   455  // TestStartupInCommittedPhase_DKGSuccess tests that the dkg end state is correctly
   456  // set when starting in EpochCommitted phase and the DKG end state is already set.
   457  func (suite *ReactorEngineSuite_CommittedPhase) TestStartupInCommittedPhase_DKGEndStateAlreadySet() {
   458  
   459  	// we are in the EpochSetup phase
   460  	suite.snap.On("Phase").Return(flow.EpochPhaseCommitted, nil).Once()
   461  	// the dkg for this epoch has been started and ended
   462  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(true, nil).Once()
   463  	suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return(flow.DKGEndStateNoKey, nil).Once()
   464  
   465  	// start up the engine
   466  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   467  
   468  	// we should not have instantiated the DKG
   469  	suite.factory.AssertNotCalled(suite.T(), "Create",
   470  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   471  		mock.Anything,
   472  		mock.Anything,
   473  	)
   474  }
   475  
   476  // TestStartupInCommittedPhase_InconsistentKey tests that the dkg end state is correctly
   477  // set when starting in EpochCommitted phase and we have stored an inconsistent key.
   478  func (suite *ReactorEngineSuite_CommittedPhase) TestStartupInCommittedPhase_InconsistentKey() {
   479  
   480  	// we are in the EpochSetup phase
   481  	suite.snap.On("Phase").Return(flow.EpochPhaseCommitted, nil).Once()
   482  	// the dkg for this epoch has been started but not ended
   483  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(true, nil).Once()
   484  	suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return(flow.DKGEndStateUnknown, storerr.ErrNotFound).Once()
   485  
   486  	// set our global pub key to a random value
   487  	suite.myGlobalBeaconPubKey = unittest.RandomBeaconPriv().PublicKey()
   488  
   489  	// start up the engine
   490  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   491  
   492  	// we should not have instantiated the DKG
   493  	suite.factory.AssertNotCalled(suite.T(), "Create",
   494  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   495  		mock.Anything,
   496  		mock.Anything,
   497  	)
   498  	// should set DKG end state
   499  	suite.Assert().Equal(flow.DKGEndStateInconsistentKey, suite.dkgEndState)
   500  }
   501  
   502  // TestStartupInCommittedPhase_MissingKey tests that the dkg end state is correctly
   503  // set when starting in EpochCommitted phase and we have not stored any key.
   504  func (suite *ReactorEngineSuite_CommittedPhase) TestStartupInCommittedPhase_MissingKey() {
   505  
   506  	// we are in the EpochSetup phase
   507  	suite.snap.On("Phase").Return(flow.EpochPhaseCommitted, nil).Once()
   508  	// the dkg for this epoch has been started but not ended
   509  	suite.dkgState.On("GetDKGStarted", suite.NextEpochCounter()).Return(true, nil).Once()
   510  	suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return(flow.DKGEndStateUnknown, storerr.ErrNotFound).Once()
   511  
   512  	// remove our key
   513  	suite.myLocalBeaconKey = nil
   514  
   515  	// start up the engine
   516  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second)
   517  
   518  	// we should not have instantiated the DKG
   519  	suite.factory.AssertNotCalled(suite.T(), "Create",
   520  		dkgmodule.CanonicalInstanceID(suite.firstBlock.ChainID, suite.NextEpochCounter()),
   521  		mock.Anything,
   522  		mock.Anything,
   523  	)
   524  	// should set DKG end state
   525  	suite.Assert().Equal(flow.DKGEndStateNoKey, suite.dkgEndState)
   526  }
   527  
   528  // utility function to track the number of warn-level calls to a logger
   529  func hookedLogger(calls *int) zerolog.Logger {
   530  	hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) {
   531  		if level == zerolog.WarnLevel {
   532  			*calls++
   533  		}
   534  	})
   535  	return zerolog.New(os.Stdout).Level(zerolog.InfoLevel).Hook(hook)
   536  }