Skip to content

Commit 309b2fe

Browse files
gballetjorgemmsilva
authored andcommitted
cmd, core, params, trie: add verkle access witness gas charging (ethereum#29338)
Implements some of the changes required to charge and do gas accounting in verkle testnet.
1 parent 49a3533 commit 309b2fe

31 files changed

+1082
-43
lines changed

cmd/geth/consolecmd_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) {
103103
"--http", "--http.port", httpPort,
104104
"--ws", "--ws.port", wsPort)
105105
t.Run("ipc", func(t *testing.T) {
106-
waitForEndpoint(t, ipc, 3*time.Second)
106+
waitForEndpoint(t, ipc, 4*time.Second)
107107
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
108108
})
109109
t.Run("http", func(t *testing.T) {
110110
endpoint := "http://127.0.0.1:" + httpPort
111-
waitForEndpoint(t, endpoint, 3*time.Second)
111+
waitForEndpoint(t, endpoint, 4*time.Second)
112112
testAttachWelcome(t, geth, endpoint, httpAPIs)
113113
})
114114
t.Run("ws", func(t *testing.T) {
115115
endpoint := "ws://127.0.0.1:" + wsPort
116-
waitForEndpoint(t, endpoint, 3*time.Second)
116+
waitForEndpoint(t, endpoint, 4*time.Second)
117117
testAttachWelcome(t, geth, endpoint, httpAPIs)
118118
})
119119
geth.Kill()

cmd/geth/verkle.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/ethereum/go-ethereum/core/rawdb"
2929
"github.com/ethereum/go-ethereum/internal/flags"
3030
"github.com/ethereum/go-ethereum/log"
31-
"github.com/gballet/go-verkle"
31+
"github.com/ethereum/go-verkle"
3232
"github.com/urfave/cli/v2"
3333
)
3434

core/chain_makers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
"github.com/ethereum/go-ethereum/ethdb"
3333
"github.com/ethereum/go-ethereum/params"
3434
"github.com/ethereum/go-ethereum/triedb"
35-
"github.com/gballet/go-verkle"
35+
"github.com/ethereum/go-verkle"
3636
"github.com/holiman/uint256"
3737
)
3838

core/error.go

+5
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ var (
6464
// than init code size limit.
6565
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
6666

67+
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
68+
// funds to cover the transfer, but not enough to pay for witness access/modification
69+
// costs for the transaction
70+
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
71+
6772
// ErrInsufficientFunds is returned if the total cost of executing a transaction
6873
// is higher than the balance of the user's account.
6974
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

core/state/access_events.go

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package state
18+
19+
import (
20+
"maps"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/common/math"
24+
"github.com/ethereum/go-ethereum/params"
25+
"github.com/ethereum/go-ethereum/trie/utils"
26+
"github.com/holiman/uint256"
27+
)
28+
29+
// mode specifies how a tree location has been accessed
30+
// for the byte value:
31+
// * the first bit is set if the branch has been edited
32+
// * the second bit is set if the branch has been read
33+
type mode byte
34+
35+
const (
36+
AccessWitnessReadFlag = mode(1)
37+
AccessWitnessWriteFlag = mode(2)
38+
)
39+
40+
var zeroTreeIndex uint256.Int
41+
42+
// AccessEvents lists the locations of the state that are being accessed
43+
// during the production of a block.
44+
type AccessEvents struct {
45+
branches map[branchAccessKey]mode
46+
chunks map[chunkAccessKey]mode
47+
48+
pointCache *utils.PointCache
49+
}
50+
51+
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
52+
return &AccessEvents{
53+
branches: make(map[branchAccessKey]mode),
54+
chunks: make(map[chunkAccessKey]mode),
55+
pointCache: pointCache,
56+
}
57+
}
58+
59+
// Merge is used to merge the access events that were generated during the
60+
// execution of a tx, with the accumulation of all access events that were
61+
// generated during the execution of all txs preceding this one in a block.
62+
func (ae *AccessEvents) Merge(other *AccessEvents) {
63+
for k := range other.branches {
64+
ae.branches[k] |= other.branches[k]
65+
}
66+
for k, chunk := range other.chunks {
67+
ae.chunks[k] |= chunk
68+
}
69+
}
70+
71+
// Keys returns, predictably, the list of keys that were touched during the
72+
// buildup of the access witness.
73+
func (ae *AccessEvents) Keys() [][]byte {
74+
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
75+
keys := make([][]byte, 0, len(ae.chunks))
76+
for chunk := range ae.chunks {
77+
basePoint := ae.pointCache.Get(chunk.addr[:])
78+
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
79+
keys = append(keys, key)
80+
}
81+
return keys
82+
}
83+
84+
func (ae *AccessEvents) Copy() *AccessEvents {
85+
cpy := &AccessEvents{
86+
branches: maps.Clone(ae.branches),
87+
chunks: maps.Clone(ae.chunks),
88+
pointCache: ae.pointCache,
89+
}
90+
return cpy
91+
}
92+
93+
// AddAccount returns the gas to be charged for each of the currently cold
94+
// member fields of an account.
95+
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
96+
var gas uint64
97+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
98+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
99+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
100+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
101+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
102+
return gas
103+
}
104+
105+
// MessageCallGas returns the gas to be charged for each of the currently
106+
// cold member fields of an account, that need to be touched when making a message
107+
// call to that account.
108+
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
109+
var gas uint64
110+
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false)
111+
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
112+
return gas
113+
}
114+
115+
// ValueTransferGas returns the gas to be charged for each of the currently
116+
// cold balance member fields of the caller and the callee accounts.
117+
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
118+
var gas uint64
119+
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
120+
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
121+
return gas
122+
}
123+
124+
// ContractCreateInitGas returns the access gas costs for the initialization of
125+
// a contract creation.
126+
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
127+
var gas uint64
128+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
129+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
130+
if createSendsValue {
131+
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
132+
}
133+
return gas
134+
}
135+
136+
// AddTxOrigin adds the member fields of the sender account to the access event list,
137+
// so that cold accesses are not charged, since they are covered by the 21000 gas.
138+
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
139+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false)
140+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
141+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
142+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
143+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
144+
}
145+
146+
// AddTxDestination adds the member fields of the sender account to the access event list,
147+
// so that cold accesses are not charged, since they are covered by the 21000 gas.
148+
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
149+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
150+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue)
151+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
152+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
153+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
154+
}
155+
156+
// SlotGas returns the amount of gas to be charged for a cold storage access.
157+
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 {
158+
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
159+
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
160+
}
161+
162+
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold
163+
// access cost to be charged, if need be.
164+
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
165+
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
166+
167+
var gas uint64
168+
if stemRead {
169+
gas += params.WitnessBranchReadCost
170+
}
171+
if selectorRead {
172+
gas += params.WitnessChunkReadCost
173+
}
174+
if stemWrite {
175+
gas += params.WitnessBranchWriteCost
176+
}
177+
if selectorWrite {
178+
gas += params.WitnessChunkWriteCost
179+
}
180+
if selectorFill {
181+
gas += params.WitnessChunkFillCost
182+
}
183+
return gas
184+
}
185+
186+
// touchAddress adds any missing access event to the access event list.
187+
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
188+
branchKey := newBranchAccessKey(addr, treeIndex)
189+
chunkKey := newChunkAccessKey(branchKey, subIndex)
190+
191+
// Read access.
192+
var branchRead, chunkRead bool
193+
if _, hasStem := ae.branches[branchKey]; !hasStem {
194+
branchRead = true
195+
ae.branches[branchKey] = AccessWitnessReadFlag
196+
}
197+
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
198+
chunkRead = true
199+
ae.chunks[chunkKey] = AccessWitnessReadFlag
200+
}
201+
202+
// Write access.
203+
var branchWrite, chunkWrite, chunkFill bool
204+
if isWrite {
205+
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
206+
branchWrite = true
207+
ae.branches[branchKey] |= AccessWitnessWriteFlag
208+
}
209+
210+
chunkValue := ae.chunks[chunkKey]
211+
if (chunkValue & AccessWitnessWriteFlag) == 0 {
212+
chunkWrite = true
213+
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
214+
}
215+
// TODO: charge chunk filling costs if the leaf was previously empty in the state
216+
}
217+
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
218+
}
219+
220+
type branchAccessKey struct {
221+
addr common.Address
222+
treeIndex uint256.Int
223+
}
224+
225+
func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey {
226+
var sk branchAccessKey
227+
sk.addr = addr
228+
sk.treeIndex = treeIndex
229+
return sk
230+
}
231+
232+
type chunkAccessKey struct {
233+
branchAccessKey
234+
leafKey byte
235+
}
236+
237+
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
238+
var lk chunkAccessKey
239+
lk.branchAccessKey = branchKey
240+
lk.leafKey = leafKey
241+
return lk
242+
}
243+
244+
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
245+
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
246+
// note that in the case where the copied code is outside the range of the
247+
// contract code but touches the last leaf with contract code in it,
248+
// we don't include the last leaf of code in the AccessWitness. The
249+
// reason that we do not need the last leaf is the account's code size
250+
// is already in the AccessWitness so a stateless verifier can see that
251+
// the code from the last leaf is not needed.
252+
if (codeLen == 0 && size == 0) || startPC > codeLen {
253+
return 0
254+
}
255+
256+
endPC := startPC + size
257+
if endPC > codeLen {
258+
endPC = codeLen
259+
}
260+
if endPC > 0 {
261+
endPC -= 1 // endPC is the last bytecode that will be touched.
262+
}
263+
264+
var statelessGasCharged uint64
265+
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
266+
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
267+
subIndex := byte((chunkNumber + 128) % 256)
268+
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
269+
var overflow bool
270+
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
271+
if overflow {
272+
panic("overflow when adding gas")
273+
}
274+
}
275+
return statelessGasCharged
276+
}
277+
278+
// VersionGas adds the account's version to the accessed data, and returns the
279+
// amount of gas that it costs.
280+
// Note that an access in write mode implies an access in read mode, whereas an
281+
// access in read mode does not imply an access in write mode.
282+
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 {
283+
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
284+
}
285+
286+
// BalanceGas adds the account's balance to the accessed data, and returns the
287+
// amount of gas that it costs.
288+
// in write mode. If false, the charged gas corresponds to an access in read mode.
289+
// Note that an access in write mode implies an access in read mode, whereas an access in
290+
// read mode does not imply an access in write mode.
291+
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
292+
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
293+
}
294+
295+
// NonceGas adds the account's nonce to the accessed data, and returns the
296+
// amount of gas that it costs.
297+
// in write mode. If false, the charged gas corresponds to an access in read mode.
298+
// Note that an access in write mode implies an access in read mode, whereas an access in
299+
// read mode does not imply an access in write mode.
300+
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
301+
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
302+
}
303+
304+
// CodeSizeGas adds the account's code size to the accessed data, and returns the
305+
// amount of gas that it costs.
306+
// in write mode. If false, the charged gas corresponds to an access in read mode.
307+
// Note that an access in write mode implies an access in read mode, whereas an access in
308+
// read mode does not imply an access in write mode.
309+
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
310+
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
311+
}
312+
313+
// CodeHashGas adds the account's code hash to the accessed data, and returns the
314+
// amount of gas that it costs.
315+
// in write mode. If false, the charged gas corresponds to an access in read mode.
316+
// Note that an access in write mode implies an access in read mode, whereas an access in
317+
// read mode does not imply an access in write mode.
318+
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
319+
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
320+
}

0 commit comments

Comments
 (0)