github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/state_stream/backend/backend_executiondata_test.go (about) 1 package backend 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/ipfs/go-datastore" 10 dssync "github.com/ipfs/go-datastore/sync" 11 "github.com/rs/zerolog" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 "github.com/stretchr/testify/suite" 16 "google.golang.org/grpc/codes" 17 "google.golang.org/grpc/status" 18 19 "github.com/onflow/flow-go/engine" 20 "github.com/onflow/flow-go/engine/access/index" 21 "github.com/onflow/flow-go/engine/access/state_stream" 22 "github.com/onflow/flow-go/engine/access/subscription" 23 subscriptionmock "github.com/onflow/flow-go/engine/access/subscription/mock" 24 "github.com/onflow/flow-go/model/flow" 25 "github.com/onflow/flow-go/module/blobs" 26 "github.com/onflow/flow-go/module/execution" 27 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 28 "github.com/onflow/flow-go/module/executiondatasync/execution_data/cache" 29 "github.com/onflow/flow-go/module/mempool/herocache" 30 "github.com/onflow/flow-go/module/metrics" 31 protocolmock "github.com/onflow/flow-go/state/protocol/mock" 32 "github.com/onflow/flow-go/storage" 33 storagemock "github.com/onflow/flow-go/storage/mock" 34 "github.com/onflow/flow-go/utils/unittest" 35 "github.com/onflow/flow-go/utils/unittest/mocks" 36 ) 37 38 var ( 39 chainID = flow.MonotonicEmulator 40 testEventTypes = []flow.EventType{ 41 unittest.EventTypeFixture(chainID), 42 unittest.EventTypeFixture(chainID), 43 unittest.EventTypeFixture(chainID), 44 } 45 ) 46 47 type BackendExecutionDataSuite struct { 48 suite.Suite 49 logger zerolog.Logger 50 state *protocolmock.State 51 params *protocolmock.Params 52 snapshot *protocolmock.Snapshot 53 headers *storagemock.Headers 54 events *storagemock.Events 55 seals *storagemock.Seals 56 results *storagemock.ExecutionResults 57 registers *storagemock.RegisterIndex 58 registersAsync *execution.RegistersAsyncStore 59 eventsIndex *index.EventsIndex 60 61 bs blobs.Blobstore 62 eds execution_data.ExecutionDataStore 63 broadcaster *engine.Broadcaster 64 execDataCache *cache.ExecutionDataCache 65 execDataHeroCache *herocache.BlockExecutionData 66 executionDataTracker *subscriptionmock.ExecutionDataTracker 67 backend *StateStreamBackend 68 executionDataTrackerReal subscription.ExecutionDataTracker 69 70 blocks []*flow.Block 71 blockEvents map[flow.Identifier][]flow.Event 72 execDataMap map[flow.Identifier]*execution_data.BlockExecutionDataEntity 73 blockMap map[uint64]*flow.Block 74 sealMap map[flow.Identifier]*flow.Seal 75 resultMap map[flow.Identifier]*flow.ExecutionResult 76 registerID flow.RegisterID 77 78 rootBlock flow.Block 79 highestBlockHeader *flow.Header 80 } 81 82 type executionDataTestType struct { 83 name string 84 highestBackfill int 85 startBlockID flow.Identifier 86 startHeight uint64 87 } 88 89 func TestBackendExecutionDataSuite(t *testing.T) { 90 suite.Run(t, new(BackendExecutionDataSuite)) 91 } 92 93 func (s *BackendExecutionDataSuite) SetupTest() { 94 blockCount := 5 95 s.SetupTestSuite(blockCount) 96 97 var err error 98 parent := s.rootBlock.Header 99 100 for i := 0; i < blockCount; i++ { 101 block := unittest.BlockWithParentFixture(parent) 102 // update for next iteration 103 parent = block.Header 104 105 seal := unittest.BlockSealsFixture(1)[0] 106 result := unittest.ExecutionResultFixture() 107 blockEvents := generateMockEvents(block.Header, (i%len(testEventTypes))*3+1) 108 109 numChunks := 5 110 chunkDatas := make([]*execution_data.ChunkExecutionData, 0, numChunks) 111 for i := 0; i < numChunks; i++ { 112 var events flow.EventsList 113 switch { 114 case i >= len(blockEvents.Events): 115 events = flow.EventsList{} 116 case i == numChunks-1: 117 events = blockEvents.Events[i:] 118 default: 119 events = flow.EventsList{blockEvents.Events[i]} 120 } 121 chunkDatas = append(chunkDatas, unittest.ChunkExecutionDataFixture(s.T(), execution_data.DefaultMaxBlobSize/5, unittest.WithChunkEvents(events))) 122 } 123 execData := unittest.BlockExecutionDataFixture( 124 unittest.WithBlockExecutionDataBlockID(block.ID()), 125 unittest.WithChunkExecutionDatas(chunkDatas...), 126 ) 127 128 result.ExecutionDataID, err = s.eds.Add(context.TODO(), execData) 129 assert.NoError(s.T(), err) 130 131 s.blocks = append(s.blocks, block) 132 s.execDataMap[block.ID()] = execution_data.NewBlockExecutionDataEntity(result.ExecutionDataID, execData) 133 s.blockEvents[block.ID()] = blockEvents.Events 134 s.blockMap[block.Header.Height] = block 135 s.sealMap[block.ID()] = seal 136 s.resultMap[seal.ResultID] = result 137 138 s.T().Logf("adding exec data for block %d %d %v => %v", i, block.Header.Height, block.ID(), result.ExecutionDataID) 139 } 140 141 s.SetupTestMocks() 142 } 143 144 func (s *BackendExecutionDataSuite) SetupTestSuite(blockCount int) { 145 s.logger = unittest.Logger() 146 147 s.state = protocolmock.NewState(s.T()) 148 s.snapshot = protocolmock.NewSnapshot(s.T()) 149 s.params = protocolmock.NewParams(s.T()) 150 s.headers = storagemock.NewHeaders(s.T()) 151 s.events = storagemock.NewEvents(s.T()) 152 s.seals = storagemock.NewSeals(s.T()) 153 s.results = storagemock.NewExecutionResults(s.T()) 154 155 s.bs = blobs.NewBlobstore(dssync.MutexWrap(datastore.NewMapDatastore())) 156 s.eds = execution_data.NewExecutionDataStore(s.bs, execution_data.DefaultSerializer) 157 158 s.broadcaster = engine.NewBroadcaster() 159 160 s.execDataHeroCache = herocache.NewBlockExecutionData(subscription.DefaultCacheSize, s.logger, metrics.NewNoopCollector()) 161 s.execDataCache = cache.NewExecutionDataCache(s.eds, s.headers, s.seals, s.results, s.execDataHeroCache) 162 s.executionDataTracker = subscriptionmock.NewExecutionDataTracker(s.T()) 163 164 s.execDataMap = make(map[flow.Identifier]*execution_data.BlockExecutionDataEntity, blockCount) 165 s.blockEvents = make(map[flow.Identifier][]flow.Event, blockCount) 166 s.blockMap = make(map[uint64]*flow.Block, blockCount) 167 s.sealMap = make(map[flow.Identifier]*flow.Seal, blockCount) 168 s.resultMap = make(map[flow.Identifier]*flow.ExecutionResult, blockCount) 169 s.blocks = make([]*flow.Block, 0, blockCount) 170 171 // generate blockCount consecutive blocks with associated seal, result and execution data 172 s.rootBlock = unittest.BlockFixture() 173 s.blockMap[s.rootBlock.Header.Height] = &s.rootBlock 174 s.highestBlockHeader = s.rootBlock.Header 175 176 s.T().Logf("Generating %d blocks, root block: %d %s", blockCount, s.rootBlock.Header.Height, s.rootBlock.ID()) 177 } 178 179 func (s *BackendExecutionDataSuite) SetupTestMocks() { 180 s.registerID = unittest.RegisterIDFixture() 181 182 s.eventsIndex = index.NewEventsIndex(s.events) 183 s.registersAsync = execution.NewRegistersAsyncStore() 184 s.registers = storagemock.NewRegisterIndex(s.T()) 185 err := s.registersAsync.Initialize(s.registers) 186 require.NoError(s.T(), err) 187 s.registers.On("LatestHeight").Return(s.rootBlock.Header.Height).Maybe() 188 s.registers.On("FirstHeight").Return(s.rootBlock.Header.Height).Maybe() 189 s.registers.On("Get", mock.AnythingOfType("RegisterID"), mock.AnythingOfType("uint64")).Return( 190 func(id flow.RegisterID, height uint64) (flow.RegisterValue, error) { 191 if id == s.registerID { 192 return flow.RegisterValue{}, nil 193 } 194 return nil, storage.ErrNotFound 195 }).Maybe() 196 197 s.state.On("Sealed").Return(s.snapshot, nil).Maybe() 198 s.snapshot.On("Head").Return(s.blocks[0].Header, nil).Maybe() 199 200 s.seals.On("FinalizedSealForBlock", mock.AnythingOfType("flow.Identifier")).Return( 201 mocks.StorageMapGetter(s.sealMap), 202 ).Maybe() 203 204 s.results.On("ByID", mock.AnythingOfType("flow.Identifier")).Return( 205 mocks.StorageMapGetter(s.resultMap), 206 ).Maybe() 207 208 s.headers.On("ByBlockID", mock.AnythingOfType("flow.Identifier")).Return( 209 func(blockID flow.Identifier) (*flow.Header, error) { 210 for _, block := range s.blockMap { 211 if block.ID() == blockID { 212 return block.Header, nil 213 } 214 } 215 return nil, storage.ErrNotFound 216 }, 217 ).Maybe() 218 219 s.headers.On("ByHeight", mock.AnythingOfType("uint64")).Return( 220 mocks.ConvertStorageOutput( 221 mocks.StorageMapGetter(s.blockMap), 222 func(block *flow.Block) *flow.Header { return block.Header }, 223 ), 224 ).Maybe() 225 226 s.headers.On("BlockIDByHeight", mock.AnythingOfType("uint64")).Return( 227 mocks.ConvertStorageOutput( 228 mocks.StorageMapGetter(s.blockMap), 229 func(block *flow.Block) flow.Identifier { return block.ID() }, 230 ), 231 ).Maybe() 232 233 s.SetupBackend(false) 234 } 235 236 func (s *BackendExecutionDataSuite) SetupBackend(useEventsIndex bool) { 237 var err error 238 s.backend, err = New( 239 s.logger, 240 s.state, 241 s.headers, 242 s.seals, 243 s.results, 244 s.eds, 245 s.execDataCache, 246 s.registersAsync, 247 s.eventsIndex, 248 useEventsIndex, 249 state_stream.DefaultRegisterIDsRequestLimit, 250 subscription.NewSubscriptionHandler( 251 s.logger, 252 s.broadcaster, 253 subscription.DefaultSendTimeout, 254 subscription.DefaultResponseLimit, 255 subscription.DefaultSendBufferSize, 256 ), 257 s.executionDataTracker, 258 ) 259 require.NoError(s.T(), err) 260 261 // create real execution data tracker to use GetStartHeight from it, instead of mocking 262 s.executionDataTrackerReal = subscription.NewExecutionDataTracker( 263 s.logger, 264 s.state, 265 s.rootBlock.Header.Height, 266 s.headers, 267 s.broadcaster, 268 s.rootBlock.Header.Height, 269 s.eventsIndex, 270 useEventsIndex, 271 ) 272 273 s.executionDataTracker.On( 274 "GetStartHeight", 275 mock.Anything, 276 mock.Anything, 277 mock.Anything, 278 ).Return(func(ctx context.Context, startBlockID flow.Identifier, startHeight uint64) (uint64, error) { 279 return s.executionDataTrackerReal.GetStartHeight(ctx, startBlockID, startHeight) 280 }, nil).Maybe() 281 282 s.executionDataTracker.On("GetHighestHeight").Return(func() uint64 { 283 return s.highestBlockHeader.Height 284 }).Maybe() 285 } 286 287 // generateMockEvents generates a set of mock events for a block split into multiple tx with 288 // appropriate indexes set 289 func generateMockEvents(header *flow.Header, eventCount int) flow.BlockEvents { 290 txCount := eventCount / 3 291 292 txID := unittest.IdentifierFixture() 293 txIndex := uint32(0) 294 eventIndex := uint32(0) 295 296 events := make([]flow.Event, eventCount) 297 for i := 0; i < eventCount; i++ { 298 if i > 0 && i%txCount == 0 { 299 txIndex++ 300 txID = unittest.IdentifierFixture() 301 eventIndex = 0 302 } 303 304 events[i] = unittest.EventFixture(testEventTypes[i%len(testEventTypes)], txIndex, eventIndex, txID, 0) 305 } 306 307 return flow.BlockEvents{ 308 BlockID: header.ID(), 309 BlockHeight: header.Height, 310 BlockTimestamp: header.Timestamp, 311 Events: events, 312 } 313 } 314 315 func (s *BackendExecutionDataSuite) TestGetExecutionDataByBlockID() { 316 ctx, cancel := context.WithCancel(context.Background()) 317 defer cancel() 318 319 block := s.blocks[0] 320 seal := s.sealMap[block.ID()] 321 result := s.resultMap[seal.ResultID] 322 execData := s.execDataMap[block.ID()] 323 324 // notify backend block is available 325 s.highestBlockHeader = block.Header 326 327 var err error 328 s.Run("happy path TestGetExecutionDataByBlockID success", func() { 329 result.ExecutionDataID, err = s.eds.Add(ctx, execData.BlockExecutionData) 330 require.NoError(s.T(), err) 331 332 res, err := s.backend.GetExecutionDataByBlockID(ctx, block.ID()) 333 assert.Equal(s.T(), execData.BlockExecutionData, res) 334 assert.NoError(s.T(), err) 335 }) 336 337 s.execDataHeroCache.Clear() 338 339 s.Run("missing exec data for TestGetExecutionDataByBlockID failure", func() { 340 result.ExecutionDataID = unittest.IdentifierFixture() 341 342 execDataRes, err := s.backend.GetExecutionDataByBlockID(ctx, block.ID()) 343 assert.Nil(s.T(), execDataRes) 344 assert.Equal(s.T(), codes.NotFound, status.Code(err)) 345 }) 346 } 347 348 func (s *BackendExecutionDataSuite) TestSubscribeExecutionData() { 349 tests := []executionDataTestType{ 350 { 351 name: "happy path - all new blocks", 352 highestBackfill: -1, // no backfill 353 startBlockID: flow.ZeroID, 354 startHeight: 0, 355 }, 356 { 357 name: "happy path - partial backfill", 358 highestBackfill: 2, // backfill the first 3 blocks 359 startBlockID: flow.ZeroID, 360 startHeight: s.blocks[0].Header.Height, 361 }, 362 { 363 name: "happy path - complete backfill", 364 highestBackfill: len(s.blocks) - 1, // backfill all blocks 365 startBlockID: s.blocks[0].ID(), 366 startHeight: 0, 367 }, 368 { 369 name: "happy path - start from root block by height", 370 highestBackfill: len(s.blocks) - 1, // backfill all blocks 371 startBlockID: flow.ZeroID, 372 startHeight: s.rootBlock.Header.Height, // start from root block 373 }, 374 { 375 name: "happy path - start from root block by id", 376 highestBackfill: len(s.blocks) - 1, // backfill all blocks 377 startBlockID: s.rootBlock.Header.ID(), // start from root block 378 startHeight: 0, 379 }, 380 } 381 382 subFunc := func(ctx context.Context, blockID flow.Identifier, startHeight uint64) subscription.Subscription { 383 return s.backend.SubscribeExecutionData(ctx, blockID, startHeight) 384 } 385 386 s.subscribe(subFunc, tests) 387 } 388 389 func (s *BackendExecutionDataSuite) TestSubscribeExecutionDataFromStartBlockID() { 390 tests := []executionDataTestType{ 391 { 392 name: "happy path - all new blocks", 393 highestBackfill: -1, // no backfill 394 startBlockID: s.rootBlock.ID(), 395 }, 396 { 397 name: "happy path - partial backfill", 398 highestBackfill: 2, // backfill the first 3 blocks 399 startBlockID: s.blocks[0].ID(), 400 }, 401 { 402 name: "happy path - complete backfill", 403 highestBackfill: len(s.blocks) - 1, // backfill all blocks 404 startBlockID: s.blocks[0].ID(), 405 }, 406 { 407 name: "happy path - start from root block by id", 408 highestBackfill: len(s.blocks) - 1, // backfill all blocks 409 startBlockID: s.rootBlock.ID(), // start from root block 410 }, 411 } 412 413 s.executionDataTracker.On( 414 "GetStartHeightFromBlockID", 415 mock.AnythingOfType("flow.Identifier"), 416 ).Return(func(startBlockID flow.Identifier) (uint64, error) { 417 return s.executionDataTrackerReal.GetStartHeightFromBlockID(startBlockID) 418 }, nil) 419 420 subFunc := func(ctx context.Context, blockID flow.Identifier, startHeight uint64) subscription.Subscription { 421 return s.backend.SubscribeExecutionDataFromStartBlockID(ctx, blockID) 422 } 423 424 s.subscribe(subFunc, tests) 425 } 426 427 func (s *BackendExecutionDataSuite) TestSubscribeExecutionDataFromStartBlockHeight() { 428 tests := []executionDataTestType{ 429 { 430 name: "happy path - all new blocks", 431 highestBackfill: -1, // no backfill 432 startHeight: s.rootBlock.Header.Height, 433 }, 434 { 435 name: "happy path - partial backfill", 436 highestBackfill: 2, // backfill the first 3 blocks 437 startHeight: s.blocks[0].Header.Height, 438 }, 439 { 440 name: "happy path - complete backfill", 441 highestBackfill: len(s.blocks) - 1, // backfill all blocks 442 startHeight: s.blocks[0].Header.Height, 443 }, 444 { 445 name: "happy path - start from root block by id", 446 highestBackfill: len(s.blocks) - 1, // backfill all blocks 447 startHeight: s.rootBlock.Header.Height, // start from root block 448 }, 449 } 450 451 s.executionDataTracker.On( 452 "GetStartHeightFromHeight", 453 mock.AnythingOfType("uint64"), 454 ).Return(func(startHeight uint64) (uint64, error) { 455 return s.executionDataTrackerReal.GetStartHeightFromHeight(startHeight) 456 }, nil) 457 458 subFunc := func(ctx context.Context, blockID flow.Identifier, startHeight uint64) subscription.Subscription { 459 return s.backend.SubscribeExecutionDataFromStartBlockHeight(ctx, startHeight) 460 } 461 462 s.subscribe(subFunc, tests) 463 } 464 465 func (s *BackendExecutionDataSuite) TestSubscribeExecutionDataFromLatest() { 466 tests := []executionDataTestType{ 467 { 468 name: "happy path - all new blocks", 469 highestBackfill: -1, // no backfill 470 }, 471 { 472 name: "happy path - partial backfill", 473 highestBackfill: 2, // backfill the first 3 blocks 474 }, 475 { 476 name: "happy path - complete backfill", 477 highestBackfill: len(s.blocks) - 1, // backfill all blocks 478 }, 479 } 480 481 s.executionDataTracker.On( 482 "GetStartHeightFromLatest", 483 mock.Anything, 484 ).Return(func(ctx context.Context) (uint64, error) { 485 return s.executionDataTrackerReal.GetStartHeightFromLatest(ctx) 486 }, nil) 487 488 subFunc := func(ctx context.Context, blockID flow.Identifier, startHeight uint64) subscription.Subscription { 489 return s.backend.SubscribeExecutionDataFromLatest(ctx) 490 } 491 492 s.subscribe(subFunc, tests) 493 } 494 495 func (s *BackendExecutionDataSuite) subscribe(subscribeFunc func(ctx context.Context, startBlockID flow.Identifier, startHeight uint64) subscription.Subscription, tests []executionDataTestType) { 496 ctx, cancel := context.WithCancel(context.Background()) 497 defer cancel() 498 499 for _, test := range tests { 500 s.Run(test.name, func() { 501 // make sure we're starting with a fresh cache 502 s.execDataHeroCache.Clear() 503 504 s.T().Logf("len(s.execDataMap) %d", len(s.execDataMap)) 505 506 // add "backfill" block - blocks that are already in the database before the test starts 507 // this simulates a subscription on a past block 508 for i := 0; i <= test.highestBackfill; i++ { 509 s.T().Logf("backfilling block %d", i) 510 s.highestBlockHeader = s.blocks[i].Header 511 } 512 513 subCtx, subCancel := context.WithCancel(ctx) 514 sub := subscribeFunc(subCtx, test.startBlockID, test.startHeight) 515 516 // loop over of the all blocks 517 for i, b := range s.blocks { 518 execData := s.execDataMap[b.ID()] 519 s.T().Logf("checking block %d %v %v", i, b.Header.Height, b.ID()) 520 521 // simulate new exec data received. 522 // exec data for all blocks with index <= highestBackfill were already received 523 if i > test.highestBackfill { 524 s.highestBlockHeader = b.Header 525 s.broadcaster.Publish() 526 } 527 528 // consume execution data from subscription 529 unittest.RequireReturnsBefore(s.T(), func() { 530 v, ok := <-sub.Channel() 531 require.True(s.T(), ok, "channel closed while waiting for exec data for block %d %v: err: %v", b.Header.Height, b.ID(), sub.Err()) 532 533 resp, ok := v.(*ExecutionDataResponse) 534 require.True(s.T(), ok, "unexpected response type: %T", v) 535 536 assert.Equal(s.T(), b.Header.Height, resp.Height) 537 assert.Equal(s.T(), execData.BlockExecutionData, resp.ExecutionData) 538 }, time.Second, fmt.Sprintf("timed out waiting for exec data for block %d %v", b.Header.Height, b.ID())) 539 } 540 541 // make sure there are no new messages waiting. the channel should be opened with nothing waiting 542 unittest.RequireNeverReturnBefore(s.T(), func() { 543 <-sub.Channel() 544 }, 100*time.Millisecond, "timed out waiting for subscription to shutdown") 545 546 // stop the subscription 547 subCancel() 548 549 // ensure subscription shuts down gracefully 550 unittest.RequireReturnsBefore(s.T(), func() { 551 v, ok := <-sub.Channel() 552 assert.Nil(s.T(), v) 553 assert.False(s.T(), ok) 554 assert.ErrorIs(s.T(), sub.Err(), context.Canceled) 555 }, 100*time.Millisecond, "timed out waiting for subscription to shutdown") 556 }) 557 } 558 } 559 560 func (s *BackendExecutionDataSuite) TestSubscribeExecutionDataHandlesErrors() { 561 ctx, cancel := context.WithCancel(context.Background()) 562 defer cancel() 563 564 s.Run("returns error if both start blockID and start height are provided", func() { 565 subCtx, subCancel := context.WithCancel(ctx) 566 defer subCancel() 567 568 sub := s.backend.SubscribeExecutionData(subCtx, unittest.IdentifierFixture(), 1) 569 assert.Equal(s.T(), codes.InvalidArgument, status.Code(sub.Err())) 570 }) 571 572 s.Run("returns error for start height before root height", func() { 573 subCtx, subCancel := context.WithCancel(ctx) 574 defer subCancel() 575 576 sub := s.backend.SubscribeExecutionData(subCtx, flow.ZeroID, s.rootBlock.Header.Height-1) 577 assert.Equal(s.T(), codes.InvalidArgument, status.Code(sub.Err())) 578 }) 579 580 s.Run("returns error for unindexed start blockID", func() { 581 subCtx, subCancel := context.WithCancel(ctx) 582 defer subCancel() 583 584 sub := s.backend.SubscribeExecutionData(subCtx, unittest.IdentifierFixture(), 0) 585 assert.Equal(s.T(), codes.NotFound, status.Code(sub.Err())) 586 }) 587 588 // make sure we're starting with a fresh cache 589 s.execDataHeroCache.Clear() 590 591 s.Run("returns error for unindexed start height", func() { 592 subCtx, subCancel := context.WithCancel(ctx) 593 defer subCancel() 594 595 sub := s.backend.SubscribeExecutionData(subCtx, flow.ZeroID, s.blocks[len(s.blocks)-1].Header.Height+10) 596 assert.Equal(s.T(), codes.NotFound, status.Code(sub.Err())) 597 }) 598 } 599 600 func (s *BackendExecutionDataSuite) TestGetRegisterValues() { 601 s.Run("normal case", func() { 602 res, err := s.backend.GetRegisterValues(flow.RegisterIDs{s.registerID}, s.rootBlock.Header.Height) 603 require.NoError(s.T(), err) 604 require.NotEmpty(s.T(), res) 605 }) 606 607 s.Run("returns error if block height is out of range", func() { 608 res, err := s.backend.GetRegisterValues(flow.RegisterIDs{s.registerID}, s.rootBlock.Header.Height+1) 609 require.Nil(s.T(), res) 610 require.Equal(s.T(), codes.OutOfRange, status.Code(err)) 611 }) 612 613 s.Run("returns error if register path is not indexed", func() { 614 falseID := flow.RegisterIDs{flow.RegisterID{Owner: "ha", Key: "ha"}} 615 res, err := s.backend.GetRegisterValues(falseID, s.rootBlock.Header.Height) 616 require.Nil(s.T(), res) 617 require.Equal(s.T(), codes.NotFound, status.Code(err)) 618 }) 619 620 s.Run("returns error if too many registers are requested", func() { 621 res, err := s.backend.GetRegisterValues(make(flow.RegisterIDs, s.backend.registerRequestLimit+1), s.rootBlock.Header.Height) 622 require.Nil(s.T(), res) 623 require.Equal(s.T(), codes.InvalidArgument, status.Code(err)) 624 }) 625 }