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