github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/blockchain/reactor_test.go (about) 1 package blockchain 2 3 import ( 4 "log/slog" 5 "os" 6 "sort" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 12 abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" 13 "github.com/gnolang/gno/tm2/pkg/bft/appconn" 14 cfg "github.com/gnolang/gno/tm2/pkg/bft/config" 15 "github.com/gnolang/gno/tm2/pkg/bft/mempool/mock" 16 "github.com/gnolang/gno/tm2/pkg/bft/proxy" 17 sm "github.com/gnolang/gno/tm2/pkg/bft/state" 18 "github.com/gnolang/gno/tm2/pkg/bft/store" 19 "github.com/gnolang/gno/tm2/pkg/bft/types" 20 tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" 21 "github.com/gnolang/gno/tm2/pkg/db/memdb" 22 "github.com/gnolang/gno/tm2/pkg/errors" 23 "github.com/gnolang/gno/tm2/pkg/log" 24 "github.com/gnolang/gno/tm2/pkg/p2p" 25 "github.com/gnolang/gno/tm2/pkg/testutils" 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 appconn.AppConns 53 } 54 55 func newBlockchainReactor(logger *slog.Logger, genDoc *types.GenesisDoc, privVals []types.PrivValidator, maxBlockHeight int64) BlockchainReactorPair { 56 if len(privVals) != 1 { 57 panic("only support one validator") 58 } 59 60 app := &testApp{} 61 cc := proxy.NewLocalClientCreator(app) 62 proxyApp := appconn.NewAppConns(cc) 63 err := proxyApp.Start() 64 if err != nil { 65 panic(errors.Wrap(err, "error start app")) 66 } 67 68 blockDB := memdb.NewMemDB() 69 stateDB := memdb.NewMemDB() 70 blockStore := store.NewBlockStore(blockDB) 71 72 state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) 73 if err != nil { 74 panic(errors.Wrap(err, "error constructing state from genesis file")) 75 } 76 77 // Make the BlockchainReactor itself. 78 // NOTE we have to create and commit the blocks first because 79 // pool.height is determined from the store. 80 fastSync := true 81 db := memdb.NewMemDB() 82 blockExec := sm.NewBlockExecutor(db, logger, proxyApp.Consensus(), mock.Mempool{}) 83 sm.SaveState(db, state) 84 85 // let's add some blocks in 86 for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { 87 lastCommit := types.NewCommit(types.BlockID{}, nil) 88 if blockHeight > 1 { 89 lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) 90 lastBlock := blockStore.LoadBlock(blockHeight - 1) 91 92 vote, err := types.MakeVote(lastBlock.Header.Height, lastBlockMeta.BlockID, state.Validators, privVals[0], lastBlock.Header.ChainID) 93 if err != nil { 94 panic(err) 95 } 96 voteCommitSig := vote.CommitSig() 97 lastCommit = types.NewCommit(lastBlockMeta.BlockID, []*types.CommitSig{voteCommitSig}) 98 } 99 100 thisBlock := makeBlock(blockHeight, state, lastCommit) 101 102 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 103 blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()} 104 105 state, err = blockExec.ApplyBlock(state, blockID, thisBlock) 106 if err != nil { 107 panic(errors.Wrap(err, "error apply block")) 108 } 109 110 blockStore.SaveBlock(thisBlock, thisParts, lastCommit) 111 } 112 113 bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) 114 bcReactor.SetLogger(logger.With("module", "blockchain")) 115 116 return BlockchainReactorPair{bcReactor, proxyApp} 117 } 118 119 func TestNoBlockResponse(t *testing.T) { 120 t.Parallel() 121 122 config, _ = cfg.ResetTestRoot("blockchain_reactor_test") 123 defer os.RemoveAll(config.RootDir) 124 genDoc, privVals := randGenesisDoc(1, false, 30) 125 126 maxBlockHeight := int64(65) 127 128 reactorPairs := make([]BlockchainReactorPair, 2) 129 130 reactorPairs[0] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, maxBlockHeight) 131 reactorPairs[1] = newBlockchainReactor(log.NewTestingLogger(t), genDoc, privVals, 0) 132 133 p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { 134 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 135 return s 136 }, p2p.Connect2Switches) 137 138 defer func() { 139 for _, r := range reactorPairs { 140 r.reactor.Stop() 141 r.app.Stop() 142 } 143 }() 144 145 tests := []struct { 146 height int64 147 existent bool 148 }{ 149 {maxBlockHeight + 2, false}, 150 {10, true}, 151 {1, true}, 152 {100, false}, 153 } 154 155 for { 156 if reactorPairs[1].reactor.pool.IsCaughtUp() { 157 break 158 } 159 160 time.Sleep(10 * time.Millisecond) 161 } 162 163 assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height()) 164 165 for _, tt := range tests { 166 block := reactorPairs[1].reactor.store.LoadBlock(tt.height) 167 if tt.existent { 168 assert.True(t, block != nil) 169 } else { 170 assert.True(t, block == nil) 171 } 172 } 173 } 174 175 // NOTE: This is too hard to test without 176 // an easy way to add test peer to switch 177 // or without significant refactoring of the module. 178 // Alternatively we could actually dial a TCP conn but 179 // that seems extreme. 180 func TestFlappyBadBlockStopsPeer(t *testing.T) { 181 t.Parallel() 182 183 testutils.FilterStability(t, testutils.Flappy) 184 185 config, _ = cfg.ResetTestRoot("blockchain_reactor_test") 186 defer os.RemoveAll(config.RootDir) 187 genDoc, privVals := randGenesisDoc(1, false, 30) 188 189 maxBlockHeight := int64(148) 190 191 otherChain := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight) 192 defer func() { 193 otherChain.reactor.Stop() 194 otherChain.app.Stop() 195 }() 196 197 reactorPairs := make([]BlockchainReactorPair, 4) 198 199 reactorPairs[0] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, maxBlockHeight) 200 reactorPairs[1] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) 201 reactorPairs[2] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) 202 reactorPairs[3] = newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) 203 204 switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch { 205 s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) 206 return s 207 }, p2p.Connect2Switches) 208 209 defer func() { 210 for _, r := range reactorPairs { 211 r.reactor.Stop() 212 r.app.Stop() 213 } 214 }() 215 216 for { 217 if reactorPairs[3].reactor.pool.IsCaughtUp() { 218 break 219 } 220 221 time.Sleep(1 * time.Second) 222 } 223 224 // at this time, reactors[0-3] is the newest 225 assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) 226 227 // mark reactorPairs[3] is an invalid peer 228 reactorPairs[3].reactor.store = otherChain.reactor.store 229 230 lastReactorPair := newBlockchainReactor(log.NewNoopLogger(), genDoc, privVals, 0) 231 reactorPairs = append(reactorPairs, lastReactorPair) 232 233 switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { 234 s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) 235 return s 236 }, p2p.Connect2Switches)...) 237 238 for i := 0; i < len(reactorPairs)-1; i++ { 239 p2p.Connect2Switches(switches, i, len(reactorPairs)-1) 240 } 241 242 for { 243 if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { 244 break 245 } 246 247 time.Sleep(1 * time.Second) 248 } 249 250 assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) 251 } 252 253 func TestBcBlockRequestMessageValidateBasic(t *testing.T) { 254 t.Parallel() 255 256 testCases := []struct { 257 testName string 258 requestHeight int64 259 expectErr bool 260 }{ 261 {"Valid Request Message", 0, false}, 262 {"Valid Request Message", 1, false}, 263 {"Invalid Request Message", -1, true}, 264 } 265 266 for _, tc := range testCases { 267 tc := tc 268 t.Run(tc.testName, func(t *testing.T) { 269 t.Parallel() 270 271 request := bcBlockRequestMessage{Height: tc.requestHeight} 272 assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") 273 }) 274 } 275 } 276 277 func TestBcNoBlockResponseMessageValidateBasic(t *testing.T) { 278 t.Parallel() 279 280 testCases := []struct { 281 testName string 282 nonResponseHeight int64 283 expectErr bool 284 }{ 285 {"Valid Non-Response Message", 0, false}, 286 {"Valid Non-Response Message", 1, false}, 287 {"Invalid Non-Response Message", -1, true}, 288 } 289 290 for _, tc := range testCases { 291 tc := tc 292 t.Run(tc.testName, func(t *testing.T) { 293 t.Parallel() 294 295 nonResponse := bcNoBlockResponseMessage{Height: tc.nonResponseHeight} 296 assert.Equal(t, tc.expectErr, nonResponse.ValidateBasic() != nil, "Validate Basic had an unexpected result") 297 }) 298 } 299 } 300 301 func TestBcStatusRequestMessageValidateBasic(t *testing.T) { 302 t.Parallel() 303 304 testCases := []struct { 305 testName string 306 requestHeight int64 307 expectErr bool 308 }{ 309 {"Valid Request Message", 0, false}, 310 {"Valid Request Message", 1, false}, 311 {"Invalid Request Message", -1, true}, 312 } 313 314 for _, tc := range testCases { 315 tc := tc 316 t.Run(tc.testName, func(t *testing.T) { 317 t.Parallel() 318 319 request := bcStatusRequestMessage{Height: tc.requestHeight} 320 assert.Equal(t, tc.expectErr, request.ValidateBasic() != nil, "Validate Basic had an unexpected result") 321 }) 322 } 323 } 324 325 func TestBcStatusResponseMessageValidateBasic(t *testing.T) { 326 t.Parallel() 327 328 testCases := []struct { 329 testName string 330 responseHeight int64 331 expectErr bool 332 }{ 333 {"Valid Response Message", 0, false}, 334 {"Valid Response Message", 1, false}, 335 {"Invalid Response Message", -1, true}, 336 } 337 338 for _, tc := range testCases { 339 tc := tc 340 t.Run(tc.testName, func(t *testing.T) { 341 t.Parallel() 342 343 response := bcStatusResponseMessage{Height: tc.responseHeight} 344 assert.Equal(t, tc.expectErr, response.ValidateBasic() != nil, "Validate Basic had an unexpected result") 345 }) 346 } 347 } 348 349 // ---------------------------------------------- 350 // utility funcs 351 352 func makeTxs(height int64) (txs []types.Tx) { 353 for i := 0; i < 10; i++ { 354 txs = append(txs, types.Tx([]byte{byte(height), byte(i)})) 355 } 356 return txs 357 } 358 359 func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 360 block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, state.Validators.GetProposer().Address) 361 return block 362 } 363 364 type testApp struct { 365 abci.BaseApplication 366 } 367 368 var _ abci.Application = (*testApp)(nil) 369 370 func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { 371 return abci.ResponseInfo{} 372 } 373 374 func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { 375 return abci.ResponseBeginBlock{} 376 } 377 378 func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { 379 return abci.ResponseEndBlock{} 380 } 381 382 func (app *testApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { 383 return abci.ResponseDeliverTx{ResponseBase: abci.ResponseBase{Events: []abci.Event{}}} 384 } 385 386 func (app *testApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { 387 return abci.ResponseCheckTx{} 388 } 389 390 func (app *testApp) Commit() abci.ResponseCommit { 391 return abci.ResponseCommit{} 392 } 393 394 func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { 395 return 396 }