-
Notifications
You must be signed in to change notification settings - Fork 20.1k
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/vm: set basefee to 0 internally on eth_call #28470
Conversation
core/vm/eips.go
Outdated
if interpreter.evm.Config.NoBaseFee { | ||
scope.Stack.push(new(uint256.Int)) |
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.
Instead of checking for NoBaseFee
in the production opcode, can't we just set the interpreter.evm.Context.BaseFee
to a new(uint256.Int)
in the NoBaseFee
-situation?
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 did consider that too. The only catch is that some code paths feed us the context directly, and we're not copying currently, so setting the basefee would modify the passed in context. We'd need to add some copying to avoid that.
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.
Like this
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 44d4ba9cff..35f0a3f7c2 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -215,12 +215,8 @@ func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// opBaseFee implements BASEFEE opcode
func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
- if interpreter.evm.Config.NoBaseFee {
- scope.Stack.push(new(uint256.Int))
- } else {
- baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee)
- scope.Stack.push(baseFee)
- }
+ baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee)
+ scope.Stack.push(baseFee)
return nil, nil
}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 2c6cc7d484..1f84ff7660 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -133,6 +133,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
}
+ if config.NoBaseFee {
+ evm.Context.BaseFee = new(big.Int)
+ }
evm.interpreter = NewEVMInterpreter(evm)
return evm
}
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 did consider that too.
Ah I didn't see this comment. But I don't think you are quite correct -- the blockCtx
is passed by value, not ref, in the path I used.
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.
Yeah, I think Martin's approach is better, BaseFee is an environment context.
We can also leave a TODO for BlobBaseFee
, we might face the same problem for this opcode too.
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 switch to Martin's approach, but we need to be aware that BaseFee form the context is used in many locations, each potentially becoming a footgun with 0.
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.
@rjl493456442 Wrt the blob fee, I think we can piggie-back on the same flag. BaseFee, BlobBaseFee, the concept is the same and setting it to zero should also be the same.
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.
Ugh, no, Martin's fix isn't good (neither is mine).
Currently the logic is that we only disable base fee checks if 1) NoBaseFee is set and 2) gas prices are 0. Both our suggested fixes unilaterally nuke it out always, which is bad.
Martin's proposal cannot work because you don't know during construction time if tx will be run with or without gas price. My solution could work if I pulled the gas price up too, but it's a bit ugh. Maybe a cleaner solution is to set/rest the field before actually executing the bytecode for each tx individually. .Will look into that.
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 Martin's does work. The EVM is reconstructed for each tx.
I thought the whole idea is that basefee is never nil.
In my opinion the issue is not with the opcode then but with the way we construct the evm.Context without a valid basefee edit: Ahh nvm, now I get it, thanks Martin |
Yeah the problem isn't that it's |
core/state_transition.go
Outdated
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call) | ||
if !st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() > 0 { |
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.
The way this became changed now, the check is skipped if msg.BlobGasFeeCap == 0
. And msg
comes from the transaction we're about to execute. In other words, we omit a check depending on either
- If a debug-setting is enabled, OR
- A certain field in a transaction is zero.
It's not obvious to me why it is safe to do number 2)
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.
Shit, good catch. Should have been an ||
core/state_transition.go
Outdated
@@ -327,7 +327,7 @@ func (st *StateTransition) preCheck() error { | |||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { | |||
if st.blobGasUsed() > 0 { | |||
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call) | |||
if !st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() > 0 { | |||
if !st.evm.Config.NoBaseFee || msg.BlobGasFeeCap.BitLen() > 0 { |
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 had to, again, look at it for a while before my brain parsed it out fully. Yeah it's ok now, but I'd prefer having it rewritten for clarity:
// skip checks if basefee was disabled and blobBaseFee is zero (eth_call)
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0
if !skipCheck {
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.
Fair enough
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.
Pushed this fix and also added it to the 1559 basefee
I've removed the check for blobfee > 2^256. We don't need it because we use uint256 in blobtxs, so it cannot happen, apart from the last mile converstion to big.Int internally. We should start converting more things to uint256 as it would get rid of these cornercases. |
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.
LGTM
* core/vm: set basefee to 0 internally on eth_call * core: nicer 0-basefee, make it work for blob fees too * internal/ethapi: make tests a bit more complex * core: fix blob fee checker * core: make code a bit more readable * core: fix some test error strings * core/vm: Get rid of weird comment * core: dict wrong typo
)" This reverts commit 0a87754.
)" This reverts commit 0a87754.
I wanted to note that this change broke gas estimation in our code when called without a gas price set for the following uint256 submissionFee = calculateRetryableSubmissionFee(data.length, block.basefee);
function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee)
public
view
override(AbsInbox, IInboxBase)
returns (uint256)
{
// Use current block basefee if baseFee parameter is 0
return (1400 + 6 * dataLength) * (baseFee == 0 ? block.basefee : baseFee);
} When the transaction is estimated, I'm wondering whether or not you considered resolving the As I mentioned we have a workaround, but this lead to some breakage so I thought I'd see if there was interest in alternative options. |
This will lead to bad UI. As accounts would need funds for fee so state overrides always have to be used. However IF that's ok for you you can already set the gasPrice field of the transaction to the current block's base fee or higher and it should work. A crucial note here is: block base fee is set to 0 only if tx.gasPrice is also 0. |
@hkalodner As @s1na also mentioned, you should really be doing gas pricing first and then estimation. Estimation needs good prices anyway and if the prices are non zero, then estimate will also use the real basefee. |
E.g. in our abigen generated code we do tx.gasprice = eth_gasPrice; and then do eth_estimateGas. |
* core/vm: set basefee to 0 internally on eth_call * core: nicer 0-basefee, make it work for blob fees too * internal/ethapi: make tests a bit more complex * core: fix blob fee checker * core: make code a bit more readable * core: fix some test error strings * core/vm: Get rid of weird comment * core: dict wrong typo
* core/vm: set basefee to 0 internally on eth_call * core: nicer 0-basefee, make it work for blob fees too * internal/ethapi: make tests a bit more complex * core: fix blob fee checker * core: make code a bit more readable * core: fix some test error strings * core/vm: Get rid of weird comment * core: dict wrong typo
After shipping 1559, we've added support for running eth_call (and it's derivatives) with a
NoBaseFee
vm.config flag. This is needed because people usually don't set gas prices on eth calls, so all the usual checks around maxfee > basefee, etc cannot pass. https://github.com/ethereum/go-ethereum/blob/master/core/state_transition.go#L293One place we didn't handle is the basefee opcode when running the EVM. Currently that returns a non-zero number, even if NoBaseFee was requested. This poses an interesting issue:
This above Solidity code reverts if ran with a gasprice of 0. That cannot happen for transaction execution, so the live network isn't impacted. And accessing the basefee in an eth_call seems pointless so people don't really hit it.
However, running a gas estimation on the above code will always fail if the user does not provide a gas price to the estimator. Whilst it really makes sense to provide a gas price (since tx execution might depend on it), if we allow 0 priced calls/estimations, then we must set the basefee to 0 too. Otherwise we create an impossible scenario (basefee > total fee), and all bets are off when the execution starts out with junk state.
FWIW, it would also make sense to consider if we want to disallow gas estimation with 0 price, since it feels wrong. My gut feeling is yes, but nobody knows what we're breaking (especially since ethclient itself fogot to forward the gas fields until this week).