github.com/onflow/flow-go@v0.33.17/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  	errors "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  		storageTxn = blockDatabase.NewSnapshotReadTransaction(stateParameters)
   165  	case TransactionProcedureType, BootstrapProcedureType:
   166  		storageTxn, err = blockDatabase.NewTransaction(
   167  			proc.ExecutionTime(),
   168  			stateParameters)
   169  	default:
   170  		return nil, ProcedureOutput{}, fmt.Errorf(
   171  			"invalid proc type: %v",
   172  			proc.Type())
   173  	}
   174  
   175  	if err != nil {
   176  		return nil, ProcedureOutput{}, fmt.Errorf(
   177  			"error creating derived transaction data: %w",
   178  			err)
   179  	}
   180  
   181  	executor := proc.NewExecutor(ctx, storageTxn)
   182  	err = Run(executor)
   183  	if err != nil {
   184  		return nil, ProcedureOutput{}, err
   185  	}
   186  
   187  	err = storageTxn.Finalize()
   188  	if err != nil {
   189  		return nil, ProcedureOutput{}, err
   190  	}
   191  
   192  	executionSnapshot, err := storageTxn.Commit()
   193  	if err != nil {
   194  		return nil, ProcedureOutput{}, err
   195  	}
   196  
   197  	return executionSnapshot, executor.Output(), nil
   198  }
   199  
   200  // GetAccount returns an account by address or an error if none exists.
   201  func (vm *VirtualMachine) GetAccount(
   202  	ctx Context,
   203  	address flow.Address,
   204  	storageSnapshot snapshot.StorageSnapshot,
   205  ) (
   206  	*flow.Account,
   207  	error,
   208  ) {
   209  	blockDatabase := storage.NewBlockDatabase(
   210  		storageSnapshot,
   211  		0,
   212  		ctx.DerivedBlockData)
   213  
   214  	storageTxn := blockDatabase.NewSnapshotReadTransaction(
   215  		state.DefaultParameters().
   216  			WithMaxKeySizeAllowed(ctx.MaxStateKeySize).
   217  			WithMaxValueSizeAllowed(ctx.MaxStateValueSize).
   218  			WithMeterParameters(
   219  				meter.DefaultParameters().
   220  					WithStorageInteractionLimit(ctx.MaxStateInteractionSize)))
   221  
   222  	env := environment.NewScriptEnv(
   223  		context.Background(),
   224  		ctx.TracerSpan,
   225  		ctx.EnvironmentParams,
   226  		storageTxn)
   227  	account, err := env.GetAccount(address)
   228  	if err != nil {
   229  		if errors.IsLedgerFailure(err) {
   230  			return nil, fmt.Errorf(
   231  				"cannot get account, this error usually happens if the "+
   232  					"reference block for this query is not set to a recent "+
   233  					"block: %w",
   234  				err)
   235  		}
   236  		return nil, fmt.Errorf("cannot get account: %w", err)
   237  	}
   238  	return account, nil
   239  }