Skip to content

Commit af9a3a1

Browse files
authored
core/state, core/vm: update stateless gas costs to follow the verkle-gen-7 testnet (#31014)
Adding values to the witness introduces a new class of issues for computing gas: if there is not enough gas to cover adding an item to the witness, then the item should not be added to the witness. The problem happens when several items are added together, and that process runs out of gas. The witness gas computation needs a way to signal that not enough gas was provided. These values can not be hardcoded, however, as they are context dependent, i.e. two calls to the same function with the same parameters can give two different results. The approach is to return both the gas that was actually consumed, and the gas that was necessary. If the values don't match, then a witness update OOG'd. The caller should then charge the `consumed` value (remaining gas will be 0) and error out. Why not return a boolean instead of the wanted value? Because when several items are touched, we want to distinguish which item lacked gas. --------- Signed-off-by: Guillaume Ballet <[email protected]>
1 parent 228803c commit af9a3a1

File tree

10 files changed

+275
-202
lines changed

10 files changed

+275
-202
lines changed

core/state/access_events.go

Lines changed: 118 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package state
1818

1919
import (
2020
"maps"
21+
gomath "math"
2122

2223
"github.com/ethereum/go-ethereum/common"
2324
"github.com/ethereum/go-ethereum/common/math"
@@ -92,127 +93,156 @@ func (ae *AccessEvents) Copy() *AccessEvents {
9293

9394
// AddAccount returns the gas to be charged for each of the currently cold
9495
// 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.BasicDataLeafKey, isWrite)
98-
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
96+
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
97+
var gas uint64 // accumulate the consumed gas
98+
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
99+
if consumed < expected {
100+
return expected
101+
}
102+
gas += consumed
103+
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
104+
if consumed < expected {
105+
return expected + gas
106+
}
107+
gas += expected
99108
return gas
100109
}
101110

102111
// MessageCallGas returns the gas to be charged for each of the currently
103112
// cold member fields of an account, that need to be touched when making a message
104113
// call to that account.
105-
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
106-
var gas uint64
107-
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false)
108-
return gas
114+
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
115+
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
116+
if expected == 0 {
117+
expected = params.WarmStorageReadCostEIP2929
118+
}
119+
return expected
109120
}
110121

111122
// ValueTransferGas returns the gas to be charged for each of the currently
112123
// cold balance member fields of the caller and the callee accounts.
113-
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
114-
var gas uint64
115-
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
116-
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
117-
return gas
124+
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
125+
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
126+
if expected1 > availableGas {
127+
return expected1
128+
}
129+
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
130+
if expected1+expected2 == 0 {
131+
return params.WarmStorageReadCostEIP2929
132+
}
133+
return expected1 + expected2
118134
}
119135

120136
// ContractCreatePreCheckGas charges access costs before
121137
// a contract creation is initiated. It is just reads, because the
122138
// address collision is done before the transfer, and so no write
123139
// are guaranteed to happen at this point.
124-
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address) uint64 {
125-
var gas uint64
126-
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
127-
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
128-
return gas
140+
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
141+
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
142+
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
143+
return expected1 + expected2
129144
}
130145

131146
// ContractCreateInitGas returns the access gas costs for the initialization of
132147
// a contract creation.
133-
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address) uint64 {
148+
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
134149
var gas uint64
135-
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
136-
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
137-
return gas
150+
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
151+
gas += consumed
152+
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
153+
gas += consumed
154+
return gas, expected1 + expected2
138155
}
139156

140157
// AddTxOrigin adds the member fields of the sender account to the access event list,
141158
// so that cold accesses are not charged, since they are covered by the 21000 gas.
142159
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
143-
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
144-
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
160+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
161+
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
145162
}
146163

147164
// AddTxDestination adds the member fields of the sender account to the access event list,
148165
// so that cold accesses are not charged, since they are covered by the 21000 gas.
149-
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
150-
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
151-
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
166+
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
167+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
168+
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
152169
}
153170

154171
// SlotGas returns the amount of gas to be charged for a cold storage access.
155-
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 {
172+
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
156173
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
157-
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
158-
}
159-
160-
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold
161-
// access cost to be charged, if need be.
162-
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
163-
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
164-
165-
var gas uint64
166-
if stemRead {
167-
gas += params.WitnessBranchReadCost
168-
}
169-
if selectorRead {
170-
gas += params.WitnessChunkReadCost
171-
}
172-
if stemWrite {
173-
gas += params.WitnessBranchWriteCost
174-
}
175-
if selectorWrite {
176-
gas += params.WitnessChunkWriteCost
174+
_, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
175+
if expected == 0 && chargeWarmCosts {
176+
expected = params.WarmStorageReadCostEIP2929
177177
}
178-
if selectorFill {
179-
gas += params.WitnessChunkFillCost
180-
}
181-
return gas
178+
return expected
182179
}
183180

184-
// touchAddress adds any missing access event to the access event list.
185-
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
181+
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the
182+
// consumed and required gas.
183+
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
186184
branchKey := newBranchAccessKey(addr, treeIndex)
187185
chunkKey := newChunkAccessKey(branchKey, subIndex)
188186

189187
// Read access.
190188
var branchRead, chunkRead bool
191189
if _, hasStem := ae.branches[branchKey]; !hasStem {
192190
branchRead = true
193-
ae.branches[branchKey] = AccessWitnessReadFlag
194191
}
195192
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
196193
chunkRead = true
197-
ae.chunks[chunkKey] = AccessWitnessReadFlag
198194
}
199195

200196
// Write access.
201197
var branchWrite, chunkWrite, chunkFill bool
202198
if isWrite {
203199
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
204200
branchWrite = true
205-
ae.branches[branchKey] |= AccessWitnessWriteFlag
206201
}
207202

208203
chunkValue := ae.chunks[chunkKey]
209204
if (chunkValue & AccessWitnessWriteFlag) == 0 {
210205
chunkWrite = true
211-
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
212206
}
213-
// TODO: charge chunk filling costs if the leaf was previously empty in the state
214207
}
215-
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
208+
209+
var gas uint64
210+
if branchRead {
211+
gas += params.WitnessBranchReadCost
212+
}
213+
if chunkRead {
214+
gas += params.WitnessChunkReadCost
215+
}
216+
if branchWrite {
217+
gas += params.WitnessBranchWriteCost
218+
}
219+
if chunkWrite {
220+
gas += params.WitnessChunkWriteCost
221+
}
222+
if chunkFill {
223+
gas += params.WitnessChunkFillCost
224+
}
225+
226+
if availableGas < gas {
227+
// consumed != expected
228+
return availableGas, gas
229+
}
230+
231+
if branchRead {
232+
ae.branches[branchKey] = AccessWitnessReadFlag
233+
}
234+
if branchWrite {
235+
ae.branches[branchKey] |= AccessWitnessWriteFlag
236+
}
237+
if chunkRead {
238+
ae.chunks[chunkKey] = AccessWitnessReadFlag
239+
}
240+
if chunkWrite {
241+
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
242+
}
243+
244+
// consumed == expected
245+
return gas, gas
216246
}
217247

218248
type branchAccessKey struct {
@@ -240,15 +270,15 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
240270
}
241271

242272
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
243-
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
273+
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
244274
// note that in the case where the copied code is outside the range of the
245275
// contract code but touches the last leaf with contract code in it,
246276
// we don't include the last leaf of code in the AccessWitness. The
247277
// reason that we do not need the last leaf is the account's code size
248278
// is already in the AccessWitness so a stateless verifier can see that
249279
// the code from the last leaf is not needed.
250280
if (codeLen == 0 && size == 0) || startPC > codeLen {
251-
return 0
281+
return 0, 0
252282
}
253283

254284
endPC := startPC + size
@@ -263,29 +293,48 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
263293
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
264294
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
265295
subIndex := byte((chunkNumber + 128) % 256)
266-
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
296+
consumed, expected := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
297+
// did we OOG ?
298+
if expected > consumed {
299+
return statelessGasCharged + consumed, statelessGasCharged + expected
300+
}
267301
var overflow bool
268-
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
302+
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
269303
if overflow {
270304
panic("overflow when adding gas")
271305
}
306+
availableGas -= consumed
272307
}
273-
return statelessGasCharged
308+
return statelessGasCharged, statelessGasCharged
274309
}
275310

276311
// BasicDataGas adds the account's basic data to the accessed data, and returns the
277312
// amount of gas that it costs.
278313
// Note that an access in write mode implies an access in read mode, whereas an
279314
// access in read mode does not imply an access in write mode.
280-
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
281-
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
315+
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
316+
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
317+
if expected == 0 && chargeWarmCosts {
318+
if availableGas < params.WarmStorageReadCostEIP2929 {
319+
return availableGas
320+
}
321+
expected = params.WarmStorageReadCostEIP2929
322+
}
323+
return expected
282324
}
283325

284326
// CodeHashGas adds the account's code hash to the accessed data, and returns the
285327
// amount of gas that it costs.
286328
// in write mode. If false, the charged gas corresponds to an access in read mode.
287329
// Note that an access in write mode implies an access in read mode, whereas an access in
288330
// read mode does not imply an access in write mode.
289-
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
290-
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
331+
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
332+
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
333+
if expected == 0 && chargeWarmCosts {
334+
if availableGas < params.WarmStorageReadCostEIP2929 {
335+
return availableGas
336+
}
337+
expected = params.WarmStorageReadCostEIP2929
338+
}
339+
return expected
291340
}

0 commit comments

Comments
 (0)