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  }