github.com/Finschia/ostracon@v1.1.5/blockchain/v0/reactor_test.go (about) 1 package v0 2 3 import ( 4 "fmt" 5 "os" 6 "sort" 7 "testing" 8 "time" 9 10 "github.com/gogo/protobuf/proto" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 abci "github.com/tendermint/tendermint/abci/types" 15 bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" 16 dbm "github.com/tendermint/tm-db" 17 18 ocabci "github.com/Finschia/ostracon/abci/types" 19 cfg "github.com/Finschia/ostracon/config" 20 "github.com/Finschia/ostracon/libs/log" 21 "github.com/Finschia/ostracon/mempool/mock" 22 "github.com/Finschia/ostracon/p2p" 23 "github.com/Finschia/ostracon/proxy" 24 sm "github.com/Finschia/ostracon/state" 25 "github.com/Finschia/ostracon/store" 26 "github.com/Finschia/ostracon/types" 27 tmtime "github.com/Finschia/ostracon/types/time" 28 ) 29 30 var config *cfg.Config 31 32 func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) { 33 validators := make([]types.GenesisValidator, numValidators) 34 privValidators := make([]types.PrivValidator, numValidators) 35 for i := 0; i < numValidators; i++ { 36 val, privVal := types.RandValidator(randPower, minPower) 37 validators[i] = types.GenesisValidator{ 38 PubKey: val.PubKey, 39 Power: val.VotingPower, 40 } 41 privValidators[i] = privVal 42 } 43 sort.Sort(types.PrivValidatorsByAddress(privValidators)) 44 45 return &types.GenesisDoc{ 46 GenesisTime: tmtime.Now(), 47 ChainID: config.ChainID(), 48 Validators: validators, 49 }, privValidators 50 } 51 52 type BlockchainReactorPair struct { 53 reactor *BlockchainReactor 54 app proxy.AppConns 55 } 56 57 func newBlockchainReactor( 58 logger log.Logger, 59 genDoc *types.GenesisDoc, 60 privVals []types.PrivValidator, 61 maxBlockHeight int64, 62 async bool, 63 recvBufSize int) BlockchainReactorPair { 64 if len(privVals) != 1 { 65 panic("only support one validator") 66 } 67 68 app := &testApp{} 69 cc := proxy.NewLocalClientCreator(app) 70 proxyApp := proxy.NewAppConns(cc) 71 err := proxyApp.Start() 72 if err != nil { 73 panic(fmt.Errorf("error start app: %w", err)) 74 } 75 76 blockDB := dbm.NewMemDB() 77 stateDB := dbm.NewMemDB() 78 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 79 DiscardABCIResponses: false, 80 }) 81 blockStore := store.NewBlockStore(blockDB) 82 83 state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) 84 if err != nil { 85 panic(fmt.Errorf("error constructing state from genesis file: %w", err)) 86 } 87 88 // Make the BlockchainReactor itself. 89 // NOTE we have to create and commit the blocks first because 90 // pool.height is determined from the store. 91 fastSync := true 92 db := dbm.NewMemDB() 93 stateStore = sm.NewStore(db, sm.StoreOptions{ 94 DiscardABCIResponses: false, 95 }) 96 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), 97 mock.Mempool{}, sm.EmptyEvidencePool{}) 98 if err = stateStore.Save(state); err != nil { 99 panic(err) 100 } 101 102 // let's add some blocks in 103 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 104 lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil) 105 if blockHeight > 1 { 106 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 107 lastBlock := blockStore.LoadBlock(blockHeight - 1) 108 109 vote, err := types.MakeVote( 110 lastBlock.Header.Height, 111 lastBlockMeta.BlockID, 112 state.Validators, 113 privVals[0], 114 lastBlock.Header.ChainID, 115 time.Now(), 116 ) 117 if err != nil { 118 panic(err) 119 } 120 lastCommit = types.NewCommit(vote.Height, vote.Round, 121 lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 122 } 123 124 thisBlock := makeBlock(privVals[0], blockHeight, state, lastCommit) 125 126 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 127 blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} 128 129 state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock, nil) 130 if err != nil { 131 panic(fmt.Errorf("error apply block: %w", err)) 132 } 133 134 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 135 } 136 137 bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync, async, recvBufSize) 138 bcReactor.SetLogger(logger.With("module", "blockchain")) 139 140 return BlockchainReactorPair{bcReactor, proxyApp} 141 } 142 143 func TestNoBlockResponse(t *testing.T) { 144 config = cfg.ResetTestRoot("blockchain_reactor_test") 145 defer os.RemoveAll(config.RootDir) 146 genDoc, privVals := randGenesisDoc(1, false, 30) 147 148 maxBlockHeight := int64(65) 149 150 reactorPairs := make([]BlockchainReactorPair, 2) 151 152 reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight, 153 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 154 reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0, 155 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 156 157 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch, config *cfg.P2PConfig) *p2p.Switch { 158 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 159 return s 160 161 }, p2p.Connect2Switches) 162 163 defer func() { 164 for _, r := range reactorPairs { 165 err := r.reactor.Stop() 166 require.NoError(t, err) 167 err = r.app.Stop() 168 require.NoError(t, err) 169 } 170 }() 171 172 tests := []struct { 173 height int64 174 existent bool 175 }{ 176 {maxBlockHeight + 2, false}, 177 {10, true}, 178 {1, true}, 179 {100, false}, 180 } 181 182 for { 183 if reactorPairs[1].reactor.pool.IsCaughtUp() { 184 break 185 } 186 187 time.Sleep(10 * time.Millisecond) 188 } 189 190 assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height()) 191 192 for _, tt := range tests { 193 block := reactorPairs[1].reactor.store.LoadBlock(tt.height) 194 if tt.existent { 195 assert.True(t, block != nil) 196 } else { 197 assert.True(t, block == nil) 198 } 199 } 200 } 201 202 func TestLegacyReactorReceiveBasic(t *testing.T) { 203 config = cfg.ResetTestRoot("blockchain_reactor_test") 204 defer os.RemoveAll(config.RootDir) 205 genDoc, privVals := randGenesisDoc(1, false, 30) 206 reactor := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 10, 207 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize).reactor 208 peer := p2p.CreateRandomPeer(false) 209 210 reactor.InitPeer(peer) 211 reactor.AddPeer(peer) 212 m := &bcproto.StatusRequest{} 213 wm := m.Wrap() 214 msg, err := proto.Marshal(wm) 215 assert.NoError(t, err) 216 217 assert.NotPanics(t, func() { 218 reactor.Receive(BlockchainChannel, peer, msg) 219 }) 220 } 221 222 // NOTE: This is too hard to test without 223 // an easy way to add test peer to switch 224 // or without significant refactoring of the module. 225 // Alternatively we could actually dial a TCP conn but 226 // that seems extreme. 227 func TestBadBlockStopsPeer(t *testing.T) { 228 config = cfg.ResetTestRoot("blockchain_reactor_test") 229 defer os.RemoveAll(config.RootDir) 230 genDoc, privVals := randGenesisDoc(1, false, 30) 231 232 maxBlockHeight := int64(148) 233 234 // Other chain needs a different validator set 235 otherGenDoc, otherPrivVals := randGenesisDoc(1, false, 30) 236 otherChain := newBlockchainReactor(log.TestingLogger(), otherGenDoc, otherPrivVals, maxBlockHeight, 237 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 238 239 defer func() { 240 err := otherChain.reactor.Stop() 241 require.Error(t, err) 242 err = otherChain.app.Stop() 243 require.NoError(t, err) 244 }() 245 246 reactorPairs := make([]BlockchainReactorPair, 4) 247 248 reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight, 249 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 250 reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0, 251 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 252 reactorPairs[2] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0, 253 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 254 reactorPairs[3] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0, 255 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 256 257 switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch, 258 config *cfg.P2PConfig) *p2p.Switch { 259 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 260 return s 261 262 }, p2p.Connect2Switches) 263 264 defer func() { 265 for _, r := range reactorPairs { 266 err := r.reactor.Stop() 267 require.NoError(t, err) 268 269 err = r.app.Stop() 270 require.NoError(t, err) 271 } 272 }() 273 274 for { 275 time.Sleep(1 * time.Second) 276 caughtUp := true 277 for _, r := range reactorPairs { 278 if !r.reactor.pool.IsCaughtUp() { 279 caughtUp = false 280 } 281 } 282 if caughtUp { 283 break 284 } 285 } 286 287 // at this time, reactors[0-3] is the newest 288 assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) 289 290 // Mark reactorPairs[3] as an invalid peer. Fiddling with .store without a mutex is a data 291 // race, but can't be easily avoided. 292 reactorPairs[3].reactor.store = otherChain.reactor.store 293 294 lastReactorPair := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0, 295 config.P2P.RecvAsync, config.P2P.BlockchainRecvBufSize) 296 reactorPairs = append(reactorPairs, lastReactorPair) 297 298 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch, 299 config *cfg.P2PConfig) *p2p.Switch { 300 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) 301 return s 302 303 }, p2p.Connect2Switches)...) 304 305 for i := 0; i < len(reactorPairs)-1; i++ { 306 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 307 } 308 309 for { 310 if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { 311 break 312 } 313 314 time.Sleep(1 * time.Second) 315 } 316 317 assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) 318 } 319 320 //---------------------------------------------- 321 // utility funcs 322 323 func makeTxs(height int64) (txs []types.Tx) { 324 for i := 0; i < 10; i++ { 325 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 326 } 327 return txs 328 } 329 330 func makeBlock(privVal types.PrivValidator, height int64, state sm.State, lastCommit *types.Commit) *types.Block { 331 message := state.MakeHashMessage(0) 332 proof, err := privVal.GenerateVRFProof(message) 333 if err != nil { 334 panic(err) 335 } 336 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, 337 state.Validators.SelectProposer(state.LastProofHash, height, 0).Address, 0, proof) 338 return block 339 } 340 341 type testApp struct { 342 ocabci.BaseApplication 343 } 344 345 var _ ocabci.Application = (*testApp)(nil) 346 347 func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { 348 return abci.ResponseInfo{} 349 } 350 351 func (app *testApp) BeginBlock(req ocabci.RequestBeginBlock) abci.ResponseBeginBlock { 352 return abci.ResponseBeginBlock{} 353 } 354 355 func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { 356 return abci.ResponseEndBlock{} 357 } 358 359 func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { 360 return abci.ResponseDeliverTx{Events: []abci.Event{}} 361 } 362 363 func (app *testApp) CheckTxSync(req abci.RequestCheckTx) ocabci.ResponseCheckTx { 364 return ocabci.ResponseCheckTx{} 365 } 366 367 func (app *testApp) CheckTxAsync(req abci.RequestCheckTx, callback ocabci.CheckTxCallback) { 368 callback(ocabci.ResponseCheckTx{}) 369 } 370 371 func (app *testApp) Commit() abci.ResponseCommit { 372 return abci.ResponseCommit{} 373 } 374 375 func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { 376 return 377 }