Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor witness-accumulation in EVM #42

Merged
Merged
Show file tree
Hide file tree
Changes from 13 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
12 changes: 9 additions & 3 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,14 +661,20 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes())

if state.Witness() != nil {
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes())
}
state.AddBalance(uncle.Coinbase, r)

r.Div(blockReward, big32)
reward.Add(reward, r)
}
coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes())
state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes())

if state.Witness() != nil {
state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes())
}
state.AddBalance(header.Coinbase, reward)
}
4 changes: 3 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}

statedb.Witness().Merge(txContext.Accesses)
if config.IsCancun(blockNumber) {
statedb.Witness().Merge(txContext.Accesses)
}

// Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)
Expand Down
10 changes: 5 additions & 5 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,26 +304,26 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if 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 {
toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes())
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
// on the statless side.
var preTN [8]byte
fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes())
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())
preFB := st.state.GetBalance(msg.From()).Bytes()
fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes())
var preFN [8]byte
binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From()))
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:])
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:])
gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:])
gas += st.evm.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:])
}
st.gas -= gas

Expand Down
12 changes: 12 additions & 0 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
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.
func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {
Expand Down
45 changes: 34 additions & 11 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,6 @@ type EVM struct {
// available gas is calculated in gasCall* according to the 63/64 rule and later
// applied in opCall*.
callGasTemp uint64

accesses map[common.Hash]common.Hash
}

// NewEVM returns a new EVM. The returned EVM is not thread safe and should
Expand Down Expand Up @@ -170,6 +168,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
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
// 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
Expand Down Expand Up @@ -232,15 +243,27 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
// Touch the account data
var data [32]byte
evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:])
binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr))
evm.Accesses.TouchAddress(utils.GetTreeKeyNonce(addr[:]), data[:])
evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())
binary.BigEndian.PutUint64(data[:], uint64(len(code)))
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:])
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())
if evm.Accesses != nil {
// Touch the account data
var data [32]byte
if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr.Bytes()), data[:])) {
return nil, 0, ErrOutOfGas
}
binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr))
if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), data[:])) {
return nil, 0, ErrOutOfGas
}
if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())) {
return nil, 0, ErrOutOfGas
}
binary.BigEndian.PutUint64(data[:], uint64(len(code)))
if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), data[:])) {
return nil, 0, ErrOutOfGas
}
if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())) {
return nil, 0, ErrOutOfGas
}
}

addrCopy := addr
// If the account has no code, we can abort here
Expand Down
71 changes: 10 additions & 61 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)

// memoryGasCost calculates the quadratic gas for memory expansion. It does so
Expand Down Expand Up @@ -88,17 +87,6 @@ func memoryCopierGas(stackpos int) gasFunc {
}
}

func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
jwasinger marked this conversation as resolved.
Show resolved Hide resolved
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 (
gasCallDataCopy = memoryCopierGas(2)
gasCodeCopyStateful = memoryCopierGas(2)
Expand All @@ -108,7 +96,7 @@ var (

func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64
if evm.accesses != nil {
if evm.Accesses != nil {
var (
codeOffset = stack.Back(1)
length = stack.Back(2)
Expand All @@ -117,52 +105,21 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow()
uint64Length, overflow := length.Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
uint64Length = 0xffffffffffffffff
}
addr := contract.Address()
chunk := uint64CodeOffset / 31
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)
}

_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, evm.Accesses)
}
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err
}

func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var statelessGas uint64
if evm.accesses != nil {
var (
a = stack.Back(0)
codeOffset = stack.Back(2)
length = stack.Back(3)
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow()
if overflow {
uint64CodeEnd = 0xffffffffffffffff
}
addr := common.Address(a.Bytes20())
chunk := uint64CodeOffset / 31
endChunk := uint64CodeEnd / 31
// XXX uint64 overflow in condition check
for ; chunk < endChunk; chunk++ {
// TODO(@gballet) make a version of GetTreeKeyCodeChunk without the bigint
index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))
statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}

if evm.Accesses != nil {
panic("extcodecopy not implemented for verkle")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

charge costs but don't panic here. add this as a comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@gballet there's another issue with EXTCODECOPY: we cannot hit the db until charging. Thus if we have an offset+size that is insanely large or outside the bounds of the target address, we have to charge for the entire range here.

This is a point that should be raised, and the spec should possibly be amended to reflect this.

}
usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err
Expand All @@ -171,11 +128,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) {
usedGas := uint64(0)

if evm.accesses != nil {
if evm.Accesses != nil {
where := stack.Back(0)
addr := contract.Address()
index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
usedGas += evm.Accesses.TouchAddressAndChargeGas(index, nil)
}

return usedGas, nil
Expand Down Expand Up @@ -207,7 +164,6 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return params.SstoreResetGas + accessGas, nil
}
}

// 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.
Expand Down Expand Up @@ -422,14 +378,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)
if evm.accesses != nil {
jwasinger marked this conversation as resolved.
Show resolved Hide resolved
// Charge witness costs
for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ {
index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i))
gas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
}
}

if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
Expand All @@ -456,6 +404,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}

return gas, nil
}

Expand Down
Loading