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