-
Notifications
You must be signed in to change notification settings - Fork 20.7k
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
eth, eth/tracers: process beacon root before transactions #29402
eth, eth/tracers: process beacon root before transactions #29402
Conversation
…rect state The beacon root when applied in `state_processor.go` is performed right before executing transaction. That means that contract reliying on this value would query the same value found in the block header. In that spirit, it means that any tracing/operation relying on state data which touches transaction must have updated the beacon root before any transaction processing. This PR brings this where I spotted where it was important through `StateAtTransaction` and those using with `StateAtBlock` and transactions. For now I've put the beacon root update outside of `StateAtBlock`. I was reluctant to include it in `StateAtBlock` because technically the state at block doesn't include any state happening "in the block". But everywhere that `StateAtBlock` was used, it felt setting the beacon root was needed, so it would be posssible to change the semantic of the function and run any system pre-hooks. Kinda relevant with PR ethereum#29372
eth/tracers/api.go
Outdated
@@ -916,6 +945,15 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc | |||
} | |||
defer release() | |||
|
|||
if config != nil && config.TxIndex != 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.
Do we still need this? you added it to StateAtTransaction
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.
Yes but it's called conditionally above, that's why I've added it like this:
if config != nil && config.TxIndex != nil {
_, _, statedb, release, err = api.backend.StateAtTransaction(ctx, block, int(*config.TxIndex), reexec)
} else {
statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, nil, true, false)
}
if err != nil {
return nil, err
}
defer release()
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.
Oh no I took the wrong condition, I need to run on the else
clause but copied straight the if ...
one, will invert the logic.
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.
Updated, please take a look.
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.
IMO we should not do this processing if txIdx is not provided. This is how I see it:
- By default eth_call and debug_traceCall will simulate the call as if it's appended to the block. So they take the final state of existing block, and execute transaction on top. But because all parameters such as block number and timestamp are taken from the block itself, it is as if this call was the final transaction of that block. -> in this case we don't need to process beacon root. It has already been done.
- When txIdx is provided, within the same block, we go backwards up to the index. Here because we must take block N-1 and apply all the transactions to get to index the system call processing is necessary. Still block context of execution is block N.
- We should also allow users to simulate call in a potentially future block. They can already change the block context like number timestamp coinbase, etc. Now we need to add the beacon root field to
BlockOverrides
and if that's present do the processing too. Hmmm this creates a tricky scenario: what if I want to simulate N+3. Because the beacon root contract allows me to query all last 8192 beacon roots, I wonder how we should handle this scenario. It is sorta similar to BLOCKHASH. I believe there we default to0x000...000
for hash of "phantom" blocks (e.g. N+2 in this case).
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.
By default eth_call and debug_traceCall will simulate the call as if it's appended to the block. So they take the final state of existing block, and execute transaction on top. But because all parameters such as block number and timestamp are taken from the block itself, it is as if this call was the final transaction of that block. -> in this case we don't need to process beacon root. It has already been done.
I did not check how they retrieved their state actually, I thought the logic was to simply take state at beginning of N+1
since it's the same as the end of N
. All in all, I think we agree that those shouldn't have to do any beacon root processing.
When txIdx is provided, within the same block, we go backwards up to the index. Here because we must take block N-1 and apply all the transactions to get to index the system call processing is necessary. Still block context of execution is block N.
If when txIdx != null
the the logic is:
- Take state at block N-1,
- Re-apply transactions 0 -> txIdx-1
While the context is block N, technically if your re-apply all transactions before txIdx
, then between 1. and 2. above, beacon root must be processed. Indeed, even the first transaction of the block should see the beacon root of block N.
Is that correct?
We should also allow users to simulate call in a potentially future block. They can already change the block context like number timestamp coinbase, etc. Now we need to add the beacon root field to BlockOverrides and if that's present do the processing too. Hmmm this creates a tricky scenario: what if I want to simulate N+3. Because the beacon root contract allows me to query all last 8192 beacon roots, I wonder how we should handle this scenario. It is sorta similar to BLOCKHASH. I believe there we default to 0x000...000 for hash of "phantom" blocks (e.g. N+2 in this case).
Of I wasn't aware it was possible to "simulate" future block. Indeed as soon as you start "to create" new blocks, the beacon root for unknown block doesn't exist. But I'm unsure about the code changes this comment should bring/remove.
Anything I need to change?
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.
Sorry for the rambling. I wanted to have a thorough record of all the cases that could happen. As for this PR the only change I would like to see is regarding block simulations.
Please add beacon root as a field to internal/ethapi.BlockOverrides
. It should be processed in eth_call
and debug_traceCall
optionally to manipulate the beacon root of a future block in the simulation.
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 just realized technically it is possible to override the beacon root for a block through state overrides because the contract is part of the state. So let's skip that request for now.
eth/tracers/api.go
Outdated
@@ -376,6 +376,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed | |||
failed = err | |||
break | |||
} | |||
|
|||
if beaconRoot := block.BeaconRoot(); beaconRoot != 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.
block
is the parent block here. We should use next
.
eth/tracers/api.go
Outdated
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { | ||
context := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) | ||
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) | ||
|
||
core.ProcessBeaconBlockRoot(*block.BeaconRoot(), vmenv, statedb) | ||
} | ||
|
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.
Please move this down, to right before transactions are iterated. Then you can also use the chainConfig
which might contain overrides, rather than api.backend.ChainConfig(),
. It might not make any difference for now, but it is the correct thing to do, IMO.
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.
Yep good idea. Applied these changes locally. Will push when I get access.
Can you please enable push-access to the PR for maintainers? I'd like to push some changes. |
…9402) The beacon root when applied in `state_processor.go` is performed right before executing transaction. That means that contract reliying on this value would query the same value found in the block header. In that spirit, it means that any tracing/operation relying on state data which touches transaction must have updated the beacon root before any transaction processing.
…9402) The beacon root when applied in `state_processor.go` is performed right before executing transaction. That means that contract reliying on this value would query the same value found in the block header. In that spirit, it means that any tracing/operation relying on state data which touches transaction must have updated the beacon root before any transaction processing.
The beacon root when applied in
state_processor.go
is performed right before executing transaction. That means that contract reliying on this value would query the same value found in the block header.In that spirit, it means that any tracing/operation relying on state data which touches transaction must have updated the beacon root before any transaction processing. This PR brings this where I spotted where it was important through
StateAtTransaction
and those using withStateAtBlock
and transactions.For now I've put the beacon root update outside of
StateAtBlock
. I was reluctant to include it inStateAtBlock
because technically the state at block doesn't include any state happening "in the block". But everywhere thatStateAtBlock
was used, it felt setting the beacon root was needed, so it would be posssible to change the semantic of the function and run any system pre-hooks.Kinda relevant with PR #29372