github.com/onflow/flow-go@v0.33.17/fvm/storage/state/transaction_state.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/cadence/runtime/common"
     7  
     8  	"github.com/onflow/flow-go/fvm/meter"
     9  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    10  	"github.com/onflow/flow-go/model/flow"
    11  )
    12  
    13  // Opaque identifier used for Restarting nested transactions
    14  type NestedTransactionId struct {
    15  	state *ExecutionState
    16  }
    17  
    18  func (id NestedTransactionId) StateForTestingOnly() *ExecutionState {
    19  	return id.state
    20  }
    21  
    22  type Meter interface {
    23  	MeterComputation(kind common.ComputationKind, intensity uint) error
    24  	ComputationAvailable(kind common.ComputationKind, intensity uint) bool
    25  	ComputationIntensities() meter.MeteredComputationIntensities
    26  	TotalComputationLimit() uint
    27  	TotalComputationUsed() uint64
    28  
    29  	MeterMemory(kind common.MemoryKind, intensity uint) error
    30  	MemoryIntensities() meter.MeteredMemoryIntensities
    31  	TotalMemoryEstimate() uint64
    32  
    33  	InteractionUsed() uint64
    34  
    35  	MeterEmittedEvent(byteSize uint64) error
    36  	TotalEmittedEventBytes() uint64
    37  
    38  	// RunWithAllLimitsDisabled runs f with limits disabled
    39  	RunWithAllLimitsDisabled(f func())
    40  }
    41  
    42  // NestedTransactionPreparer provides active transaction states and facilitates
    43  // common state management operations.
    44  type NestedTransactionPreparer interface {
    45  	Meter
    46  
    47  	// NumNestedTransactions returns the number of uncommitted nested
    48  	// transactions.  Note that the main transaction is not considered a
    49  	// nested transaction.
    50  	NumNestedTransactions() int
    51  
    52  	// IsParseRestricted returns true if the current nested transaction is in
    53  	// parse resticted access mode.
    54  	IsParseRestricted() bool
    55  
    56  	MainTransactionId() NestedTransactionId
    57  
    58  	// IsCurrent returns true if the provide id refers to the current (nested)
    59  	// transaction.
    60  	IsCurrent(id NestedTransactionId) bool
    61  
    62  	// InterimReadSet returns the current read set aggregated from all
    63  	// outstanding nested transactions.
    64  	InterimReadSet() map[flow.RegisterID]struct{}
    65  
    66  	// FinalizeMainTransaction finalizes the main transaction and returns
    67  	// its execution snapshot.  The finalized main transaction will not accept
    68  	// any new commits after this point.  This returns an error if there are
    69  	// outstanding nested transactions.
    70  	FinalizeMainTransaction() (*snapshot.ExecutionSnapshot, error)
    71  
    72  	// BeginNestedTransaction creates a unrestricted nested transaction within
    73  	// the current unrestricted (nested) transaction.  The meter parameters are
    74  	// inherited from the current transaction.  This returns error if the
    75  	// current nested transaction is program restricted.
    76  	BeginNestedTransaction() (
    77  		NestedTransactionId,
    78  		error,
    79  	)
    80  
    81  	// BeginNestedTransactionWithMeterParams creates a unrestricted nested
    82  	// transaction within the current unrestricted (nested) transaction, using
    83  	// the provided meter parameters. This returns error if the current nested
    84  	// transaction is program restricted.
    85  	BeginNestedTransactionWithMeterParams(
    86  		params meter.MeterParameters,
    87  	) (
    88  		NestedTransactionId,
    89  		error,
    90  	)
    91  
    92  	// BeginParseRestrictedNestedTransaction creates a restricted nested
    93  	// transaction within the current (nested) transaction.  The meter
    94  	// parameters are inherited from the current transaction.
    95  	BeginParseRestrictedNestedTransaction(
    96  		location common.AddressLocation,
    97  	) (
    98  		NestedTransactionId,
    99  		error,
   100  	)
   101  
   102  	// CommitNestedTransaction commits the changes in the current unrestricted
   103  	// nested transaction to the parent (nested) transaction.  This returns
   104  	// error if the expectedId does not match the current nested transaction.
   105  	// This returns the committed execution snapshot otherwise.
   106  	//
   107  	// Note: The returned committed execution snapshot may be reused by another
   108  	// transaction via AttachAndCommitNestedTransaction to update the
   109  	// transaction bookkeeping, but the caller must manually invalidate the
   110  	// state.
   111  	// USE WITH EXTREME CAUTION.
   112  	CommitNestedTransaction(
   113  		expectedId NestedTransactionId,
   114  	) (
   115  		*snapshot.ExecutionSnapshot,
   116  		error,
   117  	)
   118  
   119  	// CommitParseRestrictedNestedTransaction commits the changes in the
   120  	// current restricted nested transaction to the parent (nested)
   121  	// transaction.  This returns error if the specified location does not
   122  	// match the tracked location. This returns the committed execution
   123  	// snapshot otherwise.
   124  	//
   125  	// Note: The returned committed execution snapshot may be reused by another
   126  	// transaction via AttachAndCommitNestedTransaction to update the
   127  	// transaction bookkeeping, but the caller must manually invalidate the
   128  	// state.
   129  	// USE WITH EXTREME CAUTION.
   130  	CommitParseRestrictedNestedTransaction(
   131  		location common.AddressLocation,
   132  	) (
   133  		*snapshot.ExecutionSnapshot,
   134  		error,
   135  	)
   136  
   137  	// AttachAndCommitNestedTransaction commits the changes from the cached
   138  	// nested transaction execution snapshot to the current (nested)
   139  	// transaction.
   140  	AttachAndCommitNestedTransaction(
   141  		cachedSnapshot *snapshot.ExecutionSnapshot,
   142  	) error
   143  
   144  	// RestartNestedTransaction merges all changes that belongs to the nested
   145  	// transaction about to be restart (for spock/meter bookkeeping), then
   146  	// wipes its view changes.
   147  	RestartNestedTransaction(
   148  		id NestedTransactionId,
   149  	) error
   150  
   151  	Get(id flow.RegisterID) (flow.RegisterValue, error)
   152  
   153  	Set(id flow.RegisterID, value flow.RegisterValue) error
   154  }
   155  
   156  type nestedTransactionStackFrame struct {
   157  	*ExecutionState
   158  
   159  	// When nil, the subtransaction will have unrestricted access to the runtime
   160  	// environment.  When non-nil, the subtransaction will only have access to
   161  	// the parts of the runtime environment necessary for importing/parsing the
   162  	// program, specifically, environment.ContractReader and
   163  	// environment.Programs.
   164  	parseRestriction *common.AddressLocation
   165  }
   166  
   167  type transactionState struct {
   168  	// NOTE: The first frame is always the main transaction, and is not
   169  	// poppable during the course of the transaction.
   170  	nestedTransactions []nestedTransactionStackFrame
   171  }
   172  
   173  // NewTransactionState constructs a new state transaction which manages nested
   174  // transactions.
   175  func NewTransactionState(
   176  	snapshot snapshot.StorageSnapshot,
   177  	params StateParameters,
   178  ) NestedTransactionPreparer {
   179  	startState := NewExecutionState(snapshot, params)
   180  	return &transactionState{
   181  		nestedTransactions: []nestedTransactionStackFrame{
   182  			nestedTransactionStackFrame{
   183  				ExecutionState:   startState,
   184  				parseRestriction: nil,
   185  			},
   186  		},
   187  	}
   188  }
   189  
   190  func (txnState *transactionState) current() nestedTransactionStackFrame {
   191  	return txnState.nestedTransactions[txnState.NumNestedTransactions()]
   192  }
   193  
   194  func (txnState *transactionState) NumNestedTransactions() int {
   195  	return len(txnState.nestedTransactions) - 1
   196  }
   197  
   198  func (txnState *transactionState) IsParseRestricted() bool {
   199  	return txnState.current().parseRestriction != nil
   200  }
   201  
   202  func (txnState *transactionState) MainTransactionId() NestedTransactionId {
   203  	return NestedTransactionId{
   204  		state: txnState.nestedTransactions[0].ExecutionState,
   205  	}
   206  }
   207  
   208  func (txnState *transactionState) IsCurrent(id NestedTransactionId) bool {
   209  	return txnState.current().ExecutionState == id.state
   210  }
   211  
   212  func (txnState *transactionState) InterimReadSet() map[flow.RegisterID]struct{} {
   213  	sizeEstimate := 0
   214  	for _, frame := range txnState.nestedTransactions {
   215  		sizeEstimate += frame.readSetSize()
   216  	}
   217  
   218  	result := make(map[flow.RegisterID]struct{}, sizeEstimate)
   219  
   220  	// Note: the interim read set must be accumulated in reverse order since
   221  	// the parent frame's write set will override the child frame's read set.
   222  	for i := len(txnState.nestedTransactions) - 1; i >= 0; i-- {
   223  		txnState.nestedTransactions[i].interimReadSet(result)
   224  	}
   225  
   226  	return result
   227  }
   228  
   229  func (txnState *transactionState) FinalizeMainTransaction() (
   230  	*snapshot.ExecutionSnapshot,
   231  	error,
   232  ) {
   233  	if len(txnState.nestedTransactions) > 1 {
   234  		return nil, fmt.Errorf(
   235  			"cannot finalize with outstanding nested transaction(s)")
   236  	}
   237  
   238  	return txnState.nestedTransactions[0].Finalize(), nil
   239  }
   240  
   241  func (txnState *transactionState) BeginNestedTransaction() (
   242  	NestedTransactionId,
   243  	error,
   244  ) {
   245  	if txnState.IsParseRestricted() {
   246  		return NestedTransactionId{}, fmt.Errorf(
   247  			"cannot begin a unrestricted nested transaction inside a " +
   248  				"program restricted nested transaction",
   249  		)
   250  	}
   251  
   252  	child := txnState.current().NewChild()
   253  	txnState.push(child, nil)
   254  
   255  	return NestedTransactionId{
   256  		state: child,
   257  	}, nil
   258  }
   259  
   260  func (txnState *transactionState) BeginNestedTransactionWithMeterParams(
   261  	params meter.MeterParameters,
   262  ) (
   263  	NestedTransactionId,
   264  	error,
   265  ) {
   266  	if txnState.IsParseRestricted() {
   267  		return NestedTransactionId{}, fmt.Errorf(
   268  			"cannot begin a unrestricted nested transaction inside a " +
   269  				"program restricted nested transaction",
   270  		)
   271  	}
   272  
   273  	child := txnState.current().NewChildWithMeterParams(params)
   274  	txnState.push(child, nil)
   275  
   276  	return NestedTransactionId{
   277  		state: child,
   278  	}, nil
   279  }
   280  
   281  func (txnState *transactionState) BeginParseRestrictedNestedTransaction(
   282  	location common.AddressLocation,
   283  ) (
   284  	NestedTransactionId,
   285  	error,
   286  ) {
   287  	child := txnState.current().NewChild()
   288  	txnState.push(child, &location)
   289  
   290  	return NestedTransactionId{
   291  		state: child,
   292  	}, nil
   293  }
   294  
   295  func (txnState *transactionState) push(
   296  	child *ExecutionState,
   297  	location *common.AddressLocation,
   298  ) {
   299  	txnState.nestedTransactions = append(
   300  		txnState.nestedTransactions,
   301  		nestedTransactionStackFrame{
   302  			ExecutionState:   child,
   303  			parseRestriction: location,
   304  		},
   305  	)
   306  }
   307  
   308  func (txnState *transactionState) pop(op string) (*ExecutionState, error) {
   309  	if len(txnState.nestedTransactions) < 2 {
   310  		return nil, fmt.Errorf("cannot %s the main transaction", op)
   311  	}
   312  
   313  	child := txnState.current()
   314  	txnState.nestedTransactions = txnState.nestedTransactions[:len(txnState.nestedTransactions)-1]
   315  
   316  	return child.ExecutionState, nil
   317  }
   318  
   319  func (txnState *transactionState) mergeIntoParent() (
   320  	*snapshot.ExecutionSnapshot,
   321  	error,
   322  ) {
   323  	childState, err := txnState.pop("commit")
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  
   328  	childSnapshot := childState.Finalize()
   329  
   330  	err = txnState.current().Merge(childSnapshot)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	return childSnapshot, nil
   336  }
   337  
   338  func (txnState *transactionState) CommitNestedTransaction(
   339  	expectedId NestedTransactionId,
   340  ) (
   341  	*snapshot.ExecutionSnapshot,
   342  	error,
   343  ) {
   344  	if !txnState.IsCurrent(expectedId) {
   345  		return nil, fmt.Errorf(
   346  			"cannot commit unexpected nested transaction: id mismatch",
   347  		)
   348  	}
   349  
   350  	if txnState.IsParseRestricted() {
   351  		// This is due to a programming error.
   352  		return nil, fmt.Errorf(
   353  			"cannot commit unexpected nested transaction: parse restricted",
   354  		)
   355  	}
   356  
   357  	return txnState.mergeIntoParent()
   358  }
   359  
   360  func (txnState *transactionState) CommitParseRestrictedNestedTransaction(
   361  	location common.AddressLocation,
   362  ) (
   363  	*snapshot.ExecutionSnapshot,
   364  	error,
   365  ) {
   366  	currentFrame := txnState.current()
   367  	if currentFrame.parseRestriction == nil ||
   368  		*currentFrame.parseRestriction != location {
   369  
   370  		// This is due to a programming error.
   371  		return nil, fmt.Errorf(
   372  			"cannot commit unexpected nested transaction %v != %v",
   373  			currentFrame.parseRestriction,
   374  			location,
   375  		)
   376  	}
   377  
   378  	return txnState.mergeIntoParent()
   379  }
   380  
   381  func (txnState *transactionState) AttachAndCommitNestedTransaction(
   382  	cachedSnapshot *snapshot.ExecutionSnapshot,
   383  ) error {
   384  	return txnState.current().Merge(cachedSnapshot)
   385  }
   386  
   387  func (txnState *transactionState) RestartNestedTransaction(
   388  	id NestedTransactionId,
   389  ) error {
   390  
   391  	// NOTE: We need to verify the id is valid before any merge operation or
   392  	// else we would accidently merge everything into the main transaction.
   393  	found := false
   394  	for _, frame := range txnState.nestedTransactions {
   395  		if frame.ExecutionState == id.state {
   396  			found = true
   397  			break
   398  		}
   399  	}
   400  
   401  	if !found {
   402  		return fmt.Errorf(
   403  			"cannot restart nested transaction: nested transaction not found")
   404  	}
   405  
   406  	for txnState.current().ExecutionState != id.state {
   407  		_, err := txnState.mergeIntoParent()
   408  		if err != nil {
   409  			return fmt.Errorf("cannot restart nested transaction: %w", err)
   410  		}
   411  	}
   412  
   413  	return txnState.current().DropChanges()
   414  }
   415  
   416  func (txnState *transactionState) Get(
   417  	id flow.RegisterID,
   418  ) (
   419  	flow.RegisterValue,
   420  	error,
   421  ) {
   422  	return txnState.current().Get(id)
   423  }
   424  
   425  func (txnState *transactionState) Set(
   426  	id flow.RegisterID,
   427  	value flow.RegisterValue,
   428  ) error {
   429  	return txnState.current().Set(id, value)
   430  }
   431  
   432  func (txnState *transactionState) MeterComputation(
   433  	kind common.ComputationKind,
   434  	intensity uint,
   435  ) error {
   436  	return txnState.current().MeterComputation(kind, intensity)
   437  }
   438  
   439  func (txnState *transactionState) ComputationAvailable(
   440  	kind common.ComputationKind,
   441  	intensity uint,
   442  ) bool {
   443  	return txnState.current().ComputationAvailable(kind, intensity)
   444  }
   445  
   446  func (txnState *transactionState) MeterMemory(
   447  	kind common.MemoryKind,
   448  	intensity uint,
   449  ) error {
   450  	return txnState.current().MeterMemory(kind, intensity)
   451  }
   452  
   453  func (txnState *transactionState) ComputationIntensities() meter.MeteredComputationIntensities {
   454  	return txnState.current().ComputationIntensities()
   455  }
   456  
   457  func (txnState *transactionState) TotalComputationLimit() uint {
   458  	return txnState.current().TotalComputationLimit()
   459  }
   460  
   461  func (txnState *transactionState) TotalComputationUsed() uint64 {
   462  	return txnState.current().TotalComputationUsed()
   463  }
   464  
   465  func (txnState *transactionState) MemoryIntensities() meter.MeteredMemoryIntensities {
   466  	return txnState.current().MemoryIntensities()
   467  }
   468  
   469  func (txnState *transactionState) TotalMemoryEstimate() uint64 {
   470  	return txnState.current().TotalMemoryEstimate()
   471  }
   472  
   473  func (txnState *transactionState) InteractionUsed() uint64 {
   474  	return txnState.current().InteractionUsed()
   475  }
   476  
   477  func (txnState *transactionState) MeterEmittedEvent(byteSize uint64) error {
   478  	return txnState.current().MeterEmittedEvent(byteSize)
   479  }
   480  
   481  func (txnState *transactionState) TotalEmittedEventBytes() uint64 {
   482  	return txnState.current().TotalEmittedEventBytes()
   483  }
   484  
   485  func (txnState *transactionState) RunWithAllLimitsDisabled(f func()) {
   486  	txnState.current().RunWithAllLimitsDisabled(f)
   487  }