github.com/MagHErmit/tendermint@v0.282.1/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 dbm "github.com/tendermint/tm-db" 15 16 abci "github.com/MagHErmit/tendermint/abci/types" 17 cfg "github.com/MagHErmit/tendermint/config" 18 "github.com/MagHErmit/tendermint/libs/log" 19 "github.com/MagHErmit/tendermint/mempool/mock" 20 "github.com/MagHErmit/tendermint/p2p" 21 tmproto "github.com/MagHErmit/tendermint/proto/tendermint/types" 22 "github.com/MagHErmit/tendermint/proxy" 23 sm "github.com/MagHErmit/tendermint/state" 24 "github.com/MagHErmit/tendermint/store" 25 "github.com/MagHErmit/tendermint/types" 26 tmtime "github.com/MagHErmit/tendermint/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, sm.StoreOptions{ 106 DiscardABCIResponses: false, 107 }) 108 blockStore := store.NewBlockStore(blockDB) 109 110 state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) 111 if err != nil { 112 panic(fmt.Errorf("error constructing state from genesis file: %w", err)) 113 } 114 115 // Make the BlockchainReactor itself. 116 // NOTE we have to create and commit the blocks first because 117 // pool.height is determined from the store. 118 fastSync := true 119 db := dbm.NewMemDB() 120 stateStore = sm.NewStore(db, sm.StoreOptions{ 121 DiscardABCIResponses: false, 122 }) 123 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), 124 mock.Mempool{}, sm.EmptyEvidencePool{}) 125 if err = stateStore.Save(state); err != nil { 126 panic(err) 127 } 128 129 // let's add some blocks in 130 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 131 lastCommit := types.NewCommit(blockHeight-1, 1, types.BlockID{}, nil) 132 if blockHeight > 1 { 133 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 134 lastBlock := blockStore.LoadBlock(blockHeight - 1) 135 136 vote := makeVote(t, &lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0]) 137 lastCommit = types.NewCommit(vote.Height, vote.Round, lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 138 } 139 140 thisBlock := makeBlock(blockHeight, state, lastCommit) 141 142 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 143 blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} 144 145 state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock) 146 if err != nil { 147 panic(fmt.Errorf("error apply block: %w", err)) 148 } 149 150 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 151 } 152 153 bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) 154 bcReactor.SetLogger(logger.With("module", "blockchain")) 155 156 return bcReactor 157 } 158 159 func newBlockchainReactorPair( 160 t *testing.T, 161 logger log.Logger, 162 genDoc *types.GenesisDoc, 163 privVals []types.PrivValidator, 164 maxBlockHeight int64) BlockchainReactorPair { 165 166 consensusReactor := &consensusReactorTest{} 167 consensusReactor.BaseReactor = *p2p.NewBaseReactor("Consensus reactor", consensusReactor) 168 169 return BlockchainReactorPair{ 170 newBlockchainReactor(t, logger, genDoc, privVals, maxBlockHeight), 171 consensusReactor} 172 } 173 174 type consensusReactorTest struct { 175 p2p.BaseReactor // BaseService + p2p.Switch 176 switchedToConsensus bool 177 mtx sync.Mutex 178 } 179 180 func (conR *consensusReactorTest) SwitchToConsensus(state sm.State, blocksSynced bool) { 181 conR.mtx.Lock() 182 defer conR.mtx.Unlock() 183 conR.switchedToConsensus = true 184 } 185 186 func TestFastSyncNoBlockResponse(t *testing.T) { 187 188 config = cfg.ResetTestRoot("blockchain_new_reactor_test") 189 defer os.RemoveAll(config.RootDir) 190 genDoc, privVals := randGenesisDoc(1, false, 30) 191 192 maxBlockHeight := int64(65) 193 194 reactorPairs := make([]BlockchainReactorPair, 2) 195 196 logger := log.TestingLogger() 197 reactorPairs[0] = newBlockchainReactorPair(t, logger, genDoc, privVals, maxBlockHeight) 198 reactorPairs[1] = newBlockchainReactorPair(t, logger, genDoc, privVals, 0) 199 200 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { 201 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 202 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 203 moduleName := fmt.Sprintf("blockchain-%v", i) 204 reactorPairs[i].bcR.SetLogger(logger.With("module", moduleName)) 205 206 return s 207 208 }, p2p.Connect2Switches) 209 210 defer func() { 211 for _, r := range reactorPairs { 212 _ = r.bcR.Stop() 213 _ = r.conR.Stop() 214 } 215 }() 216 217 tests := []struct { 218 height int64 219 existent bool 220 }{ 221 {maxBlockHeight + 2, false}, 222 {10, true}, 223 {1, true}, 224 {maxBlockHeight + 100, false}, 225 } 226 227 for { 228 time.Sleep(10 * time.Millisecond) 229 reactorPairs[1].conR.mtx.Lock() 230 if reactorPairs[1].conR.switchedToConsensus { 231 reactorPairs[1].conR.mtx.Unlock() 232 break 233 } 234 reactorPairs[1].conR.mtx.Unlock() 235 } 236 237 assert.Equal(t, maxBlockHeight, reactorPairs[0].bcR.store.Height()) 238 239 for _, tt := range tests { 240 block := reactorPairs[1].bcR.store.LoadBlock(tt.height) 241 if tt.existent { 242 assert.True(t, block != nil) 243 } else { 244 assert.True(t, block == nil) 245 } 246 } 247 } 248 249 // NOTE: This is too hard to test without 250 // an easy way to add test peer to switch 251 // or without significant refactoring of the module. 252 // Alternatively we could actually dial a TCP conn but 253 // that seems extreme. 254 func TestFastSyncBadBlockStopsPeer(t *testing.T) { 255 numNodes := 4 256 maxBlockHeight := int64(148) 257 258 config = cfg.ResetTestRoot("blockchain_reactor_test") 259 defer os.RemoveAll(config.RootDir) 260 genDoc, privVals := randGenesisDoc(1, false, 30) 261 262 otherChain := newBlockchainReactorPair(t, log.TestingLogger(), genDoc, privVals, maxBlockHeight) 263 defer func() { 264 _ = otherChain.bcR.Stop() 265 _ = otherChain.conR.Stop() 266 }() 267 268 reactorPairs := make([]BlockchainReactorPair, numNodes) 269 logger := make([]log.Logger, numNodes) 270 271 for i := 0; i < numNodes; i++ { 272 logger[i] = log.TestingLogger() 273 height := int64(0) 274 if i == 0 { 275 height = maxBlockHeight 276 } 277 reactorPairs[i] = newBlockchainReactorPair(t, logger[i], genDoc, privVals, height) 278 } 279 280 switches := p2p.MakeConnectedSwitches(config.P2P, numNodes, func(i int, s *p2p.Switch) *p2p.Switch { 281 reactorPairs[i].conR.mtx.Lock() 282 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 283 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 284 moduleName := fmt.Sprintf("blockchain-%v", i) 285 reactorPairs[i].bcR.SetLogger(logger[i].With("module", moduleName)) 286 reactorPairs[i].conR.mtx.Unlock() 287 return s 288 289 }, p2p.Connect2Switches) 290 291 defer func() { 292 for _, r := range reactorPairs { 293 _ = r.bcR.Stop() 294 _ = r.conR.Stop() 295 } 296 }() 297 298 outerFor: 299 for { 300 time.Sleep(10 * time.Millisecond) 301 for i := 0; i < numNodes; i++ { 302 reactorPairs[i].conR.mtx.Lock() 303 if !reactorPairs[i].conR.switchedToConsensus { 304 reactorPairs[i].conR.mtx.Unlock() 305 continue outerFor 306 } 307 reactorPairs[i].conR.mtx.Unlock() 308 } 309 break 310 } 311 312 // at this time, reactors[0-3] is the newest 313 assert.Equal(t, numNodes-1, reactorPairs[1].bcR.Switch.Peers().Size()) 314 315 // mark last reactorPair as an invalid peer 316 reactorPairs[numNodes-1].bcR.store = otherChain.bcR.store 317 318 lastLogger := log.TestingLogger() 319 lastReactorPair := newBlockchainReactorPair(t, lastLogger, genDoc, privVals, 0) 320 reactorPairs = append(reactorPairs, lastReactorPair) 321 322 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { 323 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].bcR) 324 s.AddReactor("CONSENSUS", reactorPairs[len(reactorPairs)-1].conR) 325 moduleName := fmt.Sprintf("blockchain-%v", len(reactorPairs)-1) 326 reactorPairs[len(reactorPairs)-1].bcR.SetLogger(lastLogger.With("module", moduleName)) 327 return s 328 329 }, p2p.Connect2Switches)...) 330 331 for i := 0; i < len(reactorPairs)-1; i++ { 332 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 333 } 334 335 for { 336 time.Sleep(1 * time.Second) 337 lastReactorPair.conR.mtx.Lock() 338 if lastReactorPair.conR.switchedToConsensus { 339 lastReactorPair.conR.mtx.Unlock() 340 break 341 } 342 lastReactorPair.conR.mtx.Unlock() 343 344 if lastReactorPair.bcR.Switch.Peers().Size() == 0 { 345 break 346 } 347 } 348 349 assert.True(t, lastReactorPair.bcR.Switch.Peers().Size() < len(reactorPairs)-1) 350 } 351 352 //---------------------------------------------- 353 // utility funcs 354 355 func makeTxs(height int64) (txs []types.Tx) { 356 for i := 0; i < 10; i++ { 357 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 358 } 359 return txs 360 } 361 362 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 363 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) 364 return block 365 } 366 367 type testApp struct { 368 abci.BaseApplication 369 }