-
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: implement EIP-4788 BeaconRoot precompile #27289
core: implement EIP-4788 BeaconRoot precompile #27289
Conversation
2662722
to
0c446f1
Compare
53d13db
to
2bd0102
Compare
|
||
// BeaconRoot was added by EIP-4788 and is ignored in legacy headers. | ||
BeaconRoot *common.Hash `json:"beaconRoot" rlp:"optional"` |
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.
Adding it like this delegates the responsibility of "when to start storing data for EIP-4788" entirely to the CL layer. When/of they start including a beaconroot, we start storing it into the state.
IMO we should
- only accept BeaconRoot after the fork time,
- require the BeaconRoot after the fork time,
As for RLP, similarly, we need to be careful, and strict.
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.
Is the order between BeaconRoot and DataGasUsed specified? Both are activated in the same fork...
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 don't think its specified yet
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.
it should be at the end of all the 4844 stuff; I was trying to specify this abstractly to avoid thrash from other EIPs but I can see how this is unclear so I made it explicit:
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.
Couple nits; really nice overall though.
state.SetState(historicalStorageAddress, rootKey, root) | ||
} | ||
|
||
func calcBeaconRootIndices(header *types.Header) (timeKey, time, rootKey, root common.Hash) { |
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 function is kinda superfluous. It's just doing a bunch of one-line conversions. I think it is actually more comprehensible all in the same function, otherwise takes a bit of time to realize almost not computation is happening here.
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 think Martin proposed spinning this out, so it can be tested easier, I'll add some test cases, so it actually makes sense to move this
"github.com/ethereum/go-ethereum/params" | ||
) | ||
|
||
func TestCalcBeaconRootIndices(t *testing.T) { |
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 a good start. Mind if I move the actual test vectors of it out to a json
file, so we can share it cross-client?
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.
Sure would be nice to have some more test cases
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
@ralexstokes have you considered passing the entirety of the For some additional context, I've been working on implementing a proof of concept for remote static call for Optimism (ethereum-optimism/ecosystem-contributions#76). This involves implementing a precompile in It would be useful to be able to access There might be very good reasons to not do this, but just curious if it would make sense. |
core/vm/contracts.go
Outdated
@@ -121,6 +125,19 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ | |||
common.BytesToAddress([]byte{18}): &bls12381MapG2{}, | |||
} | |||
|
|||
var PrecompiledContracts4788 = map[common.Address]PrecompiledContract{ |
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.
Why this instead of in Cancun? Now we have cancun with KZG but no beaconnroot, and we have this with beaconroot but no KZG
core/vm/contracts.go
Outdated
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, | ||
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, | ||
common.BytesToAddress([]byte{9}): &blake2F{}, | ||
params.BeaconRootsStorageAddress: &beaconRoot{}, |
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.
Given that all other contracts are hard coded with value addresses, I'd rather stick to that vs adding a param here
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 one is slightly different though as it needs to be accessed in both consensus/misc/eip4788.go
and in core/vm/contracts.go
. It's weird it is the only non-hardcoded one here though, but is it better to hardcode in both places?
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.
It should be consistent. If there's a need to move the beaconRoot into a param, then move all of them imo.
@@ -160,6 +164,9 @@ const ( | |||
RefundQuotient uint64 = 2 | |||
RefundQuotientEIP3529 uint64 = 5 | |||
|
|||
HistoricalRootsModulus uint64 = 98304 // Limits how many historical roots are stored by EIP-4788 |
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'd recommend starting to prefix constants belonging to the same EIP/fork/whatnot since we already have tons of params and it's very hard to see what goes where. I.e. BeaconRootHistoricalRootsModulus
@@ -34,12 +34,16 @@ import ( | |||
"golang.org/x/crypto/ripemd160" | |||
) | |||
|
|||
type StateReader interface { |
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.
Need docs
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use | ||
Run(input []byte) ([]byte, error) // Run runs the precompiled contract | ||
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use | ||
Run(statedb StateReader, input []byte) ([]byte, error) // Run runs the precompiled contract |
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.
Nitpick, we have statedb
and stateDB
naming in this single PR. Can we just have state
instead?
The 4788 EIP needs to be amended, currently geth is doing something which is correct, but definitely not intended behaviour. Since the beaconroot trie account is
It is touch-deleted at the end of every block. So it works "fine" during a block, but then it becomes wiped at the end of the block. Discussion is happening regarding how to get around this behaviour. One example candidate solution is to amend the eip so that:
I see no point in merging this EIP until this has been resolved. |
I have now updated this PR to set the nonce to |
@@ -71,6 +71,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg | |||
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { | |||
misc.ApplyDAOHardFork(statedb) | |||
} | |||
if p.config.IsCancun(blockNumber, block.Time()) { |
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.
should this be in Prepare
in the consensus interface?
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 clarify, @lightclient, I don't understand what you are suggesting
The original PR did that, but I suggested passing in a smaller interface. For downstream projects, it should be a pretty small change to just take the method(s) from EVM that you need, and add them to that interface. The big bulky change of passing the thing into all precompiles is already done via this PR. Hope that's good enough for you |
func (c *beaconRoot) Run(stateDB StateReader, input []byte) ([]byte, error) { | ||
if len(input) != common.HashLength { | ||
return nil, errors.New("invalid input length") | ||
} |
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 (subtly wrong). From the EIP:
If the input is more than 32 bytes, the precompile only takes the first 32 bytes of the input buffer and ignores the rest. If the input is less than 32 bytes, the precompile should revert.
cc @shemnon will this make the next testnet go boom? Or does besu contain the same error?
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.
we can also update the EIP here, if that's simpler
I don't know if there is precedent for precompile input handling one way or the other...
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.
Besu hasn't implemented it yet. @pinges is supposed to be implementing that.
This change implements "EIP 4788 : Beacon block root in the EVM". It implements version-2 of EPI-4788, main difference being that the contract is an actual contract rather than a precompile, as in #27289.
Superseded by #27849 |
…27849) This change implements "EIP 4788 : Beacon block root in the EVM". It implements version-2 of EPI-4788, main difference being that the contract is an actual contract rather than a precompile, as in ethereum#27289.
EIP 4788 : Beacon block root in the EVM
WIP implementation of EIP-4788 for discussion on the eip
cc @ralexstokes