github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/blockchain/v0/reactor_test.go (about) 1 package v0 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "os" 7 "sort" 8 "testing" 9 "time" 10 11 mdutils "github.com/ipfs/go-merkledag/test" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 abci "github.com/lazyledger/lazyledger-core/abci/types" 16 cfg "github.com/lazyledger/lazyledger-core/config" 17 "github.com/lazyledger/lazyledger-core/libs/db/memdb" 18 "github.com/lazyledger/lazyledger-core/libs/log" 19 "github.com/lazyledger/lazyledger-core/mempool/mock" 20 "github.com/lazyledger/lazyledger-core/p2p" 21 "github.com/lazyledger/lazyledger-core/proxy" 22 sm "github.com/lazyledger/lazyledger-core/state" 23 "github.com/lazyledger/lazyledger-core/store" 24 "github.com/lazyledger/lazyledger-core/types" 25 tmtime "github.com/lazyledger/lazyledger-core/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 type BlockchainReactorPair struct { 51 reactor *BlockchainReactor 52 app proxy.AppConns 53 } 54 55 func newBlockchainReactor( 56 logger log.Logger, 57 genDoc *types.GenesisDoc, 58 privVals []types.PrivValidator, 59 maxBlockHeight int64) BlockchainReactorPair { 60 if len(privVals) != 1 { 61 panic("only support one validator") 62 } 63 64 app := &testApp{} 65 cc := proxy.NewLocalClientCreator(app) 66 proxyApp := proxy.NewAppConns(cc) 67 err := proxyApp.Start() 68 if err != nil { 69 panic(fmt.Errorf("error start app: %w", err)) 70 } 71 72 blockDB := memdb.NewDB() 73 stateDB := memdb.NewDB() 74 stateStore := sm.NewStore(stateDB) 75 blockStore := store.NewBlockStore(blockDB, mdutils.Mock()) 76 77 state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) 78 if err != nil { 79 panic(fmt.Errorf("error constructing state from genesis file: %w", err)) 80 } 81 82 // Make the BlockchainReactor itself. 83 // NOTE we have to create and commit the blocks first because 84 // pool.height is determined from the store. 85 fastSync := true 86 db := memdb.NewDB() 87 stateStore = sm.NewStore(db) 88 blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), 89 mock.Mempool{}, sm.EmptyEvidencePool{}) 90 if err = stateStore.Save(state); err != nil { 91 panic(err) 92 } 93 94 var hash = make([]byte, 32) 95 hh := sha256.Sum256([]byte("Headerhash")) 96 copy(hash, hh[:]) 97 98 // let's add some blocks in 99 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 100 lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil) 101 if blockHeight > 1 { 102 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 103 lastBlock := blockStore.LoadBlock(blockHeight - 1) 104 105 vote, err := types.MakeVote( 106 lastBlock.Header.Height, 107 lastBlockMeta.BlockID, 108 state.Validators, 109 privVals[0], 110 lastBlock.Header.ChainID, 111 time.Now(), 112 ) 113 if err != nil { 114 panic(err) 115 } 116 lastCommit = types.NewCommit(vote.Height, vote.Round, 117 lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 118 } 119 120 thisBlock := makeBlock(blockHeight, state, lastCommit) 121 122 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 123 blockID := types.BlockID{Hash: thisBlock.Hash(), PartSetHeader: thisParts.Header()} 124 125 state, _, err = blockExec.ApplyBlock(state, blockID, thisBlock) 126 if err != nil { 127 panic(fmt.Errorf("error apply block: %w", err)) 128 } 129 130 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 131 } 132 133 bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) 134 bcReactor.SetLogger(logger.With("module", "blockchain")) 135 136 return BlockchainReactorPair{bcReactor, proxyApp} 137 } 138 139 func TestNoBlockResponse(t *testing.T) { 140 config = cfg.ResetTestRoot("blockchain_reactor_test") 141 defer os.RemoveAll(config.RootDir) 142 genDoc, privVals := randGenesisDoc(1, false, 30) 143 144 maxBlockHeight := int64(65) 145 146 reactorPairs := make([]BlockchainReactorPair, 2) 147 148 reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight) 149 reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) 150 151 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { 152 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 153 return s 154 155 }, p2p.Connect2Switches) 156 157 defer func() { 158 for _, r := range reactorPairs { 159 err := r.reactor.Stop() 160 require.NoError(t, err) 161 err = r.app.Stop() 162 require.NoError(t, err) 163 } 164 }() 165 166 tests := []struct { 167 height int64 168 existent bool 169 }{ 170 {maxBlockHeight + 2, false}, 171 {10, true}, 172 {1, true}, 173 {100, false}, 174 } 175 176 for { 177 if reactorPairs[1].reactor.pool.IsCaughtUp() { 178 break 179 } 180 181 time.Sleep(10 * time.Millisecond) 182 } 183 184 assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height()) 185 186 for _, tt := range tests { 187 block := reactorPairs[1].reactor.store.LoadBlock(tt.height) 188 if tt.existent { 189 assert.True(t, block != nil) 190 } else { 191 assert.True(t, block == nil) 192 } 193 } 194 } 195 196 // NOTE: This is too hard to test without 197 // an easy way to add test peer to switch 198 // or without significant refactoring of the module. 199 // Alternatively we could actually dial a TCP conn but 200 // that seems extreme. 201 func TestBadBlockStopsPeer(t *testing.T) { 202 config = cfg.ResetTestRoot("blockchain_reactor_test") 203 defer os.RemoveAll(config.RootDir) 204 genDoc, privVals := randGenesisDoc(1, false, 30) 205 206 maxBlockHeight := int64(148) 207 208 // Other chain needs a different validator set 209 otherGenDoc, otherPrivVals := randGenesisDoc(1, false, 30) 210 otherChain := newBlockchainReactor(log.TestingLogger(), otherGenDoc, otherPrivVals, maxBlockHeight) 211 212 defer func() { 213 err := otherChain.reactor.Stop() 214 require.Error(t, err) 215 err = otherChain.app.Stop() 216 require.NoError(t, err) 217 }() 218 219 reactorPairs := make([]BlockchainReactorPair, 4) 220 221 reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight) 222 reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) 223 reactorPairs[2] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) 224 reactorPairs[3] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) 225 226 switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch { 227 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 228 return s 229 230 }, p2p.Connect2Switches) 231 232 defer func() { 233 for _, r := range reactorPairs { 234 err := r.reactor.Stop() 235 require.NoError(t, err) 236 237 err = r.app.Stop() 238 require.NoError(t, err) 239 } 240 }() 241 242 for { 243 time.Sleep(1 * time.Second) 244 caughtUp := true 245 for _, r := range reactorPairs { 246 if !r.reactor.pool.IsCaughtUp() { 247 caughtUp = false 248 } 249 } 250 if caughtUp { 251 break 252 } 253 } 254 255 // at this time, reactors[0-3] is the newest 256 assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) 257 258 // Mark reactorPairs[3] as an invalid peer. Fiddling with .store without a mutex is a data 259 // race, but can't be easily avoided. 260 reactorPairs[3].reactor.store = otherChain.reactor.store 261 262 lastReactorPair := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) 263 reactorPairs = append(reactorPairs, lastReactorPair) 264 265 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { 266 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) 267 return s 268 269 }, p2p.Connect2Switches)...) 270 271 for i := 0; i < len(reactorPairs)-1; i++ { 272 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 273 } 274 275 for { 276 if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { 277 break 278 } 279 280 time.Sleep(1 * time.Second) 281 } 282 283 assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) 284 } 285 286 //---------------------------------------------- 287 // utility funcs 288 289 func makeTxs(height int64) (txs []types.Tx) { 290 for i := 0; i < 10; i++ { 291 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 292 } 293 return txs 294 } 295 296 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 297 block, _ := state.MakeBlock(height, makeTxs(height), nil, 298 nil, types.Messages{}, lastCommit, state.Validators.GetProposer().Address) 299 return block 300 } 301 302 type testApp struct { 303 abci.BaseApplication 304 } 305 306 var _ abci.Application = (*testApp)(nil) 307 308 func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { 309 return abci.ResponseInfo{} 310 } 311 312 func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { 313 return abci.ResponseBeginBlock{} 314 } 315 316 func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { 317 return abci.ResponseEndBlock{} 318 } 319 320 func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { 321 return abci.ResponseDeliverTx{Events: []abci.Event{}} 322 } 323 324 func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { 325 return abci.ResponseCheckTx{} 326 } 327 328 func (app *testApp) Commit() abci.ResponseCommit { 329 return abci.ResponseCommit{} 330 } 331 332 func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { 333 return 334 }