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

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5" //nolint:gosec
     6  	"time"
     7  
     8  	lru "github.com/hashicorp/golang-lru"
     9  
    10  	"github.com/hashicorp/go-multierror"
    11  	execproto "github.com/onflow/flow/protobuf/go/flow/execution"
    12  	"github.com/rs/zerolog"
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/grpc/status"
    15  
    16  	"github.com/koko1123/flow-go-1/engine/common/rpc"
    17  	"github.com/koko1123/flow-go-1/model/flow"
    18  	"github.com/koko1123/flow-go-1/module"
    19  	"github.com/koko1123/flow-go-1/state/protocol"
    20  	"github.com/koko1123/flow-go-1/storage"
    21  )
    22  
    23  // uniqueScriptLoggingTimeWindow is the duration for checking the uniqueness of scripts sent for execution
    24  const uniqueScriptLoggingTimeWindow = 10 * time.Minute
    25  
    26  type backendScripts struct {
    27  	headers           storage.Headers
    28  	executionReceipts storage.ExecutionReceipts
    29  	state             protocol.State
    30  	connFactory       ConnectionFactory
    31  	log               zerolog.Logger
    32  	metrics           module.BackendScriptsMetrics
    33  	loggedScripts     *lru.Cache
    34  }
    35  
    36  func (b *backendScripts) ExecuteScriptAtLatestBlock(
    37  	ctx context.Context,
    38  	script []byte,
    39  	arguments [][]byte,
    40  ) ([]byte, error) {
    41  
    42  	// get the latest sealed header
    43  	latestHeader, err := b.state.Sealed().Head()
    44  	if err != nil {
    45  		return nil, status.Errorf(codes.Internal, "failed to get latest sealed header: %v", err)
    46  	}
    47  
    48  	// get the block id of the latest sealed header
    49  	latestBlockID := latestHeader.ID()
    50  
    51  	// execute script on the execution node at that block id
    52  	return b.executeScriptOnExecutionNode(ctx, latestBlockID, script, arguments)
    53  }
    54  
    55  func (b *backendScripts) ExecuteScriptAtBlockID(
    56  	ctx context.Context,
    57  	blockID flow.Identifier,
    58  	script []byte,
    59  	arguments [][]byte,
    60  ) ([]byte, error) {
    61  	// execute script on the execution node at that block id
    62  	return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments)
    63  }
    64  
    65  func (b *backendScripts) ExecuteScriptAtBlockHeight(
    66  	ctx context.Context,
    67  	blockHeight uint64,
    68  	script []byte,
    69  	arguments [][]byte,
    70  ) ([]byte, error) {
    71  	// get header at given height
    72  	header, err := b.headers.ByHeight(blockHeight)
    73  	if err != nil {
    74  		err = rpc.ConvertStorageError(err)
    75  		return nil, err
    76  	}
    77  
    78  	blockID := header.ID()
    79  
    80  	// execute script on the execution node at that block id
    81  	return b.executeScriptOnExecutionNode(ctx, blockID, script, arguments)
    82  }
    83  
    84  // executeScriptOnExecutionNode forwards the request to the execution node using the execution node
    85  // grpc client and converts the response back to the access node api response format
    86  func (b *backendScripts) executeScriptOnExecutionNode(
    87  	ctx context.Context,
    88  	blockID flow.Identifier,
    89  	script []byte,
    90  	arguments [][]byte,
    91  ) ([]byte, error) {
    92  
    93  	execReq := &execproto.ExecuteScriptAtBlockIDRequest{
    94  		BlockId:   blockID[:],
    95  		Script:    script,
    96  		Arguments: arguments,
    97  	}
    98  
    99  	// find few execution nodes which have executed the block earlier and provided an execution receipt for it
   100  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   101  	if err != nil {
   102  		return nil, status.Errorf(codes.Internal, "failed to find execution nodes at blockId %v: %v", blockID.String(), err)
   103  	}
   104  	// encode to MD5 as low compute/memory lookup key
   105  	// CAUTION: cryptographically insecure md5 is used here, but only to de-duplicate logs.
   106  	// *DO NOT* use this hash for any protocol-related or cryptographic functions.
   107  	insecureScriptHash := md5.Sum(script) //nolint:gosec
   108  
   109  	// try each of the execution nodes found
   110  	var errors *multierror.Error
   111  	// try to execute the script on one of the execution nodes
   112  	for _, execNode := range execNodes {
   113  		execStartTime := time.Now() // record start time
   114  		result, err := b.tryExecuteScript(ctx, execNode, execReq)
   115  		if err == nil {
   116  			if b.log.GetLevel() == zerolog.DebugLevel {
   117  				executionTime := time.Now()
   118  				if b.shouldLogScript(executionTime, insecureScriptHash) {
   119  					b.log.Debug().
   120  						Str("execution_node", execNode.String()).
   121  						Hex("block_id", blockID[:]).
   122  						Hex("script_hash", insecureScriptHash[:]).
   123  						Str("script", string(script)).
   124  						Msg("Successfully executed script")
   125  					b.loggedScripts.Add(insecureScriptHash, executionTime)
   126  				}
   127  			}
   128  
   129  			// log execution time
   130  			b.metrics.ScriptExecuted(
   131  				time.Since(execStartTime),
   132  				len(script),
   133  			)
   134  
   135  			return result, nil
   136  		}
   137  		// return if it's just a script failure as opposed to an EN failure and skip trying other ENs
   138  		if status.Code(err) == codes.InvalidArgument {
   139  			b.log.Debug().Err(err).
   140  				Str("execution_node", execNode.String()).
   141  				Hex("block_id", blockID[:]).
   142  				Hex("script_hash", insecureScriptHash[:]).
   143  				Str("script", string(script)).
   144  				Msg("script failed to execute on the execution node")
   145  			return nil, err
   146  		}
   147  		errors = multierror.Append(errors, err)
   148  	}
   149  	errToReturn := errors.ErrorOrNil()
   150  	if errToReturn != nil {
   151  		b.log.Error().Err(err).Msg("script execution failed for execution node internal reasons")
   152  	}
   153  	return nil, errToReturn
   154  }
   155  
   156  // shouldLogScript checks if the script hash is unique in the time window
   157  func (b *backendScripts) shouldLogScript(execTime time.Time, scriptHash [16]byte) bool {
   158  	rawTimestamp, seen := b.loggedScripts.Get(scriptHash)
   159  	if !seen || rawTimestamp == nil {
   160  		return true
   161  	} else {
   162  		// safe cast
   163  		timestamp := rawTimestamp.(time.Time)
   164  		return execTime.Sub(timestamp) >= uniqueScriptLoggingTimeWindow
   165  	}
   166  }
   167  
   168  func (b *backendScripts) tryExecuteScript(ctx context.Context, execNode *flow.Identity, req *execproto.ExecuteScriptAtBlockIDRequest) ([]byte, error) {
   169  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   170  	if err != nil {
   171  		return nil, status.Errorf(codes.Internal, "failed to create client for execution node %s: %v", execNode.String(), err)
   172  	}
   173  	defer closer.Close()
   174  
   175  	execResp, err := execRPCClient.ExecuteScriptAtBlockID(ctx, req)
   176  	if err != nil {
   177  		if status.Code(err) == codes.Unavailable {
   178  			b.connFactory.InvalidateExecutionAPIClient(execNode.Address)
   179  		}
   180  		return nil, status.Errorf(status.Code(err), "failed to execute the script on the execution node %s: %v", execNode.String(), err)
   181  	}
   182  	return execResp.GetValue(), nil
   183  }