From e7df142f38b627b79a2e7e2074d1da358f48619d Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Thu, 11 Jun 2026 15:14:02 +0200 Subject: [PATCH 1/7] update reth 2.3 and prep for amsterdam fork --- ams_update.md | 176 +++++++++++ docs/ev-reth/engine-api.md | 48 ++- docs/reference/api/engine-api.md | 47 ++- execution/evm/README.md | 21 +- execution/evm/engine_payload.go | 151 +++++++++ execution/evm/engine_rpc_client.go | 228 +++++++++++--- execution/evm/engine_rpc_client_test.go | 288 ++++++++++++++++-- execution/evm/engine_rpc_tracing.go | 31 +- execution/evm/execution.go | 60 ++-- execution/evm/execution_payload_attrs_test.go | 16 + execution/evm/execution_reconcile_test.go | 8 +- execution/evm/test/test_helpers.go | 17 +- 12 files changed, 961 insertions(+), 130 deletions(-) create mode 100644 ams_update.md create mode 100644 execution/evm/engine_payload.go create mode 100644 execution/evm/execution_payload_attrs_test.go diff --git a/ams_update.md b/ams_update.md new file mode 100644 index 0000000000..b24446e805 --- /dev/null +++ b/ams_update.md @@ -0,0 +1,176 @@ +# ev-node Amsterdam Engine API Updates + +This note summarizes the ev-node changes needed before enabling Amsterdam in an +ev-reth 2.3 chainspec. + +## Current ev-node support + +Current ev-node main has Prague/Osaka/Fusaka-style Engine API support: + +- `engine_forkchoiceUpdatedV3` +- `engine_getPayloadV4` with fallback/cache to `engine_getPayloadV5` +- `engine_newPayloadV4` + +That is enough while the execution chain remains pre-Amsterdam. It is not enough +for Amsterdam payloads. + +The checked-in ev-node EVM genesis currently has `pragueTime: 0` and no +`amsterdamTime`, so this is not an immediate break for that default config. + +## Why Amsterdam needs more work + +Amsterdam introduces an Engine payload shape that ev-node must preserve: + +- `PayloadAttributes` require `slotNumber`. +- Built execution payloads include `executionPayload.blockAccessList`. +- Reth 2.3 expects Amsterdam payloads through: + - `engine_forkchoiceUpdatedV4` + - `engine_getPayloadV6` + - `engine_newPayloadV5` + +The most important gap is `blockAccessList` passthrough. ev-node currently +decodes `getPayload` responses into go-ethereum's `engine.ExecutionPayloadEnvelope`. +In `go-ethereum v1.17.2`, `engine.ExecutableData` includes `slotNumber`, but the +Engine API envelope does not expose `executionPayload.blockAccessList`. Decoding +an Amsterdam payload into that type will lose the BAL before ev-node submits the +payload back with `newPayload`. + +## Required code changes + +### 1. Add Amsterdam Engine API method selection + +Update `execution/evm/engine_rpc_client.go`: + +- Add `engine_forkchoiceUpdatedV4`. +- Add `engine_getPayloadV6`. +- Add `engine_newPayloadV5`. +- Track the selected payload version beyond the current V4/V5 boolean. + +Recommended shape: + +- Replace `useV5 atomic.Bool` with a small enum or atomic version value. +- Keep unsupported-fork fallback for `getPayload`, but include V6. +- Select `newPayloadV5` whenever the payload has Amsterdam fields. +- Select `forkchoiceUpdatedV4` whenever payload attributes are Amsterdam. + +### 2. Add `slotNumber` to Amsterdam payload attributes + +Update the payload attributes map in `execution/evm/execution.go`. + +Current map includes: + +- `timestamp` +- `prevRandao` +- `suggestedFeeRecipient` +- `withdrawals` +- `parentBeaconBlockRoot` +- evolve-specific `transactions` +- evolve-specific `gasLimit` + +For Amsterdam-active timestamps, add: + +```json +"slotNumber": "" +``` + +The slot number should come from an explicit chain rule. A reasonable default is +to derive it from rollup block height, but this should be agreed with the +ev-node/ev-reth chain semantics before enabling the fork. + +### 3. Preserve `blockAccessList` from `getPayloadV6` to `newPayloadV5` + +Do not try to compute the block access list in ev-node. ev-reth builds it. + +Add a local Amsterdam-capable payload wrapper that can decode and re-encode the +full `executionPayload` object, including: + +- all fields currently in `engine.ExecutableData` +- `slotNumber` +- `blockAccessList` + +Implementation options: + +- Use a custom struct embedding or mirroring `engine.ExecutableData` and adding + `BlockAccessList hexutil.Bytes`. +- Or keep the raw `json.RawMessage` for `executionPayload`, decode only the + fields ev-node needs for `blockNumber`, `timestamp`, `blockHash`, and + `stateRoot`, then submit the raw payload object unchanged to `newPayloadV5`. + +The raw-object approach is safest for Engine API compatibility because it avoids +dropping future EL payload fields. + +### 4. Add fork activation awareness + +ev-node currently does not have an EVM fork schedule option. It receives: + +- ETH RPC URL +- Engine RPC URL +- JWT secret +- genesis hash +- fee recipient + +Amsterdam selection needs one of: + +- a new flag such as `--evm.amsterdam-time`, kept in sync with ev-reth genesis; +- parsing the ev-reth genesis/chainspec from a configured path; +- an Engine API capability probe plus explicit local state that knows when + `slotNumber` must be present. + +Do not rely only on `getPayload` unsupported-fork fallback. `forkchoiceUpdatedV4` +requires `slotNumber` when starting an Amsterdam build, so ev-node must know the +fork state before asking ev-reth to build a payload. + +### 5. Update tracing and logs + +Update `execution/evm/engine_rpc_tracing.go` so spans record the actual selected +method: + +- `engine_forkchoiceUpdatedV3` or `engine_forkchoiceUpdatedV4` +- `engine_getPayloadV4`, `engine_getPayloadV5`, or `engine_getPayloadV6` +- `engine_newPayloadV4` or `engine_newPayloadV5` + +Also update hard-coded log messages in `execution/evm/execution.go` that still +name V3/V4 methods. + +## Tests to add + +Add or extend tests in `execution/evm/engine_rpc_client_test.go`: + +- Prague path uses `getPayloadV4` and `newPayloadV4`. +- Osaka/Fusaka path falls back to and caches `getPayloadV5`. +- Amsterdam path uses `forkchoiceUpdatedV4` with `slotNumber`. +- Amsterdam path uses `getPayloadV6`. +- Amsterdam path submits with `newPayloadV5`. +- `blockAccessList` returned by `getPayloadV6` is present in the submitted + `newPayloadV5` execution payload. +- Unsupported-fork errors still switch only to valid alternate methods. + +Update integration test helpers: + +- `execution/evm/test/test_helpers.go` currently hard-codes + `engine_forkchoiceUpdatedV3` for readiness. +- Any E2E config that enables `amsterdamTime` must use the updated method path. + +## Docs to update + +Update ev-node docs that currently describe only older Engine API versions: + +- `execution/evm/README.md` +- `docs/reference/api/engine-api.md` +- `docs/ev-reth/engine-api.md` + +Document that Prague/Osaka/Fusaka support is insufficient for Amsterdam because +Amsterdam adds `slotNumber`, `blockAccessList`, and newer Engine API methods. + +## Enablement checklist + +Before setting `amsterdamTime` in an ev-reth chainspec: + +- [ ] ev-node can call `engine_forkchoiceUpdatedV4`. +- [ ] ev-node sends `slotNumber` in Amsterdam payload attributes. +- [ ] ev-node can call `engine_getPayloadV6`. +- [ ] ev-node preserves `executionPayload.blockAccessList`. +- [ ] ev-node can call `engine_newPayloadV5`. +- [ ] tracing/logs report the selected Engine API version. +- [ ] unit tests cover V4/V5/V6 version selection and BAL passthrough. +- [ ] E2E tests pass against an Amsterdam-enabled ev-reth chainspec. diff --git a/docs/ev-reth/engine-api.md b/docs/ev-reth/engine-api.md index 2f8f078513..11771196cc 100644 --- a/docs/ev-reth/engine-api.md +++ b/docs/ev-reth/engine-api.md @@ -25,14 +25,14 @@ Configure both sides: ```text ev-node ev-reth │ │ - │ 1. engine_forkchoiceUpdatedV3 │ + │ 1. engine_forkchoiceUpdatedV3/V4 │ │ (headBlockHash, payloadAttributes) │ │─────────────────────────────────────────►│ │ │ │ 2. {payloadId} │ │◄─────────────────────────────────────────│ │ │ - │ 3. engine_getPayloadV3(payloadId) │ + │ 3. engine_getPayloadV4/V5/V6(payloadId) │ │─────────────────────────────────────────►│ │ │ │ 4. {executionPayload, blockValue} │ @@ -40,7 +40,7 @@ ev-node ev-reth │ │ │ [ev-node broadcasts to P2P, submits DA] │ │ │ - │ 5. engine_newPayloadV3(executionPayload)│ + │ 5. engine_newPayloadV4/V5(payload) │ │─────────────────────────────────────────►│ │ │ │ 6. {status: VALID} │ @@ -54,7 +54,20 @@ ev-node ev-reth ## Methods -### engine_forkchoiceUpdatedV3 +| Fork family | Forkchoice | Get payload | New payload | +|-------------|------------|-------------|-------------| +| Prague | `engine_forkchoiceUpdatedV3` | `engine_getPayloadV4` | `engine_newPayloadV4` | +| Osaka/Fusaka | `engine_forkchoiceUpdatedV3` | `engine_getPayloadV5` | `engine_newPayloadV4` | +| Amsterdam | `engine_forkchoiceUpdatedV4` | `engine_getPayloadV6` | `engine_newPayloadV5` | + +Amsterdam support is more than a method rename: payload attributes include +`slotNumber`, and built payloads include `executionPayload.blockAccessList`. +ev-node preserves the raw Amsterdam `executionPayload` returned by ev-reth and +submits it unchanged to `engine_newPayloadV5`. +ev-node detects Amsterdam by retrying `engine_forkchoiceUpdatedV4` after an +unsupported-fork response from `engine_forkchoiceUpdatedV3`, then caches V4. + +### engine_forkchoiceUpdatedV3 / V4 Update the fork choice and optionally start building a new block. @@ -62,7 +75,7 @@ Update the fork choice and optionally start building a new block. ```json { - "method": "engine_forkchoiceUpdatedV3", + "method": "engine_forkchoiceUpdatedV4", "params": [ { "headBlockHash": "0x...", @@ -74,7 +87,8 @@ Update the fork choice and optionally start building a new block. "prevRandao": "0x...", "suggestedFeeRecipient": "0x...", "withdrawals": [], - "parentBeaconBlockRoot": "0x..." + "parentBeaconBlockRoot": "0x...", + "slotNumber": "0x..." } ] } @@ -92,7 +106,7 @@ Update the fork choice and optionally start building a new block. } ``` -### engine_getPayloadV3 +### engine_getPayloadV4 / V5 / V6 Retrieve a built payload. @@ -100,7 +114,7 @@ Retrieve a built payload. ```json { - "method": "engine_getPayloadV3", + "method": "engine_getPayloadV6", "params": ["0x...payloadId"] } ``` @@ -123,13 +137,15 @@ Retrieve a built payload. "extraData": "0x", "baseFeePerGas": "0x...", "blockHash": "0x...", - "transactions": ["0x..."] + "transactions": ["0x..."], + "slotNumber": "0x...", + "blockAccessList": [] }, "blockValue": "0x..." } ``` -### engine_newPayloadV3 +### engine_newPayloadV4 / V5 Validate and execute a payload. @@ -137,11 +153,17 @@ Validate and execute a payload. ```json { - "method": "engine_newPayloadV3", + "method": "engine_newPayloadV5", "params": [ - { "executionPayload": "..." }, + { + "parentHash": "0x...", + "blockHash": "0x...", + "slotNumber": "0x...", + "blockAccessList": [] + }, ["0x...versionedHashes"], - "0x...parentBeaconBlockRoot" + "0x...parentBeaconBlockRoot", + [] ] } ``` diff --git a/docs/reference/api/engine-api.md b/docs/reference/api/engine-api.md index 9e8c4e9888..f44500ad49 100644 --- a/docs/reference/api/engine-api.md +++ b/docs/reference/api/engine-api.md @@ -18,16 +18,29 @@ openssl rand -hex 32 > jwt.hex ## Methods -### engine_forkchoiceUpdatedV3 +ev-node selects Engine API methods by fork: + +| Fork family | Forkchoice | Get payload | New payload | +|-------------|------------|-------------|-------------| +| Prague | `engine_forkchoiceUpdatedV3` | `engine_getPayloadV4` | `engine_newPayloadV4` | +| Osaka/Fusaka | `engine_forkchoiceUpdatedV3` | `engine_getPayloadV5` | `engine_newPayloadV4` | +| Amsterdam | `engine_forkchoiceUpdatedV4` | `engine_getPayloadV6` | `engine_newPayloadV5` | + +### engine_forkchoiceUpdatedV3 / V4 Update fork choice and optionally build a new block. +Payload builds start with `engine_forkchoiceUpdatedV3`. If the execution layer +returns unsupported fork, ev-node retries `engine_forkchoiceUpdatedV4` with +`slotNumber` in the payload attributes and caches V4 for future calls. +`slotNumber` is derived deterministically from rollup block height. + **Request:** ```json { "jsonrpc": "2.0", - "method": "engine_forkchoiceUpdatedV3", + "method": "engine_forkchoiceUpdatedV4", "params": [ { "headBlockHash": "0x...", @@ -39,7 +52,8 @@ Update fork choice and optionally build a new block. "prevRandao": "0x...", "suggestedFeeRecipient": "0x...", "withdrawals": [], - "parentBeaconBlockRoot": "0x..." + "parentBeaconBlockRoot": "0x...", + "slotNumber": "0x..." } ], "id": 1 @@ -62,16 +76,20 @@ Update fork choice and optionally build a new block. } ``` -### engine_getPayloadV3 +### engine_getPayloadV4 / V5 / V6 Get a built payload. +ev-node starts with `engine_getPayloadV4`, caches `engine_getPayloadV5` or +`engine_getPayloadV6` after successful unsupported-fork fallback, and switches +directly to V6 after an Amsterdam `forkchoiceUpdatedV4` build request. + **Request:** ```json { "jsonrpc": "2.0", - "method": "engine_getPayloadV3", + "method": "engine_getPayloadV6", "params": ["0x...payloadId"], "id": 1 } @@ -100,7 +118,9 @@ Get a built payload. "transactions": ["0x..."], "withdrawals": [], "blobGasUsed": "0x0", - "excessBlobGas": "0x0" + "excessBlobGas": "0x0", + "slotNumber": "0x...", + "blockAccessList": [] }, "blockValue": "0x...", "blobsBundle": { @@ -114,16 +134,20 @@ Get a built payload. } ``` -### engine_newPayloadV3 +### engine_newPayloadV4 / V5 Validate and execute a payload. +Amsterdam payloads use `engine_newPayloadV5`. ev-node passes through the raw +`executionPayload` object returned by `engine_getPayloadV6` so +`blockAccessList` is preserved. + **Request:** ```json { "jsonrpc": "2.0", - "method": "engine_newPayloadV3", + "method": "engine_newPayloadV5", "params": [ { "parentHash": "0x...", @@ -142,10 +166,13 @@ Validate and execute a payload. "transactions": ["0x..."], "withdrawals": [], "blobGasUsed": "0x0", - "excessBlobGas": "0x0" + "excessBlobGas": "0x0", + "slotNumber": "0x...", + "blockAccessList": [] }, ["0x...expectedBlobVersionedHashes"], - "0x...parentBeaconBlockRoot" + "0x...parentBeaconBlockRoot", + [] ], "id": 1 } diff --git a/execution/evm/README.md b/execution/evm/README.md index b44ea71b01..21ea79cd22 100644 --- a/execution/evm/README.md +++ b/execution/evm/README.md @@ -20,7 +20,7 @@ Since the `PureEngineClient` relies on the Engine API, the genesis configuration 1. The genesis file must include post-merge hardfork configurations 2. `terminalTotalDifficulty` must be set to 0 3. `terminalTotalDifficultyPassed` must be set to true -4. Hardforks like `mergeNetsplitBlock`, `shanghaiTime`, `cancunTime` and `pragueTime` should be properly configured +4. Hardforks like `mergeNetsplitBlock`, `shanghaiTime`, `cancunTime`, `pragueTime`, and optionally `amsterdamTime` should be properly configured Example of required genesis configuration: @@ -43,18 +43,31 @@ Example of required genesis configuration: "terminalTotalDifficultyPassed": true, "shanghaiTime": 0, "cancunTime": 0, - "pragueTime": 0 + "pragueTime": 0, + "amsterdamTime": 0 } } ``` Without these settings, the Engine API will not be available, and the `PureEngineClient` will not function correctly. +### Engine API Versions + +ev-node uses fork-aware Engine API methods: + +- Prague payloads use `engine_forkchoiceUpdatedV3`, `engine_getPayloadV4`, and `engine_newPayloadV4`. +- Osaka/Fusaka payloads fall forward from `engine_getPayloadV4` to `engine_getPayloadV5` on unsupported-fork responses. +- Amsterdam payloads use `engine_forkchoiceUpdatedV4`, `engine_getPayloadV6`, and `engine_newPayloadV5`. + +ev-node auto-detects Engine API versions by retrying on unsupported-fork responses. For payload builds, it first tries `engine_forkchoiceUpdatedV3`; if the execution layer rejects that method for Amsterdam, ev-node retries `engine_forkchoiceUpdatedV4` with a deterministic `slotNumber` derived from rollup block height and caches V4 for future calls. + +Amsterdam adds `slotNumber` to payload attributes and `executionPayload.blockAccessList` to built payloads. ev-node does not compute the block access list; it preserves the raw `executionPayload` returned by ev-reth and submits that object unchanged to `engine_newPayloadV5`. + ### PayloadID Storage The `PureEngineClient` maintains the `payloadID` between calls: -1. During `InitChain`, a payload ID is obtained from the Engine API via `engine_forkchoiceUpdatedV3` +1. During `InitChain`, the genesis forkchoice is acknowledged through the Engine API 2. This payload ID is stored in the client instance as `c.payloadID` 3. The stored payload ID is used in subsequent calls to `GetTxs` to retrieve the current execution payload 4. After each `ExecuteTxs` call, a new payload ID is obtained and stored for the next block @@ -66,7 +79,7 @@ The `PureEngineClient` implements a unique approach to transaction execution: 1. In `GetTxs`, the entire execution payload is serialized to JSON and returned as the first transaction 2. In `ExecuteTxs`, this first transaction is deserialized back into an execution payload 3. The remaining transactions are added to the payload's transaction list -4. The complete payload is then submitted to the execution client via `engine_newPayloadV4` +4. The complete payload is then submitted to the execution client via the fork-appropriate `engine_newPayload` method This approach ensures that: diff --git a/execution/evm/engine_payload.go b/execution/evm/engine_payload.go new file mode 100644 index 0000000000..bc61a00ae3 --- /dev/null +++ b/execution/evm/engine_payload.go @@ -0,0 +1,151 @@ +package evm + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// EnginePayloadEnvelope mirrors engine.ExecutionPayloadEnvelope while preserving +// the raw executionPayload object for Amsterdam fields that go-ethereum does not +// yet expose on the typed envelope, such as blockAccessList. +type EnginePayloadEnvelope struct { + ExecutionPayload *engine.ExecutableData + RawExecutionPayload json.RawMessage + BlockValue *big.Int + BlobsBundle *engine.BlobsBundle + Requests [][]byte + Override bool + Witness *hexutil.Bytes +} + +func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { + type executionPayloadEnvelope struct { + ExecutionPayload json.RawMessage `json:"executionPayload"` + BlockValue *hexutil.Big `json:"blockValue"` + BlobsBundle *engine.BlobsBundle + Requests []hexutil.Bytes `json:"executionRequests"` + Override *bool `json:"shouldOverrideBuilder"` + Witness *hexutil.Bytes `json:"witness,omitempty"` + } + + var dec executionPayloadEnvelope + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if len(dec.ExecutionPayload) == 0 { + return errors.New("missing required field 'executionPayload' for EnginePayloadEnvelope") + } + + var payload engine.ExecutableData + if err := json.Unmarshal(dec.ExecutionPayload, &payload); err != nil { + return err + } + e.ExecutionPayload = &payload + e.RawExecutionPayload = append(e.RawExecutionPayload[:0], dec.ExecutionPayload...) + + if dec.BlockValue == nil { + return errors.New("missing required field 'blockValue' for EnginePayloadEnvelope") + } + e.BlockValue = (*big.Int)(dec.BlockValue) + e.BlobsBundle = dec.BlobsBundle + + if dec.Requests != nil { + e.Requests = make([][]byte, len(dec.Requests)) + for i, request := range dec.Requests { + e.Requests[i] = request + } + } + if dec.Override != nil { + e.Override = *dec.Override + } + e.Witness = dec.Witness + return nil +} + +func (e EnginePayloadEnvelope) MarshalJSON() ([]byte, error) { + type executionPayloadEnvelope struct { + ExecutionPayload json.RawMessage `json:"executionPayload"` + BlockValue *hexutil.Big `json:"blockValue"` + BlobsBundle *engine.BlobsBundle + Requests []hexutil.Bytes `json:"executionRequests"` + Override bool `json:"shouldOverrideBuilder"` + Witness *hexutil.Bytes `json:"witness,omitempty"` + } + + executionPayload, err := e.executionPayloadJSON() + if err != nil { + return nil, err + } + + var requests []hexutil.Bytes + if e.Requests != nil { + requests = make([]hexutil.Bytes, len(e.Requests)) + for i, request := range e.Requests { + requests[i] = request + } + } + + return json.Marshal(executionPayloadEnvelope{ + ExecutionPayload: executionPayload, + BlockValue: (*hexutil.Big)(e.BlockValue), + BlobsBundle: e.BlobsBundle, + Requests: requests, + Override: e.Override, + Witness: e.Witness, + }) +} + +func (e *EnginePayloadEnvelope) executionPayloadParam() (any, error) { + if e == nil { + return nil, errors.New("nil EnginePayloadEnvelope") + } + payload, err := e.executionPayloadJSON() + if err != nil { + return nil, err + } + return payload, nil +} + +func (e *EnginePayloadEnvelope) executionPayloadJSON() (json.RawMessage, error) { + if e == nil { + return nil, errors.New("nil EnginePayloadEnvelope") + } + if len(e.RawExecutionPayload) > 0 { + return json.RawMessage(e.RawExecutionPayload), nil + } + if e.ExecutionPayload == nil { + return nil, errors.New("nil execution payload") + } + payload, err := json.Marshal(e.ExecutionPayload) + if err != nil { + return nil, err + } + return json.RawMessage(payload), nil +} + +func (e *EnginePayloadEnvelope) hasAmsterdamFields() bool { + if e == nil { + return false + } + if e.ExecutionPayload != nil && e.ExecutionPayload.SlotNumber != nil { + return true + } + return rawExecutionPayloadHasField(e.RawExecutionPayload, "slotNumber") || + rawExecutionPayloadHasField(e.RawExecutionPayload, "blockAccessList") +} + +func rawExecutionPayloadHasField(payload json.RawMessage, field string) bool { + if len(payload) == 0 { + return false + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(payload, &fields); err != nil { + return false + } + _, ok := fields[field] + return ok +} diff --git a/execution/evm/engine_rpc_client.go b/execution/evm/engine_rpc_client.go index 536bf805e3..288535e34a 100644 --- a/execution/evm/engine_rpc_client.go +++ b/execution/evm/engine_rpc_client.go @@ -16,21 +16,44 @@ const engineErrUnsupportedFork = -38005 // Engine API method names for GetPayload versions. const ( - getPayloadV4Method = "engine_getPayloadV4" - getPayloadV5Method = "engine_getPayloadV5" + forkchoiceUpdatedV3Method = "engine_forkchoiceUpdatedV3" + forkchoiceUpdatedV4Method = "engine_forkchoiceUpdatedV4" + getPayloadV4Method = "engine_getPayloadV4" + getPayloadV5Method = "engine_getPayloadV5" + getPayloadV6Method = "engine_getPayloadV6" + newPayloadV4Method = "engine_newPayloadV4" + newPayloadV5Method = "engine_newPayloadV5" ) var _ EngineRPCClient = (*engineRPCClient)(nil) +type enginePayloadVersion uint32 + +const ( + enginePayloadVersionV4 enginePayloadVersion = 4 + enginePayloadVersionV5 enginePayloadVersion = 5 + enginePayloadVersionV6 enginePayloadVersion = 6 +) + +type engineForkchoiceVersion uint32 + +const ( + engineForkchoiceVersionV3 engineForkchoiceVersion = 3 + engineForkchoiceVersionV4 engineForkchoiceVersion = 4 +) + // engineRPCClient is the concrete implementation wrapping *rpc.Client. -// It auto-detects whether to use engine_getPayloadV4 (Prague) or -// engine_getPayloadV5 (Osaka) by caching the last successful version -// and falling back on "Unsupported fork" errors. +// It auto-detects whether to use engine_getPayloadV4, engine_getPayloadV5, or +// engine_getPayloadV6 by caching the last successful version and falling back on +// "Unsupported fork" errors. type engineRPCClient struct { client *rpc.Client - // useV5 tracks whether GetPayload should prefer V5 (Osaka). - // Starts false (V4/Prague). Flips automatically on unsupported-fork errors. - useV5 atomic.Bool + // payloadVersion tracks the preferred GetPayload version. The zero value + // means V4, which keeps the default Prague path unchanged. + payloadVersion atomic.Uint32 + // forkchoiceVersion tracks the preferred ForkchoiceUpdated version. The zero + // value means V3, which keeps the default pre-Amsterdam path unchanged. + forkchoiceVersion atomic.Uint32 } // NewEngineRPCClient creates a new Engine API client. @@ -39,59 +62,182 @@ func NewEngineRPCClient(client *rpc.Client) EngineRPCClient { } func (e *engineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error) { - var result engine.ForkChoiceResponse - err := e.client.CallContext(ctx, &result, "engine_forkchoiceUpdatedV3", state, args) + preferredVersion := e.preferredForkchoiceVersion() + versions := forkchoiceFallbackOrder(preferredVersion) + + var unsupportedMethods []string + var lastUnsupportedErr error + for _, version := range versions { + method := version.forkchoiceUpdatedMethod() + methodArgs, err := forkchoiceArgsForMethod(args, method) + if err != nil { + return nil, err + } + + var result engine.ForkChoiceResponse + err = e.client.CallContext(ctx, &result, method, state, methodArgs) + if err == nil { + e.forkchoiceVersion.Store(uint32(version)) + if version == engineForkchoiceVersionV4 { + e.payloadVersion.Store(uint32(enginePayloadVersionV6)) + } + return &result, nil + } + + if !isUnsupportedForkErr(err) { + if len(unsupportedMethods) == 0 { + return nil, fmt.Errorf("%s failed: %w", method, err) + } + return nil, fmt.Errorf("%s fallback after unsupported fork from %v: %w", method, unsupportedMethods, err) + } + + unsupportedMethods = append(unsupportedMethods, method) + lastUnsupportedErr = err + } + + return nil, fmt.Errorf("forkchoice update unsupported fork after trying %v: %w", unsupportedMethods, lastUnsupportedErr) +} + +func (e *engineRPCClient) GetPayload(ctx context.Context, payloadID engine.PayloadID) (*EnginePayloadEnvelope, error) { + preferredVersion := e.preferredPayloadVersion() + versions := getPayloadFallbackOrder(preferredVersion) + + var unsupportedMethods []string + var lastUnsupportedErr error + for _, version := range versions { + method := version.getPayloadMethod() + + var result EnginePayloadEnvelope + err := e.client.CallContext(ctx, &result, method, payloadID) + if err == nil { + e.payloadVersion.Store(uint32(version)) + return &result, nil + } + + if !isUnsupportedForkErr(err) { + if len(unsupportedMethods) == 0 { + return nil, fmt.Errorf("%s payload %s: %w", method, payloadID, err) + } + return nil, fmt.Errorf("%s fallback after unsupported fork from %v, payload %s: %w", method, unsupportedMethods, payloadID, err) + } + + unsupportedMethods = append(unsupportedMethods, method) + lastUnsupportedErr = err + } + + return nil, fmt.Errorf("get payload unsupported fork for payload %s after trying %v: %w", payloadID, unsupportedMethods, lastUnsupportedErr) +} + +// GetPayloadMethod returns the Engine API method name currently used by GetPayload. +// This allows wrappers (e.g. tracing) to report the resolved version. +func (e *engineRPCClient) GetPayloadMethod() string { + return e.preferredPayloadVersion().getPayloadMethod() +} + +// GetForkchoiceUpdatedMethod returns the currently preferred Engine API +// forkchoiceUpdated method. Tracing uses this to report the resolved version. +func (e *engineRPCClient) GetForkchoiceUpdatedMethod() string { + return e.preferredForkchoiceVersion().forkchoiceUpdatedMethod() +} + +func (e *engineRPCClient) NewPayload(ctx context.Context, payload *EnginePayloadEnvelope, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) { + payloadParam, err := payload.executionPayloadParam() + if err != nil { + return nil, err + } + + var result engine.PayloadStatusV1 + err = e.client.CallContext(ctx, &result, newPayloadMethod(payload), payloadParam, blobHashes, parentBeaconBlockRoot, executionRequests) if err != nil { return nil, err } return &result, nil } -func (e *engineRPCClient) GetPayload(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { - method := getPayloadV4Method - altMethod := getPayloadV5Method - if e.useV5.Load() { - method = getPayloadV5Method - altMethod = getPayloadV4Method +func (e *engineRPCClient) preferredPayloadVersion() enginePayloadVersion { + switch version := enginePayloadVersion(e.payloadVersion.Load()); version { + case enginePayloadVersionV5, enginePayloadVersionV6: + return version + default: + return enginePayloadVersionV4 } +} - var result engine.ExecutionPayloadEnvelope - err := e.client.CallContext(ctx, &result, method, payloadID) - if err == nil { - return &result, nil +func (v enginePayloadVersion) getPayloadMethod() string { + switch v { + case enginePayloadVersionV5: + return getPayloadV5Method + case enginePayloadVersionV6: + return getPayloadV6Method + default: + return getPayloadV4Method } +} - if !isUnsupportedForkErr(err) { - return nil, fmt.Errorf("%s payload %s: %w", method, payloadID, err) +func getPayloadFallbackOrder(preferred enginePayloadVersion) []enginePayloadVersion { + switch preferred { + case enginePayloadVersionV5: + return []enginePayloadVersion{enginePayloadVersionV5, enginePayloadVersionV6, enginePayloadVersionV4} + case enginePayloadVersionV6: + return []enginePayloadVersion{enginePayloadVersionV6, enginePayloadVersionV5, enginePayloadVersionV4} + default: + return []enginePayloadVersion{enginePayloadVersionV4, enginePayloadVersionV5, enginePayloadVersionV6} } +} - // Primary method returned "Unsupported fork" -- try the other version. - err = e.client.CallContext(ctx, &result, altMethod, payloadID) - if err != nil { - return nil, fmt.Errorf("%s fallback after %s unsupported fork, payload %s: %w", altMethod, method, payloadID, err) +func (e *engineRPCClient) preferredForkchoiceVersion() engineForkchoiceVersion { + switch version := engineForkchoiceVersion(e.forkchoiceVersion.Load()); version { + case engineForkchoiceVersionV4: + return version + default: + return engineForkchoiceVersionV3 } +} - // The alt method worked -- cache it for future calls. - e.useV5.Store(altMethod == getPayloadV5Method) - return &result, nil +func (v engineForkchoiceVersion) forkchoiceUpdatedMethod() string { + if v == engineForkchoiceVersionV4 { + return forkchoiceUpdatedV4Method + } + return forkchoiceUpdatedV3Method } -// GetPayloadMethod returns the Engine API method name currently used by GetPayload. -// This allows wrappers (e.g. tracing) to report the resolved version. -func (e *engineRPCClient) GetPayloadMethod() string { - if e.useV5.Load() { - return getPayloadV5Method +func forkchoiceFallbackOrder(preferred engineForkchoiceVersion) []engineForkchoiceVersion { + if preferred == engineForkchoiceVersionV4 { + return []engineForkchoiceVersion{engineForkchoiceVersionV4, engineForkchoiceVersionV3} } - return getPayloadV4Method + return []engineForkchoiceVersion{engineForkchoiceVersionV3, engineForkchoiceVersionV4} } -func (e *engineRPCClient) NewPayload(ctx context.Context, payload *engine.ExecutableData, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) { - var result engine.PayloadStatusV1 - err := e.client.CallContext(ctx, &result, "engine_newPayloadV4", payload, blobHashes, parentBeaconBlockRoot, executionRequests) - if err != nil { - return nil, err +func forkchoiceArgsForMethod(args map[string]any, method string) (map[string]any, error) { + if args == nil { + return nil, nil } - return &result, nil + + methodArgs := cloneMap(args) + switch method { + case forkchoiceUpdatedV4Method: + if _, ok := methodArgs["slotNumber"]; !ok { + return nil, fmt.Errorf("%s requires slotNumber payload attribute", method) + } + case forkchoiceUpdatedV3Method: + delete(methodArgs, "slotNumber") + } + return methodArgs, nil +} + +func cloneMap(args map[string]any) map[string]any { + cloned := make(map[string]any, len(args)) + for key, value := range args { + cloned[key] = value + } + return cloned +} + +func newPayloadMethod(payload *EnginePayloadEnvelope) string { + if payload.hasAmsterdamFields() { + return newPayloadV5Method + } + return newPayloadV4Method } // isUnsupportedForkErr reports whether err is an Engine API "Unsupported fork" diff --git a/execution/evm/engine_rpc_client_test.go b/execution/evm/engine_rpc_client_test.go index f13270eec6..dd5ac513aa 100644 --- a/execution/evm/engine_rpc_client_test.go +++ b/execution/evm/engine_rpc_client_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "sync" "testing" @@ -22,11 +23,11 @@ type jsonRPCRequest struct { ID json.RawMessage `json:"id"` } -// fakeEngineServer returns an httptest.Server that responds to engine_getPayloadV4 -// and engine_getPayloadV5 according to the provided handler. The handler receives -// the method name and returns (result JSON, error code, error message). +// fakeEngineServer returns an httptest.Server that responds according to the +// provided handler. The handler receives the full JSON-RPC request and returns +// (result JSON, error code, error message). // If errorCode is 0, a success response is sent. -func fakeEngineServer(t *testing.T, handler func(method string) (resultJSON string, errCode int, errMsg string)) *httptest.Server { +func fakeEngineServer(t *testing.T, handler func(req jsonRPCRequest) (resultJSON string, errCode int, errMsg string)) *httptest.Server { t.Helper() var mu sync.Mutex @@ -41,7 +42,7 @@ func fakeEngineServer(t *testing.T, handler func(method string) (resultJSON stri return } - resultJSON, errCode, errMsg := handler(req.Method) + resultJSON, errCode, errMsg := handler(req) w.Header().Set("Content-Type", "application/json") if errCode != 0 { @@ -86,6 +87,37 @@ const minimalPayloadEnvelopeJSON = `{ "shouldOverrideBuilder": false }` +const ( + validForkchoiceResponseJSON = `{ + "payloadStatus": { + "status": "VALID", + "latestValidHash": null, + "validationError": null + }, + "payloadId": "0x0000000000000001" + }` + validPayloadStatusJSON = `{ + "status": "VALID", + "latestValidHash": null, + "validationError": null + }` + zeroHashHex = "0x0000000000000000000000000000000000000000000000000000000000000000" +) + +func minimalAmsterdamPayloadEnvelopeJSON(t *testing.T) string { + t.Helper() + withAmsterdamFields := strings.Replace( + minimalPayloadEnvelopeJSON, + `"excessBlobGas": "0x0"`, + `"excessBlobGas": "0x0", + "slotNumber": "0x1", + "blockAccessList": ["0x1234"]`, + 1, + ) + require.NotEqual(t, minimalPayloadEnvelopeJSON, withAmsterdamFields) + return withAmsterdamFields +} + func dialTestServer(t *testing.T, serverURL string) *rpc.Client { t.Helper() client, err := rpc.Dial(serverURL) @@ -97,12 +129,12 @@ func TestGetPayload_PragueChain_UsesV4(t *testing.T) { var calledMethods []string var mu sync.Mutex - srv := fakeEngineServer(t, func(method string) (string, int, string) { + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { mu.Lock() - calledMethods = append(calledMethods, method) + calledMethods = append(calledMethods, req.Method) mu.Unlock() - if method == "engine_getPayloadV4" { + if req.Method == getPayloadV4Method { return minimalPayloadEnvelopeJSON, 0, "" } return "", -38005, "Unsupported fork" @@ -117,7 +149,7 @@ func TestGetPayload_PragueChain_UsesV4(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV4"}, calledMethods, "should call V4 only") + assert.Equal(t, []string{getPayloadV4Method}, calledMethods, "should call V4 only") calledMethods = nil mu.Unlock() @@ -126,7 +158,7 @@ func TestGetPayload_PragueChain_UsesV4(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV4"}, calledMethods, "should still use V4") + assert.Equal(t, []string{getPayloadV4Method}, calledMethods, "should still use V4") mu.Unlock() } @@ -134,12 +166,12 @@ func TestGetPayload_OsakaChain_FallsBackToV5(t *testing.T) { var calledMethods []string var mu sync.Mutex - srv := fakeEngineServer(t, func(method string) (string, int, string) { + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { mu.Lock() - calledMethods = append(calledMethods, method) + calledMethods = append(calledMethods, req.Method) mu.Unlock() - if method == "engine_getPayloadV5" { + if req.Method == getPayloadV5Method { return minimalPayloadEnvelopeJSON, 0, "" } return "", -38005, "Unsupported fork" @@ -154,7 +186,7 @@ func TestGetPayload_OsakaChain_FallsBackToV5(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV4", "engine_getPayloadV5"}, calledMethods, + assert.Equal(t, []string{getPayloadV4Method, getPayloadV5Method}, calledMethods, "should try V4 then fall back to V5") calledMethods = nil mu.Unlock() @@ -164,31 +196,68 @@ func TestGetPayload_OsakaChain_FallsBackToV5(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV5"}, calledMethods, + assert.Equal(t, []string{getPayloadV5Method}, calledMethods, "should use cached V5 without trying V4") mu.Unlock() } +func TestGetPayload_AmsterdamChain_FallsBackToV6(t *testing.T) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + + if req.Method == getPayloadV6Method { + return minimalAmsterdamPayloadEnvelopeJSON(t), 0, "" + } + return "", -38005, "Unsupported fork" + }) + defer srv.Close() + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + ctx := context.Background() + + _, err := client.GetPayload(ctx, engine.PayloadID{}) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{getPayloadV4Method, getPayloadV5Method, getPayloadV6Method}, calledMethods, + "should try V4, V5, then fall back to V6") + calledMethods = nil + mu.Unlock() + + _, err = client.GetPayload(ctx, engine.PayloadID{}) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{getPayloadV6Method}, calledMethods, + "should use cached V6 without trying earlier versions") + mu.Unlock() +} + func TestGetPayload_ForkUpgrade_SwitchesV4ToV5(t *testing.T) { var mu sync.Mutex var calledMethods []string osakaActive := false - srv := fakeEngineServer(t, func(method string) (string, int, string) { + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { mu.Lock() - calledMethods = append(calledMethods, method) + calledMethods = append(calledMethods, req.Method) active := osakaActive mu.Unlock() if active { // Post-Osaka: V5 works, V4 rejected - if method == "engine_getPayloadV5" { + if req.Method == getPayloadV5Method { return minimalPayloadEnvelopeJSON, 0, "" } return "", -38005, "Unsupported fork" } // Pre-Osaka: V4 works, V5 rejected - if method == "engine_getPayloadV4" { + if req.Method == getPayloadV4Method { return minimalPayloadEnvelopeJSON, 0, "" } return "", -38005, "Unsupported fork" @@ -203,7 +272,7 @@ func TestGetPayload_ForkUpgrade_SwitchesV4ToV5(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV4"}, calledMethods, "pre-upgrade should call V4 only") + assert.Equal(t, []string{getPayloadV4Method}, calledMethods, "pre-upgrade should call V4 only") calledMethods = nil mu.Unlock() @@ -217,7 +286,7 @@ func TestGetPayload_ForkUpgrade_SwitchesV4ToV5(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV4", "engine_getPayloadV5"}, calledMethods, + assert.Equal(t, []string{getPayloadV4Method, getPayloadV5Method}, calledMethods, "first post-upgrade call should try V4 then fall back to V5") calledMethods = nil mu.Unlock() @@ -227,13 +296,20 @@ func TestGetPayload_ForkUpgrade_SwitchesV4ToV5(t *testing.T) { require.NoError(t, err) mu.Lock() - assert.Equal(t, []string{"engine_getPayloadV5"}, calledMethods, + assert.Equal(t, []string{getPayloadV5Method}, calledMethods, "subsequent calls should use cached V5 directly") mu.Unlock() } func TestGetPayload_NonForkError_Propagated(t *testing.T) { - srv := fakeEngineServer(t, func(method string) (string, int, string) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + // Return a different error (e.g., unknown payload) return "", -38001, "Unknown payload" }) @@ -245,6 +321,172 @@ func TestGetPayload_NonForkError_Propagated(t *testing.T) { _, err := client.GetPayload(ctx, engine.PayloadID{}) require.Error(t, err) assert.Contains(t, err.Error(), "Unknown payload") + + mu.Lock() + assert.Equal(t, []string{getPayloadV4Method}, calledMethods, + "non-fork errors should not fall back to alternate versions") + mu.Unlock() +} + +func TestForkchoiceUpdated_AmsterdamAttributes_UsesV4AndPrefersGetPayloadV6(t *testing.T) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + + switch req.Method { + case forkchoiceUpdatedV3Method: + require.Len(t, req.Params, 2) + var attrs map[string]json.RawMessage + require.NoError(t, json.Unmarshal(req.Params[1], &attrs)) + require.NotContains(t, attrs, "slotNumber") + return "", -38005, "Unsupported fork" + case forkchoiceUpdatedV4Method: + require.Len(t, req.Params, 2) + var attrs map[string]json.RawMessage + require.NoError(t, json.Unmarshal(req.Params[1], &attrs)) + require.Contains(t, attrs, "slotNumber") + return validForkchoiceResponseJSON, 0, "" + case getPayloadV6Method: + return minimalAmsterdamPayloadEnvelopeJSON(t), 0, "" + default: + return "", -38005, "Unsupported fork" + } + }) + defer srv.Close() + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + ctx := context.Background() + + _, err := client.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{}, map[string]any{ + "timestamp": uint64(1), + "slotNumber": uint64(1), + }) + require.NoError(t, err) + + _, err = client.GetPayload(ctx, engine.PayloadID{}) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{forkchoiceUpdatedV3Method, forkchoiceUpdatedV4Method, getPayloadV6Method}, calledMethods) + mu.Unlock() +} + +func TestForkchoiceUpdated_PragueAttributes_UsesV3AndStripsSlotNumber(t *testing.T) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + + if req.Method != forkchoiceUpdatedV3Method { + return "", -38005, "Unsupported fork" + } + require.Len(t, req.Params, 2) + var attrs map[string]json.RawMessage + require.NoError(t, json.Unmarshal(req.Params[1], &attrs)) + require.NotContains(t, attrs, "slotNumber") + return validForkchoiceResponseJSON, 0, "" + }) + defer srv.Close() + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + _, err := client.ForkchoiceUpdated(context.Background(), engine.ForkchoiceStateV1{}, map[string]any{ + "timestamp": uint64(1), + "slotNumber": uint64(1), + }) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{forkchoiceUpdatedV3Method}, calledMethods) + mu.Unlock() +} + +func TestNewPayload_PraguePayload_UsesV4(t *testing.T) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + + if req.Method != newPayloadV4Method { + return "", -38005, "Unsupported fork" + } + + require.Len(t, req.Params, 4) + var payload map[string]json.RawMessage + require.NoError(t, json.Unmarshal(req.Params[0], &payload)) + require.NotContains(t, payload, "blockAccessList") + return validPayloadStatusJSON, 0, "" + }) + defer srv.Close() + + var envelope EnginePayloadEnvelope + require.NoError(t, json.Unmarshal([]byte(minimalPayloadEnvelopeJSON), &envelope)) + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + _, err := client.NewPayload(context.Background(), &envelope, []string{}, zeroHashHex, envelope.Requests) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{newPayloadV4Method}, calledMethods) + mu.Unlock() +} + +func TestNewPayload_AmsterdamPayload_UsesV5AndPreservesBlockAccessList(t *testing.T) { + var calledMethods []string + var mu sync.Mutex + + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + mu.Lock() + calledMethods = append(calledMethods, req.Method) + mu.Unlock() + + switch req.Method { + case forkchoiceUpdatedV3Method: + return "", -38005, "Unsupported fork" + case forkchoiceUpdatedV4Method: + return validForkchoiceResponseJSON, 0, "" + case getPayloadV6Method: + return minimalAmsterdamPayloadEnvelopeJSON(t), 0, "" + case newPayloadV5Method: + require.Len(t, req.Params, 4) + var payload map[string]json.RawMessage + require.NoError(t, json.Unmarshal(req.Params[0], &payload)) + require.Contains(t, payload, "slotNumber") + require.Contains(t, payload, "blockAccessList") + require.JSONEq(t, `["0x1234"]`, string(payload["blockAccessList"])) + return validPayloadStatusJSON, 0, "" + default: + return "", -38005, "Unsupported fork" + } + }) + defer srv.Close() + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + ctx := context.Background() + + _, err := client.ForkchoiceUpdated(ctx, engine.ForkchoiceStateV1{}, map[string]any{ + "slotNumber": uint64(1), + }) + require.NoError(t, err) + + payload, err := client.GetPayload(ctx, engine.PayloadID{}) + require.NoError(t, err) + + _, err = client.NewPayload(ctx, payload, []string{}, zeroHashHex, payload.Requests) + require.NoError(t, err) + + mu.Lock() + assert.Equal(t, []string{forkchoiceUpdatedV3Method, forkchoiceUpdatedV4Method, getPayloadV6Method, newPayloadV5Method}, calledMethods) + mu.Unlock() } func TestIsUnsupportedForkErr(t *testing.T) { diff --git a/execution/evm/engine_rpc_tracing.go b/execution/evm/engine_rpc_tracing.go index 5bba9564fd..5459761dd4 100644 --- a/execution/evm/engine_rpc_tracing.go +++ b/execution/evm/engine_rpc_tracing.go @@ -29,7 +29,6 @@ func withTracingEngineRPCClient(inner EngineRPCClient) EngineRPCClient { func (t *tracedEngineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error) { ctx, span := t.tracer.Start(ctx, "Engine.ForkchoiceUpdated", trace.WithAttributes( - attribute.String("method", "engine_forkchoiceUpdatedV3"), attribute.String("head_block_hash", state.HeadBlockHash.Hex()), attribute.String("safe_block_hash", state.SafeBlockHash.Hex()), attribute.String("finalized_block_hash", state.FinalizedBlockHash.Hex()), @@ -38,6 +37,11 @@ func (t *tracedEngineRPCClient) ForkchoiceUpdated(ctx context.Context, state eng defer span.End() result, err := t.inner.ForkchoiceUpdated(ctx, state, args) + + if m, ok := t.inner.(forkchoiceMethodGetter); ok { + span.SetAttributes(attribute.String("method", m.GetForkchoiceUpdatedMethod())) + } + if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -63,13 +67,19 @@ func (t *tracedEngineRPCClient) ForkchoiceUpdated(ctx context.Context, state eng return result, nil } +// forkchoiceMethodGetter is implemented by engineRPCClient to expose the +// resolved forkchoiceUpdated Engine API method name (V3 or V4) for tracing. +type forkchoiceMethodGetter interface { + GetForkchoiceUpdatedMethod() string +} + // payloadMethodGetter is implemented by engineRPCClient to expose the resolved -// GetPayload Engine API method name (V4 or V5) for tracing. +// GetPayload Engine API method name (V4, V5, or V6) for tracing. type payloadMethodGetter interface { GetPayloadMethod() string } -func (t *tracedEngineRPCClient) GetPayload(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { +func (t *tracedEngineRPCClient) GetPayload(ctx context.Context, payloadID engine.PayloadID) (*EnginePayloadEnvelope, error) { ctx, span := t.tracer.Start(ctx, "Engine.GetPayload", trace.WithAttributes( attribute.String("payload_id", payloadID.String()), @@ -101,15 +111,16 @@ func (t *tracedEngineRPCClient) GetPayload(ctx context.Context, payloadID engine return result, nil } -func (t *tracedEngineRPCClient) NewPayload(ctx context.Context, payload *engine.ExecutableData, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) { +func (t *tracedEngineRPCClient) NewPayload(ctx context.Context, payload *EnginePayloadEnvelope, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) { + executionPayload := payload.ExecutionPayload ctx, span := t.tracer.Start(ctx, "Engine.NewPayload", trace.WithAttributes( - attribute.String("method", "engine_newPayloadV4"), - attribute.Int64("block_number", int64(payload.Number)), - attribute.String("block_hash", payload.BlockHash.Hex()), - attribute.String("parent_hash", payload.ParentHash.Hex()), - attribute.Int("tx_count", len(payload.Transactions)), - attribute.Int64("gas_used", int64(payload.GasUsed)), + attribute.String("method", newPayloadMethod(payload)), + attribute.Int64("block_number", int64(executionPayload.Number)), + attribute.String("block_hash", executionPayload.BlockHash.Hex()), + attribute.String("parent_hash", executionPayload.ParentHash.Hex()), + attribute.Int("tx_count", len(executionPayload.Transactions)), + attribute.Int64("gas_used", int64(executionPayload.GasUsed)), ), ) defer span.End() diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 3067893360..7168c04a26 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" @@ -147,10 +148,10 @@ type EngineRPCClient interface { ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error) // GetPayload retrieves a previously requested execution payload. - GetPayload(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) + GetPayload(ctx context.Context, payloadID engine.PayloadID) (*EnginePayloadEnvelope, error) // NewPayload submits a new execution payload for validation. - NewPayload(ctx context.Context, payload *engine.ExecutableData, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) + NewPayload(ctx context.Context, payload *EnginePayloadEnvelope, blobHashes []string, parentBeaconBlockRoot string, executionRequests [][]byte) (*engine.PayloadStatusV1, error) } // EthRPCClient abstracts Ethereum JSON-RPC calls for tracing and testing. @@ -287,7 +288,7 @@ func (c *EngineClient) InitChain(ctx context.Context, genesisTime time.Time, ini nil, ) if err != nil { - return fmt.Errorf("engine_forkchoiceUpdatedV3 failed: %w", err) + return fmt.Errorf("forkchoice update failed: %w", err) } // Validate payload status @@ -296,7 +297,7 @@ func (c *EngineClient) InitChain(ctx context.Context, genesisTime time.Time, ini Str("status", forkchoiceResult.PayloadStatus.Status). Str("latestValidHash", latestValidHashHex(forkchoiceResult.PayloadStatus.LatestValidHash)). Interface("validationError", forkchoiceResult.PayloadStatus.ValidationError). - Msg("InitChain: engine_forkchoiceUpdatedV3 returned non-VALID status") + Msg("InitChain: forkchoice update returned non-VALID status") return err } @@ -388,24 +389,12 @@ func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight c.mu.Unlock() // update forkchoice to get the next payload id - // Create evolve-compatible payloadtimestamp.Unix() - evPayloadAttrs := map[string]any{ - // Standard Ethereum payload attributes (flattened) - using camelCase as expected by JSON - "timestamp": timestamp.Unix(), - "prevRandao": c.derivePrevRandao(blockHeight), - "suggestedFeeRecipient": c.feeRecipient, - "withdrawals": []*types.Withdrawal{}, - // V3 requires parentBeaconBlockRoot - "parentBeaconBlockRoot": common.Hash{}.Hex(), // Use zero hash for evolve - // evolve-specific fields - "transactions": txsPayload, - "gasLimit": prevGasLimit, // Use camelCase to match JSON conventions - } + evPayloadAttrs := c.buildPayloadAttributes(txsPayload, blockHeight, timestamp, prevGasLimit) c.logger.Debug(). Uint64("height", blockHeight). Int("tx_count", len(txs)). - Msg("engine_forkchoiceUpdatedV3") + Msg("engine_forkchoiceUpdated") // 3. Call forkchoice update to get PayloadID var newPayloadID *engine.PayloadID @@ -422,7 +411,7 @@ func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight Str("latestValidHash", latestValidHashHex(forkchoiceResult.PayloadStatus.LatestValidHash)). Interface("validationError", forkchoiceResult.PayloadStatus.ValidationError). Uint64("blockHeight", blockHeight). - Msg("ExecuteTxs: engine_forkchoiceUpdatedV3 returned non-VALID status") + Msg("ExecuteTxs: forkchoice update returned non-VALID status") return err } @@ -572,7 +561,7 @@ func (c *EngineClient) doForkchoiceUpdate(ctx context.Context, args engine.Forkc Str("latestValidHash", latestValidHashHex(forkchoiceResult.PayloadStatus.LatestValidHash)). Interface("validationError", forkchoiceResult.PayloadStatus.ValidationError). Str("operation", operation). - Msg("forkchoiceUpdatedV3 returned non-VALID status") + Msg("forkchoice update returned non-VALID status") return err } return nil @@ -963,12 +952,13 @@ func (c *EngineClient) processPayload(ctx context.Context, payloadID engine.Payl blockTimestamp := int64(payloadResult.ExecutionPayload.Timestamp) // 2. Submit Payload (newPayload) + selectedNewPayloadMethod := newPayloadMethod(payloadResult) err = retryWithBackoffOnPayloadStatus(ctx, func() error { newPayloadResult, err := c.engineClient.NewPayload(ctx, - payloadResult.ExecutionPayload, + payloadResult, []string{}, // No blob hashes common.Hash{}.Hex(), // Use zero hash for parentBeaconBlockRoot - [][]byte{}, // No execution requests + payloadResult.Requests, ) if err != nil { return fmt.Errorf("new payload submission failed: %w", err) @@ -976,11 +966,12 @@ func (c *EngineClient) processPayload(ctx context.Context, payloadID engine.Payl if err := validatePayloadStatus(*newPayloadResult); err != nil { c.logger.Warn(). + Str("method", selectedNewPayloadMethod). Str("status", newPayloadResult.Status). Str("latestValidHash", latestValidHashHex(newPayloadResult.LatestValidHash)). Interface("validationError", newPayloadResult.ValidationError). Uint64("blockHeight", blockHeight). - Msg("processPayload: engine_newPayloadV4 returned non-VALID status") + Msg("processPayload: new payload returned non-VALID status") return err } return nil @@ -1008,6 +999,29 @@ func (c *EngineClient) derivePrevRandao(blockHeight uint64) common.Hash { return common.BigToHash(new(big.Int).SetUint64(blockHeight)) } +func (c *EngineClient) buildPayloadAttributes(txsPayload []string, blockHeight uint64, timestamp time.Time, prevGasLimit uint64) map[string]any { + attrs := map[string]any{ + // Standard Ethereum payload attributes (flattened) - using camelCase as expected by JSON + "timestamp": timestamp.Unix(), + "prevRandao": c.derivePrevRandao(blockHeight), + "suggestedFeeRecipient": c.feeRecipient, + "withdrawals": []*types.Withdrawal{}, + "parentBeaconBlockRoot": common.Hash{}.Hex(), // Use zero hash for evolve + // evolve-specific fields + "transactions": txsPayload, + "gasLimit": prevGasLimit, + // slotNumber is included as a local candidate for Amsterdam. The RPC + // client strips it from V3 requests and keeps it on V4 retries. + "slotNumber": hexutil.Uint64(c.deriveSlotNumber(blockHeight)), + } + + return attrs +} + +func (c *EngineClient) deriveSlotNumber(blockHeight uint64) uint64 { + return blockHeight +} + func (c *EngineClient) getBlockInfo(ctx context.Context, height uint64) (common.Hash, common.Hash, uint64, uint64, error) { header, err := c.ethClient.HeaderByNumber(ctx, new(big.Int).SetUint64(height)) diff --git a/execution/evm/execution_payload_attrs_test.go b/execution/evm/execution_payload_attrs_test.go new file mode 100644 index 0000000000..8cb949cdd3 --- /dev/null +++ b/execution/evm/execution_payload_attrs_test.go @@ -0,0 +1,16 @@ +package evm + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +func TestBuildPayloadAttributes_AddsSlotNumberCandidate(t *testing.T) { + client := &EngineClient{} + + attrs := client.buildPayloadAttributes(nil, 12, time.Unix(1_700_000_000, 0), 30_000_000) + require.Equal(t, hexutil.Uint64(12), attrs["slotNumber"]) +} diff --git a/execution/evm/execution_reconcile_test.go b/execution/evm/execution_reconcile_test.go index 7d1243f81b..eef2d4373d 100644 --- a/execution/evm/execution_reconcile_test.go +++ b/execution/evm/execution_reconcile_test.go @@ -66,7 +66,7 @@ func TestReconcileExecutionAtHeight_StartedExecMeta(t *testing.T) { })) engineRPC := &mockReconcileEngineRPCClient{ - payloads: map[engine.PayloadID]*engine.ExecutionPayloadEnvelope{ + payloads: map[engine.PayloadID]*EnginePayloadEnvelope{ payloadID: {}, }, } @@ -92,7 +92,7 @@ func TestReconcileExecutionAtHeight_StartedExecMeta(t *testing.T) { } type mockReconcileEngineRPCClient struct { - payloads map[engine.PayloadID]*engine.ExecutionPayloadEnvelope + payloads map[engine.PayloadID]*EnginePayloadEnvelope getPayloadCalls int } @@ -100,7 +100,7 @@ func (m *mockReconcileEngineRPCClient) ForkchoiceUpdated(_ context.Context, _ en return nil, errors.New("unexpected ForkchoiceUpdated call") } -func (m *mockReconcileEngineRPCClient) GetPayload(_ context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { +func (m *mockReconcileEngineRPCClient) GetPayload(_ context.Context, payloadID engine.PayloadID) (*EnginePayloadEnvelope, error) { m.getPayloadCalls++ payload, ok := m.payloads[payloadID] if !ok { @@ -110,7 +110,7 @@ func (m *mockReconcileEngineRPCClient) GetPayload(_ context.Context, payloadID e return payload, nil } -func (m *mockReconcileEngineRPCClient) NewPayload(_ context.Context, _ *engine.ExecutableData, _ []string, _ string, _ [][]byte) (*engine.PayloadStatusV1, error) { +func (m *mockReconcileEngineRPCClient) NewPayload(_ context.Context, _ *EnginePayloadEnvelope, _ []string, _ string, _ [][]byte) (*engine.PayloadStatusV1, error) { return nil, errors.New("unexpected NewPayload call") } diff --git a/execution/evm/test/test_helpers.go b/execution/evm/test/test_helpers.go index 64ff6cafa8..6cd7e279af 100644 --- a/execution/evm/test/test_helpers.go +++ b/execution/evm/test/test_helpers.go @@ -151,7 +151,20 @@ func getGenesisHash(client *http.Client, ethURL string) (string, error) { } func waitForEngineForkchoice(client *http.Client, engineURL, authToken, genesisHash string) error { - body := fmt.Sprintf(`{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV3","params":[{"headBlockHash":%q,"safeBlockHash":%q,"finalizedBlockHash":%q},null],"id":1}`, genesisHash, genesisHash, genesisHash) + methods := []string{"engine_forkchoiceUpdatedV3", "engine_forkchoiceUpdatedV4"} + var lastErr error + for _, method := range methods { + if err := waitForEngineForkchoiceMethod(client, engineURL, authToken, genesisHash, method); err == nil { + return nil + } else { + lastErr = err + } + } + return lastErr +} + +func waitForEngineForkchoiceMethod(client *http.Client, engineURL, authToken, genesisHash, method string) error { + body := fmt.Sprintf(`{"jsonrpc":"2.0","method":%q,"params":[{"headBlockHash":%q,"safeBlockHash":%q,"finalizedBlockHash":%q},null],"id":1}`, method, genesisHash, genesisHash, genesisHash) req, err := http.NewRequest("POST", engineURL, strings.NewReader(body)) if err != nil { return err @@ -181,7 +194,7 @@ func waitForEngineForkchoice(client *http.Client, engineURL, authToken, genesisH return fmt.Errorf("decode engine forkchoice response: %w", err) } if rpcResp.Error != nil { - return fmt.Errorf("engine_forkchoiceUpdatedV3 failed: %s", rpcResp.Error.Message) + return fmt.Errorf("%s failed: %s", method, rpcResp.Error.Message) } if rpcResp.Result.PayloadStatus.Status != "VALID" { return fmt.Errorf("engine forkchoice status %s", rpcResp.Result.PayloadStatus.Status) From 50274d25141e7425c3a5b6172d37f68b5c89e256 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Thu, 18 Jun 2026 16:18:40 +0200 Subject: [PATCH 2/7] add test --- .../evm/test/engine_fork_upgrade_test.go | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 execution/evm/test/engine_fork_upgrade_test.go diff --git a/execution/evm/test/engine_fork_upgrade_test.go b/execution/evm/test/engine_fork_upgrade_test.go new file mode 100644 index 0000000000..d4edbac28c --- /dev/null +++ b/execution/evm/test/engine_fork_upgrade_test.go @@ -0,0 +1,285 @@ +//go:build evm + +package test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "strings" + "sync" + "testing" + "time" + + tastoradocker "github.com/celestiaorg/tastora/framework/docker" + "github.com/celestiaorg/tastora/framework/docker/container" + "github.com/celestiaorg/tastora/framework/docker/evstack/reth" + "github.com/ethereum/go-ethereum/common" + ds "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/evstack/ev-node/execution/evm" +) + +const ( + forkCyclePragueTimestamp = uint64(12) + forkCycleOsakaTimestamp = uint64(24) + forkCycleAmsterdamTimestamp = uint64(36) +) + +// TestEngineAPIForkUpgradeCycleE2E drives ev-node's EngineClient against a real +// ev-reth Docker node whose chainspec activates Osaka and Amsterdam at fixed +// payload timestamps. An Engine RPC proxy records the method versions used by +// ev-node, so this verifies the V4 -> V5 -> V6 upgrade path instead of only +// asserting that blocks are produced. +// +// The existing Tastora ev-reth image is used by default. Set EV_RETH_TAG, or set +// EV_RETH_IMAGE_REPO with optional EV_RETH_TAG, to point at a local or registry image. +func TestEngineAPIForkUpgradeCycleE2E(t *testing.T) { + if testing.Short() { + t.Skip("skipping due to short mode") + } + + rethImageOpt := rethImageOptFromEnv() + ctx := context.Background() + dockerClient, networkID := tastoradocker.Setup(t) + + genesis := reth.DefaultEvolveGenesisJSON(withEngineForkTimes(t, forkCycleOsakaTimestamp, forkCycleAmsterdamTimestamp)) + rethNode := SetupTestRethNode(t, dockerClient, networkID, + rethImageOpt, + func(b *reth.NodeBuilder) { + b.WithGenesis([]byte(genesis)) + }, + ) + + networkInfo, err := rethNode.GetNetworkInfo(ctx) + require.NoError(t, err) + + ethURL := "http://127.0.0.1:" + networkInfo.External.Ports.RPC + engineURL := "http://127.0.0.1:" + networkInfo.External.Ports.Engine + engineRecorder := newEngineRPCRecorder(t, engineURL) + defer engineRecorder.Close() + + genesisHash, err := rethNode.GenesisHash(ctx) + require.NoError(t, err) + + store := dssync.MutexWrap(ds.NewMapDatastore()) + executionClient, err := evm.NewEngineExecutionClient( + ethURL, + engineRecorder.URL(), + rethNode.JWTSecretHex(), + common.HexToHash(genesisHash), + common.Address{}, + store, + false, + zerolog.Nop(), + ) + require.NoError(t, err) + + stateRoot, err := executionClient.InitChain(ctx, time.Unix(0, 0), 1, "engine-fork-cycle") + require.NoError(t, err) + + steps := []struct { + name string + height uint64 + timestamp uint64 + }{ + {name: "prague", height: 1, timestamp: forkCyclePragueTimestamp}, + {name: "osaka", height: 2, timestamp: forkCycleOsakaTimestamp}, + {name: "amsterdam", height: 3, timestamp: forkCycleAmsterdamTimestamp}, + } + + for _, step := range steps { + t.Run(step.name, func(t *testing.T) { + nextStateRoot, err := executionClient.ExecuteTxs(ctx, nil, step.height, time.Unix(int64(step.timestamp), 0), stateRoot) + require.NoError(t, err) + stateRoot = nextStateRoot + }) + } + + engineRecorder.RequireCalled(t, "engine_forkchoiceUpdatedV3") + engineRecorder.RequireCalled(t, "engine_getPayloadV4") + engineRecorder.RequireCalled(t, "engine_newPayloadV4") + engineRecorder.RequireCalled(t, "engine_getPayloadV5") + engineRecorder.RequireCalled(t, "engine_forkchoiceUpdatedV4") + engineRecorder.RequireCalled(t, "engine_getPayloadV6") + engineRecorder.RequireCalled(t, "engine_newPayloadV5") + require.True(t, engineRecorder.SawNewPayloadV5BlockAccessList(), + "engine_newPayloadV5 should preserve executionPayload.blockAccessList from engine_getPayloadV6") +} + +func withEngineForkTimes(t testing.TB, osakaTime, amsterdamTime uint64) reth.GenesisOpt { + t.Helper() + return func(genesis []byte) ([]byte, error) { + var doc map[string]any + if err := json.Unmarshal(genesis, &doc); err != nil { + return nil, err + } + config, ok := doc["config"].(map[string]any) + if !ok { + return nil, fmt.Errorf("genesis config missing or invalid") + } + config["osakaTime"] = osakaTime + config["amsterdamTime"] = amsterdamTime + return json.Marshal(doc) + } +} + +func rethImageOptFromEnv() RethNodeOpt { + repo := strings.TrimSpace(os.Getenv("EV_RETH_IMAGE_REPO")) + tag := strings.TrimSpace(os.Getenv("EV_RETH_TAG")) + if repo == "" && tag == "" { + return nil + } + if tag == "" { + tag = "latest" + } + return func(b *reth.NodeBuilder) { + if repo != "" { + b.WithImage(container.NewImage(repo, tag, "")) + return + } + b.WithTag(tag) + } +} + +type engineRPCRecorder struct { + server *http.Server + listener net.Listener + target *url.URL + + mu sync.Mutex + calls []string + counts map[string]int + sawNewPayloadV5BlockAccessList bool +} + +func newEngineRPCRecorder(t testing.TB, target string) *engineRPCRecorder { + t.Helper() + + targetURL, err := url.Parse(target) + require.NoError(t, err) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + recorder := &engineRPCRecorder{ + listener: listener, + target: targetURL, + counts: make(map[string]int), + } + recorder.server = &http.Server{Handler: http.HandlerFunc(recorder.proxy)} + + go func() { + if err := recorder.server.Serve(listener); err != nil && err != http.ErrServerClosed { + t.Logf("engine RPC recorder stopped unexpectedly: %v", err) + } + }() + + return recorder +} + +func (r *engineRPCRecorder) URL() string { + return "http://" + r.listener.Addr().String() +} + +func (r *engineRPCRecorder) Close() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _ = r.server.Shutdown(ctx) +} + +func (r *engineRPCRecorder) RequireCalled(t testing.TB, method string) { + t.Helper() + r.mu.Lock() + defer r.mu.Unlock() + require.Greater(t, r.counts[method], 0, "expected Engine RPC method %s to be called; calls=%v", method, r.calls) +} + +func (r *engineRPCRecorder) SawNewPayloadV5BlockAccessList() bool { + r.mu.Lock() + defer r.mu.Unlock() + return r.sawNewPayloadV5BlockAccessList +} + +func (r *engineRPCRecorder) proxy(w http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + _ = req.Body.Close() + + method := rpcMethod(body) + if method != "" { + r.record(method, body) + } + + targetReq, err := http.NewRequestWithContext(req.Context(), req.Method, r.target.String(), bytes.NewReader(body)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + targetReq.Header = req.Header.Clone() + + resp, err := http.DefaultTransport.RoundTrip(targetReq) + if err != nil { + http.Error(w, err.Error(), http.StatusBadGateway) + return + } + defer func() { _ = resp.Body.Close() }() + + for key, values := range resp.Header { + for _, value := range values { + w.Header().Add(key, value) + } + } + w.WriteHeader(resp.StatusCode) + _, _ = io.Copy(w, resp.Body) +} + +func (r *engineRPCRecorder) record(method string, body []byte) { + r.mu.Lock() + defer r.mu.Unlock() + + r.calls = append(r.calls, method) + r.counts[method]++ + + if method == "engine_newPayloadV5" && requestHasBlockAccessList(body) { + r.sawNewPayloadV5BlockAccessList = true + } +} + +func rpcMethod(body []byte) string { + var req struct { + Method string `json:"method"` + } + if err := json.Unmarshal(body, &req); err != nil { + return "" + } + return req.Method +} + +func requestHasBlockAccessList(body []byte) bool { + var req struct { + Params []json.RawMessage `json:"params"` + } + if err := json.Unmarshal(body, &req); err != nil || len(req.Params) == 0 { + return false + } + + var payload map[string]json.RawMessage + if err := json.Unmarshal(req.Params[0], &payload); err != nil { + return false + } + _, ok := payload["blockAccessList"] + return ok +} From d39b4ab02be5ffe528979ca95620577f5d9b30ba Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Wed, 15 Jul 2026 14:32:26 +0200 Subject: [PATCH 3/7] updates --- ams_update.md | 176 ------------------ docs/ev-reth/engine-api.md | 13 ++ execution/evm/engine_payload.go | 54 +++--- execution/evm/engine_rpc_client.go | 13 +- execution/evm/engine_rpc_client_test.go | 55 ++++++ execution/evm/execution.go | 5 + execution/evm/go.mod | 3 +- execution/evm/go.sum | 16 +- execution/evm/proposer_test.go | 4 +- .../evm/test/engine_fork_upgrade_test.go | 4 +- 10 files changed, 124 insertions(+), 219 deletions(-) delete mode 100644 ams_update.md diff --git a/ams_update.md b/ams_update.md deleted file mode 100644 index b24446e805..0000000000 --- a/ams_update.md +++ /dev/null @@ -1,176 +0,0 @@ -# ev-node Amsterdam Engine API Updates - -This note summarizes the ev-node changes needed before enabling Amsterdam in an -ev-reth 2.3 chainspec. - -## Current ev-node support - -Current ev-node main has Prague/Osaka/Fusaka-style Engine API support: - -- `engine_forkchoiceUpdatedV3` -- `engine_getPayloadV4` with fallback/cache to `engine_getPayloadV5` -- `engine_newPayloadV4` - -That is enough while the execution chain remains pre-Amsterdam. It is not enough -for Amsterdam payloads. - -The checked-in ev-node EVM genesis currently has `pragueTime: 0` and no -`amsterdamTime`, so this is not an immediate break for that default config. - -## Why Amsterdam needs more work - -Amsterdam introduces an Engine payload shape that ev-node must preserve: - -- `PayloadAttributes` require `slotNumber`. -- Built execution payloads include `executionPayload.blockAccessList`. -- Reth 2.3 expects Amsterdam payloads through: - - `engine_forkchoiceUpdatedV4` - - `engine_getPayloadV6` - - `engine_newPayloadV5` - -The most important gap is `blockAccessList` passthrough. ev-node currently -decodes `getPayload` responses into go-ethereum's `engine.ExecutionPayloadEnvelope`. -In `go-ethereum v1.17.2`, `engine.ExecutableData` includes `slotNumber`, but the -Engine API envelope does not expose `executionPayload.blockAccessList`. Decoding -an Amsterdam payload into that type will lose the BAL before ev-node submits the -payload back with `newPayload`. - -## Required code changes - -### 1. Add Amsterdam Engine API method selection - -Update `execution/evm/engine_rpc_client.go`: - -- Add `engine_forkchoiceUpdatedV4`. -- Add `engine_getPayloadV6`. -- Add `engine_newPayloadV5`. -- Track the selected payload version beyond the current V4/V5 boolean. - -Recommended shape: - -- Replace `useV5 atomic.Bool` with a small enum or atomic version value. -- Keep unsupported-fork fallback for `getPayload`, but include V6. -- Select `newPayloadV5` whenever the payload has Amsterdam fields. -- Select `forkchoiceUpdatedV4` whenever payload attributes are Amsterdam. - -### 2. Add `slotNumber` to Amsterdam payload attributes - -Update the payload attributes map in `execution/evm/execution.go`. - -Current map includes: - -- `timestamp` -- `prevRandao` -- `suggestedFeeRecipient` -- `withdrawals` -- `parentBeaconBlockRoot` -- evolve-specific `transactions` -- evolve-specific `gasLimit` - -For Amsterdam-active timestamps, add: - -```json -"slotNumber": "" -``` - -The slot number should come from an explicit chain rule. A reasonable default is -to derive it from rollup block height, but this should be agreed with the -ev-node/ev-reth chain semantics before enabling the fork. - -### 3. Preserve `blockAccessList` from `getPayloadV6` to `newPayloadV5` - -Do not try to compute the block access list in ev-node. ev-reth builds it. - -Add a local Amsterdam-capable payload wrapper that can decode and re-encode the -full `executionPayload` object, including: - -- all fields currently in `engine.ExecutableData` -- `slotNumber` -- `blockAccessList` - -Implementation options: - -- Use a custom struct embedding or mirroring `engine.ExecutableData` and adding - `BlockAccessList hexutil.Bytes`. -- Or keep the raw `json.RawMessage` for `executionPayload`, decode only the - fields ev-node needs for `blockNumber`, `timestamp`, `blockHash`, and - `stateRoot`, then submit the raw payload object unchanged to `newPayloadV5`. - -The raw-object approach is safest for Engine API compatibility because it avoids -dropping future EL payload fields. - -### 4. Add fork activation awareness - -ev-node currently does not have an EVM fork schedule option. It receives: - -- ETH RPC URL -- Engine RPC URL -- JWT secret -- genesis hash -- fee recipient - -Amsterdam selection needs one of: - -- a new flag such as `--evm.amsterdam-time`, kept in sync with ev-reth genesis; -- parsing the ev-reth genesis/chainspec from a configured path; -- an Engine API capability probe plus explicit local state that knows when - `slotNumber` must be present. - -Do not rely only on `getPayload` unsupported-fork fallback. `forkchoiceUpdatedV4` -requires `slotNumber` when starting an Amsterdam build, so ev-node must know the -fork state before asking ev-reth to build a payload. - -### 5. Update tracing and logs - -Update `execution/evm/engine_rpc_tracing.go` so spans record the actual selected -method: - -- `engine_forkchoiceUpdatedV3` or `engine_forkchoiceUpdatedV4` -- `engine_getPayloadV4`, `engine_getPayloadV5`, or `engine_getPayloadV6` -- `engine_newPayloadV4` or `engine_newPayloadV5` - -Also update hard-coded log messages in `execution/evm/execution.go` that still -name V3/V4 methods. - -## Tests to add - -Add or extend tests in `execution/evm/engine_rpc_client_test.go`: - -- Prague path uses `getPayloadV4` and `newPayloadV4`. -- Osaka/Fusaka path falls back to and caches `getPayloadV5`. -- Amsterdam path uses `forkchoiceUpdatedV4` with `slotNumber`. -- Amsterdam path uses `getPayloadV6`. -- Amsterdam path submits with `newPayloadV5`. -- `blockAccessList` returned by `getPayloadV6` is present in the submitted - `newPayloadV5` execution payload. -- Unsupported-fork errors still switch only to valid alternate methods. - -Update integration test helpers: - -- `execution/evm/test/test_helpers.go` currently hard-codes - `engine_forkchoiceUpdatedV3` for readiness. -- Any E2E config that enables `amsterdamTime` must use the updated method path. - -## Docs to update - -Update ev-node docs that currently describe only older Engine API versions: - -- `execution/evm/README.md` -- `docs/reference/api/engine-api.md` -- `docs/ev-reth/engine-api.md` - -Document that Prague/Osaka/Fusaka support is insufficient for Amsterdam because -Amsterdam adds `slotNumber`, `blockAccessList`, and newer Engine API methods. - -## Enablement checklist - -Before setting `amsterdamTime` in an ev-reth chainspec: - -- [ ] ev-node can call `engine_forkchoiceUpdatedV4`. -- [ ] ev-node sends `slotNumber` in Amsterdam payload attributes. -- [ ] ev-node can call `engine_getPayloadV6`. -- [ ] ev-node preserves `executionPayload.blockAccessList`. -- [ ] ev-node can call `engine_newPayloadV5`. -- [ ] tracing/logs report the selected Engine API version. -- [ ] unit tests cover V4/V5/V6 version selection and BAL passthrough. -- [ ] E2E tests pass against an Amsterdam-enabled ev-reth chainspec. diff --git a/docs/ev-reth/engine-api.md b/docs/ev-reth/engine-api.md index 11771196cc..f520da909e 100644 --- a/docs/ev-reth/engine-api.md +++ b/docs/ev-reth/engine-api.md @@ -177,6 +177,19 @@ Validate and execute a payload. } ``` +## Amsterdam Enablement Checklist + +Before setting `amsterdamTime` in an ev-reth chainspec, verify: + +- [ ] ev-node can call `engine_forkchoiceUpdatedV4`. +- [ ] ev-node sends `slotNumber` in Amsterdam payload attributes. +- [ ] ev-node can call `engine_getPayloadV6`. +- [ ] ev-node preserves `executionPayload.blockAccessList`. +- [ ] ev-node can call `engine_newPayloadV5`. +- [ ] Tracing and logs report the selected Engine API version. +- [ ] Unit tests cover V4/V5/V6 version selection and `blockAccessList` passthrough. +- [ ] E2E tests pass against an Amsterdam-enabled ev-reth chainspec. + ## Status Codes | Status | Meaning | diff --git a/execution/evm/engine_payload.go b/execution/evm/engine_payload.go index bc61a00ae3..c08f67070a 100644 --- a/execution/evm/engine_payload.go +++ b/execution/evm/engine_payload.go @@ -20,16 +20,21 @@ type EnginePayloadEnvelope struct { Requests [][]byte Override bool Witness *hexutil.Bytes + + // rawHasAmsterdamFields caches whether RawExecutionPayload contains + // slotNumber or blockAccessList. Set during UnmarshalJSON so version + // selection does not re-parse the raw payload on every call. + rawHasAmsterdamFields bool } func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { type executionPayloadEnvelope struct { - ExecutionPayload json.RawMessage `json:"executionPayload"` - BlockValue *hexutil.Big `json:"blockValue"` - BlobsBundle *engine.BlobsBundle - Requests []hexutil.Bytes `json:"executionRequests"` - Override *bool `json:"shouldOverrideBuilder"` - Witness *hexutil.Bytes `json:"witness,omitempty"` + ExecutionPayload json.RawMessage `json:"executionPayload"` + BlockValue *hexutil.Big `json:"blockValue"` + BlobsBundle *engine.BlobsBundle `json:"blobsBundle"` + Requests []hexutil.Bytes `json:"executionRequests"` + Override *bool `json:"shouldOverrideBuilder"` + Witness *hexutil.Bytes `json:"witness,omitempty"` } var dec executionPayloadEnvelope @@ -44,8 +49,16 @@ func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(dec.ExecutionPayload, &payload); err != nil { return err } + var amsterdamProbe struct { + SlotNumber json.RawMessage `json:"slotNumber"` + BlockAccessList json.RawMessage `json:"blockAccessList"` + } + if err := json.Unmarshal(dec.ExecutionPayload, &amsterdamProbe); err != nil { + return err + } e.ExecutionPayload = &payload e.RawExecutionPayload = append(e.RawExecutionPayload[:0], dec.ExecutionPayload...) + e.rawHasAmsterdamFields = amsterdamProbe.SlotNumber != nil || amsterdamProbe.BlockAccessList != nil if dec.BlockValue == nil { return errors.New("missing required field 'blockValue' for EnginePayloadEnvelope") @@ -68,12 +81,12 @@ func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { func (e EnginePayloadEnvelope) MarshalJSON() ([]byte, error) { type executionPayloadEnvelope struct { - ExecutionPayload json.RawMessage `json:"executionPayload"` - BlockValue *hexutil.Big `json:"blockValue"` - BlobsBundle *engine.BlobsBundle - Requests []hexutil.Bytes `json:"executionRequests"` - Override bool `json:"shouldOverrideBuilder"` - Witness *hexutil.Bytes `json:"witness,omitempty"` + ExecutionPayload json.RawMessage `json:"executionPayload"` + BlockValue *hexutil.Big `json:"blockValue"` + BlobsBundle *engine.BlobsBundle `json:"blobsBundle"` + Requests []hexutil.Bytes `json:"executionRequests"` + Override bool `json:"shouldOverrideBuilder"` + Witness *hexutil.Bytes `json:"witness,omitempty"` } executionPayload, err := e.executionPayloadJSON() @@ -115,7 +128,7 @@ func (e *EnginePayloadEnvelope) executionPayloadJSON() (json.RawMessage, error) return nil, errors.New("nil EnginePayloadEnvelope") } if len(e.RawExecutionPayload) > 0 { - return json.RawMessage(e.RawExecutionPayload), nil + return e.RawExecutionPayload, nil } if e.ExecutionPayload == nil { return nil, errors.New("nil execution payload") @@ -134,18 +147,5 @@ func (e *EnginePayloadEnvelope) hasAmsterdamFields() bool { if e.ExecutionPayload != nil && e.ExecutionPayload.SlotNumber != nil { return true } - return rawExecutionPayloadHasField(e.RawExecutionPayload, "slotNumber") || - rawExecutionPayloadHasField(e.RawExecutionPayload, "blockAccessList") -} - -func rawExecutionPayloadHasField(payload json.RawMessage, field string) bool { - if len(payload) == 0 { - return false - } - var fields map[string]json.RawMessage - if err := json.Unmarshal(payload, &fields); err != nil { - return false - } - _, ok := fields[field] - return ok + return e.rawHasAmsterdamFields } diff --git a/execution/evm/engine_rpc_client.go b/execution/evm/engine_rpc_client.go index 288535e34a..acb73d3664 100644 --- a/execution/evm/engine_rpc_client.go +++ b/execution/evm/engine_rpc_client.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" ) @@ -71,6 +72,9 @@ func (e *engineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.Fo method := version.forkchoiceUpdatedMethod() methodArgs, err := forkchoiceArgsForMethod(args, method) if err != nil { + if len(unsupportedMethods) > 0 { + return nil, fmt.Errorf("fallback after unsupported fork from %v: %w", unsupportedMethods, err) + } return nil, err } @@ -146,8 +150,15 @@ func (e *engineRPCClient) NewPayload(ctx context.Context, payload *EnginePayload return nil, err } + // The Engine API encodes execution requests as hex strings. Raw [][]byte + // would JSON-encode as base64, and a nil slice as null instead of []. + requests := make([]hexutil.Bytes, len(executionRequests)) + for i, request := range executionRequests { + requests[i] = request + } + var result engine.PayloadStatusV1 - err = e.client.CallContext(ctx, &result, newPayloadMethod(payload), payloadParam, blobHashes, parentBeaconBlockRoot, executionRequests) + err = e.client.CallContext(ctx, &result, newPayloadMethod(payload), payloadParam, blobHashes, parentBeaconBlockRoot, requests) if err != nil { return nil, err } diff --git a/execution/evm/engine_rpc_client_test.go b/execution/evm/engine_rpc_client_test.go index dd5ac513aa..6a71fd09f4 100644 --- a/execution/evm/engine_rpc_client_test.go +++ b/execution/evm/engine_rpc_client_test.go @@ -489,6 +489,61 @@ func TestNewPayload_AmsterdamPayload_UsesV5AndPreservesBlockAccessList(t *testin mu.Unlock() } +func TestNewPayload_ExecutionRequests_EncodedAsHex(t *testing.T) { + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + require.Equal(t, newPayloadV4Method, req.Method) + require.Len(t, req.Params, 4) + require.JSONEq(t, `["0x1234", "0x01"]`, string(req.Params[3])) + return validPayloadStatusJSON, 0, "" + }) + defer srv.Close() + + var envelope EnginePayloadEnvelope + require.NoError(t, json.Unmarshal([]byte(minimalPayloadEnvelopeJSON), &envelope)) + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + _, err := client.NewPayload(context.Background(), &envelope, []string{}, zeroHashHex, + [][]byte{{0x12, 0x34}, {0x01}}) + require.NoError(t, err) +} + +func TestNewPayload_NilExecutionRequests_EncodedAsEmptyArray(t *testing.T) { + srv := fakeEngineServer(t, func(req jsonRPCRequest) (string, int, string) { + require.Equal(t, newPayloadV4Method, req.Method) + require.Len(t, req.Params, 4) + require.JSONEq(t, `[]`, string(req.Params[3])) + return validPayloadStatusJSON, 0, "" + }) + defer srv.Close() + + var envelope EnginePayloadEnvelope + require.NoError(t, json.Unmarshal([]byte(minimalPayloadEnvelopeJSON), &envelope)) + + client := NewEngineRPCClient(dialTestServer(t, srv.URL)) + _, err := client.NewPayload(context.Background(), &envelope, []string{}, zeroHashHex, nil) + require.NoError(t, err) +} + +func TestEnginePayloadEnvelope_MarshalJSON_UsesWireFieldNames(t *testing.T) { + var envelope EnginePayloadEnvelope + require.NoError(t, json.Unmarshal([]byte(minimalAmsterdamPayloadEnvelopeJSON(t)), &envelope)) + + encoded, err := json.Marshal(&envelope) + require.NoError(t, err) + + var fields map[string]json.RawMessage + require.NoError(t, json.Unmarshal(encoded, &fields)) + for _, key := range []string{"executionPayload", "blockValue", "blobsBundle", "executionRequests", "shouldOverrideBuilder"} { + require.Contains(t, fields, key) + } + + // Round trip must preserve Amsterdam fields and version selection. + var decoded EnginePayloadEnvelope + require.NoError(t, json.Unmarshal(encoded, &decoded)) + require.True(t, decoded.hasAmsterdamFields()) + require.JSONEq(t, string(envelope.RawExecutionPayload), string(decoded.RawExecutionPayload)) +} + func TestIsUnsupportedForkErr(t *testing.T) { tests := []struct { name string diff --git a/execution/evm/execution.go b/execution/evm/execution.go index 7e35ec2082..0b64e91e72 100644 --- a/execution/evm/execution.go +++ b/execution/evm/execution.go @@ -1078,6 +1078,11 @@ func (c *EngineClient) buildPayloadAttributes(txsPayload []string, blockHeight u return attrs } +// deriveSlotNumber returns the slotNumber payload attribute required by +// engine_forkchoiceUpdatedV4 (Amsterdam). ev-node has no beacon slots, so the +// rollup block height serves as the deterministic slot number. This is a wire +// contract with ev-reth's Amsterdam chainspec handling and must not change +// once the fork is enabled on a live chain. func (c *EngineClient) deriveSlotNumber(blockHeight uint64) uint64 { return blockHeight } diff --git a/execution/evm/go.mod b/execution/evm/go.mod index 530122b40a..53a1bc89eb 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -8,7 +8,7 @@ replace ( ) require ( - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node v1.1.2 github.com/evstack/ev-node/core v1.0.0 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -38,6 +38,7 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 8aa70a7863..71297dea72 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -119,14 +119,16 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -339,8 +341,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -363,12 +363,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= diff --git a/execution/evm/proposer_test.go b/execution/evm/proposer_test.go index 46d8d5f3a5..498bb6da06 100644 --- a/execution/evm/proposer_test.go +++ b/execution/evm/proposer_test.go @@ -121,10 +121,10 @@ func (proposerEngineRPCClient) ForkchoiceUpdated(context.Context, engine.Forkcho }, nil } -func (proposerEngineRPCClient) GetPayload(context.Context, engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { +func (proposerEngineRPCClient) GetPayload(context.Context, engine.PayloadID) (*EnginePayloadEnvelope, error) { return nil, nil } -func (proposerEngineRPCClient) NewPayload(context.Context, *engine.ExecutableData, []string, string, [][]byte) (*engine.PayloadStatusV1, error) { +func (proposerEngineRPCClient) NewPayload(context.Context, *EnginePayloadEnvelope, []string, string, [][]byte) (*engine.PayloadStatusV1, error) { return nil, nil } diff --git a/execution/evm/test/engine_fork_upgrade_test.go b/execution/evm/test/engine_fork_upgrade_test.go index d4edbac28c..4811c75c44 100644 --- a/execution/evm/test/engine_fork_upgrade_test.go +++ b/execution/evm/test/engine_fork_upgrade_test.go @@ -99,9 +99,9 @@ func TestEngineAPIForkUpgradeCycleE2E(t *testing.T) { for _, step := range steps { t.Run(step.name, func(t *testing.T) { - nextStateRoot, err := executionClient.ExecuteTxs(ctx, nil, step.height, time.Unix(int64(step.timestamp), 0), stateRoot) + result, err := executionClient.ExecuteTxs(ctx, nil, step.height, time.Unix(int64(step.timestamp), 0), stateRoot) require.NoError(t, err) - stateRoot = nextStateRoot + stateRoot = result.UpdatedStateRoot }) } From d7d761c475ec3c0ac6b3d86554307503f4ceca38 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Wed, 15 Jul 2026 14:32:32 +0200 Subject: [PATCH 4/7] updates --- execution/evm/engine_rpc_client_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/execution/evm/engine_rpc_client_test.go b/execution/evm/engine_rpc_client_test.go index 6a71fd09f4..4d1e8b0046 100644 --- a/execution/evm/engine_rpc_client_test.go +++ b/execution/evm/engine_rpc_client_test.go @@ -104,6 +104,10 @@ const ( zeroHashHex = "0x0000000000000000000000000000000000000000000000000000000000000000" ) +// minimalBlockAccessListJSON is a spec-shaped (EIP-7928) block access list with +// a single account entry, as returned by engine_getPayloadV6. +const minimalBlockAccessListJSON = `[{"address": "0x00000000000000000000000000000000000000aa"}]` + func minimalAmsterdamPayloadEnvelopeJSON(t *testing.T) string { t.Helper() withAmsterdamFields := strings.Replace( @@ -111,7 +115,7 @@ func minimalAmsterdamPayloadEnvelopeJSON(t *testing.T) string { `"excessBlobGas": "0x0"`, `"excessBlobGas": "0x0", "slotNumber": "0x1", - "blockAccessList": ["0x1234"]`, + "blockAccessList": `+minimalBlockAccessListJSON, 1, ) require.NotEqual(t, minimalPayloadEnvelopeJSON, withAmsterdamFields) From 8cd3a39fac77211102cb4d22f8789dba01f91a8a Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Wed, 15 Jul 2026 14:39:14 +0200 Subject: [PATCH 5/7] dep updates --- execution/evm/engine_rpc_client_test.go | 2 +- execution/evm/go.mod | 3 +-- execution/evm/go.sum | 16 ++++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/execution/evm/engine_rpc_client_test.go b/execution/evm/engine_rpc_client_test.go index 4d1e8b0046..567beec2a7 100644 --- a/execution/evm/engine_rpc_client_test.go +++ b/execution/evm/engine_rpc_client_test.go @@ -466,7 +466,7 @@ func TestNewPayload_AmsterdamPayload_UsesV5AndPreservesBlockAccessList(t *testin require.NoError(t, json.Unmarshal(req.Params[0], &payload)) require.Contains(t, payload, "slotNumber") require.Contains(t, payload, "blockAccessList") - require.JSONEq(t, `["0x1234"]`, string(payload["blockAccessList"])) + require.JSONEq(t, minimalBlockAccessListJSON, string(payload["blockAccessList"])) return validPayloadStatusJSON, 0, "" default: return "", -38005, "Unsupported fork" diff --git a/execution/evm/go.mod b/execution/evm/go.mod index 53a1bc89eb..530122b40a 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -8,7 +8,7 @@ replace ( ) require ( - github.com/ethereum/go-ethereum v1.17.4 + github.com/ethereum/go-ethereum v1.17.3 github.com/evstack/ev-node v1.1.2 github.com/evstack/ev-node/core v1.0.0 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -38,7 +38,6 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect - github.com/fjl/jsonw v0.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 71297dea72..8aa70a7863 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -119,16 +119,14 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= -github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= +github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= +github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= -github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= -github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -341,6 +339,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -363,8 +363,12 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= -github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= +github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= +github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= +github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= From 9b24cf93c891d099237b6d4db1e0b871f9d59409 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Wed, 15 Jul 2026 14:41:05 +0200 Subject: [PATCH 6/7] dep updates --- apps/evm/go.mod | 5 +++-- apps/evm/go.sum | 17 ++++++----------- execution/evm/go.mod | 3 ++- execution/evm/go.sum | 16 ++++++---------- execution/evm/test/go.mod | 3 ++- execution/evm/test/go.sum | 16 ++++++---------- test/docker-e2e/go.mod | 3 ++- test/docker-e2e/go.sum | 16 ++++++---------- test/e2e/go.mod | 5 +++-- test/e2e/go.sum | 17 ++++++----------- 10 files changed, 42 insertions(+), 59 deletions(-) diff --git a/apps/evm/go.mod b/apps/evm/go.mod index ef5427a84a..cc3f7fa43a 100644 --- a/apps/evm/go.mod +++ b/apps/evm/go.mod @@ -9,7 +9,7 @@ replace ( ) require ( - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node v1.1.3 github.com/evstack/ev-node/core v1.0.0 github.com/evstack/ev-node/execution/evm v1.0.1 @@ -78,6 +78,7 @@ require ( github.com/ferranbt/fastssz v0.1.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/filecoin-project/go-jsonrpc v0.10.1 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-kit/kit v0.13.0 // indirect @@ -169,7 +170,7 @@ require ( github.com/pion/sctp v1.8.39 // indirect github.com/pion/sdp/v3 v3.0.18 // indirect github.com/pion/srtp/v3 v3.0.6 // indirect - github.com/pion/stun/v3 v3.1.1 // indirect + github.com/pion/stun/v3 v3.1.2 // indirect github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v4 v4.0.1 // indirect github.com/pion/turn/v4 v4.0.2 // indirect diff --git a/apps/evm/go.sum b/apps/evm/go.sum index c3291e9d13..8dc9050f5d 100644 --- a/apps/evm/go.sum +++ b/apps/evm/go.sum @@ -226,8 +226,8 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -240,6 +240,8 @@ github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9g github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-jsonrpc v0.10.1 h1:iEhgrjO0+rawwOZWRNgexLrWGLA+IEUyWiRRL134Ob8= github.com/filecoin-project/go-jsonrpc v0.10.1/go.mod h1:OG7kVBVh/AbDFHIwx7Kw0l9ARmKOS6gGOr0LbdBpbLc= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -617,8 +619,6 @@ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8 github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= -github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -641,13 +641,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= -github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= diff --git a/execution/evm/go.mod b/execution/evm/go.mod index 530122b40a..53a1bc89eb 100644 --- a/execution/evm/go.mod +++ b/execution/evm/go.mod @@ -8,7 +8,7 @@ replace ( ) require ( - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node v1.1.2 github.com/evstack/ev-node/core v1.0.0 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -38,6 +38,7 @@ require ( github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/execution/evm/go.sum b/execution/evm/go.sum index 8aa70a7863..71297dea72 100644 --- a/execution/evm/go.sum +++ b/execution/evm/go.sum @@ -119,14 +119,16 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -339,8 +341,6 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -363,12 +363,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= diff --git a/execution/evm/test/go.mod b/execution/evm/test/go.mod index 66ffaba916..8c41ea0e43 100644 --- a/execution/evm/test/go.mod +++ b/execution/evm/test/go.mod @@ -4,7 +4,7 @@ go 1.25.8 require ( github.com/celestiaorg/tastora v0.20.0 - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node/execution/evm v0.0.0-00010101000000-000000000000 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/ipfs/go-datastore v0.9.1 @@ -82,6 +82,7 @@ require ( github.com/evstack/ev-node/core v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/getsentry/sentry-go v0.31.1 // indirect github.com/go-kit/kit v0.13.0 // indirect diff --git a/execution/evm/test/go.sum b/execution/evm/test/go.sum index bd9e81bc3e..6d779e12d6 100644 --- a/execution/evm/test/go.sum +++ b/execution/evm/test/go.sum @@ -251,8 +251,8 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -261,6 +261,8 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -621,8 +623,6 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -645,12 +645,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= diff --git a/test/docker-e2e/go.mod b/test/docker-e2e/go.mod index 28febe41fb..bbf9d918ca 100644 --- a/test/docker-e2e/go.mod +++ b/test/docker-e2e/go.mod @@ -5,7 +5,7 @@ go 1.25.8 require ( cosmossdk.io/math v1.5.3 github.com/celestiaorg/tastora v0.20.0 - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node/execution/evm v1.0.1 github.com/libp2p/go-libp2p v0.48.0 github.com/moby/moby/client v0.4.1 @@ -34,6 +34,7 @@ require ( github.com/evstack/ev-node v1.1.1 // indirect github.com/evstack/ev-node/core v1.0.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect diff --git a/test/docker-e2e/go.sum b/test/docker-e2e/go.sum index 7172445516..39603eb3b3 100644 --- a/test/docker-e2e/go.sum +++ b/test/docker-e2e/go.sum @@ -317,8 +317,8 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/evstack/ev-node v1.1.1 h1:J9h5PKx177XdvNWLZCDOkWJEGRIrPzYxkCFhbGkVUm8= github.com/evstack/ev-node v1.1.1/go.mod h1:/d/i+SSTDFnxffoijcrwmlt0LgfUU8D4S3HQqucwtu8= github.com/evstack/ev-node/core v1.0.0 h1:s0Tx0uWHme7SJn/ZNEtee4qNM8UO6PIxXnHhPbbKTz8= @@ -335,6 +335,8 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU= github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -819,8 +821,6 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -843,12 +843,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= diff --git a/test/e2e/go.mod b/test/e2e/go.mod index bec5de4217..735e1e1f66 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -8,7 +8,7 @@ require ( github.com/celestiaorg/tastora v0.20.0 github.com/cosmos/cosmos-sdk v0.53.6 github.com/cosmos/ibc-go/v8 v8.8.0 - github.com/ethereum/go-ethereum v1.17.3 + github.com/ethereum/go-ethereum v1.17.4 github.com/evstack/ev-node v1.1.2 github.com/evstack/ev-node/execution/evm v0.0.0-20250602130019-2a732cf903a5 github.com/evstack/ev-node/execution/evm/test v0.0.0-00010101000000-000000000000 @@ -137,6 +137,7 @@ require ( github.com/ferranbt/fastssz v0.1.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect github.com/filecoin-project/go-jsonrpc v0.10.1 // indirect + github.com/fjl/jsonw v0.1.0 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/getsentry/sentry-go v0.35.0 // indirect @@ -264,7 +265,7 @@ require ( github.com/pion/sctp v1.8.39 // indirect github.com/pion/sdp/v3 v3.0.18 // indirect github.com/pion/srtp/v3 v3.0.6 // indirect - github.com/pion/stun/v3 v3.1.1 // indirect + github.com/pion/stun/v3 v3.1.2 // indirect github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v4 v4.0.1 // indirect github.com/pion/turn/v4 v4.0.2 // indirect diff --git a/test/e2e/go.sum b/test/e2e/go.sum index 8dcaadb62c..b2eaad79d1 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -409,8 +409,8 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2 github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.3 h1:Ev/sQHH+UdKZHWjuVzhu2pxhi/sXaPZl23Q+Q5LDd4Q= -github.com/ethereum/go-ethereum v1.17.3/go.mod h1:f2EhRwqewIZkGoQekywI2Y2RZAMTSavLNkD9qItFy1A= +github.com/ethereum/go-ethereum v1.17.4 h1:uA4q+qiLp7QImBsjdRbINu8iX6OEVmj4DPc5/E5Fsxc= +github.com/ethereum/go-ethereum v1.17.4/go.mod h1:qMdgwqqRAen+aT8P7KKQKi0Qt6RzG4cfejVAbCpJgqA= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -423,6 +423,8 @@ github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9g github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs= github.com/filecoin-project/go-jsonrpc v0.10.1 h1:iEhgrjO0+rawwOZWRNgexLrWGLA+IEUyWiRRL134Ob8= github.com/filecoin-project/go-jsonrpc v0.10.1/go.mod h1:OG7kVBVh/AbDFHIwx7Kw0l9ARmKOS6gGOr0LbdBpbLc= +github.com/fjl/jsonw v0.1.0 h1:V3MyR79fjLpn/+bMgvegdGUIhoJOzjmqWcKDgcOmY1I= +github.com/fjl/jsonw v0.1.0/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -999,8 +1001,6 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= -github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc= github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= @@ -1023,13 +1023,8 @@ github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= -github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= -github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= -github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= +github.com/pion/stun/v3 v3.1.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY= +github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= From 768db3461bca4870342adf20632a9e13ef79c509 Mon Sep 17 00:00:00 2001 From: tac0turtle Date: Wed, 15 Jul 2026 15:05:36 +0200 Subject: [PATCH 7/7] test with pr-266 --- execution/evm/engine_payload.go | 27 +++++++++++++--- execution/evm/engine_rpc_client_test.go | 31 +++++++++++++++++-- .../evm/test/engine_fork_upgrade_test.go | 20 ++++++++---- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/execution/evm/engine_payload.go b/execution/evm/engine_payload.go index c08f67070a..ef68f88ceb 100644 --- a/execution/evm/engine_payload.go +++ b/execution/evm/engine_payload.go @@ -45,10 +45,6 @@ func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'executionPayload' for EnginePayloadEnvelope") } - var payload engine.ExecutableData - if err := json.Unmarshal(dec.ExecutionPayload, &payload); err != nil { - return err - } var amsterdamProbe struct { SlotNumber json.RawMessage `json:"slotNumber"` BlockAccessList json.RawMessage `json:"blockAccessList"` @@ -56,6 +52,29 @@ func (e *EnginePayloadEnvelope) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(dec.ExecutionPayload, &amsterdamProbe); err != nil { return err } + + // Execution clients disagree on the blockAccessList JSON schema (reth + // encodes the raw access-list bytes as a hex string, go-ethereum uses + // structured objects). Strip the field before the typed decode so either + // shape is accepted; RawExecutionPayload keeps it for passthrough. + typedPayload := dec.ExecutionPayload + if amsterdamProbe.BlockAccessList != nil { + var fields map[string]json.RawMessage + if err := json.Unmarshal(dec.ExecutionPayload, &fields); err != nil { + return err + } + delete(fields, "blockAccessList") + sanitized, err := json.Marshal(fields) + if err != nil { + return err + } + typedPayload = sanitized + } + + var payload engine.ExecutableData + if err := json.Unmarshal(typedPayload, &payload); err != nil { + return err + } e.ExecutionPayload = &payload e.RawExecutionPayload = append(e.RawExecutionPayload[:0], dec.ExecutionPayload...) e.rawHasAmsterdamFields = amsterdamProbe.SlotNumber != nil || amsterdamProbe.BlockAccessList != nil diff --git a/execution/evm/engine_rpc_client_test.go b/execution/evm/engine_rpc_client_test.go index 567beec2a7..6b39961fd9 100644 --- a/execution/evm/engine_rpc_client_test.go +++ b/execution/evm/engine_rpc_client_test.go @@ -104,9 +104,12 @@ const ( zeroHashHex = "0x0000000000000000000000000000000000000000000000000000000000000000" ) -// minimalBlockAccessListJSON is a spec-shaped (EIP-7928) block access list with -// a single account entry, as returned by engine_getPayloadV6. -const minimalBlockAccessListJSON = `[{"address": "0x00000000000000000000000000000000000000aa"}]` +// minimalBlockAccessListJSON matches ev-reth's wire encoding of +// executionPayload.blockAccessList in engine_getPayloadV6 responses: the +// encoded access list as a hex string. go-ethereum instead models the field as +// structured JSON objects; ev-node must accept either shape and pass it +// through unchanged (see TestEnginePayloadEnvelope_StructuredBlockAccessList). +const minimalBlockAccessListJSON = `"0x1234"` func minimalAmsterdamPayloadEnvelopeJSON(t *testing.T) string { t.Helper() @@ -528,6 +531,28 @@ func TestNewPayload_NilExecutionRequests_EncodedAsEmptyArray(t *testing.T) { require.NoError(t, err) } +func TestEnginePayloadEnvelope_StructuredBlockAccessList(t *testing.T) { + structured := strings.Replace( + minimalAmsterdamPayloadEnvelopeJSON(t), + `"blockAccessList": `+minimalBlockAccessListJSON, + `"blockAccessList": [{"address": "0x00000000000000000000000000000000000000aa"}]`, + 1, + ) + + var envelope EnginePayloadEnvelope + require.NoError(t, json.Unmarshal([]byte(structured), &envelope)) + require.True(t, envelope.hasAmsterdamFields()) + + payload, err := envelope.executionPayloadParam() + require.NoError(t, err) + raw, ok := payload.(json.RawMessage) + require.True(t, ok) + + var fields map[string]json.RawMessage + require.NoError(t, json.Unmarshal(raw, &fields)) + require.JSONEq(t, `[{"address": "0x00000000000000000000000000000000000000aa"}]`, string(fields["blockAccessList"])) +} + func TestEnginePayloadEnvelope_MarshalJSON_UsesWireFieldNames(t *testing.T) { var envelope EnginePayloadEnvelope require.NoError(t, json.Unmarshal([]byte(minimalAmsterdamPayloadEnvelopeJSON(t)), &envelope)) diff --git a/execution/evm/test/engine_fork_upgrade_test.go b/execution/evm/test/engine_fork_upgrade_test.go index 4811c75c44..c5190d2875 100644 --- a/execution/evm/test/engine_fork_upgrade_test.go +++ b/execution/evm/test/engine_fork_upgrade_test.go @@ -33,6 +33,12 @@ const ( forkCyclePragueTimestamp = uint64(12) forkCycleOsakaTimestamp = uint64(24) forkCycleAmsterdamTimestamp = uint64(36) + + // defaultEvRethForkTag pins the ev-reth image built from + // https://github.com/evstack/ev-reth/pull/266 (reth 2.3), the first build + // with Amsterdam Engine API support. Replace with a release tag once that + // PR ships. EV_RETH_IMAGE_REPO/EV_RETH_TAG override this. + defaultEvRethForkTag = "pr-266" ) // TestEngineAPIForkUpgradeCycleE2E drives ev-node's EngineClient against a real @@ -41,8 +47,9 @@ const ( // ev-node, so this verifies the V4 -> V5 -> V6 upgrade path instead of only // asserting that blocks are produced. // -// The existing Tastora ev-reth image is used by default. Set EV_RETH_TAG, or set -// EV_RETH_IMAGE_REPO with optional EV_RETH_TAG, to point at a local or registry image. +// The ghcr.io/evstack/ev-reth image tagged defaultEvRethForkTag is used by +// default. Set EV_RETH_TAG, or set EV_RETH_IMAGE_REPO with optional EV_RETH_TAG, +// to point at a local or registry image. func TestEngineAPIForkUpgradeCycleE2E(t *testing.T) { if testing.Short() { t.Skip("skipping due to short mode") @@ -136,11 +143,12 @@ func withEngineForkTimes(t testing.TB, osakaTime, amsterdamTime uint64) reth.Gen func rethImageOptFromEnv() RethNodeOpt { repo := strings.TrimSpace(os.Getenv("EV_RETH_IMAGE_REPO")) tag := strings.TrimSpace(os.Getenv("EV_RETH_TAG")) - if repo == "" && tag == "" { - return nil - } if tag == "" { - tag = "latest" + if repo == "" { + tag = defaultEvRethForkTag + } else { + tag = "latest" + } } return func(b *reth.NodeBuilder) { if repo != "" {