gitlab.com/gpdionisio/tendermint@v0.34.19-dev2/blockchain/v2/reactor_test.go (about) 1 package v2 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "sort" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 dbm "github.com/tendermint/tm-db" 15 16 abci "github.com/tendermint/tendermint/abci/types" 17 "github.com/tendermint/tendermint/behaviour" 18 bc "github.com/tendermint/tendermint/blockchain" 19 cfg "github.com/tendermint/tendermint/config" 20 "github.com/tendermint/tendermint/libs/log" 21 "github.com/tendermint/tendermint/libs/service" 22 "github.com/tendermint/tendermint/mempool/mock" 23 "github.com/tendermint/tendermint/p2p" 24 "github.com/tendermint/tendermint/p2p/conn" 25 bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" 26 "github.com/tendermint/tendermint/proxy" 27 sm "github.com/tendermint/tendermint/state" 28 "github.com/tendermint/tendermint/store" 29 "github.com/tendermint/tendermint/types" 30 tmtime "github.com/tendermint/tendermint/types/time" 31 ) 32 33 type mockPeer struct { 34 service.Service 35 id p2p.ID 36 } 37 38 func (mp mockPeer) FlushStop() {} 39 func (mp mockPeer) ID() p2p.ID { return mp.id } 40 func (mp mockPeer) RemoteIP() net.IP { return net.IP{} } 41 func (mp mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.RemoteIP(), Port: 8800} } 42 43 func (mp mockPeer) IsOutbound() bool { return true } 44 func (mp mockPeer) IsPersistent() bool { return true } 45 func (mp mockPeer) CloseConn() error { return nil } 46 47 func (mp mockPeer) NodeInfo() p2p.NodeInfo { 48 return p2p.DefaultNodeInfo{ 49 DefaultNodeID: "", 50 ListenAddr: "", 51 } 52 } 53 func (mp mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } 54 func (mp mockPeer) SocketAddr() *p2p.NetAddress { return &p2p.NetAddress{} } 55 56 func (mp mockPeer) Send(byte, []byte) bool { return true } 57 func (mp mockPeer) TrySend(byte, []byte) bool { return true } 58 59 func (mp mockPeer) Set(string, interface{}) {} 60 func (mp mockPeer) Get(string) interface{} { return struct{}{} } 61 62 // nolint:unused // ignore 63 type mockBlockStore struct { 64 blocks map[int64]*types.Block 65 } 66 67 // nolint:unused // ignore 68 func (ml *mockBlockStore) Height() int64 { 69 return int64(len(ml.blocks)) 70 } 71 72 // nolint:unused // ignore 73 func (ml *mockBlockStore) LoadBlock(height int64) *types.Block { 74 return ml.blocks[height] 75 } 76 77 // nolint:unused // ignore 78 func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) { 79 ml.blocks[block.Height] = block 80 } 81 82 type mockBlockApplier struct { 83 } 84 85 // XXX: Add whitelist/blacklist? 86 func (mba *mockBlockApplier) ApplyBlock( 87 state sm.State, blockID types.BlockID, block *types.Block, 88 ) (sm.State, int64, error) { 89 state.LastBlockHeight++ 90 return state, 0, nil 91 } 92 93 type mockSwitchIo struct { 94 mtx sync.Mutex 95 switchedToConsensus bool 96 numStatusResponse int 97 numBlockResponse int 98 numNoBlockResponse int 99 } 100 101 func (sio *mockSwitchIo) sendBlockRequest(peerID p2p.ID, height int64) error { 102 return nil 103 } 104 105 func (sio *mockSwitchIo) sendStatusResponse(base, height int64, peerID p2p.ID) error { 106 sio.mtx.Lock() 107 defer sio.mtx.Unlock() 108 sio.numStatusResponse++ 109 return nil 110 } 111 112 func (sio *mockSwitchIo) sendBlockToPeer(block *types.Block, peerID p2p.ID) error { 113 sio.mtx.Lock() 114 defer sio.mtx.Unlock() 115 sio.numBlockResponse++ 116 return nil 117 } 118 119 func (sio *mockSwitchIo) sendBlockNotFound(height int64, peerID p2p.ID) error { 120 sio.mtx.Lock() 121 defer sio.mtx.Unlock() 122 sio.numNoBlockResponse++ 123 return nil 124 } 125 126 func (sio *mockSwitchIo) trySwitchToConsensus(state sm.State, skipWAL bool) bool { 127 sio.mtx.Lock() 128 defer sio.mtx.Unlock() 129 sio.switchedToConsensus = true 130 return true 131 } 132 133 func (sio *mockSwitchIo) broadcastStatusRequest() error { 134 return nil 135 } 136 137 type testReactorParams struct { 138 logger log.Logger 139 genDoc *types.GenesisDoc 140 privVals []types.PrivValidator 141 startHeight int64 142 mockA bool 143 } 144 145 func newTestReactor(p testReactorParams) *BlockchainReactor { 146 store, state, _ := newReactorStore(p.genDoc, p.privVals, p.startHeight) 147 reporter := behaviour.NewMockReporter() 148 149 var appl blockApplier 150 151 if p.mockA { 152 appl = &mockBlockApplier{} 153 } else { 154 app := &testApp{} 155 cc := proxy.NewLocalClientCreator(app) 156 proxyApp := proxy.NewAppConns(cc) 157 err := proxyApp.Start() 158 if err != nil { 159 panic(fmt.Errorf("error start app: %w", err)) 160 } 161 db := dbm.NewMemDB() 162 stateStore := sm.NewStore(db) 163 appl = sm.NewBlockExecutor(stateStore, p.logger, proxyApp.Consensus(), mock.Mempool{}, sm.EmptyEvidencePool{}) 164 if err = stateStore.Save(state); err != nil { 165 panic(err) 166 } 167 } 168 169 r := newReactor(state, store, reporter, appl, true) 170 logger := log.TestingLogger() 171 r.SetLogger(logger.With("module", "blockchain")) 172 173 return r 174 } 175 176 // This test is left here and not deleted to retain the termination cases for 177 // future improvement in [#4482](https://github.com/tendermint/tendermint/issues/4482). 178 // func TestReactorTerminationScenarios(t *testing.T) { 179 180 // config := cfg.ResetTestRoot("blockchain_reactor_v2_test") 181 // defer os.RemoveAll(config.RootDir) 182 // genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30) 183 // refStore, _, _ := newReactorStore(genDoc, privVals, 20) 184 185 // params := testReactorParams{ 186 // logger: log.TestingLogger(), 187 // genDoc: genDoc, 188 // privVals: privVals, 189 // startHeight: 10, 190 // bufferSize: 100, 191 // mockA: true, 192 // } 193 194 // type testEvent struct { 195 // evType string 196 // peer string 197 // height int64 198 // } 199 200 // tests := []struct { 201 // name string 202 // params testReactorParams 203 // msgs []testEvent 204 // }{ 205 // { 206 // name: "simple termination on max peer height - one peer", 207 // params: params, 208 // msgs: []testEvent{ 209 // {evType: "AddPeer", peer: "P1"}, 210 // {evType: "ReceiveS", peer: "P1", height: 13}, 211 // {evType: "BlockReq"}, 212 // {evType: "ReceiveB", peer: "P1", height: 11}, 213 // {evType: "BlockReq"}, 214 // {evType: "BlockReq"}, 215 // {evType: "ReceiveB", peer: "P1", height: 12}, 216 // {evType: "Process"}, 217 // {evType: "ReceiveB", peer: "P1", height: 13}, 218 // {evType: "Process"}, 219 // }, 220 // }, 221 // { 222 // name: "simple termination on max peer height - two peers", 223 // params: params, 224 // msgs: []testEvent{ 225 // {evType: "AddPeer", peer: "P1"}, 226 // {evType: "AddPeer", peer: "P2"}, 227 // {evType: "ReceiveS", peer: "P1", height: 13}, 228 // {evType: "ReceiveS", peer: "P2", height: 15}, 229 // {evType: "BlockReq"}, 230 // {evType: "BlockReq"}, 231 // {evType: "ReceiveB", peer: "P1", height: 11}, 232 // {evType: "ReceiveB", peer: "P2", height: 12}, 233 // {evType: "Process"}, 234 // {evType: "BlockReq"}, 235 // {evType: "BlockReq"}, 236 // {evType: "ReceiveB", peer: "P1", height: 13}, 237 // {evType: "Process"}, 238 // {evType: "ReceiveB", peer: "P2", height: 14}, 239 // {evType: "Process"}, 240 // {evType: "BlockReq"}, 241 // {evType: "ReceiveB", peer: "P2", height: 15}, 242 // {evType: "Process"}, 243 // }, 244 // }, 245 // { 246 // name: "termination on max peer height - two peers, noBlock error", 247 // params: params, 248 // msgs: []testEvent{ 249 // {evType: "AddPeer", peer: "P1"}, 250 // {evType: "AddPeer", peer: "P2"}, 251 // {evType: "ReceiveS", peer: "P1", height: 13}, 252 // {evType: "ReceiveS", peer: "P2", height: 15}, 253 // {evType: "BlockReq"}, 254 // {evType: "BlockReq"}, 255 // {evType: "ReceiveNB", peer: "P1", height: 11}, 256 // {evType: "BlockReq"}, 257 // {evType: "ReceiveB", peer: "P2", height: 12}, 258 // {evType: "ReceiveB", peer: "P2", height: 11}, 259 // {evType: "Process"}, 260 // {evType: "BlockReq"}, 261 // {evType: "BlockReq"}, 262 // {evType: "ReceiveB", peer: "P2", height: 13}, 263 // {evType: "Process"}, 264 // {evType: "ReceiveB", peer: "P2", height: 14}, 265 // {evType: "Process"}, 266 // {evType: "BlockReq"}, 267 // {evType: "ReceiveB", peer: "P2", height: 15}, 268 // {evType: "Process"}, 269 // }, 270 // }, 271 // { 272 // name: "termination on max peer height - two peers, remove one peer", 273 // params: params, 274 // msgs: []testEvent{ 275 // {evType: "AddPeer", peer: "P1"}, 276 // {evType: "AddPeer", peer: "P2"}, 277 // {evType: "ReceiveS", peer: "P1", height: 13}, 278 // {evType: "ReceiveS", peer: "P2", height: 15}, 279 // {evType: "BlockReq"}, 280 // {evType: "BlockReq"}, 281 // {evType: "RemovePeer", peer: "P1"}, 282 // {evType: "BlockReq"}, 283 // {evType: "ReceiveB", peer: "P2", height: 12}, 284 // {evType: "ReceiveB", peer: "P2", height: 11}, 285 // {evType: "Process"}, 286 // {evType: "BlockReq"}, 287 // {evType: "BlockReq"}, 288 // {evType: "ReceiveB", peer: "P2", height: 13}, 289 // {evType: "Process"}, 290 // {evType: "ReceiveB", peer: "P2", height: 14}, 291 // {evType: "Process"}, 292 // {evType: "BlockReq"}, 293 // {evType: "ReceiveB", peer: "P2", height: 15}, 294 // {evType: "Process"}, 295 // }, 296 // }, 297 // } 298 299 // for _, tt := range tests { 300 // tt := tt 301 // t.Run(tt.name, func(t *testing.T) { 302 // reactor := newTestReactor(params) 303 // reactor.Start() 304 // reactor.reporter = behaviour.NewMockReporter() 305 // mockSwitch := &mockSwitchIo{switchedToConsensus: false} 306 // reactor.io = mockSwitch 307 // // time for go routines to start 308 // time.Sleep(time.Millisecond) 309 310 // for _, step := range tt.msgs { 311 // switch step.evType { 312 // case "AddPeer": 313 // reactor.scheduler.send(bcAddNewPeer{peerID: p2p.ID(step.peer)}) 314 // case "RemovePeer": 315 // reactor.scheduler.send(bcRemovePeer{peerID: p2p.ID(step.peer)}) 316 // case "ReceiveS": 317 // reactor.scheduler.send(bcStatusResponse{ 318 // peerID: p2p.ID(step.peer), 319 // height: step.height, 320 // time: time.Now(), 321 // }) 322 // case "ReceiveB": 323 // reactor.scheduler.send(bcBlockResponse{ 324 // peerID: p2p.ID(step.peer), 325 // block: refStore.LoadBlock(step.height), 326 // size: 10, 327 // time: time.Now(), 328 // }) 329 // case "ReceiveNB": 330 // reactor.scheduler.send(bcNoBlockResponse{ 331 // peerID: p2p.ID(step.peer), 332 // height: step.height, 333 // time: time.Now(), 334 // }) 335 // case "BlockReq": 336 // reactor.scheduler.send(rTrySchedule{time: time.Now()}) 337 // case "Process": 338 // reactor.processor.send(rProcessBlock{}) 339 // } 340 // // give time for messages to propagate between routines 341 // time.Sleep(time.Millisecond) 342 // } 343 344 // // time for processor to finish and reactor to switch to consensus 345 // time.Sleep(20 * time.Millisecond) 346 // assert.True(t, mockSwitch.hasSwitchedToConsensus()) 347 // reactor.Stop() 348 // }) 349 // } 350 // } 351 352 func TestReactorHelperMode(t *testing.T) { 353 var ( 354 channelID = byte(0x40) 355 ) 356 357 config := cfg.ResetTestRoot("blockchain_reactor_v2_test") 358 defer os.RemoveAll(config.RootDir) 359 genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30) 360 361 params := testReactorParams{ 362 logger: log.TestingLogger(), 363 genDoc: genDoc, 364 privVals: privVals, 365 startHeight: 20, 366 mockA: true, 367 } 368 369 type testEvent struct { 370 peer string 371 event interface{} 372 } 373 374 tests := []struct { 375 name string 376 params testReactorParams 377 msgs []testEvent 378 }{ 379 { 380 name: "status request", 381 params: params, 382 msgs: []testEvent{ 383 {"P1", bcproto.StatusRequest{}}, 384 {"P1", bcproto.BlockRequest{Height: 13}}, 385 {"P1", bcproto.BlockRequest{Height: 20}}, 386 {"P1", bcproto.BlockRequest{Height: 22}}, 387 }, 388 }, 389 } 390 391 for _, tt := range tests { 392 tt := tt 393 t.Run(tt.name, func(t *testing.T) { 394 reactor := newTestReactor(params) 395 mockSwitch := &mockSwitchIo{switchedToConsensus: false} 396 reactor.io = mockSwitch 397 err := reactor.Start() 398 require.NoError(t, err) 399 400 for i := 0; i < len(tt.msgs); i++ { 401 step := tt.msgs[i] 402 switch ev := step.event.(type) { 403 case bcproto.StatusRequest: 404 old := mockSwitch.numStatusResponse 405 msg, err := bc.EncodeMsg(&ev) 406 assert.NoError(t, err) 407 reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, msg) 408 assert.Equal(t, old+1, mockSwitch.numStatusResponse) 409 case bcproto.BlockRequest: 410 if ev.Height > params.startHeight { 411 old := mockSwitch.numNoBlockResponse 412 msg, err := bc.EncodeMsg(&ev) 413 assert.NoError(t, err) 414 reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, msg) 415 assert.Equal(t, old+1, mockSwitch.numNoBlockResponse) 416 } else { 417 old := mockSwitch.numBlockResponse 418 msg, err := bc.EncodeMsg(&ev) 419 assert.NoError(t, err) 420 assert.NoError(t, err) 421 reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, msg) 422 assert.Equal(t, old+1, mockSwitch.numBlockResponse) 423 } 424 } 425 } 426 err = reactor.Stop() 427 require.NoError(t, err) 428 }) 429 } 430 } 431 432 func TestReactorSetSwitchNil(t *testing.T) { 433 config := cfg.ResetTestRoot("blockchain_reactor_v2_test") 434 defer os.RemoveAll(config.RootDir) 435 genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30) 436 437 reactor := newTestReactor(testReactorParams{ 438 logger: log.TestingLogger(), 439 genDoc: genDoc, 440 privVals: privVals, 441 }) 442 reactor.SetSwitch(nil) 443 444 assert.Nil(t, reactor.Switch) 445 assert.Nil(t, reactor.io) 446 } 447 448 //---------------------------------------------- 449 // utility funcs 450 451 func makeTxs(height int64) (txs []types.Tx) { 452 for i := 0; i < 10; i++ { 453 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 454 } 455 return txs 456 } 457 458 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 459 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) 460 return block 461 } 462 463 type testApp struct { 464 abci.BaseApplication 465 } 466 467 func randGenesisDoc(chainID string, numValidators int, randPower bool, minPower int64) ( 468 *types.GenesisDoc, []types.PrivValidator) { 469 validators := make([]types.GenesisValidator, numValidators) 470 privValidators := make([]types.PrivValidator, numValidators) 471 for i := 0; i < numValidators; i++ { 472 val, privVal := types.RandValidator(randPower, minPower) 473 validators[i] = types.GenesisValidator{ 474 PubKey: val.PubKey, 475 Power: val.VotingPower, 476 } 477 privValidators[i] = privVal 478 } 479 sort.Sort(types.PrivValidatorsByAddress(privValidators)) 480 481 return &types.GenesisDoc{ 482 GenesisTime: tmtime.Now(), 483 ChainID: chainID, 484 Validators: validators, 485 }, privValidators 486 } 487 488 // Why are we importing the entire blockExecutor dependency graph here 489 // when we have the facilities to 490 func newReactorStore( 491 genDoc *types.GenesisDoc, 492 privVals []types.PrivValidator, 493 maxBlockHeight int64) (*store.BlockStore, sm.State, *sm.BlockExecutor) { 494 if len(privVals) != 1 { 495 panic("only support one validator") 496 } 497 app := &testApp{} 498 cc := proxy.NewLocalClientCreator(app) 499 proxyApp := proxy.NewAppConns(cc) 500 err := proxyApp.Start() 501 if err != nil { 502 panic(fmt.Errorf("error start app: %w", err)) 503 } 504 505 stateDB := dbm.NewMemDB() 506 blockStore := store.NewBlockStore(dbm.NewMemDB()) 507 stateStore := sm.NewStore(stateDB) 508 state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) 509 if err != nil { 510 panic(fmt.Errorf("error constructing state from genesis file: %w", err)) 511 } 512 513 db := dbm.NewMemDB() 514 stateStore = sm.NewStore(db) 515 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), 516 mock.Mempool{}, sm.EmptyEvidencePool{}) 517 if err = stateStore.Save(state); err != nil { 518 panic(err) 519 } 520 521 // add blocks in 522 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 523 lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil) 524 if blockHeight > 1 { 525 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 526 lastBlock := blockStore.LoadBlock(blockHeight - 1) 527 vote, err := types.MakeVote( 528 lastBlock.Header.Height, 529 lastBlockMeta.BlockID, 530 state.Validators, 531 privVals[0], 532 lastBlock.Header.ChainID, 533 time.Now(), 534 ) 535 if err != nil { 536 panic(err) 537 } 538 lastCommit = types.NewCommit(vote.Height, vote.Round, 539 lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 540 } 541 542 thisBlock := makeBlock(blockHeight, state, lastCommit) 543 544 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 545 blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} 546 547 state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock) 548 if err != nil { 549 panic(fmt.Errorf("error apply block: %w", err)) 550 } 551 552 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 553 } 554 return blockStore, state, blockExec 555 }