-
Notifications
You must be signed in to change notification settings - Fork 13
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
witness: only include elements if there's enough gas for it #495
Conversation
7f2d3d5
to
7aedcc9
Compare
ce68c07
to
5e0aed2
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
314ebde
to
95f771d
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
3bd3e33
to
f147cdc
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
statedb.Witness().TouchFullAccount(w.Address[:], true) | ||
statedb.Witness().TouchFullAccount(w.Address[:], true, nil) |
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.
For any Touch*(...)
call that doesn't care about charging gas, we send the nil
UseGas(...)
function.
This is the equivalent action of the previous API where we ignored the returned value (since we didn't charge that gas to anyone).
@@ -30,6 +29,9 @@ import ( | |||
// * the second bit is set if the branch has been read | |||
type mode byte | |||
|
|||
// UseGasFn is a function that can be used to charge gas for a given amount. | |||
type UseGasFn func(uint64) 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.
Now Touch*(....)
functions receive a function that we'll use as a callback to charge the required gas.
Many Touch*(...)
do more than one charging. This means that this callback could be called multiple times.
Before I tried another idea which was not passing this callback, but asking for availableGas
. The problem with that is that it adds a lot of strain in callers to solve partial charging. The Touch*(...)
calls can charge for more than one thing. In the case the availableGas
was sufficient for some of them but not all, that means the API should return:
gasThatMustBeCharged
- and
ok
bool to signal if theavailableGas
was sufficient for everything or not.
The caller should:
- Always charge
gasThatMustBeCharged
, respectively of whatok
is. Since the passedavailableGas
was at least sufficient to put some stuff in the witness. - if
ok
is false, fail due to out of gas.
The above logic should be done in all the callers.
This proposed API where we pass a callback function, avoids all this logic in all callers. The API call will already charge for whatever was allowed, and simply return false signaling that the available gas was insufficient and let the caller fail (i.e: the caller shouldn't deal with charging stuff since that was already done in the API call).
That's the "TLDR" of this approach. I know was mentioned this still sounds like can get some pushback from the geth team, so we can try again with the other approach... but I found it a bit annoying.
} | ||
|
||
// touchAddress adds any missing access event to the witness. | ||
func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { | ||
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, useGasFn UseGasFn) (uint64, 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.
This is the new "base" fundamental method that charges for stuff.
Basically:
- we fill the boolanes if this charging required
branchRead
,chunkRead
, etc. But we don't yet affectaw.[branches|chunks]
since we want to do that ONLY if we have available gas.
(Continue to read below comments for further bullets)
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 | ||
} |
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.
- With the booleans, we calcualte the
gas
as usual.
if useGasFn != nil { | ||
if ok := useGasFn(gas); !ok { | ||
return 0, 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.
- Ask for the callback to charge this gas, and signal us if it was possible or not.
If wasn't possible, return early. Notice that we haven't technically added anything to the witness. This is important.
core/vm/instructions.go
Outdated
chargedGas, ok := interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas) | ||
if !ok || (chargedGas == 0 && !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929)) { |
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.
You could be wondering why some of the Touch*(...)
APIs returned the charged gas if we were sending the callback?
This is the reason. We have this rule that if on many charges the actual charged gas was 0, then we charge warm. We ned a way to know if that UseGas
was asked to charge 0 or not.
If was 0, we do a usual scope.Contract.UseGas(Warm)
.
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.
hmmm yeah but we could simplify this by adding a chargeWarm
flag to the function, so that we don't need to clutter the code too much here. If you still have to call UseGas
after calling the function you pass it to, this makes the code that much harder to read.
core/vm/instructions.go
Outdated
func chargeCallVariantEIP4762(evm *EVM, scope *ScopeContext) bool { | ||
target := common.Address(scope.Stack.Back(1).Bytes20()) | ||
if _, isPrecompile := evm.precompile(target); isPrecompile { | ||
return scope.Contract.UseGas(params.WarmStorageReadCostEIP2929) | ||
} | ||
// The charging for the value transfer is done BEFORE subtracting | ||
// the 1/64th gas, as this is considered part of the CALL instruction. | ||
// (so before we get to this point) | ||
// But the message call is part of the subcall, for which only 63/64th | ||
// of the gas should be available. | ||
chargedGas, ok := evm.Accesses.TouchAndChargeMessageCall(target.Bytes(), scope.Contract.UseGas) | ||
if !ok || (chargedGas == 0 && !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929)) { | ||
return false | ||
} | ||
return true | ||
|
||
} |
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 is the makeCallVariantGas4762
layer that we removed and moved to op*Call(...)
codes.
core/vm/instructions.go
Outdated
if interpreter.evm.chainRules.IsEIP4762 { | ||
address := common.Address(scope.Stack.Back(1).Bytes20()) | ||
transfersValue := !scope.Stack.Back(2).IsZero() | ||
|
||
// If value is transferred, it is charged before 1/64th | ||
// is subtracted from the available gas pool. | ||
if transfersValue { | ||
if !interpreter.evm.Accesses.TouchAndChargeValueTransfer(scope.Contract.Address().Bytes()[:], address.Bytes()[:], scope.Contract.UseGas) { | ||
return nil, ErrExecutionReverted | ||
} | ||
} | ||
} | ||
|
||
var err error | ||
interpreter.evm.callGasTemp, err = callGas(interpreter.evm.chainRules.IsEIP150, scope.Contract.Gas, 0, scope.Stack.Back(0)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !scope.Contract.UseGas(interpreter.evm.callGasTemp) { | ||
return nil, ErrOutOfGas | ||
} | ||
if !chargeCallVariantEIP4762(interpreter.evm, scope) { | ||
return nil, ErrExecutionReverted | ||
} |
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.
For CALL
we moved the 1/64 stuff here.
core/vm/instructions.go
Outdated
@@ -762,6 +852,9 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt | |||
} | |||
|
|||
func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { | |||
if !chargeCallVariantEIP4762(interpreter.evm, scope) { |
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.
For the rest of *CALL
, we simply call the helper so keeps being relatively simple.
@@ -1,160 +0,0 @@ | |||
// Copyright 2024 The go-ethereum Authors |
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.
Nothing about this file survived, which makes sense.
The gas*(...)
style of apis can't work with what we need to do, so it makes sense that everything here went away (since was mostly moved to op*(..)
above).
e028e81
to
05e6092
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
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.
just leaving a comment on how to improve the handling of warm costs, going to experiment quickly but if I can't get anything to work today, I'll merge and try to fix it later.
core/vm/instructions.go
Outdated
chargedGas, ok := interpreter.evm.Accesses.TouchBasicData(address[:], false, scope.Contract.UseGas) | ||
if !ok || (chargedGas == 0 && !scope.Contract.UseGas(params.WarmStorageReadCostEIP2929)) { |
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.
hmmm yeah but we could simplify this by adding a chargeWarm
flag to the function, so that we don't need to clutter the code too much here. If you still have to call UseGas
after calling the function you pass it to, this makes the code that much harder to read.
Signed-off-by: Ignacio Hagopian <[email protected]>
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 is just a brain-dumping wall of text, so that I can come back to it when there is pushback by the geth team.
Doesn't LGTM, as I expect there will be substantial pushback when mainlining this, but I tried using the upper bound for myself and can confirm the difficulty of the approach. I still think there is a way to make this PR less intrusive that it already is.
It took me a bit to get to the depth of the reason for this function parameter, but I agree it will be hard to work around it. The approach that consumes all the gas and returns the consumed gas works most of the time, but it doesn't make the difference between something that took all the remaining gas (for which we should still execute the action), and something that went out of gas (for which we should not).
It is possible to share this information and let the calling code consume the gas, which is much cleaner. I am currently trying this approach, and will throw in the towel if I can't make it work in the next couple of hours.
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
16afde4
to
ffc1f2d
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
* fix Signed-off-by: Ignacio Hagopian <[email protected]> * eip4762: push witness fix Signed-off-by: Ignacio Hagopian <[email protected]> --------- Signed-off-by: Ignacio Hagopian <[email protected]>
* fix Signed-off-by: Ignacio Hagopian <[email protected]> * nit Signed-off-by: Ignacio Hagopian <[email protected]> --------- Signed-off-by: Ignacio Hagopian <[email protected]>
* ci: new workflows for testing Signed-off-by: Ignacio Hagopian <[email protected]> * nit Signed-off-by: Ignacio Hagopian <[email protected]> * ci: fix stable consumption Signed-off-by: Ignacio Hagopian <[email protected]> * fix Signed-off-by: Ignacio Hagopian <[email protected]> * more fixes Signed-off-by: Ignacio Hagopian <[email protected]> * more fixes Signed-off-by: Ignacio Hagopian <[email protected]> * update tag Signed-off-by: Ignacio Hagopian <[email protected]> --------- Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
1fe1124
to
b32bca9
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Guillaume Ballet <[email protected]>
…CODEHASH (#508) * avoid extra 100 gas in value-bearing CALL Signed-off-by: Ignacio Hagopian <[email protected]> * missing codeHash write event Signed-off-by: Ignacio Hagopian <[email protected]> * feedback Signed-off-by: Ignacio Hagopian <[email protected]> * Update core/vm/evm.go Co-authored-by: Guillaume Ballet <[email protected]> --------- Signed-off-by: Ignacio Hagopian <[email protected]> Co-authored-by: Guillaume Ballet <[email protected]>
23b6788
to
514b4d5
Compare
Signed-off-by: Ignacio Hagopian <[email protected]>
b4b1ff0
to
9635cc1
Compare
* fix EXTCODESIZE rule for system contract Signed-off-by: Ignacio Hagopian <[email protected]> * 21000 witness target write touch CODEHASH Signed-off-by: Ignacio Hagopian <[email protected]> * fix EXTCODECOPY witness for system contract Signed-off-by: Ignacio Hagopian <[email protected]> * rename and comment Signed-off-by: Ignacio Hagopian <[email protected]> --------- Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
This PR served us well to generate the latest v0.0.6 fixtures, and planting a flag for stable fixtures. Now closing this since we'll end up using #502 which that all bugs were fixed to pass the fixtures. |
This PR does changes to satisfy a new requirement when adding elements to the execution witness.
The new requirement involves only adding elements to the witness if there's sufficient gas for it.
Examples:
TouchCodeChunksRangeAndChargeGas(...)
would add the code chunk to the witness and return the cost to be charged. If we had, say, 100 gas available, this would mean that the user didn't have enough funds to pay for that (i.e: 100<200). But it's too late, and it was already added to the witness.BASIC_DATA
andCODEHASH
of this address. Before this PR, we always addedBASIC_DATA
andCODEHASH
and returned the cost. If the user only had gas to pay forBASIC_DATA
but notCODEHASH
, it would be too late since we have already added both.More generally, if you have
X
of available gas and include something in the witness costY
, withY>X
, then we must not include it in the witness. This is made more apparent when we try to include a wide range of code chunks and only have gas for one but are asked to include 15. Before this PR, we were including 15, not only one.From a stateless client perspective, being this strict is fine. Knowing if you have enough gas to include something in the witness doesn't depend on what you received from the witness (i.e: this PR including less things in the witness doesn't affect it). This is because everything you need to know to be sure that you can add something new to the witness is the access witness state of which branches and code chunks were added.