-
Notifications
You must be signed in to change notification settings - Fork 21.3k
cmd, core, params, trie: add verkle access witness gas charging #29338
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
Merged
holiman
merged 30 commits into
ethereum:master
from
gballet:rewritten-eip4762-into-master
May 10, 2024
+1,082
−43
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
9ad03c9
cmd, core, params, trie: Add verkle access witness
gballet bcc6da8
fix failing tests
gballet 2b3391e
fix linter issue
gballet a21c021
add beacon block hash to the witness
gballet e34ac0f
fix reference error
gballet 54bdfb4
Update core/state/access_witness.go
gballet f534a4d
partial review feedback
gballet 1bdae10
update based on fixes added to the main branch
gballet fea8553
add more fixes
gballet 2334cb7
rename access witness functions
gballet 811ebfe
fix rebase issues
gballet d4f2768
review feedback from @holiman
gballet c65d117
add verkle op handlers
gballet 4030cdd
rename AccessWitness to AccessEvents
gballet 3a5e041
improve comment for eip4762 PUSH1 handler
gballet c04b64c
remove unnecessary creation field
gballet 0620c5c
address last review comments
gballet 026ebc9
Apply suggestions from code review
gballet aa96a9c
Several fixes (#425)
rjl493456442 d5699fb
Revert "Several fixes (#425)"
gballet 04a4fe1
more review feedback
gballet 3777222
Focus on gas charges and remove witness-building parts
gballet 72efd71
move point cache to cachingDB and remove more witness stuff
gballet 7fa2327
a few bug fixes and feedback from Gary
gballet d804ab9
fix broken test
gballet 37952f6
remove unused line
gballet dbca8bb
Apply suggestions from code review
gballet 3800a70
fix some formatting
gballet 1a661dc
core/vm: remove duplicated EIP initialization
rjl493456442 88da3ce
Update core/state/access_events_test.go
holiman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
// Copyright 2021 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package state | ||
|
||
import ( | ||
"maps" | ||
|
||
"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" | ||
) | ||
|
||
// mode specifies how a tree location has been accessed | ||
// for the byte value: | ||
// * the first bit is set if the branch has been edited | ||
// * the second bit is set if the branch has been read | ||
type mode byte | ||
|
||
const ( | ||
AccessWitnessReadFlag = mode(1) | ||
AccessWitnessWriteFlag = mode(2) | ||
) | ||
|
||
var zeroTreeIndex uint256.Int | ||
|
||
// AccessEvents lists the locations of the state that are being accessed | ||
// during the production of a block. | ||
type AccessEvents struct { | ||
branches map[branchAccessKey]mode | ||
chunks map[chunkAccessKey]mode | ||
|
||
pointCache *utils.PointCache | ||
} | ||
|
||
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents { | ||
return &AccessEvents{ | ||
branches: make(map[branchAccessKey]mode), | ||
chunks: make(map[chunkAccessKey]mode), | ||
pointCache: pointCache, | ||
} | ||
} | ||
|
||
// Merge is used to merge the access events that were generated during the | ||
// execution of a tx, with the accumulation of all access events that were | ||
// generated during the execution of all txs preceding this one in a block. | ||
func (ae *AccessEvents) Merge(other *AccessEvents) { | ||
for k := range other.branches { | ||
ae.branches[k] |= other.branches[k] | ||
} | ||
for k, chunk := range other.chunks { | ||
ae.chunks[k] |= chunk | ||
} | ||
} | ||
|
||
// Keys returns, predictably, the list of keys that were touched during the | ||
// buildup of the access witness. | ||
func (ae *AccessEvents) Keys() [][]byte { | ||
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks). | ||
keys := make([][]byte, 0, len(ae.chunks)) | ||
for chunk := range ae.chunks { | ||
basePoint := ae.pointCache.Get(chunk.addr[:]) | ||
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey) | ||
keys = append(keys, key) | ||
} | ||
return keys | ||
} | ||
|
||
func (ae *AccessEvents) Copy() *AccessEvents { | ||
cpy := &AccessEvents{ | ||
branches: maps.Clone(ae.branches), | ||
chunks: maps.Clone(ae.chunks), | ||
pointCache: ae.pointCache, | ||
} | ||
return cpy | ||
} | ||
|
||
// AddAccount returns the gas to be charged for each of the currently cold | ||
// member fields of an account. | ||
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { | ||
var gas uint64 | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) | ||
return gas | ||
} | ||
|
||
// MessageCallGas returns the gas to be charged for each of the currently | ||
// cold member fields of an account, that need to be touched when making a message | ||
// call to that account. | ||
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { | ||
var gas uint64 | ||
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) | ||
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) | ||
return gas | ||
} | ||
|
||
// ValueTransferGas returns the gas to be charged for each of the currently | ||
// cold balance member fields of the caller and the callee accounts. | ||
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { | ||
var gas uint64 | ||
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) | ||
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) | ||
return gas | ||
} | ||
|
||
// ContractCreateInitGas returns the access gas costs for the initialization of | ||
// a contract creation. | ||
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { | ||
var gas uint64 | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) | ||
if createSendsValue { | ||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) | ||
} | ||
return gas | ||
} | ||
|
||
// AddTxOrigin adds the member fields of the sender account to the access event list, | ||
// so that cold accesses are not charged, since they are covered by the 21000 gas. | ||
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { | ||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false) | ||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true) | ||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true) | ||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) | ||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) | ||
} | ||
|
||
// AddTxDestination adds the member fields of the sender account to the access event list, | ||
// so that cold accesses are not charged, since they are covered by the 21000 gas. | ||
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue) | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false) | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) | ||
} | ||
|
||
// SlotGas returns the amount of gas to be charged for a cold storage access. | ||
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { | ||
treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) | ||
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) | ||
} | ||
|
||
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold | ||
// access cost to be charged, if need be. | ||
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { | ||
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite) | ||
|
||
var gas uint64 | ||
if stemRead { | ||
gas += params.WitnessBranchReadCost | ||
} | ||
if selectorRead { | ||
gas += params.WitnessChunkReadCost | ||
} | ||
if stemWrite { | ||
gas += params.WitnessBranchWriteCost | ||
} | ||
if selectorWrite { | ||
gas += params.WitnessChunkWriteCost | ||
} | ||
if selectorFill { | ||
gas += params.WitnessChunkFillCost | ||
} | ||
return gas | ||
} | ||
|
||
// touchAddress adds any missing access event to the access event list. | ||
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { | ||
branchKey := newBranchAccessKey(addr, treeIndex) | ||
chunkKey := newChunkAccessKey(branchKey, subIndex) | ||
|
||
// Read access. | ||
var branchRead, chunkRead bool | ||
if _, hasStem := ae.branches[branchKey]; !hasStem { | ||
branchRead = true | ||
ae.branches[branchKey] = AccessWitnessReadFlag | ||
} | ||
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { | ||
chunkRead = true | ||
ae.chunks[chunkKey] = AccessWitnessReadFlag | ||
} | ||
|
||
// Write access. | ||
var branchWrite, chunkWrite, chunkFill bool | ||
if isWrite { | ||
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { | ||
branchWrite = true | ||
ae.branches[branchKey] |= AccessWitnessWriteFlag | ||
} | ||
|
||
chunkValue := ae.chunks[chunkKey] | ||
if (chunkValue & AccessWitnessWriteFlag) == 0 { | ||
chunkWrite = true | ||
ae.chunks[chunkKey] |= AccessWitnessWriteFlag | ||
} | ||
// TODO: charge chunk filling costs if the leaf was previously empty in the state | ||
} | ||
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill | ||
} | ||
|
||
type branchAccessKey struct { | ||
addr common.Address | ||
treeIndex uint256.Int | ||
} | ||
|
||
func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey { | ||
var sk branchAccessKey | ||
sk.addr = addr | ||
sk.treeIndex = treeIndex | ||
return sk | ||
} | ||
|
||
type chunkAccessKey struct { | ||
branchAccessKey | ||
leafKey byte | ||
} | ||
|
||
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { | ||
var lk chunkAccessKey | ||
lk.branchAccessKey = branchKey | ||
lk.leafKey = leafKey | ||
return lk | ||
} | ||
|
||
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs | ||
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) 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 (codeLen == 0 && size == 0) || startPC > codeLen { | ||
return 0 | ||
} | ||
|
||
endPC := startPC + size | ||
if endPC > codeLen { | ||
endPC = codeLen | ||
} | ||
if endPC > 0 { | ||
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) | ||
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) | ||
var overflow bool | ||
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) | ||
if overflow { | ||
panic("overflow when adding gas") | ||
} | ||
} | ||
return statelessGasCharged | ||
} | ||
|
||
// VersionGas adds the account's version to the accessed data, and returns the | ||
// amount of gas that it costs. | ||
// Note that an access in write mode implies an access in read mode, whereas an | ||
// access in read mode does not imply an access in write mode. | ||
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 { | ||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) | ||
} | ||
|
||
// BalanceGas adds the account's balance to the accessed data, and returns the | ||
// amount of gas that it costs. | ||
// in write mode. If false, the charged gas corresponds to an access in read mode. | ||
// Note that an access in write mode implies an access in read mode, whereas an access in | ||
// read mode does not imply an access in write mode. | ||
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 { | ||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) | ||
} | ||
|
||
// NonceGas adds the account's nonce to the accessed data, and returns the | ||
// amount of gas that it costs. | ||
// in write mode. If false, the charged gas corresponds to an access in read mode. | ||
// Note that an access in write mode implies an access in read mode, whereas an access in | ||
// read mode does not imply an access in write mode. | ||
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 { | ||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) | ||
} | ||
|
||
// CodeSizeGas adds the account's code size to the accessed data, and returns the | ||
// amount of gas that it costs. | ||
// in write mode. If false, the charged gas corresponds to an access in read mode. | ||
// Note that an access in write mode implies an access in read mode, whereas an access in | ||
// read mode does not imply an access in write mode. | ||
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 { | ||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) | ||
} | ||
|
||
// CodeHashGas adds the account's code hash to the accessed data, and returns the | ||
// amount of gas that it costs. | ||
// in write mode. If false, the charged gas corresponds to an access in read mode. | ||
// Note that an access in write mode implies an access in read mode, whereas an access in | ||
// read mode does not imply an access in write mode. | ||
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { | ||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.