github.com/koko1123/flow-go-1@v0.29.6/engine/collection/epochmgr/engine_test.go (about) 1 package epochmgr 2 3 import ( 4 "io" 5 "testing" 6 "time" 7 8 "github.com/rs/zerolog" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/suite" 12 13 "github.com/koko1123/flow-go-1/consensus/hotstuff" 14 mockhotstuff "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 15 epochmgr "github.com/koko1123/flow-go-1/engine/collection/epochmgr/mock" 16 "github.com/koko1123/flow-go-1/model/flow" 17 realmodule "github.com/koko1123/flow-go-1/module" 18 "github.com/koko1123/flow-go-1/module/mempool" 19 "github.com/koko1123/flow-go-1/module/mempool/epochs" 20 "github.com/koko1123/flow-go-1/module/mempool/herocache" 21 "github.com/koko1123/flow-go-1/module/metrics" 22 module "github.com/koko1123/flow-go-1/module/mock" 23 "github.com/koko1123/flow-go-1/network" 24 "github.com/koko1123/flow-go-1/network/mocknetwork" 25 realcluster "github.com/koko1123/flow-go-1/state/cluster" 26 cluster "github.com/koko1123/flow-go-1/state/cluster/mock" 27 realprotocol "github.com/koko1123/flow-go-1/state/protocol" 28 events "github.com/koko1123/flow-go-1/state/protocol/events/mock" 29 protocol "github.com/koko1123/flow-go-1/state/protocol/mock" 30 "github.com/koko1123/flow-go-1/utils/unittest" 31 "github.com/koko1123/flow-go-1/utils/unittest/mocks" 32 ) 33 34 // mockComponents is a container for the mocked version of epoch components. 35 type mockComponents struct { 36 state *cluster.State 37 prop *mocknetwork.Engine 38 sync *mocknetwork.Engine 39 hotstuff *module.HotStuff 40 aggregator *mockhotstuff.VoteAggregator 41 } 42 43 func newMockComponents() *mockComponents { 44 45 components := &mockComponents{ 46 state: new(cluster.State), 47 prop: new(mocknetwork.Engine), 48 sync: new(mocknetwork.Engine), 49 hotstuff: new(module.HotStuff), 50 aggregator: new(mockhotstuff.VoteAggregator), 51 } 52 unittest.ReadyDoneify(components.prop) 53 unittest.ReadyDoneify(components.sync) 54 unittest.ReadyDoneify(components.hotstuff) 55 unittest.ReadyDoneify(components.aggregator) 56 57 // for now only aggregator and hotstuff supports module.Startable, mock only it 58 components.hotstuff.On("Start", mock.Anything) 59 components.aggregator.On("Start", mock.Anything) 60 61 return components 62 } 63 64 type Suite struct { 65 suite.Suite 66 67 // engine dependencies 68 log zerolog.Logger 69 me *module.Local 70 state *protocol.State 71 snap *protocol.Snapshot 72 pools *epochs.TransactionPools 73 74 // qc voter dependencies 75 signer *mockhotstuff.Signer 76 client *module.QCContractClient 77 voter *module.ClusterRootQCVoter 78 factory *epochmgr.EpochComponentsFactory 79 heights *events.Heights 80 81 epochQuery *mocks.EpochQuery 82 counter uint64 // reflects the counter of the current epoch 83 epochs map[uint64]*protocol.Epoch // track all epochs 84 components map[uint64]*mockComponents // track all epoch components 85 86 engine *Engine 87 } 88 89 func (suite *Suite) SetupTest() { 90 91 suite.log = zerolog.New(io.Discard) 92 suite.me = new(module.Local) 93 suite.state = new(protocol.State) 94 suite.snap = new(protocol.Snapshot) 95 96 suite.epochs = make(map[uint64]*protocol.Epoch) 97 suite.components = make(map[uint64]*mockComponents) 98 99 suite.signer = new(mockhotstuff.Signer) 100 suite.client = new(module.QCContractClient) 101 suite.voter = new(module.ClusterRootQCVoter) 102 suite.factory = new(epochmgr.EpochComponentsFactory) 103 suite.heights = new(events.Heights) 104 105 // mock out Create so that it instantiates the appropriate mocks 106 suite.factory.On("Create", mock.Anything). 107 Run(func(args mock.Arguments) { 108 epoch, ok := args.Get(0).(realprotocol.Epoch) 109 suite.Require().Truef(ok, "invalid type %T", args.Get(0)) 110 counter, err := epoch.Counter() 111 suite.Require().Nil(err) 112 suite.components[counter] = newMockComponents() 113 }). 114 Return( 115 func(epoch realprotocol.Epoch) realcluster.State { return suite.ComponentsForEpoch(epoch).state }, 116 func(epoch realprotocol.Epoch) network.Engine { return suite.ComponentsForEpoch(epoch).prop }, 117 func(epoch realprotocol.Epoch) network.Engine { return suite.ComponentsForEpoch(epoch).sync }, 118 func(epoch realprotocol.Epoch) realmodule.HotStuff { return suite.ComponentsForEpoch(epoch).hotstuff }, 119 func(epoch realprotocol.Epoch) hotstuff.VoteAggregator { 120 return suite.ComponentsForEpoch(epoch).aggregator 121 }, 122 func(epoch realprotocol.Epoch) error { return nil }, 123 ) 124 125 suite.epochQuery = mocks.NewEpochQuery(suite.T(), suite.counter) 126 suite.state.On("Final").Return(suite.snap) 127 suite.snap.On("Epochs").Return(suite.epochQuery) 128 129 // add current and next epochs 130 suite.AddEpoch(suite.counter) 131 suite.AddEpoch(suite.counter + 1) 132 133 suite.pools = epochs.NewTransactionPools(func(_ uint64) mempool.Transactions { 134 return herocache.NewTransactions(1000, suite.log, metrics.NewNoopCollector()) 135 }) 136 137 var err error 138 suite.engine, err = New(suite.log, suite.me, suite.state, suite.pools, suite.voter, suite.factory, suite.heights) 139 suite.Require().Nil(err) 140 } 141 142 func TestEpochManager(t *testing.T) { 143 suite.Run(t, new(Suite)) 144 } 145 146 // TransitionEpoch triggers an epoch transition in the suite's mocks. 147 func (suite *Suite) TransitionEpoch() { 148 suite.counter++ 149 suite.epochQuery.Transition() 150 } 151 152 // AddEpoch adds an epoch with the given counter. 153 func (suite *Suite) AddEpoch(counter uint64) *protocol.Epoch { 154 epoch := new(protocol.Epoch) 155 epoch.On("Counter").Return(counter, nil) 156 suite.epochs[counter] = epoch 157 suite.epochQuery.Add(epoch) 158 return epoch 159 } 160 161 // AssertEpochStarted asserts that the components for the given epoch have been started. 162 func (suite *Suite) AssertEpochStarted(counter uint64) { 163 components, ok := suite.components[counter] 164 suite.Assert().True(ok, "asserting nonexistent epoch started", counter) 165 components.prop.AssertCalled(suite.T(), "Ready") 166 components.sync.AssertCalled(suite.T(), "Ready") 167 components.aggregator.AssertCalled(suite.T(), "Ready") 168 components.aggregator.AssertCalled(suite.T(), "Start", mock.Anything) 169 } 170 171 // AssertEpochStopped asserts that the components for the given epoch have been stopped. 172 func (suite *Suite) AssertEpochStopped(counter uint64) { 173 components, ok := suite.components[counter] 174 suite.Assert().True(ok, "asserting nonexistent epoch stopped", counter) 175 components.prop.AssertCalled(suite.T(), "Done") 176 components.sync.AssertCalled(suite.T(), "Done") 177 } 178 179 func (suite *Suite) ComponentsForEpoch(epoch realprotocol.Epoch) *mockComponents { 180 counter, err := epoch.Counter() 181 suite.Require().Nil(err, "cannot get counter") 182 components, ok := suite.components[counter] 183 suite.Require().True(ok, "missing component for counter", counter) 184 return components 185 } 186 187 // MockAsUnauthorizedNode mocks the factory to return a sentinel indicating 188 // we are not authorized in the epoch 189 func (suite *Suite) MockAsUnauthorizedNode() { 190 191 suite.factory = new(epochmgr.EpochComponentsFactory) 192 suite.factory. 193 On("Create", mock.Anything). 194 Return(nil, nil, nil, nil, nil, ErrNotAuthorizedForEpoch) 195 196 var err error 197 suite.engine, err = New(suite.log, suite.me, suite.state, suite.pools, suite.voter, suite.factory, suite.heights) 198 suite.Require().Nil(err) 199 } 200 201 // if we start up during the setup phase, we should kick off the root QC voter 202 func (suite *Suite) TestRestartInSetupPhase() { 203 204 suite.snap.On("Phase").Return(flow.EpochPhaseSetup, nil) 205 // should call voter with next epoch 206 var called = make(chan struct{}) 207 suite.voter.On("Vote", mock.Anything, suite.epochQuery.Next()). 208 Return(nil). 209 Run(func(args mock.Arguments) { 210 close(called) 211 }).Once() 212 213 // start up the engine 214 unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second) 215 unittest.AssertClosesBefore(suite.T(), called, time.Second) 216 217 suite.voter.AssertExpectations(suite.T()) 218 } 219 220 // When a collection node joins the network at an epoch boundary, they must 221 // start running during the EpochSetup phase in the epoch before they become 222 // an authorized member so they submit their cluster QC vote. 223 // 224 // These nodes must kick off the root QC voter but should not attempt to 225 // participate in cluster consensus in the current epoch. 226 func (suite *Suite) TestStartAsUnauthorizedNode() { 227 suite.MockAsUnauthorizedNode() 228 229 // we are in setup phase 230 suite.snap.On("Phase").Return(flow.EpochPhaseSetup, nil) 231 232 // should call voter with next epoch 233 var called = make(chan struct{}) 234 suite.voter.On("Vote", mock.Anything, suite.epochQuery.Next()). 235 Return(nil). 236 Run(func(args mock.Arguments) { 237 close(called) 238 }).Once() 239 240 // start the engine 241 unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second) 242 243 // should have submitted vote 244 unittest.AssertClosesBefore(suite.T(), called, time.Second) 245 suite.voter.AssertExpectations(suite.T()) 246 // should have no epoch components 247 assert.Empty(suite.T(), suite.engine.epochs, "should have 0 epoch components") 248 } 249 250 // should kick off root QC voter on setup phase start event 251 func (suite *Suite) TestRespondToPhaseChange() { 252 253 // should call voter with next epoch 254 var called = make(chan struct{}) 255 suite.voter.On("Vote", mock.Anything, suite.epochQuery.Next()). 256 Return(nil). 257 Run(func(args mock.Arguments) { 258 close(called) 259 }).Once() 260 261 first := unittest.BlockHeaderFixture() 262 suite.state.On("AtBlockID", first.ID()).Return(suite.snap) 263 264 suite.engine.EpochSetupPhaseStarted(0, first) 265 unittest.AssertClosesBefore(suite.T(), called, time.Second) 266 267 suite.voter.AssertExpectations(suite.T()) 268 } 269 270 func (suite *Suite) TestRespondToEpochTransition() { 271 272 // we are in committed phase 273 suite.snap.On("Phase").Return(flow.EpochPhaseCommitted, nil) 274 275 // start the engine 276 unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), time.Second) 277 278 first := unittest.BlockHeaderFixture() 279 suite.state.On("AtBlockID", first.ID()).Return(suite.snap) 280 281 // should set up callback for height at which previous epoch expires 282 var expiryCallback func() 283 var done = make(chan struct{}) 284 suite.heights.On("OnHeight", first.Height+flow.DefaultTransactionExpiry, mock.Anything). 285 Run(func(args mock.Arguments) { 286 expiryCallback = args.Get(1).(func()) 287 close(done) 288 }). 289 Once() 290 291 // mock the epoch transition 292 suite.TransitionEpoch() 293 // notify the engine of the epoch transition 294 suite.engine.EpochTransition(suite.counter, first) 295 unittest.AssertClosesBefore(suite.T(), done, time.Second) 296 suite.Assert().NotNil(expiryCallback) 297 298 // the engine should have two epochs under management, the just ended epoch 299 // and the newly started epoch 300 suite.Assert().Len(suite.engine.epochs, 2) 301 _, exists := suite.engine.epochs[suite.counter-1] 302 suite.Assert().True(exists, "should have previous epoch components") 303 _, exists = suite.engine.epochs[suite.counter] 304 suite.Assert().True(exists, "should have current epoch components") 305 306 // the newly started (current) epoch should have been started 307 suite.AssertEpochStarted(suite.counter) 308 309 // when we invoke the callback registered to handle the previous epoch's 310 // expiry, the previous epoch components should be cleaned up 311 expiryCallback() 312 313 suite.Assert().Eventually(func() bool { 314 suite.engine.unit.Lock() 315 defer suite.engine.unit.Unlock() 316 return len(suite.engine.epochs) == 1 317 }, time.Second, time.Millisecond) 318 319 // after the previous epoch expires, we should only have current epoch 320 _, exists = suite.engine.epochs[suite.counter] 321 suite.Assert().True(exists, "should have current epoch components") 322 _, exists = suite.engine.epochs[suite.counter-1] 323 suite.Assert().False(exists, "should not have previous epoch components") 324 325 // the expired epoch should have been stopped 326 suite.AssertEpochStopped(suite.counter - 1) 327 }