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