github.com/onflow/flow-go@v0.33.17/engine/access/rpc/backend/backend_transactions_test.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  
     8  	"github.com/dgraph-io/badger/v2"
     9  	jsoncdc "github.com/onflow/cadence/encoding/json"
    10  	"github.com/onflow/flow/protobuf/go/flow/access"
    11  	"github.com/onflow/flow/protobuf/go/flow/entities"
    12  	execproto "github.com/onflow/flow/protobuf/go/flow/execution"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  
    18  	acc "github.com/onflow/flow-go/access"
    19  	connectionmock "github.com/onflow/flow-go/engine/access/rpc/connection/mock"
    20  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    21  	"github.com/onflow/flow-go/fvm/blueprints"
    22  	"github.com/onflow/flow-go/model/flow"
    23  	"github.com/onflow/flow-go/model/flow/filter"
    24  	syncmock "github.com/onflow/flow-go/module/state_synchronization/mock"
    25  	"github.com/onflow/flow-go/state/protocol"
    26  	bprotocol "github.com/onflow/flow-go/state/protocol/badger"
    27  	"github.com/onflow/flow-go/state/protocol/util"
    28  	"github.com/onflow/flow-go/storage"
    29  	"github.com/onflow/flow-go/utils/unittest"
    30  	"github.com/onflow/flow-go/utils/unittest/generator"
    31  )
    32  
    33  const expectedErrorMsg = "expected test error"
    34  
    35  func (suite *Suite) withPreConfiguredState(f func(snap protocol.Snapshot)) {
    36  	identities := unittest.CompleteIdentitySet()
    37  	rootSnapshot := unittest.RootSnapshotFixture(identities)
    38  	util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.ParticipantState) {
    39  		epochBuilder := unittest.NewEpochBuilder(suite.T(), state)
    40  
    41  		epochBuilder.
    42  			BuildEpoch().
    43  			CompleteEpoch()
    44  
    45  		// get heights of each phase in built epochs
    46  		epoch1, ok := epochBuilder.EpochHeights(1)
    47  		require.True(suite.T(), ok)
    48  
    49  		// setup AtHeight mock returns for State
    50  		for _, height := range epoch1.Range() {
    51  			suite.state.On("AtHeight", epoch1.Range()).Return(state.AtHeight(height))
    52  		}
    53  
    54  		snap := state.AtHeight(epoch1.FinalHeight())
    55  		suite.state.On("Final").Return(snap)
    56  		suite.communicator.On("CallAvailableNode",
    57  			mock.Anything,
    58  			mock.Anything,
    59  			mock.Anything).
    60  			Return(nil).Once()
    61  
    62  		f(snap)
    63  	})
    64  }
    65  
    66  // TestGetTransactionResultReturnsUnknown returns unknown result when tx not found
    67  func (suite *Suite) TestGetTransactionResultReturnsUnknown() {
    68  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
    69  		block := unittest.BlockFixture()
    70  		tbody := unittest.TransactionBodyFixture()
    71  		tx := unittest.TransactionFixture()
    72  		tx.TransactionBody = tbody
    73  
    74  		coll := flow.CollectionFromTransactions([]*flow.Transaction{&tx})
    75  		suite.state.On("AtBlockID", block.ID()).Return(snap, nil).Once()
    76  
    77  		suite.transactions.
    78  			On("ByID", tx.ID()).
    79  			Return(nil, storage.ErrNotFound)
    80  
    81  		params := suite.defaultBackendParams()
    82  		params.Communicator = suite.communicator
    83  
    84  		backend, err := New(params)
    85  		suite.Require().NoError(err)
    86  
    87  		res, err := backend.GetTransactionResult(
    88  			context.Background(),
    89  			tx.ID(),
    90  			block.ID(),
    91  			coll.ID(),
    92  			entities.EventEncodingVersion_JSON_CDC_V0,
    93  		)
    94  		suite.Require().NoError(err)
    95  		suite.Require().Equal(res.Status, flow.TransactionStatusUnknown)
    96  	})
    97  }
    98  
    99  // TestGetTransactionResultReturnsTransactionError returns error from transaction storage
   100  func (suite *Suite) TestGetTransactionResultReturnsTransactionError() {
   101  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   102  		block := unittest.BlockFixture()
   103  		tbody := unittest.TransactionBodyFixture()
   104  		tx := unittest.TransactionFixture()
   105  		tx.TransactionBody = tbody
   106  
   107  		coll := flow.CollectionFromTransactions([]*flow.Transaction{&tx})
   108  
   109  		suite.transactions.
   110  			On("ByID", tx.ID()).
   111  			Return(nil, fmt.Errorf("some other error"))
   112  
   113  		suite.blocks.
   114  			On("ByID", block.ID()).
   115  			Return(&block, nil).
   116  			Once()
   117  
   118  		suite.state.On("AtBlockID", block.ID()).Return(snap, nil).Once()
   119  
   120  		params := suite.defaultBackendParams()
   121  		params.Communicator = suite.communicator
   122  
   123  		backend, err := New(params)
   124  		suite.Require().NoError(err)
   125  
   126  		_, err = backend.GetTransactionResult(
   127  			context.Background(),
   128  			tx.ID(),
   129  			block.ID(),
   130  			coll.ID(),
   131  			entities.EventEncodingVersion_JSON_CDC_V0,
   132  		)
   133  		suite.Require().Equal(err, status.Errorf(codes.Internal, "failed to find: %v", fmt.Errorf("some other error")))
   134  	})
   135  }
   136  
   137  // TestGetTransactionResultReturnsValidTransactionResultFromHistoricNode tests lookup in historic nodes
   138  func (suite *Suite) TestGetTransactionResultReturnsValidTransactionResultFromHistoricNode() {
   139  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   140  		block := unittest.BlockFixture()
   141  		tbody := unittest.TransactionBodyFixture()
   142  		tx := unittest.TransactionFixture()
   143  		tx.TransactionBody = tbody
   144  
   145  		coll := flow.CollectionFromTransactions([]*flow.Transaction{&tx})
   146  
   147  		suite.transactions.
   148  			On("ByID", tx.ID()).
   149  			Return(nil, storage.ErrNotFound)
   150  
   151  		suite.state.On("AtBlockID", block.ID()).Return(snap, nil).Once()
   152  
   153  		transactionResultResponse := access.TransactionResultResponse{
   154  			Status:     entities.TransactionStatus_EXECUTED,
   155  			StatusCode: uint32(entities.TransactionStatus_EXECUTED),
   156  		}
   157  
   158  		suite.historicalAccessClient.
   159  			On("GetTransactionResult", mock.Anything, mock.Anything).
   160  			Return(&transactionResultResponse, nil).Once()
   161  
   162  		params := suite.defaultBackendParams()
   163  		params.HistoricalAccessNodes = []access.AccessAPIClient{suite.historicalAccessClient}
   164  		params.Communicator = suite.communicator
   165  
   166  		backend, err := New(params)
   167  		suite.Require().NoError(err)
   168  
   169  		resp, err := backend.GetTransactionResult(
   170  			context.Background(),
   171  			tx.ID(),
   172  			block.ID(),
   173  			coll.ID(),
   174  			entities.EventEncodingVersion_JSON_CDC_V0,
   175  		)
   176  		suite.Require().NoError(err)
   177  		suite.Require().Equal(flow.TransactionStatusExecuted, resp.Status)
   178  		suite.Require().Equal(uint(flow.TransactionStatusExecuted), resp.StatusCode)
   179  	})
   180  }
   181  
   182  func (suite *Suite) withGetTransactionCachingTestSetup(f func(b *flow.Block, t *flow.Transaction)) {
   183  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   184  		block := unittest.BlockFixture()
   185  		tbody := unittest.TransactionBodyFixture()
   186  		tx := unittest.TransactionFixture()
   187  		tx.TransactionBody = tbody
   188  
   189  		suite.transactions.
   190  			On("ByID", tx.ID()).
   191  			Return(nil, storage.ErrNotFound)
   192  
   193  		suite.state.On("AtBlockID", block.ID()).Return(snap, nil).Once()
   194  
   195  		f(&block, &tx)
   196  	})
   197  }
   198  
   199  // TestGetTransactionResultFromCache get historic transaction result from cache
   200  func (suite *Suite) TestGetTransactionResultFromCache() {
   201  	suite.withGetTransactionCachingTestSetup(func(block *flow.Block, tx *flow.Transaction) {
   202  		transactionResultResponse := access.TransactionResultResponse{
   203  			Status:     entities.TransactionStatus_EXECUTED,
   204  			StatusCode: uint32(entities.TransactionStatus_EXECUTED),
   205  		}
   206  
   207  		suite.historicalAccessClient.
   208  			On("GetTransactionResult", mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("*access.GetTransactionRequest")).
   209  			Return(&transactionResultResponse, nil).Once()
   210  
   211  		params := suite.defaultBackendParams()
   212  		params.HistoricalAccessNodes = []access.AccessAPIClient{suite.historicalAccessClient}
   213  		params.Communicator = suite.communicator
   214  		params.TxResultCacheSize = 10
   215  
   216  		backend, err := New(params)
   217  		suite.Require().NoError(err)
   218  
   219  		coll := flow.CollectionFromTransactions([]*flow.Transaction{tx})
   220  
   221  		resp, err := backend.GetTransactionResult(
   222  			context.Background(),
   223  			tx.ID(),
   224  			block.ID(),
   225  			coll.ID(),
   226  			entities.EventEncodingVersion_JSON_CDC_V0,
   227  		)
   228  		suite.Require().NoError(err)
   229  		suite.Require().Equal(flow.TransactionStatusExecuted, resp.Status)
   230  		suite.Require().Equal(uint(flow.TransactionStatusExecuted), resp.StatusCode)
   231  
   232  		resp2, err := backend.GetTransactionResult(
   233  			context.Background(),
   234  			tx.ID(),
   235  			block.ID(),
   236  			coll.ID(),
   237  			entities.EventEncodingVersion_JSON_CDC_V0,
   238  		)
   239  		suite.Require().NoError(err)
   240  		suite.Require().Equal(flow.TransactionStatusExecuted, resp2.Status)
   241  		suite.Require().Equal(uint(flow.TransactionStatusExecuted), resp2.StatusCode)
   242  
   243  		suite.historicalAccessClient.AssertExpectations(suite.T())
   244  	})
   245  }
   246  
   247  // TestGetTransactionResultCacheNonExistent tests caches non existing result
   248  func (suite *Suite) TestGetTransactionResultCacheNonExistent() {
   249  	suite.withGetTransactionCachingTestSetup(func(block *flow.Block, tx *flow.Transaction) {
   250  		suite.historicalAccessClient.
   251  			On("GetTransactionResult", mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("*access.GetTransactionRequest")).
   252  			Return(nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", tx.ID())).Once()
   253  
   254  		params := suite.defaultBackendParams()
   255  		params.HistoricalAccessNodes = []access.AccessAPIClient{suite.historicalAccessClient}
   256  		params.Communicator = suite.communicator
   257  		params.TxResultCacheSize = 10
   258  
   259  		backend, err := New(params)
   260  		suite.Require().NoError(err)
   261  
   262  		coll := flow.CollectionFromTransactions([]*flow.Transaction{tx})
   263  
   264  		resp, err := backend.GetTransactionResult(
   265  			context.Background(),
   266  			tx.ID(),
   267  			block.ID(),
   268  			coll.ID(),
   269  			entities.EventEncodingVersion_JSON_CDC_V0,
   270  		)
   271  		suite.Require().NoError(err)
   272  		suite.Require().Equal(flow.TransactionStatusUnknown, resp.Status)
   273  		suite.Require().Equal(uint(flow.TransactionStatusUnknown), resp.StatusCode)
   274  
   275  		// ensure the unknown transaction is cached when not found anywhere
   276  		txStatus := flow.TransactionStatusUnknown
   277  		res, ok := backend.txResultCache.Get(tx.ID())
   278  		suite.Require().True(ok)
   279  		suite.Require().Equal(res, &acc.TransactionResult{
   280  			Status:     txStatus,
   281  			StatusCode: uint(txStatus),
   282  		})
   283  
   284  		suite.historicalAccessClient.AssertExpectations(suite.T())
   285  	})
   286  }
   287  
   288  // TestGetTransactionResultUnknownFromCache retrieve unknown result from cache.
   289  func (suite *Suite) TestGetTransactionResultUnknownFromCache() {
   290  	suite.withGetTransactionCachingTestSetup(func(block *flow.Block, tx *flow.Transaction) {
   291  		suite.historicalAccessClient.
   292  			On("GetTransactionResult", mock.AnythingOfType("*context.emptyCtx"), mock.AnythingOfType("*access.GetTransactionRequest")).
   293  			Return(nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", tx.ID())).Once()
   294  
   295  		params := suite.defaultBackendParams()
   296  		params.HistoricalAccessNodes = []access.AccessAPIClient{suite.historicalAccessClient}
   297  		params.Communicator = suite.communicator
   298  		params.TxResultCacheSize = 10
   299  
   300  		backend, err := New(params)
   301  		suite.Require().NoError(err)
   302  
   303  		coll := flow.CollectionFromTransactions([]*flow.Transaction{tx})
   304  
   305  		resp, err := backend.GetTransactionResult(
   306  			context.Background(),
   307  			tx.ID(),
   308  			block.ID(),
   309  			coll.ID(),
   310  			entities.EventEncodingVersion_JSON_CDC_V0,
   311  		)
   312  		suite.Require().NoError(err)
   313  		suite.Require().Equal(flow.TransactionStatusUnknown, resp.Status)
   314  		suite.Require().Equal(uint(flow.TransactionStatusUnknown), resp.StatusCode)
   315  
   316  		// ensure the unknown transaction is cached when not found anywhere
   317  		txStatus := flow.TransactionStatusUnknown
   318  		res, ok := backend.txResultCache.Get(tx.ID())
   319  		suite.Require().True(ok)
   320  		suite.Require().Equal(res, &acc.TransactionResult{
   321  			Status:     txStatus,
   322  			StatusCode: uint(txStatus),
   323  		})
   324  
   325  		resp2, err := backend.GetTransactionResult(
   326  			context.Background(),
   327  			tx.ID(),
   328  			block.ID(),
   329  			coll.ID(),
   330  			entities.EventEncodingVersion_JSON_CDC_V0,
   331  		)
   332  		suite.Require().NoError(err)
   333  		suite.Require().Equal(flow.TransactionStatusUnknown, resp2.Status)
   334  		suite.Require().Equal(uint(flow.TransactionStatusUnknown), resp2.StatusCode)
   335  
   336  		suite.historicalAccessClient.AssertExpectations(suite.T())
   337  	})
   338  }
   339  
   340  // TestLookupTransactionErrorMessage_HappyPath tests lookup of a transaction error message. In a happy path if it wasn't found in the cache, it
   341  // has to be fetched from the execution node, otherwise served from the cache.
   342  // If the transaction has not failed, the error message must be empty.
   343  func (suite *Suite) TestLookupTransactionErrorMessage_HappyPath() {
   344  	block := unittest.BlockFixture()
   345  	blockId := block.ID()
   346  	failedTx := unittest.TransactionFixture()
   347  	failedTxId := failedTx.ID()
   348  
   349  	_, fixedENIDs := suite.setupReceipts(&block)
   350  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   351  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   352  
   353  	// create a mock connection factory
   354  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   355  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   356  
   357  	params := suite.defaultBackendParams()
   358  	// the connection factory should be used to get the execution node client
   359  	params.ConnFactory = connFactory
   360  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   361  
   362  	backend, err := New(params)
   363  	suite.Require().NoError(err)
   364  
   365  	expectedErrorMsg := "some error"
   366  
   367  	exeEventReq := &execproto.GetTransactionErrorMessageRequest{
   368  		BlockId:       blockId[:],
   369  		TransactionId: failedTxId[:],
   370  	}
   371  
   372  	exeEventResp := &execproto.GetTransactionErrorMessageResponse{
   373  		TransactionId: failedTxId[:],
   374  		ErrorMessage:  expectedErrorMsg,
   375  	}
   376  
   377  	suite.execClient.On("GetTransactionErrorMessage", mock.Anything, exeEventReq).Return(exeEventResp, nil).Once()
   378  
   379  	errMsg, err := backend.LookupErrorMessageByTransactionID(context.Background(), blockId, failedTxId)
   380  	suite.Require().NoError(err)
   381  	suite.Require().Equal(expectedErrorMsg, errMsg)
   382  
   383  	// ensure the transaction error message is cached after retrieval; we do this by mocking the grpc call
   384  	// only once
   385  	errMsg, err = backend.LookupErrorMessageByTransactionID(context.Background(), blockId, failedTxId)
   386  	suite.Require().NoError(err)
   387  	suite.Require().Equal(expectedErrorMsg, errMsg)
   388  	suite.assertAllExpectations()
   389  }
   390  
   391  // TestLookupTransactionErrorMessage_FailedToFetch tests lookup of a transaction error message, when a transaction result
   392  // is not in the cache and needs to be fetched from EN, but the EN fails to return it.
   393  func (suite *Suite) TestLookupTransactionErrorMessage_FailedToFetch() {
   394  	block := unittest.BlockFixture()
   395  	blockId := block.ID()
   396  	failedTx := unittest.TransactionFixture()
   397  	failedTxId := failedTx.ID()
   398  
   399  	_, fixedENIDs := suite.setupReceipts(&block)
   400  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   401  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   402  
   403  	// create a mock connection factory
   404  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   405  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   406  
   407  	params := suite.defaultBackendParams()
   408  	// the connection factory should be used to get the execution node client
   409  	params.ConnFactory = connFactory
   410  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   411  
   412  	backend, err := New(params)
   413  	suite.Require().NoError(err)
   414  
   415  	// lookup should try each of the 2 ENs in fixedENIDs
   416  	suite.execClient.On("GetTransactionErrorMessage", mock.Anything, mock.Anything).Return(nil,
   417  		status.Error(codes.Unavailable, "")).Twice()
   418  
   419  	errMsg, err := backend.LookupErrorMessageByTransactionID(context.Background(), blockId, failedTxId)
   420  	suite.Require().Error(err)
   421  	suite.Require().Equal(codes.Unavailable, status.Code(err))
   422  	suite.Require().Empty(errMsg)
   423  
   424  	suite.assertAllExpectations()
   425  }
   426  
   427  // TestLookupTransactionErrorMessageByIndex_HappyPath tests lookup of a transaction error message by index.
   428  // In a happy path if it wasn't found in the cache, it has to be fetched from the execution node, otherwise served from the cache.
   429  // If the transaction has not failed, the error message must be empty.
   430  func (suite *Suite) TestLookupTransactionErrorMessageByIndex_HappyPath() {
   431  	block := unittest.BlockFixture()
   432  	blockId := block.ID()
   433  	failedTx := unittest.TransactionFixture()
   434  	failedTxId := failedTx.ID()
   435  	failedTxIndex := rand.Uint32()
   436  
   437  	suite.transactionResults.On("ByBlockIDTransactionIndex", blockId, failedTxIndex).
   438  		Return(&flow.LightTransactionResult{
   439  			TransactionID:   failedTxId,
   440  			Failed:          true,
   441  			ComputationUsed: 0,
   442  		}, nil).Twice()
   443  
   444  	_, fixedENIDs := suite.setupReceipts(&block)
   445  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   446  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   447  
   448  	// create a mock connection factory
   449  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   450  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   451  
   452  	// create a mock index reporter
   453  	reporter := syncmock.NewIndexReporter(suite.T())
   454  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   455  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   456  
   457  	params := suite.defaultBackendParams()
   458  
   459  	// the connection factory should be used to get the execution node client
   460  	params.ConnFactory = connFactory
   461  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   462  
   463  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   464  	err := params.TxResultsIndex.Initialize(reporter)
   465  	suite.Require().NoError(err)
   466  
   467  	backend, err := New(params)
   468  	suite.Require().NoError(err)
   469  
   470  	expectedErrorMsg := "some error"
   471  
   472  	exeEventReq := &execproto.GetTransactionErrorMessageByIndexRequest{
   473  		BlockId: blockId[:],
   474  		Index:   failedTxIndex,
   475  	}
   476  
   477  	exeEventResp := &execproto.GetTransactionErrorMessageResponse{
   478  		TransactionId: failedTxId[:],
   479  		ErrorMessage:  expectedErrorMsg,
   480  	}
   481  
   482  	suite.execClient.On("GetTransactionErrorMessageByIndex", mock.Anything, exeEventReq).Return(exeEventResp, nil).Once()
   483  
   484  	errMsg, err := backend.LookupErrorMessageByIndex(context.Background(), blockId, block.Header.Height, failedTxIndex)
   485  	suite.Require().NoError(err)
   486  	suite.Require().Equal(expectedErrorMsg, errMsg)
   487  
   488  	// ensure the transaction error message is cached after retrieval; we do this by mocking the grpc call
   489  	// only once
   490  	errMsg, err = backend.LookupErrorMessageByIndex(context.Background(), blockId, block.Header.Height, failedTxIndex)
   491  	suite.Require().NoError(err)
   492  	suite.Require().Equal(expectedErrorMsg, errMsg)
   493  	suite.assertAllExpectations()
   494  }
   495  
   496  // TestLookupTransactionErrorMessageByIndex_UnknownTransaction tests lookup of a transaction error message by index,
   497  // when a transaction result has not been synced yet, in this case nothing we can do but return an error.
   498  func (suite *Suite) TestLookupTransactionErrorMessageByIndex_UnknownTransaction() {
   499  	block := unittest.BlockFixture()
   500  	blockId := block.ID()
   501  	failedTxIndex := rand.Uint32()
   502  
   503  	suite.transactionResults.On("ByBlockIDTransactionIndex", blockId, failedTxIndex).
   504  		Return(nil, storage.ErrNotFound).Once()
   505  
   506  	// create a mock index reporter
   507  	reporter := syncmock.NewIndexReporter(suite.T())
   508  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   509  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   510  
   511  	params := suite.defaultBackendParams()
   512  
   513  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   514  	err := params.TxResultsIndex.Initialize(reporter)
   515  	suite.Require().NoError(err)
   516  
   517  	backend, err := New(params)
   518  	suite.Require().NoError(err)
   519  
   520  	errMsg, err := backend.LookupErrorMessageByIndex(context.Background(), blockId, block.Header.Height, failedTxIndex)
   521  	suite.Require().Error(err)
   522  	suite.Require().Equal(codes.NotFound, status.Code(err))
   523  	suite.Require().Empty(errMsg)
   524  
   525  	suite.assertAllExpectations()
   526  }
   527  
   528  // TestLookupTransactionErrorMessageByIndex_FailedToFetch tests lookup of a transaction error message by index,
   529  // when a transaction result is not in the cache and needs to be fetched from EN, but the EN fails to return it.
   530  func (suite *Suite) TestLookupTransactionErrorMessageByIndex_FailedToFetch() {
   531  	block := unittest.BlockFixture()
   532  	blockId := block.ID()
   533  	failedTx := unittest.TransactionFixture()
   534  	failedTxId := failedTx.ID()
   535  	failedTxIndex := rand.Uint32()
   536  
   537  	suite.transactionResults.On("ByBlockIDTransactionIndex", blockId, failedTxIndex).
   538  		Return(&flow.LightTransactionResult{
   539  			TransactionID:   failedTxId,
   540  			Failed:          true,
   541  			ComputationUsed: 0,
   542  		}, nil).Once()
   543  
   544  	_, fixedENIDs := suite.setupReceipts(&block)
   545  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   546  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   547  
   548  	// create a mock connection factory
   549  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   550  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   551  
   552  	// create a mock index reporter
   553  	reporter := syncmock.NewIndexReporter(suite.T())
   554  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   555  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   556  
   557  	params := suite.defaultBackendParams()
   558  	// the connection factory should be used to get the execution node client
   559  	params.ConnFactory = connFactory
   560  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   561  
   562  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   563  	err := params.TxResultsIndex.Initialize(reporter)
   564  	suite.Require().NoError(err)
   565  
   566  	backend, err := New(params)
   567  	suite.Require().NoError(err)
   568  
   569  	// lookup should try each of the 2 ENs in fixedENIDs
   570  	suite.execClient.On("GetTransactionErrorMessageByIndex", mock.Anything, mock.Anything).Return(nil,
   571  		status.Error(codes.Unavailable, "")).Twice()
   572  
   573  	errMsg, err := backend.LookupErrorMessageByIndex(context.Background(), blockId, block.Header.Height, failedTxIndex)
   574  	suite.Require().Error(err)
   575  	suite.Require().Equal(codes.Unavailable, status.Code(err))
   576  	suite.Require().Empty(errMsg)
   577  
   578  	suite.assertAllExpectations()
   579  }
   580  
   581  // TestLookupTransactionErrorMessages_HappyPath tests lookup of a transaction error messages by block ID.
   582  // In a happy path, it has to be fetched from the execution node if there are no cached results.
   583  // All fetched transactions have to be cached for future calls.
   584  func (suite *Suite) TestLookupTransactionErrorMessages_HappyPath() {
   585  	block := unittest.BlockFixture()
   586  	blockId := block.ID()
   587  
   588  	resultsByBlockID := make([]flow.LightTransactionResult, 0)
   589  	for i := 0; i < 5; i++ {
   590  		resultsByBlockID = append(resultsByBlockID, flow.LightTransactionResult{
   591  			TransactionID:   unittest.IdentifierFixture(),
   592  			Failed:          i%2 == 0, // create a mix of failed and non-failed transactions
   593  			ComputationUsed: 0,
   594  		})
   595  	}
   596  
   597  	suite.transactionResults.On("ByBlockID", blockId).
   598  		Return(resultsByBlockID, nil).Twice()
   599  
   600  	_, fixedENIDs := suite.setupReceipts(&block)
   601  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   602  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   603  
   604  	// create a mock connection factory
   605  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   606  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   607  
   608  	// create a mock index reporter
   609  	reporter := syncmock.NewIndexReporter(suite.T())
   610  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   611  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   612  
   613  	params := suite.defaultBackendParams()
   614  
   615  	// the connection factory should be used to get the execution node client
   616  	params.ConnFactory = connFactory
   617  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   618  
   619  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   620  	err := params.TxResultsIndex.Initialize(reporter)
   621  	suite.Require().NoError(err)
   622  
   623  	backend, err := New(params)
   624  	suite.Require().NoError(err)
   625  
   626  	expectedErrorMsg := "some error"
   627  
   628  	exeEventReq := &execproto.GetTransactionErrorMessagesByBlockIDRequest{
   629  		BlockId: blockId[:],
   630  	}
   631  
   632  	exeEventResp := &execproto.GetTransactionErrorMessagesResponse{}
   633  	for _, result := range resultsByBlockID {
   634  		r := result
   635  		if r.Failed {
   636  			errMsg := fmt.Sprintf("%s.%s", expectedErrorMsg, r.TransactionID)
   637  			exeEventResp.Results = append(exeEventResp.Results, &execproto.GetTransactionErrorMessagesResponse_Result{
   638  				TransactionId: r.TransactionID[:],
   639  				ErrorMessage:  errMsg,
   640  			})
   641  		}
   642  	}
   643  
   644  	suite.execClient.On("GetTransactionErrorMessagesByBlockID", mock.Anything, exeEventReq).
   645  		Return(exeEventResp, nil).
   646  		Once()
   647  
   648  	errMessages, err := backend.LookupErrorMessagesByBlockID(context.Background(), blockId, block.Header.Height)
   649  	suite.Require().NoError(err)
   650  	suite.Require().Len(errMessages, len(exeEventResp.Results))
   651  	for _, expectedResult := range exeEventResp.Results {
   652  		errMsg, ok := errMessages[convert.MessageToIdentifier(expectedResult.TransactionId)]
   653  		suite.Require().True(ok)
   654  		suite.Assert().Equal(expectedResult.ErrorMessage, errMsg)
   655  	}
   656  
   657  	// ensure the transaction error message is cached after retrieval; we do this by mocking the grpc call
   658  	// only once
   659  	errMessages, err = backend.LookupErrorMessagesByBlockID(context.Background(), blockId, block.Header.Height)
   660  	suite.Require().NoError(err)
   661  	suite.Require().Len(errMessages, len(exeEventResp.Results))
   662  	for _, expectedResult := range exeEventResp.Results {
   663  		errMsg, ok := errMessages[convert.MessageToIdentifier(expectedResult.TransactionId)]
   664  		suite.Require().True(ok)
   665  		suite.Assert().Equal(expectedResult.ErrorMessage, errMsg)
   666  	}
   667  	suite.assertAllExpectations()
   668  }
   669  
   670  // TestLookupTransactionErrorMessages_HappyPath_NoFailedTxns tests lookup of a transaction error messages by block ID.
   671  // In a happy path where a block with no failed txns is requested. We don't want to perform an RPC call in this case.
   672  func (suite *Suite) TestLookupTransactionErrorMessages_HappyPath_NoFailedTxns() {
   673  	block := unittest.BlockFixture()
   674  	blockId := block.ID()
   675  
   676  	resultsByBlockID := []flow.LightTransactionResult{
   677  		{
   678  			TransactionID:   unittest.IdentifierFixture(),
   679  			Failed:          false,
   680  			ComputationUsed: 0,
   681  		},
   682  		{
   683  			TransactionID:   unittest.IdentifierFixture(),
   684  			Failed:          false,
   685  			ComputationUsed: 0,
   686  		},
   687  	}
   688  
   689  	suite.transactionResults.On("ByBlockID", blockId).
   690  		Return(resultsByBlockID, nil).Once()
   691  
   692  	// create a mock index reporter
   693  	reporter := syncmock.NewIndexReporter(suite.T())
   694  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   695  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   696  
   697  	params := suite.defaultBackendParams()
   698  
   699  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   700  	err := params.TxResultsIndex.Initialize(reporter)
   701  	suite.Require().NoError(err)
   702  
   703  	backend, err := New(params)
   704  	suite.Require().NoError(err)
   705  
   706  	errMessages, err := backend.LookupErrorMessagesByBlockID(context.Background(), blockId, block.Header.Height)
   707  	suite.Require().NoError(err)
   708  	suite.Require().Empty(errMessages)
   709  	suite.assertAllExpectations()
   710  }
   711  
   712  // TestLookupTransactionErrorMessages_UnknownTransaction tests lookup of a transaction error messages by block ID,
   713  // when a transaction results for block has not been synced yet, in this case nothing we can do but return an error.
   714  func (suite *Suite) TestLookupTransactionErrorMessages_UnknownTransaction() {
   715  	block := unittest.BlockFixture()
   716  	blockId := block.ID()
   717  
   718  	suite.transactionResults.On("ByBlockID", blockId).
   719  		Return(nil, storage.ErrNotFound).Once()
   720  
   721  	// create a mock index reporter
   722  	reporter := syncmock.NewIndexReporter(suite.T())
   723  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   724  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   725  
   726  	params := suite.defaultBackendParams()
   727  
   728  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   729  	err := params.TxResultsIndex.Initialize(reporter)
   730  	suite.Require().NoError(err)
   731  
   732  	backend, err := New(params)
   733  	suite.Require().NoError(err)
   734  
   735  	errMsg, err := backend.LookupErrorMessagesByBlockID(context.Background(), blockId, block.Header.Height)
   736  	suite.Require().Error(err)
   737  	suite.Require().Equal(codes.NotFound, status.Code(err))
   738  	suite.Require().Empty(errMsg)
   739  
   740  	suite.assertAllExpectations()
   741  }
   742  
   743  // TestLookupTransactionErrorMessages_FailedToFetch tests lookup of a transaction error messages by block ID,
   744  // when a transaction result is not in the cache and needs to be fetched from EN, but the EN fails to return it.
   745  func (suite *Suite) TestLookupTransactionErrorMessages_FailedToFetch() {
   746  	block := unittest.BlockFixture()
   747  	blockId := block.ID()
   748  
   749  	resultsByBlockID := []flow.LightTransactionResult{
   750  		{
   751  			TransactionID:   unittest.IdentifierFixture(),
   752  			Failed:          true,
   753  			ComputationUsed: 0,
   754  		},
   755  		{
   756  			TransactionID:   unittest.IdentifierFixture(),
   757  			Failed:          true,
   758  			ComputationUsed: 0,
   759  		},
   760  	}
   761  
   762  	suite.transactionResults.On("ByBlockID", blockId).
   763  		Return(resultsByBlockID, nil).Once()
   764  
   765  	_, fixedENIDs := suite.setupReceipts(&block)
   766  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   767  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
   768  
   769  	// create a mock connection factory
   770  	connFactory := connectionmock.NewConnectionFactory(suite.T())
   771  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   772  
   773  	// create a mock index reporter
   774  	reporter := syncmock.NewIndexReporter(suite.T())
   775  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
   776  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
   777  
   778  	params := suite.defaultBackendParams()
   779  	// the connection factory should be used to get the execution node client
   780  	params.ConnFactory = connFactory
   781  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
   782  
   783  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
   784  	err := params.TxResultsIndex.Initialize(reporter)
   785  	suite.Require().NoError(err)
   786  
   787  	backend, err := New(params)
   788  	suite.Require().NoError(err)
   789  
   790  	// pretend the first transaction has been cached, but there are multiple failed txns so still a request has to be made.
   791  	backend.txErrorMessagesCache.Add(resultsByBlockID[0].TransactionID, "some error")
   792  
   793  	suite.execClient.On("GetTransactionErrorMessagesByBlockID", mock.Anything, mock.Anything).Return(nil,
   794  		status.Error(codes.Unavailable, "")).Twice()
   795  
   796  	errMsg, err := backend.LookupErrorMessagesByBlockID(context.Background(), blockId, block.Header.Height)
   797  	suite.Require().Error(err)
   798  	suite.Require().Equal(codes.Unavailable, status.Code(err))
   799  	suite.Require().Empty(errMsg)
   800  
   801  	suite.assertAllExpectations()
   802  }
   803  
   804  // TestGetSystemTransaction_HappyPath tests that GetSystemTransaction call returns system chunk transaction.
   805  func (suite *Suite) TestGetSystemTransaction_HappyPath() {
   806  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   807  		suite.state.On("Sealed").Return(snap, nil).Maybe()
   808  
   809  		params := suite.defaultBackendParams()
   810  		backend, err := New(params)
   811  		suite.Require().NoError(err)
   812  
   813  		block := unittest.BlockFixture()
   814  		blockID := block.ID()
   815  
   816  		// Make the call for the system chunk transaction
   817  		res, err := backend.GetSystemTransaction(context.Background(), blockID)
   818  		suite.Require().NoError(err)
   819  		// Expected system chunk transaction
   820  		systemTx, err := blueprints.SystemChunkTransaction(suite.chainID.Chain())
   821  		suite.Require().NoError(err)
   822  
   823  		suite.Require().Equal(systemTx, res)
   824  	})
   825  }
   826  
   827  // TestGetSystemTransactionResult_HappyPath tests that GetSystemTransactionResult call returns system transaction
   828  // result for required block id.
   829  func (suite *Suite) TestGetSystemTransactionResult_HappyPath() {
   830  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   831  		suite.state.On("Sealed").Return(snap, nil).Maybe()
   832  		lastBlock, err := snap.Head()
   833  		suite.Require().NoError(err)
   834  		identities, err := snap.Identities(filter.Any)
   835  		suite.Require().NoError(err)
   836  
   837  		block := unittest.BlockWithParentFixture(lastBlock)
   838  		blockID := block.ID()
   839  		suite.state.On("AtBlockID", blockID).Return(
   840  			unittest.StateSnapshotForKnownBlock(block.Header, identities.Lookup()), nil).Once()
   841  
   842  		// block storage returns the corresponding block
   843  		suite.blocks.
   844  			On("ByID", blockID).
   845  			Return(block, nil).
   846  			Once()
   847  
   848  		receipt1 := unittest.ReceiptForBlockFixture(block)
   849  		suite.receipts.
   850  			On("ByBlockID", block.ID()).
   851  			Return(flow.ExecutionReceiptList{receipt1}, nil)
   852  
   853  		// create a mock connection factory
   854  		connFactory := connectionmock.NewConnectionFactory(suite.T())
   855  		connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   856  
   857  		// the connection factory should be used to get the execution node client
   858  		params := suite.defaultBackendParams()
   859  		params.ConnFactory = connFactory
   860  
   861  		exeEventReq := &execproto.GetTransactionsByBlockIDRequest{
   862  			BlockId: blockID[:],
   863  		}
   864  
   865  		// Generating events with event generator
   866  		exeNodeEventEncodingVersion := entities.EventEncodingVersion_CCF_V0
   867  		events := generator.GetEventsWithEncoding(1, exeNodeEventEncodingVersion)
   868  		eventMessages := convert.EventsToMessages(events)
   869  
   870  		exeEventResp := &execproto.GetTransactionResultsResponse{
   871  			TransactionResults: []*execproto.GetTransactionResultResponse{{
   872  				Events:               eventMessages,
   873  				EventEncodingVersion: exeNodeEventEncodingVersion,
   874  			}},
   875  			EventEncodingVersion: exeNodeEventEncodingVersion,
   876  		}
   877  
   878  		suite.execClient.
   879  			On("GetTransactionResultsByBlockID", mock.Anything, exeEventReq).
   880  			Return(exeEventResp, nil).
   881  			Once()
   882  
   883  		backend, err := New(params)
   884  		suite.Require().NoError(err)
   885  
   886  		// Make the call for the system transaction result
   887  		res, err := backend.GetSystemTransactionResult(
   888  			context.Background(),
   889  			block.ID(),
   890  			entities.EventEncodingVersion_JSON_CDC_V0,
   891  		)
   892  		suite.Require().NoError(err)
   893  
   894  		// Expected system chunk transaction
   895  		systemTx, err := blueprints.SystemChunkTransaction(suite.chainID.Chain())
   896  		suite.Require().NoError(err)
   897  
   898  		suite.Require().Equal(flow.TransactionStatusExecuted, res.Status)
   899  		suite.Require().Equal(systemTx.ID(), res.TransactionID)
   900  
   901  		// Check for successful decoding of event
   902  		_, err = jsoncdc.Decode(nil, res.Events[0].Payload)
   903  		suite.Require().NoError(err)
   904  
   905  		events, err = convert.MessagesToEventsWithEncodingConversion(eventMessages,
   906  			exeNodeEventEncodingVersion,
   907  			entities.EventEncodingVersion_JSON_CDC_V0)
   908  		suite.Require().NoError(err)
   909  		suite.Require().Equal(events, res.Events)
   910  	})
   911  }
   912  
   913  // TestGetSystemTransactionResult_BlockNotFound tests GetSystemTransactionResult function when block was not found.
   914  func (suite *Suite) TestGetSystemTransactionResult_BlockNotFound() {
   915  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   916  		suite.state.On("Sealed").Return(snap, nil).Maybe()
   917  		lastBlock, err := snap.Head()
   918  		suite.Require().NoError(err)
   919  		identities, err := snap.Identities(filter.Any)
   920  		suite.Require().NoError(err)
   921  
   922  		block := unittest.BlockWithParentFixture(lastBlock)
   923  		blockID := block.ID()
   924  		suite.state.On("AtBlockID", blockID).Return(
   925  			unittest.StateSnapshotForKnownBlock(block.Header, identities.Lookup()), nil).Once()
   926  
   927  		// block storage returns the ErrNotFound error
   928  		suite.blocks.
   929  			On("ByID", blockID).
   930  			Return(nil, storage.ErrNotFound).
   931  			Once()
   932  
   933  		receipt1 := unittest.ReceiptForBlockFixture(block)
   934  		suite.receipts.
   935  			On("ByBlockID", block.ID()).
   936  			Return(flow.ExecutionReceiptList{receipt1}, nil)
   937  
   938  		params := suite.defaultBackendParams()
   939  
   940  		backend, err := New(params)
   941  		suite.Require().NoError(err)
   942  
   943  		// Make the call for the system transaction result
   944  		res, err := backend.GetSystemTransactionResult(
   945  			context.Background(),
   946  			block.ID(),
   947  			entities.EventEncodingVersion_JSON_CDC_V0,
   948  		)
   949  
   950  		suite.Require().Nil(res)
   951  		suite.Require().Error(err)
   952  		suite.Require().Equal(err, status.Errorf(codes.NotFound, "not found: %v", fmt.Errorf("key not found")))
   953  	})
   954  }
   955  
   956  // TestGetSystemTransactionResult_FailedEncodingConversion tests the GetSystemTransactionResult function with different
   957  // event encoding versions.
   958  func (suite *Suite) TestGetSystemTransactionResult_FailedEncodingConversion() {
   959  	suite.withPreConfiguredState(func(snap protocol.Snapshot) {
   960  		suite.state.On("Sealed").Return(snap, nil).Maybe()
   961  		lastBlock, err := snap.Head()
   962  		suite.Require().NoError(err)
   963  		identities, err := snap.Identities(filter.Any)
   964  		suite.Require().NoError(err)
   965  
   966  		block := unittest.BlockWithParentFixture(lastBlock)
   967  		blockID := block.ID()
   968  		suite.state.On("AtBlockID", blockID).Return(
   969  			unittest.StateSnapshotForKnownBlock(block.Header, identities.Lookup()), nil).Once()
   970  
   971  		// block storage returns the corresponding block
   972  		suite.blocks.
   973  			On("ByID", blockID).
   974  			Return(block, nil).
   975  			Once()
   976  
   977  		receipt1 := unittest.ReceiptForBlockFixture(block)
   978  		suite.receipts.
   979  			On("ByBlockID", block.ID()).
   980  			Return(flow.ExecutionReceiptList{receipt1}, nil)
   981  
   982  		// create a mock connection factory
   983  		connFactory := connectionmock.NewConnectionFactory(suite.T())
   984  		connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
   985  
   986  		// the connection factory should be used to get the execution node client
   987  		params := suite.defaultBackendParams()
   988  		params.ConnFactory = connFactory
   989  
   990  		exeEventReq := &execproto.GetTransactionsByBlockIDRequest{
   991  			BlockId: blockID[:],
   992  		}
   993  
   994  		// create empty events
   995  		eventsPerBlock := 10
   996  		eventMessages := make([]*entities.Event, eventsPerBlock)
   997  
   998  		exeEventResp := &execproto.GetTransactionResultsResponse{
   999  			TransactionResults: []*execproto.GetTransactionResultResponse{{
  1000  				Events:               eventMessages,
  1001  				EventEncodingVersion: entities.EventEncodingVersion_JSON_CDC_V0,
  1002  			}},
  1003  		}
  1004  
  1005  		suite.execClient.
  1006  			On("GetTransactionResultsByBlockID", mock.Anything, exeEventReq).
  1007  			Return(exeEventResp, nil).
  1008  			Once()
  1009  
  1010  		backend, err := New(params)
  1011  		suite.Require().NoError(err)
  1012  
  1013  		// Make the call for the system transaction result
  1014  		res, err := backend.GetSystemTransactionResult(
  1015  			context.Background(),
  1016  			block.ID(),
  1017  			entities.EventEncodingVersion_CCF_V0,
  1018  		)
  1019  
  1020  		suite.Require().Nil(res)
  1021  		suite.Require().Error(err)
  1022  		suite.Require().Equal(err, status.Errorf(codes.Internal, "failed to convert events from system tx result: %v",
  1023  			fmt.Errorf("conversion from format JSON_CDC_V0 to CCF_V0 is not supported")))
  1024  	})
  1025  }
  1026  
  1027  func (suite *Suite) assertTransactionResultResponse(
  1028  	err error,
  1029  	response *acc.TransactionResult,
  1030  	block flow.Block,
  1031  	txId flow.Identifier,
  1032  	txFailed bool,
  1033  	eventsForTx []flow.Event,
  1034  ) {
  1035  	suite.Require().NoError(err)
  1036  	suite.Assert().Equal(block.ID(), response.BlockID)
  1037  	suite.Assert().Equal(block.Header.Height, response.BlockHeight)
  1038  	suite.Assert().Equal(txId, response.TransactionID)
  1039  	if txId == suite.systemTx.ID() {
  1040  		suite.Assert().Equal(flow.ZeroID, response.CollectionID)
  1041  	} else {
  1042  		suite.Assert().Equal(block.Payload.Guarantees[0].CollectionID, response.CollectionID)
  1043  	}
  1044  	suite.Assert().Equal(len(eventsForTx), len(response.Events))
  1045  	// When there are error messages occurred in the transaction, the status should be 1
  1046  	if txFailed {
  1047  		suite.Assert().Equal(uint(1), response.StatusCode)
  1048  		suite.Assert().Equal(expectedErrorMsg, response.ErrorMessage)
  1049  	} else {
  1050  		suite.Assert().Equal(uint(0), response.StatusCode)
  1051  		suite.Assert().Equal("", response.ErrorMessage)
  1052  	}
  1053  	suite.Assert().Equal(flow.TransactionStatusSealed, response.Status)
  1054  }
  1055  
  1056  // TestTransactionResultFromStorage tests the retrieval of a transaction result (flow.TransactionResult) from storage
  1057  // instead of requesting it from the Execution Node.
  1058  func (suite *Suite) TestTransactionResultFromStorage() {
  1059  	// Create fixtures for block, transaction, and collection
  1060  	block := unittest.BlockFixture()
  1061  	transaction := unittest.TransactionFixture()
  1062  	col := flow.CollectionFromTransactions([]*flow.Transaction{&transaction})
  1063  	guarantee := col.Guarantee()
  1064  	block.SetPayload(unittest.PayloadFixture(unittest.WithGuarantees(&guarantee)))
  1065  	txId := transaction.ID()
  1066  	blockId := block.ID()
  1067  
  1068  	// Mock the behavior of the blocks and transactionResults objects
  1069  	suite.blocks.
  1070  		On("ByID", blockId).
  1071  		Return(&block, nil)
  1072  
  1073  	suite.transactionResults.On("ByBlockIDTransactionID", blockId, txId).
  1074  		Return(&flow.LightTransactionResult{
  1075  			TransactionID:   txId,
  1076  			Failed:          true,
  1077  			ComputationUsed: 0,
  1078  		}, nil)
  1079  
  1080  	suite.transactions.
  1081  		On("ByID", txId).
  1082  		Return(&transaction.TransactionBody, nil)
  1083  
  1084  	// Set up the light collection and mock the behavior of the collections object
  1085  	lightCol := col.Light()
  1086  	suite.collections.On("LightByID", col.ID()).Return(&lightCol, nil)
  1087  
  1088  	// Set up the events storage mock
  1089  	totalEvents := 5
  1090  	eventsForTx := unittest.EventsFixture(totalEvents, flow.EventAccountCreated)
  1091  	eventMessages := make([]*entities.Event, totalEvents)
  1092  	for j, event := range eventsForTx {
  1093  		eventMessages[j] = convert.EventToMessage(event)
  1094  	}
  1095  
  1096  	// expect a call to lookup events by block ID and transaction ID
  1097  	suite.events.On("ByBlockIDTransactionID", blockId, txId).Return(eventsForTx, nil)
  1098  
  1099  	// Set up the state and snapshot mocks
  1100  	_, fixedENIDs := suite.setupReceipts(&block)
  1101  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
  1102  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
  1103  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
  1104  	suite.snapshot.On("Head", mock.Anything).Return(block.Header, nil)
  1105  
  1106  	// create a mock connection factory
  1107  	connFactory := connectionmock.NewConnectionFactory(suite.T())
  1108  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
  1109  
  1110  	// create a mock index reporter
  1111  	reporter := syncmock.NewIndexReporter(suite.T())
  1112  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
  1113  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
  1114  
  1115  	// Set up the backend parameters and the backend instance
  1116  	params := suite.defaultBackendParams()
  1117  	// the connection factory should be used to get the execution node client
  1118  	params.ConnFactory = connFactory
  1119  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
  1120  	params.TxResultQueryMode = IndexQueryModeLocalOnly
  1121  
  1122  	params.EventsIndex = NewEventsIndex(suite.events)
  1123  	err := params.EventsIndex.Initialize(reporter)
  1124  	suite.Require().NoError(err)
  1125  
  1126  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
  1127  	err = params.TxResultsIndex.Initialize(reporter)
  1128  	suite.Require().NoError(err)
  1129  
  1130  	backend, err := New(params)
  1131  	suite.Require().NoError(err)
  1132  
  1133  	// Set up the expected error message for the execution node response
  1134  
  1135  	exeEventReq := &execproto.GetTransactionErrorMessageRequest{
  1136  		BlockId:       blockId[:],
  1137  		TransactionId: txId[:],
  1138  	}
  1139  
  1140  	exeEventResp := &execproto.GetTransactionErrorMessageResponse{
  1141  		TransactionId: txId[:],
  1142  		ErrorMessage:  expectedErrorMsg,
  1143  	}
  1144  
  1145  	suite.execClient.On("GetTransactionErrorMessage", mock.Anything, exeEventReq).Return(exeEventResp, nil).Once()
  1146  
  1147  	response, err := backend.GetTransactionResult(context.Background(), txId, blockId, flow.ZeroID, entities.EventEncodingVersion_JSON_CDC_V0)
  1148  	suite.assertTransactionResultResponse(err, response, block, txId, true, eventsForTx)
  1149  }
  1150  
  1151  // TestTransactionByIndexFromStorage tests the retrieval of a transaction result (flow.TransactionResult) by index
  1152  // and returns it from storage instead of requesting from the Execution Node.
  1153  func (suite *Suite) TestTransactionByIndexFromStorage() {
  1154  	// Create fixtures for block, transaction, and collection
  1155  	block := unittest.BlockFixture()
  1156  	transaction := unittest.TransactionFixture()
  1157  	col := flow.CollectionFromTransactions([]*flow.Transaction{&transaction})
  1158  	guarantee := col.Guarantee()
  1159  	block.SetPayload(unittest.PayloadFixture(unittest.WithGuarantees(&guarantee)))
  1160  	blockId := block.ID()
  1161  	txId := transaction.ID()
  1162  	txIndex := rand.Uint32()
  1163  
  1164  	// Set up the light collection and mock the behavior of the collections object
  1165  	lightCol := col.Light()
  1166  	suite.collections.On("LightByID", col.ID()).Return(&lightCol, nil)
  1167  
  1168  	// Mock the behavior of the blocks and transactionResults objects
  1169  	suite.blocks.
  1170  		On("ByID", blockId).
  1171  		Return(&block, nil)
  1172  
  1173  	suite.transactionResults.On("ByBlockIDTransactionIndex", blockId, txIndex).
  1174  		Return(&flow.LightTransactionResult{
  1175  			TransactionID:   txId,
  1176  			Failed:          true,
  1177  			ComputationUsed: 0,
  1178  		}, nil)
  1179  
  1180  	// Set up the events storage mock
  1181  	totalEvents := 5
  1182  	eventsForTx := unittest.EventsFixture(totalEvents, flow.EventAccountCreated)
  1183  	eventMessages := make([]*entities.Event, totalEvents)
  1184  	for j, event := range eventsForTx {
  1185  		eventMessages[j] = convert.EventToMessage(event)
  1186  	}
  1187  
  1188  	// expect a call to lookup events by block ID and transaction ID
  1189  	suite.events.On("ByBlockIDTransactionIndex", blockId, txIndex).Return(eventsForTx, nil)
  1190  
  1191  	// Set up the state and snapshot mocks
  1192  	_, fixedENIDs := suite.setupReceipts(&block)
  1193  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
  1194  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
  1195  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
  1196  	suite.snapshot.On("Head", mock.Anything).Return(block.Header, nil)
  1197  
  1198  	// Create a mock connection factory
  1199  	connFactory := connectionmock.NewConnectionFactory(suite.T())
  1200  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
  1201  
  1202  	// create a mock index reporter
  1203  	reporter := syncmock.NewIndexReporter(suite.T())
  1204  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
  1205  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
  1206  
  1207  	// Set up the backend parameters and the backend instance
  1208  	params := suite.defaultBackendParams()
  1209  	// the connection factory should be used to get the execution node client
  1210  	params.ConnFactory = connFactory
  1211  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
  1212  	params.TxResultQueryMode = IndexQueryModeLocalOnly
  1213  
  1214  	params.EventsIndex = NewEventsIndex(suite.events)
  1215  	err := params.EventsIndex.Initialize(reporter)
  1216  	suite.Require().NoError(err)
  1217  
  1218  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
  1219  	err = params.TxResultsIndex.Initialize(reporter)
  1220  	suite.Require().NoError(err)
  1221  
  1222  	backend, err := New(params)
  1223  	suite.Require().NoError(err)
  1224  
  1225  	// Set up the expected error message for the execution node response
  1226  	exeEventReq := &execproto.GetTransactionErrorMessageByIndexRequest{
  1227  		BlockId: blockId[:],
  1228  		Index:   txIndex,
  1229  	}
  1230  
  1231  	exeEventResp := &execproto.GetTransactionErrorMessageResponse{
  1232  		TransactionId: txId[:],
  1233  		ErrorMessage:  expectedErrorMsg,
  1234  	}
  1235  
  1236  	suite.execClient.On("GetTransactionErrorMessageByIndex", mock.Anything, exeEventReq).Return(exeEventResp, nil).Once()
  1237  
  1238  	response, err := backend.GetTransactionResultByIndex(context.Background(), blockId, txIndex, entities.EventEncodingVersion_JSON_CDC_V0)
  1239  	suite.assertTransactionResultResponse(err, response, block, txId, true, eventsForTx)
  1240  }
  1241  
  1242  // TestTransactionResultsByBlockIDFromStorage tests the retrieval of transaction results ([]flow.TransactionResult)
  1243  // by block ID from storage instead of requesting from the Execution Node.
  1244  func (suite *Suite) TestTransactionResultsByBlockIDFromStorage() {
  1245  	// Create fixtures for the block and collection
  1246  	block := unittest.BlockFixture()
  1247  	col := unittest.CollectionFixture(2)
  1248  	guarantee := col.Guarantee()
  1249  	block.SetPayload(unittest.PayloadFixture(unittest.WithGuarantees(&guarantee)))
  1250  	blockId := block.ID()
  1251  
  1252  	// Mock the behavior of the blocks, collections and light transaction results objects
  1253  	suite.blocks.
  1254  		On("ByID", blockId).
  1255  		Return(&block, nil)
  1256  	lightCol := col.Light()
  1257  	suite.collections.On("LightByID", mock.Anything).Return(&lightCol, nil)
  1258  
  1259  	lightTxResults := make([]flow.LightTransactionResult, len(lightCol.Transactions))
  1260  	for i, txID := range lightCol.Transactions {
  1261  		lightTxResults[i] = flow.LightTransactionResult{
  1262  			TransactionID:   txID,
  1263  			Failed:          false,
  1264  			ComputationUsed: 0,
  1265  		}
  1266  	}
  1267  	// simulate the system tx
  1268  	lightTxResults = append(lightTxResults, flow.LightTransactionResult{
  1269  		TransactionID:   suite.systemTx.ID(),
  1270  		Failed:          false,
  1271  		ComputationUsed: 10,
  1272  	})
  1273  
  1274  	// Mark the first transaction as failed
  1275  	lightTxResults[0].Failed = true
  1276  	suite.transactionResults.On("ByBlockID", blockId).Return(lightTxResults, nil)
  1277  
  1278  	// Set up the events storage mock
  1279  	totalEvents := 5
  1280  	eventsForTx := unittest.EventsFixture(totalEvents, flow.EventAccountCreated)
  1281  	eventMessages := make([]*entities.Event, totalEvents)
  1282  	for j, event := range eventsForTx {
  1283  		eventMessages[j] = convert.EventToMessage(event)
  1284  	}
  1285  
  1286  	// expect a call to lookup events by block ID and transaction ID
  1287  	suite.events.On("ByBlockIDTransactionID", blockId, mock.Anything).Return(eventsForTx, nil)
  1288  
  1289  	// Set up the state and snapshot mocks
  1290  	_, fixedENIDs := suite.setupReceipts(&block)
  1291  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
  1292  	suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe()
  1293  	suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil)
  1294  	suite.snapshot.On("Head", mock.Anything).Return(block.Header, nil)
  1295  
  1296  	// create a mock connection factory
  1297  	connFactory := connectionmock.NewConnectionFactory(suite.T())
  1298  	connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil)
  1299  
  1300  	// create a mock index reporter
  1301  	reporter := syncmock.NewIndexReporter(suite.T())
  1302  	reporter.On("LowestIndexedHeight").Return(block.Header.Height, nil)
  1303  	reporter.On("HighestIndexedHeight").Return(block.Header.Height+10, nil)
  1304  
  1305  	// Set up the state and snapshot mocks and the backend instance
  1306  	params := suite.defaultBackendParams()
  1307  	// the connection factory should be used to get the execution node client
  1308  	params.ConnFactory = connFactory
  1309  	params.FixedExecutionNodeIDs = fixedENIDs.NodeIDs().Strings()
  1310  
  1311  	params.EventsIndex = NewEventsIndex(suite.events)
  1312  	err := params.EventsIndex.Initialize(reporter)
  1313  	suite.Require().NoError(err)
  1314  
  1315  	params.TxResultsIndex = NewTransactionResultsIndex(suite.transactionResults)
  1316  	err = params.TxResultsIndex.Initialize(reporter)
  1317  	suite.Require().NoError(err)
  1318  
  1319  	params.TxResultQueryMode = IndexQueryModeLocalOnly
  1320  
  1321  	backend, err := New(params)
  1322  	suite.Require().NoError(err)
  1323  
  1324  	// Set up the expected error message for the execution node response
  1325  	exeEventReq := &execproto.GetTransactionErrorMessagesByBlockIDRequest{
  1326  		BlockId: blockId[:],
  1327  	}
  1328  
  1329  	res := &execproto.GetTransactionErrorMessagesResponse_Result{
  1330  		TransactionId: lightTxResults[0].TransactionID[:],
  1331  		ErrorMessage:  expectedErrorMsg,
  1332  		Index:         1,
  1333  	}
  1334  	exeEventResp := &execproto.GetTransactionErrorMessagesResponse{
  1335  		Results: []*execproto.GetTransactionErrorMessagesResponse_Result{
  1336  			res,
  1337  		},
  1338  	}
  1339  
  1340  	suite.execClient.On("GetTransactionErrorMessagesByBlockID", mock.Anything, exeEventReq).Return(exeEventResp, nil).Once()
  1341  
  1342  	response, err := backend.GetTransactionResultsByBlockID(context.Background(), blockId, entities.EventEncodingVersion_JSON_CDC_V0)
  1343  	suite.Require().NoError(err)
  1344  	suite.Assert().Equal(len(lightTxResults), len(response))
  1345  
  1346  	// Assertions for each transaction result in the response
  1347  	for i, responseResult := range response {
  1348  		lightTx := lightTxResults[i]
  1349  		suite.assertTransactionResultResponse(err, responseResult, block, lightTx.TransactionID, lightTx.Failed, eventsForTx)
  1350  	}
  1351  }