github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/engine/consensus/dkg" 17 "github.com/koko1123/flow-go-1/model/flow" 18 dkgmodule "github.com/koko1123/flow-go-1/module/dkg" 19 module "github.com/koko1123/flow-go-1/module/mock" 20 "github.com/koko1123/flow-go-1/state/protocol/events/gadgets" 21 protocol "github.com/koko1123/flow-go-1/state/protocol/mock" 22 storerr "github.com/koko1123/flow-go-1/storage" 23 storage "github.com/koko1123/flow-go-1/storage/mock" 24 "github.com/koko1123/flow-go-1/utils/unittest" 25 "github.com/koko1123/flow-go-1/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 277 engine *dkg.ReactorEngine 278 } 279 280 func TestReactorEngineSuite_CommittedPhase(t *testing.T) { 281 suite.Run(t, new(ReactorEngineSuite_CommittedPhase)) 282 } 283 284 func (suite *ReactorEngineSuite_CommittedPhase) NextEpochCounter() uint64 { 285 return suite.epochCounter + 1 286 } 287 288 func (suite *ReactorEngineSuite_CommittedPhase) SetupTest() { 289 290 suite.epochCounter = rand.Uint64() 291 suite.dkgEndState = flow.DKGEndStateUnknown 292 suite.me = new(module.Local) 293 294 id := unittest.IdentifierFixture() 295 suite.me.On("NodeID").Return(id) 296 297 // by default we seed the test suite with consistent keys 298 suite.myLocalBeaconKey = unittest.RandomBeaconPriv().PrivateKey 299 suite.myGlobalBeaconPubKey = suite.myLocalBeaconKey.PublicKey() 300 301 suite.dkgState = new(storage.DKGState) 302 suite.dkgState.On("RetrieveMyBeaconPrivateKey", suite.NextEpochCounter()).Return( 303 func(_ uint64) crypto.PrivateKey { return suite.myLocalBeaconKey }, 304 func(_ uint64) error { 305 if suite.myLocalBeaconKey == nil { 306 return storerr.ErrNotFound 307 } 308 return nil 309 }, 310 ) 311 suite.dkgState.On("SetDKGEndState", suite.NextEpochCounter(), mock.Anything). 312 Run(func(args mock.Arguments) { 313 assert.Equal(suite.T(), flow.DKGEndStateUnknown, suite.dkgEndState) // must be unset 314 endState := args[1].(flow.DKGEndState) 315 suite.dkgEndState = endState 316 }). 317 Return(nil) 318 suite.dkgState.On("GetDKGEndState", suite.NextEpochCounter()).Return( 319 func(_ uint64) flow.DKGEndState { return suite.dkgEndState }, 320 func(_ uint64) error { 321 if suite.dkgEndState == flow.DKGEndStateUnknown { 322 return storerr.ErrNotFound 323 } 324 return nil 325 }, 326 ) 327 328 currentEpoch := new(protocol.Epoch) 329 currentEpoch.On("Counter").Return(suite.epochCounter, nil) 330 331 nextDKG := new(protocol.DKG) 332 nextDKG.On("KeyShare", id).Return( 333 func(_ flow.Identifier) crypto.PublicKey { return suite.myGlobalBeaconPubKey }, 334 func(_ flow.Identifier) error { return nil }, 335 ) 336 337 nextEpoch := new(protocol.Epoch) 338 nextEpoch.On("Counter").Return(suite.NextEpochCounter(), nil) 339 nextEpoch.On("DKG").Return(nextDKG, nil) 340 341 epochQuery := mocks.NewEpochQuery(suite.T(), suite.epochCounter) 342 epochQuery.Add(currentEpoch) 343 epochQuery.Add(nextEpoch) 344 345 firstBlock := unittest.BlockHeaderFixture(unittest.HeaderWithView(100)) 346 suite.firstBlock = firstBlock 347 348 suite.snap = new(protocol.Snapshot) 349 suite.snap.On("Epochs").Return(epochQuery) 350 351 suite.state = new(protocol.State) 352 suite.state.On("AtBlockID", firstBlock.ID()).Return(suite.snap) 353 354 // count number of warn-level logs 355 suite.warnsLogged = 0 356 logger := hookedLogger(&suite.warnsLogged) 357 358 factory := new(module.DKGControllerFactory) 359 viewEvents := gadgets.NewViews() 360 361 suite.engine = dkg.NewReactorEngine( 362 logger, 363 suite.me, 364 suite.state, 365 suite.dkgState, 366 factory, 367 viewEvents, 368 ) 369 } 370 371 // TestDKGSuccess tests the path where we are checking the global DKG 372 // results and observe our key is consistent. 373 // We should: 374 // * set the DKG end state to Success 375 func (suite *ReactorEngineSuite_CommittedPhase) TestDKGSuccess() { 376 377 // no change to suite - this is the happy path 378 379 suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock) 380 suite.Require().Equal(0, suite.warnsLogged) 381 suite.Assert().Equal(flow.DKGEndStateSuccess, suite.dkgEndState) 382 } 383 384 // TestInconsistentKey tests the path where we are checking the global DKG 385 // results and observe that our locally computed key is inconsistent. 386 // We should: 387 // * log a warning 388 // * set the DKG end state accordingly 389 func (suite *ReactorEngineSuite_CommittedPhase) TestInconsistentKey() { 390 391 // set our global pub key to a random value 392 suite.myGlobalBeaconPubKey = unittest.RandomBeaconPriv().PublicKey() 393 394 suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock) 395 suite.Require().Equal(1, suite.warnsLogged) 396 suite.Assert().Equal(flow.DKGEndStateInconsistentKey, suite.dkgEndState) 397 } 398 399 // TestMissingKey tests the path where we are checking the global DKG results 400 // and observe that we have not stored a locally computed key. 401 // We should: 402 // * log a warning 403 // * set the DKG end state accordingly 404 func (suite *ReactorEngineSuite_CommittedPhase) TestMissingKey() { 405 406 // remove our key 407 suite.myLocalBeaconKey = nil 408 409 suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock) 410 suite.Require().Equal(1, suite.warnsLogged) 411 suite.Assert().Equal(flow.DKGEndStateNoKey, suite.dkgEndState) 412 } 413 414 // TestLocalDKGFailure tests the path where we are checking the global DKG 415 // results and observe that we have already set the DKG end state as a failure. 416 // We should: 417 // * log a warning 418 // * keep the dkg end state as it is 419 func (suite *ReactorEngineSuite_CommittedPhase) TestLocalDKGFailure() { 420 421 // set dkg end state as failure 422 suite.dkgEndState = flow.DKGEndStateDKGFailure 423 424 suite.engine.EpochCommittedPhaseStarted(suite.epochCounter, suite.firstBlock) 425 suite.Require().Equal(1, suite.warnsLogged) 426 suite.Assert().Equal(flow.DKGEndStateDKGFailure, suite.dkgEndState) 427 } 428 429 // utility function to track the number of warn-level calls to a logger 430 func hookedLogger(calls *int) zerolog.Logger { 431 hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) { 432 if level == zerolog.WarnLevel { 433 *calls++ 434 } 435 }) 436 return zerolog.New(os.Stdout).Level(zerolog.InfoLevel).Hook(hook) 437 }