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