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

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	lru "github.com/hashicorp/golang-lru/v2"
    10  	accessproto "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/rs/zerolog"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  
    17  	"github.com/onflow/flow-go/access"
    18  	"github.com/onflow/flow-go/engine/access/rpc/connection"
    19  	"github.com/onflow/flow-go/engine/common/rpc"
    20  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    21  	"github.com/onflow/flow-go/model/flow"
    22  	"github.com/onflow/flow-go/module"
    23  	"github.com/onflow/flow-go/module/irrecoverable"
    24  	"github.com/onflow/flow-go/state"
    25  	"github.com/onflow/flow-go/storage"
    26  )
    27  
    28  type backendTransactions struct {
    29  	TransactionsLocalDataProvider
    30  	staticCollectionRPC  accessproto.AccessAPIClient // rpc client tied to a fixed collection node
    31  	transactions         storage.Transactions
    32  	executionReceipts    storage.ExecutionReceipts
    33  	chainID              flow.ChainID
    34  	transactionMetrics   module.TransactionMetrics
    35  	transactionValidator *access.TransactionValidator
    36  	retry                *Retry
    37  	connFactory          connection.ConnectionFactory
    38  
    39  	previousAccessNodes  []accessproto.AccessAPIClient
    40  	log                  zerolog.Logger
    41  	nodeCommunicator     Communicator
    42  	txResultCache        *lru.Cache[flow.Identifier, *access.TransactionResult]
    43  	txErrorMessagesCache *lru.Cache[flow.Identifier, string] // cache for transactions error messages, indexed by hash(block_id, tx_id).
    44  	txResultQueryMode    IndexQueryMode
    45  
    46  	systemTxID flow.Identifier
    47  	systemTx   *flow.TransactionBody
    48  }
    49  
    50  var _ TransactionErrorMessage = (*backendTransactions)(nil)
    51  
    52  // SendTransaction forwards the transaction to the collection node
    53  func (b *backendTransactions) SendTransaction(
    54  	ctx context.Context,
    55  	tx *flow.TransactionBody,
    56  ) error {
    57  	now := time.Now().UTC()
    58  
    59  	err := b.transactionValidator.Validate(tx)
    60  	if err != nil {
    61  		return status.Errorf(codes.InvalidArgument, "invalid transaction: %s", err.Error())
    62  	}
    63  
    64  	// send the transaction to the collection node if valid
    65  	err = b.trySendTransaction(ctx, tx)
    66  	if err != nil {
    67  		b.transactionMetrics.TransactionSubmissionFailed()
    68  		return rpc.ConvertError(err, "failed to send transaction to a collection node", codes.Internal)
    69  	}
    70  
    71  	b.transactionMetrics.TransactionReceived(tx.ID(), now)
    72  
    73  	// store the transaction locally
    74  	err = b.transactions.Store(tx)
    75  	if err != nil {
    76  		return status.Errorf(codes.Internal, "failed to store transaction: %v", err)
    77  	}
    78  
    79  	if b.retry.IsActive() {
    80  		go b.registerTransactionForRetry(tx)
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // trySendTransaction tries to transaction to a collection node
    87  func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.TransactionBody) error {
    88  	// if a collection node rpc client was provided at startup, just use that
    89  	if b.staticCollectionRPC != nil {
    90  		return b.grpcTxSend(ctx, b.staticCollectionRPC, tx)
    91  	}
    92  
    93  	// otherwise choose all collection nodes to try
    94  	collNodes, err := b.chooseCollectionNodes(tx.ID())
    95  	if err != nil {
    96  		return fmt.Errorf("failed to determine collection node for tx %x: %w", tx, err)
    97  	}
    98  
    99  	var sendError error
   100  	logAnyError := func() {
   101  		if sendError != nil {
   102  			b.log.Info().Err(err).Msg("failed to send transactions  to collector nodes")
   103  		}
   104  	}
   105  	defer logAnyError()
   106  
   107  	// try sending the transaction to one of the chosen collection nodes
   108  	sendError = b.nodeCommunicator.CallAvailableNode(
   109  		collNodes,
   110  		func(node *flow.Identity) error {
   111  			err = b.sendTransactionToCollector(ctx, tx, node.Address)
   112  			if err != nil {
   113  				return err
   114  			}
   115  			return nil
   116  		},
   117  		nil,
   118  	)
   119  
   120  	return sendError
   121  }
   122  
   123  // chooseCollectionNodes finds a random subset of size sampleSize of collection node addresses from the
   124  // collection node cluster responsible for the given tx
   125  func (b *backendTransactions) chooseCollectionNodes(txID flow.Identifier) (flow.IdentityList, error) {
   126  
   127  	// retrieve the set of collector clusters
   128  	clusters, err := b.state.Final().Epochs().Current().Clustering()
   129  	if err != nil {
   130  		return nil, fmt.Errorf("could not cluster collection nodes: %w", err)
   131  	}
   132  
   133  	// get the cluster responsible for the transaction
   134  	targetNodes, ok := clusters.ByTxID(txID)
   135  	if !ok {
   136  		return nil, fmt.Errorf("could not get local cluster by txID: %x", txID)
   137  	}
   138  
   139  	return targetNodes, nil
   140  }
   141  
   142  // sendTransactionToCollection sends the transaction to the given collection node via grpc
   143  func (b *backendTransactions) sendTransactionToCollector(
   144  	ctx context.Context,
   145  	tx *flow.TransactionBody,
   146  	collectionNodeAddr string,
   147  ) error {
   148  	collectionRPC, closer, err := b.connFactory.GetAccessAPIClient(collectionNodeAddr, nil)
   149  	if err != nil {
   150  		return fmt.Errorf("failed to connect to collection node at %s: %w", collectionNodeAddr, err)
   151  	}
   152  	defer closer.Close()
   153  
   154  	err = b.grpcTxSend(ctx, collectionRPC, tx)
   155  	if err != nil {
   156  		return fmt.Errorf("failed to send transaction to collection node at %s: %w", collectionNodeAddr, err)
   157  	}
   158  	return nil
   159  }
   160  
   161  func (b *backendTransactions) grpcTxSend(ctx context.Context, client accessproto.AccessAPIClient, tx *flow.TransactionBody) error {
   162  	colReq := &accessproto.SendTransactionRequest{
   163  		Transaction: convert.TransactionToMessage(*tx),
   164  	}
   165  
   166  	clientDeadline := time.Now().Add(time.Duration(2) * time.Second)
   167  	ctx, cancel := context.WithDeadline(ctx, clientDeadline)
   168  	defer cancel()
   169  
   170  	_, err := client.SendTransaction(ctx, colReq)
   171  	return err
   172  }
   173  
   174  // SendRawTransaction sends a raw transaction to the collection node
   175  func (b *backendTransactions) SendRawTransaction(
   176  	ctx context.Context,
   177  	tx *flow.TransactionBody,
   178  ) error {
   179  	// send the transaction to the collection node
   180  	return b.trySendTransaction(ctx, tx)
   181  }
   182  
   183  func (b *backendTransactions) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.TransactionBody, error) {
   184  	// look up transaction from storage
   185  	tx, err := b.transactions.ByID(txID)
   186  	txErr := rpc.ConvertStorageError(err)
   187  
   188  	if txErr != nil {
   189  		if status.Code(txErr) == codes.NotFound {
   190  			return b.getHistoricalTransaction(ctx, txID)
   191  		}
   192  		// Other Error trying to retrieve the transaction, return with err
   193  		return nil, txErr
   194  	}
   195  
   196  	return tx, nil
   197  }
   198  
   199  func (b *backendTransactions) GetTransactionsByBlockID(
   200  	_ context.Context,
   201  	blockID flow.Identifier,
   202  ) ([]*flow.TransactionBody, error) {
   203  	var transactions []*flow.TransactionBody
   204  
   205  	// TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID
   206  	block, err := b.blocks.ByID(blockID)
   207  	if err != nil {
   208  		return nil, rpc.ConvertStorageError(err)
   209  	}
   210  
   211  	for _, guarantee := range block.Payload.Guarantees {
   212  		collection, err := b.collections.ByID(guarantee.CollectionID)
   213  		if err != nil {
   214  			return nil, rpc.ConvertStorageError(err)
   215  		}
   216  
   217  		transactions = append(transactions, collection.Transactions...)
   218  	}
   219  
   220  	transactions = append(transactions, b.systemTx)
   221  
   222  	return transactions, nil
   223  }
   224  
   225  func (b *backendTransactions) GetTransactionResult(
   226  	ctx context.Context,
   227  	txID flow.Identifier,
   228  	blockID flow.Identifier,
   229  	collectionID flow.Identifier,
   230  	requiredEventEncodingVersion entities.EventEncodingVersion,
   231  ) (*access.TransactionResult, error) {
   232  	// look up transaction from storage
   233  	start := time.Now()
   234  
   235  	tx, err := b.transactions.ByID(txID)
   236  	if err != nil {
   237  		txErr := rpc.ConvertStorageError(err)
   238  
   239  		if status.Code(txErr) != codes.NotFound {
   240  			return nil, txErr
   241  		}
   242  
   243  		// Tx not found. If we have historical Sporks setup, lets look through those as well
   244  		if b.txResultCache != nil {
   245  			val, ok := b.txResultCache.Get(txID)
   246  			if ok {
   247  				return val, nil
   248  			}
   249  		}
   250  		historicalTxResult, err := b.getHistoricalTransactionResult(ctx, txID)
   251  		if err != nil {
   252  			// if tx not found in old access nodes either, then assume that the tx was submitted to a different AN
   253  			// and return status as unknown
   254  			txStatus := flow.TransactionStatusUnknown
   255  			result := &access.TransactionResult{
   256  				Status:     txStatus,
   257  				StatusCode: uint(txStatus),
   258  			}
   259  			if b.txResultCache != nil {
   260  				b.txResultCache.Add(txID, result)
   261  			}
   262  			return result, nil
   263  		}
   264  
   265  		if b.txResultCache != nil {
   266  			b.txResultCache.Add(txID, historicalTxResult)
   267  		}
   268  		return historicalTxResult, nil
   269  	}
   270  
   271  	block, err := b.retrieveBlock(blockID, collectionID, txID)
   272  	// an error occurred looking up the block or the requested block or collection was not found.
   273  	// If looking up the block based solely on the txID returns not found, then no error is
   274  	// returned since the block may not be finalized yet.
   275  	if err != nil {
   276  		return nil, rpc.ConvertStorageError(err)
   277  	}
   278  
   279  	var blockHeight uint64
   280  	var txResult *access.TransactionResult
   281  	// access node may not have the block if it hasn't yet been finalized, hence block can be nil at this point
   282  	if block != nil {
   283  		txResult, err = b.lookupTransactionResult(ctx, txID, block, requiredEventEncodingVersion)
   284  		if err != nil {
   285  			return nil, rpc.ConvertError(err, "failed to retrieve result", codes.Internal)
   286  		}
   287  
   288  		// an additional check to ensure the correctness of the collection ID.
   289  		expectedCollectionID, err := b.lookupCollectionIDInBlock(block, txID)
   290  		if err != nil {
   291  			// if the collection has not been indexed yet, the lookup will return a not found error.
   292  			// if the request included a blockID or collectionID in its the search criteria, not found
   293  			// should result in an error because it's not possible to guarantee that the result found
   294  			// is the correct one.
   295  			if blockID != flow.ZeroID || collectionID != flow.ZeroID {
   296  				return nil, rpc.ConvertStorageError(err)
   297  			}
   298  		}
   299  
   300  		if collectionID == flow.ZeroID {
   301  			collectionID = expectedCollectionID
   302  		} else if collectionID != expectedCollectionID {
   303  			return nil, status.Error(codes.InvalidArgument, "transaction not found in provided collection")
   304  		}
   305  
   306  		blockID = block.ID()
   307  		blockHeight = block.Header.Height
   308  	}
   309  
   310  	// If there is still no transaction result, provide one based on available information.
   311  	if txResult == nil {
   312  		var txStatus flow.TransactionStatus
   313  		// Derive the status of the transaction.
   314  		if block == nil {
   315  			txStatus, err = b.deriveUnknownTransactionStatus(tx.ReferenceBlockID)
   316  		} else {
   317  			txStatus, err = b.deriveTransactionStatus(blockID, blockHeight, false)
   318  		}
   319  
   320  		if err != nil {
   321  			if !errors.Is(err, state.ErrUnknownSnapshotReference) {
   322  				irrecoverable.Throw(ctx, err)
   323  			}
   324  			return nil, rpc.ConvertStorageError(err)
   325  		}
   326  
   327  		txResult = &access.TransactionResult{
   328  			BlockID:       blockID,
   329  			BlockHeight:   blockHeight,
   330  			TransactionID: txID,
   331  			Status:        txStatus,
   332  			CollectionID:  collectionID,
   333  		}
   334  	} else {
   335  		txResult.CollectionID = collectionID
   336  	}
   337  
   338  	b.transactionMetrics.TransactionResultFetched(time.Since(start), len(tx.Script))
   339  
   340  	return txResult, nil
   341  }
   342  
   343  // retrieveBlock function returns a block based on the input argument. The block ID lookup has the highest priority,
   344  // followed by the collection ID lookup. If both are missing, the default lookup by transaction ID is performed.
   345  func (b *backendTransactions) retrieveBlock(
   346  	// the requested block or collection was not found. If looking up the block based solely on the txID returns
   347  	// not found, then no error is returned.
   348  	blockID flow.Identifier,
   349  	collectionID flow.Identifier,
   350  	txID flow.Identifier,
   351  ) (*flow.Block, error) {
   352  	if blockID != flow.ZeroID {
   353  		return b.blocks.ByID(blockID)
   354  	}
   355  
   356  	if collectionID != flow.ZeroID {
   357  		return b.blocks.ByCollectionID(collectionID)
   358  	}
   359  
   360  	// find the block for the transaction
   361  	block, err := b.lookupBlock(txID)
   362  
   363  	if err != nil && !errors.Is(err, storage.ErrNotFound) {
   364  		return nil, err
   365  	}
   366  
   367  	return block, nil
   368  }
   369  
   370  func (b *backendTransactions) GetTransactionResultsByBlockID(
   371  	ctx context.Context,
   372  	blockID flow.Identifier,
   373  	requiredEventEncodingVersion entities.EventEncodingVersion,
   374  ) ([]*access.TransactionResult, error) {
   375  	// TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID
   376  	block, err := b.blocks.ByID(blockID)
   377  	if err != nil {
   378  		return nil, rpc.ConvertStorageError(err)
   379  	}
   380  
   381  	switch b.txResultQueryMode {
   382  	case IndexQueryModeExecutionNodesOnly:
   383  		return b.getTransactionResultsByBlockIDFromExecutionNode(ctx, block, requiredEventEncodingVersion)
   384  	case IndexQueryModeLocalOnly:
   385  		return b.GetTransactionResultsByBlockIDFromStorage(ctx, block, requiredEventEncodingVersion)
   386  	case IndexQueryModeFailover:
   387  		results, err := b.GetTransactionResultsByBlockIDFromStorage(ctx, block, requiredEventEncodingVersion)
   388  		if err == nil {
   389  			return results, nil
   390  		}
   391  
   392  		// If any error occurs with local storage - request transaction result from EN
   393  		return b.getTransactionResultsByBlockIDFromExecutionNode(ctx, block, requiredEventEncodingVersion)
   394  	default:
   395  		return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode)
   396  	}
   397  }
   398  
   399  func (b *backendTransactions) getTransactionResultsByBlockIDFromExecutionNode(
   400  	ctx context.Context,
   401  	block *flow.Block,
   402  	requiredEventEncodingVersion entities.EventEncodingVersion,
   403  ) ([]*access.TransactionResult, error) {
   404  	blockID := block.ID()
   405  	req := &execproto.GetTransactionsByBlockIDRequest{
   406  		BlockId: blockID[:],
   407  	}
   408  
   409  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   410  	if err != nil {
   411  		if IsInsufficientExecutionReceipts(err) {
   412  			return nil, status.Errorf(codes.NotFound, err.Error())
   413  		}
   414  		return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal)
   415  	}
   416  
   417  	resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req)
   418  	if err != nil {
   419  		return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal)
   420  	}
   421  
   422  	results := make([]*access.TransactionResult, 0, len(resp.TransactionResults))
   423  	i := 0
   424  	errInsufficientResults := status.Errorf(
   425  		codes.Internal,
   426  		"number of transaction results returned by execution node is less than the number of transactions  in the block",
   427  	)
   428  
   429  	for _, guarantee := range block.Payload.Guarantees {
   430  		collection, err := b.collections.LightByID(guarantee.CollectionID)
   431  		if err != nil {
   432  			return nil, rpc.ConvertStorageError(err)
   433  		}
   434  
   435  		for _, txID := range collection.Transactions {
   436  			// bounds check. this means the EN returned fewer transaction results than the transactions  in the block
   437  			if i >= len(resp.TransactionResults) {
   438  				return nil, errInsufficientResults
   439  			}
   440  			txResult := resp.TransactionResults[i]
   441  
   442  			// tx body is irrelevant to status if it's in an executed block
   443  			txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true)
   444  			if err != nil {
   445  				if !errors.Is(err, state.ErrUnknownSnapshotReference) {
   446  					irrecoverable.Throw(ctx, err)
   447  				}
   448  				return nil, rpc.ConvertStorageError(err)
   449  			}
   450  			events, err := convert.MessagesToEventsWithEncodingConversion(txResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion)
   451  			if err != nil {
   452  				return nil, status.Errorf(codes.Internal,
   453  					"failed to convert events to message in txID %x: %v", txID, err)
   454  			}
   455  
   456  			results = append(results, &access.TransactionResult{
   457  				Status:        txStatus,
   458  				StatusCode:    uint(txResult.GetStatusCode()),
   459  				Events:        events,
   460  				ErrorMessage:  txResult.GetErrorMessage(),
   461  				BlockID:       blockID,
   462  				TransactionID: txID,
   463  				CollectionID:  guarantee.CollectionID,
   464  				BlockHeight:   block.Header.Height,
   465  			})
   466  
   467  			i++
   468  		}
   469  	}
   470  
   471  	// after iterating through all transactions  in each collection, i equals the total number of
   472  	// user transactions  in the block
   473  	txCount := i
   474  
   475  	sporkRootBlockHeight, err := b.state.Params().SporkRootBlockHeight()
   476  	if err != nil {
   477  		return nil, status.Errorf(codes.Internal, "failed to retrieve root block: %v", err)
   478  	}
   479  
   480  	// root block has no system transaction result
   481  	if block.Header.Height > sporkRootBlockHeight {
   482  		// system chunk transaction
   483  
   484  		// resp.TransactionResults includes the system tx result, so there should be exactly one
   485  		// more result than txCount
   486  		if txCount != len(resp.TransactionResults)-1 {
   487  			if txCount >= len(resp.TransactionResults) {
   488  				return nil, errInsufficientResults
   489  			}
   490  			// otherwise there are extra results
   491  			// TODO(bft): slashable offense
   492  			return nil, status.Errorf(codes.Internal, "number of transaction results returned by execution node is more than the number of transactions  in the block")
   493  		}
   494  
   495  		systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1]
   496  		systemTxStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true)
   497  		if err != nil {
   498  			if !errors.Is(err, state.ErrUnknownSnapshotReference) {
   499  				irrecoverable.Throw(ctx, err)
   500  			}
   501  			return nil, rpc.ConvertStorageError(err)
   502  		}
   503  
   504  		events, err := convert.MessagesToEventsWithEncodingConversion(systemTxResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion)
   505  		if err != nil {
   506  			return nil, rpc.ConvertError(err, "failed to convert events from system tx result", codes.Internal)
   507  		}
   508  
   509  		results = append(results, &access.TransactionResult{
   510  			Status:        systemTxStatus,
   511  			StatusCode:    uint(systemTxResult.GetStatusCode()),
   512  			Events:        events,
   513  			ErrorMessage:  systemTxResult.GetErrorMessage(),
   514  			BlockID:       blockID,
   515  			TransactionID: b.systemTxID,
   516  			BlockHeight:   block.Header.Height,
   517  		})
   518  	}
   519  	return results, nil
   520  }
   521  
   522  // GetTransactionResultByIndex returns transactions Results for an index in a block that is executed,
   523  // pending or finalized transactions  return errors
   524  func (b *backendTransactions) GetTransactionResultByIndex(
   525  	ctx context.Context,
   526  	blockID flow.Identifier,
   527  	index uint32,
   528  	requiredEventEncodingVersion entities.EventEncodingVersion,
   529  ) (*access.TransactionResult, error) {
   530  	// TODO: https://github.com/onflow/flow-go/issues/2175 so caching doesn't cause a circular dependency
   531  	block, err := b.blocks.ByID(blockID)
   532  	if err != nil {
   533  		return nil, rpc.ConvertStorageError(err)
   534  	}
   535  
   536  	switch b.txResultQueryMode {
   537  	case IndexQueryModeExecutionNodesOnly:
   538  		return b.getTransactionResultByIndexFromExecutionNode(ctx, block, index, requiredEventEncodingVersion)
   539  	case IndexQueryModeLocalOnly:
   540  		return b.GetTransactionResultByIndexFromStorage(ctx, block, index, requiredEventEncodingVersion)
   541  	case IndexQueryModeFailover:
   542  		result, err := b.GetTransactionResultByIndexFromStorage(ctx, block, index, requiredEventEncodingVersion)
   543  		if err == nil {
   544  			return result, nil
   545  		}
   546  
   547  		// If any error occurs with local storage - request transaction result from EN
   548  		return b.getTransactionResultByIndexFromExecutionNode(ctx, block, index, requiredEventEncodingVersion)
   549  	default:
   550  		return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode)
   551  	}
   552  }
   553  
   554  func (b *backendTransactions) getTransactionResultByIndexFromExecutionNode(
   555  	ctx context.Context,
   556  	block *flow.Block,
   557  	index uint32,
   558  	requiredEventEncodingVersion entities.EventEncodingVersion,
   559  ) (*access.TransactionResult, error) {
   560  	blockID := block.ID()
   561  	// create request and forward to EN
   562  	req := &execproto.GetTransactionByIndexRequest{
   563  		BlockId: blockID[:],
   564  		Index:   index,
   565  	}
   566  
   567  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   568  	if err != nil {
   569  		if IsInsufficientExecutionReceipts(err) {
   570  			return nil, status.Errorf(codes.NotFound, err.Error())
   571  		}
   572  		return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal)
   573  	}
   574  
   575  	resp, err := b.getTransactionResultByIndexFromAnyExeNode(ctx, execNodes, req)
   576  	if err != nil {
   577  		return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal)
   578  	}
   579  
   580  	// tx body is irrelevant to status if it's in an executed block
   581  	txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true)
   582  	if err != nil {
   583  		if !errors.Is(err, state.ErrUnknownSnapshotReference) {
   584  			irrecoverable.Throw(ctx, err)
   585  		}
   586  		return nil, rpc.ConvertStorageError(err)
   587  	}
   588  
   589  	events, err := convert.MessagesToEventsWithEncodingConversion(resp.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion)
   590  	if err != nil {
   591  		return nil, status.Errorf(codes.Internal, "failed to convert events in blockID %x: %v", blockID, err)
   592  	}
   593  
   594  	// convert to response, cache and return
   595  	return &access.TransactionResult{
   596  		Status:       txStatus,
   597  		StatusCode:   uint(resp.GetStatusCode()),
   598  		Events:       events,
   599  		ErrorMessage: resp.GetErrorMessage(),
   600  		BlockID:      blockID,
   601  		BlockHeight:  block.Header.Height,
   602  	}, nil
   603  }
   604  
   605  // GetSystemTransaction returns system transaction
   606  func (b *backendTransactions) GetSystemTransaction(ctx context.Context, _ flow.Identifier) (*flow.TransactionBody, error) {
   607  	return b.systemTx, nil
   608  }
   609  
   610  // GetSystemTransactionResult returns system transaction result
   611  func (b *backendTransactions) GetSystemTransactionResult(ctx context.Context, blockID flow.Identifier, requiredEventEncodingVersion entities.EventEncodingVersion) (*access.TransactionResult, error) {
   612  	block, err := b.blocks.ByID(blockID)
   613  	if err != nil {
   614  		return nil, rpc.ConvertStorageError(err)
   615  	}
   616  
   617  	req := &execproto.GetTransactionsByBlockIDRequest{
   618  		BlockId: blockID[:],
   619  	}
   620  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   621  	if err != nil {
   622  		if IsInsufficientExecutionReceipts(err) {
   623  			return nil, status.Errorf(codes.NotFound, err.Error())
   624  		}
   625  		return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal)
   626  	}
   627  
   628  	resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req)
   629  	if err != nil {
   630  		return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal)
   631  	}
   632  
   633  	systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1]
   634  	systemTxStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true)
   635  	if err != nil {
   636  		return nil, rpc.ConvertStorageError(err)
   637  	}
   638  
   639  	events, err := convert.MessagesToEventsWithEncodingConversion(systemTxResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion)
   640  	if err != nil {
   641  		return nil, rpc.ConvertError(err, "failed to convert events from system tx result", codes.Internal)
   642  	}
   643  
   644  	return &access.TransactionResult{
   645  		Status:        systemTxStatus,
   646  		StatusCode:    uint(systemTxResult.GetStatusCode()),
   647  		Events:        events,
   648  		ErrorMessage:  systemTxResult.GetErrorMessage(),
   649  		BlockID:       blockID,
   650  		TransactionID: b.systemTxID,
   651  		BlockHeight:   block.Header.Height,
   652  	}, nil
   653  }
   654  
   655  // Error returns:
   656  //   - `storage.ErrNotFound` - collection referenced by transaction or block by a collection has not been found.
   657  //   - all other errors are unexpected and potentially symptoms of internal implementation bugs or state corruption (fatal).
   658  func (b *backendTransactions) lookupBlock(txID flow.Identifier) (*flow.Block, error) {
   659  	collection, err := b.collections.LightByTransactionID(txID)
   660  	if err != nil {
   661  		return nil, err
   662  	}
   663  
   664  	block, err := b.blocks.ByCollectionID(collection.ID())
   665  	if err != nil {
   666  		return nil, err
   667  	}
   668  
   669  	return block, nil
   670  }
   671  
   672  func (b *backendTransactions) lookupTransactionResult(
   673  	ctx context.Context,
   674  	txID flow.Identifier,
   675  	block *flow.Block,
   676  	requiredEventEncodingVersion entities.EventEncodingVersion,
   677  ) (*access.TransactionResult, error) {
   678  	var txResult *access.TransactionResult
   679  	var err error
   680  	switch b.txResultQueryMode {
   681  	case IndexQueryModeExecutionNodesOnly:
   682  		txResult, err = b.getTransactionResultFromExecutionNode(ctx, block, txID, requiredEventEncodingVersion)
   683  	case IndexQueryModeLocalOnly:
   684  		txResult, err = b.GetTransactionResultFromStorage(ctx, block, txID, requiredEventEncodingVersion)
   685  	case IndexQueryModeFailover:
   686  		txResult, err = b.GetTransactionResultFromStorage(ctx, block, txID, requiredEventEncodingVersion)
   687  		if err != nil {
   688  			// If any error occurs with local storage - request transaction result from EN
   689  			txResult, err = b.getTransactionResultFromExecutionNode(ctx, block, txID, requiredEventEncodingVersion)
   690  		}
   691  	default:
   692  		return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode)
   693  	}
   694  
   695  	if err != nil {
   696  		// if either the storage or execution node reported no results or there were not enough execution results
   697  		if status.Code(err) == codes.NotFound {
   698  			// No result yet, indicate that it has not been executed
   699  			return nil, nil
   700  		}
   701  		// Other Error trying to retrieve the result, return with err
   702  		return nil, err
   703  	}
   704  
   705  	// considered executed as long as some result is returned, even if it's an error message
   706  	return txResult, nil
   707  }
   708  
   709  func (b *backendTransactions) getHistoricalTransaction(
   710  	ctx context.Context,
   711  	txID flow.Identifier,
   712  ) (*flow.TransactionBody, error) {
   713  	for _, historicalNode := range b.previousAccessNodes {
   714  		txResp, err := historicalNode.GetTransaction(ctx, &accessproto.GetTransactionRequest{Id: txID[:]})
   715  		if err == nil {
   716  			tx, err := convert.MessageToTransaction(txResp.Transaction, b.chainID.Chain())
   717  			if err != nil {
   718  				return nil, status.Errorf(codes.Internal, "could not convert transaction: %v", err)
   719  			}
   720  
   721  			// Found on a historical node. Report
   722  			return &tx, nil
   723  		}
   724  		// Otherwise, if not found, just continue
   725  		if status.Code(err) == codes.NotFound {
   726  			continue
   727  		}
   728  		// TODO should we do something if the error isn't not found?
   729  	}
   730  	return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID)
   731  }
   732  
   733  func (b *backendTransactions) getHistoricalTransactionResult(
   734  	ctx context.Context,
   735  	txID flow.Identifier,
   736  ) (*access.TransactionResult, error) {
   737  	for _, historicalNode := range b.previousAccessNodes {
   738  		result, err := historicalNode.GetTransactionResult(ctx, &accessproto.GetTransactionRequest{Id: txID[:]})
   739  		if err == nil {
   740  			// Found on a historical node. Report
   741  			if result.GetStatus() == entities.TransactionStatus_UNKNOWN {
   742  				// We've moved to returning Status UNKNOWN instead of an error with the NotFound status,
   743  				// Therefore we should continue and look at the next access node for answers.
   744  				continue
   745  			}
   746  
   747  			if result.GetStatus() == entities.TransactionStatus_PENDING {
   748  				// This is on a historical node. No transactions  from it will ever be
   749  				// executed, therefore we should consider this expired
   750  				result.Status = entities.TransactionStatus_EXPIRED
   751  			}
   752  
   753  			return access.MessageToTransactionResult(result), nil
   754  		}
   755  		// Otherwise, if not found, just continue
   756  		if status.Code(err) == codes.NotFound {
   757  			continue
   758  		}
   759  		// TODO should we do something if the error isn't not found?
   760  	}
   761  	return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID)
   762  }
   763  
   764  func (b *backendTransactions) registerTransactionForRetry(tx *flow.TransactionBody) {
   765  	referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head()
   766  	if err != nil {
   767  		return
   768  	}
   769  
   770  	b.retry.RegisterTransaction(referenceBlock.Height, tx)
   771  }
   772  
   773  func (b *backendTransactions) getTransactionResultFromExecutionNode(
   774  	ctx context.Context,
   775  	block *flow.Block,
   776  	transactionID flow.Identifier,
   777  	requiredEventEncodingVersion entities.EventEncodingVersion,
   778  ) (*access.TransactionResult, error) {
   779  	blockID := block.ID()
   780  	// create an execution API request for events at blockID and transactionID
   781  	req := &execproto.GetTransactionResultRequest{
   782  		BlockId:       blockID[:],
   783  		TransactionId: transactionID[:],
   784  	}
   785  
   786  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   787  	if err != nil {
   788  		// if no execution receipt were found, return a NotFound GRPC error
   789  		if IsInsufficientExecutionReceipts(err) {
   790  			return nil, status.Errorf(codes.NotFound, err.Error())
   791  		}
   792  		return nil, err
   793  	}
   794  
   795  	resp, err := b.getTransactionResultFromAnyExeNode(ctx, execNodes, req)
   796  	if err != nil {
   797  		return nil, err
   798  	}
   799  
   800  	// tx body is irrelevant to status if it's in an executed block
   801  	txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true)
   802  	if err != nil {
   803  		if !errors.Is(err, state.ErrUnknownSnapshotReference) {
   804  			irrecoverable.Throw(ctx, err)
   805  		}
   806  		return nil, rpc.ConvertStorageError(err)
   807  	}
   808  
   809  	events, err := convert.MessagesToEventsWithEncodingConversion(resp.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion)
   810  	if err != nil {
   811  		return nil, rpc.ConvertError(err, "failed to convert events to message", codes.Internal)
   812  	}
   813  
   814  	return &access.TransactionResult{
   815  		TransactionID: transactionID,
   816  		Status:        txStatus,
   817  		StatusCode:    uint(resp.GetStatusCode()),
   818  		Events:        events,
   819  		ErrorMessage:  resp.GetErrorMessage(),
   820  		BlockID:       blockID,
   821  		BlockHeight:   block.Header.Height,
   822  	}, nil
   823  }
   824  
   825  // ATTENTION: might be a source of problems in future. We run this code on finalization gorotuine,
   826  // potentially lagging finalization events if operations take long time.
   827  // We might need to move this logic on dedicated goroutine and provide a way to skip finalization events if they are delivered
   828  // too often for this engine. An example of similar approach - https://github.com/onflow/flow-go/blob/10b0fcbf7e2031674c00f3cdd280f27bd1b16c47/engine/common/follower/compliance_engine.go#L201..
   829  // No errors expected during normal operations.
   830  func (b *backendTransactions) ProcessFinalizedBlockHeight(height uint64) error {
   831  	return b.retry.Retry(height)
   832  }
   833  
   834  func (b *backendTransactions) getTransactionResultFromAnyExeNode(
   835  	ctx context.Context,
   836  	execNodes flow.IdentityList,
   837  	req *execproto.GetTransactionResultRequest,
   838  ) (*execproto.GetTransactionResultResponse, error) {
   839  	var errToReturn error
   840  
   841  	defer func() {
   842  		if errToReturn != nil {
   843  			b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes")
   844  		}
   845  	}()
   846  
   847  	var resp *execproto.GetTransactionResultResponse
   848  	errToReturn = b.nodeCommunicator.CallAvailableNode(
   849  		execNodes,
   850  		func(node *flow.Identity) error {
   851  			var err error
   852  			resp, err = b.tryGetTransactionResult(ctx, node, req)
   853  			if err == nil {
   854  				b.log.Debug().
   855  					Str("execution_node", node.String()).
   856  					Hex("block_id", req.GetBlockId()).
   857  					Hex("transaction_id", req.GetTransactionId()).
   858  					Msg("Successfully got transaction results from any node")
   859  				return nil
   860  			}
   861  			return err
   862  		},
   863  		nil,
   864  	)
   865  
   866  	return resp, errToReturn
   867  }
   868  
   869  func (b *backendTransactions) tryGetTransactionResult(
   870  	ctx context.Context,
   871  	execNode *flow.Identity,
   872  	req *execproto.GetTransactionResultRequest,
   873  ) (*execproto.GetTransactionResultResponse, error) {
   874  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   875  	if err != nil {
   876  		return nil, err
   877  	}
   878  	defer closer.Close()
   879  
   880  	resp, err := execRPCClient.GetTransactionResult(ctx, req)
   881  	if err != nil {
   882  		return nil, err
   883  	}
   884  
   885  	return resp, nil
   886  }
   887  
   888  func (b *backendTransactions) getTransactionResultsByBlockIDFromAnyExeNode(
   889  	ctx context.Context,
   890  	execNodes flow.IdentityList,
   891  	req *execproto.GetTransactionsByBlockIDRequest,
   892  ) (*execproto.GetTransactionResultsResponse, error) {
   893  	var errToReturn error
   894  
   895  	defer func() {
   896  		// log the errors
   897  		if errToReturn != nil {
   898  			b.log.Err(errToReturn).Msg("failed to get transaction results from execution nodes")
   899  		}
   900  	}()
   901  
   902  	// if we were passed 0 execution nodes add a specific error
   903  	if len(execNodes) == 0 {
   904  		return nil, errors.New("zero execution nodes")
   905  	}
   906  
   907  	var resp *execproto.GetTransactionResultsResponse
   908  	errToReturn = b.nodeCommunicator.CallAvailableNode(
   909  		execNodes,
   910  		func(node *flow.Identity) error {
   911  			var err error
   912  			resp, err = b.tryGetTransactionResultsByBlockID(ctx, node, req)
   913  			if err == nil {
   914  				b.log.Debug().
   915  					Str("execution_node", node.String()).
   916  					Hex("block_id", req.GetBlockId()).
   917  					Msg("Successfully got transaction results from any node")
   918  				return nil
   919  			}
   920  			return err
   921  		},
   922  		nil,
   923  	)
   924  
   925  	return resp, errToReturn
   926  }
   927  
   928  func (b *backendTransactions) tryGetTransactionResultsByBlockID(
   929  	ctx context.Context,
   930  	execNode *flow.Identity,
   931  	req *execproto.GetTransactionsByBlockIDRequest,
   932  ) (*execproto.GetTransactionResultsResponse, error) {
   933  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  	defer closer.Close()
   938  
   939  	resp, err := execRPCClient.GetTransactionResultsByBlockID(ctx, req)
   940  	if err != nil {
   941  		return nil, err
   942  	}
   943  
   944  	return resp, nil
   945  }
   946  
   947  func (b *backendTransactions) getTransactionResultByIndexFromAnyExeNode(
   948  	ctx context.Context,
   949  	execNodes flow.IdentityList,
   950  	req *execproto.GetTransactionByIndexRequest,
   951  ) (*execproto.GetTransactionResultResponse, error) {
   952  	var errToReturn error
   953  	defer func() {
   954  		if errToReturn != nil {
   955  			b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes")
   956  		}
   957  	}()
   958  
   959  	if len(execNodes) == 0 {
   960  		return nil, errors.New("zero execution nodes provided")
   961  	}
   962  
   963  	var resp *execproto.GetTransactionResultResponse
   964  	errToReturn = b.nodeCommunicator.CallAvailableNode(
   965  		execNodes,
   966  		func(node *flow.Identity) error {
   967  			var err error
   968  			resp, err = b.tryGetTransactionResultByIndex(ctx, node, req)
   969  			if err == nil {
   970  				b.log.Debug().
   971  					Str("execution_node", node.String()).
   972  					Hex("block_id", req.GetBlockId()).
   973  					Uint32("index", req.GetIndex()).
   974  					Msg("Successfully got transaction results from any node")
   975  				return nil
   976  			}
   977  			return err
   978  		},
   979  		nil,
   980  	)
   981  
   982  	return resp, errToReturn
   983  }
   984  
   985  func (b *backendTransactions) tryGetTransactionResultByIndex(
   986  	ctx context.Context,
   987  	execNode *flow.Identity,
   988  	req *execproto.GetTransactionByIndexRequest,
   989  ) (*execproto.GetTransactionResultResponse, error) {
   990  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   991  	if err != nil {
   992  		return nil, err
   993  	}
   994  	defer closer.Close()
   995  
   996  	resp, err := execRPCClient.GetTransactionResultByIndex(ctx, req)
   997  	if err != nil {
   998  		return nil, err
   999  	}
  1000  
  1001  	return resp, nil
  1002  }
  1003  
  1004  // LookupErrorMessageByTransactionID returns transaction error message for specified transaction.
  1005  // If an error message for transaction can be found in the cache then it will be used to serve the request, otherwise
  1006  // an RPC call will be made to the EN to fetch that error message, fetched value will be cached in the LRU cache.
  1007  // Expected errors during normal operation:
  1008  //   - InsufficientExecutionReceipts - found insufficient receipts for given block ID.
  1009  //   - status.Error - remote GRPC call to EN has failed.
  1010  func (b *backendTransactions) LookupErrorMessageByTransactionID(
  1011  	ctx context.Context,
  1012  	blockID flow.Identifier,
  1013  	transactionID flow.Identifier,
  1014  ) (string, error) {
  1015  	var cacheKey flow.Identifier
  1016  	var value string
  1017  
  1018  	if b.txErrorMessagesCache != nil {
  1019  		cacheKey = flow.MakeIDFromFingerPrint(append(blockID[:], transactionID[:]...))
  1020  		value, cached := b.txErrorMessagesCache.Get(cacheKey)
  1021  		if cached {
  1022  			return value, nil
  1023  		}
  1024  	}
  1025  
  1026  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
  1027  	if err != nil {
  1028  		if IsInsufficientExecutionReceipts(err) {
  1029  			return "", status.Errorf(codes.NotFound, err.Error())
  1030  		}
  1031  		return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal)
  1032  	}
  1033  	req := &execproto.GetTransactionErrorMessageRequest{
  1034  		BlockId:       convert.IdentifierToMessage(blockID),
  1035  		TransactionId: convert.IdentifierToMessage(transactionID),
  1036  	}
  1037  
  1038  	resp, err := b.getTransactionErrorMessageFromAnyEN(ctx, execNodes, req)
  1039  	if err != nil {
  1040  		return "", fmt.Errorf("could not fetch error message from ENs: %w", err)
  1041  	}
  1042  	value = resp.ErrorMessage
  1043  
  1044  	if b.txErrorMessagesCache != nil {
  1045  		b.txErrorMessagesCache.Add(cacheKey, value)
  1046  	}
  1047  
  1048  	return value, nil
  1049  }
  1050  
  1051  // LookupErrorMessageByIndex returns transaction error message for specified transaction using its index.
  1052  // If an error message for transaction can be found in cache then it will be used to serve the request, otherwise
  1053  // an RPC call will be made to the EN to fetch that error message, fetched value will be cached in the LRU cache.
  1054  // Expected errors during normal operation:
  1055  //   - status.Error[codes.NotFound] - transaction result for given block ID and tx index is not available.
  1056  //   - InsufficientExecutionReceipts - found insufficient receipts for given block ID.
  1057  //   - status.Error - remote GRPC call to EN has failed.
  1058  func (b *backendTransactions) LookupErrorMessageByIndex(
  1059  	ctx context.Context,
  1060  	blockID flow.Identifier,
  1061  	height uint64,
  1062  	index uint32,
  1063  ) (string, error) {
  1064  	txResult, err := b.txResultsIndex.ByBlockIDTransactionIndex(blockID, height, index)
  1065  	if err != nil {
  1066  		return "", rpc.ConvertStorageError(err)
  1067  	}
  1068  
  1069  	var cacheKey flow.Identifier
  1070  	var value string
  1071  
  1072  	if b.txErrorMessagesCache != nil {
  1073  		cacheKey = flow.MakeIDFromFingerPrint(append(blockID[:], txResult.TransactionID[:]...))
  1074  		value, cached := b.txErrorMessagesCache.Get(cacheKey)
  1075  		if cached {
  1076  			return value, nil
  1077  		}
  1078  	}
  1079  
  1080  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
  1081  	if err != nil {
  1082  		if IsInsufficientExecutionReceipts(err) {
  1083  			return "", status.Errorf(codes.NotFound, err.Error())
  1084  		}
  1085  		return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal)
  1086  	}
  1087  	req := &execproto.GetTransactionErrorMessageByIndexRequest{
  1088  		BlockId: convert.IdentifierToMessage(blockID),
  1089  		Index:   index,
  1090  	}
  1091  
  1092  	resp, err := b.getTransactionErrorMessageByIndexFromAnyEN(ctx, execNodes, req)
  1093  	if err != nil {
  1094  		return "", fmt.Errorf("could not fetch error message from ENs: %w", err)
  1095  	}
  1096  	value = resp.ErrorMessage
  1097  
  1098  	if b.txErrorMessagesCache != nil {
  1099  		b.txErrorMessagesCache.Add(cacheKey, value)
  1100  	}
  1101  
  1102  	return value, nil
  1103  }
  1104  
  1105  // LookupErrorMessagesByBlockID returns all error messages for failed transactions by blockID.
  1106  // An RPC call will be made to the EN to fetch missing errors messages, fetched value will be cached in the LRU cache.
  1107  // Expected errors during normal operation:
  1108  //   - status.Error[codes.NotFound] - transaction results for given block ID are not available.
  1109  //   - InsufficientExecutionReceipts - found insufficient receipts for given block ID.
  1110  //   - status.Error - remote GRPC call to EN has failed.
  1111  func (b *backendTransactions) LookupErrorMessagesByBlockID(
  1112  	ctx context.Context,
  1113  	blockID flow.Identifier,
  1114  	height uint64,
  1115  ) (map[flow.Identifier]string, error) {
  1116  	txResults, err := b.txResultsIndex.ByBlockID(blockID, height)
  1117  	if err != nil {
  1118  		return nil, rpc.ConvertStorageError(err)
  1119  	}
  1120  
  1121  	results := make(map[flow.Identifier]string)
  1122  
  1123  	if b.txErrorMessagesCache != nil {
  1124  		needToFetch := false
  1125  		for _, txResult := range txResults {
  1126  			if txResult.Failed {
  1127  				cacheKey := flow.MakeIDFromFingerPrint(append(blockID[:], txResult.TransactionID[:]...))
  1128  				if value, ok := b.txErrorMessagesCache.Get(cacheKey); ok {
  1129  					results[txResult.TransactionID] = value
  1130  				} else {
  1131  					needToFetch = true
  1132  				}
  1133  			}
  1134  		}
  1135  
  1136  		// all transactions were served from cache or there were no failed transactions
  1137  		if !needToFetch {
  1138  			return results, nil
  1139  		}
  1140  	}
  1141  
  1142  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
  1143  	if err != nil {
  1144  		if IsInsufficientExecutionReceipts(err) {
  1145  			return nil, status.Errorf(codes.NotFound, err.Error())
  1146  		}
  1147  		return nil, rpc.ConvertError(err, "failed to select execution nodes", codes.Internal)
  1148  	}
  1149  	req := &execproto.GetTransactionErrorMessagesByBlockIDRequest{
  1150  		BlockId: convert.IdentifierToMessage(blockID),
  1151  	}
  1152  
  1153  	resp, err := b.getTransactionErrorMessagesFromAnyEN(ctx, execNodes, req)
  1154  	if err != nil {
  1155  		return nil, fmt.Errorf("could not fetch error message from ENs: %w", err)
  1156  	}
  1157  	result := make(map[flow.Identifier]string, len(resp))
  1158  	for _, value := range resp {
  1159  		if b.txErrorMessagesCache != nil {
  1160  			cacheKey := flow.MakeIDFromFingerPrint(append(req.BlockId, value.TransactionId...))
  1161  			b.txErrorMessagesCache.Add(cacheKey, value.ErrorMessage)
  1162  		}
  1163  		result[convert.MessageToIdentifier(value.TransactionId)] = value.ErrorMessage
  1164  	}
  1165  	return result, nil
  1166  }
  1167  
  1168  // getTransactionErrorMessageFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned.
  1169  // Expected errors during normal operation:
  1170  //   - status.Error - GRPC call failed, some of possible codes are:
  1171  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1172  //   - codes.Unavailable - remote node is not unavailable.
  1173  func (b *backendTransactions) getTransactionErrorMessageFromAnyEN(
  1174  	ctx context.Context,
  1175  	execNodes flow.IdentityList,
  1176  	req *execproto.GetTransactionErrorMessageRequest,
  1177  ) (*execproto.GetTransactionErrorMessageResponse, error) {
  1178  	// if we were passed 0 execution nodes add a specific error
  1179  	if len(execNodes) == 0 {
  1180  		return nil, errors.New("zero execution nodes")
  1181  	}
  1182  
  1183  	var resp *execproto.GetTransactionErrorMessageResponse
  1184  	errToReturn := b.nodeCommunicator.CallAvailableNode(
  1185  		execNodes,
  1186  		func(node *flow.Identity) error {
  1187  			var err error
  1188  			resp, err = b.tryGetTransactionErrorMessageFromEN(ctx, node, req)
  1189  			if err == nil {
  1190  				b.log.Debug().
  1191  					Str("execution_node", node.String()).
  1192  					Hex("block_id", req.GetBlockId()).
  1193  					Hex("transaction_id", req.GetTransactionId()).
  1194  					Msg("Successfully got transaction error message from any node")
  1195  				return nil
  1196  			}
  1197  			return err
  1198  		},
  1199  		nil,
  1200  	)
  1201  
  1202  	// log the errors
  1203  	if errToReturn != nil {
  1204  		b.log.Err(errToReturn).Msg("failed to get transaction error message from execution nodes")
  1205  		return nil, errToReturn
  1206  	}
  1207  
  1208  	return resp, nil
  1209  }
  1210  
  1211  // getTransactionErrorMessageFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned.
  1212  // Expected errors during normal operation:
  1213  //   - status.Error - GRPC call failed, some of possible codes are:
  1214  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1215  //   - codes.Unavailable - remote node is not unavailable.
  1216  func (b *backendTransactions) getTransactionErrorMessageByIndexFromAnyEN(
  1217  	ctx context.Context,
  1218  	execNodes flow.IdentityList,
  1219  	req *execproto.GetTransactionErrorMessageByIndexRequest,
  1220  ) (*execproto.GetTransactionErrorMessageResponse, error) {
  1221  	// if we were passed 0 execution nodes add a specific error
  1222  	if len(execNodes) == 0 {
  1223  		return nil, errors.New("zero execution nodes")
  1224  	}
  1225  
  1226  	var resp *execproto.GetTransactionErrorMessageResponse
  1227  	errToReturn := b.nodeCommunicator.CallAvailableNode(
  1228  		execNodes,
  1229  		func(node *flow.Identity) error {
  1230  			var err error
  1231  			resp, err = b.tryGetTransactionErrorMessageByIndexFromEN(ctx, node, req)
  1232  			if err == nil {
  1233  				b.log.Debug().
  1234  					Str("execution_node", node.String()).
  1235  					Hex("block_id", req.GetBlockId()).
  1236  					Uint32("index", req.GetIndex()).
  1237  					Msg("Successfully got transaction error message by index from any node")
  1238  				return nil
  1239  			}
  1240  			return err
  1241  		},
  1242  		nil,
  1243  	)
  1244  	if errToReturn != nil {
  1245  		b.log.Err(errToReturn).Msg("failed to get transaction error message by index from execution nodes")
  1246  		return nil, errToReturn
  1247  	}
  1248  
  1249  	return resp, nil
  1250  }
  1251  
  1252  // getTransactionErrorMessagesFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned.
  1253  // Expected errors during normal operation:
  1254  //   - status.Error - GRPC call failed, some of possible codes are:
  1255  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1256  //   - codes.Unavailable - remote node is not unavailable.
  1257  func (b *backendTransactions) getTransactionErrorMessagesFromAnyEN(
  1258  	ctx context.Context,
  1259  	execNodes flow.IdentityList,
  1260  	req *execproto.GetTransactionErrorMessagesByBlockIDRequest,
  1261  ) ([]*execproto.GetTransactionErrorMessagesResponse_Result, error) {
  1262  	// if we were passed 0 execution nodes add a specific error
  1263  	if len(execNodes) == 0 {
  1264  		return nil, errors.New("zero execution nodes")
  1265  	}
  1266  
  1267  	var resp *execproto.GetTransactionErrorMessagesResponse
  1268  	errToReturn := b.nodeCommunicator.CallAvailableNode(
  1269  		execNodes,
  1270  		func(node *flow.Identity) error {
  1271  			var err error
  1272  			resp, err = b.tryGetTransactionErrorMessagesByBlockIDFromEN(ctx, node, req)
  1273  			if err == nil {
  1274  				b.log.Debug().
  1275  					Str("execution_node", node.String()).
  1276  					Hex("block_id", req.GetBlockId()).
  1277  					Msg("Successfully got transaction error messages from any node")
  1278  				return nil
  1279  			}
  1280  			return err
  1281  		},
  1282  		nil,
  1283  	)
  1284  
  1285  	// log the errors
  1286  	if errToReturn != nil {
  1287  		b.log.Err(errToReturn).Msg("failed to get transaction error messages from execution nodes")
  1288  		return nil, errToReturn
  1289  	}
  1290  
  1291  	return resp.GetResults(), nil
  1292  }
  1293  
  1294  // Expected errors during normal operation:
  1295  //   - status.Error - GRPC call failed, some of possible codes are:
  1296  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1297  //   - codes.Unavailable - remote node is not unavailable.
  1298  //
  1299  // tryGetTransactionErrorMessageFromEN performs a grpc call to the specified execution node and returns response.
  1300  func (b *backendTransactions) tryGetTransactionErrorMessageFromEN(
  1301  	ctx context.Context,
  1302  	execNode *flow.Identity,
  1303  	req *execproto.GetTransactionErrorMessageRequest,
  1304  ) (*execproto.GetTransactionErrorMessageResponse, error) {
  1305  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
  1306  	if err != nil {
  1307  		return nil, err
  1308  	}
  1309  	defer closer.Close()
  1310  	return execRPCClient.GetTransactionErrorMessage(ctx, req)
  1311  }
  1312  
  1313  // tryGetTransactionErrorMessageByIndexFromEN performs a grpc call to the specified execution node and returns response.
  1314  // Expected errors during normal operation:
  1315  //   - status.Error - GRPC call failed, some of possible codes are:
  1316  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1317  //   - codes.Unavailable - remote node is not unavailable.
  1318  func (b *backendTransactions) tryGetTransactionErrorMessageByIndexFromEN(
  1319  	ctx context.Context,
  1320  	execNode *flow.Identity,
  1321  	req *execproto.GetTransactionErrorMessageByIndexRequest,
  1322  ) (*execproto.GetTransactionErrorMessageResponse, error) {
  1323  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
  1324  	if err != nil {
  1325  		return nil, err
  1326  	}
  1327  	defer closer.Close()
  1328  	return execRPCClient.GetTransactionErrorMessageByIndex(ctx, req)
  1329  }
  1330  
  1331  // tryGetTransactionErrorMessagesByBlockIDFromEN performs a grpc call to the specified execution node and returns response.
  1332  // Expected errors during normal operation:
  1333  //   - status.Error - GRPC call failed, some of possible codes are:
  1334  //   - codes.NotFound - request cannot be served by EN because of absence of data.
  1335  //   - codes.Unavailable - remote node is not unavailable.
  1336  func (b *backendTransactions) tryGetTransactionErrorMessagesByBlockIDFromEN(
  1337  	ctx context.Context,
  1338  	execNode *flow.Identity,
  1339  	req *execproto.GetTransactionErrorMessagesByBlockIDRequest,
  1340  ) (*execproto.GetTransactionErrorMessagesResponse, error) {
  1341  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
  1342  	if err != nil {
  1343  		return nil, err
  1344  	}
  1345  	defer closer.Close()
  1346  	return execRPCClient.GetTransactionErrorMessagesByBlockID(ctx, req)
  1347  }