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