github.com/onflow/flow-go@v0.33.17/engine/execution/rpc/engine_test.go (about)

     1  package rpc
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/rs/zerolog"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/suite"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  	"google.golang.org/protobuf/testing/protocmp"
    17  
    18  	"github.com/onflow/flow/protobuf/go/flow/entities"
    19  	"github.com/onflow/flow/protobuf/go/flow/execution"
    20  
    21  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    22  	mockEng "github.com/onflow/flow-go/engine/execution/mock"
    23  	"github.com/onflow/flow-go/engine/execution/state"
    24  	"github.com/onflow/flow-go/model/flow"
    25  	realstorage "github.com/onflow/flow-go/storage"
    26  	storage "github.com/onflow/flow-go/storage/mock"
    27  	"github.com/onflow/flow-go/utils/unittest"
    28  )
    29  
    30  type Suite struct {
    31  	suite.Suite
    32  	log        zerolog.Logger
    33  	events     *storage.Events
    34  	exeResults *storage.ExecutionResults
    35  	txResults  *storage.TransactionResults
    36  	commits    *storage.Commits
    37  	headers    *storage.Headers
    38  }
    39  
    40  func TestHandler(t *testing.T) {
    41  	suite.Run(t, new(Suite))
    42  }
    43  
    44  func (suite *Suite) SetupTest() {
    45  	suite.log = zerolog.Logger{}
    46  	suite.events = storage.NewEvents(suite.T())
    47  	suite.exeResults = storage.NewExecutionResults(suite.T())
    48  	suite.txResults = storage.NewTransactionResults(suite.T())
    49  	suite.commits = storage.NewCommits(suite.T())
    50  	suite.headers = storage.NewHeaders(suite.T())
    51  }
    52  
    53  // TestExecuteScriptAtBlockID tests the ExecuteScriptAtBlockID API call
    54  func (suite *Suite) TestExecuteScriptAtBlockID() {
    55  	// setup handler
    56  	mockEngine := mockEng.NewScriptExecutor(suite.T())
    57  	handler := &handler{
    58  		engine:  mockEngine,
    59  		chain:   flow.Mainnet,
    60  		commits: suite.commits,
    61  	}
    62  
    63  	// setup dummy request/response
    64  	ctx := context.Background()
    65  	mockIdentifier := unittest.IdentifierFixture()
    66  	script := []byte("dummy script")
    67  	arguments := [][]byte(nil)
    68  	executionReq := execution.ExecuteScriptAtBlockIDRequest{
    69  		BlockId: mockIdentifier[:],
    70  		Script:  script,
    71  	}
    72  	scriptExecValue := []byte{9, 10, 11}
    73  	executionResp := execution.ExecuteScriptAtBlockIDResponse{
    74  		Value: scriptExecValue,
    75  	}
    76  
    77  	suite.Run("happy path with successful script execution", func() {
    78  		suite.commits.On("ByBlockID", mockIdentifier).Return(nil, nil).Once()
    79  		mockEngine.On("ExecuteScriptAtBlockID", ctx, script, arguments, mockIdentifier).
    80  			Return(scriptExecValue, nil).Once()
    81  		response, err := handler.ExecuteScriptAtBlockID(ctx, &executionReq)
    82  		suite.Require().NoError(err)
    83  		suite.Require().Equal(&executionResp, response)
    84  		mockEngine.AssertExpectations(suite.T())
    85  	})
    86  
    87  	suite.Run("valid request for unknown block", func() {
    88  		suite.commits.On("ByBlockID", mockIdentifier).Return(nil, realstorage.ErrNotFound).Once()
    89  		_, err := handler.ExecuteScriptAtBlockID(ctx, &executionReq)
    90  		suite.Require().Error(err)
    91  		errors.Is(err, status.Error(codes.NotFound, ""))
    92  	})
    93  
    94  	suite.Run("valid request with script execution failure", func() {
    95  		suite.commits.On("ByBlockID", mockIdentifier).Return(nil, nil).Once()
    96  		mockEngine.On("ExecuteScriptAtBlockID", ctx, script, arguments, mockIdentifier).
    97  			Return(nil, status.Error(codes.InvalidArgument, "")).Once()
    98  		_, err := handler.ExecuteScriptAtBlockID(ctx, &executionReq)
    99  		suite.Require().Error(err)
   100  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   101  	})
   102  
   103  	suite.Run("invalid request with nil blockID", func() {
   104  		executionReqWithNilBlock := execution.ExecuteScriptAtBlockIDRequest{
   105  			BlockId: nil,
   106  			Script:  script,
   107  		}
   108  		_, err := handler.ExecuteScriptAtBlockID(ctx, &executionReqWithNilBlock)
   109  
   110  		// check that an error was received
   111  		suite.Require().Error(err)
   112  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   113  	})
   114  
   115  }
   116  
   117  // TestGetEventsForBlockIDs tests the GetEventsForBlockIDs API call
   118  func (suite *Suite) TestGetEventsForBlockIDs() {
   119  
   120  	totalBlocks := 10
   121  	eventsPerBlock := 10
   122  
   123  	blockIDs := make([][]byte, totalBlocks)
   124  	expectedResult := make([]*execution.GetEventsForBlockIDsResponse_Result, totalBlocks)
   125  
   126  	// setup the events storage mock
   127  	for i := range blockIDs {
   128  		block := unittest.BlockFixture()
   129  		block.Header.Height = uint64(i)
   130  		id := block.ID()
   131  		blockIDs[i] = id[:]
   132  		eventsForBlock := make([]flow.Event, eventsPerBlock)
   133  		eventMessages := make([]*entities.Event, eventsPerBlock)
   134  		for j := range eventsForBlock {
   135  			e := unittest.EventFixture(flow.EventAccountCreated, uint32(j), uint32(j), unittest.IdentifierFixture(), 0)
   136  			eventsForBlock[j] = e
   137  			eventMessages[j] = convert.EventToMessage(e)
   138  		}
   139  		// expect one call to lookup result for each block ID
   140  		suite.commits.On("ByBlockID", id).Return(nil, nil).Once()
   141  
   142  		// expect one call to lookup events for each block ID
   143  		suite.events.On("ByBlockIDEventType", id, flow.EventAccountCreated).Return(eventsForBlock, nil).Once()
   144  
   145  		// expect one call to lookup each block
   146  		suite.headers.On("ByBlockID", id).Return(block.Header, nil).Once()
   147  
   148  		// create the expected result for this block
   149  		expectedResult[i] = &execution.GetEventsForBlockIDsResponse_Result{
   150  			BlockId:     id[:],
   151  			BlockHeight: block.Header.Height,
   152  			Events:      eventMessages,
   153  		}
   154  	}
   155  
   156  	// create the handler
   157  	handler := &handler{
   158  		headers:            suite.headers,
   159  		events:             suite.events,
   160  		exeResults:         suite.exeResults,
   161  		transactionResults: suite.txResults,
   162  		commits:            suite.commits,
   163  		chain:              flow.Mainnet,
   164  		maxBlockRange:      DefaultMaxBlockRange,
   165  	}
   166  
   167  	concoctReq := func(errType string, blockIDs [][]byte) *execution.GetEventsForBlockIDsRequest {
   168  		return &execution.GetEventsForBlockIDsRequest{
   169  			Type:     errType,
   170  			BlockIds: blockIDs,
   171  		}
   172  	}
   173  
   174  	// happy path - valid requests receives a valid response
   175  	suite.Run("happy path", func() {
   176  
   177  		// create a valid API request
   178  		req := concoctReq(string(flow.EventAccountCreated), blockIDs)
   179  
   180  		// execute the GetEventsForBlockIDs call
   181  		resp, err := handler.GetEventsForBlockIDs(context.Background(), req)
   182  
   183  		// check that a successful response is received
   184  		suite.Require().NoError(err)
   185  
   186  		actualResult := resp.GetResults()
   187  		suite.Require().ElementsMatch(expectedResult, actualResult)
   188  	})
   189  
   190  	// failure path - empty even type in the request results in an error
   191  	suite.Run("request with empty event type", func() {
   192  
   193  		// create an API request with empty even type
   194  		req := concoctReq("", blockIDs)
   195  
   196  		_, err := handler.GetEventsForBlockIDs(context.Background(), req)
   197  
   198  		// check that an error was received
   199  		suite.Require().Error(err)
   200  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   201  	})
   202  
   203  	// failure path - empty block ids in request results in an error
   204  	suite.Run("request with empty block IDs", func() {
   205  
   206  		// create an API request with empty block ids
   207  		req := concoctReq(string(flow.EventAccountCreated), nil)
   208  
   209  		_, err := handler.GetEventsForBlockIDs(context.Background(), req)
   210  
   211  		// check that an error was received
   212  		suite.Require().Error(err)
   213  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   214  	})
   215  
   216  	// failure path - non-existent block id in request results in an error
   217  	suite.Run("request with non-existent block ID", func() {
   218  
   219  		id := unittest.IdentifierFixture()
   220  
   221  		// expect a storage call for the invalid id but return an error
   222  		suite.commits.On("ByBlockID", id).Return(nil, realstorage.ErrNotFound).Once()
   223  
   224  		// create an API request with the invalid block id
   225  		req := concoctReq(string(flow.EventAccountCreated), [][]byte{id[:]})
   226  
   227  		_, err := handler.GetEventsForBlockIDs(context.Background(), req)
   228  
   229  		// check that an error was received
   230  		suite.Require().Error(err)
   231  		errors.Is(err, status.Error(codes.NotFound, ""))
   232  	})
   233  
   234  	// request for too many blocks - receives a InvalidArgument error
   235  	suite.Run("request for too many blocks", func() {
   236  
   237  		// update range so it's smaller than list of blockIDs
   238  		handler.maxBlockRange = totalBlocks / 2
   239  
   240  		// create a valid API request
   241  		req := concoctReq(string(flow.EventAccountCreated), blockIDs)
   242  
   243  		// execute the GetEventsForBlockIDs call
   244  		_, err := handler.GetEventsForBlockIDs(context.Background(), req)
   245  
   246  		// check that an error was received
   247  		suite.Require().Error(err)
   248  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   249  	})
   250  }
   251  
   252  // Test GetAccountAtBlockID tests the GetAccountAtBlockID API call
   253  func (suite *Suite) TestGetAccountAtBlockID() {
   254  
   255  	id := unittest.IdentifierFixture()
   256  	serviceAddress := flow.Mainnet.Chain().ServiceAddress()
   257  
   258  	serviceAccount := flow.Account{
   259  		Address: serviceAddress,
   260  	}
   261  
   262  	mockEngine := mockEng.NewScriptExecutor(suite.T())
   263  
   264  	// create the handler
   265  	handler := &handler{
   266  		engine:  mockEngine,
   267  		chain:   flow.Mainnet,
   268  		commits: suite.commits,
   269  	}
   270  
   271  	createReq := func(id []byte, address []byte) *execution.GetAccountAtBlockIDRequest {
   272  		return &execution.GetAccountAtBlockIDRequest{
   273  			Address: address,
   274  			BlockId: id,
   275  		}
   276  	}
   277  
   278  	suite.Run("happy path with valid request", func() {
   279  
   280  		// setup mock expectations
   281  		suite.commits.On("ByBlockID", id).Return(nil, nil).Once()
   282  
   283  		mockEngine.On("GetAccount", mock.Anything, serviceAddress, id).Return(&serviceAccount, nil).Once()
   284  
   285  		req := createReq(id[:], serviceAddress.Bytes())
   286  
   287  		resp, err := handler.GetAccountAtBlockID(context.Background(), req)
   288  
   289  		suite.Require().NoError(err)
   290  		actualAccount := resp.GetAccount()
   291  		expectedAccount, err := convert.AccountToMessage(&serviceAccount)
   292  		suite.Require().NoError(err)
   293  		suite.Require().Empty(
   294  			cmp.Diff(expectedAccount, actualAccount, protocmp.Transform()))
   295  	})
   296  
   297  	suite.Run("invalid request with unknown block id", func() {
   298  
   299  		// setup mock expectations
   300  		suite.commits.On("ByBlockID", id).Return(nil, realstorage.ErrNotFound).Once()
   301  
   302  		req := createReq(id[:], serviceAddress.Bytes())
   303  
   304  		_, err := handler.GetAccountAtBlockID(context.Background(), req)
   305  
   306  		suite.Require().Error(err)
   307  		errors.Is(err, status.Error(codes.NotFound, ""))
   308  	})
   309  
   310  	suite.Run("invalid request with nil block id", func() {
   311  
   312  		req := createReq(nil, serviceAddress.Bytes())
   313  
   314  		_, err := handler.GetAccountAtBlockID(context.Background(), req)
   315  
   316  		suite.Require().Error(err)
   317  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   318  	})
   319  
   320  	suite.Run("invalid request with nil root address", func() {
   321  
   322  		req := createReq(id[:], nil)
   323  
   324  		_, err := handler.GetAccountAtBlockID(context.Background(), req)
   325  
   326  		suite.Require().Error(err)
   327  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   328  	})
   329  
   330  	suite.Run("valid request for unavailable data", func() {
   331  		suite.commits.On("ByBlockID", id).Return(nil, nil).Once()
   332  
   333  		expectedErr := fmt.Errorf(
   334  			"failed to execute script at block (%s): %w (%s). "+
   335  				"this error usually happens if the reference "+
   336  				"block for this script is not set to a recent block.",
   337  			id,
   338  			state.ErrExecutionStatePruned,
   339  			unittest.IdentifierFixture(),
   340  		)
   341  
   342  		mockEngine.On("GetAccount", mock.Anything, serviceAddress, id).Return(nil, expectedErr).Once()
   343  
   344  		req := createReq(id[:], serviceAddress.Bytes())
   345  
   346  		resp, err := handler.GetAccountAtBlockID(context.Background(), req)
   347  		suite.Assert().Nil(resp)
   348  		suite.Assert().Equal(codes.OutOfRange, status.Code(err))
   349  	})
   350  }
   351  
   352  // Test GetRegisterAtBlockID tests the GetRegisterAtBlockID API call
   353  func (suite *Suite) TestGetRegisterAtBlockID() {
   354  
   355  	id := unittest.IdentifierFixture()
   356  	serviceAddress := flow.Mainnet.Chain().ServiceAddress()
   357  	validKey := []byte("exists")
   358  
   359  	mockEngine := mockEng.NewScriptExecutor(suite.T())
   360  
   361  	// create the handler
   362  	handler := &handler{
   363  		engine: mockEngine,
   364  		chain:  flow.Mainnet,
   365  	}
   366  
   367  	createReq := func(id, owner, key []byte) *execution.GetRegisterAtBlockIDRequest {
   368  		return &execution.GetRegisterAtBlockIDRequest{
   369  			RegisterOwner: owner,
   370  			RegisterKey:   key,
   371  			BlockId:       id,
   372  		}
   373  	}
   374  
   375  	suite.Run("happy path with valid request", func() {
   376  
   377  		// setup mock expectations
   378  		mockEngine.On("GetRegisterAtBlockID", mock.Anything, serviceAddress.Bytes(), validKey, id).Return([]uint8{1}, nil).Once()
   379  
   380  		req := createReq(id[:], serviceAddress.Bytes(), validKey)
   381  		resp, err := handler.GetRegisterAtBlockID(context.Background(), req)
   382  
   383  		suite.Require().NoError(err)
   384  		value := resp.GetValue()
   385  		suite.Require().NoError(err)
   386  		suite.Require().True(len(value) > 0)
   387  	})
   388  
   389  	suite.Run("invalid request with bad address", func() {
   390  		badOwner := []byte("\uFFFD")
   391  		// return error
   392  		mockEngine.On("GetRegisterAtBlockID", mock.Anything, badOwner, validKey, id).Return(nil, errors.New("error")).Once()
   393  
   394  		req := createReq(id[:], badOwner, validKey)
   395  		_, err := handler.GetRegisterAtBlockID(context.Background(), req)
   396  		suite.Require().Error(err)
   397  	})
   398  }
   399  
   400  // TestGetTransactionResult tests the GetTransactionResult and GetTransactionResultByIndex API calls
   401  func (suite *Suite) TestGetTransactionResult() {
   402  
   403  	totalEvents := 10
   404  	block := unittest.BlockFixture()
   405  	tx := unittest.TransactionFixture()
   406  	bID := block.ID()
   407  	txID := tx.ID()
   408  	txIndex := rand.Uint32()
   409  
   410  	// setup the events storage mock
   411  	eventsForTx := make([]flow.Event, totalEvents)
   412  	eventMessages := make([]*entities.Event, totalEvents)
   413  	for j := range eventsForTx {
   414  		e := unittest.EventFixture(flow.EventAccountCreated, uint32(j), uint32(j), unittest.IdentifierFixture(), 0)
   415  		eventsForTx[j] = e
   416  		eventMessages[j] = convert.EventToMessage(e)
   417  	}
   418  
   419  	// expect a call to lookup events by block ID and transaction ID
   420  	suite.events.On("ByBlockIDTransactionID", bID, txID).Return(eventsForTx, nil)
   421  
   422  	// create the handler
   423  	createHandler := func(txResults *storage.TransactionResults) *handler {
   424  		handler := &handler{
   425  			headers:            suite.headers,
   426  			events:             suite.events,
   427  			transactionResults: txResults,
   428  			commits:            suite.commits,
   429  			chain:              flow.Mainnet,
   430  		}
   431  		return handler
   432  	}
   433  
   434  	// concoctReq creates a GetEventsForBlockIDTransactionIDRequest
   435  	concoctReq := func(bID []byte, tID []byte) *execution.GetTransactionResultRequest {
   436  		return &execution.GetTransactionResultRequest{
   437  			BlockId:       bID,
   438  			TransactionId: tID,
   439  		}
   440  	}
   441  
   442  	// concoctIndexReq creates a GetTransactionByIndexRequest
   443  	concoctIndexReq := func(bID []byte, tIndex uint32) *execution.GetTransactionByIndexRequest {
   444  		return &execution.GetTransactionByIndexRequest{
   445  			BlockId: bID,
   446  			Index:   uint32(tIndex),
   447  		}
   448  	}
   449  
   450  	assertEqual := func(expected, actual *execution.GetTransactionResultResponse) {
   451  		suite.Require().Equal(expected.GetStatusCode(), actual.GetStatusCode())
   452  		suite.Require().Equal(expected.GetErrorMessage(), actual.GetErrorMessage())
   453  		suite.Require().ElementsMatch(expected.GetEvents(), actual.GetEvents())
   454  	}
   455  
   456  	// happy path - valid requests receives all events for the given transaction
   457  	suite.Run("happy path with valid events and no transaction error", func() {
   458  
   459  		// create the expected result
   460  		expectedResult := &execution.GetTransactionResultResponse{
   461  			StatusCode:   0,
   462  			ErrorMessage: "",
   463  			Events:       eventMessages,
   464  		}
   465  
   466  		// expect a call to lookup transaction result by block ID and transaction ID, return a result with no error
   467  		txResults := storage.NewTransactionResults(suite.T())
   468  		txResult := flow.TransactionResult{
   469  			TransactionID: flow.Identifier{},
   470  			ErrorMessage:  "",
   471  		}
   472  		txResults.On("ByBlockIDTransactionID", bID, txID).Return(&txResult, nil).Once()
   473  
   474  		handler := createHandler(txResults)
   475  
   476  		// create a valid API request
   477  		req := concoctReq(bID[:], txID[:])
   478  
   479  		// execute the GetTransactionResult call
   480  		actualResult, err := handler.GetTransactionResult(context.Background(), req)
   481  
   482  		// check that a successful response is received
   483  		suite.Require().NoError(err)
   484  
   485  		// check that all fields in response are as expected
   486  		assertEqual(expectedResult, actualResult)
   487  	})
   488  
   489  	// happy path - valid requests receives all events for the given transaction by index
   490  	suite.Run("index happy path with valid events and no transaction error", func() {
   491  
   492  		// create the expected result
   493  		expectedResult := &execution.GetTransactionResultResponse{
   494  			StatusCode:   0,
   495  			ErrorMessage: "",
   496  			Events:       eventMessages,
   497  		}
   498  
   499  		// expect a call to lookup transaction result by block ID and transaction ID, return a result with no error
   500  		txResults := storage.NewTransactionResults(suite.T())
   501  		txResult := flow.TransactionResult{
   502  			TransactionID: flow.Identifier{},
   503  			ErrorMessage:  "",
   504  		}
   505  		txResults.On("ByBlockIDTransactionIndex", bID, txIndex).Return(&txResult, nil).Once()
   506  
   507  		// expect a call to lookup events by block ID and tx index
   508  		suite.events.On("ByBlockIDTransactionIndex", bID, txIndex).Return(eventsForTx, nil).Once()
   509  
   510  		handler := createHandler(txResults)
   511  
   512  		// create a valid API request
   513  		req := concoctIndexReq(bID[:], txIndex)
   514  
   515  		// execute the GetTransactionResult call
   516  		actualResult, err := handler.GetTransactionResultByIndex(context.Background(), req)
   517  
   518  		// check that a successful response is received
   519  		suite.Require().NoError(err)
   520  
   521  		// check that all fields in response are as expected
   522  		assertEqual(expectedResult, actualResult)
   523  	})
   524  
   525  	// happy path - valid requests receives all events and an error for the given transaction
   526  	suite.Run("happy path with valid events and a transaction error", func() {
   527  
   528  		// create the expected result
   529  		expectedResult := &execution.GetTransactionResultResponse{
   530  			StatusCode:   1,
   531  			ErrorMessage: "runtime error",
   532  			Events:       eventMessages,
   533  		}
   534  
   535  		// setup the storage to return a transaction error
   536  		txResults := storage.NewTransactionResults(suite.T())
   537  		txResult := flow.TransactionResult{
   538  			TransactionID: txID,
   539  			ErrorMessage:  "runtime error",
   540  		}
   541  		txResults.On("ByBlockIDTransactionID", bID, txID).Return(&txResult, nil).Once()
   542  
   543  		handler := createHandler(txResults)
   544  
   545  		// create a valid API request
   546  		req := concoctReq(bID[:], txID[:])
   547  
   548  		// execute the GetEventsForBlockIDTransactionID call
   549  		actualResult, err := handler.GetTransactionResult(context.Background(), req)
   550  
   551  		// check that a successful response is received
   552  		suite.Require().NoError(err)
   553  
   554  		// check that all fields in response are as expected
   555  		assertEqual(expectedResult, actualResult)
   556  	})
   557  
   558  	// happy path - valid requests receives all events and an error for the given transaction
   559  	suite.Run("index happy path with valid events and a transaction error", func() {
   560  
   561  		// create the expected result
   562  		expectedResult := &execution.GetTransactionResultResponse{
   563  			StatusCode:   1,
   564  			ErrorMessage: "runtime error",
   565  			Events:       eventMessages,
   566  		}
   567  
   568  		// setup the storage to return a transaction error
   569  		txResults := storage.NewTransactionResults(suite.T())
   570  		txResult := flow.TransactionResult{
   571  			TransactionID: txID,
   572  			ErrorMessage:  "runtime error",
   573  		}
   574  		txResults.On("ByBlockIDTransactionIndex", bID, txIndex).Return(&txResult, nil).Once()
   575  
   576  		// expect a call to lookup events by block ID and tx index
   577  		suite.events.On("ByBlockIDTransactionIndex", bID, txIndex).Return(eventsForTx, nil).Once()
   578  
   579  		handler := createHandler(txResults)
   580  
   581  		// create a valid API request
   582  		req := concoctIndexReq(bID[:], txIndex)
   583  
   584  		// execute the GetEventsForBlockIDTransactionID call
   585  		actualResult, err := handler.GetTransactionResultByIndex(context.Background(), req)
   586  
   587  		// check that a successful response is received
   588  		suite.Require().NoError(err)
   589  
   590  		// check that all fields in response are as expected
   591  		assertEqual(expectedResult, actualResult)
   592  
   593  		// check that appropriate storage calls were made
   594  	})
   595  
   596  	// failure path - nil transaction ID in the request results in an error
   597  	suite.Run("request with nil tx ID", func() {
   598  
   599  		// create an API request with transaction ID as nil
   600  		req := concoctReq(bID[:], nil)
   601  
   602  		txResults := storage.NewTransactionResults(suite.T())
   603  		handler := createHandler(txResults)
   604  
   605  		_, err := handler.GetTransactionResult(context.Background(), req)
   606  
   607  		// check that an error was received
   608  		suite.Require().Error(err)
   609  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   610  	})
   611  
   612  	// failure path - nil block id in the request results in an error
   613  	suite.Run("request with nil block ID", func() {
   614  
   615  		// create an API request with a nil block id
   616  		req := concoctReq(nil, txID[:])
   617  
   618  		txResults := storage.NewTransactionResults(suite.T())
   619  		handler := createHandler(txResults)
   620  
   621  		_, err := handler.GetTransactionResult(context.Background(), req)
   622  
   623  		// check that an error was received
   624  		suite.Require().Error(err)
   625  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   626  	})
   627  
   628  	// failure path - nil block id in the index request results in an error
   629  	suite.Run("index request with nil block ID", func() {
   630  
   631  		// create an API request with a nil block id
   632  		req := concoctIndexReq(nil, txIndex)
   633  
   634  		txResults := storage.NewTransactionResults(suite.T())
   635  		handler := createHandler(txResults)
   636  
   637  		_, err := handler.GetTransactionResultByIndex(context.Background(), req)
   638  
   639  		// check that an error was received
   640  		suite.Require().Error(err)
   641  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   642  	})
   643  
   644  	// failure path - non-existent transaction ID in request results in an error
   645  	suite.Run("request with non-existent transaction ID", func() {
   646  
   647  		wrongTxID := unittest.IdentifierFixture()
   648  
   649  		// create an API request with the invalid transaction ID
   650  		req := concoctReq(bID[:], wrongTxID[:])
   651  
   652  		// expect a storage call for the invalid tx ID but return an error
   653  		txResults := storage.NewTransactionResults(suite.T())
   654  		txResults.On("ByBlockIDTransactionID", bID, wrongTxID).Return(nil, realstorage.ErrNotFound).Once()
   655  
   656  		handler := createHandler(txResults)
   657  
   658  		_, err := handler.GetTransactionResult(context.Background(), req)
   659  
   660  		// check that an error was received
   661  		suite.Require().Error(err)
   662  		errors.Is(err, status.Error(codes.NotFound, ""))
   663  	})
   664  
   665  	// failure path - non-existent transaction ID in request results in an exception
   666  	suite.Run("request with non-existent transaction ID, exception", func() {
   667  
   668  		wrongTxID := unittest.IdentifierFixture()
   669  
   670  		// create an API request with the invalid transaction ID
   671  		req := concoctReq(bID[:], wrongTxID[:])
   672  
   673  		// expect a storage call for the invalid tx ID but return an exception
   674  		txResults := storage.NewTransactionResults(suite.T())
   675  		txResults.On("ByBlockIDTransactionID", bID, wrongTxID).Return(nil, errors.New("internal-error")).Once()
   676  
   677  		handler := createHandler(txResults)
   678  
   679  		_, err := handler.GetTransactionResult(context.Background(), req)
   680  
   681  		// check that an error was received
   682  		suite.Require().Error(err)
   683  		errors.Is(err, status.Error(codes.Internal, ""))
   684  	})
   685  
   686  	// failure path - non-existent transaction index in request results in an error
   687  	suite.Run("request with non-existent transaction index", func() {
   688  
   689  		wrongTxIndex := txIndex + 1
   690  
   691  		// create an API request with the invalid transaction ID
   692  		req := concoctIndexReq(bID[:], wrongTxIndex)
   693  
   694  		// expect a storage call for the invalid tx ID but return an error
   695  		txResults := storage.NewTransactionResults(suite.T())
   696  		txResults.On("ByBlockIDTransactionIndex", bID, wrongTxIndex).Return(nil, realstorage.ErrNotFound).Once()
   697  
   698  		handler := createHandler(txResults)
   699  
   700  		_, err := handler.GetTransactionResultByIndex(context.Background(), req)
   701  
   702  		// check that an error was received
   703  		suite.Require().Error(err)
   704  		errors.Is(err, status.Error(codes.NotFound, ""))
   705  	})
   706  
   707  	// failure path - non-existent transaction index in request results in an exception
   708  	suite.Run("request with non-existent transaction index, exception", func() {
   709  
   710  		wrongTxIndex := txIndex + 1
   711  
   712  		// create an API request with the invalid transaction ID
   713  		req := concoctIndexReq(bID[:], wrongTxIndex)
   714  
   715  		// expect a storage call for the invalid tx ID but return an exception
   716  		txResults := storage.NewTransactionResults(suite.T())
   717  		txResults.On("ByBlockIDTransactionIndex", bID, wrongTxIndex).Return(nil, errors.New("internal-error")).Once()
   718  
   719  		handler := createHandler(txResults)
   720  
   721  		_, err := handler.GetTransactionResultByIndex(context.Background(), req)
   722  
   723  		// check that an error was received
   724  		suite.Require().Error(err)
   725  		errors.Is(err, status.Error(codes.Internal, ""))
   726  	})
   727  }
   728  
   729  // TestGetTransactionResultsByBlock tests TestGetTransactionResultsByBlockID API calls
   730  func (suite *Suite) TestGetTransactionResultsByBlockID() {
   731  
   732  	totalEvents := 10
   733  	block := unittest.BlockFixture()
   734  	tx := unittest.TransactionFixture()
   735  	bID := block.ID()
   736  	nonexistingBlockID := unittest.IdentifierFixture()
   737  	tx1ID := tx.ID()
   738  	tx2ID := tx.ID()
   739  	//txIndex := rand.Uint32()
   740  
   741  	// setup the events storage mock
   742  	eventsForTx1 := make([]flow.Event, totalEvents-3)
   743  	eventsForTx2 := make([]flow.Event, totalEvents-len(eventsForTx1))
   744  	eventsForBlock := make([]flow.Event, totalEvents)
   745  
   746  	convertedEventsForTx1 := make([]*entities.Event, len(eventsForTx1))
   747  	convertedEventsForTx2 := make([]*entities.Event, len(eventsForTx2))
   748  
   749  	for j := 0; j < len(eventsForTx1); j++ {
   750  		e := unittest.EventFixture(flow.EventAccountCreated, uint32(0), uint32(j), tx1ID, 0)
   751  		eventsForTx1[j] = e
   752  		eventsForBlock[j] = e
   753  		convertedEventsForTx1[j] = convert.EventToMessage(e)
   754  	}
   755  	for j := 0; j < len(eventsForTx2); j++ {
   756  		e := unittest.EventFixture(flow.EventAccountCreated, uint32(1), uint32(j), tx2ID, 0)
   757  		eventsForTx2[j] = e
   758  		eventsForBlock[len(eventsForTx1)+j] = e
   759  		convertedEventsForTx2[j] = convert.EventToMessage(e)
   760  	}
   761  
   762  	// create the handler
   763  	createHandler := func(txResults *storage.TransactionResults) *handler {
   764  		handler := &handler{
   765  			headers:            suite.headers,
   766  			events:             suite.events,
   767  			transactionResults: txResults,
   768  			commits:            suite.commits,
   769  			chain:              flow.Mainnet,
   770  		}
   771  		return handler
   772  	}
   773  
   774  	// concoctReq creates a GetTransactionResultsByBlockIDRequest
   775  	concoctReq := func(bID []byte) *execution.GetTransactionsByBlockIDRequest {
   776  		return &execution.GetTransactionsByBlockIDRequest{
   777  			BlockId: bID,
   778  		}
   779  	}
   780  
   781  	assertEqual := func(expected, actual *execution.GetTransactionResultsResponse) {
   782  
   783  		suite.Require().Len(expected.TransactionResults, len(actual.TransactionResults))
   784  
   785  		for i, txResult := range actual.TransactionResults {
   786  			suite.Require().Equal(txResult.GetStatusCode(), actual.TransactionResults[i].GetStatusCode())
   787  			suite.Require().Equal(txResult.GetErrorMessage(), actual.TransactionResults[i].GetErrorMessage())
   788  			suite.Require().ElementsMatch(txResult.GetEvents(), actual.TransactionResults[i].GetEvents())
   789  		}
   790  	}
   791  
   792  	// happy path - valid requests receives all events for the given transaction
   793  	suite.Run("happy path with valid events and no transaction error", func() {
   794  
   795  		suite.commits.On("ByBlockID", bID).Return(nil, nil).Once()
   796  
   797  		// expect a call to lookup events by block ID and transaction ID
   798  		suite.events.On("ByBlockID", bID).Return(eventsForBlock, nil).Once()
   799  
   800  		// create the expected result
   801  		expectedResult := &execution.GetTransactionResultsResponse{
   802  			TransactionResults: []*execution.GetTransactionResultResponse{
   803  				{
   804  					StatusCode:   0,
   805  					ErrorMessage: "",
   806  					Events:       convertedEventsForTx1,
   807  				},
   808  				{
   809  					StatusCode:   0,
   810  					ErrorMessage: "",
   811  					Events:       convertedEventsForTx1,
   812  				},
   813  			},
   814  		}
   815  
   816  		// expect a call to lookup transaction result by block ID return a result with no error
   817  		txResultsMock := storage.NewTransactionResults(suite.T())
   818  		txResults := []flow.TransactionResult{
   819  			{
   820  				TransactionID: tx1ID,
   821  				ErrorMessage:  "",
   822  			},
   823  			{
   824  				TransactionID: tx2ID,
   825  				ErrorMessage:  "",
   826  			},
   827  		}
   828  		txResultsMock.On("ByBlockID", bID).Return(txResults, nil).Once()
   829  
   830  		handler := createHandler(txResultsMock)
   831  
   832  		// create a valid API request
   833  		req := concoctReq(bID[:])
   834  
   835  		// execute the GetTransactionResult call
   836  		actualResult, err := handler.GetTransactionResultsByBlockID(context.Background(), req)
   837  
   838  		// check that a successful response is received
   839  		suite.Require().NoError(err)
   840  
   841  		// check that all fields in response are as expected
   842  		assertEqual(expectedResult, actualResult)
   843  	})
   844  
   845  	// happy path - valid requests receives all events and an error for the given transaction
   846  	suite.Run("happy path with valid events and a transaction error", func() {
   847  
   848  		suite.commits.On("ByBlockID", bID).Return(nil, nil).Once()
   849  
   850  		// expect a call to lookup events by block ID and transaction ID
   851  		suite.events.On("ByBlockID", bID).Return(eventsForBlock, nil).Once()
   852  
   853  		// create the expected result
   854  		expectedResult := &execution.GetTransactionResultsResponse{
   855  			TransactionResults: []*execution.GetTransactionResultResponse{
   856  				{
   857  					StatusCode:   0,
   858  					ErrorMessage: "",
   859  					Events:       convertedEventsForTx1,
   860  				},
   861  				{
   862  					StatusCode:   1,
   863  					ErrorMessage: "runtime error",
   864  					Events:       convertedEventsForTx2,
   865  				},
   866  			},
   867  		}
   868  
   869  		// expect a call to lookup transaction result by block ID return a result with no error
   870  		txResultsMock := storage.NewTransactionResults(suite.T())
   871  		txResults := []flow.TransactionResult{
   872  			{
   873  				TransactionID: tx1ID,
   874  				ErrorMessage:  "",
   875  			},
   876  			{
   877  				TransactionID: tx2ID,
   878  				ErrorMessage:  "runtime error",
   879  			},
   880  		}
   881  		txResultsMock.On("ByBlockID", bID).Return(txResults, nil).Once()
   882  
   883  		handler := createHandler(txResultsMock)
   884  
   885  		// create a valid API request
   886  		req := concoctReq(bID[:])
   887  
   888  		// execute the GetTransactionResult call
   889  		actualResult, err := handler.GetTransactionResultsByBlockID(context.Background(), req)
   890  
   891  		// check that a successful response is received
   892  		suite.Require().NoError(err)
   893  
   894  		// check that all fields in response are as expected
   895  		assertEqual(expectedResult, actualResult)
   896  	})
   897  
   898  	// failure path - nil block id in the request results in an error
   899  	suite.Run("request with nil block ID", func() {
   900  
   901  		// create an API request with a nil block id
   902  		req := concoctReq(nil)
   903  
   904  		txResults := storage.NewTransactionResults(suite.T())
   905  		handler := createHandler(txResults)
   906  
   907  		_, err := handler.GetTransactionResultsByBlockID(context.Background(), req)
   908  
   909  		// check that an error was received
   910  		suite.Require().Error(err)
   911  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
   912  	})
   913  
   914  	// failure path - nonexisting block id in the request results in not found error
   915  	suite.Run("request with nonexisting block ID", func() {
   916  
   917  		suite.commits.On("ByBlockID", nonexistingBlockID).Return(nil, realstorage.ErrNotFound).Once()
   918  
   919  		txResultsMock := storage.NewTransactionResults(suite.T())
   920  		handler := createHandler(txResultsMock)
   921  
   922  		// create a valid API request
   923  		req := concoctReq(nonexistingBlockID[:])
   924  
   925  		// execute the GetTransactionResult call
   926  		_, err := handler.GetTransactionResultsByBlockID(context.Background(), req)
   927  
   928  		// check that an error was received
   929  		suite.Require().Error(err)
   930  		errors.Is(err, status.Error(codes.NotFound, ""))
   931  	})
   932  }
   933  
   934  // TestGetTransactionErrorMessage tests the GetTransactionErrorMessage and GetTransactionErrorMessageByIndex API calls
   935  func (suite *Suite) TestGetTransactionErrorMessage() {
   936  	block := unittest.BlockFixture()
   937  	tx := unittest.TransactionFixture()
   938  	bID := block.ID()
   939  	txID := tx.ID()
   940  	txIndex := rand.Uint32()
   941  
   942  	// create the handler
   943  	createHandler := func(txResults *storage.TransactionResults) *handler {
   944  		handler := &handler{
   945  			headers:            suite.headers,
   946  			events:             suite.events,
   947  			transactionResults: txResults,
   948  			commits:            suite.commits,
   949  			chain:              flow.Mainnet,
   950  		}
   951  		return handler
   952  	}
   953  
   954  	// concoctReq creates a GetTransactionErrorMessageRequest
   955  	concoctReq := func(bID []byte, tID []byte) *execution.GetTransactionErrorMessageRequest {
   956  		return &execution.GetTransactionErrorMessageRequest{
   957  			BlockId:       bID,
   958  			TransactionId: tID,
   959  		}
   960  	}
   961  
   962  	// concoctIndexReq creates a GetTransactionErrorMessageByIndexRequest
   963  	concoctIndexReq := func(bID []byte, tIndex uint32) *execution.GetTransactionErrorMessageByIndexRequest {
   964  		return &execution.GetTransactionErrorMessageByIndexRequest{
   965  			BlockId: bID,
   966  			Index:   tIndex,
   967  		}
   968  	}
   969  
   970  	suite.Run("happy path - by tx id - no transaction error", func() {
   971  
   972  		// create the expected result
   973  		expectedResult := &execution.GetTransactionErrorMessageResponse{
   974  			TransactionId: convert.IdentifierToMessage(txID),
   975  			ErrorMessage:  "",
   976  		}
   977  
   978  		// expect a call to lookup transaction result by block ID and transaction ID, return a result with no error
   979  		txResults := storage.NewTransactionResults(suite.T())
   980  		txResult := flow.TransactionResult{
   981  			TransactionID: txID,
   982  			ErrorMessage:  "",
   983  		}
   984  		txResults.On("ByBlockIDTransactionID", bID, txID).Return(&txResult, nil).Once()
   985  
   986  		handler := createHandler(txResults)
   987  
   988  		// create a valid API request
   989  		req := concoctReq(bID[:], txID[:])
   990  
   991  		// execute the GetTransactionErrorMessage call
   992  		actualResult, err := handler.GetTransactionErrorMessage(context.Background(), req)
   993  
   994  		// check that a successful response is received
   995  		suite.Require().NoError(err)
   996  
   997  		// check that all fields in response are as expected
   998  		suite.Equal(expectedResult, actualResult)
   999  	})
  1000  
  1001  	suite.Run("happy path - at index - no transaction error", func() {
  1002  
  1003  		// create the expected result
  1004  		expectedResult := &execution.GetTransactionErrorMessageResponse{
  1005  			TransactionId: convert.IdentifierToMessage(txID),
  1006  			ErrorMessage:  "",
  1007  		}
  1008  
  1009  		// expect a call to lookup transaction result by block ID and transaction ID, return a result with no error
  1010  		txResults := storage.NewTransactionResults(suite.T())
  1011  		txResult := flow.TransactionResult{
  1012  			TransactionID: txID,
  1013  			ErrorMessage:  "",
  1014  		}
  1015  		txResults.On("ByBlockIDTransactionIndex", bID, txIndex).Return(&txResult, nil).Once()
  1016  
  1017  		handler := createHandler(txResults)
  1018  
  1019  		// create a valid API request
  1020  		req := concoctIndexReq(bID[:], txIndex)
  1021  
  1022  		// execute the GetTransactionResult call
  1023  		actualResult, err := handler.GetTransactionErrorMessageByIndex(context.Background(), req)
  1024  
  1025  		// check that a successful response is received
  1026  		suite.Require().NoError(err)
  1027  
  1028  		// check that all fields in response are as expected
  1029  		suite.Equal(expectedResult, actualResult)
  1030  	})
  1031  
  1032  	suite.Run("happy path - by tx id - transaction error", func() {
  1033  
  1034  		// create the expected result
  1035  		expectedResult := &execution.GetTransactionErrorMessageResponse{
  1036  			TransactionId: convert.IdentifierToMessage(txID),
  1037  			ErrorMessage:  "runtime error",
  1038  		}
  1039  
  1040  		// setup the storage to return a transaction error
  1041  		txResults := storage.NewTransactionResults(suite.T())
  1042  		txResult := flow.TransactionResult{
  1043  			TransactionID: txID,
  1044  			ErrorMessage:  "runtime error",
  1045  		}
  1046  		txResults.On("ByBlockIDTransactionID", bID, txID).Return(&txResult, nil).Once()
  1047  
  1048  		handler := createHandler(txResults)
  1049  
  1050  		// create a valid API request
  1051  		req := concoctReq(bID[:], txID[:])
  1052  
  1053  		// execute the GetTransactionErrorMessage call
  1054  		actualResult, err := handler.GetTransactionErrorMessage(context.Background(), req)
  1055  
  1056  		// check that a successful response is received
  1057  		suite.Require().NoError(err)
  1058  
  1059  		// check that all fields in response are as expected
  1060  		suite.Equal(expectedResult, actualResult)
  1061  	})
  1062  
  1063  	suite.Run("happy path - at index - transaction error", func() {
  1064  
  1065  		// create the expected result
  1066  		expectedResult := &execution.GetTransactionErrorMessageResponse{
  1067  			TransactionId: convert.IdentifierToMessage(txID),
  1068  			ErrorMessage:  "runtime error",
  1069  		}
  1070  
  1071  		// setup the storage to return a transaction error
  1072  		txResults := storage.NewTransactionResults(suite.T())
  1073  		txResult := flow.TransactionResult{
  1074  			TransactionID: txID,
  1075  			ErrorMessage:  "runtime error",
  1076  		}
  1077  		txResults.On("ByBlockIDTransactionIndex", bID, txIndex).Return(&txResult, nil).Once()
  1078  
  1079  		handler := createHandler(txResults)
  1080  
  1081  		// create a valid API request
  1082  		req := concoctIndexReq(bID[:], txIndex)
  1083  
  1084  		// execute the GetTransactionErrorMessageByIndex call
  1085  		actualResult, err := handler.GetTransactionErrorMessageByIndex(context.Background(), req)
  1086  
  1087  		// check that a successful response is received
  1088  		suite.Require().NoError(err)
  1089  
  1090  		// check that all fields in response are as expected
  1091  		suite.Equal(expectedResult, actualResult)
  1092  	})
  1093  
  1094  	// failure path - nil transaction ID in the request results in an error
  1095  	suite.Run("request with nil tx ID", func() {
  1096  
  1097  		// create an API request with transaction ID as nil
  1098  		req := concoctReq(bID[:], nil)
  1099  
  1100  		txResults := storage.NewTransactionResults(suite.T())
  1101  		handler := createHandler(txResults)
  1102  
  1103  		_, err := handler.GetTransactionErrorMessage(context.Background(), req)
  1104  
  1105  		// check that an error was received
  1106  		suite.Require().Error(err)
  1107  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
  1108  	})
  1109  
  1110  	// failure path - nil block id in the request results in an error
  1111  	suite.Run("request with nil block ID", func() {
  1112  
  1113  		// create an API request with a nil block id
  1114  		req := concoctReq(nil, txID[:])
  1115  
  1116  		txResults := storage.NewTransactionResults(suite.T())
  1117  		handler := createHandler(txResults)
  1118  
  1119  		_, err := handler.GetTransactionErrorMessage(context.Background(), req)
  1120  
  1121  		// check that an error was received
  1122  		suite.Require().Error(err)
  1123  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
  1124  	})
  1125  
  1126  	// failure path - nil block id in the index request results in an error
  1127  	suite.Run("index request with nil block ID", func() {
  1128  
  1129  		// create an API request with a nil block id
  1130  		req := concoctIndexReq(nil, txIndex)
  1131  
  1132  		txResults := storage.NewTransactionResults(suite.T())
  1133  		handler := createHandler(txResults)
  1134  
  1135  		_, err := handler.GetTransactionErrorMessageByIndex(context.Background(), req)
  1136  
  1137  		// check that an error was received
  1138  		suite.Require().Error(err)
  1139  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
  1140  	})
  1141  
  1142  	// failure path - non-existent transaction ID in request results in an error
  1143  	suite.Run("request with non-existent transaction ID", func() {
  1144  
  1145  		wrongTxID := unittest.IdentifierFixture()
  1146  
  1147  		// create an API request with the invalid transaction ID
  1148  		req := concoctReq(bID[:], wrongTxID[:])
  1149  
  1150  		// expect a storage call for the invalid tx ID but return an error
  1151  		txResults := storage.NewTransactionResults(suite.T())
  1152  		txResults.On("ByBlockIDTransactionID", bID, wrongTxID).Return(nil, realstorage.ErrNotFound).Once()
  1153  
  1154  		handler := createHandler(txResults)
  1155  
  1156  		_, err := handler.GetTransactionErrorMessage(context.Background(), req)
  1157  
  1158  		// check that an error was received
  1159  		suite.Require().Error(err)
  1160  		errors.Is(err, status.Error(codes.NotFound, ""))
  1161  	})
  1162  
  1163  	// failure path - non-existent transaction ID in request results in an exception
  1164  	suite.Run("request with non-existent transaction ID, exception", func() {
  1165  
  1166  		wrongTxID := unittest.IdentifierFixture()
  1167  
  1168  		// create an API request with the invalid transaction ID
  1169  		req := concoctReq(bID[:], wrongTxID[:])
  1170  
  1171  		// expect a storage call for the invalid tx ID but return an exception
  1172  		txResults := storage.NewTransactionResults(suite.T())
  1173  		txResults.On("ByBlockIDTransactionID", bID, wrongTxID).Return(nil, errors.New("internal-error")).Once()
  1174  
  1175  		handler := createHandler(txResults)
  1176  
  1177  		_, err := handler.GetTransactionErrorMessage(context.Background(), req)
  1178  
  1179  		// check that an error was received
  1180  		suite.Require().Error(err)
  1181  		errors.Is(err, status.Error(codes.Internal, ""))
  1182  	})
  1183  
  1184  	// failure path - non-existent transaction index in request results in an error
  1185  	suite.Run("request with non-existent transaction index", func() {
  1186  
  1187  		wrongTxIndex := txIndex + 1
  1188  
  1189  		// create an API request with the invalid transaction ID
  1190  		req := concoctIndexReq(bID[:], wrongTxIndex)
  1191  
  1192  		// expect a storage call for the invalid tx ID but return an error
  1193  		txResults := storage.NewTransactionResults(suite.T())
  1194  		txResults.On("ByBlockIDTransactionIndex", bID, wrongTxIndex).Return(nil, realstorage.ErrNotFound).Once()
  1195  
  1196  		handler := createHandler(txResults)
  1197  
  1198  		_, err := handler.GetTransactionErrorMessageByIndex(context.Background(), req)
  1199  
  1200  		// check that an error was received
  1201  		suite.Require().Error(err)
  1202  		errors.Is(err, status.Error(codes.NotFound, ""))
  1203  	})
  1204  
  1205  	// failure path - non-existent transaction index in request results in an exception
  1206  	suite.Run("request with non-existent transaction index, exception", func() {
  1207  
  1208  		wrongTxIndex := txIndex + 1
  1209  
  1210  		// create an API request with the invalid transaction ID
  1211  		req := concoctIndexReq(bID[:], wrongTxIndex)
  1212  
  1213  		// expect a storage call for the invalid tx ID but return an exception
  1214  		txResults := storage.NewTransactionResults(suite.T())
  1215  		txResults.On("ByBlockIDTransactionIndex", bID, wrongTxIndex).Return(nil, errors.New("internal-error")).Once()
  1216  
  1217  		handler := createHandler(txResults)
  1218  
  1219  		_, err := handler.GetTransactionErrorMessageByIndex(context.Background(), req)
  1220  
  1221  		// check that an error was received
  1222  		suite.Require().Error(err)
  1223  		errors.Is(err, status.Error(codes.Internal, ""))
  1224  	})
  1225  }
  1226  
  1227  // TestGetTransactionErrorMessagesByBlockID tests GetTransactionErrorMessagesByBlockID API calls
  1228  func (suite *Suite) TestGetTransactionErrorMessagesByBlockID() {
  1229  	block := unittest.BlockFixture()
  1230  	tx := unittest.TransactionFixture()
  1231  	bID := block.ID()
  1232  	nonexistingBlockID := unittest.IdentifierFixture()
  1233  	tx1ID := tx.ID()
  1234  	tx2ID := tx.ID()
  1235  	tx3ID := tx.ID()
  1236  
  1237  	// create the handler
  1238  	createHandler := func(txResults *storage.TransactionResults) *handler {
  1239  		handler := &handler{
  1240  			headers:            suite.headers,
  1241  			events:             suite.events,
  1242  			transactionResults: txResults,
  1243  			commits:            suite.commits,
  1244  			chain:              flow.Mainnet,
  1245  		}
  1246  		return handler
  1247  	}
  1248  
  1249  	// concoctReq creates a GetTransactionErrorMessagesByBlockIDRequest
  1250  	concoctReq := func(bID []byte) *execution.GetTransactionErrorMessagesByBlockIDRequest {
  1251  		return &execution.GetTransactionErrorMessagesByBlockIDRequest{
  1252  			BlockId: bID,
  1253  		}
  1254  	}
  1255  
  1256  	// happy path - if no transaction errors are found, an empty list is returned
  1257  	suite.Run("happy path with no transaction error", func() {
  1258  		suite.commits.On("ByBlockID", bID).Return(nil, nil).Once()
  1259  
  1260  		// create the expected result
  1261  		expectedResult := &execution.GetTransactionErrorMessagesResponse{
  1262  			Results: []*execution.GetTransactionErrorMessagesResponse_Result{},
  1263  		}
  1264  
  1265  		// expect a call to lookup transaction result by block ID return a result with no error
  1266  		txResultsMock := storage.NewTransactionResults(suite.T())
  1267  		txResults := []flow.TransactionResult{
  1268  			{
  1269  				TransactionID: tx1ID,
  1270  				ErrorMessage:  "",
  1271  			},
  1272  			{
  1273  				TransactionID: tx2ID,
  1274  				ErrorMessage:  "",
  1275  			},
  1276  		}
  1277  		txResultsMock.On("ByBlockID", bID).Return(txResults, nil).Once()
  1278  
  1279  		handler := createHandler(txResultsMock)
  1280  
  1281  		// create a valid API request
  1282  		req := concoctReq(bID[:])
  1283  
  1284  		// execute the GetTransactionErrorMessagesByBlockID call
  1285  		actualResult, err := handler.GetTransactionErrorMessagesByBlockID(context.Background(), req)
  1286  
  1287  		// check that a successful response is received
  1288  		suite.Require().NoError(err)
  1289  
  1290  		// check that all fields in response are as expected
  1291  		suite.Assert().ElementsMatch(expectedResult.Results, actualResult.Results)
  1292  	})
  1293  
  1294  	// happy path - valid requests receives error messages for all failed transactions.
  1295  	suite.Run("happy path with transaction errors", func() {
  1296  
  1297  		suite.commits.On("ByBlockID", bID).Return(nil, nil).Once()
  1298  
  1299  		// create the expected result
  1300  		expectedResult := &execution.GetTransactionErrorMessagesResponse{
  1301  			Results: []*execution.GetTransactionErrorMessagesResponse_Result{
  1302  				{
  1303  					TransactionId: convert.IdentifierToMessage(tx2ID),
  1304  					Index:         1,
  1305  					ErrorMessage:  "runtime error",
  1306  				},
  1307  				{
  1308  					TransactionId: convert.IdentifierToMessage(tx3ID),
  1309  					Index:         2,
  1310  					ErrorMessage:  "runtime error",
  1311  				},
  1312  			},
  1313  		}
  1314  
  1315  		// expect a call to lookup transaction result by block ID return a result with no error
  1316  		txResultsMock := storage.NewTransactionResults(suite.T())
  1317  		txResults := []flow.TransactionResult{
  1318  			{
  1319  				TransactionID: tx1ID,
  1320  				ErrorMessage:  "",
  1321  			},
  1322  			{
  1323  				TransactionID: tx2ID,
  1324  				ErrorMessage:  "runtime error",
  1325  			},
  1326  			{
  1327  				TransactionID: tx3ID,
  1328  				ErrorMessage:  "runtime error",
  1329  			},
  1330  		}
  1331  		txResultsMock.On("ByBlockID", bID).Return(txResults, nil).Once()
  1332  
  1333  		handler := createHandler(txResultsMock)
  1334  
  1335  		// create a valid API request
  1336  		req := concoctReq(bID[:])
  1337  
  1338  		// execute the GetTransactionErrorMessagesByBlockID call
  1339  		actualResult, err := handler.GetTransactionErrorMessagesByBlockID(context.Background(), req)
  1340  
  1341  		// check that a successful response is received
  1342  		suite.Require().NoError(err)
  1343  
  1344  		// check that all fields in response are as expected
  1345  		suite.Assert().ElementsMatch(expectedResult.Results, actualResult.Results)
  1346  	})
  1347  
  1348  	// failure path - nil block id in the request results in an error
  1349  	suite.Run("request with nil block ID", func() {
  1350  
  1351  		// create an API request with a nil block id
  1352  		req := concoctReq(nil)
  1353  
  1354  		txResults := storage.NewTransactionResults(suite.T())
  1355  		handler := createHandler(txResults)
  1356  
  1357  		_, err := handler.GetTransactionErrorMessagesByBlockID(context.Background(), req)
  1358  
  1359  		// check that an error was received
  1360  		suite.Require().Error(err)
  1361  		errors.Is(err, status.Error(codes.InvalidArgument, ""))
  1362  	})
  1363  
  1364  	// failure path - nonexisting block id in the request results in not found error
  1365  	suite.Run("request with nonexisting block ID", func() {
  1366  
  1367  		suite.commits.On("ByBlockID", nonexistingBlockID).Return(nil, realstorage.ErrNotFound).Once()
  1368  
  1369  		txResultsMock := storage.NewTransactionResults(suite.T())
  1370  		handler := createHandler(txResultsMock)
  1371  
  1372  		// create a valid API request
  1373  		req := concoctReq(nonexistingBlockID[:])
  1374  
  1375  		// execute the GetTransactionResult call
  1376  		_, err := handler.GetTransactionErrorMessagesByBlockID(context.Background(), req)
  1377  
  1378  		// check that an error was received
  1379  		suite.Require().Error(err)
  1380  		errors.Is(err, status.Error(codes.NotFound, ""))
  1381  	})
  1382  }