github.com/DFWallet/tendermint-cosmos@v0.0.2/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/DFWallet/tendermint-cosmos/abci/types" 17 cfg "github.com/DFWallet/tendermint-cosmos/config" 18 "github.com/DFWallet/tendermint-cosmos/libs/log" 19 "github.com/DFWallet/tendermint-cosmos/mempool/mock" 20 "github.com/DFWallet/tendermint-cosmos/p2p" 21 tmproto "github.com/DFWallet/tendermint-cosmos/proto/tendermint/types" 22 "github.com/DFWallet/tendermint-cosmos/proxy" 23 sm "github.com/DFWallet/tendermint-cosmos/state" 24 "github.com/DFWallet/tendermint-cosmos/store" 25 "github.com/DFWallet/tendermint-cosmos/types" 26 tmtime "github.com/DFWallet/tendermint-cosmos/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(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) 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 bcReactor.SetLogger(logger.With("module", "blockchain")) 151 152 return bcReactor 153 } 154 155 func newBlockchainReactorPair( 156 t *testing.T, 157 logger log.Logger, 158 genDoc *types.GenesisDoc, 159 privVals []types.PrivValidator, 160 maxBlockHeight int64) BlockchainReactorPair { 161 162 consensusReactor := &consensusReactorTest{} 163 consensusReactor.BaseReactor = *p2p.NewBaseReactor("Consensus reactor", consensusReactor) 164 165 return BlockchainReactorPair{ 166 newBlockchainReactor(t, logger, genDoc, privVals, maxBlockHeight), 167 consensusReactor} 168 } 169 170 type consensusReactorTest struct { 171 p2p.BaseReactor // BaseService + p2p.Switch 172 switchedToConsensus bool 173 mtx sync.Mutex 174 } 175 176 func (conR *consensusReactorTest) SwitchToConsensus(state sm.State, blocksSynced bool) { 177 conR.mtx.Lock() 178 defer conR.mtx.Unlock() 179 conR.switchedToConsensus = true 180 } 181 182 func TestFastSyncNoBlockResponse(t *testing.T) { 183 184 config = cfg.ResetTestRoot("blockchain_new_reactor_test") 185 defer os.RemoveAll(config.RootDir) 186 genDoc, privVals := randGenesisDoc(1, false, 30) 187 188 maxBlockHeight := int64(65) 189 190 reactorPairs := make([]BlockchainReactorPair, 2) 191 192 logger := log.TestingLogger() 193 reactorPairs[0] = newBlockchainReactorPair(t, logger, genDoc, privVals, maxBlockHeight) 194 reactorPairs[1] = newBlockchainReactorPair(t, logger, genDoc, privVals, 0) 195 196 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { 197 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 198 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 199 moduleName := fmt.Sprintf("blockchain-%v", i) 200 reactorPairs[i].bcR.SetLogger(logger.With("module", moduleName)) 201 202 return s 203 204 }, p2p.Connect2Switches) 205 206 defer func() { 207 for _, r := range reactorPairs { 208 _ = r.bcR.Stop() 209 _ = r.conR.Stop() 210 } 211 }() 212 213 tests := []struct { 214 height int64 215 existent bool 216 }{ 217 {maxBlockHeight + 2, false}, 218 {10, true}, 219 {1, true}, 220 {maxBlockHeight + 100, false}, 221 } 222 223 for { 224 time.Sleep(10 * time.Millisecond) 225 reactorPairs[1].conR.mtx.Lock() 226 if reactorPairs[1].conR.switchedToConsensus { 227 reactorPairs[1].conR.mtx.Unlock() 228 break 229 } 230 reactorPairs[1].conR.mtx.Unlock() 231 } 232 233 assert.Equal(t, maxBlockHeight, reactorPairs[0].bcR.store.Height()) 234 235 for _, tt := range tests { 236 block := reactorPairs[1].bcR.store.LoadBlock(tt.height) 237 if tt.existent { 238 assert.True(t, block != nil) 239 } else { 240 assert.True(t, block == nil) 241 } 242 } 243 } 244 245 // NOTE: This is too hard to test without 246 // an easy way to add test peer to switch 247 // or without significant refactoring of the module. 248 // Alternatively we could actually dial a TCP conn but 249 // that seems extreme. 250 func TestFastSyncBadBlockStopsPeer(t *testing.T) { 251 numNodes := 4 252 maxBlockHeight := int64(148) 253 254 config = cfg.ResetTestRoot("blockchain_reactor_test") 255 defer os.RemoveAll(config.RootDir) 256 genDoc, privVals := randGenesisDoc(1, false, 30) 257 258 otherChain := newBlockchainReactorPair(t, log.TestingLogger(), genDoc, privVals, maxBlockHeight) 259 defer func() { 260 _ = otherChain.bcR.Stop() 261 _ = otherChain.conR.Stop() 262 }() 263 264 reactorPairs := make([]BlockchainReactorPair, numNodes) 265 logger := make([]log.Logger, numNodes) 266 267 for i := 0; i < numNodes; i++ { 268 logger[i] = log.TestingLogger() 269 height := int64(0) 270 if i == 0 { 271 height = maxBlockHeight 272 } 273 reactorPairs[i] = newBlockchainReactorPair(t, logger[i], genDoc, privVals, height) 274 } 275 276 switches := p2p.MakeConnectedSwitches(config.P2P, numNodes, func(i int, s *p2p.Switch) *p2p.Switch { 277 reactorPairs[i].conR.mtx.Lock() 278 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 279 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 280 moduleName := fmt.Sprintf("blockchain-%v", i) 281 reactorPairs[i].bcR.SetLogger(logger[i].With("module", moduleName)) 282 reactorPairs[i].conR.mtx.Unlock() 283 return s 284 285 }, p2p.Connect2Switches) 286 287 defer func() { 288 for _, r := range reactorPairs { 289 _ = r.bcR.Stop() 290 _ = r.conR.Stop() 291 } 292 }() 293 294 outerFor: 295 for { 296 time.Sleep(10 * time.Millisecond) 297 for i := 0; i < numNodes; i++ { 298 reactorPairs[i].conR.mtx.Lock() 299 if !reactorPairs[i].conR.switchedToConsensus { 300 reactorPairs[i].conR.mtx.Unlock() 301 continue outerFor 302 } 303 reactorPairs[i].conR.mtx.Unlock() 304 } 305 break 306 } 307 308 // at this time, reactors[0-3] is the newest 309 assert.Equal(t, numNodes-1, reactorPairs[1].bcR.Switch.Peers().Size()) 310 311 // mark last reactorPair as an invalid peer 312 reactorPairs[numNodes-1].bcR.store = otherChain.bcR.store 313 314 lastLogger := log.TestingLogger() 315 lastReactorPair := newBlockchainReactorPair(t, lastLogger, genDoc, privVals, 0) 316 reactorPairs = append(reactorPairs, lastReactorPair) 317 318 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { 319 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].bcR) 320 s.AddReactor("CONSENSUS", reactorPairs[len(reactorPairs)-1].conR) 321 moduleName := fmt.Sprintf("blockchain-%v", len(reactorPairs)-1) 322 reactorPairs[len(reactorPairs)-1].bcR.SetLogger(lastLogger.With("module", moduleName)) 323 return s 324 325 }, p2p.Connect2Switches)...) 326 327 for i := 0; i < len(reactorPairs)-1; i++ { 328 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 329 } 330 331 for { 332 time.Sleep(1 * time.Second) 333 lastReactorPair.conR.mtx.Lock() 334 if lastReactorPair.conR.switchedToConsensus { 335 lastReactorPair.conR.mtx.Unlock() 336 break 337 } 338 lastReactorPair.conR.mtx.Unlock() 339 340 if lastReactorPair.bcR.Switch.Peers().Size() == 0 { 341 break 342 } 343 } 344 345 assert.True(t, lastReactorPair.bcR.Switch.Peers().Size() < len(reactorPairs)-1) 346 } 347 348 //---------------------------------------------- 349 // utility funcs 350 351 func makeTxs(height int64) (txs []types.Tx) { 352 for i := 0; i < 10; i++ { 353 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 354 } 355 return txs 356 } 357 358 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 359 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) 360 return block 361 } 362 363 type testApp struct { 364 abci.BaseApplication 365 }