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