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, 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.
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.
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.
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.
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.
| Module | Depends on | Holds |
|---|---|---|
| node | all of the below | Composition root. The only module that sees everything |
| rpc | domain, execution, consensus, sync, network, storage | JSON-RPC, GraphQL, Engine API |
| sync | network, consensus, execution, storage, trie | Chain synchronization |
| consensus-pow · consensus-pos | consensus-api, execution | One module per consensus family |
| consensus-api | execution, evm, domain | The seam a consensus family plugs into |
| network | domain, crypto, rlp, common | Wire protocols, discovery, peering |
| execution | evm, trie, storage, domain | State transition and block processing |
| evm | domain, crypto, rlp | Bytecode execution |
| trie | domain, crypto, storage | State trie |
| storage | domain, common | Persistence |
| domain | bytes, crypto, rlp, common | Pure value types, no upward edges |
| crypto · rlp | bytes | Primitives |
| bytes · common | nothing | Foundation, true leaves |
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
