github.com/aakash4dev/cometbft@v0.38.2/evidence/pool_test.go (about)

     1  package evidence_test
     2  
     3  import (
     4  	"os"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	dbm "github.com/aakash4dev/cometbft-db"
    13  
    14  	"github.com/aakash4dev/cometbft/evidence"
    15  	"github.com/aakash4dev/cometbft/evidence/mocks"
    16  	"github.com/aakash4dev/cometbft/internal/test"
    17  	"github.com/aakash4dev/cometbft/libs/log"
    18  	cmtversion "github.com/aakash4dev/cometbft/proto/tendermint/version"
    19  	sm "github.com/aakash4dev/cometbft/state"
    20  	smmocks "github.com/aakash4dev/cometbft/state/mocks"
    21  	"github.com/aakash4dev/cometbft/store"
    22  	"github.com/aakash4dev/cometbft/types"
    23  	"github.com/aakash4dev/cometbft/version"
    24  )
    25  
    26  func TestMain(m *testing.M) {
    27  	code := m.Run()
    28  	os.Exit(code)
    29  }
    30  
    31  const evidenceChainID = "test_chain"
    32  
    33  var (
    34  	defaultEvidenceTime           = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
    35  	defaultEvidenceMaxBytes int64 = 1000
    36  )
    37  
    38  func TestEvidencePoolBasic(t *testing.T) {
    39  	var (
    40  		height     = int64(1)
    41  		stateStore = &smmocks.Store{}
    42  		evidenceDB = dbm.NewMemDB()
    43  		blockStore = &mocks.BlockStore{}
    44  	)
    45  
    46  	valSet, privVals := types.RandValidatorSet(1, 10)
    47  
    48  	blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(
    49  		&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}},
    50  	)
    51  	stateStore.On("LoadValidators", mock.AnythingOfType("int64")).Return(valSet, nil)
    52  	stateStore.On("Load").Return(createState(height+1, valSet), nil)
    53  
    54  	pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore)
    55  	require.NoError(t, err)
    56  	pool.SetLogger(log.TestingLogger())
    57  
    58  	// evidence not seen yet:
    59  	evs, size := pool.PendingEvidence(defaultEvidenceMaxBytes)
    60  	assert.Equal(t, 0, len(evs))
    61  	assert.Zero(t, size)
    62  
    63  	ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID)
    64  	require.NoError(t, err)
    65  
    66  	// good evidence
    67  	evAdded := make(chan struct{})
    68  	go func() {
    69  		<-pool.EvidenceWaitChan()
    70  		close(evAdded)
    71  	}()
    72  
    73  	// evidence seen but not yet committed:
    74  	assert.NoError(t, pool.AddEvidence(ev))
    75  
    76  	select {
    77  	case <-evAdded:
    78  	case <-time.After(5 * time.Second):
    79  		t.Fatal("evidence was not added to list after 5s")
    80  	}
    81  
    82  	next := pool.EvidenceFront()
    83  	assert.Equal(t, ev, next.Value.(types.Evidence))
    84  
    85  	const evidenceBytes int64 = 372
    86  	evs, size = pool.PendingEvidence(evidenceBytes)
    87  	assert.Equal(t, 1, len(evs))
    88  	assert.Equal(t, evidenceBytes, size) // check that the size of the single evidence in bytes is correct
    89  
    90  	// shouldn't be able to add evidence twice
    91  	assert.NoError(t, pool.AddEvidence(ev))
    92  	evs, _ = pool.PendingEvidence(defaultEvidenceMaxBytes)
    93  	assert.Equal(t, 1, len(evs))
    94  }
    95  
    96  // Tests inbound evidence for the right time and height
    97  func TestAddExpiredEvidence(t *testing.T) {
    98  	var (
    99  		val                 = types.NewMockPV()
   100  		height              = int64(30)
   101  		stateStore          = initializeValidatorState(val, height)
   102  		evidenceDB          = dbm.NewMemDB()
   103  		blockStore          = &mocks.BlockStore{}
   104  		expiredEvidenceTime = time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)
   105  		expiredHeight       = int64(2)
   106  	)
   107  
   108  	blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(func(h int64) *types.BlockMeta {
   109  		if h == height || h == expiredHeight {
   110  			return &types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}}
   111  		}
   112  		return &types.BlockMeta{Header: types.Header{Time: expiredEvidenceTime}}
   113  	})
   114  
   115  	pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore)
   116  	require.NoError(t, err)
   117  
   118  	testCases := []struct {
   119  		evHeight      int64
   120  		evTime        time.Time
   121  		expErr        bool
   122  		evDescription string
   123  	}{
   124  		{height, defaultEvidenceTime, false, "valid evidence"},
   125  		{expiredHeight, defaultEvidenceTime, false, "valid evidence (despite old height)"},
   126  		{height - 1, expiredEvidenceTime, false, "valid evidence (despite old time)"},
   127  		{
   128  			expiredHeight - 1, expiredEvidenceTime, true,
   129  			"evidence from height 1 (created at: 2019-01-01 00:00:00 +0000 UTC) is too old",
   130  		},
   131  		{height, defaultEvidenceTime.Add(1 * time.Minute), true, "evidence time and block time is different"},
   132  	}
   133  
   134  	for _, tc := range testCases {
   135  		tc := tc
   136  		t.Run(tc.evDescription, func(t *testing.T) {
   137  			ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID)
   138  			require.NoError(t, err)
   139  			err = pool.AddEvidence(ev)
   140  			if tc.expErr {
   141  				assert.Error(t, err)
   142  			} else {
   143  				assert.NoError(t, err)
   144  			}
   145  		})
   146  	}
   147  }
   148  
   149  func TestReportConflictingVotes(t *testing.T) {
   150  	var height int64 = 10
   151  
   152  	pool, pv := defaultTestPool(t, height)
   153  	val := types.NewValidator(pv.PrivKey.PubKey(), 10)
   154  	ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID)
   155  	require.NoError(t, err)
   156  
   157  	pool.ReportConflictingVotes(ev.VoteA, ev.VoteB)
   158  
   159  	// shouldn't be able to submit the same evidence twice
   160  	pool.ReportConflictingVotes(ev.VoteA, ev.VoteB)
   161  
   162  	// evidence from consensus should not be added immediately but reside in the consensus buffer
   163  	evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes)
   164  	require.Empty(t, evList)
   165  	require.Zero(t, evSize)
   166  
   167  	next := pool.EvidenceFront()
   168  	require.Nil(t, next)
   169  
   170  	// move to next height and update state and evidence pool
   171  	state := pool.State()
   172  	state.LastBlockHeight++
   173  	state.LastBlockTime = ev.Time()
   174  	state.LastValidators = types.NewValidatorSet([]*types.Validator{val})
   175  	pool.Update(state, []types.Evidence{})
   176  
   177  	// should be able to retrieve evidence from pool
   178  	evList, _ = pool.PendingEvidence(defaultEvidenceMaxBytes)
   179  	require.Equal(t, []types.Evidence{ev}, evList)
   180  
   181  	next = pool.EvidenceFront()
   182  	require.NotNil(t, next)
   183  }
   184  
   185  func TestEvidencePoolUpdate(t *testing.T) {
   186  	height := int64(21)
   187  	pool, val := defaultTestPool(t, height)
   188  	state := pool.State()
   189  
   190  	// create new block (no need to save it to blockStore)
   191  	prunedEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute),
   192  		val, evidenceChainID)
   193  	require.NoError(t, err)
   194  	err = pool.AddEvidence(prunedEv)
   195  	require.NoError(t, err)
   196  	ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute),
   197  		val, evidenceChainID)
   198  	require.NoError(t, err)
   199  	lastExtCommit := makeExtCommit(height, val.PrivKey.PubKey().Address())
   200  	block := types.MakeBlock(height+1, []types.Tx{}, lastExtCommit.ToCommit(), []types.Evidence{ev})
   201  	// update state (partially)
   202  	state.LastBlockHeight = height + 1
   203  	state.LastBlockTime = defaultEvidenceTime.Add(22 * time.Minute)
   204  	err = pool.CheckEvidence(types.EvidenceList{ev})
   205  	require.NoError(t, err)
   206  
   207  	pool.Update(state, block.Evidence.Evidence)
   208  	// a) Update marks evidence as committed so pending evidence should be empty
   209  	evList, evSize := pool.PendingEvidence(defaultEvidenceMaxBytes)
   210  	assert.Empty(t, evList)
   211  	assert.Zero(t, evSize)
   212  
   213  	// b) If we try to check this evidence again it should fail because it has already been committed
   214  	err = pool.CheckEvidence(types.EvidenceList{ev})
   215  	if assert.Error(t, err) {
   216  		assert.Equal(t, "evidence was already committed", err.(*types.ErrInvalidEvidence).Reason.Error())
   217  	}
   218  }
   219  
   220  func TestVerifyPendingEvidencePasses(t *testing.T) {
   221  	var height int64 = 1
   222  	pool, val := defaultTestPool(t, height)
   223  	ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
   224  		val, evidenceChainID)
   225  	require.NoError(t, err)
   226  	err = pool.AddEvidence(ev)
   227  	require.NoError(t, err)
   228  
   229  	err = pool.CheckEvidence(types.EvidenceList{ev})
   230  	assert.NoError(t, err)
   231  }
   232  
   233  func TestVerifyDuplicatedEvidenceFails(t *testing.T) {
   234  	var height int64 = 1
   235  	pool, val := defaultTestPool(t, height)
   236  	ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
   237  		val, evidenceChainID)
   238  	require.NoError(t, err)
   239  	err = pool.CheckEvidence(types.EvidenceList{ev, ev})
   240  	if assert.Error(t, err) {
   241  		assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error())
   242  	}
   243  }
   244  
   245  // check that valid light client evidence is correctly validated and stored in
   246  // evidence pool
   247  func TestLightClientAttackEvidenceLifecycle(t *testing.T) {
   248  	var (
   249  		height       int64 = 100
   250  		commonHeight int64 = 90
   251  	)
   252  
   253  	ev, trusted, common := makeLunaticEvidence(t, height, commonHeight,
   254  		10, 5, 5, defaultEvidenceTime, defaultEvidenceTime.Add(1*time.Hour))
   255  
   256  	state := sm.State{
   257  		LastBlockTime:   defaultEvidenceTime.Add(2 * time.Hour),
   258  		LastBlockHeight: 110,
   259  		ConsensusParams: *types.DefaultConsensusParams(),
   260  	}
   261  	stateStore := &smmocks.Store{}
   262  	stateStore.On("LoadValidators", height).Return(trusted.ValidatorSet, nil)
   263  	stateStore.On("LoadValidators", commonHeight).Return(common.ValidatorSet, nil)
   264  	stateStore.On("Load").Return(state, nil)
   265  	blockStore := &mocks.BlockStore{}
   266  	blockStore.On("LoadBlockMeta", height).Return(&types.BlockMeta{Header: *trusted.Header})
   267  	blockStore.On("LoadBlockMeta", commonHeight).Return(&types.BlockMeta{Header: *common.Header})
   268  	blockStore.On("LoadBlockCommit", height).Return(trusted.Commit)
   269  	blockStore.On("LoadBlockCommit", commonHeight).Return(common.Commit)
   270  
   271  	pool, err := evidence.NewPool(dbm.NewMemDB(), stateStore, blockStore)
   272  	require.NoError(t, err)
   273  	pool.SetLogger(log.TestingLogger())
   274  
   275  	err = pool.AddEvidence(ev)
   276  	assert.NoError(t, err)
   277  
   278  	hash := ev.Hash()
   279  
   280  	require.NoError(t, pool.AddEvidence(ev))
   281  	require.NoError(t, pool.AddEvidence(ev))
   282  
   283  	pendingEv, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
   284  	require.Equal(t, 1, len(pendingEv))
   285  	require.Equal(t, ev, pendingEv[0])
   286  
   287  	require.NoError(t, pool.CheckEvidence(pendingEv))
   288  	require.Equal(t, ev, pendingEv[0])
   289  
   290  	state.LastBlockHeight++
   291  	state.LastBlockTime = state.LastBlockTime.Add(1 * time.Minute)
   292  	pool.Update(state, pendingEv)
   293  	require.Equal(t, hash, pendingEv[0].Hash())
   294  
   295  	remaindingEv, _ := pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
   296  	require.Empty(t, remaindingEv)
   297  
   298  	// evidence is already committed so it shouldn't pass
   299  	require.Error(t, pool.CheckEvidence(types.EvidenceList{ev}))
   300  	require.NoError(t, pool.AddEvidence(ev))
   301  
   302  	remaindingEv, _ = pool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes)
   303  	require.Empty(t, remaindingEv)
   304  }
   305  
   306  // Tests that restarting the evidence pool after a potential failure will recover the
   307  // pending evidence and continue to gossip it
   308  func TestRecoverPendingEvidence(t *testing.T) {
   309  	height := int64(10)
   310  	val := types.NewMockPV()
   311  	valAddress := val.PrivKey.PubKey().Address()
   312  	evidenceDB := dbm.NewMemDB()
   313  	stateStore := initializeValidatorState(val, height)
   314  	state, err := stateStore.Load()
   315  	require.NoError(t, err)
   316  	blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress)
   317  	require.NoError(t, err)
   318  	// create previous pool and populate it
   319  	pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore)
   320  	require.NoError(t, err)
   321  	pool.SetLogger(log.TestingLogger())
   322  	goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(height,
   323  		defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID)
   324  	require.NoError(t, err)
   325  	expiredEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1),
   326  		defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID)
   327  	require.NoError(t, err)
   328  	err = pool.AddEvidence(goodEvidence)
   329  	require.NoError(t, err)
   330  	err = pool.AddEvidence(expiredEvidence)
   331  	require.NoError(t, err)
   332  
   333  	// now recover from the previous pool at a different time
   334  	newStateStore := &smmocks.Store{}
   335  	newStateStore.On("Load").Return(sm.State{
   336  		LastBlockTime:   defaultEvidenceTime.Add(25 * time.Minute),
   337  		LastBlockHeight: height + 15,
   338  		ConsensusParams: types.ConsensusParams{
   339  			Block: types.BlockParams{
   340  				MaxBytes: 22020096,
   341  				MaxGas:   -1,
   342  			},
   343  			Evidence: types.EvidenceParams{
   344  				MaxAgeNumBlocks: 20,
   345  				MaxAgeDuration:  20 * time.Minute,
   346  				MaxBytes:        defaultEvidenceMaxBytes,
   347  			},
   348  		},
   349  	}, nil)
   350  	newPool, err := evidence.NewPool(evidenceDB, newStateStore, blockStore)
   351  	assert.NoError(t, err)
   352  	evList, _ := newPool.PendingEvidence(defaultEvidenceMaxBytes)
   353  	assert.Equal(t, 1, len(evList))
   354  	next := newPool.EvidenceFront()
   355  	assert.Equal(t, goodEvidence, next.Value.(types.Evidence))
   356  }
   357  
   358  func initializeStateFromValidatorSet(valSet *types.ValidatorSet, height int64) sm.Store {
   359  	stateDB := dbm.NewMemDB()
   360  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   361  		DiscardABCIResponses: false,
   362  	})
   363  	state := sm.State{
   364  		ChainID:                     evidenceChainID,
   365  		InitialHeight:               1,
   366  		LastBlockHeight:             height,
   367  		LastBlockTime:               defaultEvidenceTime,
   368  		Validators:                  valSet,
   369  		NextValidators:              valSet.CopyIncrementProposerPriority(1),
   370  		LastValidators:              valSet,
   371  		LastHeightValidatorsChanged: 1,
   372  		ConsensusParams: types.ConsensusParams{
   373  			Block: types.BlockParams{
   374  				MaxBytes: 22020096,
   375  				MaxGas:   -1,
   376  			},
   377  			Evidence: types.EvidenceParams{
   378  				MaxAgeNumBlocks: 20,
   379  				MaxAgeDuration:  20 * time.Minute,
   380  				MaxBytes:        1000,
   381  			},
   382  		},
   383  	}
   384  
   385  	// save all states up to height
   386  	for i := int64(0); i <= height; i++ {
   387  		state.LastBlockHeight = i
   388  		if err := stateStore.Save(state); err != nil {
   389  			panic(err)
   390  		}
   391  	}
   392  
   393  	return stateStore
   394  }
   395  
   396  func initializeValidatorState(privVal types.PrivValidator, height int64) sm.Store {
   397  	pubKey, _ := privVal.GetPubKey()
   398  	validator := &types.Validator{Address: pubKey.Address(), VotingPower: 10, PubKey: pubKey}
   399  
   400  	// create validator set and state
   401  	valSet := &types.ValidatorSet{
   402  		Validators: []*types.Validator{validator},
   403  		Proposer:   validator,
   404  	}
   405  
   406  	return initializeStateFromValidatorSet(valSet, height)
   407  }
   408  
   409  // initializeBlockStore creates a block storage and populates it w/ a dummy
   410  // block at +height+.
   411  func initializeBlockStore(db dbm.DB, state sm.State, valAddr []byte) (*store.BlockStore, error) {
   412  	blockStore := store.NewBlockStore(db)
   413  
   414  	for i := int64(1); i <= state.LastBlockHeight; i++ {
   415  		lastCommit := makeExtCommit(i-1, valAddr)
   416  		block := state.MakeBlock(i, test.MakeNTxs(i, 1), lastCommit.ToCommit(), nil, state.Validators.Proposer.Address)
   417  		block.Header.Time = defaultEvidenceTime.Add(time.Duration(i) * time.Minute)
   418  		block.Header.Version = cmtversion.Consensus{Block: version.BlockProtocol, App: 1}
   419  		const parts = 1
   420  		partSet, err := block.MakePartSet(parts)
   421  		if err != nil {
   422  			return nil, err
   423  		}
   424  
   425  		seenCommit := makeExtCommit(i, valAddr)
   426  		blockStore.SaveBlockWithExtendedCommit(block, partSet, seenCommit)
   427  	}
   428  
   429  	return blockStore, nil
   430  }
   431  
   432  func makeExtCommit(height int64, valAddr []byte) *types.ExtendedCommit {
   433  	return &types.ExtendedCommit{
   434  		Height: height,
   435  		ExtendedSignatures: []types.ExtendedCommitSig{{
   436  			CommitSig: types.CommitSig{
   437  				BlockIDFlag:      types.BlockIDFlagCommit,
   438  				ValidatorAddress: valAddr,
   439  				Timestamp:        defaultEvidenceTime,
   440  				Signature:        []byte("Signature"),
   441  			},
   442  			ExtensionSignature: []byte("Extended Signature"),
   443  		}},
   444  	}
   445  }
   446  
   447  func defaultTestPool(t *testing.T, height int64) (*evidence.Pool, types.MockPV) {
   448  	t.Helper()
   449  	val := types.NewMockPV()
   450  	valAddress := val.PrivKey.PubKey().Address()
   451  	evidenceDB := dbm.NewMemDB()
   452  	stateStore := initializeValidatorState(val, height)
   453  	state, _ := stateStore.Load()
   454  	blockStore, err := initializeBlockStore(dbm.NewMemDB(), state, valAddress)
   455  	require.NoError(t, err)
   456  	pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore)
   457  	if err != nil {
   458  		panic("test evidence pool could not be created")
   459  	}
   460  	pool.SetLogger(log.TestingLogger())
   461  	return pool, val
   462  }
   463  
   464  func createState(height int64, valSet *types.ValidatorSet) sm.State {
   465  	return sm.State{
   466  		ChainID:         evidenceChainID,
   467  		LastBlockHeight: height,
   468  		LastBlockTime:   defaultEvidenceTime,
   469  		Validators:      valSet,
   470  		ConsensusParams: *types.DefaultConsensusParams(),
   471  	}
   472  }