Skip to content

Commit cb65ee0

Browse files
committed
atomic gas charging
Signed-off-by: Guillaume Ballet <[email protected]>
1 parent 4a2f6fb commit cb65ee0

File tree

9 files changed

+277
-146
lines changed

9 files changed

+277
-146
lines changed

cmd/evm/internal/t8ntool/execution.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
343343
// Amount is in gwei, turn into wei
344344
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
345345
statedb.AddBalance(w.Address, amount)
346-
statedb.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
346+
statedb.Witness().TouchFullAccount(w.Address[:], true)
347347
}
348348
if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) {
349349
if err := overlay.OverlayVerkleTransition(statedb, common.Hash{}, chainConfig.OverlayStride); err != nil {

consensus/beacon/consensus.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"math/big"
2323

2424
"github.com/ethereum/go-ethereum/common"
25-
"github.com/ethereum/go-ethereum/common/math"
2625
"github.com/ethereum/go-ethereum/consensus"
2726
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
2827
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@@ -359,7 +358,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
359358
state.AddBalance(w.Address, amount)
360359

361360
// The returned gas is not charged
362-
state.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
361+
state.Witness().TouchFullAccount(w.Address[:], true)
363362
}
364363

365364
if chain.Config().IsVerkle(header.Number, header.Time) {

core/state/access_witness.go

Lines changed: 153 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package state
1818

1919
import (
20+
"errors"
21+
2022
"github.com/ethereum/go-ethereum/common"
2123
"github.com/ethereum/go-ethereum/common/math"
2224
"github.com/ethereum/go-ethereum/params"
@@ -89,83 +91,152 @@ func (aw *AccessWitness) Copy() *AccessWitness {
8991
return naw
9092
}
9193

92-
func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, availableGas uint64) uint64 {
93-
var gas uint64
94+
func (aw *AccessWitness) FullAccountGas(addr []byte, isWrite bool) uint64 {
95+
return aw.calculateWitnessGas(addr, zeroTreeIndex, isWrite, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
96+
}
97+
98+
func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) {
9499
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
95-
consumed, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, availableGas)
96-
if consumed < wanted {
97-
return wanted + gas
98-
}
99-
availableGas -= consumed
100-
gas += consumed
100+
aw.touchLocation(addr, zeroTreeIndex, byte(i), isWrite)
101101
}
102-
return gas
103102
}
104103

105-
func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, availableGas uint64) uint64 {
106-
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
104+
func (aw *AccessWitness) MessageCallGas(addr []byte) uint64 {
105+
wanted := aw.calculateWitnessGas(addr, zeroTreeIndex, false, utils.BasicDataLeafKey)
107106
if wanted == 0 {
108107
wanted = params.WarmStorageReadCostEIP2929
109108
}
110109
return wanted
111110
}
112111

113-
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, availableGas uint64) uint64 {
114-
_, wanted1 := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
115-
if wanted1 > availableGas {
116-
return wanted1
117-
}
118-
_, wanted2 := aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-wanted1)
112+
func (aw *AccessWitness) TouchMessageCall(addr []byte) {
113+
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
114+
}
115+
116+
func (aw *AccessWitness) TouchValueTransfer(callerAddr, targetAddr []byte) {
117+
aw.touchLocation(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
118+
aw.touchLocation(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
119+
}
120+
121+
func (aw *AccessWitness) ValueTransferGas(callerAddr, targetAddr []byte) uint64 {
122+
wanted1 := aw.calculateWitnessGas(callerAddr, zeroTreeIndex, true, utils.BasicDataLeafKey)
123+
wanted2 := aw.calculateWitnessGas(targetAddr, zeroTreeIndex, true, utils.BasicDataLeafKey)
119124
if wanted1+wanted2 == 0 {
120125
return params.WarmStorageReadCostEIP2929
121126
}
122127
return wanted1 + wanted2
123128
}
124129

130+
// ContractCreateCheckGas charges access costs before
131+
// a contract creation is initiated. It is just reads, because the
132+
// address collision is done before the transfer, and so no write
133+
// are guaranteed to happen at this point.
134+
func (aw *AccessWitness) ContractCreateCheckGas(addr []byte) uint64 {
135+
return aw.calculateWitnessGas(addr, zeroTreeIndex, false, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
136+
}
137+
125138
// TouchAndChargeContractCreateCheck charges access costs before
126139
// a contract creation is initiated. It is just reads, because the
127140
// address collision is done before the transfer, and so no write
128141
// are guaranteed to happen at this point.
129-
func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, availableGas uint64) uint64 {
130-
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
131-
_, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-gas1)
132-
return wanted1 + wanted2
142+
func (aw *AccessWitness) TouchContractCreateCheck(addr []byte) {
143+
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
144+
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
145+
}
146+
147+
// ContractCreateInitGas charges access costs to initiate a contract creation.
148+
func (aw *AccessWitness) ContractCreateInitGas(addr []byte) uint64 {
149+
return aw.calculateWitnessGas(addr, zeroTreeIndex, true, utils.BasicDataLeafKey, utils.CodeHashLeafKey)
133150
}
134151

135152
// TouchAndChargeContractCreateInit charges access costs to initiate
136153
// a contract creation.
137-
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, availableGas uint64) (uint64, uint64) {
138-
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
139-
gas2, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-gas1)
140-
return gas1 + gas2, wanted1 + wanted2
154+
func (aw *AccessWitness) TouchContractCreateInit(addr []byte, availableGas uint64) {
155+
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
156+
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
141157
}
142158

143159
func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) {
144160
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
145-
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, math.MaxUint64)
161+
aw.touchLocation(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey)
146162
}
147163
}
148164

149165
func (aw *AccessWitness) TouchTxTarget(targetAddr []byte, sendsValue, doesntExist bool) {
150-
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, math.MaxUint64)
166+
aw.touchLocation(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
151167
// Note that we do a write-event in CodeHash without distinguishing if the tx target account
152168
// exists or not. Pre-7702, there's no situation in which an existing codeHash can be mutated, thus
153169
// doing a write-event shouldn't cause an observable difference in gas usage.
154170
// TODO(7702): re-check this in the spec and implementation to be sure is a correct solution after
155171
// EIP-7702 is implemented.
156-
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, math.MaxUint64)
172+
aw.touchLocation(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist)
157173
}
158174

159-
func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
175+
func (aw *AccessWitness) SlotGas(addr []byte, slot common.Hash, isWrite bool) uint64 {
160176
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
161-
_, wanted := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
162-
if wanted == 0 && warmCostCharging {
177+
wanted := aw.calculateWitnessGas(addr, *treeIndex, isWrite, subIndex)
178+
if wanted == 0 {
163179
wanted = params.WarmStorageReadCostEIP2929
164180
}
165181
return wanted
166182
}
167183

168-
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
184+
func (aw *AccessWitness) TouchSlot(addr []byte, slot common.Hash, isWrite bool) {
185+
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
186+
aw.touchLocation(addr, *treeIndex, subIndex, isWrite)
187+
}
188+
189+
func (aw *AccessWitness) calculateWitnessGas(addr []byte, treeIndex uint256.Int, isWrite bool, subIndices ...byte) uint64 {
190+
var (
191+
gas uint64
192+
branchKey = newBranchAccessKey(addr, treeIndex)
193+
branchRead, branchWrite bool
194+
)
195+
196+
// Read access.
197+
if _, hasStem := aw.branches[branchKey]; !hasStem {
198+
branchRead = true
199+
}
200+
201+
// Write access.
202+
if isWrite {
203+
if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
204+
branchWrite = true
205+
}
206+
}
207+
208+
if branchRead {
209+
gas += params.WitnessBranchReadCost
210+
}
211+
if branchWrite {
212+
gas += params.WitnessBranchWriteCost
213+
}
214+
215+
for _, subIndex := range subIndices {
216+
var chunkRead, chunkWrite, chunkFill bool
217+
chunkKey := newChunkAccessKey(branchKey, subIndex)
218+
if _, hasSelector := aw.chunks[chunkKey]; !hasSelector {
219+
chunkRead = true
220+
221+
}
222+
if isWrite && (aw.chunks[chunkKey]&AccessWitnessWriteFlag) == 0 {
223+
chunkWrite = true
224+
}
225+
if chunkRead {
226+
gas += params.WitnessChunkReadCost
227+
}
228+
if chunkWrite {
229+
gas += params.WitnessChunkWriteCost
230+
}
231+
if chunkFill {
232+
gas += params.WitnessChunkFillCost
233+
}
234+
}
235+
236+
return gas
237+
}
238+
239+
func (aw *AccessWitness) touchLocation(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) {
169240
branchKey := newBranchAccessKey(addr, treeIndex)
170241
chunkKey := newChunkAccessKey(branchKey, subIndex)
171242

@@ -191,28 +262,6 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256
191262
}
192263
}
193264

194-
var gas uint64
195-
if branchRead {
196-
gas += params.WitnessBranchReadCost
197-
}
198-
if chunkRead {
199-
gas += params.WitnessChunkReadCost
200-
}
201-
if branchWrite {
202-
gas += params.WitnessBranchWriteCost
203-
}
204-
if chunkWrite {
205-
gas += params.WitnessChunkWriteCost
206-
}
207-
if chunkFill {
208-
gas += params.WitnessChunkFillCost
209-
}
210-
211-
if availableGas < gas {
212-
// consumed != wanted
213-
return availableGas, gas
214-
}
215-
216265
if branchRead {
217266
aw.branches[branchKey] = AccessWitnessReadFlag
218267
}
@@ -224,10 +273,11 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256
224273
}
225274
if chunkWrite {
226275
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
227-
}
228276

229-
// consumed == wanted
230-
return gas, gas
277+
if chunkFill {
278+
// TODO when FILL_COST is implemented
279+
}
280+
}
231281
}
232282

233283
type branchAccessKey struct {
@@ -254,16 +304,16 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
254304
return lk
255305
}
256306

257-
// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
258-
func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
307+
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
308+
func (aw *AccessWitness) CodeChunksRangeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) (uint64, error) {
259309
// note that in the case where the copied code is outside the range of the
260310
// contract code but touches the last leaf with contract code in it,
261311
// we don't include the last leaf of code in the AccessWitness. The
262312
// reason that we do not need the last leaf is the account's code size
263313
// is already in the AccessWitness so a stateless verifier can see that
264314
// the code from the last leaf is not needed.
265315
if size == 0 || startPC >= codeLen {
266-
return 0, 0
316+
return 0, nil
267317
}
268318

269319
endPC := startPC + size
@@ -278,39 +328,63 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s
278328
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
279329
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
280330
subIndex := byte((chunkNumber + 128) % 256)
281-
consumed, wanted := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
282-
// did we OOG ?
283-
if wanted > consumed {
284-
return statelessGasCharged + consumed, statelessGasCharged + wanted
285-
}
331+
wanted := aw.calculateWitnessGas(contractAddr, treeIndex, isWrite, subIndex)
286332
var overflow bool
287-
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
333+
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, wanted)
288334
if overflow {
289-
panic("overflow when adding gas")
335+
return 0, errors.New("gas uint overflow")
290336
}
291-
availableGas -= consumed
292337
}
293338

294-
return statelessGasCharged, statelessGasCharged
339+
return statelessGasCharged, nil
295340
}
296341

297-
func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
298-
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
342+
// TouchCodeChunksRange is a helper function to touch every chunk in a code range and charge witness gas costs
343+
func (aw *AccessWitness) TouchCodeChunksRange(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) {
344+
// note that in the case where the copied code is outside the range of the
345+
// contract code but touches the last leaf with contract code in it,
346+
// we don't include the last leaf of code in the AccessWitness. The
347+
// reason that we do not need the last leaf is the account's code size
348+
// is already in the AccessWitness so a stateless verifier can see that
349+
// the code from the last leaf is not needed.
350+
if size == 0 || startPC >= codeLen {
351+
return
352+
}
353+
354+
endPC := startPC + size
355+
if endPC > codeLen {
356+
endPC = codeLen
357+
}
358+
if endPC > 0 {
359+
endPC -= 1 // endPC is the last bytecode that will be touched.
360+
}
361+
362+
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
363+
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
364+
subIndex := byte((chunkNumber + 128) % 256)
365+
aw.touchLocation(contractAddr, treeIndex, subIndex, isWrite)
366+
}
367+
}
368+
369+
func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool) {
370+
aw.touchLocation(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
371+
}
372+
373+
func (aw *AccessWitness) BasicDataGas(addr []byte, isWrite bool, warmCostCharging bool) uint64 {
374+
wanted := aw.calculateWitnessGas(addr, zeroTreeIndex, isWrite, utils.BasicDataLeafKey)
299375
if wanted == 0 && warmCostCharging {
300-
if availableGas < params.WarmStorageReadCostEIP2929 {
301-
return availableGas
302-
}
303376
wanted = params.WarmStorageReadCostEIP2929
304377
}
305378
return wanted
306379
}
307380

308-
func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
309-
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
381+
func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) {
382+
aw.touchLocation(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
383+
}
384+
385+
func (aw *AccessWitness) CodeHashGas(addr []byte, isWrite bool, chargeWarmCosts bool) uint64 {
386+
wanted := aw.calculateWitnessGas(addr, zeroTreeIndex, isWrite, utils.CodeHashLeafKey)
310387
if wanted == 0 && chargeWarmCosts {
311-
if availableGas < params.WarmStorageReadCostEIP2929 {
312-
return availableGas
313-
}
314388
wanted = params.WarmStorageReadCostEIP2929
315389
}
316390
return wanted

core/state_processor.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"math/big"
2424

2525
"github.com/ethereum/go-ethereum/common"
26-
"github.com/ethereum/go-ethereum/common/math"
2726
"github.com/ethereum/go-ethereum/consensus"
2827
"github.com/ethereum/go-ethereum/consensus/misc"
2928
"github.com/ethereum/go-ethereum/core/state"
@@ -178,7 +177,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
178177

179178
func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) {
180179
// Make sure that the historical contract is added to the witness
181-
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, math.MaxUint64)
180+
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true)
182181

183182
ancestor := chain.GetHeader(prevHash, prevNumber)
184183
for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- {
@@ -192,5 +191,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash
192191
var key common.Hash
193192
binary.BigEndian.PutUint64(key[24:], ringIndex)
194193
statedb.SetState(params.HistoryStorageAddress, key, prevHash)
195-
statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, math.MaxUint64, false)
194+
statedb.Witness().TouchSlot(params.HistoryStorageAddress[:], key, true)
196195
}

core/state_transition.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
453453

454454
// add the coinbase to the witness iff the fee is greater than 0
455455
if rules.IsEIP4762 && fee.Sign() != 0 {
456-
st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, math.MaxUint64)
456+
st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true)
457457
}
458458
}
459459

0 commit comments

Comments
 (0)