github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/access_test.go (about) 1 package access_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "testing" 8 9 "github.com/dgraph-io/badger/v2" 10 "github.com/google/go-cmp/cmp" 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/protobuf/testing/protocmp" 17 18 "github.com/onflow/crypto" 19 20 "github.com/onflow/flow-go/access" 21 "github.com/onflow/flow-go/cmd/build" 22 hsmock "github.com/onflow/flow-go/consensus/hotstuff/mocks" 23 "github.com/onflow/flow-go/consensus/hotstuff/model" 24 "github.com/onflow/flow-go/engine/access/ingestion" 25 accessmock "github.com/onflow/flow-go/engine/access/mock" 26 "github.com/onflow/flow-go/engine/access/rpc/backend" 27 connectionmock "github.com/onflow/flow-go/engine/access/rpc/connection/mock" 28 "github.com/onflow/flow-go/engine/access/subscription" 29 "github.com/onflow/flow-go/engine/common/rpc/convert" 30 "github.com/onflow/flow-go/model/flow" 31 "github.com/onflow/flow-go/model/flow/factory" 32 "github.com/onflow/flow-go/model/flow/filter" 33 "github.com/onflow/flow-go/module" 34 "github.com/onflow/flow-go/module/counters" 35 "github.com/onflow/flow-go/module/irrecoverable" 36 "github.com/onflow/flow-go/module/mempool/stdmap" 37 "github.com/onflow/flow-go/module/metrics" 38 mockmodule "github.com/onflow/flow-go/module/mock" 39 "github.com/onflow/flow-go/module/signature" 40 "github.com/onflow/flow-go/module/state_synchronization/indexer" 41 "github.com/onflow/flow-go/network/channels" 42 "github.com/onflow/flow-go/network/mocknetwork" 43 protocol "github.com/onflow/flow-go/state/protocol/mock" 44 "github.com/onflow/flow-go/storage" 45 bstorage "github.com/onflow/flow-go/storage/badger" 46 "github.com/onflow/flow-go/storage/badger/operation" 47 "github.com/onflow/flow-go/storage/util" 48 "github.com/onflow/flow-go/utils/unittest" 49 "github.com/onflow/flow-go/utils/unittest/mocks" 50 51 accessproto "github.com/onflow/flow/protobuf/go/flow/access" 52 entitiesproto "github.com/onflow/flow/protobuf/go/flow/entities" 53 execproto "github.com/onflow/flow/protobuf/go/flow/execution" 54 ) 55 56 type Suite struct { 57 suite.Suite 58 state *protocol.State 59 sealedSnapshot *protocol.Snapshot 60 finalSnapshot *protocol.Snapshot 61 epochQuery *protocol.EpochQuery 62 params *protocol.Params 63 signerIndicesDecoder *hsmock.BlockSignerDecoder 64 signerIds flow.IdentifierList 65 log zerolog.Logger 66 net *mocknetwork.Network 67 request *mockmodule.Requester 68 collClient *accessmock.AccessAPIClient 69 execClient *accessmock.ExecutionAPIClient 70 me *mockmodule.Local 71 rootBlock *flow.Header 72 sealedBlock *flow.Header 73 finalizedBlock *flow.Header 74 chainID flow.ChainID 75 metrics *metrics.NoopCollector 76 finalizedHeaderCache module.FinalizedHeaderCache 77 backend *backend.Backend 78 sporkID flow.Identifier 79 protocolVersion uint 80 } 81 82 // TestAccess tests scenarios which exercise multiple API calls using both the RPC handler and the ingest engine 83 // and using a real badger storage 84 func TestAccess(t *testing.T) { 85 suite.Run(t, new(Suite)) 86 } 87 88 func (suite *Suite) SetupTest() { 89 suite.log = zerolog.New(os.Stderr) 90 suite.net = new(mocknetwork.Network) 91 suite.state = new(protocol.State) 92 suite.finalSnapshot = new(protocol.Snapshot) 93 suite.sealedSnapshot = new(protocol.Snapshot) 94 suite.sporkID = unittest.IdentifierFixture() 95 suite.protocolVersion = uint(unittest.Uint64InRange(10, 30)) 96 97 suite.rootBlock = unittest.BlockHeaderFixture(unittest.WithHeaderHeight(0)) 98 suite.sealedBlock = suite.rootBlock 99 suite.finalizedBlock = unittest.BlockHeaderWithParentFixture(suite.sealedBlock) 100 101 suite.epochQuery = new(protocol.EpochQuery) 102 suite.state.On("Sealed").Return(suite.sealedSnapshot, nil).Maybe() 103 suite.state.On("Final").Return(suite.finalSnapshot, nil).Maybe() 104 suite.finalSnapshot.On("Epochs").Return(suite.epochQuery).Maybe() 105 suite.sealedSnapshot.On("Head").Return( 106 func() *flow.Header { 107 return suite.sealedBlock 108 }, 109 nil, 110 ).Maybe() 111 suite.finalSnapshot.On("Head").Return( 112 func() *flow.Header { 113 return suite.finalizedBlock 114 }, 115 nil, 116 ).Maybe() 117 118 suite.params = new(protocol.Params) 119 suite.params.On("FinalizedRoot").Return(suite.rootBlock, nil) 120 suite.params.On("SporkID").Return(suite.sporkID, nil) 121 suite.params.On("ProtocolVersion").Return(suite.protocolVersion, nil) 122 suite.params.On("SporkRootBlockHeight").Return(suite.rootBlock.Height, nil) 123 suite.params.On("SealedRoot").Return(suite.rootBlock, nil) 124 suite.state.On("Params").Return(suite.params).Maybe() 125 suite.collClient = new(accessmock.AccessAPIClient) 126 suite.execClient = new(accessmock.ExecutionAPIClient) 127 128 suite.request = new(mockmodule.Requester) 129 suite.request.On("EntityByID", mock.Anything, mock.Anything) 130 131 suite.me = new(mockmodule.Local) 132 133 suite.signerIds = unittest.IdentifierListFixture(4) 134 suite.signerIndicesDecoder = new(hsmock.BlockSignerDecoder) 135 suite.signerIndicesDecoder.On("DecodeSignerIDs", mock.Anything).Return(suite.signerIds, nil).Maybe() 136 137 accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) 138 suite.me. 139 On("NodeID"). 140 Return(accessIdentity.NodeID) 141 142 suite.chainID = flow.Testnet 143 suite.metrics = metrics.NewNoopCollector() 144 suite.finalizedHeaderCache = mocks.NewFinalizedHeaderCache(suite.T(), suite.state) 145 } 146 147 func (suite *Suite) RunTest( 148 f func(handler *access.Handler, db *badger.DB, all *storage.All), 149 ) { 150 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 151 all := util.StorageLayer(suite.T(), db) 152 153 var err error 154 suite.backend, err = backend.New(backend.Params{ 155 State: suite.state, 156 CollectionRPC: suite.collClient, 157 Blocks: all.Blocks, 158 Headers: all.Headers, 159 Collections: all.Collections, 160 Transactions: all.Transactions, 161 ExecutionResults: all.Results, 162 ExecutionReceipts: all.Receipts, 163 ChainID: suite.chainID, 164 AccessMetrics: suite.metrics, 165 MaxHeightRange: backend.DefaultMaxHeightRange, 166 Log: suite.log, 167 SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, 168 Communicator: backend.NewNodeCommunicator(false), 169 }) 170 require.NoError(suite.T(), err) 171 172 handler := access.NewHandler( 173 suite.backend, 174 suite.chainID.Chain(), 175 suite.finalizedHeaderCache, 176 suite.me, 177 subscription.DefaultMaxGlobalStreams, 178 access.WithBlockSignerDecoder(suite.signerIndicesDecoder), 179 ) 180 f(handler, db, all) 181 }) 182 } 183 184 func (suite *Suite) TestSendAndGetTransaction() { 185 suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.All) { 186 referenceBlock := unittest.BlockHeaderFixture() 187 transaction := unittest.TransactionFixture() 188 transaction.SetReferenceBlockID(referenceBlock.ID()) 189 190 refSnapshot := new(protocol.Snapshot) 191 192 suite.state. 193 On("AtBlockID", referenceBlock.ID()). 194 Return(refSnapshot, nil) 195 196 refSnapshot. 197 On("Head"). 198 Return(referenceBlock, nil). 199 Twice() 200 201 suite.finalSnapshot. 202 On("Head"). 203 Return(referenceBlock, nil). 204 Once() 205 206 expected := convert.TransactionToMessage(transaction.TransactionBody) 207 sendReq := &accessproto.SendTransactionRequest{ 208 Transaction: expected, 209 } 210 sendResp := accessproto.SendTransactionResponse{} 211 212 suite.collClient. 213 On("SendTransaction", mock.Anything, mock.Anything). 214 Return(&sendResp, nil). 215 Once() 216 217 // Send transaction 218 resp, err := handler.SendTransaction(context.Background(), sendReq) 219 suite.Require().NoError(err) 220 suite.Require().NotNil(resp) 221 222 id := transaction.ID() 223 getReq := &accessproto.GetTransactionRequest{ 224 Id: id[:], 225 } 226 227 // Get transaction 228 gResp, err := handler.GetTransaction(context.Background(), getReq) 229 suite.Require().NoError(err) 230 suite.Require().NotNil(gResp) 231 232 actual := gResp.GetTransaction() 233 suite.Require().Equal(expected, actual) 234 }) 235 } 236 237 func (suite *Suite) TestSendExpiredTransaction() { 238 suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.All) { 239 referenceBlock := suite.finalizedBlock 240 241 transaction := unittest.TransactionFixture() 242 transaction.SetReferenceBlockID(referenceBlock.ID()) 243 // create latest block that is past the expiry window 244 latestBlock := unittest.BlockHeaderFixture() 245 latestBlock.Height = referenceBlock.Height + flow.DefaultTransactionExpiry*2 246 247 refSnapshot := new(protocol.Snapshot) 248 249 suite.state. 250 On("AtBlockID", referenceBlock.ID()). 251 Return(refSnapshot, nil) 252 253 refSnapshot. 254 On("Head"). 255 Return(referenceBlock, nil). 256 Twice() 257 258 //Advancing final state to expire ref block 259 suite.finalizedBlock = latestBlock 260 261 req := &accessproto.SendTransactionRequest{ 262 Transaction: convert.TransactionToMessage(transaction.TransactionBody), 263 } 264 265 _, err := handler.SendTransaction(context.Background(), req) 266 suite.Require().Error(err) 267 }) 268 } 269 270 type mockCloser struct{} 271 272 func (mc *mockCloser) Close() error { return nil } 273 274 // TestSendTransactionToRandomCollectionNode tests that collection nodes are chosen from the appropriate cluster when 275 // forwarding transactions by sending two transactions bound for two different collection clusters. 276 func (suite *Suite) TestSendTransactionToRandomCollectionNode() { 277 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 278 279 // create a transaction 280 referenceBlock := unittest.BlockHeaderFixture() 281 transaction := unittest.TransactionFixture() 282 transaction.SetReferenceBlockID(referenceBlock.ID()) 283 284 // setup the state and finalSnapshot mock expectations 285 suite.state.On("AtBlockID", referenceBlock.ID()).Return(suite.finalSnapshot, nil) 286 suite.finalSnapshot.On("Head").Return(referenceBlock, nil) 287 288 // create storage 289 metrics := metrics.NewNoopCollector() 290 transactions := bstorage.NewTransactions(metrics, db) 291 collections := bstorage.NewCollections(db, transactions) 292 293 // create collection node cluster 294 count := 2 295 collNodes := unittest.IdentityListFixture(count, unittest.WithRole(flow.RoleCollection)).ToSkeleton() 296 assignments := unittest.ClusterAssignment(uint(count), collNodes) 297 clusters, err := factory.NewClusterList(assignments, collNodes) 298 suite.Require().Nil(err) 299 collNode1 := clusters[0][0] 300 collNode2 := clusters[1][0] 301 epoch := new(protocol.Epoch) 302 suite.epochQuery.On("Current").Return(epoch) 303 epoch.On("Clustering").Return(clusters, nil) 304 305 // create two transactions bound for each of the cluster 306 cluster1 := clusters[0] 307 cluster1tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster1, func(transaction *flow.TransactionBody) {}) 308 tx1 := convert.TransactionToMessage(cluster1tx) 309 sendReq1 := &accessproto.SendTransactionRequest{ 310 Transaction: tx1, 311 } 312 cluster2 := clusters[1] 313 cluster2tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster2, func(transaction *flow.TransactionBody) {}) 314 tx2 := convert.TransactionToMessage(cluster2tx) 315 sendReq2 := &accessproto.SendTransactionRequest{ 316 Transaction: tx2, 317 } 318 sendResp := accessproto.SendTransactionResponse{} 319 320 // create mock access api clients for each of the collection node expecting the correct transaction once 321 col1ApiClient := new(accessmock.AccessAPIClient) 322 col1ApiClient.On("SendTransaction", mock.Anything, sendReq1).Return(&sendResp, nil).Once() 323 col2ApiClient := new(accessmock.AccessAPIClient) 324 col2ApiClient.On("SendTransaction", mock.Anything, sendReq2).Return(&sendResp, nil).Once() 325 326 // create a mock connection factory 327 connFactory := connectionmock.NewConnectionFactory(suite.T()) 328 connFactory.On("GetAccessAPIClient", collNode1.Address, nil).Return(col1ApiClient, &mockCloser{}, nil) 329 connFactory.On("GetAccessAPIClient", collNode2.Address, nil).Return(col2ApiClient, &mockCloser{}, nil) 330 331 bnd, err := backend.New(backend.Params{State: suite.state, 332 Collections: collections, 333 Transactions: transactions, 334 ChainID: suite.chainID, 335 AccessMetrics: metrics, 336 ConnFactory: connFactory, 337 MaxHeightRange: backend.DefaultMaxHeightRange, 338 Log: suite.log, 339 SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, 340 Communicator: backend.NewNodeCommunicator(false), 341 TxErrorMessagesCacheSize: 1000, 342 }) 343 require.NoError(suite.T(), err) 344 345 handler := access.NewHandler(bnd, suite.chainID.Chain(), suite.finalizedHeaderCache, suite.me, subscription.DefaultMaxGlobalStreams) 346 347 // Send transaction 1 348 resp, err := handler.SendTransaction(context.Background(), sendReq1) 349 require.NoError(suite.T(), err) 350 require.NotNil(suite.T(), resp) 351 352 // Send transaction 2 353 resp, err = handler.SendTransaction(context.Background(), sendReq2) 354 require.NoError(suite.T(), err) 355 require.NotNil(suite.T(), resp) 356 357 // verify that a collection node in the correct cluster was contacted exactly once 358 col1ApiClient.AssertExpectations(suite.T()) 359 col2ApiClient.AssertExpectations(suite.T()) 360 epoch.AssertNumberOfCalls(suite.T(), "Clustering", 2) 361 362 // additionally do a GetTransaction request for the two transactions 363 getTx := func(tx flow.TransactionBody) { 364 id := tx.ID() 365 getReq := &accessproto.GetTransactionRequest{ 366 Id: id[:], 367 } 368 gResp, err := handler.GetTransaction(context.Background(), getReq) 369 require.NoError(suite.T(), err) 370 require.NotNil(suite.T(), gResp) 371 actual := gResp.GetTransaction() 372 expected := convert.TransactionToMessage(tx) 373 require.Equal(suite.T(), expected, actual) 374 } 375 376 getTx(cluster1tx) 377 getTx(cluster1tx) 378 }) 379 } 380 381 func (suite *Suite) TestGetBlockByIDAndHeight() { 382 suite.RunTest(func(handler *access.Handler, db *badger.DB, all *storage.All) { 383 384 // test block1 get by ID 385 block1 := unittest.BlockFixture() 386 // test block2 get by height 387 block2 := unittest.BlockFixture() 388 block2.Header.Height = 2 389 390 require.NoError(suite.T(), all.Blocks.Store(&block1)) 391 require.NoError(suite.T(), all.Blocks.Store(&block2)) 392 393 // the follower logic should update height index on the block storage when a block is finalized 394 err := db.Update(operation.IndexBlockHeight(block2.Header.Height, block2.ID())) 395 require.NoError(suite.T(), err) 396 397 assertHeaderResp := func( 398 resp *accessproto.BlockHeaderResponse, 399 err error, 400 header *flow.Header, 401 ) { 402 require.NoError(suite.T(), err) 403 require.NotNil(suite.T(), resp) 404 actual := resp.Block 405 expectedMessage, err := convert.BlockHeaderToMessage(header, suite.signerIds) 406 require.NoError(suite.T(), err) 407 require.Empty(suite.T(), cmp.Diff(expectedMessage, actual, protocmp.Transform())) 408 expectedBlockHeader, err := convert.MessageToBlockHeader(actual) 409 require.NoError(suite.T(), err) 410 require.Equal(suite.T(), expectedBlockHeader, header) 411 } 412 413 assertBlockResp := func( 414 resp *accessproto.BlockResponse, 415 err error, 416 block *flow.Block, 417 ) { 418 require.NoError(suite.T(), err) 419 require.NotNil(suite.T(), resp) 420 actual := resp.Block 421 expectedMessage, err := convert.BlockToMessage(block, suite.signerIds) 422 require.NoError(suite.T(), err) 423 require.Equal(suite.T(), expectedMessage, actual) 424 expectedBlock, err := convert.MessageToBlock(resp.Block) 425 require.NoError(suite.T(), err) 426 require.Equal(suite.T(), expectedBlock.ID(), block.ID()) 427 } 428 429 assertLightBlockResp := func( 430 resp *accessproto.BlockResponse, 431 err error, 432 block *flow.Block, 433 ) { 434 require.NoError(suite.T(), err) 435 require.NotNil(suite.T(), resp) 436 actual := resp.Block 437 expectedMessage := convert.BlockToMessageLight(block) 438 require.Equal(suite.T(), expectedMessage, actual) 439 } 440 441 suite.finalSnapshot.On("Head").Return(block1.Header, nil) 442 suite.Run("get header 1 by ID", func() { 443 // get header by ID 444 id := block1.ID() 445 req := &accessproto.GetBlockHeaderByIDRequest{ 446 Id: id[:], 447 } 448 449 resp, err := handler.GetBlockHeaderByID(context.Background(), req) 450 451 // assert it is indeed block1 452 assertHeaderResp(resp, err, block1.Header) 453 }) 454 455 suite.Run("get block 1 by ID", func() { 456 id := block1.ID() 457 // get block details by ID 458 req := &accessproto.GetBlockByIDRequest{ 459 Id: id[:], 460 FullBlockResponse: true, 461 } 462 463 resp, err := handler.GetBlockByID(context.Background(), req) 464 465 assertBlockResp(resp, err, &block1) 466 }) 467 468 suite.Run("get block light 1 by ID", func() { 469 id := block1.ID() 470 // get block details by ID 471 req := &accessproto.GetBlockByIDRequest{ 472 Id: id[:], 473 } 474 475 resp, err := handler.GetBlockByID(context.Background(), req) 476 477 assertLightBlockResp(resp, err, &block1) 478 }) 479 480 suite.Run("get header 2 by height", func() { 481 482 // get header by height 483 req := &accessproto.GetBlockHeaderByHeightRequest{ 484 Height: block2.Header.Height, 485 } 486 487 resp, err := handler.GetBlockHeaderByHeight(context.Background(), req) 488 489 assertHeaderResp(resp, err, block2.Header) 490 }) 491 492 suite.Run("get block 2 by height", func() { 493 // get block details by height 494 req := &accessproto.GetBlockByHeightRequest{ 495 Height: block2.Header.Height, 496 FullBlockResponse: true, 497 } 498 499 resp, err := handler.GetBlockByHeight(context.Background(), req) 500 501 assertBlockResp(resp, err, &block2) 502 }) 503 504 suite.Run("get block 2 by height", func() { 505 // get block details by height 506 req := &accessproto.GetBlockByHeightRequest{ 507 Height: block2.Header.Height, 508 } 509 510 resp, err := handler.GetBlockByHeight(context.Background(), req) 511 512 assertLightBlockResp(resp, err, &block2) 513 }) 514 }) 515 } 516 517 func (suite *Suite) TestGetExecutionResultByBlockID() { 518 suite.RunTest(func(handler *access.Handler, db *badger.DB, all *storage.All) { 519 520 // test block1 get by ID 521 nonexistingID := unittest.IdentifierFixture() 522 blockID := unittest.IdentifierFixture() 523 524 er := unittest.ExecutionResultFixture( 525 unittest.WithExecutionResultBlockID(blockID), 526 unittest.WithServiceEvents(3)) 527 528 require.NoError(suite.T(), all.Results.Store(er)) 529 require.NoError(suite.T(), all.Results.Index(blockID, er.ID())) 530 531 assertResp := func( 532 resp *accessproto.ExecutionResultForBlockIDResponse, 533 err error, 534 executionResult *flow.ExecutionResult, 535 ) { 536 require.NoError(suite.T(), err) 537 require.NotNil(suite.T(), resp) 538 er := resp.ExecutionResult 539 540 require.Len(suite.T(), er.Chunks, len(executionResult.Chunks)) 541 require.Len(suite.T(), er.ServiceEvents, len(executionResult.ServiceEvents)) 542 543 assert.Equal(suite.T(), executionResult.BlockID, convert.MessageToIdentifier(er.BlockId)) 544 assert.Equal(suite.T(), executionResult.PreviousResultID, convert.MessageToIdentifier(er.PreviousResultId)) 545 assert.Equal(suite.T(), executionResult.ExecutionDataID, convert.MessageToIdentifier(er.ExecutionDataId)) 546 547 for i, chunk := range executionResult.Chunks { 548 assert.Equal(suite.T(), chunk.BlockID[:], er.Chunks[i].BlockId) 549 assert.Equal(suite.T(), chunk.Index, er.Chunks[i].Index) 550 assert.Equal(suite.T(), uint32(chunk.CollectionIndex), er.Chunks[i].CollectionIndex) 551 assert.Equal(suite.T(), chunk.StartState[:], er.Chunks[i].StartState) 552 assert.Equal(suite.T(), chunk.EventCollection[:], er.Chunks[i].EventCollection) 553 assert.Equal(suite.T(), chunk.TotalComputationUsed, er.Chunks[i].TotalComputationUsed) 554 assert.Equal(suite.T(), uint32(chunk.NumberOfTransactions), er.Chunks[i].NumberOfTransactions) 555 assert.Equal(suite.T(), chunk.EndState[:], er.Chunks[i].EndState) 556 } 557 558 for i, serviceEvent := range executionResult.ServiceEvents { 559 assert.Equal(suite.T(), serviceEvent.Type.String(), er.ServiceEvents[i].Type) 560 event := serviceEvent.Event 561 marshalledEvent, err := json.Marshal(event) 562 require.NoError(suite.T(), err) 563 assert.Equal(suite.T(), marshalledEvent, er.ServiceEvents[i].Payload) 564 } 565 parsedExecResult, err := convert.MessageToExecutionResult(resp.ExecutionResult) 566 require.NoError(suite.T(), err) 567 assert.Equal(suite.T(), parsedExecResult.ID(), executionResult.ID()) 568 } 569 570 suite.Run("nonexisting block", func() { 571 req := &accessproto.GetExecutionResultForBlockIDRequest{ 572 BlockId: nonexistingID[:], 573 } 574 575 resp, err := handler.GetExecutionResultForBlockID(context.Background(), req) 576 577 require.Error(suite.T(), err) 578 require.Nil(suite.T(), resp) 579 }) 580 581 suite.Run("some block", func() { 582 // get header by ID 583 req := &accessproto.GetExecutionResultForBlockIDRequest{ 584 BlockId: blockID[:], 585 } 586 587 resp, err := handler.GetExecutionResultForBlockID(context.Background(), req) 588 589 require.NoError(suite.T(), err) 590 591 assertResp(resp, err, er) 592 }) 593 594 }) 595 } 596 597 // TestGetSealedTransaction tests that transactions status of transaction that belongs to a sealed block 598 // is reported as sealed 599 func (suite *Suite) TestGetSealedTransaction() { 600 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 601 all := util.StorageLayer(suite.T(), db) 602 results := bstorage.NewExecutionResults(suite.metrics, db) 603 receipts := bstorage.NewExecutionReceipts(suite.metrics, db, results, bstorage.DefaultCacheSize) 604 enIdentities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 605 enNodeIDs := enIdentities.NodeIDs() 606 607 // create block -> collection -> transactions 608 block, collection := suite.createChain() 609 610 // setup mocks 611 conduit := new(mocknetwork.Conduit) 612 suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil). 613 Once() 614 suite.request.On("Request", mock.Anything, mock.Anything).Return() 615 616 colIdentities := unittest.IdentityListFixture(1, unittest.WithRole(flow.RoleCollection)) 617 allIdentities := append(colIdentities, enIdentities...) 618 619 suite.finalSnapshot.On("Identities", mock.Anything).Return(allIdentities, nil).Once() 620 621 exeEventResp := execproto.GetTransactionResultResponse{ 622 Events: nil, 623 } 624 625 // generate receipts 626 executionReceipts := unittest.ReceiptsForBlockFixture(block, enNodeIDs) 627 628 // assume execution node returns an empty list of events 629 suite.execClient.On("GetTransactionResult", mock.Anything, mock.Anything).Return(&exeEventResp, nil) 630 631 // create a mock connection factory 632 connFactory := connectionmock.NewConnectionFactory(suite.T()) 633 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 634 635 // initialize storage 636 metrics := metrics.NewNoopCollector() 637 transactions := bstorage.NewTransactions(metrics, db) 638 collections := bstorage.NewCollections(db, transactions) 639 collectionsToMarkFinalized, err := stdmap.NewTimes(100) 640 require.NoError(suite.T(), err) 641 collectionsToMarkExecuted, err := stdmap.NewTimes(100) 642 require.NoError(suite.T(), err) 643 blocksToMarkExecuted, err := stdmap.NewTimes(100) 644 require.NoError(suite.T(), err) 645 646 bnd, err := backend.New(backend.Params{State: suite.state, 647 CollectionRPC: suite.collClient, 648 Blocks: all.Blocks, 649 Headers: all.Headers, 650 Collections: collections, 651 Transactions: transactions, 652 ExecutionReceipts: receipts, 653 ExecutionResults: results, 654 ChainID: suite.chainID, 655 AccessMetrics: suite.metrics, 656 ConnFactory: connFactory, 657 MaxHeightRange: backend.DefaultMaxHeightRange, 658 PreferredExecutionNodeIDs: enNodeIDs.Strings(), 659 Log: suite.log, 660 SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, 661 Communicator: backend.NewNodeCommunicator(false), 662 TxErrorMessagesCacheSize: 1000, 663 TxResultQueryMode: backend.IndexQueryModeExecutionNodesOnly, 664 }) 665 require.NoError(suite.T(), err) 666 667 handler := access.NewHandler(bnd, suite.chainID.Chain(), suite.finalizedHeaderCache, suite.me, subscription.DefaultMaxGlobalStreams) 668 669 collectionExecutedMetric, err := indexer.NewCollectionExecutedMetricImpl( 670 suite.log, 671 metrics, 672 collectionsToMarkFinalized, 673 collectionsToMarkExecuted, 674 blocksToMarkExecuted, 675 collections, 676 all.Blocks, 677 ) 678 require.NoError(suite.T(), err) 679 680 lastFullBlockHeight, err := counters.NewPersistentStrictMonotonicCounter( 681 bstorage.NewConsumerProgress(db, module.ConsumeProgressLastFullBlockHeight), 682 suite.rootBlock.Height, 683 ) 684 require.NoError(suite.T(), err) 685 686 // create the ingest engine 687 processedHeight := bstorage.NewConsumerProgress(db, module.ConsumeProgressIngestionEngineBlockHeight) 688 689 ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, all.Blocks, all.Headers, collections, 690 transactions, results, receipts, collectionExecutedMetric, processedHeight, lastFullBlockHeight) 691 require.NoError(suite.T(), err) 692 693 // 1. Assume that follower engine updated the block storage and the protocol state. The block is reported as sealed 694 err = all.Blocks.Store(block) 695 require.NoError(suite.T(), err) 696 697 err = db.Update(operation.IndexBlockHeight(block.Header.Height, block.ID())) 698 require.NoError(suite.T(), err) 699 700 suite.sealedBlock = block.Header 701 702 background, cancel := context.WithCancel(context.Background()) 703 defer cancel() 704 705 ctx, _ := irrecoverable.WithSignaler(background) 706 ingestEng.Start(ctx) 707 <-ingestEng.Ready() 708 709 // 2. Ingest engine was notified by the follower engine about a new block. 710 // Follower engine --> Ingest engine 711 mb := &model.Block{ 712 BlockID: block.ID(), 713 } 714 ingestEng.OnFinalizedBlock(mb) 715 716 // 3. Request engine is used to request missing collection 717 suite.request.On("EntityByID", collection.ID(), mock.Anything).Return() 718 // 4. Indexer HandleCollection receives the requested collection and all the execution receipts 719 err = indexer.HandleCollection(collection, collections, transactions, suite.log, collectionExecutedMetric) 720 require.NoError(suite.T(), err) 721 722 for _, r := range executionReceipts { 723 err = ingestEng.Process(channels.ReceiveReceipts, enNodeIDs[0], r) 724 require.NoError(suite.T(), err) 725 } 726 727 // 5. Client requests a transaction 728 tx := collection.Transactions[0] 729 txID := tx.ID() 730 getReq := &accessproto.GetTransactionRequest{ 731 Id: txID[:], 732 } 733 gResp, err := handler.GetTransactionResult(context.Background(), getReq) 734 require.NoError(suite.T(), err) 735 // assert that the transaction is reported as Sealed 736 require.Equal(suite.T(), entitiesproto.TransactionStatus_SEALED, gResp.GetStatus()) 737 }) 738 } 739 740 // TestGetTransactionResult tests different approaches to using the GetTransactionResult query, including using 741 // transaction ID, block ID, and collection ID. 742 func (suite *Suite) TestGetTransactionResult() { 743 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 744 all := util.StorageLayer(suite.T(), db) 745 results := bstorage.NewExecutionResults(suite.metrics, db) 746 receipts := bstorage.NewExecutionReceipts(suite.metrics, db, results, bstorage.DefaultCacheSize) 747 748 originID := unittest.IdentifierFixture() 749 750 *suite.state = protocol.State{} 751 752 // create block -> collection -> transactions 753 block, collection := suite.createChain() 754 blockNegative, collectionNegative := suite.createChain() 755 blockId := block.ID() 756 blockNegativeId := blockNegative.ID() 757 758 finalSnapshot := new(protocol.Snapshot) 759 finalSnapshot.On("Head").Return(suite.finalizedBlock, nil) 760 761 suite.state.On("Params").Return(suite.params) 762 suite.state.On("Final").Return(finalSnapshot) 763 suite.state.On("Sealed").Return(suite.sealedSnapshot) 764 sealedBlock := unittest.GenesisFixture().Header 765 // specifically for this test we will consider that sealed block is far behind finalized, so we get EXECUTED status 766 suite.sealedSnapshot.On("Head").Return(sealedBlock, nil) 767 768 err := all.Blocks.Store(block) 769 require.NoError(suite.T(), err) 770 err = all.Blocks.Store(blockNegative) 771 require.NoError(suite.T(), err) 772 773 suite.state.On("AtBlockID", blockId).Return(suite.sealedSnapshot) 774 775 colIdentities := unittest.IdentityListFixture(1, unittest.WithRole(flow.RoleCollection)) 776 enIdentities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 777 778 enNodeIDs := enIdentities.NodeIDs() 779 allIdentities := append(colIdentities, enIdentities...) 780 finalSnapshot.On("Identities", mock.Anything).Return(allIdentities, nil) 781 782 // assume execution node returns an empty list of events 783 suite.execClient.On("GetTransactionResult", mock.Anything, mock.Anything).Return(&execproto.GetTransactionResultResponse{ 784 Events: nil, 785 }, nil) 786 787 // setup mocks 788 conduit := new(mocknetwork.Conduit) 789 suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil).Once() 790 suite.request.On("Request", mock.Anything, mock.Anything).Return() 791 792 // create a mock connection factory 793 connFactory := connectionmock.NewConnectionFactory(suite.T()) 794 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 795 796 // initialize storage 797 metrics := metrics.NewNoopCollector() 798 transactions := bstorage.NewTransactions(metrics, db) 799 collections := bstorage.NewCollections(db, transactions) 800 err = collections.Store(collectionNegative) 801 require.NoError(suite.T(), err) 802 collectionsToMarkFinalized, err := stdmap.NewTimes(100) 803 require.NoError(suite.T(), err) 804 collectionsToMarkExecuted, err := stdmap.NewTimes(100) 805 require.NoError(suite.T(), err) 806 blocksToMarkExecuted, err := stdmap.NewTimes(100) 807 require.NoError(suite.T(), err) 808 809 bnd, err := backend.New(backend.Params{State: suite.state, 810 CollectionRPC: suite.collClient, 811 Blocks: all.Blocks, 812 Headers: all.Headers, 813 Collections: collections, 814 Transactions: transactions, 815 ExecutionReceipts: receipts, 816 ExecutionResults: results, 817 ChainID: suite.chainID, 818 AccessMetrics: suite.metrics, 819 ConnFactory: connFactory, 820 MaxHeightRange: backend.DefaultMaxHeightRange, 821 PreferredExecutionNodeIDs: enNodeIDs.Strings(), 822 Log: suite.log, 823 SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, 824 Communicator: backend.NewNodeCommunicator(false), 825 TxErrorMessagesCacheSize: 1000, 826 TxResultQueryMode: backend.IndexQueryModeExecutionNodesOnly, 827 }) 828 require.NoError(suite.T(), err) 829 830 handler := access.NewHandler(bnd, suite.chainID.Chain(), suite.finalizedHeaderCache, suite.me, subscription.DefaultMaxGlobalStreams) 831 832 collectionExecutedMetric, err := indexer.NewCollectionExecutedMetricImpl( 833 suite.log, 834 metrics, 835 collectionsToMarkFinalized, 836 collectionsToMarkExecuted, 837 blocksToMarkExecuted, 838 collections, 839 all.Blocks, 840 ) 841 require.NoError(suite.T(), err) 842 843 processedHeight := bstorage.NewConsumerProgress(db, module.ConsumeProgressIngestionEngineBlockHeight) 844 lastFullBlockHeight, err := counters.NewPersistentStrictMonotonicCounter( 845 bstorage.NewConsumerProgress(db, module.ConsumeProgressLastFullBlockHeight), 846 suite.rootBlock.Height, 847 ) 848 require.NoError(suite.T(), err) 849 850 // create the ingest engine 851 ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, all.Blocks, all.Headers, collections, 852 transactions, results, receipts, collectionExecutedMetric, processedHeight, lastFullBlockHeight) 853 require.NoError(suite.T(), err) 854 855 background, cancel := context.WithCancel(context.Background()) 856 defer cancel() 857 858 ctx := irrecoverable.NewMockSignalerContext(suite.T(), background) 859 ingestEng.Start(ctx) 860 <-ingestEng.Ready() 861 862 processExecutionReceipts := func( 863 block *flow.Block, 864 collection *flow.Collection, 865 enNodeIDs flow.IdentifierList, 866 originID flow.Identifier, 867 ingestEng *ingestion.Engine, 868 ) { 869 executionReceipts := unittest.ReceiptsForBlockFixture(block, enNodeIDs) 870 // Ingest engine was notified by the follower engine about a new block. 871 // Follower engine --> Ingest engine 872 mb := &model.Block{ 873 BlockID: block.ID(), 874 } 875 ingestEng.OnFinalizedBlock(mb) 876 877 // Indexer HandleCollection receives the requested collection and all the execution receipts 878 err = indexer.HandleCollection(collection, collections, transactions, suite.log, collectionExecutedMetric) 879 require.NoError(suite.T(), err) 880 881 for _, r := range executionReceipts { 882 err = ingestEng.Process(channels.ReceiveReceipts, enNodeIDs[0], r) 883 require.NoError(suite.T(), err) 884 } 885 } 886 err = db.Update(operation.IndexBlockHeight(block.Header.Height, block.ID())) 887 require.NoError(suite.T(), err) 888 finalSnapshot.On("Head").Return(block.Header, nil) 889 890 processExecutionReceipts(block, collection, enNodeIDs, originID, ingestEng) 891 processExecutionReceipts(blockNegative, collectionNegative, enNodeIDs, originID, ingestEng) 892 893 txId := collection.Transactions[0].ID() 894 collectionId := collection.ID() 895 txIdNegative := collectionNegative.Transactions[0].ID() 896 collectionIdNegative := collectionNegative.ID() 897 898 assertTransactionResult := func( 899 resp *accessproto.TransactionResultResponse, 900 err error, 901 ) { 902 require.NoError(suite.T(), err) 903 actualTxId := flow.HashToID(resp.TransactionId) 904 require.Equal(suite.T(), txId, actualTxId) 905 actualBlockId := flow.HashToID(resp.BlockId) 906 require.Equal(suite.T(), blockId, actualBlockId) 907 actualCollectionId := flow.HashToID(resp.CollectionId) 908 require.Equal(suite.T(), collectionId, actualCollectionId) 909 } 910 911 // Test behaviour with transactionId provided 912 // POSITIVE 913 suite.Run("Get transaction result by transaction ID", func() { 914 getReq := &accessproto.GetTransactionRequest{ 915 Id: txId[:], 916 } 917 resp, err := handler.GetTransactionResult(context.Background(), getReq) 918 assertTransactionResult(resp, err) 919 }) 920 921 // Test behaviour with blockId provided 922 suite.Run("Get transaction result by block ID", func() { 923 getReq := &accessproto.GetTransactionRequest{ 924 Id: txId[:], 925 BlockId: blockId[:], 926 } 927 resp, err := handler.GetTransactionResult(context.Background(), getReq) 928 assertTransactionResult(resp, err) 929 }) 930 931 suite.Run("Get transaction result with wrong transaction ID and correct block ID", func() { 932 getReq := &accessproto.GetTransactionRequest{ 933 Id: txIdNegative[:], 934 BlockId: blockId[:], 935 } 936 resp, err := handler.GetTransactionResult(context.Background(), getReq) 937 require.Error(suite.T(), err) 938 require.Nil(suite.T(), resp) 939 }) 940 941 suite.Run("Get transaction result with wrong block ID and correct transaction ID", func() { 942 getReq := &accessproto.GetTransactionRequest{ 943 Id: txId[:], 944 BlockId: blockNegativeId[:], 945 } 946 resp, err := handler.GetTransactionResult(context.Background(), getReq) 947 require.Error(suite.T(), err) 948 require.Nil(suite.T(), resp) 949 }) 950 951 // Test behaviour with collectionId provided 952 suite.Run("Get transaction result by collection ID", func() { 953 getReq := &accessproto.GetTransactionRequest{ 954 Id: txId[:], 955 CollectionId: collectionId[:], 956 } 957 resp, err := handler.GetTransactionResult(context.Background(), getReq) 958 assertTransactionResult(resp, err) 959 }) 960 961 suite.Run("Get transaction result with wrong collection ID but correct transaction ID", func() { 962 getReq := &accessproto.GetTransactionRequest{ 963 Id: txId[:], 964 CollectionId: collectionIdNegative[:], 965 } 966 resp, err := handler.GetTransactionResult(context.Background(), getReq) 967 require.Error(suite.T(), err) 968 require.Nil(suite.T(), resp) 969 }) 970 971 suite.Run("Get transaction result with wrong transaction ID and correct collection ID", func() { 972 getReq := &accessproto.GetTransactionRequest{ 973 Id: txIdNegative[:], 974 CollectionId: collectionId[:], 975 } 976 resp, err := handler.GetTransactionResult(context.Background(), getReq) 977 require.Error(suite.T(), err) 978 require.Nil(suite.T(), resp) 979 }) 980 981 // Test behaviour with blockId and collectionId provided 982 suite.Run("Get transaction result by block ID and collection ID", func() { 983 getReq := &accessproto.GetTransactionRequest{ 984 Id: txId[:], 985 BlockId: blockId[:], 986 CollectionId: collectionId[:], 987 } 988 resp, err := handler.GetTransactionResult(context.Background(), getReq) 989 assertTransactionResult(resp, err) 990 }) 991 992 suite.Run("Get transaction result by block ID with wrong collection ID", func() { 993 getReq := &accessproto.GetTransactionRequest{ 994 Id: txId[:], 995 BlockId: blockId[:], 996 CollectionId: collectionIdNegative[:], 997 } 998 resp, err := handler.GetTransactionResult(context.Background(), getReq) 999 require.Error(suite.T(), err) 1000 require.Nil(suite.T(), resp) 1001 }) 1002 }) 1003 } 1004 1005 // TestExecuteScript tests the three execute Script related calls to make sure that the execution api is called with 1006 // the correct block id 1007 func (suite *Suite) TestExecuteScript() { 1008 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 1009 all := util.StorageLayer(suite.T(), db) 1010 transactions := bstorage.NewTransactions(suite.metrics, db) 1011 collections := bstorage.NewCollections(db, transactions) 1012 results := bstorage.NewExecutionResults(suite.metrics, db) 1013 receipts := bstorage.NewExecutionReceipts(suite.metrics, db, results, bstorage.DefaultCacheSize) 1014 1015 identities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 1016 suite.sealedSnapshot.On("Identities", mock.Anything).Return(identities, nil) 1017 suite.finalSnapshot.On("Identities", mock.Anything).Return(identities, nil) 1018 1019 // create a mock connection factory 1020 connFactory := connectionmock.NewConnectionFactory(suite.T()) 1021 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 1022 1023 var err error 1024 suite.backend, err = backend.New(backend.Params{ 1025 State: suite.state, 1026 CollectionRPC: suite.collClient, 1027 Blocks: all.Blocks, 1028 Headers: all.Headers, 1029 Collections: collections, 1030 Transactions: transactions, 1031 ExecutionReceipts: receipts, 1032 ExecutionResults: results, 1033 ChainID: suite.chainID, 1034 AccessMetrics: suite.metrics, 1035 ConnFactory: connFactory, 1036 MaxHeightRange: backend.DefaultMaxHeightRange, 1037 FixedExecutionNodeIDs: (identities.NodeIDs()).Strings(), 1038 Log: suite.log, 1039 SnapshotHistoryLimit: backend.DefaultSnapshotHistoryLimit, 1040 Communicator: backend.NewNodeCommunicator(false), 1041 ScriptExecutionMode: backend.IndexQueryModeExecutionNodesOnly, 1042 TxErrorMessagesCacheSize: 1000, 1043 TxResultQueryMode: backend.IndexQueryModeExecutionNodesOnly, 1044 }) 1045 require.NoError(suite.T(), err) 1046 1047 handler := access.NewHandler(suite.backend, suite.chainID.Chain(), suite.finalizedHeaderCache, suite.me, subscription.DefaultMaxGlobalStreams) 1048 1049 // initialize metrics related storage 1050 metrics := metrics.NewNoopCollector() 1051 collectionsToMarkFinalized, err := stdmap.NewTimes(100) 1052 require.NoError(suite.T(), err) 1053 collectionsToMarkExecuted, err := stdmap.NewTimes(100) 1054 require.NoError(suite.T(), err) 1055 blocksToMarkExecuted, err := stdmap.NewTimes(100) 1056 require.NoError(suite.T(), err) 1057 1058 collectionExecutedMetric, err := indexer.NewCollectionExecutedMetricImpl( 1059 suite.log, 1060 metrics, 1061 collectionsToMarkFinalized, 1062 collectionsToMarkExecuted, 1063 blocksToMarkExecuted, 1064 collections, 1065 all.Blocks, 1066 ) 1067 require.NoError(suite.T(), err) 1068 1069 conduit := new(mocknetwork.Conduit) 1070 suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil). 1071 Once() 1072 1073 processedHeight := bstorage.NewConsumerProgress(db, module.ConsumeProgressIngestionEngineBlockHeight) 1074 lastFullBlockHeight, err := counters.NewPersistentStrictMonotonicCounter( 1075 bstorage.NewConsumerProgress(db, module.ConsumeProgressLastFullBlockHeight), 1076 suite.rootBlock.Height, 1077 ) 1078 require.NoError(suite.T(), err) 1079 1080 // create the ingest engine 1081 ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, all.Blocks, all.Headers, collections, 1082 transactions, results, receipts, collectionExecutedMetric, processedHeight, lastFullBlockHeight) 1083 require.NoError(suite.T(), err) 1084 1085 // create another block as a predecessor of the block created earlier 1086 prevBlock := unittest.BlockWithParentFixture(suite.finalizedBlock) 1087 1088 // create a block and a seal pointing to that block 1089 lastBlock := unittest.BlockWithParentFixture(prevBlock.Header) 1090 err = all.Blocks.Store(lastBlock) 1091 require.NoError(suite.T(), err) 1092 err = db.Update(operation.IndexBlockHeight(lastBlock.Header.Height, lastBlock.ID())) 1093 require.NoError(suite.T(), err) 1094 //update latest sealed block 1095 suite.sealedBlock = lastBlock.Header 1096 // create execution receipts for each of the execution node and the last block 1097 executionReceipts := unittest.ReceiptsForBlockFixture(lastBlock, identities.NodeIDs()) 1098 // notify the ingest engine about the receipts 1099 for _, r := range executionReceipts { 1100 err = ingestEng.Process(channels.ReceiveReceipts, unittest.IdentifierFixture(), r) 1101 require.NoError(suite.T(), err) 1102 } 1103 1104 err = all.Blocks.Store(prevBlock) 1105 require.NoError(suite.T(), err) 1106 err = db.Update(operation.IndexBlockHeight(prevBlock.Header.Height, prevBlock.ID())) 1107 require.NoError(suite.T(), err) 1108 1109 // create execution receipts for each of the execution node and the previous block 1110 executionReceipts = unittest.ReceiptsForBlockFixture(prevBlock, identities.NodeIDs()) 1111 // notify the ingest engine about the receipts 1112 for _, r := range executionReceipts { 1113 err = ingestEng.Process(channels.ReceiveReceipts, unittest.IdentifierFixture(), r) 1114 require.NoError(suite.T(), err) 1115 } 1116 1117 ctx := context.Background() 1118 1119 script := []byte("dummy script") 1120 1121 // setupExecClientMock sets up the mock the execution client and returns the access response to expect 1122 setupExecClientMock := func(blockID flow.Identifier) *accessproto.ExecuteScriptResponse { 1123 id := blockID[:] 1124 executionReq := execproto.ExecuteScriptAtBlockIDRequest{ 1125 BlockId: id, 1126 Script: script, 1127 } 1128 executionResp := execproto.ExecuteScriptAtBlockIDResponse{ 1129 Value: []byte{9, 10, 11}, 1130 } 1131 1132 suite.execClient.On("ExecuteScriptAtBlockID", ctx, &executionReq).Return(&executionResp, nil).Once() 1133 1134 finalizedHeader := suite.finalizedHeaderCache.Get() 1135 finalizedHeaderId := finalizedHeader.ID() 1136 nodeId := suite.me.NodeID() 1137 1138 expectedResp := accessproto.ExecuteScriptResponse{ 1139 Value: executionResp.GetValue(), 1140 Metadata: &entitiesproto.Metadata{ 1141 LatestFinalizedBlockId: finalizedHeaderId[:], 1142 LatestFinalizedHeight: finalizedHeader.Height, 1143 NodeId: nodeId[:], 1144 }, 1145 } 1146 return &expectedResp 1147 } 1148 1149 assertResult := func(err error, expected interface{}, actual interface{}) { 1150 suite.Require().NoError(err) 1151 suite.Require().Equal(expected, actual) 1152 suite.execClient.AssertExpectations(suite.T()) 1153 } 1154 1155 suite.Run("execute script at latest block", func() { 1156 suite.state. 1157 On("AtBlockID", lastBlock.ID()). 1158 Return(suite.sealedSnapshot, nil) 1159 1160 expectedResp := setupExecClientMock(lastBlock.ID()) 1161 req := accessproto.ExecuteScriptAtLatestBlockRequest{ 1162 Script: script, 1163 } 1164 actualResp, err := handler.ExecuteScriptAtLatestBlock(ctx, &req) 1165 assertResult(err, expectedResp, actualResp) 1166 }) 1167 1168 suite.Run("execute script at block id", func() { 1169 suite.state. 1170 On("AtBlockID", prevBlock.ID()). 1171 Return(suite.sealedSnapshot, nil) 1172 1173 expectedResp := setupExecClientMock(prevBlock.ID()) 1174 id := prevBlock.ID() 1175 req := accessproto.ExecuteScriptAtBlockIDRequest{ 1176 BlockId: id[:], 1177 Script: script, 1178 } 1179 actualResp, err := handler.ExecuteScriptAtBlockID(ctx, &req) 1180 assertResult(err, expectedResp, actualResp) 1181 }) 1182 1183 suite.Run("execute script at block height", func() { 1184 suite.state. 1185 On("AtBlockID", prevBlock.ID()). 1186 Return(suite.sealedSnapshot, nil) 1187 1188 expectedResp := setupExecClientMock(prevBlock.ID()) 1189 req := accessproto.ExecuteScriptAtBlockHeightRequest{ 1190 BlockHeight: prevBlock.Header.Height, 1191 Script: script, 1192 } 1193 actualResp, err := handler.ExecuteScriptAtBlockHeight(ctx, &req) 1194 assertResult(err, expectedResp, actualResp) 1195 }) 1196 }) 1197 } 1198 1199 // TestAPICallNodeVersionInfo tests the GetNodeVersionInfo query and check response returns correct node version 1200 // information 1201 func (suite *Suite) TestAPICallNodeVersionInfo() { 1202 suite.RunTest(func(handler *access.Handler, db *badger.DB, all *storage.All) { 1203 req := &accessproto.GetNodeVersionInfoRequest{} 1204 resp, err := handler.GetNodeVersionInfo(context.Background(), req) 1205 require.NoError(suite.T(), err) 1206 require.NotNil(suite.T(), resp) 1207 1208 respNodeVersionInfo := resp.Info 1209 suite.Require().Equal(respNodeVersionInfo, &entitiesproto.NodeVersionInfo{ 1210 Semver: build.Version(), 1211 Commit: build.Commit(), 1212 SporkId: suite.sporkID[:], 1213 ProtocolVersion: uint64(suite.protocolVersion), 1214 }) 1215 }) 1216 } 1217 1218 // TestLastFinalizedBlockHeightResult tests on example of the GetBlockHeaderByID function that the LastFinalizedBlock 1219 // field in the response matches the finalized header from cache. It also tests that the LastFinalizedBlock field is 1220 // updated correctly when a block with a greater height is finalized. 1221 func (suite *Suite) TestLastFinalizedBlockHeightResult() { 1222 suite.RunTest(func(handler *access.Handler, db *badger.DB, all *storage.All) { 1223 block := unittest.BlockWithParentFixture(suite.finalizedBlock) 1224 newFinalizedBlock := unittest.BlockWithParentFixture(block.Header) 1225 1226 // store new block 1227 require.NoError(suite.T(), all.Blocks.Store(block)) 1228 1229 assertFinalizedBlockHeader := func(resp *accessproto.BlockHeaderResponse, err error) { 1230 require.NoError(suite.T(), err) 1231 require.NotNil(suite.T(), resp) 1232 1233 finalizedHeaderId := suite.finalizedBlock.ID() 1234 nodeId := suite.me.NodeID() 1235 1236 require.Equal(suite.T(), &entitiesproto.Metadata{ 1237 LatestFinalizedBlockId: finalizedHeaderId[:], 1238 LatestFinalizedHeight: suite.finalizedBlock.Height, 1239 NodeId: nodeId[:], 1240 }, resp.Metadata) 1241 } 1242 1243 id := block.ID() 1244 req := &accessproto.GetBlockHeaderByIDRequest{ 1245 Id: id[:], 1246 } 1247 1248 resp, err := handler.GetBlockHeaderByID(context.Background(), req) 1249 assertFinalizedBlockHeader(resp, err) 1250 1251 suite.finalizedBlock = newFinalizedBlock.Header 1252 1253 resp, err = handler.GetBlockHeaderByID(context.Background(), req) 1254 assertFinalizedBlockHeader(resp, err) 1255 }) 1256 } 1257 1258 func (suite *Suite) createChain() (*flow.Block, *flow.Collection) { 1259 collection := unittest.CollectionFixture(10) 1260 refBlockID := unittest.IdentifierFixture() 1261 // prepare cluster committee members 1262 clusterCommittee := unittest.IdentityListFixture(32 * 4).Filter(filter.HasRole[flow.Identity](flow.RoleCollection)) 1263 // guarantee signers must be cluster committee members, so that access will fetch collection from 1264 // the signers that are specified by guarantee.SignerIndices 1265 indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) 1266 require.NoError(suite.T(), err) 1267 guarantee := &flow.CollectionGuarantee{ 1268 CollectionID: collection.ID(), 1269 Signature: crypto.Signature([]byte("signature A")), 1270 ReferenceBlockID: refBlockID, 1271 SignerIndices: indices, 1272 } 1273 block := unittest.BlockWithParentFixture(suite.finalizedBlock) 1274 block.SetPayload(unittest.PayloadFixture(unittest.WithGuarantees(guarantee))) 1275 1276 cluster := new(protocol.Cluster) 1277 cluster.On("Members").Return(clusterCommittee.ToSkeleton(), nil) 1278 epoch := new(protocol.Epoch) 1279 epoch.On("ClusterByChainID", mock.Anything).Return(cluster, nil) 1280 epochs := new(protocol.EpochQuery) 1281 epochs.On("Current").Return(epoch) 1282 snap := new(protocol.Snapshot) 1283 snap.On("Epochs").Return(epochs).Maybe() 1284 snap.On("Params").Return(suite.params).Maybe() 1285 snap.On("Head").Return(block.Header, nil).Maybe() 1286 1287 suite.state.On("AtBlockID", refBlockID).Return(snap) 1288 1289 return block, &collection 1290 }