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 }