github.com/koko1123/flow-go-1@v0.29.6/engine/execution/computation/manager.go (about)

     1  package computation
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"math/rand"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	jsoncdc "github.com/onflow/cadence/encoding/json"
    13  	"github.com/onflow/cadence/runtime"
    14  	"github.com/rs/zerolog"
    15  
    16  	"github.com/koko1123/flow-go-1/engine/execution"
    17  	"github.com/koko1123/flow-go-1/engine/execution/computation/computer"
    18  	"github.com/koko1123/flow-go-1/fvm"
    19  	"github.com/koko1123/flow-go-1/fvm/derived"
    20  	reusableRuntime "github.com/koko1123/flow-go-1/fvm/runtime"
    21  	"github.com/koko1123/flow-go-1/fvm/state"
    22  	"github.com/koko1123/flow-go-1/model/flow"
    23  	"github.com/koko1123/flow-go-1/module"
    24  	"github.com/koko1123/flow-go-1/module/executiondatasync/provider"
    25  	"github.com/koko1123/flow-go-1/module/mempool/entity"
    26  	"github.com/koko1123/flow-go-1/state/protocol"
    27  	"github.com/koko1123/flow-go-1/utils/debug"
    28  	"github.com/koko1123/flow-go-1/utils/logging"
    29  )
    30  
    31  const (
    32  	DefaultScriptLogThreshold       = 1 * time.Second
    33  	DefaultScriptExecutionTimeLimit = 10 * time.Second
    34  
    35  	MaxScriptErrorMessageSize = 1000 // 1000 chars
    36  
    37  	ReusableCadenceRuntimePoolSize = 1000
    38  )
    39  
    40  type ComputationManager interface {
    41  	ExecuteScript(context.Context, []byte, [][]byte, *flow.Header, state.View) ([]byte, error)
    42  	ComputeBlock(
    43  		ctx context.Context,
    44  		block *entity.ExecutableBlock,
    45  		view state.View,
    46  	) (*execution.ComputationResult, error)
    47  	GetAccount(addr flow.Address, header *flow.Header, view state.View) (*flow.Account, error)
    48  }
    49  
    50  type ComputationConfig struct {
    51  	CadenceTracing           bool
    52  	ExtensiveTracing         bool
    53  	DerivedDataCacheSize     uint
    54  	ScriptLogThreshold       time.Duration
    55  	ScriptExecutionTimeLimit time.Duration
    56  
    57  	// When NewCustomVirtualMachine is nil, the manager will create a standard
    58  	// fvm virtual machine via fvm.NewVirtualMachine.  Otherwise, the manager
    59  	// will create a virtual machine using this function.
    60  	//
    61  	// Note that this is primarily used for testing.
    62  	NewCustomVirtualMachine func() fvm.VM
    63  }
    64  
    65  // Manager manages computation and execution
    66  type Manager struct {
    67  	log                      zerolog.Logger
    68  	tracer                   module.Tracer
    69  	metrics                  module.ExecutionMetrics
    70  	me                       module.Local
    71  	protoState               protocol.State
    72  	vm                       fvm.VM
    73  	vmCtx                    fvm.Context
    74  	blockComputer            computer.BlockComputer
    75  	derivedChainData         *derived.DerivedChainData
    76  	scriptLogThreshold       time.Duration
    77  	scriptExecutionTimeLimit time.Duration
    78  	rngLock                  *sync.Mutex
    79  	rng                      *rand.Rand
    80  }
    81  
    82  func New(
    83  	logger zerolog.Logger,
    84  	metrics module.ExecutionMetrics,
    85  	tracer module.Tracer,
    86  	me module.Local,
    87  	protoState protocol.State,
    88  	vmCtx fvm.Context,
    89  	committer computer.ViewCommitter,
    90  	executionDataProvider *provider.Provider,
    91  	params ComputationConfig,
    92  ) (*Manager, error) {
    93  	log := logger.With().Str("engine", "computation").Logger()
    94  
    95  	var vm fvm.VM
    96  	if params.NewCustomVirtualMachine != nil {
    97  		vm = params.NewCustomVirtualMachine()
    98  	} else {
    99  		vm = fvm.NewVirtualMachine()
   100  	}
   101  
   102  	options := []fvm.Option{
   103  		fvm.WithReusableCadenceRuntimePool(
   104  			reusableRuntime.NewReusableCadenceRuntimePool(
   105  				ReusableCadenceRuntimePoolSize,
   106  				runtime.Config{
   107  					TracingEnabled: params.CadenceTracing,
   108  				})),
   109  	}
   110  	if params.ExtensiveTracing {
   111  		options = append(options, fvm.WithExtensiveTracing())
   112  	}
   113  
   114  	vmCtx = fvm.NewContextFromParent(vmCtx, options...)
   115  
   116  	blockComputer, err := computer.NewBlockComputer(
   117  		vm,
   118  		vmCtx,
   119  		metrics,
   120  		tracer,
   121  		log.With().Str("component", "block_computer").Logger(),
   122  		committer,
   123  		me,
   124  		executionDataProvider,
   125  	)
   126  
   127  	if err != nil {
   128  		return nil, fmt.Errorf("cannot create block computer: %w", err)
   129  	}
   130  
   131  	derivedChainData, err := derived.NewDerivedChainData(params.DerivedDataCacheSize)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("cannot create derived data cache: %w", err)
   134  	}
   135  
   136  	e := Manager{
   137  		log:                      log,
   138  		tracer:                   tracer,
   139  		metrics:                  metrics,
   140  		me:                       me,
   141  		protoState:               protoState,
   142  		vm:                       vm,
   143  		vmCtx:                    vmCtx,
   144  		blockComputer:            blockComputer,
   145  		derivedChainData:         derivedChainData,
   146  		scriptLogThreshold:       params.ScriptLogThreshold,
   147  		scriptExecutionTimeLimit: params.ScriptExecutionTimeLimit,
   148  		rngLock:                  &sync.Mutex{},
   149  		rng:                      rand.New(rand.NewSource(time.Now().UnixNano())),
   150  	}
   151  
   152  	return &e, nil
   153  }
   154  
   155  func (e *Manager) VM() fvm.VM {
   156  	return e.vm
   157  }
   158  
   159  func (e *Manager) ExecuteScript(
   160  	ctx context.Context,
   161  	code []byte,
   162  	arguments [][]byte,
   163  	blockHeader *flow.Header,
   164  	view state.View,
   165  ) ([]byte, error) {
   166  
   167  	startedAt := time.Now()
   168  	memAllocBefore := debug.GetHeapAllocsBytes()
   169  
   170  	// allocate a random ID to be able to track this script when its done,
   171  	// scripts might not be unique so we use this extra tracker to follow their logs
   172  	// TODO: this is a temporary measure, we could remove this in the future
   173  	if e.log.Debug().Enabled() {
   174  		e.rngLock.Lock()
   175  		trackerID := e.rng.Uint32()
   176  		e.rngLock.Unlock()
   177  
   178  		trackedLogger := e.log.With().Hex("script_hex", code).Uint32("trackerID", trackerID).Logger()
   179  		trackedLogger.Debug().Msg("script is sent for execution")
   180  		defer func() {
   181  			trackedLogger.Debug().Msg("script execution is complete")
   182  		}()
   183  	}
   184  
   185  	requestCtx, cancel := context.WithTimeout(ctx, e.scriptExecutionTimeLimit)
   186  	defer cancel()
   187  
   188  	script := fvm.NewScriptWithContextAndArgs(code, requestCtx, arguments...)
   189  	blockCtx := fvm.NewContextFromParent(
   190  		e.vmCtx,
   191  		fvm.WithBlockHeader(blockHeader),
   192  		fvm.WithDerivedBlockData(
   193  			e.derivedChainData.NewDerivedBlockDataForScript(blockHeader.ID())))
   194  
   195  	err := func() (err error) {
   196  
   197  		start := time.Now()
   198  
   199  		defer func() {
   200  
   201  			prepareLog := func() *zerolog.Event {
   202  
   203  				args := make([]string, 0, len(arguments))
   204  				for _, a := range arguments {
   205  					args = append(args, hex.EncodeToString(a))
   206  				}
   207  				return e.log.Error().
   208  					Hex("script_hex", code).
   209  					Str("args", strings.Join(args, ","))
   210  			}
   211  
   212  			elapsed := time.Since(start)
   213  
   214  			if r := recover(); r != nil {
   215  				prepareLog().
   216  					Interface("recovered", r).
   217  					Msg("script execution caused runtime panic")
   218  
   219  				err = fmt.Errorf("cadence runtime error: %s", r)
   220  				return
   221  			}
   222  			if elapsed >= e.scriptLogThreshold {
   223  				prepareLog().
   224  					Dur("duration", elapsed).
   225  					Msg("script execution exceeded threshold")
   226  			}
   227  		}()
   228  
   229  		return e.vm.Run(blockCtx, script, view)
   230  	}()
   231  	if err != nil {
   232  		return nil, fmt.Errorf("failed to execute script (internal error): %w", err)
   233  	}
   234  
   235  	if script.Err != nil {
   236  		scriptErrMsg := script.Err.Error()
   237  		if len(scriptErrMsg) > MaxScriptErrorMessageSize {
   238  			split := int(MaxScriptErrorMessageSize/2) - 1
   239  			var sb strings.Builder
   240  			sb.WriteString(scriptErrMsg[:split])
   241  			sb.WriteString(" ... ")
   242  			sb.WriteString(scriptErrMsg[len(scriptErrMsg)-split:])
   243  			scriptErrMsg = sb.String()
   244  		}
   245  
   246  		return nil, fmt.Errorf("failed to execute script at block (%s): %s", blockHeader.ID(), scriptErrMsg)
   247  	}
   248  
   249  	encodedValue, err := jsoncdc.Encode(script.Value)
   250  	if err != nil {
   251  		return nil, fmt.Errorf("failed to encode runtime value: %w", err)
   252  	}
   253  
   254  	memAllocAfter := debug.GetHeapAllocsBytes()
   255  	e.metrics.ExecutionScriptExecuted(time.Since(startedAt), script.GasUsed, memAllocAfter-memAllocBefore, script.MemoryEstimate)
   256  
   257  	return encodedValue, nil
   258  }
   259  
   260  func (e *Manager) ComputeBlock(
   261  	ctx context.Context,
   262  	block *entity.ExecutableBlock,
   263  	view state.View,
   264  ) (*execution.ComputationResult, error) {
   265  
   266  	e.log.Debug().
   267  		Hex("block_id", logging.Entity(block.Block)).
   268  		Msg("received complete block")
   269  
   270  	derivedBlockData := e.derivedChainData.GetOrCreateDerivedBlockData(
   271  		block.ID(),
   272  		block.ParentID())
   273  
   274  	result, err := e.blockComputer.ExecuteBlock(ctx, block, view, derivedBlockData)
   275  	if err != nil {
   276  		e.log.Error().
   277  			Hex("block_id", logging.Entity(block.Block)).
   278  			Msg("failed to compute block result")
   279  
   280  		return nil, fmt.Errorf("failed to execute block: %w", err)
   281  	}
   282  
   283  	e.log.Debug().
   284  		Hex("block_id", logging.Entity(result.ExecutableBlock.Block)).
   285  		Msg("computed block result")
   286  
   287  	return result, nil
   288  }
   289  
   290  func (e *Manager) GetAccount(address flow.Address, blockHeader *flow.Header, view state.View) (*flow.Account, error) {
   291  	blockCtx := fvm.NewContextFromParent(
   292  		e.vmCtx,
   293  		fvm.WithBlockHeader(blockHeader),
   294  		fvm.WithDerivedBlockData(
   295  			e.derivedChainData.NewDerivedBlockDataForScript(blockHeader.ID())))
   296  
   297  	account, err := e.vm.GetAccount(blockCtx, address, view)
   298  	if err != nil {
   299  		return nil, fmt.Errorf("failed to get account (%s) at block (%s): %w", address.String(), blockHeader.ID(), err)
   300  	}
   301  
   302  	return account, nil
   303  }