Architecture
Anvil256 — Architecture
Three components. Zero servers. No coordinator.
┌──────────────────────┐ ┌───────────────────────────┐ ┌──────────────────────────────┐│ Static website │ │ CLI (Rust) │ │ Anvil256.sol ││ Astro Starlight │ │ γ-counter read │ │ Base L2 ││ IPFS + Cloudflare │ │ epoch_entropy fetch │ │ immutable ERC-20 + PoW ││ no backend │ │ nonce search dispatch │ │ PI + NCT controller ││ │ │ tx construction + submit │ │ Cascade verifier │└──────────▲───────────┘ └────────────▲──────────────┘ └──────────────▲───────────────┘ │ │ │ │ browser, viem read-only │ operator RPC (HTTPS, EIP-1559) │ same RPC └─────────────────────────────┴───────────────────────────────────┘ no Anvil256 infrastructure in the loopOn-Chain / Off-Chain Split
What Lives On-Chain
| Component | State | Formal Invariant |
|---|---|---|
| ERC-20 | balanceOf, totalSupply, transfer | totalSupply <= 21,000,000e18 (I1) |
| Mining state | epoch, difficulty , currentReward(), integralErrorWad , lastMineBlock, epochEntropy , period start timestamp | (I5); $ |
| Miner identity | gamma(m) = minerEpochCount[m] — monotonic per-address counter | strictly non-decreasing (I13) |
| NCT window | minerWindow[256] packed circular buffer, windowHead, uniqueCount , windowFreq[m] | (I11) |
| Cascade verifier | mine(ν) recomputes on-chain; asserts | I12 |
| Distribution | Valid mine(): mint to msg.sender, mint up to to POL reserve, increment , update NCT window | |
| Fee | Split currentFeeWei(): 50% to feeRecipient, 50% to lpReserveEthWei; failed dev transfer parks in stuckFeesWei | I7: dev + stuck + lpReserve = fee exactly |
| Adjustment | Every epochs: PIController.step(...) updates before entropy is written | — |
| Entropy | `epochEntropy = H(entropySource | |
| Halving | , clamped to 0 at epoch | — |
What Lives Off-Chain
| Component | Description | Mathematical Role |
|---|---|---|
| read | CLI reads before each job dispatch — one eth_call per epoch | required to compute |
| fetch | CLI reads epochEntropy from contract — mandatory round-trip, cannot be skipped | is the outer-pass input; withholding it makes nonce computation impossible (Theorem 4.1, SECURITY.md). EntropyFallback event indicates prevrandao-only path was used (PATCH-1). |
| Nonce search | CUDA/OpenCL kernel iterates: find s.t. | Brute-force search over ; expected iterations |
| Tx construction | CLI reads currentFeeWei(), attaches as msg.value, signs EIP-1559 tx | Ensures I7 is satisfiable |
| Stats site | Browser reads on-chain state via viem — no server, no cache | — |
Cascade PoW — Off-Chain Computation
The mining kernel receives a pre-computed job from the CLI:
JOB <inner_hex64> <epoch_entropy_hex64> <difficulty_hex64> <job_id>where inner_hex64 is precomputed by the CLI
(requiring both from chain and the current epoch ).
Kernel computation. For each in the search space:
If : FOUND. Expected iterations before first success:
is held in GPU constant memory for the duration of the epoch — no per-batch network call needed. The mandatory network round-trips (for and ) occur once per epoch transition, not once per batch.
Security note. The kernel never receives the raw miner address or the private key — only the derived value. Compromise of the kernel process exposes only (a one-way derivative of , , ), not the signer key.
CLI ↔ Kernel Protocol
host → kernel (line-oriented, plain text): JOB <inner_hex64> <epoch_entropy_hex64> <difficulty_hex64> <job_id> STOP EXIT
kernel → host (JSON, one object per line): {"type":"ready", "devices":[...]} {"type":"progress", "job":J, "device":"…", "hashes":H, "hashrate":R, "elapsed_ms":T} {"type":"found", "job":J, "nonce":"<decimal>", "hash":"0x…"} {"type":"error", "message":"…"}The CLI precomputes (requiring from chain and miner address) before dispatching the job. The kernel never sees the raw miner address — it only operates on the derived value, ensuring:
is a one-way commitment to the miner’s identity, verifiable on-chain without exposing to the kernel process.
On-Chain State Machine
┌─────────────────────┐ │ epoch n, D[n] │ ◄──── mine(ν) called └──────────┬──────────┘ │ κ(m,ν,n) < D[n]? ┌────────┴────────┐ no yes │ │ revert ┌────▼───────────────────────────────────┐ │ γ(m)++ │ │ win[h] ← m; h ← (h+1) mod 256 │ │ uniqueCount ← updated C_u │ │ mint R(n) to msg.sender │ │ mint 0.1R(n) to POL reserve if cap allows │ │ split currentFeeWei(): dev + LP reserve │ │ │ │ if (currentEpoch % 2016 == 0): │ │ e[n] ← timing error │ │ I[n] ← sat(I[n−1]+e, ±I_MAX) │ │ u_nct ← |nctSignal()| (≥ 0) │ │ u ← sat(u_pi + u_nct, ±0.5) │ │ D[n+1] ← sat(D[n]·exp4(u), D/4,4D) │ │ │ │ ε[n+1] ← H(entropySource ∥ D_cur) │ │ entropySource=H(blockhash ∥ prevrandao ∥ ts) │ │ (PATCH-1 fallback: H(prevrandao ∥ ts) if bh=0) │ │ (D_cur = D[n+1] at boundary, else D[n])│ └─────────────────────────────────────────┘ │ epoch n+1 beginsOperator Trust Model
Formal trust assumptions. The protocol requires no trust in any party except the operator’s own infrastructure:
| Trust Required | Rationale |
|---|---|
| Own machine | Kernel runs locally; key signs locally; no remote attestation |
| Own RPC endpoint | Malicious RPC can censor but cannot steal: CLI verifies eth_getTransactionReceipt |
| Chainlink oracle | Compromise fee spike only; client-side MAX_FEE_USD guard |
Formal trust exclusions. These parties have zero protocol-level authority:
| Party | Why No Trust Needed |
|---|---|
| Anvil256 maintainers | Contract is immutable and verified; admin key |
| Pool operators | No pool protocol exists or is necessary |
| Centralised gateways | None in the architecture |
| Other miners | No coordination primitive; each mine() is fully independent |
| Post-deployment deployer | Constructor ensures (I3); privileged function post-deploy |
The operator’s private key is the unique secret in the system. All other values — , , , — are either public on-chain state or deterministic functions thereof.