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