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