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  }