Refactor witness-accumulation in EVM (#42)

* make push dynamically-charged.  charge witness gas costs for push.  refactor evm witness gas charging to move logic for touching a range of bytecode into a helper method 'touchEachChunksAndChargeGas'

* add witness gas calculation for CodeCopy, ExtCodeCopy, SLoad back to gas_table.go

* witness gas charging for CALL

* remove explicit reference to evm.TxContext

* core/vm: make touchEachChunksAndCharge gas handle nil code value

* core/vm: call implementation, separate out witnesses into touch/set

* some fixes

* remove witness touching from opCall: this will go in evm.go

* remove witness touching for call from gas_table.go

* (hopefully) fix tests

* add SSTORE witness charging that was removed mistakenly

* charge witness gas for call

* clean up and comment touchEachChunksAndChargeGas

* make suggested changes

* address remaining points

* fix build issues

* remove double-charging for contract creation witness gas charging
This commit is contained in:
jwasinger
2021-12-16 00:21:59 -10:00
committed by GitHub
parent 6af78cba9e
commit 99ebf767b9
8 changed files with 188 additions and 206 deletions

View File

@ -661,14 +661,20 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number) r.Sub(r, header.Number)
r.Mul(r, blockReward) r.Mul(r, blockReward)
r.Div(r, big8) r.Div(r, big8)
if state.Witness() != nil {
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes()) uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes()) state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes())
}
state.AddBalance(uncle.Coinbase, r) state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32) r.Div(blockReward, big32)
reward.Add(reward, r) reward.Add(reward, r)
} }
coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes()) coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes())
if state.Witness() != nil {
state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes()) state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes())
}
state.AddBalance(header.Coinbase, reward) state.AddBalance(header.Coinbase, reward)
} }

View File

@ -128,7 +128,9 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
} }
if config.IsCancun(blockNumber) {
statedb.Witness().Merge(txContext.Accesses) statedb.Witness().Merge(txContext.Accesses)
}
// Set the receipt logs and create the bloom filter. // Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash) receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)

View File

@ -304,26 +304,26 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gas < gas { if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
} }
if st.evm.TxContext.Accesses != nil { if st.evm.Accesses != nil {
if msg.To() != nil { if msg.To() != nil {
toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes()) toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes())
pre := st.state.GetBalance(*msg.To()) pre := st.state.GetBalance(*msg.To())
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes()) gas += st.evm.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes())
// NOTE: Nonce also needs to be charged, because it is needed for execution // NOTE: Nonce also needs to be charged, because it is needed for execution
// on the statless side. // on the statless side.
var preTN [8]byte var preTN [8]byte
fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes()) fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes())
binary.BigEndian.PutUint64(preTN[:], st.state.GetNonce(*msg.To())) binary.BigEndian.PutUint64(preTN[:], st.state.GetNonce(*msg.To()))
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:]) gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:])
} }
fromBalance := trieUtils.GetTreeKeyBalance(msg.From().Bytes()) fromBalance := trieUtils.GetTreeKeyBalance(msg.From().Bytes())
preFB := st.state.GetBalance(msg.From()).Bytes() preFB := st.state.GetBalance(msg.From()).Bytes()
fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes()) fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes())
var preFN [8]byte var preFN [8]byte
binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From())) binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From()))
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:]) gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:])
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:]) gas += st.evm.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:])
} }
st.gas -= gas st.gas -= gas

View File

@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
return common.RightPadBytes(data[start:end], int(size)) return common.RightPadBytes(data[start:end], int(size))
} }
func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
length := uint64(len(data))
if start > length {
start = length
}
end := start + size
if end > length {
end = length
}
return common.RightPadBytes(data[start:end], int(size)), start, end
}
// toWordSize returns the ceiled word size required for memory expansion. // toWordSize returns the ceiled word size required for memory expansion.
func toWordSize(size uint64) uint64 { func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 { if size > math.MaxUint64-31 {

View File

@ -125,8 +125,6 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later // available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*. // applied in opCall*.
callGasTemp uint64 callGasTemp uint64
accesses map[common.Hash]common.Hash
} }
// NewEVM returns a new EVM. The returned EVM is not thread safe and should // NewEVM returns a new EVM. The returned EVM is not thread safe and should
@ -170,6 +168,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
return evm.interpreter return evm.interpreter
} }
// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
// otherwise, do the subtraction setting the result in gasPool and return true
func tryConsumeGas(gasPool *uint64, gas uint64) bool {
if *gasPool < gas {
*gasPool = 0
return false
}
*gasPool -= gas
return true
}
// Call executes the contract associated with the addr with the given input as // Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes // parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
@ -232,6 +243,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if len(code) == 0 { if len(code) == 0 {
ret, err = nil, nil // gas is unchanged ret, err = nil, nil // gas is unchanged
} else { } else {
if evm.Accesses != nil {
// Touch the account data // Touch the account data
var data [32]byte var data [32]byte
evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:]) evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:])
@ -241,6 +253,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
binary.BigEndian.PutUint64(data[:], uint64(len(code))) binary.BigEndian.PutUint64(data[:], uint64(len(code)))
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:]) evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:])
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes()) evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())
}
addrCopy := addr addrCopy := addr
// If the account has no code, we can abort here // If the account has no code, we can abort here

View File

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils" trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
) )
// memoryGasCost calculates the quadratic gas for memory expansion. It does so // memoryGasCost calculates the quadratic gas for memory expansion. It does so
@ -88,17 +87,6 @@ func memoryCopierGas(stackpos int) gasFunc {
} }
} }
func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0)
slot := stack.Back(0)
if evm.accesses != nil {
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
return usedGas, nil
}
var ( var (
gasCallDataCopy = memoryCopierGas(2) gasCallDataCopy = memoryCopierGas(2)
gasCodeCopyStateful = memoryCopierGas(2) gasCodeCopyStateful = memoryCopierGas(2)
@ -106,9 +94,20 @@ var (
gasReturnDataCopy = memoryCopierGas(2) gasReturnDataCopy = memoryCopierGas(2)
) )
func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0)
slot := stack.Back(0)
if evm.Accesses != nil {
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
return usedGas, nil
}
func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64 var statelessGas uint64
if evm.accesses != nil { if evm.Accesses != nil {
var ( var (
codeOffset = stack.Back(1) codeOffset = stack.Back(1)
length = stack.Back(2) length = stack.Back(2)
@ -117,21 +116,12 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if overflow { if overflow {
uint64CodeOffset = 0xffffffffffffffff uint64CodeOffset = 0xffffffffffffffff
} }
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() uint64Length, overflow := length.Uint64WithOverflow()
if overflow { if overflow {
uint64CodeEnd = 0xffffffffffffffff uint64Length = 0xffffffffffffffff
} }
addr := contract.Address() _, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
chunk := uint64CodeOffset / 31 statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, evm.Accesses)
endChunk := uint64CodeEnd / 31
// XXX uint64 overflow in condition check
for ; chunk < endChunk; chunk++ {
// TODO make a version of GetTreeKeyCodeChunk without the bigint
index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))
statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
} }
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize) usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err return usedGas + statelessGas, err
@ -139,9 +129,8 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64 var statelessGas uint64
if evm.accesses != nil { if evm.Accesses != nil {
var ( var (
a = stack.Back(0)
codeOffset = stack.Back(2) codeOffset = stack.Back(2)
length = stack.Back(3) length = stack.Back(3)
) )
@ -149,20 +138,17 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
if overflow { if overflow {
uint64CodeOffset = 0xffffffffffffffff uint64CodeOffset = 0xffffffffffffffff
} }
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() uint64Length, overflow := length.Uint64WithOverflow()
if overflow { if overflow {
uint64CodeEnd = 0xffffffffffffffff uint64Length = 0xffffffffffffffff
} }
addr := common.Address(a.Bytes20()) // note: we must charge witness costs for the specified range regardless of whether it
chunk := uint64CodeOffset / 31 // is in-bounds of the actual target account code. This is because we must charge the cost
endChunk := uint64CodeEnd / 31 // before hitting the db to be able to now what the actual code size is. This is different
// XXX uint64 overflow in condition check // behavior from CODECOPY which only charges witness access costs for the part of the range
for ; chunk < endChunk; chunk++ { // which overlaps in the account code. TODO: clarify this is desired behavior and amend the
// TODO(@gballet) make a version of GetTreeKeyCodeChunk without the bigint // spec.
index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)) statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, nil, nil, evm.Accesses)
statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
} }
usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err return usedGas + statelessGas, err
@ -171,11 +157,11 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0) usedGas := uint64(0)
if evm.accesses != nil { if evm.Accesses != nil {
where := stack.Back(0) where := stack.Back(0)
addr := contract.Address() addr := contract.Address()
index := trieUtils.GetTreeKeyStorageSlot(addr[:], where) index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) usedGas += evm.Accesses.TouchAddressAndChargeGas(index, nil)
} }
return usedGas, nil return usedGas, nil
@ -207,7 +193,6 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return params.SstoreResetGas + accessGas, nil return params.SstoreResetGas + accessGas, nil
} }
} }
// The new gas metering is based on net gas costs (EIP-1283): // The new gas metering is based on net gas costs (EIP-1283):
// //
// 1. If current value equals new value (this is a no-op), 200 gas is deducted. // 1. If current value equals new value (this is a no-op), 200 gas is deducted.
@ -422,7 +407,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero() transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20()) address = common.Address(stack.Back(1).Bytes20())
) )
if evm.accesses != nil { if evm.Accesses != nil {
// Charge witness costs // Charge witness costs
for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ { for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ {
index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i)) index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i))
@ -456,6 +441,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }

View File

@ -19,6 +19,7 @@ package vm
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils" trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -343,9 +344,10 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek() slot := scope.Stack.peek()
cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())) cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))
if interpreter.evm.accesses != nil { if interpreter.evm.Accesses != nil {
index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
interpreter.evm.TxContext.Accesses.TouchAddress(index, uint256.NewInt(cs).Bytes()) statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, uint256.NewInt(cs).Bytes())
scope.Contract.UseGas(statelessGas)
} }
slot.SetUint64(cs) slot.SetUint64(cs)
return nil, nil return nil, nil
@ -368,63 +370,92 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if overflow { if overflow {
uint64CodeOffset = 0xffffffffffffffff uint64CodeOffset = 0xffffffffffffffff
} }
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
if interpreter.evm.accesses != nil {
copyCodeFromAccesses(scope.Contract.Address(), uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope)
} else {
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
if interpreter.evm.Accesses != nil {
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses)
} }
scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
return nil, nil return nil, nil
} }
// Helper function to touch every chunk in a code range // touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func touchEachChunks(start, end uint64, code []byte, contract *Contract, evm *EVM) { func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *Contract, accesses *types.AccessWitness) uint64 {
for chunk := start / 31; chunk <= end/31 && chunk <= uint64(len(code))/31; chunk++ { // note that in the case where the copied code is outside the range of the
index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(chunk)) // contract code but touches the last leaf with contract code in it,
count := uint64(0) // we don't include the last leaf of code in the AccessWitness. The
end := (chunk + 1) * 31 // 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 contract != nil && (size == 0 || offset > uint64(len(contract.Code))) {
return 0
}
var (
statelessGasCharged uint64
startLeafOffset uint64
endLeafOffset uint64
startOffset uint64
endOffset uint64
numLeaves uint64
code []byte
index [32]byte
)
if contract != nil {
code = contract.Code[:]
}
// startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched
// and the evm code offset of the last byte in the last leaf touched
startOffset = offset - (offset % 31)
if contract != nil && startOffset+size > uint64(len(contract.Code)) {
endOffset = uint64(len(contract.Code))
} else {
endOffset = startOffset + size
}
endLeafOffset = endOffset + (endOffset % 31)
numLeaves = (endLeafOffset - startLeafOffset) / 31
chunkOffset := new(uint256.Int)
treeIndex := new(uint256.Int)
for i := 0; i < int(numLeaves); i++ {
chunkOffset.Add(trieUtils.CodeOffset, uint256.NewInt(uint64(i)))
treeIndex.Div(chunkOffset, trieUtils.VerkleNodeWidth)
var subIndex byte
subIndexMod := chunkOffset.Mod(chunkOffset, trieUtils.VerkleNodeWidth).Bytes()
if len(subIndexMod) == 0 {
subIndex = 0
} else {
subIndex = subIndexMod[0]
}
treeKey := trieUtils.GetTreeKey(address, treeIndex, subIndex)
copy(index[0:31], treeKey)
index[31] = subIndex
var value []byte
if contract != nil {
// the offset into the leaf that the first PUSH occurs
var firstPushOffset uint64 = 0
// Look for the first code byte (i.e. no pushdata) // Look for the first code byte (i.e. no pushdata)
for ; count < 31 && end+count < uint64(len(contract.Code)) && !contract.IsCode(chunk*31+count); count++ { for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ {
} }
var value [32]byte curEnd := (uint64(i) + 1) * 31
value[0] = byte(count) if curEnd > endOffset {
if end > uint64(len(code)) { curEnd = endOffset
end = uint64(len(code))
} }
copy(value[1:], code[chunk*31:end]) valueSize := curEnd - (uint64(i) * 31)
evm.Accesses.TouchAddress(index, value[:]) value = make([]byte, 32, 32)
value[0] = byte(firstPushOffset)
copy(value[1:valueSize+1], code[i*31:curEnd])
if valueSize < 31 {
padding := make([]byte, 31-valueSize, 31-valueSize)
copy(value[valueSize+1:], padding)
} }
} }
// copyCodeFromAccesses perform codecopy from the witness, not from the db. statelessGasCharged += accesses.TouchAddressAndChargeGas(index[:], value)
func copyCodeFromAccesses(addr common.Address, codeOffset, codeEnd, memOffset uint64, in *EVMInterpreter, scope *ScopeContext) {
chunk := codeOffset / 31
endChunk := codeEnd / 31
start := codeOffset % 31 // start inside the first code chunk
offset := uint64(0) // memory offset to write to
// XXX uint64 overflow in condition check
for end := uint64(31); chunk < endChunk; chunk, start = chunk+1, 0 {
// case of the last chunk: figure out how many bytes need to
// be extracted from the last chunk.
if chunk+1 == endChunk {
end = codeEnd % 31
} }
// TODO make a version of GetTreeKeyCodeChunk without the bigint return statelessGasCharged
index := common.BytesToHash(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)))
h := in.evm.accesses[index]
//in.evm.Accesses.TouchAddress(index.Bytes(), h[1+start:1+end])
scope.Memory.Set(memOffset+offset, end-start, h[1+start:end])
offset += 31 - start
}
} }
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
@ -439,18 +470,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if overflow { if overflow {
uint64CodeOffset = 0xffffffffffffffff uint64CodeOffset = 0xffffffffffffffff
} }
uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
if interpreter.evm.accesses != nil { if interpreter.evm.Accesses != nil {
copyCodeFromAccesses(addr, uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope) log.Warn("setting witness values for extcodecopy is not currently implemented")
} else { } else {
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm)
} }
return nil, nil return nil, nil
@ -579,10 +604,11 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
hash := common.Hash(loc.Bytes32()) hash := common.Hash(loc.Bytes32())
val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash)
loc.SetBytes(val.Bytes()) loc.SetBytes(val.Bytes())
// Get the initial value as it might not be present
if interpreter.evm.Accesses != nil {
index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc)
interpreter.evm.TxContext.Accesses.TouchAddress(index, val.Bytes()) interpreter.evm.Accesses.TouchAddressAndChargeGas(index, val.Bytes())
}
return nil, nil return nil, nil
} }
@ -907,9 +933,11 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1 *pc += 1
if *pc < codeLen { if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
if interpreter.evm.Accesses != nil && *pc%31 == 0 {
// touch next chunk if PUSH1 is at the boundary. if so, *pc has // touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary. // advanced past this boundary.
if *pc%31 == 0 {
// touch push data by adding the last byte of the pushdata // touch push data by adding the last byte of the pushdata
var value [32]byte var value [32]byte
chunk := *pc / 31 chunk := *pc / 31
@ -924,7 +952,8 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
} }
copy(value[1:], scope.Contract.Code[chunk*31:endMin]) copy(value[1:], scope.Contract.Code[chunk*31:endMin])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, nil)
scope.Contract.UseGas(statelessGas)
} }
} else { } else {
scope.Stack.push(integer.Clear()) scope.Stack.push(integer.Clear())
@ -947,43 +976,15 @@ func makePush(size uint64, pushByteSize int) executionFunc {
endMin = startMin + pushByteSize endMin = startMin + pushByteSize
} }
if interpreter.evm.Accesses != nil {
statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses)
scope.Contract.UseGas(statelessGas)
}
integer := new(uint256.Int) integer := new(uint256.Int)
scope.Stack.push(integer.SetBytes(common.RightPadBytes( scope.Stack.push(integer.SetBytes(common.RightPadBytes(
scope.Contract.Code[startMin:endMin], pushByteSize))) scope.Contract.Code[startMin:endMin], pushByteSize)))
// touch push data by adding the last byte of the pushdata
var value [32]byte
chunk := uint64(endMin-1) / 31
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
copy(value[1:], scope.Contract.Code[chunk*31:endMin])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
// in the case of PUSH32, the end data might be two chunks away,
// so also get the middle chunk. There is a boundary condition
// check (endMin > 2) in the case the code is a single PUSH32
// insctruction, whose immediate are just 0s.
if pushByteSize == 32 && endMin > 2 {
chunk = uint64(endMin-2) / 31
count = uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
end := (chunk + 1) * 31
if end > uint64(len(scope.Contract.Code)) {
end = uint64(len(scope.Contract.Code))
}
copy(value[1:], scope.Contract.Code[chunk*31:end])
index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
*pc += size *pc += size
return nil, nil return nil, nil
} }

View File

@ -17,15 +17,12 @@
package vm package vm
import ( import (
"errors"
"hash" "hash"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
) )
// Config are the configuration options for the Interpreter // Config are the configuration options for the Interpreter
@ -194,51 +191,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
logged, pcCopy, gasCopy = false, pc, contract.Gas logged, pcCopy, gasCopy = false, pc, contract.Gas
} }
if in.evm.TxContext.Accesses != nil {
// if the PC ends up in a new "page" of verkleized code, charge the // if the PC ends up in a new "page" of verkleized code, charge the
// associated witness costs. // associated witness costs.
inWitness := false contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract, in.evm.TxContext.Accesses)
var codePage common.Hash
if in.evm.chainRules.IsCancun {
index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(pc/31))
var value [32]byte
if in.evm.accesses != nil {
codePage, inWitness = in.evm.accesses[common.BytesToHash(index)]
// Return an error if we're in stateless mode
// and the code isn't in the witness. It means
// that if code is read beyond the actual code
// size, pages of 0s need to be added to the
// witness.
if !inWitness {
return nil, errors.New("code chunk missing from proof")
}
copy(value[:], codePage[:])
} else {
// Calculate the chunk
chunk := pc / 31
end := (chunk + 1) * 31
if end >= uint64(len(contract.Code)) {
end = uint64(len(contract.Code))
}
count := uint64(0)
// Look for the first code byte (i.e. no pushdata)
for ; chunk*31+count < end && count < 31 && !contract.IsCode(chunk*31+count); count++ {
}
value[0] = byte(count)
copy(value[1:], contract.Code[chunk*31:end])
}
contract.Gas -= in.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, value[:])
} }
if inWitness { // TODO how can we tell if we are in stateless mode here and need to get the op from the witness
// Get the op from the tree, skipping the header byte
op = OpCode(codePage[1+pc%31])
} else {
// If we are in witness mode, then raise an error // If we are in witness mode, then raise an error
op = contract.GetOp(pc) op = contract.GetOp(pc)
}
// Get the operation from the jump table and validate the stack to ensure there are // Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation. // enough stack items available to perform the operation.
operation := in.cfg.JumpTable[op] operation := in.cfg.JumpTable[op]