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