Nodes
Nodes are long-running processes that apply an application's state and state transition function by processing events, updating metadata, and optionally producing attestations or proofs, while exposing APIs for external interaction. At a high level, a node continuously:
- Fetches new events from a source chain or oracle (Fetcher)
- Applies the app’s state transition to a local state (StateBuffer)
- Updates metadata (heights, hashes, roots), and archives state snapshots (Archiver)
- Optionally produces an attestation (signature) or a zero-knowledge proof for the new state (Attester/Prover)
- Optionally exposes an API (ex. JSON-RPC) to read state and metadata
Under the hood, node code calls into one of the void-node loops:
run_app(RunConfig, ...)– execution-onlyrun_attesting_app(RunAttestingConfig, ...)– execution + attestationsrun_proving_app(RunProvingConfig, ...)– execution + ZK proving
For startup, they either:
start_app(state_buffer, fetcher, archiver)– initialize from archive, then start fetchingstart_app_with(start_state, start_metadata, state_buffer, fetcher)– initialize from an explicit state (e.g., benchmarks)
Node flavors and examples
-
Execution node (execution only)
- Example:
e2e/zk/node/ - Uses
EvmChainEventFetcher,ByteArchiver,StreamingKvStateBuffer,run_app
- Example:
-
Attesting node (TEE / signature-based)
- Example:
e2e/tee/node/ - Uses
OracleEventFetcher(multiple EVM RPCs),ByteArchiver,StreamingKvStateBuffer,ECDSAAttester,run_attesting_app
- Example:
-
Proving node (ZK)
- Example:
e2e/zk/proving-node/host/ - Uses
EvmChainEventFetcher,ByteArchiver,StreamingKvStateBuffer,Risc0Prover,run_proving_app
- Example:
-
Benchmark node (uses test scenario)
- Example:
benchmarks/max-tps/ - Uses
TestEventFetcher,ByteArchiver,StreamingKvStateBuffer,start_app_with,run_app
- Example:
Core components (from void/void-node/)
-
Fetchers (
fetcher)- Role: connect to an event source and stream “bundles” of events, tracking heights and latest height boundaries (useful during syncing).
- Implementations (feature-gated):
test-fetcher– in-memory test/scenario driver (benchmarks)evm-fetcher– reads events from an EVM chainoracle-fetcher– reads via an oracle stream (supports multiple EVM endpoints)
-
Archivers (
archiver)- Role: persist and restore state and metadata; create periodic snapshots according to a min-height interval.
- Implementations (feature-gated):
byte-archiver– byte-oriented archive (used by examples)empty-archiver– no-op (useful for tests)
-
State Buffers (
state_buffer)- Role: own the app State and StateMetadata with safe concurrent access; expose read/write closures and a read-only
StateReaderfor powering APIs (ex. JSON-RPC). - Implementations (feature-gated):
stream-state-buffer– streaming KV buffer with concurrent readers (uses more memory, but allows for near limitless reads with low performance overhead)lock-state-buffer– lock-based buffer (reads slow down performance, but uses less memory)empty-state-buffer– no-op (useful for tests)
- Role: own the app State and StateMetadata with safe concurrent access; expose read/write closures and a read-only
-
Provers and Attesters (
prover/attester)- Role: produce proofs/attestations of an apps state (
state_root) and processed events (events_hash). - Attester (
StateAttester): produces a signature attesting to{events_hash, state_root}at intervals. Example:ECDSAAttestertakes a hex private key (or reads from env if not provided explicitly). - Attester Implementations (feature-gated):
ecdsa– takes a hex private key (or reads from env if not provided explicitly) to sign over{events_hash, state_root}.
- Prover (
StateProver): produces a recursive proof for the transition from prior proof + new events + witness. - Prover Implementations (feature-gated):
r0/r0-cuda– proves using the Risc Zero zkVM.sp1– proves using the SP1 zkVM.
- Role: produce proofs/attestations of an apps state (
Runtime flow
1) Startup
start_app_with(...)when you already have an initial state/metadata (benchmarks)start_app(...)to restore from archive and continue syncing
2) Loop (all variants)
- Fetch next events bundle
- Apply
state_transition(state, &events) - Flush state (
state.flush()orstate.flush_with_witness()for ZK) - Update metadata:
latest,sync_height,sync_hash,events_hash,state_root, and proof fields - When at configured height interval, archive snapshot via
archiver.archive
3) Additional steps by variant
- Attesting: when at configured height interval, call
attester.attest, and write to metadata - Proving: when at configured height interval, build witness, call
prover.prove, and update metadata with returned proof
JSON-RPC surface
Nodes often start a lightweight Axum-based server to expose read APIs over the StateBuffer’s StateReader, using additional tools found in void/void-node/.
- For example, the nodes in the end-to-end tests add an
rpc_routerand callaxum_server::start("127.0.0.1:<port>", rpc_router). - The
json_rpcmodule invoid/void-node/provides helpers to build routers from aStateBufferand method list.
Extending or writing your own node
To customize a node:
- Pick the appropriate fetcher (or implement
Fetcher) - Choose an archiver policy (or implement
Archiver<S>) - Choose a state buffer (or implement
StateBuffer<S>) - Decide whether you need attestations (
StateAttester) or proofs (StateProver) (or neither), and use the corresponding run loop - Expose the parts of state you need via the JSON-RPC helpers (or implement custom server solution)