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