Conversation
Move the module tree into `lib.rs` so other testing crates can reuse `LocalNetwork`, the checks and the retry helper instead of duplicating node orchestration. The `simulator` binary keeps its subcommands and now builds on the library.
Let tests drive a production beacon node's proof pipeline without an `ere-verifier` build or a Beacon API: - `ClientConfig::proof_engine_override` injects a ready-built `ProofEngine` (never serialized) ahead of the `proof_engine` config, so each node can run its own mock verifier. - `Client::network_senders()` and `Client::network_globals()` expose the network channels and peer state for publishing proofs and reading peer scores. - `PendingPayloadCache::cached_execution_proof_types()` reports which proof types are cached for a block. - `ProofEngine` implements `Debug` so it can live in `ClientConfig`.
Add `testing/execution_proof_network_tests`, a deterministic multi-node harness for EIP-8025 execution proofs built on the simulator's `LocalNetwork`. Tests choose per node whether it runs a mock proof engine, which proof bytes it accepts and whether it carries validators; sign proof envelopes with the interop validator keys; submit them through a node (verify, cache, publish) or inject them unverified onto gossip; and observe verification, caching, storage and payload import on every node with bounded waits that print a per-node snapshot on timeout. Four scenarios cover the Gloas baseline (verifiers hold a payload until two proof types arrive), the end-to-end submit, propagate, verify and import path, invalid proof data rejected by a verifier that does not accept it, and an unsupported proof type rejected before verification. The two scenarios that need proof verification are ignored on this base: gossip verification loads the envelope from the store, which a proof-engine node fills only after two proofs, so submission fails with `PayloadUnavailable`. They carry the full assertions for when that is fixed. Tests bind the simulator's fixed ports, so `make test-execution-proof-network` runs them sequentially in release and the workspace test targets exclude the crate.
Keep `ClientConfig::proof_engine_override` out of production builds: the field, its default and the builder branch now exist only with the new `client/test-utils` feature, and `Config::proof_engine_override()` returns `None` without it. The execution proof network test crate enables the feature, and feature unification makes the field available to `beacon_node` and `node_test_rig` in that build.
…er/build-lighthouse-execution-proof-network-test-crate
`add_beacon_node_without_mock_execution_layer` starts a beacon node without pairing it with a mock execution node and keeps the caller's `execution_layer` config: `None` for a node that validates payloads with execution proofs alone, or an endpoint the caller runs itself, such as a proxy in front of its own mock. `add_beacon_node` is unchanged.
Adopt the merged base in the execution proof network test crate:
- Node modes follow the base's semantics. Execution-layer nodes carry the
validators and import payloads once executed; verifiers also verify,
cache and serve proofs; proof-only nodes have no execution layer and
hold every payload until two proof types verify.
- Proofs are submitted through `POST /eth/v1/beacon/execution_proofs`
and observed through `GET /eth/v1/beacon/execution_proofs/{block_id}`
as well as the chain's caches, and `ProofStatus` reports how many
proofs a node requires.
- `ProvingExecutionLayer` generates proofs on the network: a JSON-RPC
proxy in front of a node's mock execution layer that, when
`engine_newPayload` returns `VALID`, resolves the beacon block carrying
the payload through the Beacon API, signs one envelope per configured
proof type and posts them to the node's API. `NodeSpec::proving`
attaches it; different nodes can prove different types.
Eight scenarios cover the baseline, Beacon API submission and retrieval,
a proving node keeping a proof-only node in sync, two provers combining
one type each, a six-node network, invalid proof data, an unsupported
proof type and a two-node network where the proof-only node is the only
verifier. All run on this base; none are ignored.
Add an `execution-proof-network-tests-ubuntu` job next to the simulator jobs. It installs `cargo-nextest` and runs `make test-execution-proof-network`, which executes the crate's scenarios one at a time in release because each starts several beacon nodes on fixed ports. The job is required by `test-suite-success`.
Let the `proof_engine` crate own how an engine is built from its config. `ProofEngineConfig::build_engine` returns an injected test engine when one was supplied through the `test-utils`-gated `with_engine`, and otherwise builds the ERE verifiers (or fails without `ere-verifier`). The client builder calls it instead of carrying both feature branches, and `ClientConfig` loses its test-only field and `test-utils` feature. Equality on `ProofEngineConfig` covers the configured verifiers only. The execution proof network test crate enables `proof_engine/test-utils` and injects each node's mock engine with `with_engine`. Its network start-up awaits are boxed so `ProofNetwork::build` keeps a small stack frame under `clippy::large_stack_frames`.
`ProofNetwork::add_node` starts another beacon node once the network is running, so scenarios can observe how a node that joins late behaves. The new scenario shows what this base does for a proof-only node that joins a few slots behind: lookup sync routes its envelopes through the proof gate, the proofs for those payloads were gossiped before it existed and nothing re-serves them, so it stops at the first full block. Blocks after it are never admitted, so the proofs it does receive for later payloads are dropped as unknown blocks, while the prover still serves the missed proofs over its Beacon API. The crate docs no longer claim that this waits on RPC proof retrieval; they record the per-payload gate, the refused successor block and the difference between the lookup and range sync paths instead.
`NodeSpec::with_syncing_execution_layer` starts a node whose mock execution layer answers `SYNCING`, and `ProofNetwork::set_execution_layer_syncing` flips that at runtime. `ProofStatus` documents that its fork choice fields read false once a block is finalized and pruned, and the harness can read a block's `execution_optimistic` flag and look up roots by slot. Two scenarios record how Gloas handles payloads the execution layer has not validated. At the head, an envelope with an optimistic status is rejected (`OptimisticSyncNotSupported`), so the node stalls while the chain moves on and catches up once the execution layer answers `VALID` again. Far behind, range sync imports every such envelope, fork choice records the payload as received, and the block is reported as not optimistic. Pre-Gloas optimistic sync settled such payloads later; Gloas has no provisional payload state, which is the same gap that keeps a proof-only node from using a later proof to cover its ancestry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
testing/execution_proof_network_tests, a reusable deterministic harness for multi-node EIP-8025 execution-proof integration tests, built on the simulator'sLocalNetworkrather than a second node orchestrator. The branch is merged withoptional-proofs-gloasafter #42 and #43, and uses both: proof-only nodes and theexecution_proofsBeacon API endpoints.NodeSpec): execution-layer nodes (carry the validators, import payloads once executed), verifiers (execution layer plus proof engine: verify, cache and serve proofs without gating import), and proof-only nodes (no execution layer: hold every payload until two proof types verify). Validators only ever sit on execution-layer nodes.MockProofEnginethroughProofEngineConfig::with_engine, gated behind atest-utilsfeature on theproof_enginecrate and never serialized, so valid and invalid proof data are chosen per scenario.ProofEngineConfig::build_enginereturns the injected engine or builds the ERE verifiers, so the client builder no longer carries theere-verifierbranches andClientConfighas no test-only field. Production builds oflighthousedo not enable the feature.ProvingExecutionLayer, attached withNodeSpec::proving): a JSON-RPC proxy in front of a node's mock execution layer. It forwards every engine call; whenengine_newPayloadreturnsVALIDit resolves the beacon block carrying the payload through the node's Beacon API (headers by parent root, matched on the bid's block hash), signs one envelope per configured proof type with a deterministic interop validator key, and posts them toPOST /eth/v1/beacon/execution_proofs. Different proving nodes can supply different proof types.submit_execution_proofposts to a node's Beacon API;publish_execution_proofinjects an unverified proof onto gossip, as a faulty peer would;retrieved_proof_typesreadsGET /eth/v1/beacon/execution_proofs/{block_id}.ProofStatussnapshots, per node, whether the block is known, a valid proof of a type was verified, which proof types the pending payload cache holds, whether the envelope is pending or stored, whether fork choice has the payload, and how many proofs the node requires. Waits are bounded and print every node's mode, head, payload status and peer count on timeout.Production and simulator changes are small and additive: the simulator gains a library target and
add_beacon_node_without_mock_execution_layer,Clientexposesnetwork_senders()andnetwork_globals(), andProofEngineimplementsDebugso it can live inClientConfig.Scenarios
execution_layer_nodes_import_payloads_while_proof_only_nodes_waitproofs_posted_to_the_beacon_api_reach_every_verifier_and_unlock_proof_only_importproving_execution_layer_keeps_a_proof_only_node_in_syncproofs_from_two_provers_combine_to_unlock_importlarger_network_delivers_proofs_to_every_verifierinvalid_proof_data_is_rejected_by_verifiers_that_do_not_accept_itInvalidProof, gossip rejection with peer penalty, an honest prover of the same type still lands, and a type node 3 does not accept leaves its payload lockedunsupported_proof_type_is_rejected_before_verificationUnsupportedProofTypeat the API and on gossip, with peer penaltyproof_only_node_can_submit_its_own_proofssyncing_execution_layer_stalls_a_node_at_the_head_until_it_recoversVALIDagainrange_sync_imports_payloads_a_syncing_execution_layer_never_verifiedSYNCINGanswers, and the node reports those blocks as not optimisticlate_joining_proof_only_node_stalls_on_proofs_it_missedFindings on the base (for the EL-optional owners, not changed here)
PendingComponents::make_availablerequires the configured proof types for that payload; a verified proof for a descendant does not mark ancestors received (on_valid_payload_envelope_receivedtouches one root). A proof for payload N should attest to N and its ancestry.ParentPayloadNotVerified, so a later proof is dropped asUnknownBlockRoot. Without a prover a proof-only node stops at the first full block (baseline scenario).SLOT_IMPORT_TOLERANCE, 32 slots) routes envelopes throughprocess_execution_payload_envelopeand the gate, so a late joiner stalls on proofs gossiped before it joined (late-joiner scenario). By inspection, range sync usesprocess_range_sync_envelope, which gives a proof-only nodePayloadVerificationStatus::Irrelevantand imports without proofs; that path is not exercised here. Recursion-aware gating (finding 1) would let the near joiner recover from the proofs it does receive; proof retrieval from peers is the alternative.into_executed_payload_enveloperejects an optimistic status withOptimisticSyncNotSupported(theTODO(gloas)above it notes optimistic sync was not re-added), while range sync passes the optimistic status to import, where only events and metrics see it; fork choice records the payload as received and the API reports the block as not optimistic (syncing-execution-layer scenarios). Pre-Gloas,propagate_execution_payload_validationsettled such payloads later; restoring that state for Gloas, with an execution-layer verdict or a verified proof as the settling event, would fix findings 1 to 4 together.Validation
Base:
a62a9709da55d98664f4d903e75041f71aae23d8(origin/optional-proofs-gloas, merged into this branch).cargo fmt --all -- --checkcargo clippywith the Makefile lint flags:proof_engine,client,beacon_nodewithouttest-utils; the test crate,proof_engine,simulatorwith itcargo nextest run -p execution_proof_network_tests --release --test-threads 1CI runs the suite in a dedicated
execution-proof-network-tests-ubuntujob (required bytest-suite-success). Locally, run it withmake test-execution-proof-network(cargo nextest run -p execution_proof_network_tests --release --test-threads 1). Tests bind the simulator's fixed ports, so they are serialised in-process and ignored in debug builds.Reviewer focus
testing/execution_proof_network_tests/src/proving_execution_layer.rs: the proxy, block-root resolution and proof submission.testing/simulator/src/local_network.rs: nodes without a mock execution layer keep the caller'sexecution_layerconfig.beacon_node/proof_engine/src/config.rs: thetest-utils-gated engine onProofEngineConfig,build_engine, and the manualPartialEqthat ignores it.testing/execution_proof_network_tests/src/tests.rs: topologies and assertions.