github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/rpc_beacon_blocks_by_range_test.go (about) 1 package sync 2 3 import ( 4 "context" 5 "io" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/kevinms/leakybucket-go" 11 "github.com/libp2p/go-libp2p-core/network" 12 "github.com/libp2p/go-libp2p-core/protocol" 13 types "github.com/prysmaticlabs/eth2-types" 14 chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" 15 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 16 db2 "github.com/prysmaticlabs/prysm/beacon-chain/db" 17 db "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" 18 "github.com/prysmaticlabs/prysm/beacon-chain/p2p" 19 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder" 20 p2ptest "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" 21 p2ptypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types" 22 "github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags" 23 pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" 24 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 25 "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper" 26 "github.com/prysmaticlabs/prysm/shared/bytesutil" 27 "github.com/prysmaticlabs/prysm/shared/params" 28 "github.com/prysmaticlabs/prysm/shared/testutil" 29 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 30 "github.com/prysmaticlabs/prysm/shared/testutil/require" 31 logTest "github.com/sirupsen/logrus/hooks/test" 32 ) 33 34 func TestRPCBeaconBlocksByRange_RPCHandlerReturnsBlocks(t *testing.T) { 35 p1 := p2ptest.NewTestP2P(t) 36 p2 := p2ptest.NewTestP2P(t) 37 p1.Connect(p2) 38 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 39 d := db.SetupDB(t) 40 41 req := &pb.BeaconBlocksByRangeRequest{ 42 StartSlot: 100, 43 Step: 64, 44 Count: 16, 45 } 46 47 // Populate the database with blocks that would match the request. 48 for i := req.StartSlot; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 49 blk := testutil.NewBeaconBlock() 50 blk.Block.Slot = i 51 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 52 } 53 54 // Start service with 160 as allowed blocks capacity (and almost zero capacity recovery). 55 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 56 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 57 topic := string(pcl) 58 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, int64(req.Count*10), false) 59 var wg sync.WaitGroup 60 wg.Add(1) 61 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 62 defer wg.Done() 63 for i := req.StartSlot; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) { 64 expectSuccess(t, stream) 65 res := testutil.NewBeaconBlock() 66 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 67 if res.Block.Slot.SubSlot(req.StartSlot).Mod(req.Step) != 0 { 68 t.Errorf("Received unexpected block slot %d", res.Block.Slot) 69 } 70 } 71 }) 72 73 stream1, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 74 require.NoError(t, err) 75 76 err = r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream1) 77 require.NoError(t, err) 78 79 // Make sure that rate limiter doesn't limit capacity exceedingly. 80 remainingCapacity := r.rateLimiter.limiterMap[topic].Remaining(p2.PeerID().String()) 81 expectedCapacity := int64(req.Count*10 - req.Count) 82 require.Equal(t, expectedCapacity, remainingCapacity, "Unexpected rate limiting capacity") 83 84 if testutil.WaitTimeout(&wg, 1*time.Second) { 85 t.Fatal("Did not receive stream within 1 sec") 86 } 87 } 88 89 func TestRPCBeaconBlocksByRange_ReturnCorrectNumberBack(t *testing.T) { 90 p1 := p2ptest.NewTestP2P(t) 91 p2 := p2ptest.NewTestP2P(t) 92 p1.Connect(p2) 93 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 94 d := db.SetupDB(t) 95 96 req := &pb.BeaconBlocksByRangeRequest{ 97 StartSlot: 0, 98 Step: 1, 99 Count: 200, 100 } 101 102 genRoot := [32]byte{} 103 // Populate the database with blocks that would match the request. 104 for i := req.StartSlot; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 105 blk := testutil.NewBeaconBlock() 106 blk.Block.Slot = i 107 if i == 0 { 108 rt, err := blk.Block.HashTreeRoot() 109 require.NoError(t, err) 110 genRoot = rt 111 } 112 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 113 } 114 require.NoError(t, d.SaveGenesisBlockRoot(context.Background(), genRoot)) 115 116 // Start service with 160 as allowed blocks capacity (and almost zero capacity recovery). 117 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 118 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 119 topic := string(pcl) 120 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, int64(req.Count*10), false) 121 var wg sync.WaitGroup 122 wg.Add(1) 123 124 // Use a new request to test this out 125 newReq := &pb.BeaconBlocksByRangeRequest{StartSlot: 0, Step: 1, Count: 1} 126 127 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 128 defer wg.Done() 129 for i := newReq.StartSlot; i < newReq.StartSlot.Add(newReq.Count*newReq.Step); i += types.Slot(newReq.Step) { 130 expectSuccess(t, stream) 131 res := testutil.NewBeaconBlock() 132 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 133 if res.Block.Slot.SubSlot(newReq.StartSlot).Mod(newReq.Step) != 0 { 134 t.Errorf("Received unexpected block slot %d", res.Block.Slot) 135 } 136 // Expect EOF 137 b := make([]byte, 1) 138 _, err := stream.Read(b) 139 require.ErrorContains(t, io.EOF.Error(), err) 140 } 141 }) 142 143 stream1, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 144 require.NoError(t, err) 145 146 err = r.beaconBlocksByRangeRPCHandler(context.Background(), newReq, stream1) 147 require.NoError(t, err) 148 149 if testutil.WaitTimeout(&wg, 1*time.Second) { 150 t.Fatal("Did not receive stream within 1 sec") 151 } 152 } 153 154 func TestRPCBeaconBlocksByRange_RPCHandlerReturnsSortedBlocks(t *testing.T) { 155 p1 := p2ptest.NewTestP2P(t) 156 p2 := p2ptest.NewTestP2P(t) 157 p1.Connect(p2) 158 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 159 d := db.SetupDB(t) 160 161 req := &pb.BeaconBlocksByRangeRequest{ 162 StartSlot: 200, 163 Step: 21, 164 Count: 33, 165 } 166 167 endSlot := req.StartSlot.Add(req.Step * (req.Count - 1)) 168 expectedRoots := make([][32]byte, req.Count) 169 // Populate the database with blocks that would match the request. 170 for i, j := endSlot, req.Count-1; i >= req.StartSlot; i -= types.Slot(req.Step) { 171 blk := testutil.NewBeaconBlock() 172 blk.Block.Slot = i 173 rt, err := blk.Block.HashTreeRoot() 174 require.NoError(t, err) 175 expectedRoots[j] = rt 176 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 177 j-- 178 } 179 180 // Start service with 160 as allowed blocks capacity (and almost zero capacity recovery). 181 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 182 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 183 topic := string(pcl) 184 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, int64(req.Count*10), false) 185 186 var wg sync.WaitGroup 187 wg.Add(1) 188 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 189 defer wg.Done() 190 prevSlot := types.Slot(0) 191 require.Equal(t, uint64(len(expectedRoots)), req.Count, "Number of roots not expected") 192 for i, j := req.StartSlot, 0; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) { 193 expectSuccess(t, stream) 194 res := ðpb.SignedBeaconBlock{} 195 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 196 if res.Block.Slot < prevSlot { 197 t.Errorf("Received block is unsorted with slot %d lower than previous slot %d", res.Block.Slot, prevSlot) 198 } 199 rt, err := res.Block.HashTreeRoot() 200 require.NoError(t, err) 201 assert.Equal(t, expectedRoots[j], rt, "roots not equal") 202 prevSlot = res.Block.Slot 203 j++ 204 } 205 }) 206 207 stream1, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 208 require.NoError(t, err) 209 require.NoError(t, r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream1)) 210 211 if testutil.WaitTimeout(&wg, 1*time.Second) { 212 t.Fatal("Did not receive stream within 1 sec") 213 } 214 } 215 216 func TestRPCBeaconBlocksByRange_ReturnsGenesisBlock(t *testing.T) { 217 p1 := p2ptest.NewTestP2P(t) 218 p2 := p2ptest.NewTestP2P(t) 219 p1.Connect(p2) 220 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 221 d := db.SetupDB(t) 222 223 req := &pb.BeaconBlocksByRangeRequest{ 224 StartSlot: 0, 225 Step: 1, 226 Count: 4, 227 } 228 229 prevRoot := [32]byte{} 230 // Populate the database with blocks that would match the request. 231 for i := req.StartSlot; i < req.StartSlot.Add(req.Step*req.Count); i++ { 232 blk := testutil.NewBeaconBlock() 233 blk.Block.Slot = i 234 blk.Block.ParentRoot = prevRoot[:] 235 rt, err := blk.Block.HashTreeRoot() 236 require.NoError(t, err) 237 238 // Save genesis block 239 if i == 0 { 240 require.NoError(t, d.SaveGenesisBlockRoot(context.Background(), rt)) 241 } 242 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 243 prevRoot = rt 244 } 245 246 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 247 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 248 topic := string(pcl) 249 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(10000, 10000, false) 250 251 var wg sync.WaitGroup 252 wg.Add(1) 253 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 254 defer wg.Done() 255 // check for genesis block 256 expectSuccess(t, stream) 257 res := ðpb.SignedBeaconBlock{} 258 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 259 assert.Equal(t, types.Slot(0), res.Block.Slot, "genesis block was not returned") 260 for i := req.StartSlot.Add(req.Step); i < types.Slot(req.Count*req.Step); i += types.Slot(req.Step) { 261 expectSuccess(t, stream) 262 res := ðpb.SignedBeaconBlock{} 263 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 264 } 265 }) 266 267 stream1, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 268 require.NoError(t, err) 269 require.NoError(t, r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream1)) 270 271 if testutil.WaitTimeout(&wg, 1*time.Second) { 272 t.Fatal("Did not receive stream within 1 sec") 273 } 274 } 275 276 func TestRPCBeaconBlocksByRange_RPCHandlerRateLimitOverflow(t *testing.T) { 277 d := db.SetupDB(t) 278 saveBlocks := func(req *pb.BeaconBlocksByRangeRequest) { 279 // Populate the database with blocks that would match the request. 280 parentRoot := [32]byte{} 281 for i := req.StartSlot; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 282 block := testutil.NewBeaconBlock() 283 block.Block.Slot = i 284 if req.Step == 1 { 285 block.Block.ParentRoot = parentRoot[:] 286 } 287 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(block))) 288 rt, err := block.Block.HashTreeRoot() 289 require.NoError(t, err) 290 parentRoot = rt 291 } 292 } 293 sendRequest := func(p1, p2 *p2ptest.TestP2P, r *Service, 294 req *pb.BeaconBlocksByRangeRequest, validateBlocks bool, success bool) error { 295 var wg sync.WaitGroup 296 wg.Add(1) 297 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 298 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 299 defer wg.Done() 300 if !validateBlocks { 301 return 302 } 303 for i := req.StartSlot; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) { 304 if !success { 305 continue 306 } 307 expectSuccess(t, stream) 308 res := testutil.NewBeaconBlock() 309 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, res)) 310 if res.Block.Slot.SubSlot(req.StartSlot).Mod(req.Step) != 0 { 311 t.Errorf("Received unexpected block slot %d", res.Block.Slot) 312 } 313 } 314 }) 315 stream, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 316 require.NoError(t, err) 317 if err := r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream); err != nil { 318 return err 319 } 320 if testutil.WaitTimeout(&wg, 1*time.Second) { 321 t.Fatal("Did not receive stream within 1 sec") 322 } 323 return nil 324 } 325 326 t.Run("high request count param and no overflow", func(t *testing.T) { 327 p1 := p2ptest.NewTestP2P(t) 328 p2 := p2ptest.NewTestP2P(t) 329 p1.Connect(p2) 330 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 331 332 capacity := int64(flags.Get().BlockBatchLimit * 3) 333 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 334 335 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 336 topic := string(pcl) 337 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, capacity, false) 338 req := &pb.BeaconBlocksByRangeRequest{ 339 StartSlot: 100, 340 Step: 5, 341 Count: uint64(capacity), 342 } 343 saveBlocks(req) 344 345 assert.NoError(t, sendRequest(p1, p2, r, req, true, true)) 346 347 remainingCapacity := r.rateLimiter.limiterMap[topic].Remaining(p2.PeerID().String()) 348 expectedCapacity := int64(0) // Whole capacity is used, but no overflow. 349 assert.Equal(t, expectedCapacity, remainingCapacity, "Unexpected rate limiting capacity") 350 }) 351 352 t.Run("high request count param and overflow", func(t *testing.T) { 353 p1 := p2ptest.NewTestP2P(t) 354 p2 := p2ptest.NewTestP2P(t) 355 p1.Connect(p2) 356 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 357 358 capacity := int64(flags.Get().BlockBatchLimit * 3) 359 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 360 361 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 362 topic := string(pcl) 363 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, capacity, false) 364 365 req := &pb.BeaconBlocksByRangeRequest{ 366 StartSlot: 100, 367 Step: 5, 368 Count: uint64(capacity + 1), 369 } 370 saveBlocks(req) 371 372 for i := 0; i < p2.Peers().Scorers().BadResponsesScorer().Params().Threshold; i++ { 373 err := sendRequest(p1, p2, r, req, false, true) 374 assert.ErrorContains(t, p2ptypes.ErrRateLimited.Error(), err) 375 } 376 377 remainingCapacity := r.rateLimiter.limiterMap[topic].Remaining(p2.PeerID().String()) 378 expectedCapacity := int64(0) // Whole capacity is used. 379 assert.Equal(t, expectedCapacity, remainingCapacity, "Unexpected rate limiting capacity") 380 }) 381 382 t.Run("many requests with count set to max blocks per second", func(t *testing.T) { 383 p1 := p2ptest.NewTestP2P(t) 384 p2 := p2ptest.NewTestP2P(t) 385 p1.Connect(p2) 386 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 387 388 capacity := int64(flags.Get().BlockBatchLimit * flags.Get().BlockBatchLimitBurstFactor) 389 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 390 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 391 topic := string(pcl) 392 r.rateLimiter.limiterMap[topic] = leakybucket.NewCollector(0.000001, capacity, false) 393 394 req := &pb.BeaconBlocksByRangeRequest{ 395 StartSlot: 100, 396 Step: 1, 397 Count: uint64(flags.Get().BlockBatchLimit), 398 } 399 saveBlocks(req) 400 401 for i := 0; i < flags.Get().BlockBatchLimitBurstFactor; i++ { 402 assert.NoError(t, sendRequest(p1, p2, r, req, true, false)) 403 } 404 405 // One more request should result in overflow. 406 for i := 0; i < p2.Peers().Scorers().BadResponsesScorer().Params().Threshold; i++ { 407 err := sendRequest(p1, p2, r, req, false, false) 408 assert.ErrorContains(t, p2ptypes.ErrRateLimited.Error(), err) 409 } 410 411 remainingCapacity := r.rateLimiter.limiterMap[topic].Remaining(p2.PeerID().String()) 412 expectedCapacity := int64(0) // Whole capacity is used. 413 assert.Equal(t, expectedCapacity, remainingCapacity, "Unexpected rate limiting capacity") 414 }) 415 } 416 417 func TestRPCBeaconBlocksByRange_validateRangeRequest(t *testing.T) { 418 slotsSinceGenesis := types.Slot(1000) 419 offset := int64(slotsSinceGenesis.Mul(params.BeaconConfig().SecondsPerSlot)) 420 r := &Service{ 421 cfg: &Config{ 422 Chain: &chainMock.ChainService{ 423 Genesis: time.Now().Add(time.Second * time.Duration(-1*offset)), 424 }, 425 }, 426 } 427 428 tests := []struct { 429 name string 430 req *pb.BeaconBlocksByRangeRequest 431 expectedError error 432 errorToLog string 433 }{ 434 { 435 name: "Zero Count", 436 req: &pb.BeaconBlocksByRangeRequest{ 437 Count: 0, 438 Step: 1, 439 }, 440 expectedError: p2ptypes.ErrInvalidRequest, 441 errorToLog: "validation did not fail with bad count", 442 }, 443 { 444 name: "Over limit Count", 445 req: &pb.BeaconBlocksByRangeRequest{ 446 Count: params.BeaconNetworkConfig().MaxRequestBlocks + 1, 447 Step: 1, 448 }, 449 expectedError: p2ptypes.ErrInvalidRequest, 450 errorToLog: "validation did not fail with bad count", 451 }, 452 { 453 name: "Correct Count", 454 req: &pb.BeaconBlocksByRangeRequest{ 455 Count: params.BeaconNetworkConfig().MaxRequestBlocks - 1, 456 Step: 1, 457 }, 458 errorToLog: "validation failed with correct count", 459 }, 460 { 461 name: "Zero Step", 462 req: &pb.BeaconBlocksByRangeRequest{ 463 Step: 0, 464 Count: 1, 465 }, 466 expectedError: p2ptypes.ErrInvalidRequest, 467 errorToLog: "validation did not fail with bad step", 468 }, 469 { 470 name: "Over limit Step", 471 req: &pb.BeaconBlocksByRangeRequest{ 472 Step: rangeLimit + 1, 473 Count: 1, 474 }, 475 expectedError: p2ptypes.ErrInvalidRequest, 476 errorToLog: "validation did not fail with bad step", 477 }, 478 { 479 name: "Correct Step", 480 req: &pb.BeaconBlocksByRangeRequest{ 481 Step: rangeLimit - 1, 482 Count: 2, 483 }, 484 errorToLog: "validation failed with correct step", 485 }, 486 { 487 name: "Over Limit Start Slot", 488 req: &pb.BeaconBlocksByRangeRequest{ 489 StartSlot: slotsSinceGenesis.Add((2 * rangeLimit) + 1), 490 Step: 1, 491 Count: 1, 492 }, 493 expectedError: p2ptypes.ErrInvalidRequest, 494 errorToLog: "validation did not fail with bad start slot", 495 }, 496 { 497 name: "Over Limit End Slot", 498 req: &pb.BeaconBlocksByRangeRequest{ 499 Step: 1, 500 Count: params.BeaconNetworkConfig().MaxRequestBlocks + 1, 501 }, 502 expectedError: p2ptypes.ErrInvalidRequest, 503 errorToLog: "validation did not fail with bad end slot", 504 }, 505 { 506 name: "Exceed Range Limit", 507 req: &pb.BeaconBlocksByRangeRequest{ 508 Step: 3, 509 Count: uint64(slotsSinceGenesis / 2), 510 }, 511 expectedError: p2ptypes.ErrInvalidRequest, 512 errorToLog: "validation did not fail with bad range", 513 }, 514 { 515 name: "Valid Request", 516 req: &pb.BeaconBlocksByRangeRequest{ 517 Step: 1, 518 Count: params.BeaconNetworkConfig().MaxRequestBlocks - 1, 519 StartSlot: 50, 520 }, 521 errorToLog: "validation failed with valid params", 522 }, 523 } 524 525 for _, tt := range tests { 526 t.Run(tt.name, func(t *testing.T) { 527 if tt.expectedError != nil { 528 assert.ErrorContains(t, tt.expectedError.Error(), r.validateRangeRequest(tt.req), tt.errorToLog) 529 } else { 530 assert.NoError(t, r.validateRangeRequest(tt.req), tt.errorToLog) 531 } 532 }) 533 } 534 } 535 536 func TestRPCBeaconBlocksByRange_EnforceResponseInvariants(t *testing.T) { 537 d := db.SetupDB(t) 538 hook := logTest.NewGlobal() 539 saveBlocks := func(req *pb.BeaconBlocksByRangeRequest) { 540 // Populate the database with blocks that would match the request. 541 parentRoot := [32]byte{} 542 for i := req.StartSlot; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 543 block := testutil.NewBeaconBlock() 544 block.Block.Slot = i 545 block.Block.ParentRoot = parentRoot[:] 546 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(block))) 547 rt, err := block.Block.HashTreeRoot() 548 require.NoError(t, err) 549 parentRoot = rt 550 } 551 } 552 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 553 sendRequest := func(p1, p2 *p2ptest.TestP2P, r *Service, 554 req *pb.BeaconBlocksByRangeRequest, processBlocks func([]*ethpb.SignedBeaconBlock)) error { 555 var wg sync.WaitGroup 556 wg.Add(1) 557 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 558 defer wg.Done() 559 blocks := make([]*ethpb.SignedBeaconBlock, 0, req.Count) 560 for i := req.StartSlot; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) { 561 expectSuccess(t, stream) 562 blk := testutil.NewBeaconBlock() 563 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, blk)) 564 if blk.Block.Slot.SubSlot(req.StartSlot).Mod(req.Step) != 0 { 565 t.Errorf("Received unexpected block slot %d", blk.Block.Slot) 566 } 567 blocks = append(blocks, blk) 568 } 569 processBlocks(blocks) 570 }) 571 stream, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 572 require.NoError(t, err) 573 if err := r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream); err != nil { 574 return err 575 } 576 if testutil.WaitTimeout(&wg, 1*time.Second) { 577 t.Fatal("Did not receive stream within 1 sec") 578 } 579 return nil 580 } 581 582 t.Run("assert range", func(t *testing.T) { 583 p1 := p2ptest.NewTestP2P(t) 584 p2 := p2ptest.NewTestP2P(t) 585 p1.Connect(p2) 586 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 587 588 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 589 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 590 req := &pb.BeaconBlocksByRangeRequest{ 591 StartSlot: 448, 592 Step: 1, 593 Count: 64, 594 } 595 saveBlocks(req) 596 597 hook.Reset() 598 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 599 assert.Equal(t, req.Count, uint64(len(blocks))) 600 for _, blk := range blocks { 601 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= req.StartSlot.Add(req.Count*req.Step) { 602 t.Errorf("Block slot is out of range: %d is not within [%d, %d)", 603 blk.Block.Slot, req.StartSlot, req.StartSlot.Add(req.Count*req.Step)) 604 } 605 } 606 }) 607 assert.NoError(t, err) 608 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 609 }) 610 } 611 612 func TestRPCBeaconBlocksByRange_FilterBlocks(t *testing.T) { 613 hook := logTest.NewGlobal() 614 615 saveBlocks := func(d db2.Database, chain *chainMock.ChainService, req *pb.BeaconBlocksByRangeRequest, finalized bool) { 616 blk := testutil.NewBeaconBlock() 617 blk.Block.Slot = 0 618 previousRoot, err := blk.Block.HashTreeRoot() 619 require.NoError(t, err) 620 621 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 622 require.NoError(t, d.SaveGenesisBlockRoot(context.Background(), previousRoot)) 623 blocks := make([]*ethpb.SignedBeaconBlock, req.Count) 624 // Populate the database with blocks that would match the request. 625 for i, j := req.StartSlot, 0; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 626 parentRoot := make([]byte, 32) 627 copy(parentRoot, previousRoot[:]) 628 blocks[j] = testutil.NewBeaconBlock() 629 blocks[j].Block.Slot = i 630 blocks[j].Block.ParentRoot = parentRoot 631 var err error 632 previousRoot, err = blocks[j].Block.HashTreeRoot() 633 require.NoError(t, err) 634 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blocks[j]))) 635 j++ 636 } 637 stateSummaries := make([]*pb.StateSummary, len(blocks)) 638 639 if finalized { 640 if chain.CanonicalRoots == nil { 641 chain.CanonicalRoots = map[[32]byte]bool{} 642 } 643 for i, b := range blocks { 644 bRoot, err := b.Block.HashTreeRoot() 645 require.NoError(t, err) 646 stateSummaries[i] = &pb.StateSummary{ 647 Slot: b.Block.Slot, 648 Root: bRoot[:], 649 } 650 chain.CanonicalRoots[bRoot] = true 651 } 652 require.NoError(t, d.SaveStateSummaries(context.Background(), stateSummaries)) 653 require.NoError(t, d.SaveFinalizedCheckpoint(context.Background(), ðpb.Checkpoint{ 654 Epoch: helpers.SlotToEpoch(stateSummaries[len(stateSummaries)-1].Slot), 655 Root: stateSummaries[len(stateSummaries)-1].Root, 656 })) 657 } 658 } 659 saveBadBlocks := func(d db2.Database, chain *chainMock.ChainService, 660 req *pb.BeaconBlocksByRangeRequest, badBlockNum uint64, finalized bool) { 661 blk := testutil.NewBeaconBlock() 662 blk.Block.Slot = 0 663 previousRoot, err := blk.Block.HashTreeRoot() 664 require.NoError(t, err) 665 genRoot := previousRoot 666 667 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blk))) 668 require.NoError(t, d.SaveGenesisBlockRoot(context.Background(), previousRoot)) 669 blocks := make([]*ethpb.SignedBeaconBlock, req.Count) 670 // Populate the database with blocks with non linear roots. 671 for i, j := req.StartSlot, 0; i < req.StartSlot.Add(req.Step*req.Count); i += types.Slot(req.Step) { 672 parentRoot := make([]byte, 32) 673 copy(parentRoot, previousRoot[:]) 674 blocks[j] = testutil.NewBeaconBlock() 675 blocks[j].Block.Slot = i 676 blocks[j].Block.ParentRoot = parentRoot 677 // Make the 2nd block have a bad root. 678 if j == int(badBlockNum) { 679 blocks[j].Block.ParentRoot = genRoot[:] 680 } 681 var err error 682 previousRoot, err = blocks[j].Block.HashTreeRoot() 683 require.NoError(t, err) 684 require.NoError(t, d.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(blocks[j]))) 685 j++ 686 } 687 stateSummaries := make([]*pb.StateSummary, len(blocks)) 688 if finalized { 689 if chain.CanonicalRoots == nil { 690 chain.CanonicalRoots = map[[32]byte]bool{} 691 } 692 for i, b := range blocks { 693 bRoot, err := b.Block.HashTreeRoot() 694 require.NoError(t, err) 695 stateSummaries[i] = &pb.StateSummary{ 696 Slot: b.Block.Slot, 697 Root: bRoot[:], 698 } 699 chain.CanonicalRoots[bRoot] = true 700 } 701 require.NoError(t, d.SaveStateSummaries(context.Background(), stateSummaries)) 702 require.NoError(t, d.SaveFinalizedCheckpoint(context.Background(), ðpb.Checkpoint{ 703 Epoch: helpers.SlotToEpoch(stateSummaries[len(stateSummaries)-1].Slot), 704 Root: stateSummaries[len(stateSummaries)-1].Root, 705 })) 706 } 707 } 708 pcl := protocol.ID(p2p.RPCBlocksByRangeTopicV1) 709 sendRequest := func(p1, p2 *p2ptest.TestP2P, r *Service, 710 req *pb.BeaconBlocksByRangeRequest, processBlocks func([]*ethpb.SignedBeaconBlock)) error { 711 var wg sync.WaitGroup 712 wg.Add(1) 713 p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) { 714 defer wg.Done() 715 blocks := make([]*ethpb.SignedBeaconBlock, 0, req.Count) 716 for i := req.StartSlot; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) { 717 code, _, err := ReadStatusCode(stream, &encoder.SszNetworkEncoder{}) 718 if err != nil && err != io.EOF { 719 t.Fatal(err) 720 } 721 if code != 0 || err == io.EOF { 722 break 723 } 724 blk := testutil.NewBeaconBlock() 725 assert.NoError(t, r.cfg.P2P.Encoding().DecodeWithMaxLength(stream, blk)) 726 if blk.Block.Slot.SubSlot(req.StartSlot).Mod(req.Step) != 0 { 727 t.Errorf("Received unexpected block slot %d", blk.Block.Slot) 728 } 729 blocks = append(blocks, blk) 730 } 731 processBlocks(blocks) 732 }) 733 stream, err := p1.BHost.NewStream(context.Background(), p2.BHost.ID(), pcl) 734 require.NoError(t, err) 735 if err := r.beaconBlocksByRangeRPCHandler(context.Background(), req, stream); err != nil { 736 return err 737 } 738 if testutil.WaitTimeout(&wg, 1*time.Second) { 739 t.Fatal("Did not receive stream within 1 sec") 740 } 741 return nil 742 } 743 744 t.Run("process normal range", func(t *testing.T) { 745 p1 := p2ptest.NewTestP2P(t) 746 p2 := p2ptest.NewTestP2P(t) 747 d := db.SetupDB(t) 748 749 p1.Connect(p2) 750 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 751 752 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 753 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 754 req := &pb.BeaconBlocksByRangeRequest{ 755 StartSlot: 1, 756 Step: 1, 757 Count: 64, 758 } 759 saveBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, true) 760 761 hook.Reset() 762 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 763 assert.Equal(t, req.Count, uint64(len(blocks))) 764 for _, blk := range blocks { 765 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= req.StartSlot.Add(req.Count*req.Step) { 766 t.Errorf("Block slot is out of range: %d is not within [%d, %d)", 767 blk.Block.Slot, req.StartSlot, req.StartSlot.Add(req.Count*req.Step)) 768 } 769 } 770 }) 771 assert.NoError(t, err) 772 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 773 }) 774 775 t.Run("process non linear blocks", func(t *testing.T) { 776 p1 := p2ptest.NewTestP2P(t) 777 p2 := p2ptest.NewTestP2P(t) 778 d := db.SetupDB(t) 779 780 p1.Connect(p2) 781 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 782 783 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 784 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 785 req := &pb.BeaconBlocksByRangeRequest{ 786 StartSlot: 1, 787 Step: 1, 788 Count: 64, 789 } 790 saveBadBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, 2, true) 791 792 hook.Reset() 793 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 794 assert.Equal(t, uint64(2), uint64(len(blocks))) 795 prevRoot := [32]byte{} 796 for _, blk := range blocks { 797 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= req.StartSlot.Add(req.Count*req.Step) { 798 t.Errorf("Block slot is out of range: %d is not within [%d, %d)", 799 blk.Block.Slot, req.StartSlot, req.StartSlot.Add(req.Count*req.Step)) 800 } 801 if prevRoot != [32]byte{} && bytesutil.ToBytes32(blk.Block.ParentRoot) != prevRoot { 802 t.Errorf("non linear chain received, expected %#x but got %#x", prevRoot, blk.Block.ParentRoot) 803 } 804 } 805 }) 806 assert.NoError(t, err) 807 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 808 }) 809 810 t.Run("process non linear blocks with 2nd bad batch", func(t *testing.T) { 811 p1 := p2ptest.NewTestP2P(t) 812 p2 := p2ptest.NewTestP2P(t) 813 d := db.SetupDB(t) 814 815 p1.Connect(p2) 816 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 817 818 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 819 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 820 req := &pb.BeaconBlocksByRangeRequest{ 821 StartSlot: 1, 822 Step: 1, 823 Count: 128, 824 } 825 saveBadBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, 65, true) 826 827 hook.Reset() 828 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 829 assert.Equal(t, uint64(65), uint64(len(blocks))) 830 prevRoot := [32]byte{} 831 for _, blk := range blocks { 832 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= req.StartSlot.Add(req.Count*req.Step) { 833 t.Errorf("Block slot is out of range: %d is not within [%d, %d)", 834 blk.Block.Slot, req.StartSlot, req.StartSlot.Add(req.Count*req.Step)) 835 } 836 if prevRoot != [32]byte{} && bytesutil.ToBytes32(blk.Block.ParentRoot) != prevRoot { 837 t.Errorf("non linear chain received, expected %#x but got %#x", prevRoot, blk.Block.ParentRoot) 838 } 839 } 840 }) 841 assert.NoError(t, err) 842 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 843 }) 844 845 t.Run("only return finalized blocks", func(t *testing.T) { 846 p1 := p2ptest.NewTestP2P(t) 847 p2 := p2ptest.NewTestP2P(t) 848 d := db.SetupDB(t) 849 850 p1.Connect(p2) 851 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 852 853 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 854 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 855 req := &pb.BeaconBlocksByRangeRequest{ 856 StartSlot: 1, 857 Step: 1, 858 Count: 64, 859 } 860 saveBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, true) 861 req.StartSlot = 65 862 req.Step = 1 863 req.Count = 128 864 // Save unfinalized chain. 865 saveBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, false) 866 867 req.StartSlot = 1 868 hook.Reset() 869 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 870 assert.Equal(t, uint64(64), uint64(len(blocks))) 871 prevRoot := [32]byte{} 872 for _, blk := range blocks { 873 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= 65 { 874 t.Errorf("Block slot is out of range: %d is not within [%d, 64)", 875 blk.Block.Slot, req.StartSlot) 876 } 877 if prevRoot != [32]byte{} && bytesutil.ToBytes32(blk.Block.ParentRoot) != prevRoot { 878 t.Errorf("non linear chain received, expected %#x but got %#x", prevRoot, blk.Block.ParentRoot) 879 } 880 } 881 }) 882 assert.NoError(t, err) 883 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 884 }) 885 t.Run("reject duplicate and non canonical blocks", func(t *testing.T) { 886 p1 := p2ptest.NewTestP2P(t) 887 p2 := p2ptest.NewTestP2P(t) 888 d := db.SetupDB(t) 889 890 p1.Connect(p2) 891 assert.Equal(t, 1, len(p1.BHost.Network().Peers()), "Expected peers to be connected") 892 893 r := &Service{cfg: &Config{P2P: p1, DB: d, Chain: &chainMock.ChainService{}}, rateLimiter: newRateLimiter(p1)} 894 r.rateLimiter.limiterMap[string(pcl)] = leakybucket.NewCollector(0.000001, 640, false) 895 req := &pb.BeaconBlocksByRangeRequest{ 896 StartSlot: 1, 897 Step: 1, 898 Count: 64, 899 } 900 saveBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, true) 901 902 // Create a duplicate set of unfinalized blocks. 903 req.StartSlot = 1 904 req.Step = 1 905 req.Count = 300 906 // Save unfinalized chain. 907 saveBlocks(d, r.cfg.Chain.(*chainMock.ChainService), req, false) 908 909 req.Count = 64 910 hook.Reset() 911 err := sendRequest(p1, p2, r, req, func(blocks []*ethpb.SignedBeaconBlock) { 912 assert.Equal(t, uint64(64), uint64(len(blocks))) 913 prevRoot := [32]byte{} 914 for _, blk := range blocks { 915 if blk.Block.Slot < req.StartSlot || blk.Block.Slot >= 65 { 916 t.Errorf("Block slot is out of range: %d is not within [%d, 64)", 917 blk.Block.Slot, req.StartSlot) 918 } 919 if prevRoot != [32]byte{} && bytesutil.ToBytes32(blk.Block.ParentRoot) != prevRoot { 920 t.Errorf("non linear chain received, expected %#x but got %#x", prevRoot, blk.Block.ParentRoot) 921 } 922 } 923 }) 924 assert.NoError(t, err) 925 require.LogsDoNotContain(t, hook, "Disconnecting bad peer") 926 }) 927 }