rpc: implement testing_commitBlockV1 - #22403
Conversation
Adds testing_commitBlockV1 to the testing_ namespace as the write companion to testing_buildBlockV1, per the execution-apis spec proposal (ethereum/execution-apis#801) and mirroring go-ethereum's implementation (ethereum/go-ethereum#34995). The method builds a block on top of the current canonical head from the supplied payload attributes, transactions (null = mempool, [] = empty block, [...] = exactly these, strict nonce check) and optional extraData, inserts it, validates it via the staged sync, and advances the fork choice with safe/finalized preserved. Any failure leaves the canonical head unchanged (fork choice is the last step). extraData is now a builder.Parameters field consumed by the block builder, so the assembled block carries the requested extra data (and the correct hash) from the start; this replaces the post-hoc header rewrite testing_buildBlockV1 used to do on its response. The shared attribute validation and synchronous assembly are extracted from BuildBlockV1 into assembleTestingBlock; both endpoints share the one-slot deadline budget and engine-lock busy polling.
There was a problem hiding this comment.
Pull request overview
This PR adds testing_commitBlockV1 to Erigon’s testing_ RPC namespace as the “write” companion to testing_buildBlockV1, and refactors shared block assembly so both endpoints use the same builder path (including proper builder-level extraData application).
Changes:
- Implement
testing_commitBlockV1(assemble → insert → validate → forkchoice update) and preserve safe/finalized hashes. - Refactor
BuildBlockV1to reuse a sharedassembleTestingBlockhelper and forwardextraDatainto the block builder instead of patching responses. - Extend the block builder to accept per-build
ExtraData, add comprehensive unit tests, and document the new RPC method.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| execution/engineapi/testing_api.go | Adds CommitBlockV1, introduces shared assembly helper, and forwards extraData into the builder parameters. |
| execution/engineapi/testing_api_test.go | Extends the execution-module stub and updates BuildBlockV1 tests for builder-level extraData forwarding. |
| execution/engineapi/testing_api_commit_test.go | New unit tests covering CommitBlockV1 success paths, validation/errors, busy handling, and immutability on failure. |
| execution/builder/parameters.go | Adds ExtraData to builder parameters to support per-call overrides. |
| execution/builder/create_block.go | Applies ExtraData override to the block header when provided via builder parameters. |
| cmd/rpcdaemon/README.md | Documents testing_buildBlockV1 and testing_commitBlockV1 as implemented (testing-only). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
yperbasis
left a comment
There was a problem hiding this comment.
LGTM — verified locally (build, vet, new tests incl. -race on the PR head) and cross-checked against the spec proposal, geth's implementation, and the chainreader/execmodule semantics the new code relies on.
Minor suggestions, none blocking:
- The
CommitBlockV1docstring's "On any failure the canonical head is left unchanged" has one exception: module FCU runs on a background context, so if the slot budget expires mid fork choice, the call returns the busy error but the head can still advance asynchronously afterwards. Worth qualifying in the docstring (or the busy error message). InsertBlockis the only step guarded by barelock.Lock()/Unlock()withoutdefer— a panic would leave the engine lock held. Suggest the closure+deferidiom used elsewhere in the file.- Optional: a
len(*extraData) > 32pre-check returning-32602would fail fast instead of building+inserting a block thatValidateChainthen rejects with-32000(and would stopbuildBlockV1from returning an uncommittable payload). Geth has the same gap, so parity holds either way. - Test nit: the
err.(rpc.Error).ErrorCode()assertions in the invalid-fork-choice and reorg-too-deep subtests panic rather than fail if the error type is ever wrong;require.ErrorAsis cleaner. - Pre-existing, possible follow-up: with
extraDatanull, erigon falls back toBuilderConfig.ExtraData(defaulterigon-<version>, so block hashes vary by erigon version), while geth's testing path forces empty extra. Spec fixtures always pass"0x"explicitly, but consider matching geth for determinism.
On the Copilot comments: the decodeTxnProvider "testing_buildBlockV1" error-prefix fix and the stale stub comment are worth taking; the deadline point is negligible in practice (CurrentHeader/GetForkChoice are fast read-only lookups, no semaphore) — moving the slotDeadline call to the top of CommitBlockV1 satisfies it for free if you care.
|
…ockV1 Address yperbasis's review of #22403: guard InsertBlock with defer, compute slotDeadline up front to cover CurrentHeader/GetForkChoice, reject extraData over 32 bytes early with -32602, force empty extra data on nil (matching geth's determinism), use require.ErrorAs for the engine error code assertions, drop the stale "testing_buildBlockV1" error prefix in decodeTxnProvider now that it's shared with CommitBlockV1, and fix the stubExecutionModule doc comment.
Closes #22392
Implements
testing_commitBlockV1, the write companion totesting_buildBlockV1, in thetesting_RPC namespace (enabled via--http.api=...,testing, never on production networks).Spec conformance
Follows the execution-apis proposal ethereum/execution-apis#801:
payloadAttributes(required),transactions(array | null),extraData(optional) — noparentHash: the block is always built on top of the current canonical head, as the spec mandates ("the client MUST build a new execution payload on top of its current canonical head").transactions = []→ builds an empty block (mempool bypassed);null→ builds from the local mempool; non-empty array → exactly those transactions, in order, with a strict nonce pre-check against state.UpdateForkChoiceruns asynchronously in the execution module, so if the slot budget expires mid fork choice the call returns a busy error but the head can still advance afterwards.-32602invalid params (includingextraDataover 32 bytes),-32000for nonce/validation failures,-38002/-38006for invalid fork choice / too-deep reorg.Parity with go-ethereum
Mirrors geth's implementation (ethereum/go-ethereum#34995): same signature (
payloadAttributes, *[]hexutil.Bytes, *hexutil.Bytes→common.Hash), same build → insert → set-canonical flow (erigon-native equivalent:AssembleBlock/GetAssembledBlock→InsertBlock→ValidateChain→UpdateForkChoice), same extraData handling through the block builder. Erigon is additionally stricter than geth: it validates timestamp > parent and pre-checks nonces, returning a clear error instead of a failed build.As in geth,
extraDatais applied by the block builder itself: it is now abuilder.Parametersfield consumed increate_block.go(nil-guarded for the production engine path, which never sets it and is unchanged). This also fixestesting_buildBlockV1, which previously patched extraData onto its response after the fact instead of building the block with it. Within thetesting_namespace, anilextraDatanow forces empty extra data rather than falling back to the builder's configured default (which embeds the erigon version) — matching geth's testing path and keeping block hashes deterministic across erigon versions.extraDatalonger than 32 bytes is rejected up front with-32602instead of building and inserting an uncommittable block.Unit tests (TDD)
Written before the implementation (red → green). 24 sub-tests in
testing_api_commit_test.go:-38002invalid fork choice;-38006reorg too deep;-32000with validation error messagePlus updated
BuildBlockV1tests covering the shared assembly/validation path and the builder-level extraData propagation (including the same extraData size and determinism cases).InsertBlockis guarded with the closure+deferidiom used elsewhere in the file, so a panic can't leave the engine lock held.slotDeadlineis computed up front inCommitBlockV1, beforeCurrentHeader/GetForkChoice, so those calls are covered by the one-slot budget too.Verification
make lintclean,make test-shortgreen (only pre-existing unrelated failure:cmd/rpcdaemon/graphql)execution/engineapisuite green (serial exec mode), including the engineapitester end-to-end block-production tests that exercise thecreate_block.gochangerpc-compatsuite was run against an erigon image built from this branch — 234 tests, 0 failures, confirming no regression across the whole RPC surface,testing_namespace includedtesting_namespace on both clients and compare erigon's responses against geth's, verifying the two implementations behave identicallyRelated