Repository navigation
Conversation
…EI-1086) A follower runs the finalizer and syncer, so it executes every finalized block against its own execution client and tracks validator-set changes, but it runs no Simplex, joins no P2P peer set, and reads no keystore. It fetches finalized blocks from other nodes over JSON-RPC instead. Sources are untrusted: every reply goes through the syncer's `handle_deliver`, the same block/certificate checks a P2P peer's reply gets. Serving side: - `--follower-rpc-port` / `--follower-rpc-ip` start an opt-in listener (loopback by default) serving `getResolverValue` and `getLatestHeight`. `getResolverValue` hands the key to the syncer's resolver handler (`Handler::try_produce`), so RPC and P2P serve identical bytes. - The listener has its own limits: no batching, 16 KiB requests, 100 connections, 1024 requests waiting on the syncer, and responses sized from genesis `max_message_size_bytes`. A full listener or mailbox returns 3008. - The resolver handler is now created in `Engine::new` and passed into `resolver::p2p::init`, so the RPC server can share it. Following side (`summit run --follow <url>...`, conflicts with `--observer`): - `syncer::resolver::opaque` wraps commonware's opaque resolver behind a transport-free `Source` trait. Each attempt goes to a random source, bounded by the engine's fetch timeout. - `node::follow`: `RpcSource` (JSON-RPC `Source`), a follow `Driver` that runs in place of the orchestrator (registers epoch verifiers, polls source tips, hints the syncer at most 256 heights ahead and never past the entered epoch's last height), and a null network/oracle for the actors that still expect P2P. - `Engine::start_follow` wires these together. The application and orchestrator are built but never started. The engine runs verifier-only with throwaway keys. - The follower's RPC returns 4005 for `getPublicKeys` and doesn't start the admin listener. Follow-ups: - #481 removes the workarounds follow mode ships with, because the syncer and orchestrator still assume P2P and Simplex: the null broadcast network, keeping the unstarted application alive for its cancel-on-drop, and the driver copying the orchestrator's verifier registration. Step 1 ports marshal's unbuffered start into the syncer. Step 2 makes the orchestrator generic over the per-epoch engine, so followers run an epoch fetcher in it and `follow/driver.rs` goes away. - Separately, the node's mode is derived in several places today: `Option<KeyStore>` (None means follower), `flags.observer`, and a `follower: bool` passed to the RPC server. `run_node_inner` and `run_node_local_inner` also each duplicate the validator/observer setup. Parsing the flags once into a `RunMode { Validator, Observer(index), Follower(sources) }` and matching on it would remove those proxies and the duplication.
samlaf
marked this pull request as draft
October 29, 2026 20:27
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.
Closes SEI-1086
Have been trying to figure out how to do rpc nodes in TEE world (see SEI-1055) and there are some issues with the current observer mode (mainly that all observers share the same ed25519 keys with the main node, so would need a transport). Sharing this draft PR for a "follow-mode" which is similar but doesn't require any consensus awareness. IMO this is simpler and would be easier to setup for rpc nodes since they can just setup their topology however they want, and create caching services, etc. without having to join the actual commonware p2p mesh.
Looks like a lot of code but the idea is quite simple, and represented by this diagram:

Most of the code is just plumbing and wiring. commonware's marshal (which we fork in sync) recent version even already has a follow-up which we could use in our fork. And the code could be made more generic between the node, observer, and follower modes, but have opted to keep this PR as small as possible just to get feedback, and have mentioned those possible cleanups in #481
LLM Summary
A follower runs the finalizer and syncer, so it executes every finalized block against its own execution client and tracks validator-set changes, but it runs no Simplex, joins no P2P peer set, and reads no keystore. It fetches finalized blocks from other nodes over JSON-RPC instead. Sources are untrusted: every reply goes through the syncer's
handle_deliver, the same block/certificate checks a P2P peer's reply gets.Serving side:
--follower-rpc-port/--follower-rpc-ipstart an opt-in listener (loopback by default) servinggetResolverValueandgetLatestHeight.getResolverValuehands the key to the syncer's resolver handler (Handler::try_produce), so RPC and P2P serve identical bytes.max_message_size_bytes. A full listener or mailbox returns 3008.Engine::newand passed intoresolver::p2p::init, so the RPC server can share it.Following side (
summit run --follow <url>..., conflicts with--observer):syncer::resolver::opaquewraps commonware's opaque resolver behind a transport-freeSourcetrait. Each attempt goes to a random source, bounded by the engine's fetch timeout.node::follow:RpcSource(JSON-RPCSource), a followDriverthat runs in place of the orchestrator (registers epoch verifiers, polls source tips, hints the syncer at most 256 heights ahead and never past the entered epoch's last height), and a null network/oracle for the actors that still expect P2P.Engine::start_followwires these together. The application and orchestrator are built but never started. The engine runs verifier-only with throwaway keys.getPublicKeysand doesn't start the admin listener.Follow-ups:
follow/driver.rsgoes away.Option<KeyStore>(None means follower),flags.observer, and afollower: boolpassed to the RPC server.run_node_innerandrun_node_local_inneralso each duplicate the validator/observer setup. Parsing the flags once into aRunMode { Validator, Observer(index), Follower(sources) }and matching on it would remove those proxies and the duplication.