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 }