github.com/koko1123/flow-go-1@v0.29.6/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/v3"
    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/koko1123/flow-go-1/access"
    22  	hsmock "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    23  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    24  	"github.com/onflow/flow-go/crypto"
    25  	"github.com/koko1123/flow-go-1/engine/access/ingestion"
    26  	accessmock "github.com/koko1123/flow-go-1/engine/access/mock"
    27  	"github.com/koko1123/flow-go-1/engine/access/rpc"
    28  	"github.com/koko1123/flow-go-1/engine/access/rpc/backend"
    29  	factorymock "github.com/koko1123/flow-go-1/engine/access/rpc/backend/mock"
    30  	"github.com/koko1123/flow-go-1/engine/common/rpc/convert"
    31  	"github.com/koko1123/flow-go-1/model/flow"
    32  	"github.com/koko1123/flow-go-1/model/flow/factory"
    33  	"github.com/koko1123/flow-go-1/model/flow/filter"
    34  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    35  	"github.com/koko1123/flow-go-1/module/mempool/stdmap"
    36  	"github.com/koko1123/flow-go-1/module/metrics"
    37  	module "github.com/koko1123/flow-go-1/module/mock"
    38  	"github.com/koko1123/flow-go-1/module/signature"
    39  	"github.com/koko1123/flow-go-1/network/channels"
    40  	"github.com/koko1123/flow-go-1/network/mocknetwork"
    41  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    42  	storage "github.com/koko1123/flow-go-1/storage/badger"
    43  	"github.com/koko1123/flow-go-1/storage/badger/operation"
    44  	"github.com/koko1123/flow-go-1/storage/util"
    45  	"github.com/koko1123/flow-go-1/utils/unittest"
    46  )
    47  
    48  type Suite struct {
    49  	suite.Suite
    50  	state                *protocol.State
    51  	snapshot             *protocol.Snapshot
    52  	epochQuery           *protocol.EpochQuery
    53  	signerIndicesDecoder *hsmock.BlockSignerDecoder
    54  	signerIds            flow.IdentifierList
    55  	log                  zerolog.Logger
    56  	net                  *mocknetwork.Network
    57  	request              *module.Requester
    58  	collClient           *accessmock.AccessAPIClient
    59  	execClient           *accessmock.ExecutionAPIClient
    60  	me                   *module.Local
    61  	chainID              flow.ChainID
    62  	metrics              *metrics.NoopCollector
    63  	backend              *backend.Backend
    64  }
    65  
    66  // TestAccess tests scenarios which exercise multiple API calls using both the RPC handler and the ingest engine
    67  // and using a real badger storage
    68  func TestAccess(t *testing.T) {
    69  	suite.Run(t, new(Suite))
    70  }
    71  
    72  func (suite *Suite) SetupTest() {
    73  	suite.log = zerolog.New(os.Stderr)
    74  	suite.net = new(mocknetwork.Network)
    75  	suite.state = new(protocol.State)
    76  	suite.snapshot = new(protocol.Snapshot)
    77  
    78  	suite.epochQuery = new(protocol.EpochQuery)
    79  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
    80  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
    81  	suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe()
    82  
    83  	header := unittest.BlockHeaderFixture()
    84  	params := new(protocol.Params)
    85  	params.On("Root").Return(header, nil)
    86  	suite.state.On("Params").Return(params).Maybe()
    87  	suite.collClient = new(accessmock.AccessAPIClient)
    88  	suite.execClient = new(accessmock.ExecutionAPIClient)
    89  
    90  	suite.request = new(module.Requester)
    91  	suite.request.On("EntityByID", mock.Anything, mock.Anything)
    92  
    93  	suite.me = new(module.Local)
    94  
    95  	suite.signerIds = unittest.IdentifierListFixture(4)
    96  	suite.signerIndicesDecoder = new(hsmock.BlockSignerDecoder)
    97  	suite.signerIndicesDecoder.On("DecodeSignerIDs", mock.Anything).Return(suite.signerIds, nil).Maybe()
    98  
    99  	accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess))
   100  	suite.me.
   101  		On("NodeID").
   102  		Return(accessIdentity.NodeID)
   103  
   104  	suite.chainID = flow.Testnet
   105  	suite.metrics = metrics.NewNoopCollector()
   106  }
   107  
   108  func (suite *Suite) RunTest(
   109  	f func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, headers *storage.Headers, results *storage.ExecutionResults),
   110  ) {
   111  	unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) {
   112  		headers, _, _, _, _, blocks, _, _, _, results := util.StorageLayer(suite.T(), db)
   113  		transactions := storage.NewTransactions(suite.metrics, db)
   114  		collections := storage.NewCollections(db, transactions)
   115  		receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize)
   116  
   117  		suite.backend = backend.New(suite.state,
   118  			suite.collClient,
   119  			nil,
   120  			blocks,
   121  			headers,
   122  			collections,
   123  			transactions,
   124  			receipts,
   125  			results,
   126  			suite.chainID,
   127  			suite.metrics,
   128  			nil,
   129  			false,
   130  			backend.DefaultMaxHeightRange,
   131  			nil,
   132  			nil,
   133  			suite.log,
   134  			backend.DefaultSnapshotHistoryLimit,
   135  		)
   136  
   137  		handler := access.NewHandler(suite.backend, suite.chainID.Chain(), access.WithBlockSignerDecoder(suite.signerIndicesDecoder))
   138  		f(handler, db, blocks, headers, results)
   139  	})
   140  }
   141  
   142  func (suite *Suite) TestSendAndGetTransaction() {
   143  	suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) {
   144  		referenceBlock := unittest.BlockHeaderFixture()
   145  		transaction := unittest.TransactionFixture()
   146  		transaction.SetReferenceBlockID(referenceBlock.ID())
   147  
   148  		refSnapshot := new(protocol.Snapshot)
   149  
   150  		suite.state.
   151  			On("AtBlockID", referenceBlock.ID()).
   152  			Return(refSnapshot, nil)
   153  
   154  		refSnapshot.
   155  			On("Head").
   156  			Return(referenceBlock, nil).
   157  			Twice()
   158  
   159  		suite.snapshot.
   160  			On("Head").
   161  			Return(referenceBlock, nil).
   162  			Once()
   163  
   164  		expected := convert.TransactionToMessage(transaction.TransactionBody)
   165  		sendReq := &accessproto.SendTransactionRequest{
   166  			Transaction: expected,
   167  		}
   168  		sendResp := accessproto.SendTransactionResponse{}
   169  
   170  		suite.collClient.
   171  			On("SendTransaction", mock.Anything, mock.Anything).
   172  			Return(&sendResp, nil).
   173  			Once()
   174  
   175  		// Send transaction
   176  		resp, err := handler.SendTransaction(context.Background(), sendReq)
   177  		suite.Require().NoError(err)
   178  		suite.Require().NotNil(resp)
   179  
   180  		id := transaction.ID()
   181  		getReq := &accessproto.GetTransactionRequest{
   182  			Id: id[:],
   183  		}
   184  
   185  		// Get transaction
   186  		gResp, err := handler.GetTransaction(context.Background(), getReq)
   187  		suite.Require().NoError(err)
   188  		suite.Require().NotNil(gResp)
   189  
   190  		actual := gResp.GetTransaction()
   191  		suite.Require().Equal(expected, actual)
   192  	})
   193  }
   194  
   195  func (suite *Suite) TestSendExpiredTransaction() {
   196  	suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) {
   197  		referenceBlock := unittest.BlockHeaderFixture()
   198  
   199  		// create latest block that is past the expiry window
   200  		latestBlock := unittest.BlockHeaderFixture()
   201  		latestBlock.Height = referenceBlock.Height + flow.DefaultTransactionExpiry*2
   202  
   203  		transaction := unittest.TransactionFixture()
   204  		transaction.SetReferenceBlockID(referenceBlock.ID())
   205  
   206  		refSnapshot := new(protocol.Snapshot)
   207  
   208  		suite.state.
   209  			On("AtBlockID", referenceBlock.ID()).
   210  			Return(refSnapshot, nil)
   211  
   212  		refSnapshot.
   213  			On("Head").
   214  			Return(referenceBlock, nil).
   215  			Twice()
   216  
   217  		suite.snapshot.
   218  			On("Head").
   219  			Return(latestBlock, nil).
   220  			Once()
   221  
   222  		req := &accessproto.SendTransactionRequest{
   223  			Transaction: convert.TransactionToMessage(transaction.TransactionBody),
   224  		}
   225  
   226  		_, err := handler.SendTransaction(context.Background(), req)
   227  		suite.Require().Error(err)
   228  	})
   229  }
   230  
   231  type mockCloser struct{}
   232  
   233  func (mc *mockCloser) Close() error { return nil }
   234  
   235  // TestSendTransactionToRandomCollectionNode tests that collection nodes are chosen from the appropriate cluster when
   236  // forwarding transactions by sending two transactions bound for two different collection clusters.
   237  func (suite *Suite) TestSendTransactionToRandomCollectionNode() {
   238  	unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) {
   239  
   240  		// create a transaction
   241  		referenceBlock := unittest.BlockHeaderFixture()
   242  		transaction := unittest.TransactionFixture()
   243  		transaction.SetReferenceBlockID(referenceBlock.ID())
   244  
   245  		// setup the state and snapshot mock expectations
   246  		suite.state.On("AtBlockID", referenceBlock.ID()).Return(suite.snapshot, nil)
   247  		suite.snapshot.On("Head").Return(referenceBlock, nil)
   248  
   249  		// create storage
   250  		metrics := metrics.NewNoopCollector()
   251  		transactions := storage.NewTransactions(metrics, db)
   252  		collections := storage.NewCollections(db, transactions)
   253  
   254  		// create collection node cluster
   255  		count := 2
   256  		collNodes := unittest.IdentityListFixture(count, unittest.WithRole(flow.RoleCollection))
   257  		assignments := unittest.ClusterAssignment(uint(count), collNodes)
   258  		clusters, err := factory.NewClusterList(assignments, collNodes)
   259  		suite.Require().Nil(err)
   260  		collNode1 := clusters[0][0]
   261  		collNode2 := clusters[1][0]
   262  		epoch := new(protocol.Epoch)
   263  		suite.epochQuery.On("Current").Return(epoch)
   264  		epoch.On("Clustering").Return(clusters, nil)
   265  
   266  		// create two transactions bound for each of the cluster
   267  		cluster1 := clusters[0]
   268  		cluster1tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster1, func(transaction *flow.TransactionBody) {})
   269  		tx1 := convert.TransactionToMessage(cluster1tx)
   270  		sendReq1 := &accessproto.SendTransactionRequest{
   271  			Transaction: tx1,
   272  		}
   273  		cluster2 := clusters[1]
   274  		cluster2tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster2, func(transaction *flow.TransactionBody) {})
   275  		tx2 := convert.TransactionToMessage(cluster2tx)
   276  		sendReq2 := &accessproto.SendTransactionRequest{
   277  			Transaction: tx2,
   278  		}
   279  		sendResp := accessproto.SendTransactionResponse{}
   280  
   281  		// create mock access api clients for each of the collection node expecting the correct transaction once
   282  		col1ApiClient := new(accessmock.AccessAPIClient)
   283  		col1ApiClient.On("SendTransaction", mock.Anything, sendReq1).Return(&sendResp, nil).Once()
   284  		col2ApiClient := new(accessmock.AccessAPIClient)
   285  		col2ApiClient.On("SendTransaction", mock.Anything, sendReq2).Return(&sendResp, nil).Once()
   286  
   287  		// create a mock connection factory
   288  		connFactory := new(factorymock.ConnectionFactory)
   289  		connFactory.On("GetAccessAPIClient", collNode1.Address).Return(col1ApiClient, &mockCloser{}, nil)
   290  		connFactory.On("GetAccessAPIClient", collNode2.Address).Return(col2ApiClient, &mockCloser{}, nil)
   291  
   292  		backend := backend.New(suite.state,
   293  			nil,
   294  			nil,
   295  			nil,
   296  			nil,
   297  			collections,
   298  			transactions,
   299  			nil,
   300  			nil,
   301  			suite.chainID,
   302  			metrics,
   303  			connFactory,
   304  			false,
   305  			backend.DefaultMaxHeightRange,
   306  			nil,
   307  			nil,
   308  			suite.log,
   309  			backend.DefaultSnapshotHistoryLimit,
   310  		)
   311  
   312  		handler := access.NewHandler(backend, suite.chainID.Chain())
   313  
   314  		// Send transaction 1
   315  		resp, err := handler.SendTransaction(context.Background(), sendReq1)
   316  		require.NoError(suite.T(), err)
   317  		require.NotNil(suite.T(), resp)
   318  
   319  		// Send transaction 2
   320  		resp, err = handler.SendTransaction(context.Background(), sendReq2)
   321  		require.NoError(suite.T(), err)
   322  		require.NotNil(suite.T(), resp)
   323  
   324  		// verify that a collection node in the correct cluster was contacted exactly once
   325  		col1ApiClient.AssertExpectations(suite.T())
   326  		col2ApiClient.AssertExpectations(suite.T())
   327  		epoch.AssertNumberOfCalls(suite.T(), "Clustering", 2)
   328  
   329  		// additionally do a GetTransaction request for the two transactions
   330  		getTx := func(tx flow.TransactionBody) {
   331  			id := tx.ID()
   332  			getReq := &accessproto.GetTransactionRequest{
   333  				Id: id[:],
   334  			}
   335  			gResp, err := handler.GetTransaction(context.Background(), getReq)
   336  			require.NoError(suite.T(), err)
   337  			require.NotNil(suite.T(), gResp)
   338  			actual := gResp.GetTransaction()
   339  			expected := convert.TransactionToMessage(tx)
   340  			require.Equal(suite.T(), expected, actual)
   341  		}
   342  
   343  		getTx(cluster1tx)
   344  		getTx(cluster1tx)
   345  	})
   346  }
   347  
   348  func (suite *Suite) TestGetBlockByIDAndHeight() {
   349  	suite.RunTest(func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) {
   350  
   351  		// test block1 get by ID
   352  		block1 := unittest.BlockFixture()
   353  		// test block2 get by height
   354  		block2 := unittest.BlockFixture()
   355  		block2.Header.Height = 2
   356  
   357  		require.NoError(suite.T(), blocks.Store(&block1))
   358  		require.NoError(suite.T(), blocks.Store(&block2))
   359  
   360  		// the follower logic should update height index on the block storage when a block is finalized
   361  		err := db.Update(operation.IndexBlockHeight(block2.Header.Height, block2.ID()))
   362  		require.NoError(suite.T(), err)
   363  
   364  		assertHeaderResp := func(resp *accessproto.BlockHeaderResponse, err error, header *flow.Header) {
   365  			require.NoError(suite.T(), err)
   366  			require.NotNil(suite.T(), resp)
   367  			actual := resp.Block
   368  			expectedMessage, err := convert.BlockHeaderToMessage(header, suite.signerIds)
   369  			require.NoError(suite.T(), err)
   370  			require.Empty(suite.T(), cmp.Diff(expectedMessage, actual, protocmp.Transform()))
   371  			expectedBlockHeader, err := convert.MessageToBlockHeader(actual)
   372  			require.NoError(suite.T(), err)
   373  			require.Equal(suite.T(), expectedBlockHeader, header)
   374  		}
   375  
   376  		assertBlockResp := func(resp *accessproto.BlockResponse, err error, block *flow.Block) {
   377  			require.NoError(suite.T(), err)
   378  			require.NotNil(suite.T(), resp)
   379  			actual := resp.Block
   380  			expectedMessage, err := convert.BlockToMessage(block, suite.signerIds)
   381  			require.NoError(suite.T(), err)
   382  			require.Equal(suite.T(), expectedMessage, actual)
   383  			expectedBlock, err := convert.MessageToBlock(resp.Block)
   384  			require.NoError(suite.T(), err)
   385  			require.Equal(suite.T(), expectedBlock.ID(), block.ID())
   386  		}
   387  
   388  		assertLightBlockResp := func(resp *accessproto.BlockResponse, err error, block *flow.Block) {
   389  			require.NoError(suite.T(), err)
   390  			require.NotNil(suite.T(), resp)
   391  			actual := resp.Block
   392  			expectedMessage := convert.BlockToMessageLight(block)
   393  			require.Equal(suite.T(), expectedMessage, actual)
   394  		}
   395  
   396  		suite.snapshot.On("Head").Return(block1.Header, nil)
   397  		suite.Run("get header 1 by ID", func() {
   398  			// get header by ID
   399  			id := block1.ID()
   400  			req := &accessproto.GetBlockHeaderByIDRequest{
   401  				Id: id[:],
   402  			}
   403  
   404  			resp, err := handler.GetBlockHeaderByID(context.Background(), req)
   405  
   406  			// assert it is indeed block1
   407  			assertHeaderResp(resp, err, block1.Header)
   408  		})
   409  
   410  		suite.Run("get block 1 by ID", func() {
   411  			id := block1.ID()
   412  			// get block details by ID
   413  			req := &accessproto.GetBlockByIDRequest{
   414  				Id:                id[:],
   415  				FullBlockResponse: true,
   416  			}
   417  
   418  			resp, err := handler.GetBlockByID(context.Background(), req)
   419  
   420  			assertBlockResp(resp, err, &block1)
   421  		})
   422  
   423  		suite.Run("get block light 1 by ID", func() {
   424  			id := block1.ID()
   425  			// get block details by ID
   426  			req := &accessproto.GetBlockByIDRequest{
   427  				Id: id[:],
   428  			}
   429  
   430  			resp, err := handler.GetBlockByID(context.Background(), req)
   431  
   432  			assertLightBlockResp(resp, err, &block1)
   433  		})
   434  
   435  		suite.Run("get header 2 by height", func() {
   436  
   437  			// get header by height
   438  			req := &accessproto.GetBlockHeaderByHeightRequest{
   439  				Height: block2.Header.Height,
   440  			}
   441  
   442  			resp, err := handler.GetBlockHeaderByHeight(context.Background(), req)
   443  
   444  			assertHeaderResp(resp, err, block2.Header)
   445  		})
   446  
   447  		suite.Run("get block 2 by height", func() {
   448  			// get block details by height
   449  			req := &accessproto.GetBlockByHeightRequest{
   450  				Height:            block2.Header.Height,
   451  				FullBlockResponse: true,
   452  			}
   453  
   454  			resp, err := handler.GetBlockByHeight(context.Background(), req)
   455  
   456  			assertBlockResp(resp, err, &block2)
   457  		})
   458  
   459  		suite.Run("get block 2 by height", func() {
   460  			// get block details by height
   461  			req := &accessproto.GetBlockByHeightRequest{
   462  				Height: block2.Header.Height,
   463  			}
   464  
   465  			resp, err := handler.GetBlockByHeight(context.Background(), req)
   466  
   467  			assertLightBlockResp(resp, err, &block2)
   468  		})
   469  	})
   470  }
   471  
   472  func (suite *Suite) TestGetExecutionResultByBlockID() {
   473  	suite.RunTest(func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, _ *storage.Headers, executionResults *storage.ExecutionResults) {
   474  
   475  		// test block1 get by ID
   476  		nonexistingID := unittest.IdentifierFixture()
   477  		blockID := unittest.IdentifierFixture()
   478  
   479  		er := unittest.ExecutionResultFixture(
   480  			unittest.WithExecutionResultBlockID(blockID),
   481  			unittest.WithServiceEvents(2))
   482  
   483  		require.NoError(suite.T(), executionResults.Store(er))
   484  		require.NoError(suite.T(), executionResults.Index(blockID, er.ID()))
   485  
   486  		assertResp := func(resp *accessproto.ExecutionResultForBlockIDResponse, err error, executionResult *flow.ExecutionResult) {
   487  			require.NoError(suite.T(), err)
   488  			require.NotNil(suite.T(), resp)
   489  			er := resp.ExecutionResult
   490  
   491  			require.Len(suite.T(), er.Chunks, len(executionResult.Chunks))
   492  			require.Len(suite.T(), er.ServiceEvents, len(executionResult.ServiceEvents))
   493  
   494  			assert.Equal(suite.T(), executionResult.BlockID, convert.MessageToIdentifier(er.BlockId))
   495  			assert.Equal(suite.T(), executionResult.PreviousResultID, convert.MessageToIdentifier(er.PreviousResultId))
   496  			assert.Equal(suite.T(), executionResult.ExecutionDataID, convert.MessageToIdentifier(er.ExecutionDataId))
   497  
   498  			for i, chunk := range executionResult.Chunks {
   499  				assert.Equal(suite.T(), chunk.BlockID[:], er.Chunks[i].BlockId)
   500  				assert.Equal(suite.T(), chunk.Index, er.Chunks[i].Index)
   501  				assert.Equal(suite.T(), uint32(chunk.CollectionIndex), er.Chunks[i].CollectionIndex)
   502  				assert.Equal(suite.T(), chunk.StartState[:], er.Chunks[i].StartState)
   503  				assert.Equal(suite.T(), chunk.EventCollection[:], er.Chunks[i].EventCollection)
   504  				assert.Equal(suite.T(), chunk.TotalComputationUsed, er.Chunks[i].TotalComputationUsed)
   505  				assert.Equal(suite.T(), uint32(chunk.NumberOfTransactions), er.Chunks[i].NumberOfTransactions)
   506  				assert.Equal(suite.T(), chunk.EndState[:], er.Chunks[i].EndState)
   507  			}
   508  
   509  			for i, serviceEvent := range executionResult.ServiceEvents {
   510  				assert.Equal(suite.T(), serviceEvent.Type, er.ServiceEvents[i].Type)
   511  				event := serviceEvent.Event
   512  
   513  				marshalledEvent, err := json.Marshal(event)
   514  				require.NoError(suite.T(), err)
   515  
   516  				assert.Equal(suite.T(), marshalledEvent, er.ServiceEvents[i].Payload)
   517  			}
   518  			parsedExecResult, err := convert.MessageToExecutionResult(resp.ExecutionResult)
   519  			require.NoError(suite.T(), err)
   520  			assert.Equal(suite.T(), parsedExecResult, executionResult)
   521  			assert.Equal(suite.T(), parsedExecResult.ID(), executionResult.ID())
   522  		}
   523  
   524  		suite.Run("nonexisting block", func() {
   525  			req := &accessproto.GetExecutionResultForBlockIDRequest{
   526  				BlockId: nonexistingID[:],
   527  			}
   528  
   529  			resp, err := handler.GetExecutionResultForBlockID(context.Background(), req)
   530  
   531  			require.Error(suite.T(), err)
   532  			require.Nil(suite.T(), resp)
   533  		})
   534  
   535  		suite.Run("some block", func() {
   536  			// get header by ID
   537  			req := &accessproto.GetExecutionResultForBlockIDRequest{
   538  				BlockId: blockID[:],
   539  			}
   540  
   541  			resp, err := handler.GetExecutionResultForBlockID(context.Background(), req)
   542  
   543  			require.NoError(suite.T(), err)
   544  
   545  			assertResp(resp, err, er)
   546  		})
   547  
   548  	})
   549  }
   550  
   551  // TestGetSealedTransaction tests that transactions status of transaction that belongs to a sealed block
   552  // is reported as sealed
   553  func (suite *Suite) TestGetSealedTransaction() {
   554  	unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) {
   555  		headers, _, _, _, _, blocks, _, _, _, _ := util.StorageLayer(suite.T(), db)
   556  		results := storage.NewExecutionResults(suite.metrics, db)
   557  		receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize)
   558  		enIdentities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution))
   559  		enNodeIDs := flow.IdentifierList(enIdentities.NodeIDs())
   560  
   561  		// create block -> collection -> transactions
   562  		block, collection := suite.createChain()
   563  
   564  		// setup mocks
   565  		originID := unittest.IdentifierFixture()
   566  		conduit := new(mocknetwork.Conduit)
   567  		suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil).
   568  			Once()
   569  		suite.request.On("Request", mock.Anything, mock.Anything).Return()
   570  
   571  		suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
   572  
   573  		colIdentities := unittest.IdentityListFixture(1, unittest.WithRole(flow.RoleCollection))
   574  		allIdentities := append(colIdentities, enIdentities...)
   575  
   576  		suite.snapshot.On("Identities", mock.Anything).Return(allIdentities, nil).Once()
   577  
   578  		exeEventResp := execproto.GetTransactionResultResponse{
   579  			Events: nil,
   580  		}
   581  
   582  		// generate receipts
   583  		executionReceipts := unittest.ReceiptsForBlockFixture(&block, enNodeIDs)
   584  
   585  		// assume execution node returns an empty list of events
   586  		suite.execClient.On("GetTransactionResult", mock.Anything, mock.Anything).Return(&exeEventResp, nil)
   587  
   588  		// create a mock connection factory
   589  		connFactory := new(factorymock.ConnectionFactory)
   590  		connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   591  
   592  		// initialize storage
   593  		metrics := metrics.NewNoopCollector()
   594  		transactions := storage.NewTransactions(metrics, db)
   595  		collections := storage.NewCollections(db, transactions)
   596  		collectionsToMarkFinalized, err := stdmap.NewTimes(100)
   597  		require.NoError(suite.T(), err)
   598  		collectionsToMarkExecuted, err := stdmap.NewTimes(100)
   599  		require.NoError(suite.T(), err)
   600  		blocksToMarkExecuted, err := stdmap.NewTimes(100)
   601  		require.NoError(suite.T(), err)
   602  
   603  		backend := backend.New(suite.state,
   604  			suite.collClient,
   605  			nil,
   606  			blocks,
   607  			headers,
   608  			collections,
   609  			transactions,
   610  			receipts,
   611  			results,
   612  			suite.chainID,
   613  			suite.metrics,
   614  			connFactory,
   615  			false,
   616  			backend.DefaultMaxHeightRange,
   617  			nil,
   618  			enNodeIDs.Strings(),
   619  			suite.log,
   620  			backend.DefaultSnapshotHistoryLimit,
   621  		)
   622  
   623  		handler := access.NewHandler(backend, suite.chainID.Chain())
   624  
   625  		rpcEngBuilder, err := rpc.NewBuilder(suite.log, suite.state, rpc.Config{}, nil, nil, blocks, headers, collections, transactions, receipts,
   626  			results, suite.chainID, metrics, metrics, 0, 0, false, false, nil, nil)
   627  		require.NoError(suite.T(), err)
   628  		rpcEng, err := rpcEngBuilder.WithLegacy().Build()
   629  		require.NoError(suite.T(), err)
   630  
   631  		// create the ingest engine
   632  		ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, blocks, headers, collections,
   633  			transactions, results, receipts, metrics, collectionsToMarkFinalized, collectionsToMarkExecuted, blocksToMarkExecuted, rpcEng)
   634  		require.NoError(suite.T(), err)
   635  
   636  		// 1. Assume that follower engine updated the block storage and the protocol state. The block is reported as sealed
   637  		err = blocks.Store(&block)
   638  		require.NoError(suite.T(), err)
   639  		suite.snapshot.On("Head").Return(block.Header, nil).Twice()
   640  
   641  		background, cancel := context.WithCancel(context.Background())
   642  		defer cancel()
   643  
   644  		ctx, _ := irrecoverable.WithSignaler(background)
   645  		ingestEng.Start(ctx)
   646  		<-ingestEng.Ready()
   647  
   648  		// 2. Ingest engine was notified by the follower engine about a new block.
   649  		// Follower engine --> Ingest engine
   650  		mb := &model.Block{
   651  			BlockID: block.ID(),
   652  		}
   653  		ingestEng.OnFinalizedBlock(mb)
   654  
   655  		// 3. Request engine is used to request missing collection
   656  		suite.request.On("EntityByID", collection.ID(), mock.Anything).Return()
   657  
   658  		// 4. Ingest engine receives the requested collection and all the execution receipts
   659  		ingestEng.OnCollection(originID, &collection)
   660  
   661  		for _, r := range executionReceipts {
   662  			err = ingestEng.Process(channels.ReceiveReceipts, enNodeIDs[0], r)
   663  			require.NoError(suite.T(), err)
   664  		}
   665  
   666  		// 5. Client requests a transaction
   667  		tx := collection.Transactions[0]
   668  		txID := tx.ID()
   669  		getReq := &accessproto.GetTransactionRequest{
   670  			Id: txID[:],
   671  		}
   672  		gResp, err := handler.GetTransactionResult(context.Background(), getReq)
   673  		require.NoError(suite.T(), err)
   674  		// assert that the transaction is reported as Sealed
   675  		require.Equal(suite.T(), entitiesproto.TransactionStatus_SEALED, gResp.GetStatus())
   676  	})
   677  }
   678  
   679  // TestExecuteScript tests the three execute Script related calls to make sure that the execution api is called with
   680  // the correct block id
   681  func (suite *Suite) TestExecuteScript() {
   682  	unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) {
   683  		headers, _, _, _, _, blocks, _, _, _, _ := util.StorageLayer(suite.T(), db)
   684  		transactions := storage.NewTransactions(suite.metrics, db)
   685  		collections := storage.NewCollections(db, transactions)
   686  		results := storage.NewExecutionResults(suite.metrics, db)
   687  		receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize)
   688  
   689  		identities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution))
   690  		suite.snapshot.On("Identities", mock.Anything).Return(identities, nil)
   691  
   692  		// create a mock connection factory
   693  		connFactory := new(factorymock.ConnectionFactory)
   694  		connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   695  
   696  		suite.backend = backend.New(suite.state,
   697  			suite.collClient,
   698  			nil,
   699  			blocks,
   700  			headers,
   701  			collections,
   702  			transactions,
   703  			receipts,
   704  			results,
   705  			suite.chainID,
   706  			suite.metrics,
   707  			connFactory,
   708  			false,
   709  			backend.DefaultMaxHeightRange,
   710  			nil,
   711  			flow.IdentifierList(identities.NodeIDs()).Strings(),
   712  			suite.log,
   713  			backend.DefaultSnapshotHistoryLimit,
   714  		)
   715  
   716  		handler := access.NewHandler(suite.backend, suite.chainID.Chain())
   717  
   718  		// initialize metrics related storage
   719  		metrics := metrics.NewNoopCollector()
   720  		collectionsToMarkFinalized, err := stdmap.NewTimes(100)
   721  		require.NoError(suite.T(), err)
   722  		collectionsToMarkExecuted, err := stdmap.NewTimes(100)
   723  		require.NoError(suite.T(), err)
   724  		blocksToMarkExecuted, err := stdmap.NewTimes(100)
   725  		require.NoError(suite.T(), err)
   726  
   727  		conduit := new(mocknetwork.Conduit)
   728  		suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil).
   729  			Once()
   730  		// create the ingest engine
   731  		ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, blocks, headers, collections,
   732  			transactions, results, receipts, metrics, collectionsToMarkFinalized, collectionsToMarkExecuted, blocksToMarkExecuted, nil)
   733  		require.NoError(suite.T(), err)
   734  
   735  		// create a block and a seal pointing to that block
   736  		lastBlock := unittest.BlockFixture()
   737  		lastBlock.Header.Height = 2
   738  		err = blocks.Store(&lastBlock)
   739  		require.NoError(suite.T(), err)
   740  		err = db.Update(operation.IndexBlockHeight(lastBlock.Header.Height, lastBlock.ID()))
   741  		require.NoError(suite.T(), err)
   742  		suite.snapshot.On("Head").Return(lastBlock.Header, nil).Once()
   743  
   744  		// create execution receipts for each of the execution node and the last block
   745  		executionReceipts := unittest.ReceiptsForBlockFixture(&lastBlock, identities.NodeIDs())
   746  		// notify the ingest engine about the receipts
   747  		for _, r := range executionReceipts {
   748  			err = ingestEng.ProcessLocal(r)
   749  			require.NoError(suite.T(), err)
   750  		}
   751  
   752  		// create another block as a predecessor of the block created earlier
   753  		prevBlock := unittest.BlockFixture()
   754  		prevBlock.Header.Height = lastBlock.Header.Height - 1
   755  		err = blocks.Store(&prevBlock)
   756  		require.NoError(suite.T(), err)
   757  		err = db.Update(operation.IndexBlockHeight(prevBlock.Header.Height, prevBlock.ID()))
   758  		require.NoError(suite.T(), err)
   759  
   760  		// create execution receipts for each of the execution node and the previous block
   761  		executionReceipts = unittest.ReceiptsForBlockFixture(&prevBlock, identities.NodeIDs())
   762  		// notify the ingest engine about the receipts
   763  		for _, r := range executionReceipts {
   764  			err = ingestEng.ProcessLocal(r)
   765  			require.NoError(suite.T(), err)
   766  		}
   767  
   768  		ctx := context.Background()
   769  
   770  		script := []byte("dummy script")
   771  
   772  		// setupExecClientMock sets up the mock the execution client and returns the access response to expect
   773  		setupExecClientMock := func(blockID flow.Identifier) *accessproto.ExecuteScriptResponse {
   774  			id := blockID[:]
   775  			executionReq := execproto.ExecuteScriptAtBlockIDRequest{
   776  				BlockId: id,
   777  				Script:  script,
   778  			}
   779  			executionResp := execproto.ExecuteScriptAtBlockIDResponse{
   780  				Value: []byte{9, 10, 11},
   781  			}
   782  
   783  			suite.execClient.On("ExecuteScriptAtBlockID", ctx, &executionReq).Return(&executionResp, nil).Once()
   784  
   785  			expectedResp := accessproto.ExecuteScriptResponse{
   786  				Value: executionResp.GetValue(),
   787  			}
   788  			return &expectedResp
   789  		}
   790  
   791  		assertResult := func(err error, expected interface{}, actual interface{}) {
   792  			suite.Require().NoError(err)
   793  			suite.Require().Equal(expected, actual)
   794  			suite.execClient.AssertExpectations(suite.T())
   795  		}
   796  
   797  		suite.Run("execute script at latest block", func() {
   798  			suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
   799  			suite.state.
   800  				On("AtBlockID", lastBlock.ID()).
   801  				Return(suite.snapshot, nil)
   802  
   803  			expectedResp := setupExecClientMock(lastBlock.ID())
   804  			req := accessproto.ExecuteScriptAtLatestBlockRequest{
   805  				Script: script,
   806  			}
   807  			actualResp, err := handler.ExecuteScriptAtLatestBlock(ctx, &req)
   808  			assertResult(err, expectedResp, actualResp)
   809  		})
   810  
   811  		suite.Run("execute script at block id", func() {
   812  			suite.state.
   813  				On("AtBlockID", prevBlock.ID()).
   814  				Return(suite.snapshot, nil)
   815  
   816  			expectedResp := setupExecClientMock(prevBlock.ID())
   817  			id := prevBlock.ID()
   818  			req := accessproto.ExecuteScriptAtBlockIDRequest{
   819  				BlockId: id[:],
   820  				Script:  script,
   821  			}
   822  			actualResp, err := handler.ExecuteScriptAtBlockID(ctx, &req)
   823  			assertResult(err, expectedResp, actualResp)
   824  		})
   825  
   826  		suite.Run("execute script at block height", func() {
   827  			suite.state.
   828  				On("AtBlockID", prevBlock.ID()).
   829  				Return(suite.snapshot, nil)
   830  
   831  			expectedResp := setupExecClientMock(prevBlock.ID())
   832  			req := accessproto.ExecuteScriptAtBlockHeightRequest{
   833  				BlockHeight: prevBlock.Header.Height,
   834  				Script:      script,
   835  			}
   836  			actualResp, err := handler.ExecuteScriptAtBlockHeight(ctx, &req)
   837  			assertResult(err, expectedResp, actualResp)
   838  		})
   839  	})
   840  }
   841  
   842  func (suite *Suite) createChain() (flow.Block, flow.Collection) {
   843  	collection := unittest.CollectionFixture(10)
   844  	refBlockID := unittest.IdentifierFixture()
   845  	// prepare cluster committee members
   846  	clusterCommittee := unittest.IdentityListFixture(32 * 4).Filter(filter.HasRole(flow.RoleCollection))
   847  	// guarantee signers must be cluster committee members, so that access will fetch collection from
   848  	// the signers that are specified by guarantee.SignerIndices
   849  	indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs())
   850  	require.NoError(suite.T(), err)
   851  	guarantee := &flow.CollectionGuarantee{
   852  		CollectionID:     collection.ID(),
   853  		Signature:        crypto.Signature([]byte("signature A")),
   854  		ReferenceBlockID: refBlockID,
   855  		SignerIndices:    indices,
   856  	}
   857  	block := unittest.BlockFixture()
   858  	block.Payload.Guarantees = []*flow.CollectionGuarantee{guarantee}
   859  	block.Header.PayloadHash = block.Payload.Hash()
   860  
   861  	cluster := new(protocol.Cluster)
   862  	cluster.On("Members").Return(clusterCommittee, nil)
   863  	epoch := new(protocol.Epoch)
   864  	epoch.On("ClusterByChainID", mock.Anything).Return(cluster, nil)
   865  	epochs := new(protocol.EpochQuery)
   866  	epochs.On("Current").Return(epoch)
   867  	snap := new(protocol.Snapshot)
   868  	snap.On("Epochs").Return(epochs)
   869  	snap.On("SealingSegment").Return(&flow.SealingSegment{Blocks: unittest.BlockFixtures(2)}, nil).Maybe()
   870  
   871  	suite.state.On("AtBlockID", mock.Anything).Return(snap).Once() // initial height lookup in ingestion engine
   872  	suite.state.On("AtBlockID", refBlockID).Return(snap)
   873  
   874  	return block, collection
   875  }