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

     1  package fvm
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"github.com/onflow/cadence"
     8  	"github.com/onflow/cadence/runtime/common"
     9  	"golang.org/x/exp/slices"
    10  
    11  	"github.com/onflow/flow-go/fvm/environment"
    12  	"github.com/onflow/flow-go/fvm/errors"
    13  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    14  	"github.com/onflow/flow-go/fvm/systemcontracts"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module/trace"
    17  )
    18  
    19  func addressFromRegisterId(id flow.RegisterID) (flow.Address, bool) {
    20  	if len(id.Owner) != flow.AddressLength {
    21  		return flow.EmptyAddress, false
    22  	}
    23  
    24  	return flow.BytesToAddress([]byte(id.Owner)), true
    25  }
    26  
    27  type TransactionStorageLimiter struct{}
    28  
    29  // CheckStorageLimits checks each account that had its storage written to during a transaction, that its storage used
    30  // is less than its storage capacity.
    31  // Storage used is an FVM register and is easily accessible.
    32  // Storage capacity is calculated by the FlowStorageFees contract from the account's flow balance.
    33  //
    34  // The payers balance is considered to be maxTxFees lower that its actual balance, due to the fact that
    35  // the fee deduction step happens after the storage limit check.
    36  func (limiter TransactionStorageLimiter) CheckStorageLimits(
    37  	ctx Context,
    38  	env environment.Environment,
    39  	snapshot *snapshot.ExecutionSnapshot,
    40  	payer flow.Address,
    41  	maxTxFees uint64,
    42  ) error {
    43  	if !env.LimitAccountStorage() {
    44  		return nil
    45  	}
    46  
    47  	defer env.StartChildSpan(trace.FVMTransactionStorageUsedCheck).End()
    48  
    49  	err := limiter.checkStorageLimits(ctx, env, snapshot, payer, maxTxFees)
    50  	if err != nil {
    51  		return fmt.Errorf("storage limit check failed: %w", err)
    52  	}
    53  	return nil
    54  }
    55  
    56  // getStorageCheckAddresses returns a list of addresses to be checked whether
    57  // storage limit is exceeded.  The returned list include addresses of updated
    58  // registers (and the payer's address).
    59  func (limiter TransactionStorageLimiter) getStorageCheckAddresses(
    60  	ctx Context,
    61  	snapshot *snapshot.ExecutionSnapshot,
    62  	payer flow.Address,
    63  	maxTxFees uint64,
    64  ) []flow.Address {
    65  	// Multiple updated registers might be from the same address.  We want to
    66  	// duplicated the addresses to reduce check overhead.
    67  	dedup := make(map[flow.Address]struct{}, len(snapshot.WriteSet)+1)
    68  	addresses := make([]flow.Address, 0, len(snapshot.WriteSet)+1)
    69  
    70  	// In case the payer is not updated, include it here.  If the maxTxFees is
    71  	// zero, it doesn't matter if the payer is included or not.
    72  	if maxTxFees > 0 {
    73  		dedup[payer] = struct{}{}
    74  		addresses = append(addresses, payer)
    75  	}
    76  
    77  	sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID())
    78  	for id := range snapshot.WriteSet {
    79  		address, ok := addressFromRegisterId(id)
    80  		if !ok {
    81  			continue
    82  		}
    83  
    84  		if limiter.shouldSkipSpecialAddress(ctx, address, sc) {
    85  			continue
    86  		}
    87  
    88  		_, ok = dedup[address]
    89  		if ok {
    90  			continue
    91  		}
    92  
    93  		dedup[address] = struct{}{}
    94  		addresses = append(addresses, address)
    95  	}
    96  
    97  	slices.SortFunc(
    98  		addresses,
    99  		func(a flow.Address, b flow.Address) int {
   100  			// reverse order to maintain compatibility with previous
   101  			// implementation.
   102  			return bytes.Compare(b[:], a[:])
   103  		})
   104  	return addresses
   105  }
   106  
   107  // checkStorageLimits checks if the transaction changed the storage of any
   108  // address and exceeded the storage limit.
   109  func (limiter TransactionStorageLimiter) checkStorageLimits(
   110  	ctx Context,
   111  	env environment.Environment,
   112  	snapshot *snapshot.ExecutionSnapshot,
   113  	payer flow.Address,
   114  	maxTxFees uint64,
   115  ) error {
   116  	addresses := limiter.getStorageCheckAddresses(ctx, snapshot, payer, maxTxFees)
   117  
   118  	usages := make([]uint64, len(addresses))
   119  
   120  	for i, address := range addresses {
   121  		ca := common.Address(address)
   122  		u, err := env.GetStorageUsed(ca)
   123  		if err != nil {
   124  			return err
   125  		}
   126  
   127  		usages[i] = u
   128  	}
   129  
   130  	result, invokeErr := env.AccountsStorageCapacity(
   131  		addresses,
   132  		payer,
   133  		maxTxFees,
   134  	)
   135  
   136  	// This error only occurs in case of implementation errors.
   137  	// InvokeAccountsStorageCapacity already handles cases where the default
   138  	// vault is missing.
   139  	if invokeErr != nil {
   140  		return invokeErr
   141  	}
   142  
   143  	// The resultArray elements are in the same order as the addresses and the
   144  	// addresses are deterministically sorted.
   145  	resultArray, ok := result.(cadence.Array)
   146  	if !ok {
   147  		return fmt.Errorf("AccountsStorageCapacity did not return an array")
   148  	}
   149  
   150  	if len(addresses) != len(resultArray.Values) {
   151  		return fmt.Errorf("number of addresses does not match number of result")
   152  	}
   153  
   154  	for i, address := range addresses {
   155  		capacity := environment.StorageMBUFixToBytesUInt(resultArray.Values[i])
   156  
   157  		if usages[i] > capacity {
   158  			return errors.NewStorageCapacityExceededError(
   159  				address,
   160  				usages[i],
   161  				capacity)
   162  		}
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  // shouldSkipSpecialAddress returns true if the address is a special address where storage
   169  // limits are not enforced.
   170  // This is currently only the EVM storage address. This is a temporary solution.
   171  func (limiter TransactionStorageLimiter) shouldSkipSpecialAddress(
   172  	ctx Context,
   173  	address flow.Address,
   174  	sc *systemcontracts.SystemContracts,
   175  ) bool {
   176  	if !ctx.EVMEnabled {
   177  		return false
   178  	}
   179  
   180  	return sc.EVMStorage.Address == address
   181  }