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