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