Skip to content
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

Conversation

maoueh
Copy link
Contributor

@maoueh maoueh commented Mar 29, 2024

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 #29372

…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
@@ -916,6 +945,15 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
}
defer release()

if config != nil && config.TxIndex != nil {
Copy link
Contributor

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

Copy link
Contributor Author

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()

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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 to 0x000...000 for hash of "phantom" blocks (e.g. N+2 in this case).

Copy link
Contributor Author

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:

  1. Take state at block N-1,
  2. 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?

Copy link
Contributor

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.

Copy link
Contributor

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.

@@ -376,6 +376,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
failed = err
break
}

if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
Copy link
Contributor

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.

Comment on lines 753 to 759
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)
}

Copy link
Contributor

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.

Copy link
Contributor

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.

@s1na
Copy link
Contributor

s1na commented Apr 16, 2024

Can you please enable push-access to the PR for maintainers? I'd like to push some changes.

@maoueh
Copy link
Contributor Author

maoueh commented Apr 16, 2024

@s1na @holiman I've pushed @s1na commits back in my branch.

@holiman holiman changed the title The beacon root must be processed before any transactions to have correct state eth, eth/tracers: process beacon root before transactions Apr 24, 2024
@holiman holiman merged commit ade7515 into ethereum:master Apr 24, 2024
3 checks passed
@holiman holiman added this to the 1.14.0 milestone Apr 24, 2024
jorgemmsilva pushed a commit to iotaledger/go-ethereum that referenced this pull request Jun 17, 2024
…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.
stwiname pushed a commit to subquery/data-node-go-ethereum that referenced this pull request Sep 9, 2024
…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.
@maoueh maoueh deleted the fix/wrong-state-at-transaction-due-to-beacon-root branch September 27, 2024 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants