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