op-devstack: run lokahi as the supernode - #22602
Open
claude[bot] wants to merge 13 commits into
Open
Conversation
The single-chain presets can already swap their per-chain CL between op-node and the Rust kona-node via DEVSTACK_L2CL_KIND, but the multi-chain and interop presets silently ignore it: their CL is a supernode hosting every chain behind one RPC, and there is no per-chain node to swap. Selecting the supernode implementation needs its own knob, because a run can legitimately want kona-node for the single-chain presets and lokahi for the multi-chain ones. Add DEVSTACK_SUPERNODE_KIND (op-supernode | lokahi), defaulting to the in-process Go op-supernode so an unset environment behaves exactly as before, and dispatch on it in both shared-supernode start paths before anything is started. An unrecognized value fails rather than being ignored. lokahi cannot host a chain yet -- rust/lokahi is a CLI skeleton that prints a greeting and exits -- so startLokahiSupernode fails immediately and says why, naming the two gaps that have to close first: the per-chain RPC routes, and stack.SupernodeTestControl handing tests an in-process *interop.Interop pointer that no out-of-process implementation can satisfy. Landing the seam now means the switch is written and reviewed once rather than re-derived when lokahi grows a node; its config struct carries the same inputs the Go path turns into an snconfig.CLIConfig, so the two cannot drift in what they get. DEVSTACK_L2CL_KIND=lokahi now fails with a pointer at the right variable instead of falling through to op-node and reporting the run as a lokahi one. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Covers the default, both explicit values, rejection of unknown values, and that DEVSTACK_SUPERNODE_KIND is independent of DEVSTACK_L2CL_KIND -- the acceptance CI variant sets both. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
The kona acceptance variant is where op-supernode gets swapped for lokahi, rather than a new job: the interop presets have no op-node to swap for kona-node, so today they run twice unchanged -- 83 of the 92 devstack instantiations under op-acceptance-tests/tests/interop and tests/supernode are identical between memory-all-opn-op-reth and the kona variant. Rename the variant to memory-all-kona-lokahi-op-reth-<fork> so it names the components it runs, and thread a supernode_kind parameter through to DEVSTACK_SUPERNODE_KIND. l2_cl_kind keeps selecting kona-node for the single-chain presets from the same job. supernode_kind stays op-supernode: lokahi is still a CLI skeleton, and this job is a ci-gate requirement, so selecting lokahi now would fail every supernode and interop test. Flipping it is a one-line change once lokahi can host a chain. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Brings in the rust-docs fix for kona-engine's private_intra_doc_links. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
NetworkConfig::new leaves gossip_config at libp2p::gossipsub::Config::default(), whose ValidationMode::Strict is rejected by the MessageAuthenticity::Anonymous the gossip behaviour is constructed with. Every chain therefore failed to compose with GossipDriverBuilder(BehaviourError(GossipsubCreationFailed)), so the supernode could not start a single chain. kona-node never hits this because its CLI always overrides the field; take the same kona_gossip defaults it starts from, which also attach the per-chain message-id label. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Each chain answers on its own socket with the method set a single-chain node has; this adds one server that answers for the process. It reports the chain set the configuration resolved to, and it is the only endpoint whose address a caller can learn without knowing the configuration: it may bind port 0 and logs the address it got, which is what lets an out-of-process launch wait for one line rather than N. The surface stays small on purpose. It does not report liveness — a chain's own RPC answering is the signal a caller needs, and this server would be guessing at it. The controls that do need process-wide reach, pausing and resuming interop and looking into backfill, belong here for the same reason and arrive with the phases that need them. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Turns the selection seam into a launch: the binary is built or located through rustbin, its logs are piped into the test logger, and startup waits for lokahi to say its admin RPC is listening — the same handshake startMixedKonaNode performs on kona-node. With N chains a flag per chain per setting is not a usable interface, so the configuration is a generated file instead of an environment, and the chain set lokahi resolved from it is read back over lokahi_chains so a mistake in generating it fails here rather than becoming a chain that never started. Each chain gets its own socket, fronted by a proxy so callers keep one URL, rather than a /<chainID> route on a shared server as op-supernode has. The shared-supernode presets still turn lokahi away, and now say why: they drive the supernode through stack.SupernodeTestControl, whose InteropActivity() returns an in-process *interop.Interop pointer no out-of-process supernode can provide. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
A two-L2 world where each chain is produced by its own op-node sequencer and batcher and then verified by one lokahi process over a second execution layer per chain. Nothing joins the two sides but L1, so a safe head advancing under lokahi is lokahi deriving that chain rather than being handed it. The test asserts the three things a supernode has to get right that a single-chain node cannot get wrong: both chains derive, each endpoint answers for its own chain, and taking one chain's execution layer away stalls that chain alone while the process and the other chain carry on. Also builds and paths the binary, without which none of this could run: lokahi is not a workspace default member, so the release job's package list and the acceptance job's RUST_BINARY_PATH_* exports both skipped it, and the persisted kona-* glob would not have covered it either. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
The unsafe block signer was taken from the batcher address, which is not the key sequenced blocks are signed with. kona-node never has to be told: it resolves the signer from the chain's SystemConfig contract when no flag gives it one. lokahi has no such path and a devnet chain is not in the superchain registry, so the devstack states the address the sequencer's P2P key signs with. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
TestLokahiTwoChains timed out waiting for either chain's safe head to leave genesis, and lokahi made no engine call to either verifier execution layer for the whole three minutes. A kona validator gates derivation behind EL-sync completion and only marks EL sync complete once a forkchoiceUpdated comes back VALID; in validator mode the only thing that starts that is an unsafe payload arriving over gossip, whose parents the execution layer then backfills from its peer. The runtime peered neither side, so the derivation actor sat in AwaitingELSyncCompletion forever. op-node has no such gate, which is why the op-node runtimes derive from L1 unpeered, and why the isolated-verifier SDM test is op-node-only. Peer each chain's lokahi endpoint and verifier execution layer with the node producing that chain, the way every other devstack runtime does. The mesh supplies the unsafe head only: the safe head is still set solely by attributes derived from L1, so the test still proves what it says it proves. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5 # Conflicts: # rust/kona/bin/node/src/flags/metrics.rs # rust/kona/crates/node/engine/src/lib.rs # rust/kona/crates/node/engine/src/state/core.rs # rust/kona/crates/node/engine/src/task_queue/core.rs # rust/kona/crates/node/engine/src/task_queue/tasks/consolidate/task.rs # rust/kona/crates/node/engine/src/task_queue/tasks/insert/task.rs # rust/kona/crates/node/engine/src/task_queue/tasks/seal/task.rs # rust/kona/crates/node/engine/src/task_queue/tasks/seal/task_test.rs # rust/kona/crates/node/engine/src/task_queue/tasks/task.rs # rust/kona/crates/node/engine/src/test_utils/engine_state.rs # rust/kona/crates/node/service/src/actors/chain_controller/controller.rs # rust/kona/crates/node/service/src/lib.rs # rust/kona/crates/node/service/src/service/node.rs # rust/kona/crates/protocol/protocol/src/sync.rs # rust/kona/crates/providers/providers-alloy/src/beacon_client.rs # rust/kona/crates/providers/providers-alloy/src/pipeline.rs # rust/kona/crates/providers/providers-local/src/buffer.rs # rust/kona/tests/node/reorgs/l2_reorg_after_l1_reorgs_test.go
Develop's #22466 removed the HashMap collect in beacon_client.rs; the merge resolution kept this branch's import line, which all-features clippy rejects as an unused import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
…m develop merge) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Sebastian Stammler <seb@oplabs.co> Claude-Session: https://claude.ai/code/session_01WEDeDWcHt2X1mZYhvHmnZ5
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.
Requested by Sebastian Stammler · Slack thread
Part of #22533, under the #22521 epic.
Runs
lokahias a real out-of-process supernode in the devstack, hosting two chains, and gates it on a test that both chains actually derive.Before: selecting
DEVSTACK_SUPERNODE_KIND=lokahifailed fast with a config dump, because nothing launched. Separately,lokahicould not start a chain at all — every chain died in gossipsub construction — and the release job never built the binary, so there would have been nothing to launch.After: the seam launches the binary, waits for it to say it is listening, and hands out one endpoint per chain. A two-chain acceptance test asserts both chains derive a safe head from L1, that each endpoint answers for its own chain, and that taking one chain's execution layer away stalls that chain alone.
How
lokahigains a process-wide admin RPC (lokahi_chains,lokahi_version). It may bind port 0 and logs the address it got, so an out-of-process launch waits for one line rather than one per chain, and the chain set it ended up with is read back to catch a mis-generated config.startMixedKonaNode:rustbinlocates or builds the binary, logs are piped into the test logger, and startup blocks on the log line. The configuration is a generated file rather than an environment, because with N chains a flag per chain per setting is not a usable interface.NetworkConfig::newleftgossip_configat libp2p's default, whoseValidationMode::Strictthe gossip behaviour rejects outright; andlokahiis not a workspace default member, so the release job's package list, its persistedkona-*glob, and the acceptance job'sRUST_BINARY_PATH_*exports all skipped it.The shared-supernode presets still turn
lokahiaway, and now say why: they drive the supernode throughstack.SupernodeTestControl, whoseInteropActivity()returns an in-process*interop.Interoppointer that no out-of-process supernode can provide. Replacing it with something RPC-shaped is separate work; until then the component is covered by the preset built for it.Based on #22599.