AN EXECUTION CLIENT
FOR THE EVM ECOSYSTEM

LinuxmacOSWindowsDocker
Architecture

How Fukuii is built

Execution is separated from consensus, and network-specific behavior is a parameter rather than a branch in the engine. That is what lets one binary serve networks with different consensus models, and lets a new network be a definition instead of a rewrite.

docker run ghcr.io/fukuii-project/fukuii-cli
Scala 3PekkoJVMRocksDB
Design principles

What the structure is for

These commitments shape everything below. They are stated as constraints rather than goals, because each one rules out a shortcut that would otherwise be tempting.

Chain identity is a parameter

Not a global assumption.

  • Chain ID, gas schedule and precompiles set per instance
  • Transaction types and state commitment configurable
  • Nothing network-specific compiled in

Consensus is a module

The Engine API is the boundary.

  • Any driver of newPayload and forkchoiceUpdated qualifies
  • Proof-of-Work, Proof-of-Authority, rollup derivation
  • No branching on the consensus model in shared code

One runtime, many chains

Each with its own everything.

  • Separate state database per network
  • Separate chain configuration and consensus driver
  • Natural for L2, whose driver reads L1 state
The layers

The layers, read top to bottom

Each layer depends only on the one beneath it. Swapping the consensus driver, adding a network, or deriving L2 blocks all happen above the execution engine, so none of them reaches the EVM. Reward and finalization are a per-family hook, which is why there is no branch on the consensus model anywhere in shared code.

CONSENSUS DRIVER

pluggable

Whatever decides which block is canonical. ETChash for Proof-of-Work, a consensus layer over the Engine API for Proof-of-Stake, Clique or QBFT for permissioned networks, and the same seam takes rollup derivation and ZK verification. Anything that can drive the Engine API is a valid driver.

CHAIN ENVIRONMENT

fukuii-env

The parameters that make one network different from another: chain identity, genesis, the fork schedule, the gas schedule and fee mechanics. EVM divergences live here rather than in the engine, so a new network is a definition instead of a code change.

EXECUTION ENGINE

fukuii-core

Consensus-agnostic and reusable: EVM bytecode execution, state trie and world state, transaction validation, block construction and the state transition, plus the JSON-RPC and Engine API surface. It holds no consensus logic and no chain-specific behavior.

The module graph

How the layering is enforced

Fukuii is built as a set of modules, and each one declares exactly which others it is allowed to use. A module can only use modules below it in this table.

Code that reaches upward does not compile. If something in the EVM tried to call into the RPC layer the build would reject it, so the separation holds on its own instead of depending on someone catching it in review. Each module also builds and runs its tests without the others, which is what makes any one of them possible to work on alone.

Build modules and their declared dependencies, top of the stack downward
ModuleDepends onHolds
nodeall of the belowComposition root. The only module that sees everything
rpcdomain, execution, consensus, sync, network, storageJSON-RPC, GraphQL, Engine API
syncnetwork, consensus, execution, storage, trieChain synchronization
consensus-pow · consensus-posconsensus-api, executionOne module per consensus family
consensus-apiexecution, evm, domainThe seam a consensus family plugs into
networkdomain, crypto, rlp, commonWire protocols, discovery, peering
executionevm, trie, storage, domainState transition and block processing
evmdomain, crypto, rlpBytecode execution
triedomain, crypto, storageState trie
storagedomain, commonPersistence
domainbytes, crypto, rlp, commonPure value types, no upward edges
crypto · rlpbytesPrimitives
bytes · commonnothingFoundation, true leaves
The runtime

Why this stack, for this problem

A node is a long-lived process doing a great deal of concurrent I/O at once: peer management, gossip, chain sync, the transaction pool, block import, execution, RPC serving and indexing. Each of those has to stay responsive while the others are busy with expensive work, and all of them have to survive a network that misbehaves rather than merely tolerating one that does not. That is the shape of the problem, and it is what the stack was chosen against.

Apache Pekko actors match that shape most directly. Every subsystem in the module list above is an actor tree that communicates by message rather than by shared memory, which is what makes backpressure, failure isolation and supervised restart properties of the structure instead of things to remember.

Scala 3 carries the part that changes. Fork rules, state transitions and validation pipelines are where a protocol evolves, and a type system that turns an unhandled case into a build failure is worth more there than anywhere else in the codebase.

The JVM is the runtime rather than the language, chosen for long-running behavior. A process that stays up for months gives a JIT every opportunity to optimize the paths that actually run, and the tooling to inspect it is already standard rather than something this project had to build. The Engine API is the seam that makes the consensus driver replaceable on top of all of it.

One process, many networks

Actors give each network its own supervision tree.

  • Own state, own metrics registry, own config
  • Separate dispatchers for sync, rpc and general work, so sync cannot starve the RPC
  • One actor owns its state, so nothing else can reach in and change it

A failure is contained

Supervision restarts a subtree, not the process.

  • A failing network restarts under its supervisor
  • The other networks keep syncing and serving throughout
  • The blast radius of a fault is the subtree it happened in

Bounded under load

Streams pull only as fast as they can process.

  • Backpressure from the consumer to the producer
  • A fast peer cannot grow the heap faster than sync drains it
  • Memory stays bounded during a full sync rather than tracking peer speed

Bugs that cannot be written

The type system rejects them at compile time.

  • Algebraic data types for consensus state
  • Exhaustive pattern matching on every variant
  • Immutable data by default, so shared state cannot race
  • Errors surface in the build, not in a block

Proven libraries, not rewrites

The JVM ecosystem does the parts it already does well.

  • BouncyCastle for cryptography, with a long audit history
  • RocksDB for storage, through its own JNI bindings
  • Nothing security-critical reimplemented for the sake of it

The toolchain comes free

It is a JVM process like any other.

  • JFR, async-profiler, JMX, heap dumps
  • No foreign-language bridge to cross
  • Nothing new to learn to diagnose it