github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/consensusfsm/fsm_test.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package consensusfsm 7 8 import ( 9 "context" 10 "testing" 11 "time" 12 13 "github.com/facebookgo/clock" 14 "github.com/golang/mock/gomock" 15 fsm "github.com/iotexproject/go-fsm" 16 "github.com/pkg/errors" 17 "github.com/stretchr/testify/require" 18 19 "github.com/iotexproject/iotex-core/pkg/log" 20 "github.com/iotexproject/iotex-core/testutil" 21 ) 22 23 func TestBackdoorEvt(t *testing.T) { 24 t.Parallel() 25 require := require.New(t) 26 ctrl := gomock.NewController(t) 27 mockCtx := NewMockContext(ctrl) 28 mockCtx.EXPECT().IsFutureEvent(gomock.Any()).Return(false).AnyTimes() 29 mockCtx.EXPECT().IsStaleEvent(gomock.Any()).Return(false).AnyTimes() 30 mockCtx.EXPECT().EventChanSize().Return(uint(10)).AnyTimes() 31 mockCtx.EXPECT().Logger().Return(log.Logger("consensus")).AnyTimes() 32 mockCtx.EXPECT().Prepare().Return(nil).AnyTimes() 33 mockCtx.EXPECT().NewConsensusEvent(gomock.Any(), gomock.Any()).DoAndReturn( 34 func(eventType fsm.EventType, data interface{}) *ConsensusEvent { 35 return &ConsensusEvent{ 36 eventType: eventType, 37 data: data, 38 } 39 }).AnyTimes() 40 cfsm, err := NewConsensusFSM(mockCtx, clock.NewMock()) 41 require.NoError(err) 42 require.NotNil(cfsm) 43 require.Equal(sPrepare, cfsm.CurrentState()) 44 45 require.NoError(cfsm.Start(context.Background())) 46 defer func() { 47 require.NoError(cfsm.Stop(context.Background())) 48 }() 49 50 for _, state := range consensusStates { 51 backdoorEvt := &ConsensusEvent{ 52 eventType: BackdoorEvent, 53 data: state, 54 } 55 cfsm.produce(backdoorEvt, 0) 56 require.NoError(testutil.WaitUntil(10*time.Millisecond, 100*time.Millisecond, func() (bool, error) { 57 return state == cfsm.CurrentState(), nil 58 })) 59 } 60 } 61 62 func TestStateTransitionFunctions(t *testing.T) { 63 t.Parallel() 64 require := require.New(t) 65 ctrl := gomock.NewController(t) 66 mockClock := clock.NewMock() 67 mockCtx := NewMockContext(ctrl) 68 mockCtx.EXPECT().Logger().Return(log.Logger("consensus")).AnyTimes() 69 mockCtx.EXPECT().EventChanSize().Return(uint(10)).AnyTimes() 70 mockCtx.EXPECT().AcceptBlockTTL(gomock.Any()).Return(4 * time.Second).AnyTimes() 71 mockCtx.EXPECT().AcceptProposalEndorsementTTL(gomock.Any()).Return(2 * time.Second).AnyTimes() 72 mockCtx.EXPECT().AcceptLockEndorsementTTL(gomock.Any()).Return(2 * time.Second).AnyTimes() 73 mockCtx.EXPECT().CommitTTL(gomock.Any()).Return(2 * time.Second).AnyTimes() 74 mockCtx.EXPECT().UnmatchedEventInterval(gomock.Any()).Return(100 * time.Millisecond).AnyTimes() 75 mockCtx.EXPECT().NewConsensusEvent(gomock.Any(), gomock.Any()).DoAndReturn( 76 func(eventType fsm.EventType, data interface{}) *ConsensusEvent { 77 return &ConsensusEvent{ 78 eventType: eventType, 79 data: data, 80 } 81 }).AnyTimes() 82 cfsm, err := NewConsensusFSM(mockCtx, mockClock) 83 require.NoError(err) 84 require.NotNil(cfsm) 85 require.Equal(sPrepare, cfsm.CurrentState()) 86 evt := &ConsensusEvent{eventType: BackdoorEvent, data: sPrepare} 87 88 t.Run("prepare", func(t *testing.T) { 89 t.Run("with-error", func(t *testing.T) { 90 mockCtx.EXPECT().Prepare().Return(errors.New("some error")).Times(1) 91 mockCtx.EXPECT().Active().Return(true).Times(1) 92 state, err := cfsm.prepare(evt) 93 require.NoError(err) 94 require.Equal(sPrepare, state) 95 time.Sleep(100 * time.Millisecond) 96 mockClock.Add(10 * time.Second) 97 evt := <-cfsm.evtq 98 require.Equal(ePrepare, evt.Type()) 99 }) 100 t.Run("stand-by-or-is-not-delegate", func(t *testing.T) { 101 mockCtx.EXPECT().Prepare().Return(nil).Times(2) 102 mockCtx.EXPECT().Proposal().Return(nil, nil).Times(2) 103 mockCtx.EXPECT().WaitUntilRoundStart().Return(time.Duration(0)).Times(2) 104 mockCtx.EXPECT().IsDelegate().Return(false).Times(2) 105 mockCtx.EXPECT().Active().Return(true).Times(1) 106 state, err := cfsm.prepare(evt) 107 require.NoError(err) 108 require.Equal(sPrepare, state) 109 time.Sleep(100 * time.Millisecond) 110 mockClock.Add(10 * time.Second) 111 evt := <-cfsm.evtq 112 require.Equal(ePrepare, evt.Type()) 113 // deactivate node 114 mockCtx.EXPECT().Active().Return(false).Times(1) 115 state, err = cfsm.prepare(evt) 116 require.NoError(err) 117 require.Equal(sPrepare, state) 118 time.Sleep(100 * time.Millisecond) 119 mockClock.Add(10 * time.Second) 120 require.Equal(0, len(cfsm.evtq)) 121 // reactivate node 122 mockCtx.EXPECT().Active().Return(true).Times(1) 123 _, err = cfsm.BackToPrepare(0) 124 require.NoError(err) 125 time.Sleep(100 * time.Millisecond) 126 mockClock.Add(10 * time.Second) 127 evt = <-cfsm.evtq 128 require.Equal(ePrepare, evt.Type()) 129 }) 130 t.Run("is-delegate", func(t *testing.T) { 131 mockCtx.EXPECT().IsDelegate().Return(true).AnyTimes() 132 t.Run("not-a-proposer", func(t *testing.T) { 133 t.Run("not-ready-to-commit", func(t *testing.T) { 134 mockCtx.EXPECT().Prepare().Return(nil).Times(1) 135 mockCtx.EXPECT().Proposal().Return(nil, nil).Times(1) 136 mockCtx.EXPECT().WaitUntilRoundStart().Return(time.Duration(0)).Times(1) 137 mockCtx.EXPECT().PreCommitEndorsement().Return(nil).Times(1) 138 state, err := cfsm.prepare(evt) 139 require.NoError(err) 140 require.Equal(sAcceptBlockProposal, state) 141 time.Sleep(100 * time.Millisecond) 142 // garbage collection 143 mockClock.Add(cfsm.ctx.AcceptBlockTTL(0)) 144 evt := <-cfsm.evtq 145 require.Equal(eFailedToReceiveBlock, evt.Type()) 146 mockClock.Add(cfsm.ctx.AcceptProposalEndorsementTTL(0)) 147 evt = <-cfsm.evtq 148 require.Equal(eStopReceivingProposalEndorsement, evt.Type()) 149 mockClock.Add(cfsm.ctx.AcceptLockEndorsementTTL(0)) 150 evt = <-cfsm.evtq 151 require.Equal(eStopReceivingLockEndorsement, evt.Type()) 152 mockClock.Add(cfsm.ctx.CommitTTL(0)) 153 evt = <-cfsm.evtq 154 require.Equal(eStopReceivingPreCommitEndorsement, evt.Type()) 155 }) 156 t.Run("ready-to-commit", func(t *testing.T) { 157 mockEndorsement := NewMockEndorsement(ctrl) 158 mockCtx.EXPECT().Prepare().Return(nil).Times(1) 159 mockCtx.EXPECT().Proposal().Return(nil, nil).Times(1) 160 mockCtx.EXPECT().WaitUntilRoundStart().Return(time.Duration(0)).Times(1) 161 mockCtx.EXPECT().PreCommitEndorsement().Return(mockEndorsement).Times(1) 162 state, err := cfsm.prepare(evt) 163 require.NoError(err) 164 require.Equal(sAcceptPreCommitEndorsement, state) 165 time.Sleep(100 * time.Millisecond) 166 // garbage collection 167 mockClock.Add(cfsm.ctx.AcceptBlockTTL(0)) 168 evt := <-cfsm.evtq 169 require.Equal(eBroadcastPreCommitEndorsement, evt.Type()) 170 mockClock.Add(cfsm.ctx.AcceptProposalEndorsementTTL(0)) 171 evt = <-cfsm.evtq 172 require.Equal(eBroadcastPreCommitEndorsement, evt.Type()) 173 mockClock.Add(cfsm.ctx.AcceptLockEndorsementTTL(0)) 174 evt = <-cfsm.evtq 175 require.Equal(eBroadcastPreCommitEndorsement, evt.Type()) 176 mockClock.Add(cfsm.ctx.CommitTTL(0)) 177 evt = <-cfsm.evtq 178 require.Equal(eStopReceivingPreCommitEndorsement, evt.Type()) 179 }) 180 }) 181 t.Run("is-proposer", func(t *testing.T) { 182 t.Run("fail-to-mint", func(t *testing.T) { 183 mockCtx.EXPECT().Prepare().Return(nil).Times(1) 184 mockCtx.EXPECT().Proposal().Return(nil, errors.New("some error")).Times(1) 185 mockCtx.EXPECT().Active().Return(true).Times(1) 186 state, err := cfsm.prepare(evt) 187 require.NoError(err) 188 require.Equal(sPrepare, state) 189 evt := <-cfsm.evtq 190 require.Equal(ePrepare, evt.Type()) 191 }) 192 t.Run("success-to-mint", func(t *testing.T) { 193 mockProposal := NewMockEndorsement(ctrl) 194 mockCtx.EXPECT().Prepare().Return(nil).Times(1) 195 mockCtx.EXPECT().Proposal().Return(mockProposal, nil).Times(1) 196 mockCtx.EXPECT().WaitUntilRoundStart().Return(time.Duration(0)).Times(1) 197 mockCtx.EXPECT().PreCommitEndorsement().Return(nil).Times(1) 198 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) 199 state, err := cfsm.prepare(evt) 200 require.NoError(err) 201 require.Equal(sAcceptBlockProposal, state) 202 evt := <-cfsm.evtq 203 require.Equal(eReceiveBlock, evt.Type()) 204 // garbage collection 205 time.Sleep(100 * time.Millisecond) 206 mockClock.Add(4 * time.Second) 207 evt = <-cfsm.evtq 208 require.Equal(eFailedToReceiveBlock, evt.Type()) 209 mockClock.Add(2 * time.Second) 210 evt = <-cfsm.evtq 211 require.Equal(eStopReceivingProposalEndorsement, evt.Type()) 212 mockClock.Add(2 * time.Second) 213 evt = <-cfsm.evtq 214 require.Equal(eStopReceivingLockEndorsement, evt.Type()) 215 }) 216 }) 217 }) 218 }) 219 t.Run("onReceiveBlock", func(t *testing.T) { 220 state, err := cfsm.handleBackdoorEvt( 221 &ConsensusEvent{eventType: BackdoorEvent, data: sAcceptBlockProposal}, 222 ) 223 require.NoError(err) 224 require.Equal(sAcceptBlockProposal, state) 225 t.Run("invalid-fsm-event", func(t *testing.T) { 226 state, err := cfsm.onReceiveBlock(nil) 227 require.NoError(err) 228 require.Equal(sAcceptBlockProposal, state) 229 }) 230 t.Run("fail-to-new-proposal-vote", func(t *testing.T) { 231 mockCtx.EXPECT().NewProposalEndorsement(gomock.Any()).Return(nil, errors.New("some error")).Times(1) 232 state, err := cfsm.onReceiveBlock(&ConsensusEvent{data: NewMockEndorsement(ctrl)}) 233 require.NoError(err) 234 require.Equal(sAcceptBlockProposal, state) 235 }) 236 t.Run("success", func(t *testing.T) { 237 mockCtx.EXPECT().NewProposalEndorsement(gomock.Any()).Return(NewMockEndorsement(ctrl), nil).Times(1) 238 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) 239 state, err := cfsm.onReceiveBlock(&ConsensusEvent{data: NewMockEndorsement(ctrl)}) 240 require.NoError(err) 241 require.Equal(sAcceptProposalEndorsement, state) 242 evt := <-cfsm.evtq 243 require.Equal(eReceiveProposalEndorsement, evt.Type()) 244 }) 245 }) 246 t.Run("onFailedToReceiveBlock", func(t *testing.T) { 247 mockCtx.EXPECT().NewProposalEndorsement(nil).Return(NewMockEndorsement(ctrl), nil).Times(1) 248 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) 249 state, err := cfsm.onFailedToReceiveBlock(nil) 250 require.NoError(err) 251 require.Equal(sAcceptProposalEndorsement, state) 252 evt := <-cfsm.evtq 253 require.Equal(eReceiveProposalEndorsement, evt.Type()) 254 }) 255 t.Run("onReceiveProposalEndorsementInAcceptProposalEndorsementState", func(t *testing.T) { 256 t.Run("invalid-fsm-event", func(t *testing.T) { 257 state, err := cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(nil) 258 require.Error(err) 259 require.Equal(sAcceptProposalEndorsement, state) 260 }) 261 t.Run("fail-to-add-proposal-vote", func(t *testing.T) { 262 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(nil, errors.New("some error")).Times(1) 263 mockEndorsement := NewMockEndorsement(ctrl) 264 state, err := cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(&ConsensusEvent{ 265 eventType: eReceiveProposalEndorsement, 266 data: mockEndorsement, 267 }) 268 require.NoError(err) 269 require.Equal(sAcceptProposalEndorsement, state) 270 }) 271 t.Run("is-not-locked", func(t *testing.T) { 272 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(nil, nil).Times(2) 273 state, err := cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(&ConsensusEvent{ 274 eventType: eReceiveProposalEndorsement, 275 data: NewMockEndorsement(ctrl), 276 }) 277 require.NoError(err) 278 require.Equal(sAcceptProposalEndorsement, state) 279 state, err = cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(&ConsensusEvent{ 280 eventType: eReceivePreCommitEndorsement, 281 data: NewMockEndorsement(ctrl), 282 }) 283 require.NoError(err) 284 require.Equal(sAcceptProposalEndorsement, state) 285 }) 286 t.Run("is-locked", func(t *testing.T) { 287 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(NewMockEndorsement(ctrl), nil).Times(2) 288 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(2) 289 state, err := cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(&ConsensusEvent{ 290 eventType: eReceiveProposalEndorsement, 291 data: NewMockEndorsement(ctrl), 292 }) 293 require.NoError(err) 294 require.Equal(sAcceptLockEndorsement, state) 295 evt := <-cfsm.evtq 296 require.Equal(eReceiveLockEndorsement, evt.Type()) 297 state, err = cfsm.onReceiveProposalEndorsementInAcceptProposalEndorsementState(&ConsensusEvent{ 298 eventType: eReceivePreCommitEndorsement, 299 data: NewMockEndorsement(ctrl), 300 }) 301 require.NoError(err) 302 require.Equal(sAcceptLockEndorsement, state) 303 evt = <-cfsm.evtq 304 require.Equal(eReceiveLockEndorsement, evt.Type()) 305 }) 306 }) 307 t.Run("onStopReceivingProposalEndorsement", func(t *testing.T) { 308 state, err := cfsm.onStopReceivingProposalEndorsement(nil) 309 require.NoError(err) 310 require.Equal(sAcceptLockEndorsement, state) 311 }) 312 t.Run("onReceiveProposalEndorsementInAcceptLockEndorsementState", func(t *testing.T) { 313 t.Run("invalid-fsm-event", func(t *testing.T) { 314 state, err := cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(nil) 315 require.Error(err) 316 require.Equal(sAcceptLockEndorsement, state) 317 }) 318 t.Run("fail-to-add-proposal-vote", func(t *testing.T) { 319 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(nil, errors.New("some error")).Times(1) 320 mockEndorsement := NewMockEndorsement(ctrl) 321 state, err := cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(&ConsensusEvent{ 322 eventType: eReceiveProposalEndorsement, 323 data: mockEndorsement, 324 }) 325 require.NoError(err) 326 require.Equal(sAcceptLockEndorsement, state) 327 }) 328 t.Run("is-not-locked", func(t *testing.T) { 329 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(nil, nil).Times(2) 330 state, err := cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(&ConsensusEvent{ 331 eventType: eReceiveProposalEndorsement, 332 data: NewMockEndorsement(ctrl), 333 }) 334 require.NoError(err) 335 require.Equal(sAcceptLockEndorsement, state) 336 state, err = cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(&ConsensusEvent{ 337 eventType: eReceivePreCommitEndorsement, 338 data: NewMockEndorsement(ctrl), 339 }) 340 require.NoError(err) 341 require.Equal(sAcceptLockEndorsement, state) 342 }) 343 t.Run("is-locked", func(t *testing.T) { 344 mockCtx.EXPECT().NewLockEndorsement(gomock.Any()).Return(NewMockEndorsement(ctrl), nil).Times(2) 345 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(2) 346 state, err := cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(&ConsensusEvent{ 347 eventType: eReceiveProposalEndorsement, 348 data: NewMockEndorsement(ctrl), 349 }) 350 require.NoError(err) 351 require.Equal(sAcceptLockEndorsement, state) 352 evt := <-cfsm.evtq 353 require.Equal(eReceiveLockEndorsement, evt.Type()) 354 state, err = cfsm.onReceiveProposalEndorsementInAcceptLockEndorsementState(&ConsensusEvent{ 355 eventType: eReceivePreCommitEndorsement, 356 data: NewMockEndorsement(ctrl), 357 }) 358 require.NoError(err) 359 require.Equal(sAcceptLockEndorsement, state) 360 evt = <-cfsm.evtq 361 require.Equal(eReceiveLockEndorsement, evt.Type()) 362 }) 363 }) 364 t.Run("onReceiveLockEndorsement", func(t *testing.T) { 365 t.Run("invalid-fsm-event", func(t *testing.T) { 366 state, err := cfsm.onReceiveLockEndorsement(nil) 367 require.Error(err) 368 require.Equal(sAcceptLockEndorsement, state) 369 }) 370 t.Run("fail-to-add-lock-vote", func(t *testing.T) { 371 mockCtx.EXPECT().NewPreCommitEndorsement(gomock.Any()).Return(nil, errors.New("some error")).Times(1) 372 mockEndorsement := NewMockEndorsement(ctrl) 373 state, err := cfsm.onReceiveLockEndorsement(&ConsensusEvent{ 374 eventType: eReceiveLockEndorsement, 375 data: mockEndorsement, 376 }) 377 require.Error(err) 378 require.Equal(sAcceptLockEndorsement, state) 379 }) 380 t.Run("not-ready-to-pre-commit", func(t *testing.T) { 381 mockCtx.EXPECT().NewPreCommitEndorsement(gomock.Any()).Return(nil, nil).Times(2) 382 state, err := cfsm.onReceiveLockEndorsement(&ConsensusEvent{ 383 eventType: eReceiveLockEndorsement, 384 data: NewMockEndorsement(ctrl), 385 }) 386 require.NoError(err) 387 require.Equal(sAcceptLockEndorsement, state) 388 state, err = cfsm.onReceiveLockEndorsement(&ConsensusEvent{ 389 eventType: eReceivePreCommitEndorsement, 390 data: NewMockEndorsement(ctrl), 391 }) 392 require.NoError(err) 393 require.Equal(sAcceptLockEndorsement, state) 394 }) 395 t.Run("ready-to-pre-commit", func(t *testing.T) { 396 mockCtx.EXPECT().NewPreCommitEndorsement(gomock.Any()).Return(NewMockEndorsement(ctrl), nil).Times(1) 397 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) 398 state, err := cfsm.onReceiveLockEndorsement(&ConsensusEvent{ 399 eventType: eReceiveLockEndorsement, 400 data: NewMockEndorsement(ctrl), 401 }) 402 require.NoError(err) 403 require.Equal(sAcceptPreCommitEndorsement, state) 404 evt := <-cfsm.evtq 405 require.Equal(eReceivePreCommitEndorsement, evt.Type()) 406 }) 407 }) 408 t.Run("onStopReceivingLockEndorsement", func(t *testing.T) { 409 mockCtx.EXPECT().Active().Return(true).Times(1) 410 state, err := cfsm.onStopReceivingLockEndorsement(nil) 411 require.NoError(err) 412 require.Equal(sPrepare, state) 413 evt := <-cfsm.evtq 414 require.Equal(ePrepare, evt.Type()) 415 }) 416 t.Run("onBroadcastPreCommitEndorsement", func(t *testing.T) { 417 t.Run("invalid-fsm-event", func(t *testing.T) { 418 state, err := cfsm.onBroadcastPreCommitEndorsement(nil) 419 require.Error(err) 420 require.Equal(sAcceptPreCommitEndorsement, state) 421 }) 422 t.Run("success", func(t *testing.T) { 423 mockCtx.EXPECT().Broadcast(gomock.Any()).Return().Times(1) 424 mockEndorsement := NewMockEndorsement(ctrl) 425 state, err := cfsm.onBroadcastPreCommitEndorsement(&ConsensusEvent{ 426 eventType: eBroadcastPreCommitEndorsement, 427 data: mockEndorsement, 428 }) 429 require.NoError(err) 430 require.Equal(sAcceptPreCommitEndorsement, state) 431 }) 432 }) 433 t.Run("onReceivePreCommitEndorsement", func(t *testing.T) { 434 t.Run("invalid-fsm-event", func(t *testing.T) { 435 mockCtx.EXPECT().Active().Return(true).Times(1) 436 state, err := cfsm.onReceivePreCommitEndorsement(nil) 437 require.Error(err) 438 require.Equal(sAcceptPreCommitEndorsement, state) 439 }) 440 t.Run("fail-to-add-commit-vote", func(t *testing.T) { 441 mockCtx.EXPECT().Commit(gomock.Any()).Return(false, errors.New("some error")).Times(1) 442 mockEndorsement := NewMockEndorsement(ctrl) 443 state, err := cfsm.onReceivePreCommitEndorsement(&ConsensusEvent{ 444 eventType: eReceiveLockEndorsement, 445 data: mockEndorsement, 446 }) 447 require.Error(err) 448 require.Equal(sAcceptPreCommitEndorsement, state) 449 }) 450 t.Run("not-enough-commit-vote", func(t *testing.T) { 451 mockCtx.EXPECT().Commit(gomock.Any()).Return(false, nil).Times(1) 452 mockEndorsement := NewMockEndorsement(ctrl) 453 state, err := cfsm.onReceivePreCommitEndorsement(&ConsensusEvent{ 454 eventType: eReceiveLockEndorsement, 455 data: mockEndorsement, 456 }) 457 require.NoError(err) 458 require.Equal(sAcceptPreCommitEndorsement, state) 459 }) 460 t.Run("success", func(t *testing.T) { 461 mockCtx.EXPECT().Commit(gomock.Any()).Return(true, nil).Times(1) 462 mockEndorsement := NewMockEndorsement(ctrl) 463 state, err := cfsm.onReceivePreCommitEndorsement(&ConsensusEvent{ 464 eventType: eReceiveLockEndorsement, 465 data: mockEndorsement, 466 }) 467 require.NoError(err) 468 require.Equal(sPrepare, state) 469 evt := <-cfsm.evtq 470 require.Equal(ePrepare, evt.Type()) 471 }) 472 }) 473 t.Run("calibrate", func(t *testing.T) { 474 mockCtx.EXPECT().Height().Return(uint64(2)).Times(2) 475 mockCtx.EXPECT().Active().Return(true).Times(2) 476 _, err := cfsm.calibrate(nil) 477 require.Error(err) 478 _, err = cfsm.calibrate(&ConsensusEvent{ 479 eventType: eCalibrate, 480 data: nil, 481 }) 482 require.Error(err) 483 _, err = cfsm.calibrate(&ConsensusEvent{ 484 eventType: eCalibrate, 485 data: uint64(1), 486 }) 487 require.Error(err) 488 state, err := cfsm.calibrate(&ConsensusEvent{ 489 eventType: eCalibrate, 490 data: uint64(2), 491 }) 492 require.NoError(err) 493 require.Equal(sPrepare, state) 494 evt := <-cfsm.evtq 495 require.Equal(ePrepare, evt.Type()) 496 }) 497 t.Run("handle", func(t *testing.T) { 498 t.Run("is-stale-event", func(t *testing.T) { 499 mockCtx.EXPECT().IsStaleEvent(gomock.Any()).Return(true).Times(1) 500 require.NoError(cfsm.handle(&ConsensusEvent{ 501 eventType: ePrepare, 502 height: 10, 503 round: 2, 504 })) 505 }) 506 t.Run("is-future-event", func(t *testing.T) { 507 mockCtx.EXPECT().IsStaleEvent(gomock.Any()).Return(false).Times(1) 508 mockCtx.EXPECT().IsFutureEvent(gomock.Any()).Return(true).Times(1) 509 cEvt := &ConsensusEvent{ 510 eventType: ePrepare, 511 height: 10, 512 round: 2, 513 } 514 require.NoError(cfsm.handle(cEvt)) 515 time.Sleep(10 * time.Millisecond) 516 mockClock.Add(cfsm.ctx.UnmatchedEventInterval(0)) 517 evt := <-cfsm.evtq 518 require.Equal(cEvt, evt) 519 }) 520 mockCtx.EXPECT().IsStaleEvent(gomock.Any()).Return(false).AnyTimes() 521 mockCtx.EXPECT().IsFutureEvent(gomock.Any()).Return(false).AnyTimes() 522 t.Run("transition-not-found", func(t *testing.T) { 523 cEvt := &ConsensusEvent{ 524 eventType: eFailedToReceiveBlock, 525 height: 10, 526 round: 2, 527 } 528 require.NoError(cfsm.handle( 529 &ConsensusEvent{eventType: BackdoorEvent, data: sPrepare}, 530 )) 531 t.Run("is-stale-unmatched-event", func(t *testing.T) { 532 mockCtx.EXPECT().IsStaleUnmatchedEvent(gomock.Any()).Return(true).Times(1) 533 require.NoError(cfsm.handle(cEvt)) 534 }) 535 t.Run("not-stale-unmatched-event", func(t *testing.T) { 536 mockCtx.EXPECT().IsStaleUnmatchedEvent(gomock.Any()).Return(false).Times(1) 537 require.NoError(cfsm.handle(cEvt)) 538 time.Sleep(10 * time.Millisecond) 539 mockClock.Add(cfsm.ctx.UnmatchedEventInterval(0)) 540 evtc := <-cfsm.evtq 541 require.Equal(evtc, cEvt) 542 }) 543 }) 544 t.Run("transition-success", func(t *testing.T) { 545 mockCtx.EXPECT().Height().Return(uint64(0)).Times(1) 546 require.NoError(cfsm.handle( 547 &ConsensusEvent{eventType: BackdoorEvent, data: sAcceptBlockProposal}, 548 )) 549 require.Equal(sAcceptBlockProposal, cfsm.CurrentState()) 550 require.NoError(cfsm.handle(&ConsensusEvent{ 551 eventType: eCalibrate, 552 data: uint64(1), 553 })) 554 require.Equal(sPrepare, cfsm.CurrentState()) 555 }) 556 }) 557 }