github.com/vipernet-xyz/tendermint-core@v0.32.0/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/pkg/errors" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 dbm "github.com/tendermint/tm-db" 16 17 abci "github.com/tendermint/tendermint/abci/types" 18 cfg "github.com/tendermint/tendermint/config" 19 "github.com/tendermint/tendermint/libs/log" 20 "github.com/tendermint/tendermint/mock" 21 "github.com/tendermint/tendermint/p2p" 22 "github.com/tendermint/tendermint/proxy" 23 sm "github.com/tendermint/tendermint/state" 24 "github.com/tendermint/tendermint/store" 25 "github.com/tendermint/tendermint/types" 26 tmtime "github.com/tendermint/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: types.PrecommitType, 69 BlockID: blockID, 70 } 71 72 _ = privVal.SignVote(header.ChainID, vote) 73 74 return vote 75 } 76 77 type BlockchainReactorPair struct { 78 bcR *BlockchainReactor 79 conR *consensusReactorTest 80 } 81 82 func newBlockchainReactor( 83 t *testing.T, 84 logger log.Logger, 85 genDoc *types.GenesisDoc, 86 privVals []types.PrivValidator, 87 maxBlockHeight int64) *BlockchainReactor { 88 if len(privVals) != 1 { 89 panic("only support one validator") 90 } 91 92 app := &testApp{} 93 cc := proxy.NewLocalClientCreator(app) 94 proxyApp := proxy.NewAppConns(cc) 95 err := proxyApp.Start() 96 if err != nil { 97 panic(errors.Wrap(err, "error start app")) 98 } 99 100 blockDB := dbm.NewMemDB() 101 stateDB := dbm.NewMemDB() 102 blockStore := store.NewBlockStore(blockDB) 103 104 state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) 105 if err != nil { 106 panic(errors.Wrap(err, "error constructing state from genesis file")) 107 } 108 109 // Make the BlockchainReactor itself. 110 // NOTE we have to create and commit the blocks first because 111 // pool.height is determined from the store. 112 fastSync := true 113 db := dbm.NewMemDB() 114 blockExec := sm.NewBlockExecutor(db, log.TestingLogger(), proxyApp.Consensus(), 115 mock.Mempool{}, sm.MockEvidencePool{}) 116 sm.SaveState(db, state) 117 118 // let's add some blocks in 119 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 120 lastCommit := types.NewCommit(blockHeight-1, 1, types.BlockID{}, nil) 121 if blockHeight > 1 { 122 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 123 lastBlock := blockStore.LoadBlock(blockHeight - 1) 124 125 vote := makeVote(t, &lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0]) 126 lastCommit = types.NewCommit(vote.Height, vote.Round, lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 127 } 128 129 thisBlock := makeBlock(blockHeight, state, lastCommit) 130 131 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 132 blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()} 133 134 state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock) 135 if err != nil { 136 panic(errors.Wrap(err, "error apply block")) 137 } 138 139 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 140 } 141 142 bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) 143 bcReactor.SetLogger(logger.With("module", "blockchain")) 144 145 return bcReactor 146 } 147 148 func newBlockchainReactorPair( 149 t *testing.T, 150 logger log.Logger, 151 genDoc *types.GenesisDoc, 152 privVals []types.PrivValidator, 153 maxBlockHeight int64) BlockchainReactorPair { 154 155 consensusReactor := &consensusReactorTest{} 156 consensusReactor.BaseReactor = *p2p.NewBaseReactor("Consensus reactor", consensusReactor) 157 158 return BlockchainReactorPair{ 159 newBlockchainReactor(t, logger, genDoc, privVals, maxBlockHeight), 160 consensusReactor} 161 } 162 163 type consensusReactorTest struct { 164 p2p.BaseReactor // BaseService + p2p.Switch 165 switchedToConsensus bool 166 mtx sync.Mutex 167 } 168 169 func (conR *consensusReactorTest) SwitchToConsensus(state sm.State, blocksSynced uint64) { 170 conR.mtx.Lock() 171 defer conR.mtx.Unlock() 172 conR.switchedToConsensus = true 173 } 174 175 func TestFastSyncNoBlockResponse(t *testing.T) { 176 177 config = cfg.ResetTestRoot("blockchain_new_reactor_test") 178 defer os.RemoveAll(config.RootDir) 179 genDoc, privVals := randGenesisDoc(1, false, 30) 180 181 maxBlockHeight := int64(65) 182 183 reactorPairs := make([]BlockchainReactorPair, 2) 184 185 logger := log.TestingLogger() 186 reactorPairs[0] = newBlockchainReactorPair(t, logger, genDoc, privVals, maxBlockHeight) 187 reactorPairs[1] = newBlockchainReactorPair(t, logger, genDoc, privVals, 0) 188 189 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { 190 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 191 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 192 moduleName := fmt.Sprintf("blockchain-%v", i) 193 reactorPairs[i].bcR.SetLogger(logger.With("module", moduleName)) 194 195 return s 196 197 }, p2p.Connect2Switches) 198 199 defer func() { 200 for _, r := range reactorPairs { 201 _ = r.bcR.Stop() 202 _ = r.conR.Stop() 203 } 204 }() 205 206 tests := []struct { 207 height int64 208 existent bool 209 }{ 210 {maxBlockHeight + 2, false}, 211 {10, true}, 212 {1, true}, 213 {maxBlockHeight + 100, false}, 214 } 215 216 for { 217 time.Sleep(10 * time.Millisecond) 218 reactorPairs[1].conR.mtx.Lock() 219 if reactorPairs[1].conR.switchedToConsensus { 220 reactorPairs[1].conR.mtx.Unlock() 221 break 222 } 223 reactorPairs[1].conR.mtx.Unlock() 224 } 225 226 assert.Equal(t, maxBlockHeight, reactorPairs[0].bcR.store.Height()) 227 228 for _, tt := range tests { 229 block := reactorPairs[1].bcR.store.LoadBlock(tt.height) 230 if tt.existent { 231 assert.True(t, block != nil) 232 } else { 233 assert.True(t, block == nil) 234 } 235 } 236 } 237 238 // NOTE: This is too hard to test without 239 // an easy way to add test peer to switch 240 // or without significant refactoring of the module. 241 // Alternatively we could actually dial a TCP conn but 242 // that seems extreme. 243 func TestFastSyncBadBlockStopsPeer(t *testing.T) { 244 numNodes := 4 245 maxBlockHeight := int64(148) 246 247 config = cfg.ResetTestRoot("blockchain_reactor_test") 248 defer os.RemoveAll(config.RootDir) 249 genDoc, privVals := randGenesisDoc(1, false, 30) 250 251 otherChain := newBlockchainReactorPair(t, log.TestingLogger(), genDoc, privVals, maxBlockHeight) 252 defer func() { 253 _ = otherChain.bcR.Stop() 254 _ = otherChain.conR.Stop() 255 }() 256 257 reactorPairs := make([]BlockchainReactorPair, numNodes) 258 logger := make([]log.Logger, numNodes) 259 260 for i := 0; i < numNodes; i++ { 261 logger[i] = log.TestingLogger() 262 height := int64(0) 263 if i == 0 { 264 height = maxBlockHeight 265 } 266 reactorPairs[i] = newBlockchainReactorPair(t, logger[i], genDoc, privVals, height) 267 } 268 269 switches := p2p.MakeConnectedSwitches(config.P2P, numNodes, func(i int, s *p2p.Switch) *p2p.Switch { 270 reactorPairs[i].conR.mtx.Lock() 271 s.AddReactor("BLOCKCHAIN", reactorPairs[i].bcR) 272 s.AddReactor("CONSENSUS", reactorPairs[i].conR) 273 moduleName := fmt.Sprintf("blockchain-%v", i) 274 reactorPairs[i].bcR.SetLogger(logger[i].With("module", moduleName)) 275 reactorPairs[i].conR.mtx.Unlock() 276 return s 277 278 }, p2p.Connect2Switches) 279 280 defer func() { 281 for _, r := range reactorPairs { 282 _ = r.bcR.Stop() 283 _ = r.conR.Stop() 284 } 285 }() 286 287 outerFor: 288 for { 289 time.Sleep(10 * time.Millisecond) 290 for i := 0; i < numNodes; i++ { 291 reactorPairs[i].conR.mtx.Lock() 292 if !reactorPairs[i].conR.switchedToConsensus { 293 reactorPairs[i].conR.mtx.Unlock() 294 continue outerFor 295 } 296 reactorPairs[i].conR.mtx.Unlock() 297 } 298 break 299 } 300 301 //at this time, reactors[0-3] is the newest 302 assert.Equal(t, numNodes-1, reactorPairs[1].bcR.Switch.Peers().Size()) 303 304 //mark last reactorPair as an invalid peer 305 reactorPairs[numNodes-1].bcR.store = otherChain.bcR.store 306 307 lastLogger := log.TestingLogger() 308 lastReactorPair := newBlockchainReactorPair(t, lastLogger, genDoc, privVals, 0) 309 reactorPairs = append(reactorPairs, lastReactorPair) 310 311 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { 312 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].bcR) 313 s.AddReactor("CONSENSUS", reactorPairs[len(reactorPairs)-1].conR) 314 moduleName := fmt.Sprintf("blockchain-%v", len(reactorPairs)-1) 315 reactorPairs[len(reactorPairs)-1].bcR.SetLogger(lastLogger.With("module", moduleName)) 316 return s 317 318 }, p2p.Connect2Switches)...) 319 320 for i := 0; i < len(reactorPairs)-1; i++ { 321 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 322 } 323 324 for { 325 time.Sleep(1 * time.Second) 326 lastReactorPair.conR.mtx.Lock() 327 if lastReactorPair.conR.switchedToConsensus { 328 lastReactorPair.conR.mtx.Unlock() 329 break 330 } 331 lastReactorPair.conR.mtx.Unlock() 332 333 if lastReactorPair.bcR.Switch.Peers().Size() == 0 { 334 break 335 } 336 } 337 338 assert.True(t, lastReactorPair.bcR.Switch.Peers().Size() < len(reactorPairs)-1) 339 } 340 341 func TestBcBlockRequestMessageValidateBasic(t *testing.T) { 342 testCases := []struct { 343 testName string 344 requestHeight int64 345 expectErr bool 346 }{ 347 {"Valid Request Message", 0, false}, 348 {"Valid Request Message", 1, false}, 349 {"Invalid Request Message", -1, true}, 350 } 351 352 for _, tc := range testCases { 353 tc := tc 354 t.Run(tc.testName, func(t *testing.T) { 355 request := bcBlockRequestMessage{Height: tc.requestHeight} 356 assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") 357 }) 358 } 359 } 360 361 func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { 362 testCases := []struct { 363 testName string 364 nonResponseHeight int64 365 expectErr bool 366 }{ 367 {"Valid Non-Response Message", 0, false}, 368 {"Valid Non-Response Message", 1, false}, 369 {"Invalid Non-Response Message", -1, true}, 370 } 371 372 for _, tc := range testCases { 373 tc := tc 374 t.Run(tc.testName, func(t *testing.T) { 375 nonResponse := bcNoBlockResponseMessage{Height: tc.nonResponseHeight} 376 assert.Equal(t, tc.expectErr, nonResponse.ValidateBasic() != nil, "Validate Basic had an unexpected result") 377 }) 378 } 379 } 380 381 func TestBcStatusRequestMessageValidateBasic(t *testing.T) { 382 testCases := []struct { 383 testName string 384 requestHeight int64 385 expectErr bool 386 }{ 387 {"Valid Request Message", 0, false}, 388 {"Valid Request Message", 1, false}, 389 {"Invalid Request Message", -1, true}, 390 } 391 392 for _, tc := range testCases { 393 tc := tc 394 t.Run(tc.testName, func(t *testing.T) { 395 request := bcStatusRequestMessage{Height: tc.requestHeight} 396 assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") 397 }) 398 } 399 } 400 401 func TestBcStatusResponseMessageValidateBasic(t *testing.T) { 402 testCases := []struct { 403 testName string 404 responseHeight int64 405 expectErr bool 406 }{ 407 {"Valid Response Message", 0, false}, 408 {"Valid Response Message", 1, false}, 409 {"Invalid Response Message", -1, true}, 410 } 411 412 for _, tc := range testCases { 413 tc := tc 414 t.Run(tc.testName, func(t *testing.T) { 415 response := bcStatusResponseMessage{Height: tc.responseHeight} 416 assert.Equal(t, tc.expectErr, response.ValidateBasic() != nil, "Validate Basic had an unexpected result") 417 }) 418 } 419 } 420 421 //---------------------------------------------- 422 // utility funcs 423 424 func makeTxs(height int64) (txs []types.Tx) { 425 for i := 0; i < 10; i++ { 426 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 427 } 428 return txs 429 } 430 431 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 432 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) 433 return block 434 } 435 436 type testApp struct { 437 abci.BaseApplication 438 }