Skip to content

Commit

Permalink
pass upper bound for gas consumption
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Ballet <[email protected]>
  • Loading branch information
gballet committed Sep 24, 2024
1 parent 9ea37be commit caa00f3
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 244 deletions.
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
// Amount is in gwei, turn into wei
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, amount)
statedb.Witness().TouchFullAccount(w.Address[:], true, nil)
statedb.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
}
if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) {
if err := overlay.OverlayVerkleTransition(statedb, common.Hash{}, chainConfig.OverlayStride); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
Expand Down Expand Up @@ -358,7 +359,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
state.AddBalance(w.Address, amount)

// The returned gas is not charged
state.Witness().TouchFullAccount(w.Address[:], true, nil)
state.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
}

if chain.Config().IsVerkle(header.Number, header.Time) {
Expand Down
124 changes: 74 additions & 50 deletions core/state/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package state

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -91,69 +92,72 @@ func (aw *AccessWitness) Copy() *AccessWitness {
return naw
}

func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, useGasFn UseGasFn) bool {
func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, availableGas uint64) uint64 {
var gas uint64
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, useGasFn); !ok {
return false
consumed, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, availableGas)
if consumed < wanted {
return wanted
}
availableGas -= consumed
gas += consumed
}
return true
return gas
}

func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, useGasFn UseGasFn) bool {
chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn)
return ok && (chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929))
func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, availableGas uint64) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
if wanted == 0 {
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, useGasFn UseGasFn) bool {
_, ok := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn)
if !ok {
return false
}
_, ok = aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn)
return ok
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, availableGas uint64) uint64 {
chargedGas, _ := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
chargedGas, _ = aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-chargedGas)
return chargedGas
}

// TouchAndChargeContractCreateCheck charges access costs before
// a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write
// are guaranteed to happen at this point.
func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, useGasFn UseGasFn) bool {
if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, useGasFn); !ok {
return false
}
_, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, useGasFn)
return ok
func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, availableGas uint64) uint64 {
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
_, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-gas1)
return wanted1 + wanted2
}

// TouchAndChargeContractCreateInit charges access costs to initiate
// a contract creation.
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, useGasFn UseGasFn) bool {
if _, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, useGasFn); !ok {
return false
}
_, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, useGasFn)
return ok
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, availableGas uint64) (uint64, uint64) {
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas2, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-gas1)
return gas1 + gas2, wanted1 + wanted2
}

func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) {
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, nil)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, math.MaxUint64)
}
}

func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, nil)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, nil)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, math.MaxUint64)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, math.MaxUint64)
}

func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool {
func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
chargedGas, ok := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, useGasFn)
return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929))
_, wanted := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if wanted == 0 && warmCostCharging {
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, useGasFn UseGasFn) (uint64, bool) {
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex)

Expand Down Expand Up @@ -196,10 +200,9 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256
gas += params.WitnessChunkFillCost
}

if useGasFn != nil {
if ok := useGasFn(gas); !ok {
return 0, false
}
if availableGas < gas {
// consumed != wanted
return availableGas, gas
}

if branchRead {
Expand All @@ -212,11 +215,11 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256
aw.chunks[chunkKey] = AccessWitnessReadFlag
}
if chunkWrite {
chunkWrite = true
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}

return gas, true
// consumed == wanted
return gas, gas
}

type branchAccessKey struct {
Expand Down Expand Up @@ -244,15 +247,15 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
}

// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, useGasFn UseGasFn) bool {
func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
// note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The
// reason that we do not need the last leaf is the account's code size
// is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed.
if size == 0 || startPC >= codeLen {
return true
return 0, 0
}

endPC := startPC + size
Expand All @@ -263,23 +266,44 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s
endPC -= 1 // endPC is the last bytecode that will be touched.
}

var statelessGasCharged uint64
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256)
if _, ok := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, useGasFn); !ok {
return false
consumed, wanted := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if wanted > consumed {
return statelessGasCharged + consumed, statelessGasCharged + wanted
}
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
if overflow {
panic("overflow when adding gas")
}
availableGas -= consumed
}

return true
return statelessGasCharged, statelessGasCharged
}

func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, useGasFn UseGasFn, warmCostCharging bool) bool {
chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, useGasFn)
return ok && (!warmCostCharging || chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929))
func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
chargedGas, _ := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
if chargedGas == 0 && availableGas > 0 && warmCostCharging {
if availableGas > params.WarmStorageReadCostEIP2929 {
return availableGas
}
chargedGas = params.WarmStorageReadCostEIP2929
}
return chargedGas
}

func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, useGasFn UseGasFn) bool {
chargedGas, ok := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, useGasFn)
return ok && (chargedGas > 0 || useGasFn(params.WarmStorageReadCostEIP2929))
func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, availableGas uint64) uint64 {
chargedGas, _ := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
if chargedGas == 0 && availableGas > 0 {
if availableGas > params.WarmStorageReadCostEIP2929 {
return availableGas
}
chargedGas = params.WarmStorageReadCostEIP2929
}
return chargedGas
}
5 changes: 3 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
Expand Down Expand Up @@ -177,7 +178,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo

func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) {
// Make sure that the historical contract is added to the witness
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, nil)
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, math.MaxUint64)

ancestor := chain.GetHeader(prevHash, prevNumber)
for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- {
Expand All @@ -191,5 +192,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash
var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex)
statedb.SetState(params.HistoryStorageAddress, key, prevHash)
statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, nil, false)
statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, math.MaxUint64, false)
}
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {

// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, nil)
st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, math.MaxUint64)
}
}

Expand Down
12 changes: 0 additions & 12 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,3 @@ func allZero(b []byte) bool {
}
return true
}

type gasConsumer struct {
availableGas uint64
}

func (gc *gasConsumer) consumeGas(gas uint64) bool {
if gc.availableGas < gas {
return false
}
gc.availableGas -= gas
return true
}
22 changes: 11 additions & 11 deletions core/vm/eips.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,26 +307,26 @@ func enable6780(jt *JumpTable) {

func enable4762(jt *JumpTable) {
jt[SSTORE].constantGas = 0
jt[SSTORE].dynamicGas = nil
jt[SSTORE].dynamicGas = gasSStore4762
jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = nil
jt[BALANCE].dynamicGas = nil
jt[SLOAD].dynamicGas = gasSLoad4762
jt[BALANCE].dynamicGas = gasBalance4762
jt[BALANCE].constantGas = 0
jt[EXTCODESIZE].constantGas = 0
jt[EXTCODESIZE].dynamicGas = nil
jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762
jt[EXTCODEHASH].constantGas = 0
jt[EXTCODEHASH].dynamicGas = nil
jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762
jt[EXTCODECOPY].constantGas = 0
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy
jt[SELFDESTRUCT].dynamicGas = nil
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762
jt[CREATE].constantGas = params.CreateNGasEip4762
jt[CREATE2].constantGas = params.CreateNGasEip4762
jt[CALL].constantGas = 0
jt[CALL].dynamicGas = gasCall
jt[CALL].dynamicGas = gasCallEIP4762
jt[CALLCODE].constantGas = 0
jt[CALLCODE].dynamicGas = gasCallCode
jt[CALLCODE].dynamicGas = gasCallCodeEIP4762
jt[STATICCALL].constantGas = 0
jt[STATICCALL].dynamicGas = gasStaticCall
jt[STATICCALL].dynamicGas = gasStaticCallEIP4762
jt[DELEGATECALL].constantGas = 0
jt[DELEGATECALL].dynamicGas = gasDelegateCall
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762
}
23 changes: 12 additions & 11 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,12 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 {
// add proof of absence to witness
gc := gasConsumer{availableGas: gas}
ok := evm.Accesses.TouchFullAccount(addr.Bytes(), false, gc.consumeGas)
if !ok {
wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false, gas)
if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas
}
gas = gc.availableGas
gas -= wgas
}

if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
Expand Down Expand Up @@ -458,11 +457,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,

// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
gc := gasConsumer{availableGas: gas}
if !evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes(), gc.consumeGas) {
statelessGas := evm.Accesses.TouchAndChargeContractCreateCheck(address.Bytes(), gas)
if statelessGas > gas {
return nil, common.Address{}, 0, ErrOutOfGas
}
gas = gc.availableGas
gas -= statelessGas
}
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
// the access-list change should not be rolled back
Expand All @@ -483,11 +482,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,

// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
gc := gasConsumer{availableGas: gas}
if !evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), gc.consumeGas) {
consumed, wanted := evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), gas)
if consumed < wanted {
return nil, common.Address{}, 0, ErrOutOfGas
}
gas = gc.availableGas
gas -= consumed
}
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)

Expand Down Expand Up @@ -528,7 +527,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
err = ErrCodeStoreOutOfGas
}
} else {
if err == nil && len(ret) > 0 && !evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true, contract.UseGas) {
consumed, wanted := evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas)
contract.UseGas(consumed) // consumed <= contract.Gas, so no return value check is needed
if err == nil && len(ret) > 0 && (consumed < wanted) {
err = ErrCodeStoreOutOfGas
}
}
Expand Down
Loading

0 comments on commit caa00f3

Please sign in to comment.