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

Implement EIP 7685 #3372

Merged
merged 34 commits into from Apr 30, 2024
Merged

Implement EIP 7685 #3372

merged 34 commits into from Apr 30, 2024

Conversation

acolytec3
Copy link
Contributor

@acolytec3 acolytec3 commented Apr 25, 2024

Implements EIP-7685

Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

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

Left some comments. I think we need a sorting mechanism for any EIP which implements this.

For 6110:
Each deposit accumulated in the block must appear in the EIP-7685 requests list in the order they appear in the logs

For 7002:
It should be in the order it is pulled out of the queue (FIFO)

So this is not lexicographic, but in the order of how they are executed or in whatever order they get added to the FIFO queue.

packages/block/src/block.ts Outdated Show resolved Hide resolved
packages/block/src/block.ts Show resolved Hide resolved
@g11tech
Copy link
Contributor

g11tech commented Apr 28, 2024

Left some comments. I think we need a sorting mechanism for any EIP which implements this.

We discussed this on call (@acolytec3 and me), 7685 should have a code block to gather the requests from other EIPs in the sequence ordered by type and then there should be an exact match of requests in blockbody/request root.

@@ -285,6 +285,12 @@ export async function runBlock(this: VM, opts: RunBlockOpts): Promise<RunBlockRe
preimages: result.preimages,
}

if (this.common.isActivatedEIP(7685)) {
Copy link
Contributor

@g11tech g11tech Apr 28, 2024

Choose a reason for hiding this comment

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

we can do request field existence check even before we run the block

Also we need to pop requests out before we verify stateroot (and call verkle verify state): i think in apply block with a fn called accumulateRequests or something (and use the same fn in buildblock's build, which should return requests in result which we should return in above results:RunBlockResult


// TODO: Add in code to accumulate partial withdrawals (EIP-7002)

if (requests.length > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ideally we should just make sure that we pop and push the requests in the type sequence` but i guess we can let it be for the moment

Copy link
Member

Choose a reason for hiding this comment

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

I agree that this check is not necessary if we check the requests in the right order above.

g11tech
g11tech previously approved these changes Apr 29, 2024
Copy link
Contributor

@g11tech g11tech left a comment

Choose a reason for hiding this comment

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

lgtm

@acolytec3 acolytec3 marked this pull request as ready for review April 29, 2024 16:51
@acolytec3 acolytec3 requested a review from g11tech April 29, 2024 16:51
g11tech
g11tech previously approved these changes Apr 29, 2024
g11tech
g11tech previously approved these changes Apr 29, 2024
Copy link
Member

@jochem-brouwer jochem-brouwer left a comment

Choose a reason for hiding this comment

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

Looks good so far! I got a few comments. I like the tests 👍

if (clRequests !== undefined && clRequests.length > 1) {
for (let x = 1; x < clRequests.length; x++) {
if (clRequests[x].type < clRequests[x - 1].type)
throw new Error('requests are not sorted in ascending order')
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this check be moved into the constructor? It is possible to create a block which does not satisfy this condition by using fromValuesArray or fromRlpSerializedBlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair point. Will move

@@ -286,4 +302,5 @@ export type ExecutionPayload = {
parentBeaconBlockRoot?: PrefixedHexString | string // QUANTITY, 64 Bits
// VerkleExecutionWitness is already a hex serialized object
executionWitness?: VerkleExecutionWitness | null // QUANTITY, 64 Bits, null implies not available
// TODO: Determine if we need the requestsRoot here
Copy link
Member

Choose a reason for hiding this comment

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

I would assume so!

Copy link
Contributor Author

@acolytec3 acolytec3 Apr 30, 2024

Choose a reason for hiding this comment

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

@g11tech , agree?

Copy link
Member

Choose a reason for hiding this comment

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

I found this in the CL specs: https://github.com/ethereum/consensus-specs/blob/7bf43d1bc4fdb91059f0e6f4f7f0f3349b144950/specs/electra/beacon-chain.md#executionpayload

It is not updated yet (it is on old 6110 / 7002 which did not yet use 7685), but I would strongly assume that thus the requestsRoot is added.

packages/block/test/eip7685block.spec.ts Show resolved Hide resolved
packages/common/src/eips.ts Show resolved Hide resolved
packages/util/src/requests.ts Show resolved Hide resolved
packages/vm/src/buildBlock.ts Outdated Show resolved Hide resolved
packages/vm/src/buildBlock.ts Outdated Show resolved Hide resolved
)
} else {
if (this.common.isActivatedEIP(7685)) {
const valid = await block.requestsTrieIsValid()
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we do this check before running all txs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the generate option is called, the requestsRoot passed in the block will be disregarded and the correct one derived so checking before we validate the generate flag doesn't make sense. We validate it here if we are re-executing a block to ascertain its validity.

Copy link
Member

Choose a reason for hiding this comment

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

If generate is called we generate the requestsRoot and put that in the block (this is correct). However, this check is done in case we are not using generate (so generate is false). The block.requestsTrieIsValid is called on whatever block is provided - this itself, should not change (so this can be done before running any txs). However what you also want to check is that the block.genRequestsTrieRoot(requests) (so not block.requests, but the requests we just have accumulated in accumulateRequests(this)) equals the provided block.header.requestsRoot. If this is not the case, then the block thus has a different requests root and the block is incorrect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, that makes sense. Will push an update shortly.

packages/vm/src/runBlock.ts Outdated Show resolved Hide resolved

// TODO: Add in code to accumulate partial withdrawals (EIP-7002)

if (requests.length > 1) {
Copy link
Member

Choose a reason for hiding this comment

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

I agree that this check is not necessary if we check the requests in the right order above.

Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

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

Looks absolutely amazing 🤩, also agree with Jochem, test cases added are really fabulous and already covering such an amazingly broad scope of the added functionality!

Some small comments but nothing major to be added from my review here.


export class CLRequest implements CLRequestType {
type: number
bytes: Uint8Array = new Uint8Array()
Copy link
Member

Choose a reason for hiding this comment

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

Short nit: I guess new Uint8Array() is not needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably not. It was just easier than logging out random bytes to the console and then putting it in. Will adjust.

Copy link
Member

Choose a reason for hiding this comment

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

Guess comment is misplaced here? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, meant to put that one on your question about randomBytes. This is why I shouldn't respond to code reviews on mobile.

@@ -417,6 +472,7 @@ export class Block {
uncleHeaders: BlockHeader[] = [],
withdrawals?: Withdrawal[],
opts: BlockOptions = {},
requests?: CLRequest[],
executionWitness?: VerkleExecutionWitness | null
) {
Copy link
Member

Choose a reason for hiding this comment

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

Just to note here that we need to be a bit careful with such parameter order switches (so: putting requests before executionWitness) for backwards compatibility reasons (we already have released a block library version with executionWitness included. Guess in this case it should be ok though and does make sense to have this in the "correct" order from the beginning.

@@ -140,6 +140,8 @@ const jsonRpcBlock = async (
blobGasUsed: header.blobGasUsed,
excessBlobGas: header.excessBlobGas,
parentBeaconBlockRoot: header.parentBeaconBlockRoot,
requestsRoot: header.requestsRoot,
requests: block.requests?.map((req) => bytesToHex(req.serialize())),
Copy link
Member

Choose a reason for hiding this comment

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

Side note: I do not see executionWitness in here? Is this forgotten as well?

Copy link
Member

Choose a reason for hiding this comment

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

(in doubt not for this PR)

status: Status.Draft,
// TODO: Set correct minimum hardfork
minimumHardfork: Hardfork.Cancun,
requiredEIPs: [],
Copy link
Member

Choose a reason for hiding this comment

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

Guess we can (minimally) add 3675 (consensus to PoS Upgrade) here if we want to.

Yeah, but also not so relevant eventually.


// TODO: Add in code to accumulate deposits (EIP-6110)

// TODO: Add in code to accumulate partial withdrawals (EIP-7002)
Copy link
Member

Choose a reason for hiding this comment

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

Simply for my understand: so here a call into read_withdrawal_requests() from https://eips.ethereum.org/EIPS/eip-7002#execution-layer ("System Call" section) would take place, right?

Side question for @acolytec3, since you likely have better imagination on this: how does this affect execution with e.g. RPCStateManager? Does this still work (do not yet get the pieces together in my head)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To the question for me, I don't "think" this should have any impact. We already track contract state updates in a local cache in the RPCStateManager so it should keep track of any changes we make in the processing of requests.

Copy link
Member

Choose a reason for hiding this comment

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

@holgerd77 Yes exactly here the call to read_withdrawal_requests is made. For 6110 I am not sure if all the logic is in here or not (but I am not super deep into 6110, @scorbajio might have better insights here)

Copy link
Contributor

Choose a reason for hiding this comment

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

For 6110, deposits are retrieved from the block receipt logs for any logs whose address is DEPOSIT_CONTRACT_ADDRESS and also from the block body requests field for any requests that are typed as deposit requests.

packages/vm/test/api/EIPs/eip-7685.spec.ts Outdated Show resolved Hide resolved
Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

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

Will take this in now to "break the PR dependency cascade", guess we can eventually fine tune last things along both practical integrations.

@holgerd77 holgerd77 merged commit 55a8e66 into master Apr 30, 2024
33 of 34 checks passed
@holgerd77 holgerd77 deleted the eip-7685 branch April 30, 2024 18:12
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.

None yet

5 participants