github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/backend_transactions.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-multierror"
    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/koko1123/flow-go-1/access"
    18  	"github.com/koko1123/flow-go-1/engine/common/rpc"
    19  	"github.com/koko1123/flow-go-1/engine/common/rpc/convert"
    20  	"github.com/koko1123/flow-go-1/fvm/blueprints"
    21  	"github.com/koko1123/flow-go-1/model/flow"
    22  	"github.com/koko1123/flow-go-1/module"
    23  	"github.com/koko1123/flow-go-1/state/protocol"
    24  	"github.com/koko1123/flow-go-1/storage"
    25  )
    26  
    27  const collectionNodesToTry uint = 3
    28  
    29  type backendTransactions struct {
    30  	staticCollectionRPC  accessproto.AccessAPIClient // rpc client tied to a fixed collection node
    31  	transactions         storage.Transactions
    32  	executionReceipts    storage.ExecutionReceipts
    33  	collections          storage.Collections
    34  	blocks               storage.Blocks
    35  	state                protocol.State
    36  	chainID              flow.ChainID
    37  	transactionMetrics   module.TransactionMetrics
    38  	transactionValidator *access.TransactionValidator
    39  	retry                *Retry
    40  	connFactory          ConnectionFactory
    41  
    42  	previousAccessNodes []accessproto.AccessAPIClient
    43  	log                 zerolog.Logger
    44  }
    45  
    46  // SendTransaction forwards the transaction to the collection node
    47  func (b *backendTransactions) SendTransaction(
    48  	ctx context.Context,
    49  	tx *flow.TransactionBody,
    50  ) error {
    51  	now := time.Now().UTC()
    52  
    53  	err := b.transactionValidator.Validate(tx)
    54  	if err != nil {
    55  		return status.Errorf(codes.InvalidArgument, "invalid transaction: %s", err.Error())
    56  	}
    57  
    58  	// send the transaction to the collection node if valid
    59  	err = b.trySendTransaction(ctx, tx)
    60  	if err != nil {
    61  		b.transactionMetrics.TransactionSubmissionFailed()
    62  		return status.Error(codes.Internal, fmt.Sprintf("failed to send transaction to a collection node: %v", err))
    63  	}
    64  
    65  	b.transactionMetrics.TransactionReceived(tx.ID(), now)
    66  
    67  	// store the transaction locally
    68  	err = b.transactions.Store(tx)
    69  	if err != nil {
    70  		// TODO: why would this be InvalidArgument?
    71  		return status.Error(codes.InvalidArgument, fmt.Sprintf("failed to store transaction: %v", err))
    72  	}
    73  
    74  	if b.retry.IsActive() {
    75  		go b.registerTransactionForRetry(tx)
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // trySendTransaction tries to transaction to a collection node
    82  func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.TransactionBody) error {
    83  
    84  	// if a collection node rpc client was provided at startup, just use that
    85  	if b.staticCollectionRPC != nil {
    86  		return b.grpcTxSend(ctx, b.staticCollectionRPC, tx)
    87  	}
    88  
    89  	// otherwise choose a random set of collections nodes to try
    90  	collAddrs, err := b.chooseCollectionNodes(tx, collectionNodesToTry)
    91  	if err != nil {
    92  		return fmt.Errorf("failed to determine collection node for tx %x: %w", tx, err)
    93  	}
    94  
    95  	var sendErrors *multierror.Error
    96  	logAnyError := func() {
    97  		err = sendErrors.ErrorOrNil()
    98  		if err != nil {
    99  			b.log.Info().Err(err).Msg("failed to send transactions to collector nodes")
   100  		}
   101  	}
   102  	defer logAnyError()
   103  
   104  	// try sending the transaction to one of the chosen collection nodes
   105  	for _, addr := range collAddrs {
   106  		err = b.sendTransactionToCollector(ctx, tx, addr)
   107  		if err == nil {
   108  			return nil
   109  		}
   110  		sendErrors = multierror.Append(sendErrors, err)
   111  	}
   112  
   113  	return sendErrors.ErrorOrNil()
   114  }
   115  
   116  // chooseCollectionNodes finds a random subset of size sampleSize of collection node addresses from the
   117  // collection node cluster responsible for the given tx
   118  func (b *backendTransactions) chooseCollectionNodes(tx *flow.TransactionBody, sampleSize uint) ([]string, error) {
   119  
   120  	// retrieve the set of collector clusters
   121  	clusters, err := b.state.Final().Epochs().Current().Clustering()
   122  	if err != nil {
   123  		return nil, fmt.Errorf("could not cluster collection nodes: %w", err)
   124  	}
   125  
   126  	// get the cluster responsible for the transaction
   127  	txCluster, ok := clusters.ByTxID(tx.ID())
   128  	if !ok {
   129  		return nil, fmt.Errorf("could not get local cluster by txID: %x", tx.ID())
   130  	}
   131  
   132  	// select a random subset of collection nodes from the cluster to be tried in order
   133  	targetNodes := txCluster.Sample(sampleSize)
   134  
   135  	// collect the addresses of all the chosen collection nodes
   136  	var targetAddrs = make([]string, len(targetNodes))
   137  	for i, id := range targetNodes {
   138  		targetAddrs[i] = id.Address
   139  	}
   140  
   141  	return targetAddrs, nil
   142  }
   143  
   144  // sendTransactionToCollection sends the transaction to the given collection node via grpc
   145  func (b *backendTransactions) sendTransactionToCollector(ctx context.Context,
   146  	tx *flow.TransactionBody,
   147  	collectionNodeAddr string) error {
   148  
   149  	collectionRPC, closer, err := b.connFactory.GetAccessAPIClient(collectionNodeAddr)
   150  	if err != nil {
   151  		return fmt.Errorf("failed to connect to collection node at %s: %w", collectionNodeAddr, err)
   152  	}
   153  	defer closer.Close()
   154  
   155  	err = b.grpcTxSend(ctx, collectionRPC, tx)
   156  	if err != nil {
   157  		if status.Code(err) == codes.Unavailable {
   158  			b.connFactory.InvalidateAccessAPIClient(collectionNodeAddr)
   159  		}
   160  		return fmt.Errorf("failed to send transaction to collection node at %s: %v", collectionNodeAddr, err)
   161  	}
   162  	return nil
   163  }
   164  
   165  func (b *backendTransactions) grpcTxSend(ctx context.Context, client accessproto.AccessAPIClient, tx *flow.TransactionBody) error {
   166  	colReq := &accessproto.SendTransactionRequest{
   167  		Transaction: convert.TransactionToMessage(*tx),
   168  	}
   169  
   170  	clientDeadline := time.Now().Add(time.Duration(2) * time.Second)
   171  	ctx, cancel := context.WithDeadline(ctx, clientDeadline)
   172  	defer cancel()
   173  	_, err := client.SendTransaction(ctx, colReq)
   174  	return err
   175  }
   176  
   177  // SendRawTransaction sends a raw transaction to the collection node
   178  func (b *backendTransactions) SendRawTransaction(
   179  	ctx context.Context,
   180  	tx *flow.TransactionBody,
   181  ) error {
   182  
   183  	// send the transaction to the collection node
   184  	return b.trySendTransaction(ctx, tx)
   185  }
   186  
   187  func (b *backendTransactions) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.TransactionBody, error) {
   188  	// look up transaction from storage
   189  	tx, err := b.transactions.ByID(txID)
   190  	txErr := rpc.ConvertStorageError(err)
   191  
   192  	if txErr != nil {
   193  		if status.Code(txErr) == codes.NotFound {
   194  			return b.getHistoricalTransaction(ctx, txID)
   195  		}
   196  		// Other Error trying to retrieve the transaction, return with err
   197  		return nil, txErr
   198  	}
   199  
   200  	return tx, nil
   201  }
   202  
   203  func (b *backendTransactions) GetTransactionsByBlockID(
   204  	ctx context.Context,
   205  	blockID flow.Identifier,
   206  ) ([]*flow.TransactionBody, error) {
   207  	var transactions []*flow.TransactionBody
   208  
   209  	// TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID
   210  	block, err := b.blocks.ByID(blockID)
   211  	if err != nil {
   212  		return nil, rpc.ConvertStorageError(err)
   213  	}
   214  
   215  	for _, guarantee := range block.Payload.Guarantees {
   216  		collection, err := b.collections.ByID(guarantee.CollectionID)
   217  		if err != nil {
   218  			return nil, rpc.ConvertStorageError(err)
   219  		}
   220  
   221  		transactions = append(transactions, collection.Transactions...)
   222  	}
   223  
   224  	systemTx, err := blueprints.SystemChunkTransaction(b.chainID.Chain())
   225  	if err != nil {
   226  		return nil, fmt.Errorf("could not get system chunk transaction: %w", err)
   227  	}
   228  
   229  	transactions = append(transactions, systemTx)
   230  
   231  	return transactions, nil
   232  }
   233  
   234  func (b *backendTransactions) GetTransactionResult(
   235  	ctx context.Context,
   236  	txID flow.Identifier,
   237  ) (*access.TransactionResult, error) {
   238  	// look up transaction from storage
   239  	start := time.Now()
   240  	tx, err := b.transactions.ByID(txID)
   241  
   242  	txErr := rpc.ConvertStorageError(err)
   243  	if txErr != nil {
   244  		if status.Code(txErr) == codes.NotFound {
   245  			// Tx not found. If we have historical Sporks setup, lets look through those as well
   246  			historicalTxResult, err := b.getHistoricalTransactionResult(ctx, txID)
   247  			if err != nil {
   248  				// if tx not found in old access nodes either, then assume that the tx was submitted to a different AN
   249  				// and return status as unknown
   250  				txStatus := flow.TransactionStatusUnknown
   251  				return &access.TransactionResult{
   252  					Status:     txStatus,
   253  					StatusCode: uint(txStatus),
   254  				}, nil
   255  			}
   256  			return historicalTxResult, nil
   257  		}
   258  		return nil, txErr
   259  	}
   260  
   261  	// find the block for the transaction
   262  	block, err := b.lookupBlock(txID)
   263  	if err != nil && !errors.Is(err, storage.ErrNotFound) {
   264  		return nil, rpc.ConvertStorageError(err)
   265  	}
   266  
   267  	var blockID flow.Identifier
   268  	var transactionWasExecuted bool
   269  	var events []flow.Event
   270  	var txError string
   271  	var statusCode uint32
   272  	var blockHeight uint64
   273  	// access node may not have the block if it hasn't yet been finalized, hence block can be nil at this point
   274  	if block != nil {
   275  		blockID = block.ID()
   276  		transactionWasExecuted, events, statusCode, txError, err = b.lookupTransactionResult(ctx, txID, blockID)
   277  		blockHeight = block.Header.Height
   278  		if err != nil {
   279  			return nil, rpc.ConvertStorageError(err)
   280  		}
   281  	}
   282  
   283  	// derive status of the transaction
   284  	txStatus, err := b.deriveTransactionStatus(tx, transactionWasExecuted, block)
   285  	if err != nil {
   286  		return nil, rpc.ConvertStorageError(err)
   287  	}
   288  
   289  	b.transactionMetrics.TransactionResultFetched(time.Since(start), len(tx.Script))
   290  
   291  	return &access.TransactionResult{
   292  		Status:        txStatus,
   293  		StatusCode:    uint(statusCode),
   294  		Events:        events,
   295  		ErrorMessage:  txError,
   296  		BlockID:       blockID,
   297  		TransactionID: txID,
   298  		BlockHeight:   blockHeight,
   299  	}, nil
   300  }
   301  
   302  func (b *backendTransactions) GetTransactionResultsByBlockID(
   303  	ctx context.Context,
   304  	blockID flow.Identifier,
   305  ) ([]*access.TransactionResult, error) {
   306  	// TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID
   307  	block, err := b.blocks.ByID(blockID)
   308  	if err != nil {
   309  		return nil, rpc.ConvertStorageError(err)
   310  	}
   311  
   312  	req := &execproto.GetTransactionsByBlockIDRequest{
   313  		BlockId: blockID[:],
   314  	}
   315  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   316  	if err != nil {
   317  		_, isInsufficientExecReceipts := err.(*InsufficientExecutionReceipts)
   318  		if isInsufficientExecReceipts {
   319  			return nil, status.Errorf(codes.NotFound, err.Error())
   320  		}
   321  		return nil, status.Errorf(codes.Internal, "failed to retrieve results from any execution node: %v", err)
   322  	}
   323  
   324  	resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req)
   325  	if err != nil {
   326  		if status.Code(err) == codes.NotFound {
   327  			return nil, err
   328  		}
   329  		return nil, status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err)
   330  	}
   331  
   332  	results := make([]*access.TransactionResult, 0, len(resp.TransactionResults))
   333  	i := 0
   334  	errInsufficientResults := status.Errorf(
   335  		codes.Internal,
   336  		"number of transaction results returned by execution node is less than the number of transactions in the block",
   337  	)
   338  
   339  	for _, guarantee := range block.Payload.Guarantees {
   340  		collection, err := b.collections.LightByID(guarantee.CollectionID)
   341  		if err != nil {
   342  			return nil, rpc.ConvertStorageError(err)
   343  		}
   344  
   345  		for _, txID := range collection.Transactions {
   346  			if i >= len(resp.TransactionResults) {
   347  				return nil, errInsufficientResults
   348  			}
   349  
   350  			txResult := resp.TransactionResults[i]
   351  			// tx body is irrelevant to status if it's in an executed block
   352  			txStatus, err := b.deriveTransactionStatus(nil, true, block)
   353  			if err != nil {
   354  				return nil, rpc.ConvertStorageError(err)
   355  			}
   356  
   357  			results = append(results, &access.TransactionResult{
   358  				Status:        txStatus,
   359  				StatusCode:    uint(txResult.GetStatusCode()),
   360  				Events:        convert.MessagesToEvents(txResult.GetEvents()),
   361  				ErrorMessage:  txResult.GetErrorMessage(),
   362  				BlockID:       blockID,
   363  				TransactionID: txID,
   364  				CollectionID:  guarantee.CollectionID,
   365  				BlockHeight:   block.Header.Height,
   366  			})
   367  
   368  			i++
   369  		}
   370  	}
   371  
   372  	rootBlock, err := b.state.Params().Root()
   373  	if err != nil {
   374  		return nil, status.Errorf(codes.Internal, "failed to retrieve root block: %v", err)
   375  	}
   376  
   377  	// root block has no system transaction result
   378  	if rootBlock.ID() != blockID {
   379  		// system chunk transaction
   380  		if i >= len(resp.TransactionResults) {
   381  			return nil, errInsufficientResults
   382  		} else if i < len(resp.TransactionResults)-1 {
   383  			return nil, status.Errorf(codes.Internal, "number of transaction results returned by execution node is more than the number of transactions in the block")
   384  		}
   385  
   386  		systemTx, err := blueprints.SystemChunkTransaction(b.chainID.Chain())
   387  		if err != nil {
   388  			return nil, fmt.Errorf("could not get system chunk transaction: %w", err)
   389  		}
   390  		systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1]
   391  		systemTxStatus, err := b.deriveTransactionStatus(systemTx, true, block)
   392  		if err != nil {
   393  			return nil, rpc.ConvertStorageError(err)
   394  		}
   395  
   396  		results = append(results, &access.TransactionResult{
   397  			Status:        systemTxStatus,
   398  			StatusCode:    uint(systemTxResult.GetStatusCode()),
   399  			Events:        convert.MessagesToEvents(systemTxResult.GetEvents()),
   400  			ErrorMessage:  systemTxResult.GetErrorMessage(),
   401  			BlockID:       blockID,
   402  			TransactionID: systemTx.ID(),
   403  			BlockHeight:   block.Header.Height,
   404  		})
   405  	}
   406  
   407  	return results, nil
   408  }
   409  
   410  // GetTransactionResultByIndex returns TransactionsResults for an index in a block that is executed,
   411  // pending or finalized transactions return errors
   412  func (b *backendTransactions) GetTransactionResultByIndex(
   413  	ctx context.Context,
   414  	blockID flow.Identifier,
   415  	index uint32,
   416  ) (*access.TransactionResult, error) {
   417  	// TODO: https://github.com/koko1123/flow-go-1/issues/2175 so caching doesn't cause a circular dependency
   418  	block, err := b.blocks.ByID(blockID)
   419  	if err != nil {
   420  		return nil, rpc.ConvertStorageError(err)
   421  	}
   422  
   423  	// create request and forward to EN
   424  	req := &execproto.GetTransactionByIndexRequest{
   425  		BlockId: blockID[:],
   426  		Index:   index,
   427  	}
   428  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   429  	if err != nil {
   430  		_, isInsufficientExecReceipts := err.(*InsufficientExecutionReceipts)
   431  		if isInsufficientExecReceipts {
   432  			return nil, status.Errorf(codes.NotFound, err.Error())
   433  		}
   434  		return nil, status.Errorf(codes.Internal, "failed to retrieve result from any execution node: %v", err)
   435  	}
   436  
   437  	resp, err := b.getTransactionResultByIndexFromAnyExeNode(ctx, execNodes, req)
   438  	if err != nil {
   439  		if status.Code(err) == codes.NotFound {
   440  			return nil, err
   441  		}
   442  		return nil, status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err)
   443  	}
   444  
   445  	// tx body is irrelevant to status if it's in an executed block
   446  	txStatus, err := b.deriveTransactionStatus(nil, true, block)
   447  	if err != nil {
   448  		return nil, rpc.ConvertStorageError(err)
   449  	}
   450  
   451  	// convert to response, cache and return
   452  	return &access.TransactionResult{
   453  		Status:       txStatus,
   454  		StatusCode:   uint(resp.GetStatusCode()),
   455  		Events:       convert.MessagesToEvents(resp.GetEvents()),
   456  		ErrorMessage: resp.GetErrorMessage(),
   457  		BlockID:      blockID,
   458  		BlockHeight:  block.Header.Height,
   459  	}, nil
   460  }
   461  
   462  // deriveTransactionStatus derives the transaction status based on current protocol state
   463  func (b *backendTransactions) deriveTransactionStatus(
   464  	tx *flow.TransactionBody,
   465  	executed bool,
   466  	block *flow.Block,
   467  ) (flow.TransactionStatus, error) {
   468  
   469  	if block == nil {
   470  		// Not in a block, let's see if it's expired
   471  		referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head()
   472  		if err != nil {
   473  			return flow.TransactionStatusUnknown, err
   474  		}
   475  		refHeight := referenceBlock.Height
   476  		// get the latest finalized block from the state
   477  		finalized, err := b.state.Final().Head()
   478  		if err != nil {
   479  			return flow.TransactionStatusUnknown, err
   480  		}
   481  		finalizedHeight := finalized.Height
   482  
   483  		// if we haven't seen the expiry block for this transaction, it's not expired
   484  		if !b.isExpired(refHeight, finalizedHeight) {
   485  			return flow.TransactionStatusPending, nil
   486  		}
   487  
   488  		// At this point, we have seen the expiry block for the transaction.
   489  		// This means that, if no collections prior to the expiry block contain
   490  		// the transaction, it can never be included and is expired.
   491  		//
   492  		// To ensure this, we need to have received all collections up to the
   493  		// expiry block to ensure the transaction did not appear in any.
   494  
   495  		// the last full height is the height where we have received all
   496  		// collections for all blocks with a lower height
   497  		fullHeight, err := b.blocks.GetLastFullBlockHeight()
   498  		if err != nil {
   499  			return flow.TransactionStatusUnknown, err
   500  		}
   501  
   502  		// if we have received collections for all blocks up to the expiry block, the transaction is expired
   503  		if b.isExpired(refHeight, fullHeight) {
   504  			return flow.TransactionStatusExpired, err
   505  		}
   506  
   507  		// tx found in transaction storage and collection storage but not in block storage
   508  		// However, this will not happen as of now since the ingestion engine doesn't subscribe
   509  		// for collections
   510  		return flow.TransactionStatusPending, nil
   511  	}
   512  
   513  	if !executed {
   514  		// If we've gotten here, but the block has not yet been executed, report it as only been finalized
   515  		return flow.TransactionStatusFinalized, nil
   516  	}
   517  
   518  	// From this point on, we know for sure this transaction has at least been executed
   519  
   520  	// get the latest sealed block from the state
   521  	sealed, err := b.state.Sealed().Head()
   522  	if err != nil {
   523  		return flow.TransactionStatusUnknown, err
   524  	}
   525  
   526  	if block.Header.Height > sealed.Height {
   527  		// The block is not yet sealed, so we'll report it as only executed
   528  		return flow.TransactionStatusExecuted, nil
   529  	}
   530  
   531  	// otherwise, this block has been executed, and sealed, so report as sealed
   532  	return flow.TransactionStatusSealed, nil
   533  }
   534  
   535  // isExpired checks whether a transaction is expired given the height of the
   536  // transaction's reference block and the height to compare against.
   537  func (b *backendTransactions) isExpired(refHeight, compareToHeight uint64) bool {
   538  	if compareToHeight <= refHeight {
   539  		return false
   540  	}
   541  	return compareToHeight-refHeight > flow.DefaultTransactionExpiry
   542  }
   543  
   544  func (b *backendTransactions) lookupBlock(txID flow.Identifier) (*flow.Block, error) {
   545  
   546  	collection, err := b.collections.LightByTransactionID(txID)
   547  	if err != nil {
   548  		return nil, err
   549  	}
   550  
   551  	block, err := b.blocks.ByCollectionID(collection.ID())
   552  	if err != nil {
   553  		return nil, err
   554  	}
   555  
   556  	return block, nil
   557  }
   558  
   559  func (b *backendTransactions) lookupTransactionResult(
   560  	ctx context.Context,
   561  	txID flow.Identifier,
   562  	blockID flow.Identifier,
   563  ) (bool, []flow.Event, uint32, string, error) {
   564  
   565  	events, txStatus, message, err := b.getTransactionResultFromExecutionNode(ctx, blockID, txID[:])
   566  	if err != nil {
   567  		// if either the execution node reported no results or the execution node could not be chosen
   568  		if status.Code(err) == codes.NotFound {
   569  			// No result yet, indicate that it has not been executed
   570  			return false, nil, 0, "", nil
   571  		}
   572  		// Other Error trying to retrieve the result, return with err
   573  		return false, nil, 0, "", err
   574  	}
   575  
   576  	// considered executed as long as some result is returned, even if it's an error message
   577  	return true, events, txStatus, message, nil
   578  }
   579  
   580  func (b *backendTransactions) getHistoricalTransaction(
   581  	ctx context.Context,
   582  	txID flow.Identifier,
   583  ) (*flow.TransactionBody, error) {
   584  	for _, historicalNode := range b.previousAccessNodes {
   585  		txResp, err := historicalNode.GetTransaction(ctx, &accessproto.GetTransactionRequest{Id: txID[:]})
   586  		if err == nil {
   587  			tx, err := convert.MessageToTransaction(txResp.Transaction, b.chainID.Chain())
   588  			// Found on a historical node. Report
   589  			return &tx, err
   590  		}
   591  		// Otherwise, if not found, just continue
   592  		if status.Code(err) == codes.NotFound {
   593  			continue
   594  		}
   595  		// TODO should we do something if the error isn't not found?
   596  	}
   597  	return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID)
   598  }
   599  
   600  func (b *backendTransactions) getHistoricalTransactionResult(
   601  	ctx context.Context,
   602  	txID flow.Identifier,
   603  ) (*access.TransactionResult, error) {
   604  	for _, historicalNode := range b.previousAccessNodes {
   605  		result, err := historicalNode.GetTransactionResult(ctx, &accessproto.GetTransactionRequest{Id: txID[:]})
   606  		if err == nil {
   607  			// Found on a historical node. Report
   608  			if result.GetStatus() == entities.TransactionStatus_PENDING {
   609  				// This is on a historical node. No transactions from it will ever be
   610  				// executed, therefore we should consider this expired
   611  				result.Status = entities.TransactionStatus_EXPIRED
   612  			} else if result.GetStatus() == entities.TransactionStatus_UNKNOWN {
   613  				// We've moved to returning Status UNKNOWN instead of an error with the NotFound status,
   614  				// Therefore we should continue and look at the next access node for answers.
   615  				continue
   616  			}
   617  			return access.MessageToTransactionResult(result), nil
   618  		}
   619  		// Otherwise, if not found, just continue
   620  		if status.Code(err) == codes.NotFound {
   621  			continue
   622  		}
   623  		// TODO should we do something if the error isn't not found?
   624  	}
   625  	return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID)
   626  }
   627  
   628  func (b *backendTransactions) registerTransactionForRetry(tx *flow.TransactionBody) {
   629  	referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head()
   630  	if err != nil {
   631  		return
   632  	}
   633  
   634  	b.retry.RegisterTransaction(referenceBlock.Height, tx)
   635  }
   636  
   637  func (b *backendTransactions) getTransactionResultFromExecutionNode(
   638  	ctx context.Context,
   639  	blockID flow.Identifier,
   640  	transactionID []byte,
   641  ) ([]flow.Event, uint32, string, error) {
   642  
   643  	// create an execution API request for events at blockID and transactionID
   644  	req := &execproto.GetTransactionResultRequest{
   645  		BlockId:       blockID[:],
   646  		TransactionId: transactionID,
   647  	}
   648  
   649  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   650  	if err != nil {
   651  		// if no execution receipt were found, return a NotFound GRPC error
   652  		if errors.As(err, &InsufficientExecutionReceipts{}) {
   653  			return nil, 0, "", status.Errorf(codes.NotFound, err.Error())
   654  		}
   655  		return nil, 0, "", status.Errorf(codes.Internal, "failed to retrieve result from any execution node: %v", err)
   656  	}
   657  
   658  	resp, err := b.getTransactionResultFromAnyExeNode(ctx, execNodes, req)
   659  	if err != nil {
   660  		if status.Code(err) == codes.NotFound {
   661  			return nil, 0, "", err
   662  		}
   663  		return nil, 0, "", status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err)
   664  	}
   665  
   666  	events := convert.MessagesToEvents(resp.GetEvents())
   667  
   668  	return events, resp.GetStatusCode(), resp.GetErrorMessage(), nil
   669  }
   670  
   671  func (b *backendTransactions) NotifyFinalizedBlockHeight(height uint64) {
   672  	b.retry.Retry(height)
   673  }
   674  
   675  func (b *backendTransactions) getTransactionResultFromAnyExeNode(
   676  	ctx context.Context,
   677  	execNodes flow.IdentityList,
   678  	req *execproto.GetTransactionResultRequest,
   679  ) (*execproto.GetTransactionResultResponse, error) {
   680  	var errs *multierror.Error
   681  	logAnyError := func() {
   682  		errToReturn := errs.ErrorOrNil()
   683  		if errToReturn != nil {
   684  			b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes")
   685  		}
   686  	}
   687  	defer logAnyError()
   688  	// try to execute the script on one of the execution nodes
   689  	for _, execNode := range execNodes {
   690  		resp, err := b.tryGetTransactionResult(ctx, execNode, req)
   691  		if err == nil {
   692  			b.log.Debug().
   693  				Str("execution_node", execNode.String()).
   694  				Hex("block_id", req.GetBlockId()).
   695  				Hex("transaction_id", req.GetTransactionId()).
   696  				Msg("Successfully got transaction results from any node")
   697  			return resp, nil
   698  		}
   699  		if status.Code(err) == codes.NotFound {
   700  			return nil, err
   701  		}
   702  		errs = multierror.Append(errs, err)
   703  	}
   704  	return nil, errs.ErrorOrNil()
   705  }
   706  
   707  func (b *backendTransactions) tryGetTransactionResult(
   708  	ctx context.Context,
   709  	execNode *flow.Identity,
   710  	req *execproto.GetTransactionResultRequest,
   711  ) (*execproto.GetTransactionResultResponse, error) {
   712  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   713  	if err != nil {
   714  		return nil, err
   715  	}
   716  	defer closer.Close()
   717  
   718  	resp, err := execRPCClient.GetTransactionResult(ctx, req)
   719  	if err != nil {
   720  		if status.Code(err) == codes.Unavailable {
   721  			b.connFactory.InvalidateExecutionAPIClient(execNode.Address)
   722  		}
   723  		return nil, err
   724  	}
   725  	return resp, err
   726  }
   727  
   728  func (b *backendTransactions) getTransactionResultsByBlockIDFromAnyExeNode(
   729  	ctx context.Context,
   730  	execNodes flow.IdentityList,
   731  	req *execproto.GetTransactionsByBlockIDRequest,
   732  ) (*execproto.GetTransactionResultsResponse, error) {
   733  	var errs *multierror.Error
   734  
   735  	defer func() {
   736  		if err := errs.ErrorOrNil(); err != nil {
   737  			b.log.Err(errs).Msg("failed to get transaction results from execution nodes")
   738  		}
   739  	}()
   740  
   741  	// if we were passed 0 execution nodes add a specific error
   742  	if len(execNodes) == 0 {
   743  		return nil, errors.New("zero execution nodes")
   744  	}
   745  
   746  	for _, execNode := range execNodes {
   747  		resp, err := b.tryGetTransactionResultsByBlockID(ctx, execNode, req)
   748  		if err == nil {
   749  			b.log.Debug().
   750  				Str("execution_node", execNode.String()).
   751  				Hex("block_id", req.GetBlockId()).
   752  				Msg("Successfully got transaction results from any node")
   753  			return resp, nil
   754  		}
   755  		if status.Code(err) == codes.NotFound {
   756  			return nil, err
   757  		}
   758  		errs = multierror.Append(errs, err)
   759  	}
   760  
   761  	// log the errors
   762  	return nil, errs.ErrorOrNil()
   763  }
   764  
   765  func (b *backendTransactions) tryGetTransactionResultsByBlockID(
   766  	ctx context.Context,
   767  	execNode *flow.Identity,
   768  	req *execproto.GetTransactionsByBlockIDRequest,
   769  ) (*execproto.GetTransactionResultsResponse, error) {
   770  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   771  	if err != nil {
   772  		return nil, err
   773  	}
   774  	defer closer.Close()
   775  
   776  	resp, err := execRPCClient.GetTransactionResultsByBlockID(ctx, req)
   777  	if err != nil {
   778  		if status.Code(err) == codes.Unavailable {
   779  			b.connFactory.InvalidateExecutionAPIClient(execNode.Address)
   780  		}
   781  		return nil, err
   782  	}
   783  	return resp, err
   784  }
   785  
   786  func (b *backendTransactions) getTransactionResultByIndexFromAnyExeNode(
   787  	ctx context.Context,
   788  	execNodes flow.IdentityList,
   789  	req *execproto.GetTransactionByIndexRequest,
   790  ) (*execproto.GetTransactionResultResponse, error) {
   791  	var errs *multierror.Error
   792  	logAnyError := func() {
   793  		errToReturn := errs.ErrorOrNil()
   794  		if errToReturn != nil {
   795  			b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes")
   796  		}
   797  	}
   798  	defer logAnyError()
   799  
   800  	if len(execNodes) == 0 {
   801  		return nil, errors.New("zero execution nodes provided")
   802  	}
   803  
   804  	// try to execute the script on one of the execution nodes
   805  	for _, execNode := range execNodes {
   806  		resp, err := b.tryGetTransactionResultByIndex(ctx, execNode, req)
   807  		if err == nil {
   808  			b.log.Debug().
   809  				Str("execution_node", execNode.String()).
   810  				Hex("block_id", req.GetBlockId()).
   811  				Uint32("index", req.GetIndex()).
   812  				Msg("Successfully got transaction results from any node")
   813  			return resp, nil
   814  		}
   815  		if status.Code(err) == codes.NotFound {
   816  			return nil, err
   817  		}
   818  		errs = multierror.Append(errs, err)
   819  	}
   820  
   821  	return nil, errs.ErrorOrNil()
   822  }
   823  
   824  func (b *backendTransactions) tryGetTransactionResultByIndex(
   825  	ctx context.Context,
   826  	execNode *flow.Identity,
   827  	req *execproto.GetTransactionByIndexRequest,
   828  ) (*execproto.GetTransactionResultResponse, error) {
   829  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   830  	if err != nil {
   831  		return nil, err
   832  	}
   833  	defer closer.Close()
   834  
   835  	resp, err := execRPCClient.GetTransactionResultByIndex(ctx, req)
   836  	if err != nil {
   837  		if status.Code(err) == codes.Unavailable {
   838  			b.connFactory.InvalidateExecutionAPIClient(execNode.Address)
   839  		}
   840  		return nil, err
   841  	}
   842  	return resp, err
   843  }