Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, math.MaxUint64)
statedb.Witness().TouchFullAccount(w.Address[:], true)
}
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: 1 addition & 2 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ 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 @@ -359,7 +358,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, math.MaxUint64)
state.Witness().TouchFullAccount(w.Address[:], true)
}

if chain.Config().IsVerkle(header.Number, header.Time) {
Expand Down
251 changes: 170 additions & 81 deletions core/state/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package state

import (
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
Expand Down Expand Up @@ -89,83 +91,152 @@
return naw
}

func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, availableGas uint64) uint64 {
var gas uint64
func (aw *AccessWitness) FullAccountGas(addr []byte, isWrite bool) uint64 {
return aw.calculateWitnessGasRange(addr, zeroTreeIndex, isWrite, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
}

func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) {
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
consumed, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, availableGas)
if consumed < wanted {
return wanted + gas
}
availableGas -= consumed
gas += consumed
aw.touchLocation(addr, zeroTreeIndex, byte(i), isWrite)
}
return gas
}

func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, availableGas uint64) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
func (aw *AccessWitness) MessageCallGas(addr []byte) uint64 {
wanted := aw.calculateWitnessGasRange(addr, zeroTreeIndex, false, utils.BasicDataLeafKey, utils.BasicDataLeafKey)
if wanted == 0 {
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, availableGas uint64) uint64 {
_, wanted1 := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
if wanted1 > availableGas {
return wanted1
}
_, wanted2 := aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-wanted1)
func (aw *AccessWitness) TouchMessageCall(addr []byte) {
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
}

func (aw *AccessWitness) TouchValueTransfer(callerAddr, targetAddr []byte) {
aw.touchLocation(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
aw.touchLocation(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
}

func (aw *AccessWitness) ValueTransferGas(callerAddr, targetAddr []byte) uint64 {
wanted1 := aw.calculateWitnessGasRange(callerAddr, zeroTreeIndex, true, utils.BasicDataLeafKey, utils.BasicDataLeafKey)
wanted2 := aw.calculateWitnessGasRange(targetAddr, zeroTreeIndex, true, utils.BasicDataLeafKey, utils.BasicDataLeafKey)
if wanted1+wanted2 == 0 {
return params.WarmStorageReadCostEIP2929
}
return wanted1 + wanted2
}

// ContractCreateCheckGas 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) ContractCreateCheckGas(addr []byte) uint64 {
return aw.calculateWitnessGasRange(addr, zeroTreeIndex, false, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
}

// 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, 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
func (aw *AccessWitness) TouchContractCreateCheck(addr []byte) {
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
}

// ContractCreateInitGas charges access costs to initiate a contract creation.
func (aw *AccessWitness) ContractCreateInitGas(addr []byte) uint64 {
return aw.calculateWitnessGasRange(addr, zeroTreeIndex, true, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
}

// TouchAndChargeContractCreateInit charges access costs to initiate
// a contract creation.
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) TouchContractCreateInit(addr []byte, availableGas uint64) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

availableGas parameter is redundant

aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
}

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

func (aw *AccessWitness) TouchTxTarget(targetAddr []byte, sendsValue, doesntExist bool) {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, math.MaxUint64)
aw.touchLocation(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
// Note that we do a write-event in CodeHash without distinguishing if the tx target account
// exists or not. Pre-7702, there's no situation in which an existing codeHash can be mutated, thus
// doing a write-event shouldn't cause an observable difference in gas usage.
// TODO(7702): re-check this in the spec and implementation to be sure is a correct solution after
// EIP-7702 is implemented.
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, math.MaxUint64)
aw.touchLocation(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist)
}

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

func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
func (aw *AccessWitness) TouchSlot(addr []byte, slot common.Hash, isWrite bool) {
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
aw.touchLocation(addr, *treeIndex, subIndex, isWrite)
}

func (aw *AccessWitness) calculateWitnessGasRange(addr []byte, treeIndex uint256.Int, isWrite bool, from, to uint64) uint64 {
var (
gas uint64
branchKey = newBranchAccessKey(addr, treeIndex)
branchRead, branchWrite bool
)

// Read access.
if _, hasStem := aw.branches[branchKey]; !hasStem {
branchRead = true
}

// Write access.
if isWrite {
if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true
}
}

if branchRead {
gas += params.WitnessBranchReadCost
}
if branchWrite {
gas += params.WitnessBranchWriteCost
}

for subIndex := from; subIndex <= to; subIndex++ {
var chunkRead, chunkWrite, chunkFill bool
chunkKey := newChunkAccessKey(branchKey, byte(subIndex))
if _, hasSelector := aw.chunks[chunkKey]; !hasSelector {
chunkRead = true

}
if isWrite && (aw.chunks[chunkKey]&AccessWitnessWriteFlag) == 0 {
chunkWrite = true
}
if chunkRead {
gas += params.WitnessChunkReadCost
}
if chunkWrite {
gas += params.WitnessChunkWriteCost
}
if chunkFill {
gas += params.WitnessChunkFillCost
}
}

return gas
}

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

Expand All @@ -191,28 +262,6 @@
}
}

var gas uint64
if branchRead {
gas += params.WitnessBranchReadCost
}
if chunkRead {
gas += params.WitnessChunkReadCost
}
if branchWrite {
gas += params.WitnessBranchWriteCost
}
if chunkWrite {
gas += params.WitnessChunkWriteCost
}
if chunkFill {
gas += params.WitnessChunkFillCost
}

if availableGas < gas {
// consumed != wanted
return availableGas, gas
}

if branchRead {
aw.branches[branchKey] = AccessWitnessReadFlag
}
Expand All @@ -224,10 +273,11 @@
}
if chunkWrite {
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}

// consumed == wanted
return gas, gas
if chunkFill {

Check failure on line 277 in core/state/access_witness.go

View workflow job for this annotation

GitHub Actions / lint

SA9003: empty branch (staticcheck)
// TODO when FILL_COST is implemented
}
}
}

type branchAccessKey struct {
Expand All @@ -254,16 +304,16 @@
return lk
}

// 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, availableGas uint64) (uint64, uint64) {
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (aw *AccessWitness) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) (uint64, error) {
// 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 0, 0
return 0, nil
}

endPC := startPC + size
Expand All @@ -275,42 +325,81 @@
}

var statelessGasCharged uint64
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256)
consumed, wanted := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if wanted > consumed {
return statelessGasCharged + consumed, statelessGasCharged + wanted
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; {
startSubIndex := (chunkNumber + 128) % 256
var endSubIndex uint64
if chunkNumber < 128 {
// special case of finding the upper boundary for the header group
endSubIndex = min(endPC/31+128, 255)
} else {
endSubIndex = min(endPC/31-chunkNumber+(chunkNumber+128)%256, 255)
}

treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
wanted := aw.calculateWitnessGasRange(contractAddr, treeIndex, isWrite, startSubIndex, endSubIndex)
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, wanted)
if overflow {
panic("overflow when adding gas")
return 0, errors.New("gas uint overflow")
}

// Find the next group boundary, taking the 128 offset into account.
if chunkNumber < 128 {
chunkNumber = 128
} else {
chunkNumber += 256
}
availableGas -= consumed
}

return statelessGasCharged, statelessGasCharged
return statelessGasCharged, nil
}

// TouchCodeChunksRange is a helper function to touch every chunk in a code range and charge witness gas costs
func (aw *AccessWitness) TouchCodeChunksRange(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) {
// 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
}

endPC := startPC + size
if endPC > codeLen {
endPC = codeLen
}
if endPC > 0 {
endPC -= 1 // endPC is the last bytecode that will be touched.
}

for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256)
aw.touchLocation(contractAddr, treeIndex, subIndex, isWrite)
}
}

func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool) {
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
}

func (aw *AccessWitness) BasicDataGas(addr []byte, isWrite bool, warmCostCharging bool) uint64 {
wanted := aw.calculateWitnessGasRange(addr, zeroTreeIndex, isWrite, utils.BasicDataLeafKey, utils.BasicDataLeafKey)
if wanted == 0 && warmCostCharging {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) {
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
}

func (aw *AccessWitness) CodeHashGas(addr []byte, isWrite bool, chargeWarmCosts bool) uint64 {
wanted := aw.calculateWitnessGasRange(addr, zeroTreeIndex, isWrite, utils.CodeHashLeafKey, utils.CodeHashLeafKey)
if wanted == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
Expand Down
Loading
Loading