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 }