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 }