github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/fvm.go (about)

     1  package fvm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/onflow/cadence"
     8  
     9  	"github.com/onflow/flow-go/fvm/environment"
    10  	"github.com/onflow/flow-go/fvm/errors"
    11  	"github.com/onflow/flow-go/fvm/meter"
    12  	"github.com/onflow/flow-go/fvm/storage"
    13  	"github.com/onflow/flow-go/fvm/storage/logical"
    14  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    15  	"github.com/onflow/flow-go/fvm/storage/state"
    16  	"github.com/onflow/flow-go/model/flow"
    17  )
    18  
    19  type ProcedureType string
    20  
    21  const (
    22  	BootstrapProcedureType   = ProcedureType("bootstrap")
    23  	TransactionProcedureType = ProcedureType("transaction")
    24  	ScriptProcedureType      = ProcedureType("script")
    25  )
    26  
    27  type ProcedureOutput struct {
    28  	// Output by both transaction and script.
    29  	Logs                   []string
    30  	Events                 flow.EventsList
    31  	ServiceEvents          flow.EventsList
    32  	ConvertedServiceEvents flow.ServiceEventList
    33  	ComputationUsed        uint64
    34  	ComputationIntensities meter.MeteredComputationIntensities
    35  	MemoryEstimate         uint64
    36  	Err                    errors.CodedError
    37  
    38  	// Output only by script.
    39  	Value cadence.Value
    40  }
    41  
    42  func (output *ProcedureOutput) PopulateEnvironmentValues(
    43  	env environment.Environment,
    44  ) error {
    45  	output.Logs = env.Logs()
    46  
    47  	computationUsed, err := env.ComputationUsed()
    48  	if err != nil {
    49  		return fmt.Errorf("error getting computation used: %w", err)
    50  	}
    51  	output.ComputationUsed = computationUsed
    52  
    53  	memoryUsed, err := env.MemoryUsed()
    54  	if err != nil {
    55  		return fmt.Errorf("error getting memory used: %w", err)
    56  	}
    57  	output.MemoryEstimate = memoryUsed
    58  
    59  	output.ComputationIntensities = env.ComputationIntensities()
    60  
    61  	// if tx failed this will only contain fee deduction events
    62  	output.Events = env.Events()
    63  	output.ServiceEvents = env.ServiceEvents()
    64  	output.ConvertedServiceEvents = env.ConvertedServiceEvents()
    65  
    66  	return nil
    67  }
    68  
    69  type ProcedureExecutor interface {
    70  	Preprocess() error
    71  	Execute() error
    72  	Cleanup()
    73  
    74  	Output() ProcedureOutput
    75  }
    76  
    77  func Run(executor ProcedureExecutor) error {
    78  	defer executor.Cleanup()
    79  	err := executor.Preprocess()
    80  	if err != nil {
    81  		return err
    82  	}
    83  	return executor.Execute()
    84  }
    85  
    86  // An Procedure is an operation (or set of operations) that reads or writes ledger state.
    87  type Procedure interface {
    88  	NewExecutor(
    89  		ctx Context,
    90  		txnState storage.TransactionPreparer,
    91  	) ProcedureExecutor
    92  
    93  	ComputationLimit(ctx Context) uint64
    94  	MemoryLimit(ctx Context) uint64
    95  	ShouldDisableMemoryAndInteractionLimits(ctx Context) bool
    96  
    97  	Type() ProcedureType
    98  
    99  	// For transactions, the execution time is TxIndex.  For scripts, the
   100  	// execution time is EndOfBlockExecutionTime.
   101  	ExecutionTime() logical.Time
   102  }
   103  
   104  // VM runs procedures
   105  type VM interface {
   106  	NewExecutor(
   107  		Context,
   108  		Procedure,
   109  		storage.TransactionPreparer,
   110  	) ProcedureExecutor
   111  
   112  	Run(
   113  		Context,
   114  		Procedure,
   115  		snapshot.StorageSnapshot,
   116  	) (
   117  		*snapshot.ExecutionSnapshot,
   118  		ProcedureOutput,
   119  		error,
   120  	)
   121  
   122  	GetAccount(Context, flow.Address, snapshot.StorageSnapshot) (*flow.Account, error)
   123  }
   124  
   125  var _ VM = (*VirtualMachine)(nil)
   126  
   127  // A VirtualMachine augments the Cadence runtime with Flow host functionality.
   128  type VirtualMachine struct {
   129  }
   130  
   131  func NewVirtualMachine() *VirtualMachine {
   132  	return &VirtualMachine{}
   133  }
   134  
   135  func (vm *VirtualMachine) NewExecutor(
   136  	ctx Context,
   137  	proc Procedure,
   138  	txn storage.TransactionPreparer,
   139  ) ProcedureExecutor {
   140  	return proc.NewExecutor(ctx, txn)
   141  }
   142  
   143  // Run runs a procedure against a ledger in the given context.
   144  func (vm *VirtualMachine) Run(
   145  	ctx Context,
   146  	proc Procedure,
   147  	storageSnapshot snapshot.StorageSnapshot,
   148  ) (
   149  	*snapshot.ExecutionSnapshot,
   150  	ProcedureOutput,
   151  	error,
   152  ) {
   153  	blockDatabase := storage.NewBlockDatabase(
   154  		storageSnapshot,
   155  		proc.ExecutionTime(),
   156  		ctx.DerivedBlockData)
   157  
   158  	stateParameters := ProcedureStateParameters(ctx, proc)
   159  
   160  	var storageTxn storage.Transaction
   161  	var err error
   162  	switch proc.Type() {
   163  	case ScriptProcedureType:
   164  		if ctx.AllowProgramCacheWritesInScripts {
   165  			// if configured, allow scripts to update the programs cache
   166  			storageTxn, err = blockDatabase.NewCachingSnapshotReadTransaction(stateParameters)
   167  		} else {
   168  			storageTxn = blockDatabase.NewSnapshotReadTransaction(stateParameters)
   169  		}
   170  	case TransactionProcedureType, BootstrapProcedureType:
   171  		storageTxn, err = blockDatabase.NewTransaction(
   172  			proc.ExecutionTime(),
   173  			stateParameters)
   174  	default:
   175  		return nil, ProcedureOutput{}, fmt.Errorf(
   176  			"invalid proc type: %v",
   177  			proc.Type())
   178  	}
   179  
   180  	if err != nil {
   181  		return nil, ProcedureOutput{}, fmt.Errorf(
   182  			"error creating derived transaction data: %w",
   183  			err)
   184  	}
   185  
   186  	executor := proc.NewExecutor(ctx, storageTxn)
   187  	err = Run(executor)
   188  	if err != nil {
   189  		return nil, ProcedureOutput{}, err
   190  	}
   191  
   192  	err = storageTxn.Finalize()
   193  	if err != nil {
   194  		return nil, ProcedureOutput{}, err
   195  	}
   196  
   197  	executionSnapshot, err := storageTxn.Commit()
   198  	if err != nil {
   199  		return nil, ProcedureOutput{}, err
   200  	}
   201  
   202  	return executionSnapshot, executor.Output(), nil
   203  }
   204  
   205  // GetAccount returns an account by address or an error if none exists.
   206  func (vm *VirtualMachine) GetAccount(
   207  	ctx Context,
   208  	address flow.Address,
   209  	storageSnapshot snapshot.StorageSnapshot,
   210  ) (
   211  	*flow.Account,
   212  	error,
   213  ) {
   214  	blockDatabase := storage.NewBlockDatabase(
   215  		storageSnapshot,
   216  		0,
   217  		ctx.DerivedBlockData)
   218  
   219  	storageTxn := blockDatabase.NewSnapshotReadTransaction(
   220  		state.DefaultParameters().
   221  			WithMaxKeySizeAllowed(ctx.MaxStateKeySize).
   222  			WithMaxValueSizeAllowed(ctx.MaxStateValueSize).
   223  			WithMeterParameters(
   224  				meter.DefaultParameters().
   225  					WithStorageInteractionLimit(ctx.MaxStateInteractionSize)))
   226  
   227  	env := environment.NewScriptEnv(
   228  		context.Background(),
   229  		ctx.TracerSpan,
   230  		ctx.EnvironmentParams,
   231  		storageTxn)
   232  	account, err := env.GetAccount(address)
   233  	if err != nil {
   234  		if errors.IsLedgerFailure(err) {
   235  			return nil, fmt.Errorf(
   236  				"cannot get account, this error usually happens if the "+
   237  					"reference block for this query is not set to a recent "+
   238  					"block: %w",
   239  				err)
   240  		}
   241  		return nil, fmt.Errorf("cannot get account: %w", err)
   242  	}
   243  	return account, nil
   244  }