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

Update EIP-4788: initial stab at v2 #7456

Merged
merged 19 commits into from
Aug 24, 2023
Merged

Conversation

lightclient
Copy link
Member

@lightclient lightclient commented Aug 4, 2023

Based on the conversation in ACD #167, I wanted to take a look at rewriting this EIP to use a regular contract instead of a precompile. The main motivating factor for this is to reduce the surface of consensus failures. Instead of N implementations of the precompile, we can rely on the same EVM logic as we do for any other contract execution and execute a standardized bytecode implementation of the beacon roots contract.

The changes are as follows:

  • require timestamp input to be exactly 32 bytes
  • revert if timestamp input does not match stored value (instead of returning zeroed word)
  • remove precompile concept, use regular smart contract with provided bytecode

The main open question here is where and how to deploy the code for the beacon roots contract. As I see it, there are three options:

  1. deploy as a normal contract
  2. predeploy the code via enshirement during the hard fork to the same address as the original precompile (0x0b)
  3. predeploy the code to an arbitrary address in the address space (maybe a create/create2 calc'd address?)

I prefer option 1) as it aligns with the original motivation of the v2 change, which is to minimize the surface of consensus failures and reuse EVM infrastructure where possible. The addresses in the EIP were generated using this script.

--

I have also taken a stab at writing an assembly version of the beacon roots contract. It can be found here: https://github.com/lightclient/4788asm

I think that is roughly the minimal bytecode representation of the logic we wish to enshrine.

@github-actions github-actions bot added c-update Modifies an existing proposal s-draft This EIP is a Draft t-core labels Aug 4, 2023
@eth-bot
Copy link
Collaborator

eth-bot commented Aug 4, 2023

✅ All reviewers have approved.

@eth-bot eth-bot changed the title 4788: initial stab at v2 Update EIP-4788: initial stab at v2 Aug 4, 2023
@eth-bot eth-bot added the a-review Waiting on author to review label Aug 4, 2023
Copy link
Contributor

@djrtwo djrtwo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

"input": "0x605a8060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604257602036146024575f5ffd5b620180005f350680545f351415603d576201800001545f525b60205ff35b42620180004206555f3562018000420662018000015500",
"accessList": [],
"v": "0x0",
"r": "0x539",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this method with a fake signature and stuff...

Can we not just drop bytecode X at address Y at the fork?
e.g. like 1011 proposed -- https://eips.ethereum.org/EIPS/eip-1011#deploying-casper-contract

I think it's more straight forward to just place it exactly where we want it, rather than trying to use a TX of sorts but without gas or signature verification which requires a lot more exceptional logic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this style of contract creation is not tied to any specific initcode like create2 is, the synthetic address is cryptographically bound to the input data of the transaction (e.g. the initcode).

and then we don't have to think about things like this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than trying to use a TX of sorts but without gas or signature verification which requires a lot more exceptional logic

To be clear, this tx does still abide by gas and signature verification. The signature is constructed arbitrarily and therefore the sender is a one-time sender address (pk is not known).

--

In an effort to minimize protocol baggage, I think it is better to deploy the contract using standard methods. Once we are satisfied with the bytecode we'd like to deploy, it could be deployed by anyone. We simply have to agree on the address at which it is deployed. It doesn't have to be a synthetic tx, although I do slightly prefer this method as it is permissionless (anyone can fund the account and submit the tx).


#### Block processing
The beacon roots contract has two operations: `get` and `set`. The input itself is not used to determine which function to execute, for that the result of `caller` is used. If `caller` is equal to `SYSTEM_ADDRESS` then the operation to perform is `set`. Otherwise, `get`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, funny. seems okay though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This somewhat seems to imply that this contract has an ABI (which it has not, at least not in current assembly). I would really like to do the ABI-like approach as in lightclient/4788asm#5 since this will also be very helpful for solidity users (you can now BeaconRootContract(address).get(timest).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a bad idea to enshrine solidity ABI behavior. It is ubiquitous today, but in the future I expect other language will have different calling conventions. Solidity should directly support this contract like they do for ecrecover.

EIPS/eip-4788.md Outdated Show resolved Hide resolved
EIPS/eip-4788.md Outdated Show resolved Hide resolved
EIPS/eip-4788.md Outdated
The 32 bytes of the `parent_beacon_block_root` (as provided) are the value to write behind the `root_index`.
* If `caller` is not equal to `SYSTEM_ADDRESS`, the contract must revert.
* Caller provides the parent beacon block root as calldata to the contract.
* Set the storage value at `header.timestamp % HISTORICAL_ROOTS_MODULUS` to be `header.timestamp`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of want to make the case for avoiding a system-TX and just setting these storage values at the top of the block. Then the code is just get and we don't have to test/specify how to do system-TXs

weaker held opinion than the deploy method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we're going to use a standard contract we should embrace it and allow it to have storing functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrote this already in discord, posting here too for visibility.

With the spec being 'system call', it means a reference client can just do a simple call. A client implementor can still choose not to, and instead bypass the whole call and update the values directly.

If the eip standardizes 'direct update', however, and omits the system-update path from the contract, then we remove all optionality, and force more special-case code into clients

Therefore I think the 'system call' approach is the best way forward.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the EIP should mention that "direct update" is a valid/possible client implementation/optimization detail.

Copy link
Contributor

@g11tech g11tech Aug 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

system-tx would be a good generic functionality to have in long term imo

EIPS/eip-4788.md Outdated
"maxPriorityFeePerGas": "0x9c7652400",
"maxFeePerGas": "0xe8d4a51000",
"value": "0x0",
"input": "0x605a8060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604257602036146024575f5ffd5b620180005f350680545f351415603d576201800001545f525b60205ff35b42620180004206555f3562018000420662018000015500",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might put many of these values behind a VAR -- e.g. input and the stubbed r, s

EIPS/eip-4788.md Show resolved Hide resolved
* the call must execute to completion, therefore the available gas can be considered as infinite
* the call does not count against the block's gas limit
* the call does not follow the [EIP-1559](./eip-1559.md) burn semantics - no value should be transferred as part of the call
* if no code exists at `BEACON_ROOTS_ADDRESS`, the call must fail silently
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the prior section specifies when to deploy the bytecode, this bullet will become unnecessary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in other thread, I don't think it is important to specify the deployment. But I suppose the bullet is unnecessary because that is the semantics of an evm call anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bullet is unnecessary, other than as clarification: any non-value call to account with non-existing code has no discernible effect on state.
If you prefix the bullet with Note: or Clarification:, it becomes apparent that this bullet does not intend to add any behavioural changes.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@holiman But shouldn't it then rather say:

  • if no code exists at BEACON_ROOTS_ADDRESS, the call must succeed as with any other account without code

I find "fail" a bit misleading here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree: fail or succeed depends on your point of view. Whole bullet is moot and should not exist

EIPS/eip-4788.md Outdated Show resolved Hide resolved
EIPS/eip-4788.md Outdated Show resolved Hide resolved
EIPS/eip-4788.md Outdated
Comment on lines 215 to 216
"maxPriorityFeePerGas": "0x9c7652400",
"maxFeePerGas": "0xe8d4a51000",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do these gas values come from?

to echo what others have said, it seems fragile to enshrine a "synthetic transaction"

easier to just drop byte code a la EIP-1011

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just arbitrary values that would be included on mainnet. It isn't really enshrining the tx. I would think more of it as two steps: 1) deploy contract 2) set address in clients to call as part of pre block processing. A synthetic tx is just one way of deploying the contract. I think it is best because it is clear, transparent, and permissionless. We could just as easily have someone hand deploy the contract before the fork (now even, if the byte code is ready) and then set the address in client configurations.

Co-authored-by: Alex Stokes <[email protected]>
@holiman
Copy link
Contributor

holiman commented Aug 7, 2023

I also think that the 'get'-path should have an abi signature: getBeaconRoot(uint256), thus takign 36 bytes input.
It can either check the abi, or if we for some reason want to, we can omit the signature-check and ignore the first four bytes. But I don't see any reason to be overly "clever".

The ABI encoding is ubiquitous, not merely solidity, it's the calling-convention used in the ethereum application layer, and I think it makes sense to have the precompile use that callpattern.

With abi, even with zero compiler support, this is possible:

root = BeaconRoot( address ).getBeaconRoot( tstamp );

As opposed to something like this:

(bool ret, bytes memory data) = address.call(bytes.concat(tstamp));

(I'm happy to update that example, I'm not really sure of the syntax for doing a call that way. )

@holiman
Copy link
Contributor

holiman commented Aug 8, 2023

In the original EIP, the contract was a precompile, and this 'subsidized' such that for call-purposes, the address is considered already included in the transaction access list. The difference is, as far as I can tell, 700 instead of 2600 gas.

I don't really care which way we go, but the "least changes to the original eip" route suggests that the hardfork should add the address to the access list.

EIPS/eip-4788.md Outdated Show resolved Hide resolved
Co-authored-by: Martin Holst Swende <[email protected]>
@yperbasis
Copy link
Member

yperbasis commented Aug 9, 2023

In the original EIP, the contract was a precompile, and this 'subsidized' such that for call-purposes, the address is considered already included in the transaction access list. The difference is, as far as I can tell, 700 instead of 2600 gas.

I don't really care which way we go, but the "least changes to the original eip" route suggests that the hardfork should add the address to the access list.

Yes, adding the address to the access list (from Cancun) makes sense, similar to how EIP-3651 did it for coinbase in Shanghai.

@lightclient
Copy link
Member Author

@yperbasis there was some discussion on discord about warming the address. After your comment I added it to this PR, but I now realize the comparison with EIP-3651 isn't quite correct because the EIP-4788 contract is only touched once before any transactions execute.

me from discord:

so we treat it more like a precompile - but naively adding it to the list of precompiled addresses causes issues with vm calls because we usually check if the call destination is a precompile and intercept it before creating an actual EVM frame (which we do want in the case of 4788 contract)
so we'd have to add the 4788 address in some different way, check if cancun, etc

Anyways, to minimize complexity and special handling I would prefer to not add this to the warm list.

EIPS/eip-4788.md Outdated

The precompile costs `G_beacon_root` gas to reflect the two (2) implicit `SLOAD`s from the precompile's state.
Client may decide to omit an explicit EVM call and directly set the storage values.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we have to decide whether we want to do the EVM call or to directly set the storage values. I understand that when we set the value directly and the the account is empty we do remove the account at the end of the block, but there are the edge case where someone puts some eth into the account, which means it is not empty anymore.
I would prefer to set the values directly, as that is 10x faster and we do not require all clients to implement the system operation.

Copy link
Contributor

@holiman holiman Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we have to decide whether we want to do the EVM call

The consensus-definition is that an EVM call is made. A client implementor may choose to optimize this, and do a direct update instead. This choice may cause incompatibilities in non-mainnet scenarios, e.g. if the code is replaced with different code.

I understand that when we set the value directly and the the account is empty we do remove the account at the end of the block, but there are the edge case where someone puts some eth into the account, which means it is not empty anymore.

No, there is now code on the account, so it would not be removed, since it is not empty, according to the definition of empty.

I would prefer to set the values directly, as that is 10x faster and we do not require all clients to implement the system operation.

You are at liberty to do so, since "we do not require all clients to implement the system operation" -- however, the consensus-correct behaviour, in any given scenario, is determined by how the system-call would execute

IMO this is a pretty good middle ground.

EIPS/eip-4788.md Outdated Show resolved Hide resolved
EIPS/eip-4788.md Outdated Show resolved Hide resolved
@jaydub74
Copy link

jaydub74 commented Aug 21, 2023 via email

Co-authored-by: Martin Holst Swende <[email protected]>
EIPS/eip-4788.md Outdated Show resolved Hide resolved
@holiman
Copy link
Contributor

holiman commented Aug 21, 2023

BEACON_ROOTS_ADDRESS should be 0xBeAC00541d49391ED88ABF392bfC1F4dEa8c4143 now AFAIK

@yperbasis nope, should be 0xbEac00dDB15f3B6d645C48263dC93862413A222D. Sorry for the mixups

holiman
holiman previously approved these changes Aug 21, 2023
Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

EIPS/eip-4788.md Outdated

timestamp_reduced = to_uint64_be(timestamp) % HISTORICAL_ROOTS_MODULUS
timestamp_index = to_uint256_be(timestamp_reduced)
* the call must execute to completion, therefore the available gas can be considered as infinite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?
I would rather specify some hard limit, like 30M gas, as suggested by @yperbasis . I suspect most actual implementation will need to set a limit anyway.

yperbasis added a commit to erigontech/erigon that referenced this pull request Aug 23, 2023
#8055)

[devnet-8](https://notes.ethereum.org/@ethpandaops/dencun-devnet-8)
doesn't include ethereum/EIPs#7431 since it's
rendered obsolete by upcoming ethereum/EIPs#7456
(to be implemented by PR #8038).

This reverts PR #7952.
@holiman
Copy link
Contributor

holiman commented Aug 24, 2023

Why no merge? I thought this was mostly agreed upon on by ACD?

@yperbasis
Copy link
Member

Please change the gas limit to 30M. Geth, Erigon, and probably Nethermind have implementations with the gas limit of 30M.

@holiman
Copy link
Contributor

holiman commented Aug 24, 2023

I agree with @yperbasis . To clarify what I said earlier:

I suspect most actual implementation will need to set a limit anyway.

Doing metered calls is the natural way that clients implement calls. Doing an "infinite-gas" call, or "unmetered call" is not a common thing, and it wouldn't surprise me is there are clients which do not even support unmetered calls. So some limit needs to be set, in those cases, and in many cases maxuint64 is the closes to infinite gas there is. So let's just set it to something and avoid the confusion.

yperbasis added a commit to erigontech/erigon that referenced this pull request Aug 24, 2023
See ethereum/EIPs#7456 &
ethereum/go-ethereum#27849. Also set the gas
limit for system calls to 30M (previously 2^64-1), which is in line with
the [Gnosis
spec](https://github.com/gnosischain/specs/blob/master/execution/withdrawals.md#specification),
but should be doubled checked for Gnosis Chain.
@ralexstokes
Copy link
Member

Why no merge? I thought this was mostly agreed upon on by ACD?

I see two outstanding changes we want to make:

  1. Set gas limit of the "system execution" to 30M
  2. change HISTORICAL_ROOTS_MODULUS to HISTORY_BUFFER_LENGTH (following a review w/ @djrtwo )

@ralexstokes
Copy link
Member

ralexstokes commented Aug 24, 2023

here's a PR to address the gas limit:

Set gas limit of the "system execution" to 30M

lightclient#4

and for the name change:

lightclient#5

change name of length of the EIP-4788 ring buffer
Copy link
Member

@ralexstokes ralexstokes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of work has gone into this update, thanks everyone for taking a look and contributing!

going to go ahead and merge, if we need further tweaks we can update in future PRs

@eth-bot eth-bot enabled auto-merge (squash) August 24, 2023 21:07
Copy link
Collaborator

@eth-bot eth-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All Reviewers Have Approved; Performing Automatic Merge...

@eth-bot eth-bot merged commit 46f8d5b into ethereum:master Aug 24, 2023
10 checks passed
@yperbasis
Copy link
Member

@lightclient Section "Gas cost of precompile" is obsolete now and should be removed. Also, "precompile" is still mentioned in a couple of other places in the Rationale.

AskAlexSharov pushed a commit to erigontech/erigon that referenced this pull request Sep 6, 2023
AskAlexSharov pushed a commit to erigontech/erigon that referenced this pull request Sep 6, 2023
See ethereum/EIPs#7456 &
ethereum/go-ethereum#27849. Also set the gas
limit for system calls to 30M (previously 2^64-1), which is in line with
the [Gnosis
spec](https://github.com/gnosischain/specs/blob/master/execution/withdrawals.md#specification),
but should be doubled checked for Gnosis Chain.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-review Waiting on author to review c-update Modifies an existing proposal s-draft This EIP is a Draft t-core
Projects
None yet
Development

Successfully merging this pull request may close these issues.