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