-
Notifications
You must be signed in to change notification settings - Fork 20.8k
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
core/{state, vm}: update stateless gas costs to follow the verkle-gen-7 testnet #31014
base: master
Are you sure you want to change the base?
Conversation
9f3ab25
to
c92a0d4
Compare
// list in write mode. If there is enough gas paying for the addition of the code | ||
// hash leaf to the access list, then account creation will proceed unimpaired. | ||
// Thus, only pay for the creation of the code hash leaf here. | ||
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the available gas is not sufficient to pay the cost, availableGas
will be returned.
Originally, the evm execution should stop without executing this operation;
Now, the evm execution will execute this operation, and may stop later;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already responded during standup - good idea but that's a spec change so we should try to fix it over time but this is what the testnet does.
1ceb3e2
to
e7dd2e9
Compare
2bc5759
to
a749131
Compare
… testnet Co-authored-by: Ignacio Hagopian <[email protected]> Signed-off-by: Guillaume Ballet <[email protected]>
a749131
to
13a09a4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request updates several gas cost functions and witness accounting logic to better match the verkle-gen-7 testnet requirements while also revising the instruction set and precompiled contract mappings. Key changes include:
- Updating gas calculations across storage, call, and contract creation operations to return both consumed and required gas.
- Changing the jump table from the Cancun to the Shanghai instruction set and remapping precompiled contracts.
- Adapting tests and access events to align with the new gas API signatures.
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
core/vm/operations_verkle.go | Revised gas functions and witness gas calculations for various operations. |
core/vm/jump_table.go | Switched the underlying instruction set from Cancun to Shanghai. |
core/vm/interpreter.go | Updated code chunk gas charging logic in the interpreter loop. |
core/vm/gas_table.go | Removed ValueTransferGas charging in favor of the new gas model. |
core/vm/evm.go | Modified contract creation and code chunk gas functions using updated APIs. |
core/vm/eips.go | Adjusted gas charging in extcode copy and push operations. |
core/vm/contracts.go | Remapped the Verkle precompiled contracts reference from Prague to Berlin. |
core/state_transition.go | Updated transaction destination logic to account for account existence. |
core/state/access_events_test.go | Refactored tests to use the updated access events gas functions. |
core/state/access_events.go | Revised gas access functions to include availableGas and warm cost logic. |
@@ -339,12 +339,10 @@ func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC | |||
addr := common.Address(a.Bytes20()) | |||
code := interpreter.evm.StateDB.GetCode(addr) | |||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) | |||
if !scope.Contract.IsSystemCall { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this implements the suggestion that extcodecopy of a "system contract" (yeah I know you guys don't like the name but it has the advantage that everyone gets what I'm talking about) will end up in the witness. Spec change is underway, I'll hack the stuff back in when I sync the testnet in the meantime.
core/state/access_events.go
Outdated
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite) | ||
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 { | ||
var gas uint64 // accumulate the consumed gas | ||
consumed, wanted := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe expected
instead of wanted
?
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue) | ||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false) | ||
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename doesntExist
to exists
and inverse it when passing it to ae.touchAddressAndChargeGas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll push back against this one, because if in the future we have to check a negative condition, we'll get !doesntExist
and double negatives are a nasty pitfall.
core/state/access_events.go
Outdated
if wanted == 0 && chargeWarmCosts { | ||
wanted = params.WarmStorageReadCostEIP2929 | ||
} | ||
return wanted | ||
} | ||
|
||
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold | ||
// access cost to be charged, if need be. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outdated comment, returns the consumed and required gas
core/state/access_events.go
Outdated
var gas uint64 | ||
if branchRead { | ||
gas += params.WitnessBranchReadCost | ||
} | ||
if chunkRead { | ||
gas += params.WitnessChunkReadCost | ||
} | ||
if branchWrite { | ||
gas += params.WitnessBranchWriteCost | ||
} | ||
if chunkWrite { | ||
gas += params.WitnessChunkWriteCost | ||
} | ||
if chunkFill { | ||
gas += params.WitnessChunkFillCost | ||
} | ||
|
||
if availableGas < gas { | ||
// consumed != wanted | ||
return availableGas, gas | ||
} | ||
|
||
if branchRead { | ||
ae.branches[branchKey] = AccessWitnessReadFlag | ||
} | ||
if branchWrite { | ||
ae.branches[branchKey] |= AccessWitnessWriteFlag | ||
} | ||
if chunkRead { | ||
ae.chunks[chunkKey] = AccessWitnessReadFlag | ||
} | ||
if chunkWrite { | ||
ae.chunks[chunkKey] |= AccessWitnessWriteFlag | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the first block, you're doing read read, write write
in the second block read write, read write
.
Am I correct in assuming that writes MUST be preceded by a read?
If so the code I think doing something like this would make the relationship a bit clearer (same with the gas computation):
e.g.
if branchRead {
ae.branches[branchKey] = AccessWitnessReadFlag
if branchWrite {
ae.branches[branchKey] |= AccessWitnessWriteFlag
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it wouldn't work, because you can have the read happen at an instruction before the one doing the write, and so if you did condition the write to only happen with a read, you would skip setting the write flag.
@@ -139,7 +139,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{ | |||
|
|||
var PrecompiledContractsBLS = PrecompiledContractsPrague | |||
|
|||
var PrecompiledContractsVerkle = PrecompiledContractsPrague | |||
var PrecompiledContractsVerkle = PrecompiledContractsBerlin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No KZG precompile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope it's for a pre-cancun chain at the moment
core/vm/eips.go
Outdated
consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas) | ||
scope.Contract.UseGas(consumed, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) | ||
if consumed < wanted { | ||
return nil, ErrOutOfGas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you also consume all the gas of this scope here?
Actually UseGas might underflow the contracts gas here, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be a bit confusing indeed: if you have enough gas, then consumed == wanted
so it's fine. If consumed < wanted
then consumed
contains all the remaining gas anyway.
Signed-off-by: Guillaume Ballet <[email protected]>
core/vm/eips.go
Outdated
scope.Contract.Gas = 0 | ||
return nil, ErrOutOfGas | ||
} | ||
consumed, wanted := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit weird to do the gas cost computation and deduction in opcode execution.
The information for computation the gas cost is: (1) code size (2) code offset (3) code length to copy
all these fields are available in gas func. Let's move it to gas func
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { | ||
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the | ||
// consumed and required gas. | ||
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function returns two parameters: (a) the number of gas required (b) the number of gas consumed
If the available gas is not sufficient to cover the gas cost, an error or a flag should be returned indicating the gas is not sufficient.
It's unnecessary to return availableGas
as consumed one, as the error will ultimately be captured by the outer part and leftover gas will be all consumed (e.g. err != ErrExecutionReverted)
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.