Skip to content

feat: modularize the "consensus" engine so it can be ran standalone #28

Description

@nuntax

Alternative Arbitrum execution clients still need Nitro for message ingestion. Nitro runs in consensus mode and exposes its execution API over JSON-RPC, and the other client implements the server side.

arbitrum-reth does both halves in one binary. Fine for a standalone node, but an operator can't swap in just one half.

Split it so arbitrum-reth can run three ways:

Mode Role
standalone (today) consensus + execution
execution serves the API, driven by Nitro or by arbitrum-reth
consensus drives the API, against Nitro's gethexec or another client

Interface

Nitro defines it in execution/interface.go, served under the nitroexecution JSON-RPC namespace (execution/rpcserver, execution/rpcclient).

  • ExecutionClient: DigestMessage, Reorg, HeadMessageIndex, ResultAtMessageIndex, SetFinalityData, SetConsensusSyncData, MarkFeedStart, maintenance hooks, ArbOSVersionForMessageIndex
  • ExecutionRecorder: validators and stakers
  • ExecutionSequencer: sequencing

Only ExecutionClient is in scope. It's enough for a following node.

The seam already exists

  • L1 derivation and the feed follower push ordered BroadcastFeedMessages into a channel, and ArbEngineDriver reads them one at a time. That channel is the boundary.
  • arb_revm's digest_message is the same operation as Nitro's DigestMessage.
  • Messages are already numbered by message index rather than block number (L1SyncConfig::genesis_block), which is what the API uses.
  • Reorg maps to the existing rewind path, SetFinalityData to forkchoice safe/finalized.

So step 1 below is mostly a refactor.

Why

Adoption: operators can replace one half at a time instead of taking the whole binary.

Differential testing: run arbitrum-reth consensus against Nitro execution, and the reverse. A divergence is then isolated to one side before any block-level debugging. Most parity work here starts with "derivation or execution?", and this answers it directly.

Work

  1. Extract an ArbExecutionClient trait from the driver. The current in-process path becomes the first impl. No behaviour change, no new mode, lands on its own.
  2. --mode execution: serve nitroexecution over JSON-RPC. Testable against nitro-testnode with Nitro as consensus.
  3. --mode consensus: an RPC-backed impl of the same trait, so the driver drives a remote client unchanged.

Open questions

  • Nitro hashes MessageWithMetadata (Hash() over its RLP), so the encoding has to match byte for byte, not just structurally. Same for MessageResult.
  • Nitro's methods return PromiseInterface. Over RPC they're already awaited, so this only matters if the in-process path is ever exposed to a Go caller.
  • SetConsensusSyncData and the maintenance hooks have no equivalent here. Unclear which are needed for a following node and which are Nitro-internal.
  • Where the database lives in split mode, and which side owns the resume checkpoint.
  • Whether ExecutionRecorder is needed to be useful to validators, or is a follow-up.

Acceptance

  • Standalone unchanged, step 1 with no behavioural diff.
  • Nitro consensus plus arbitrum-reth execution gives the same block hashes as standalone over a range on nitro-testnode.
  • arbitrum-reth consensus plus Nitro's gethexec gives the same hashes over the same range.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions