py-ethclient

February 27, 2026 · View on GitHub

The Python L2 development platform — build application-specific ZK rollups in pure Python

Python 3.12+ License: MIT Tests LOC

py-ethclient is a Python L2 development platform for building application-specific ZK rollups. Define your state transition function as a plain Python function, and py-ethclient handles the rest — sequencing, batching, Groth16 proving, and L1 verification.

Built on a fully independent Ethereum L1 execution client inspired by ethrex (Rust). It connects directly to the Ethereum P2P network via devp2p/RLPx, implements the EVM with 140+ opcodes, and supports full sync and snap sync for Mainnet and Sepolia. The built-in Groth16 ZK proving, L1↔L2 General State Bridge, and application-specific rollup framework make it the fastest way to prototype L2 protocols and ZK circuits.

All core protocol logic — RLP encoding, Merkle Patricia Trie, EVM execution, RLPx transport encryption, eth/68 and snap/1 wire protocols, Discovery v4, Engine API, Groth16 ZK proving, L1↔L2 bridge, and L2 rollup framework — is implemented from scratch in pure Python. Only cryptographic primitives and the web framework are external dependencies.

한국어 README

Table of Contents

Key Features

  • Application-Specific ZK Rollup — Define your rollup logic as a Python function (State Transition Function), plug it into the Rollup orchestrator, and get automatic sequencing, Groth16 proving, and L1 verification out of the box
  • 4 Pluggable Interfaces — StateTransitionFunction, DAProvider, L1Backend, ProofBackend — swap any component without touching the rest
  • Full Prove-Verify Pipeline — Sequencer → Batch → Groth16 Proof → L1 Verification, all in a single Python process
  • L2 RPC API — 7 l2_* JSON-RPC methods for transaction submission, state queries, batch production, and proof submission
  • L1↔L2 General State Bridge — Optimism-style CrossDomainMessenger with pluggable relay handlers (EVM, Merkle proof, ZK proof, TinyDB, direct state), force inclusion (anti-censorship), and escape hatch (value recovery)
  • Groth16 ZK Toolkit — Circuit definition, trusted setup, proof generation, native + EVM verification, gas profiling, snarkjs compatibility — all in pure Python
  • Full EVM — 140+ opcodes, precompiles (ecrecover, SHA-256, RIPEMD-160, modexp, BN128, BLAKE2f, KZG), EIP-1559/2929/2930/4844/7702 support
  • Ethereum P2P Networking — RLPx encrypted transport, eth/68 and snap/1 wire protocols, Discovery v4 with Kademlia routing
  • Sync Modes — Full sync (sequential block execution) and snap sync (4-phase parallel state download)
  • JSON-RPC 2.0 — 20+ methods including eth_call, eth_estimateGas, transaction/receipt lookups, log queries, zk_ and l2_ namespaces
  • Engine API V1/V2/V3forkchoiceUpdated, getPayload, newPayload with JWT authentication for consensus layer integration
  • Persistent Storage — LMDB-backed disk backend with hybrid overlay pattern for atomic state commits
  • Multi-Network — Mainnet, Sepolia, and Holesky with per-network genesis and fork configurations
  • 1,031 Tests — Comprehensive test suite covering all protocol layers from RLP to ZK proving to L2 rollup to end-to-end integration
  • Docker Support — Ready-to-use Docker Compose setup for quick deployment

Why py-ethclient?

Ethereum client diversity is critical for network resilience. py-ethclient is the only Ethereum execution client written in Python, making it uniquely valuable for:

  • Application-Specific L2 Development — Write your rollup logic as a plain Python function. The framework handles sequencing, batching, Groth16 proving, and L1 verification. No Solidity, no circom, no complex toolchains — just Python
  • ZK Circuit Development — Define circuits in Python, generate proofs, and test on-chain verification in a single Jupyter notebook. No circom/snarkjs/Solidity toolchain needed — the fastest way to prototype ZK applications, especially with AI coding agents
  • Education & Research — Python's readability makes it the best codebase for understanding how Ethereum actually works at the protocol level. Every component (EVM, RLPx, Merkle tries, sync) is implemented in clear, readable Python
  • Rapid Prototyping — Test new EIPs, custom opcodes, or consensus changes in hours instead of days. Python's dynamic nature enables fast iteration on protocol experiments
  • Client Diversity — Adding a Python client to the Ethereum ecosystem (alongside Go, Rust, C#, Java) strengthens the network against implementation-specific bugs

Comparison with Other Execution Clients

py-ethclientgethrethnethermind
LanguagePythonGoRustC#
PurposeL2 Development, ZK, EducationProductionProductionProduction
App-Specific RollupBuilt-in frameworkN/AN/AN/A
ZK ProvingBuilt-in Groth16N/AN/AN/A
L2 BridgeBuilt-in CrossDomainMessengerN/AN/AN/A
EVM140+ opcodesFullFullFull
Sync modesFull + SnapFull + Snap + LightFull + SnapFull + Snap + Fast
Engine APIV1/V2/V3V1/V2/V3V1/V2/V3V1/V2/V3
P2P protocolseth/68, snap/1eth/68, snap/1eth/68, snap/1eth/68, snap/1
Code readabilityVery HighHighMediumMedium

L2 Rollup Framework

py-ethclient includes a complete application-specific ZK rollup framework. Define your state transition logic as a plain Python function, and the framework handles sequencing, batching, Groth16 proving, and L1 verification.

Quick Example: Counter Rollup

from ethclient.l2 import Rollup, L2Tx, L2TxType

# 1. Define your State Transition Function — just a Python function
def counter_stf(state, tx):
    count = state.get("count", 0)
    if tx.data.get("action") == "increment":
        state["count"] = count + 1
        return {"new_count": count + 1}

# 2. Create a Rollup with your STF
rollup = Rollup(stf=counter_stf)
rollup.setup()  # Groth16 trusted setup + L1 verifier deployment

# 3. Submit transactions
tx = L2Tx(sender=b"\x01"*20, nonce=0, data={"action": "increment"},
          tx_type=L2TxType.CALL)
rollup.submit_tx(tx)

# 4. Produce batch + prove + verify on L1
batch = rollup.produce_batch()
receipt = rollup.prove_and_submit(batch)

assert receipt.verified          # L1 accepted the proof
assert rollup.state["count"] == 1

How It Works

User Tx → Sequencer → State Transition Function → Batch Assembly

                          L1 Verification ← Groth16 Proof ← DA Storage
  1. Sequencer receives transactions, validates nonces, executes STF with snapshot/rollback
  2. Batch is sealed when max_txs_per_batch is reached or force_seal() is called
  3. Groth16 Prover generates a ZK proof over old_state_root → new_state_root transition
  4. L1 Backend verifies the proof and records the new state root

Pluggable Components

The framework uses 4 pluggable interfaces — swap any component without touching the rest:

InterfaceDefaultDescription
StateTransitionFunctionPythonRuntime (wraps any callable)Your rollup logic
DAProviderLocalDAProvider (in-memory)Data availability storage
ProofBackendGroth16ProofBackendZK proof generation and verification
L1BackendInMemoryL1BackendL1 contract interaction (verifier)
from ethclient.l2 import Rollup, L2Config

# Custom configuration
config = L2Config(
    name="my-rollup",
    chain_id=42170,
    max_txs_per_batch=128,
    batch_timeout=30,
    rpc_port=9545,
)

# Plug in custom components
rollup = Rollup(
    stf=my_stf_function,
    da=my_custom_da,        # implement DAProvider
    l1=my_l1_backend,       # implement L1Backend
    prover=my_prover,       # implement ProofBackend
    config=config,
)

Example Apps

9 ready-to-run app examples demonstrate real-world use cases on the rollup framework:

ExampleDescriptionRun
ERC20 TokenMint, transfer, burn with admin controlpython examples/apps/l2_token.py
Name ServiceENS-style domain register, update, transferpython examples/apps/l2_nameservice.py
VotingProposal creation, weighted voting, quorum-based finalizationpython examples/apps/l2_voting.py
Rock-Paper-ScissorsCommit-reveal game with betting and settlementpython examples/apps/l2_rps_game.py
DEX (AMM)Constant product AMM with LP tokens, 0.3% fee, slippage checkspython examples/apps/l2_dex.py
NFT MarketplaceMint, list, buy, transfer with 5% creator royaltiespython examples/apps/l2_nft_marketplace.py
Multisig WalletN-of-M approval, propose/approve/execute workflowpython examples/apps/l2_multisig.py
Escrow3-party escrow with state machine (Created→Funded→Released/Refunded)python examples/apps/l2_escrow.py
Prediction MarketCreate markets, bet, oracle resolution, proportional payoutpython examples/apps/l2_prediction_market.py

Infrastructure Examples

10 infrastructure examples demonstrate production features:

ExampleDescriptionRun
RPC ServerJSON-RPC server with APIKey/RateLimit/RequestSize middlewarepython examples/infra/l2_rpc_server.py
Persistent StateLMDB persistence + crash recovery via WAL replaypython examples/infra/l2_persistent_state.py
DA ProvidersLocal vs Calldata (EIP-1559) vs Blob (EIP-4844) comparisonpython examples/infra/l2_da_providers.py
Censorship ResistanceForce inclusion + escape hatch when sequencer censorspython examples/infra/l2_censorship_resistance.py
Multi-Batch Ops15-batch loop with prove/submit separationpython examples/infra/l2_multi_batch_ops.py
Config Full-StackL2Config-based backend auto-wiring + NativeProver fallbackpython examples/infra/l2_config_fullstack.py
State BridgeL1↔L2 general state bridge end-to-endpython examples/infra/general_state_bridge.py
Relay ModesEVM, Merkle, ZK proof, TinyDB, Direct relay comparisonpython examples/infra/bridge_relay_modes.py
ZK NotebookGroth16 circuit → setup → prove → verify → EVM demopython examples/infra/zk_notebook_demo.py
ZK Note SettleZK proof-based note settlementpython examples/infra/zk_note_settle.py

Each example follows the same pattern: define an STF → wrap with PythonRuntime → create Rollup → submit txs → produce batches → prove and verify on L1.

Balance Transfer Example

def balance_stf(state, tx):
    action = tx.data.get("action")
    if action == "mint":
        addr = tx.data["to"]
        amount = tx.data["amount"]
        state[addr] = state.get(addr, 0) + amount
        return {"minted": amount, "to": addr}
    elif action == "transfer":
        src, dst = tx.data["from"], tx.data["to"]
        amount = tx.data["amount"]
        if state.get(src, 0) < amount:
            raise ValueError("insufficient balance")
        state[src] -= amount
        state[dst] = state.get(dst, 0) + amount
        return {"transferred": amount}

rollup = Rollup(stf=balance_stf)
rollup.setup()
# mint, transfer, produce batch, prove, verify — all works

L2 CLI

# Scaffold a new rollup project
ethclient l2 init --name my-rollup

# This creates:
#   l2.json      — rollup configuration
#   stf.py       — State Transition Function template

# Start the rollup node (loads stf.py, starts RPC server)
ethclient l2 start --config l2.json --rpc-port 9545

# Generate ZK proofs for all sealed batches
ethclient l2 prove --config l2.json

# Submit proven batches to L1
ethclient l2 submit --config l2.json

L2 RPC API

When running with the L2 module, 7 additional JSON-RPC methods are available:

MethodDescription
l2_sendTransactionSubmit a transaction to the rollup
l2_getStateGet current rollup state
l2_getStateRootGet current Merkle state root
l2_getBatchQuery a sealed batch by number
l2_produceBatchTrigger batch production
l2_proveAndSubmitProve a batch and submit to L1
l2_chainInfoGet rollup chain info (name, chain_id, batch count, etc.)

L2 Bridge

py-ethclient includes an L1↔L2 General State Bridge — an Optimism-style CrossDomainMessenger that relays arbitrary messages between L1 and L2 with real EVM execution on the target domain.

from ethclient.bridge import BridgeEnvironment

# Create L1 + L2 environment (two independent EVMs + watcher)
env = BridgeEnvironment()

# Deposit: Alice sends 1 ETH from L1 to Bob on L2
env.send_l1(sender=alice, target=bob, value=1000)
result = env.relay()  # watcher relays L1→L2
assert result.all_success
assert env.l2_balance(bob) == 1000

# State relay: relay arbitrary calldata to L2 contracts
env.send_l1(sender=alice, target=oracle, data=price_calldata)
env.relay()  # executes calldata on L2's EVM

Anti-Censorship

If an L2 operator censors messages, users can bypass them:

MechanismDescription
Force InclusionRegister censored message on L1 → after 50 blocks, anyone can force-relay to L2
Escape HatchLast resort: recover deposited value directly on L1 when L2 is unresponsive
# Operator censors Alice's message
msg = env.send_l1(sender=alice, target=bob, value=1000)
env.l1_messenger.drain_outbox()  # operator takes but doesn't relay

# Force inclusion path
env.force_include(msg)
env.advance_l1_block(50)  # wait for inclusion window
result = env.force_relay(msg)
assert result.success  # bypasses operator

# Or escape hatch (value recovery on L1)
result = env.escape_hatch(msg)
assert env.l1_balance(alice) == 1000  # value returned

Pluggable Relay Handlers

The bridge supports multiple relay modes — L2 doesn't need to run EVM:

HandlerTrust ModelEVM Required
EVMRelayHandlerOn-chain execution (default)Yes
MerkleProofHandlerMerkle proof against trusted L1 rootNo
ZKProofHandlerGroth16 zero-knowledge proofNo
TinyDBHandlerDocument DB backend (TinyDB)No
DirectStateHandlerTrusted relayerNo
from ethclient.bridge import BridgeEnvironment, StateUpdate, encode_state_updates

# Direct state relay (no EVM needed)
env = BridgeEnvironment.with_direct_state()
updates = [StateUpdate(address=alice, balance=1000)]
env.send_l1(sender=alice, target=bob, data=encode_state_updates(updates))
env.relay()

# ZK proof relay
env = BridgeEnvironment.with_zk_proof(vk)  # pass Groth16 verification key

Run the full demo:

python examples/infra/general_state_bridge.py

ZK Toolkit

py-ethclient includes a Groth16 ZK proving toolkit — the only Python environment where you can define circuits, generate proofs, and test EVM on-chain verification in a single process.

from ethclient.zk import Circuit, groth16
from ethclient.zk.evm_verifier import EVMVerifier

# 1. Define circuit (Python expressions)
c = Circuit()
x, y = c.private("x"), c.private("y")
z = c.public("z")
c.constrain(x * y, z)   # R1CS: x * y = z

# 2. Trusted setup
pk, vk = groth16.setup(c)

# 3. Generate proof
proof = groth16.prove(pk, private={"x": 3, "y": 5}, public={"z": 15}, circuit=c)

# 4. Native verification
assert groth16.verify(vk, proof, [15])

# 5. EVM on-chain verification (uses built-in EVM + ecPairing precompile)
result = EVMVerifier(vk).verify_on_evm(proof, [15])
assert result.success  # gas_used ≈ 210,000

What's Included

ComponentDescription
Circuit BuilderPython operator overloading for R1CS constraint definition
Groth16 ProverFull proving pipeline: R1CS → QAP → trusted setup → proof generation
Native VerifierPairing-based verification with debug mode (intermediate pairing values)
EVM VerifierAuto-generated verifier bytecode using ecAdd/ecMul/ecPairing precompiles
Gas ProfilerPer-precompile gas breakdown for on-chain cost optimization
Poseidon HashZK-friendly hash (~240 R1CS constraints vs ~150,000 for keccak256), with circuit encoding for in-proof Merkle trees
snarkjs CompatImport/export snarkjs JSON format (vkey, proof, public inputs)
ZK RPC APIzk_verifyGroth16, zk_deployVerifier, zk_verifyOnChain endpoints

Why Not circom + snarkjs?

circom + snarkjs + Hardhatpy-ethclient
Languages neededcircom (DSL) + Node.js + SolidityPython only
Tools to installRust compiler + Node.js + Solidity toolchainpip install py-ethclient
Circuit → Proof → Verify5 CLI commands across 3 tools3 Python function calls
EVM testingDeploy to testnetIn-memory EVM, instant
Debug failuresHex dump analysisPython traceback + pairing values
AI agent friendlyMultiple tools, niche DSLPython (best language for AI agents)
Iteration speedMinutes (compile → setup → prove → deploy)Seconds

Run the full demo:

python examples/infra/zk_notebook_demo.py

Requirements

  • Python 3.12+
  • System dependency: snappy library (required to build python-snappy)

macOS

brew install snappy

Ubuntu/Debian

sudo apt install libsnappy-dev

Installation

# Clone the repository
git clone https://github.com/tokamak-network/py-ethclient.git
cd py-ethclient

# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate

# Install in editable mode with dev dependencies
pip install -e ".[dev]"

Docker

# Build and run (mainnet)
docker compose up -d

# Sepolia testnet
NETWORK=sepolia docker compose up -d

# Debug logging
LOG_LEVEL=DEBUG docker compose up -d

# View logs
docker compose logs -f

# Stop
docker compose down

Or build manually:

docker build -t py-ethclient .
docker run -p 30303:30303 -p 8545:8545 py-ethclient --network sepolia

Quick Start

L2 Rollup Mode

# Scaffold a new rollup project
ethclient l2 init --name my-rollup

# Edit stf.py to define your State Transition Function
# Then start the rollup node
ethclient l2 start --config l2.json

L1 Node Mode

# Run with defaults (mainnet, snap sync, ports 30303/8545)
ethclient

# Connect to Sepolia testnet
ethclient --network sepolia

# Full sync mode (instead of snap sync)
ethclient --network sepolia --sync-mode full

# Custom configuration
ethclient --network sepolia --port 30304 --rpc-port 8546 --max-peers 10

# Run with a custom genesis file
ethclient --genesis ./genesis.json --port 30303

CLI Options

L2 Commands

CommandDescription
ethclient l2 init --name <name>Scaffold a new rollup project (creates l2.json + stf.py)
ethclient l2 start --config <path>Start the L2 rollup node (loads STF, starts RPC server)
ethclient l2 prove --config <path>Generate ZK proofs for all sealed batches
ethclient l2 submit --config <path>Submit proven batches to L1

L1 Node Options

OptionDefaultDescription
--networkmainnetNetwork to join (mainnet, sepolia, holesky)
--genesisPath to a custom genesis.json file
--port30303P2P TCP/UDP listen port
--rpc-port8545JSON-RPC HTTP listen port
--max-peers25Maximum number of peer connections
--bootnodesper-network defaultsComma-separated enode URLs for bootstrap
--private-keyauto-generatedsecp256k1 private key for node identity (hex)
--log-levelINFOLogging level (DEBUG, INFO, WARNING, ERROR)
--sync-modesnapSync mode: snap (fast state download) or full (sequential block execution)
--data-dirData directory for persistent storage (in-memory if not set)
--datadirAlias for --data-dir (geth-compatible)
--engine-port8551Engine API JSON-RPC listen port
--metrics-port6060Prometheus metrics listen port
--bootnode-onlyoffOnly dial configured bootnodes
--archiveoffEnable archive mode RPC semantics for historical state queries
--jwt-secretJWT secret or path to jwtsecret file for Engine API auth

JSON-RPC API

A JSON-RPC 2.0 endpoint is served at http://localhost:8545.

Supported Methods

l2_ namespace (L2 rollup operations)

MethodDescription
l2_sendTransactionSubmit a transaction to the rollup
l2_getStateGet current rollup state dict
l2_getStateRootGet current Merkle state root (hex)
l2_getBatchQuery a sealed batch by number
l2_produceBatchTrigger batch production
l2_proveAndSubmitProve a batch and submit to L1
l2_chainInfoGet rollup chain info

eth_ namespace

MethodDescription
eth_blockNumberLatest block number
eth_getBlockByNumberGet block by number
eth_getBlockByHashGet block by hash
eth_getBalanceAccount balance
eth_getTransactionCountAccount nonce
eth_getCodeContract bytecode
eth_getStorageAtStorage slot value
eth_sendRawTransactionSubmit a signed transaction
eth_callExecute read-only contract call via EVM
eth_estimateGasEstimate gas via EVM execution
eth_gasPriceCurrent gas price
eth_maxPriorityFeePerGasPriority fee suggestion
eth_feeHistoryFee history
eth_chainIdChain ID
eth_syncingSync status
eth_getTransactionByHashTransaction by hash
eth_getTransactionReceiptTransaction receipt
eth_getBlockTransactionCountByNumberTransaction count in block (by number)
eth_getBlockTransactionCountByHashTransaction count in block (by hash)
eth_getLogsLog filter query
eth_getBlockReceiptsBlock receipts

net_ namespace

MethodDescription
net_versionNetwork ID
net_peerCountConnected peer count
net_listeningListening status

web3_ namespace

MethodDescription
web3_clientVersionClient version string
web3_sha3Keccak-256 hash

engine_ namespace (served on --engine-port, JWT-authenticated)

MethodDescription
engine_exchangeCapabilitiesCapability negotiation
engine_getClientVersionV1Client version info
engine_forkchoiceUpdatedV1/V2/V3Fork choice state update + payload build trigger
engine_getPayloadV1/V2/V3Retrieve built execution payload
engine_newPayloadV1/V2/V3Validate and import execution payload

zk_ namespace

MethodDescription
zk_verifyGroth16Verify a Groth16 proof (accepts snarkjs or native format)
zk_deployVerifierDeploy a verifier contract and return bytecode + gas estimate
zk_verifyOnChainVerify a proof on-chain via the in-memory EVM

Usage Examples

# Submit L2 transaction
curl -X POST http://localhost:9545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"l2_sendTransaction","params":[{"sender":"0x01","data":{"action":"increment"}}],"id":1}'

# Get L2 state root
curl -X POST http://localhost:9545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"l2_getStateRoot","params":[],"id":1}'

# Get latest block number (L1)
curl -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

Testing

# Run all tests (1,031 tests)
pytest

# Run L2 rollup tests
pytest tests/test_l2_types.py            # L2 types, encoding, state snapshots
pytest tests/test_l2_da.py               # Data availability provider
pytest tests/test_l2_da_providers.py     # Production DA providers (S3, Calldata, Blob)
pytest tests/test_l2_runtime.py          # Python STF runtime wrapper
pytest tests/test_l2_sequencer.py        # Sequencer, mempool, batch assembly
pytest tests/test_l2_sequencer_hardening.py # Sequencer input validation, defensive checks
pytest tests/test_l2_prover.py           # Groth16 proof backend
pytest tests/test_l2_native_prover.py    # Native prover (rapidsnark/snarkjs subprocess)
pytest tests/test_l2_l1.py               # L1 backend, proof verification
pytest tests/test_l2_eth_l1_backend.py   # Real Ethereum L1 backend (JSON-RPC)
pytest tests/test_l2_rpc.py              # L2 RPC API (l2_* methods)
pytest tests/test_l2_state.py            # State store boundary checks, determinism
pytest tests/test_l2_persistent_state.py # LMDB persistent state, overlay, WAL
pytest tests/test_l2_health.py           # Health/ready endpoints
pytest tests/test_l2_middleware.py       # RPC middleware (API key, rate limit, request size)
pytest tests/test_l2_integration.py      # Full cycle: STF → batch → prove → L1 verify
pytest tests/test_l2_framework_hardening.py # Framework hardening: config validation, thread safety, liveness, retry, L1 finality

# Run L1 client tests
pytest tests/test_rlp.py                 # RLP encoding/decoding
pytest tests/test_trie.py                # Merkle Patricia Trie
pytest tests/test_trie_proofs.py         # Trie Merkle proofs & range verification
pytest tests/test_evm.py                 # EVM opcode execution
pytest tests/test_storage.py             # State storage
pytest tests/test_blockchain.py          # Block validation/execution
pytest tests/test_p2p.py                 # P2P networking
pytest tests/test_protocol_registry.py   # Multi-protocol capability negotiation
pytest tests/test_snap_messages.py       # snap/1 message encoding/decoding
pytest tests/test_snap_sync.py           # Snap sync state machine
pytest tests/test_rpc.py                 # JSON-RPC server + Engine API
pytest tests/test_disk_backend.py        # LMDB persistent storage

# Run ZK tests
pytest tests/test_zk_circuit.py          # ZK circuit builder (R1CS)
pytest tests/test_zk_groth16.py          # Groth16 prove/verify + snarkjs compat
pytest tests/test_zk_evm.py              # EVM-based ZK verification
pytest tests/test_poseidon.py            # Poseidon hash, circuit, trie, L2 integration

# Run bridge tests
pytest tests/test_bridge_messenger.py    # L2 bridge messenger send/relay
pytest tests/test_bridge_e2e.py          # L2 bridge end-to-end scenarios
pytest tests/test_bridge_censorship.py   # Force inclusion + escape hatch
pytest tests/test_bridge_proof_relay.py  # Proof-based relay handlers

# Run integration tests
pytest tests/test_integration.py         # End-to-end integration

# Verbose output
pytest -v

Claude Code Skills

This project includes 7 Claude Code skills that provide domain-specific guidance. Invoke them with /<skill-name> in Claude Code.

SkillCommandDescription
L2 Rollup/l2-rollup <app name or use case>Create app-specific ZK rollups — STF definition, PythonRuntime, Rollup lifecycle
ZK Circuit/zk-circuit <circuit or verification target>Build arithmetic circuits & Groth16 proofs — setup, prove, verify, EVM on-chain
Bridge/bridge <use case or direction>L1↔L2 bridge — CrossDomainMessenger, force inclusion, escape hatch
Sepolia Deploy/sepolia-deploy <app or contract>Deploy to Sepolia testnet — EthL1Backend, EIP-1559 tx, on-chain verification
Test/test <module or feature>Run & write tests — pytest commands, patterns, coverage
L1 Node/l1-node <task or question>L1 Ethereum node — EVM, eth/68, snap/1, Engine API, P2P
Debug P2P/debug-p2p <error message or symptom>Debug P2P networking — RLPx, devp2p, sync issues

Example Usage

# In Claude Code, type:
/l2-rollup token transfer app
/zk-circuit merkle proof verification
/bridge L1→L2 deposit
/test l2 sequencer

Architecture

ethclient/
├── main.py                          # CLI entry point, node initialization
├── l2/                              # Application-specific ZK rollup framework
│   ├── types.py                     # L2Tx, L2State, Batch, BatchReceipt, STFResult
│   ├── config.py                    # L2Config (chain_id, batch size, timeouts)
│   ├── interfaces.py                # 4 ABCs: STF, DAProvider, L1Backend, ProofBackend
│   ├── state.py                     # L2StateStore (Trie-based Merkle state roots)
│   ├── runtime.py                   # PythonRuntime (wraps callable → STF)
│   ├── da.py                        # LocalDAProvider (in-memory DA)
│   ├── da_s3.py                     # S3DAProvider (AWS S3 DA)
│   ├── da_calldata.py               # CalldataDAProvider (EIP-1559 L1 calldata DA)
│   ├── da_blob.py                   # BlobDAProvider (EIP-4844 blob DA)
│   ├── sequencer.py                 # Sequencer (mempool, nonce tracking, batch assembly)
│   ├── prover.py                    # Groth16ProofBackend (circuit → proof → verify)
│   ├── native_prover.py             # NativeProverBackend (rapidsnark/snarkjs subprocess)
│   ├── l1_backend.py                # InMemoryL1Backend (verifier simulation)
│   ├── eth_l1_backend.py            # EthL1Backend (real Ethereum L1 via JSON-RPC)
│   ├── eth_rpc.py                   # Lightweight Ethereum JSON-RPC client
│   ├── persistent_state.py          # L2PersistentStateStore (LMDB state, overlay, WAL)
│   ├── submitter.py                 # BatchSubmitter (prove → submit → verify pipeline)
│   ├── rollup.py                    # Rollup orchestrator (main user-facing API)
│   ├── rpc_api.py                   # l2_* JSON-RPC method registration
│   ├── health.py                    # /health, /ready, /metrics endpoints
│   ├── metrics.py                   # L2MetricsCollector (operational metrics)
│   ├── middleware.py                # APIKey, RateLimit, RequestSize middleware
│   └── cli.py                       # CLI: ethclient l2 {init|start|prove|submit}
├── common/                          # Core foundation modules
│   ├── rlp.py                       # RLP encoding/decoding
│   ├── types.py                     # Block, BlockHeader, Transaction, Account, etc.
│   ├── trie.py                      # Merkle Patricia Trie + proof generation/verification
│   ├── crypto.py                    # keccak256, secp256k1 ECDSA, address derivation
│   ├── hash.py                      # Poseidon hash (ZK-friendly, BN128 scalar field)
│   └── config.py                    # Chain config, hardfork params, Genesis
├── vm/                              # EVM (Ethereum Virtual Machine)
│   ├── evm.py                       # Main execution loop, transaction execution
│   ├── opcodes.py                   # All opcode handlers (140+)
│   ├── precompiles.py               # Precompiled contracts (ecrecover, SHA256, etc.)
│   ├── gas.py                       # Gas calculation (EIP-2929, EIP-2200)
│   ├── memory.py                    # 256-bit stack, byte-addressable memory
│   ├── call_frame.py                # Call frames, JUMPDEST validation
│   └── hooks.py                     # Execution hooks (L2 extensibility)
├── storage/                         # State storage
│   ├── store.py                     # Abstract Store interface (+ snap sync methods)
│   ├── memory_backend.py            # In-memory implementation, state root computation
│   └── disk_backend.py              # LMDB-backed persistent storage with overlay
├── blockchain/                      # Blockchain engine
│   ├── chain.py                     # Block validation, tx/block execution, simulate_call
│   ├── mempool.py                   # Transaction pool (nonce ordering, replacement)
│   └── fork_choice.py               # Canonical chain management, reorgs
├── zk/                              # ZK proving toolkit
│   ├── circuit.py                   # R1CS circuit builder (Signal, Circuit, R1CS)
│   ├── groth16.py                   # Groth16 prover, verifier, debug verifier
│   ├── poseidon_circuit.py          # Poseidon hash R1CS encoding (~240 constraints)
│   ├── evm_verifier.py              # EVM verifier bytecode generator + executor
│   ├── snarkjs_compat.py            # snarkjs JSON format import/export
│   ├── r1cs_export.py               # R1CS binary export for snarkjs/circom compat
│   └── types.py                     # G1Point, G2Point, Proof, VerificationKey
├── bridge/                          # L1↔L2 General State Bridge
│   ├── types.py                     # CrossDomainMessage, RelayResult, ForceInclusionEntry
│   ├── relay_handlers.py            # RelayHandler ABC + EVM/Merkle/ZK/TinyDB/Direct handlers
│   ├── messenger.py                 # CrossDomainMessenger (send, relay, pluggable handlers)
│   ├── watcher.py                   # BridgeWatcher (outbox drain + relay + force queue)
│   └── environment.py               # BridgeEnvironment (L1+L2+Watcher + factory methods)
├── networking/                      # P2P networking
│   ├── server.py                    # P2P server — multi-protocol dispatch
│   ├── protocol_registry.py         # Dynamic capability negotiation & offset calculation
│   ├── rlpx/
│   │   ├── handshake.py             # ECIES handshake (auth/ack)
│   │   ├── framing.py               # RLPx frame encryption/decryption
│   │   └── connection.py            # Encrypted TCP connection management
│   ├── eth/
│   │   ├── protocol.py              # p2p/eth message codes, protocol constants
│   │   └── messages.py              # eth/68 message encoding/decoding
│   ├── snap/
│   │   ├── protocol.py              # snap/1 message codes (SnapMsg enum)
│   │   └── messages.py              # snap/1 message encoding/decoding (8 types)
│   ├── discv4/
│   │   ├── discovery.py             # Discovery v4 UDP protocol
│   │   └── routing.py               # Kademlia k-bucket routing table
│   └── sync/
│       ├── full_sync.py             # Full sync pipeline (+ head discovery)
│       └── snap_sync.py             # Snap sync 4-phase state machine
├── rpc/                             # JSON-RPC server
│   ├── server.py                    # FastAPI-based JSON-RPC 2.0 dispatcher
│   ├── eth_api.py                   # eth_/net_/web3_ API handlers
│   ├── engine_api.py                # Engine API V1/V2/V3 handlers
│   ├── engine_types.py              # Engine API request/response types
│   └── zk_api.py                    # zk_ namespace RPC handlers
└── examples/
    ├── apps/                            # L2 application STF examples
    │   ├── l2_token.py                  # ERC20 token (mint/transfer/burn)
    │   ├── l2_nameservice.py            # ENS-style name service
    │   ├── l2_voting.py                 # Governance (proposal/vote/finalize)
    │   ├── l2_rps_game.py               # Commit-reveal rock-paper-scissors
    │   ├── l2_dex.py                    # AMM DEX (constant product, LP tokens)
    │   ├── l2_nft_marketplace.py        # NFT marketplace with royalties
    │   ├── l2_multisig.py               # N-of-M multisig wallet
    │   ├── l2_escrow.py                 # 3-party escrow service
    │   └── l2_prediction_market.py      # Prediction market with oracle
    ├── infra/                           # Production infrastructure examples
    │   ├── l2_rpc_server.py             # RPC server + middleware
    │   ├── l2_persistent_state.py       # LMDB persistence + crash recovery
    │   ├── l2_da_providers.py           # DA layer comparison (Local/Calldata/Blob)
    │   ├── l2_censorship_resistance.py  # Force inclusion + escape hatch
    │   ├── l2_multi_batch_ops.py        # Multi-batch operations loop
    │   ├── l2_config_fullstack.py       # L2Config full-stack wiring
    │   ├── general_state_bridge.py      # L1↔L2 bridge end-to-end
    │   ├── bridge_relay_modes.py        # Relay modes comparison
    │   ├── zk_notebook_demo.py          # ZK toolkit end-to-end
    │   └── zk_note_settle.py            # ZK note settlement
    ├── l2_sepolia_hello.py              # Sepolia live deployment
    └── l2_sepolia_all.py                # Sepolia 4-app batch deployment

Dependencies

PackagePurpose
pycryptodomeAES encryption, SHA-256, RIPEMD-160
coincurvesecp256k1 ECDSA sign/recover, ECDH
eth-hashKeccak-256 hashing
FastAPIJSON-RPC HTTP server
uvicornASGI server
python-snappyRLPx message Snappy compression
py-eccBN128 elliptic curve operations (ecAdd, ecMul, ecPairing)
ckzgKZG point evaluation (EIP-4844)
lmdbLMDB key-value store for persistent storage

Dev dependencies:

PackagePurpose
pytestTest framework
pytest-asyncioAsync test support

Implementation Details

Components Built from Scratch

  • Application-Specific ZK Rollup Framework — Pluggable STF/DA/Prover/L1 interfaces, Sequencer with mempool and nonce tracking, batch assembly with auto-seal, Groth16 proof backend with 128-bit field truncation, BatchSubmitter pipeline, Rollup orchestrator, L2 RPC API, CLI scaffolding
  • L2 State Management — Trie-based Merkle state root computation for arbitrary key-value state, snapshot/rollback for atomic batch execution, tag-based encoding for mixed-type state values
  • RLP (Recursive Length Prefix) — Ethereum serialization format: encoding/decoding, list/bytes discrimination
  • Merkle Patricia Trie — Branch/Extension/Leaf nodes, hex-prefix encoding, state root computation, Merkle proof generation/verification, range proofs
  • EVM — 140+ opcodes, 256-bit stack, byte-addressable memory, EIP-2929 cold/warm tracking, EIP-1559 base fee
  • Precompiles — ecrecover, SHA-256, RIPEMD-160, identity, modexp (EIP-2565), BN128 ecAdd/ecMul/ecPairing (EIP-196/197), BLAKE2f (EIP-152), KZG point evaluation (EIP-4844)
  • RLPx Transport — ECIES encryption, AES-256-CTR frame encryption, SHA3 MAC authentication
  • Protocol Registry — Dynamic multi-protocol capability negotiation and message ID offset calculation
  • eth/68 Protocol — Status, GetBlockHeaders, BlockHeaders, Transactions, and all other message types
  • snap/1 Protocol — GetAccountRange, AccountRange, GetStorageRanges, StorageRanges, GetByteCodes, ByteCodes, GetTrieNodes, TrieNodes
  • Discovery v4 — UDP Ping/Pong/FindNeighbours/Neighbours, Kademlia routing table
  • Full Sync — Peer head discovery via best_hash → header download → body download → block execution pipeline
  • Snap Sync — 4-phase state machine: account download → storage download → bytecode download → trie healing
  • Engine API — V1/V2/V3 forkchoiceUpdated, getPayload, newPayload; deterministic payload ID, payload queue, JWT authentication
  • JSON-RPC 2.0 — Request parsing, batch support, error handling, method dispatch
  • Groth16 ZK Proving — R1CS circuit builder with operator overloading, QAP via Lagrange interpolation, trusted setup with toxic waste, proof generation with randomization, pairing-based verification
  • EVM ZK Verifier — Auto-generated EVM bytecode for on-chain Groth16 verification using ecAdd/ecMul/ecPairing precompiles, gas profiling, execution tracing
  • snarkjs Compatibility — Round-trip import/export of snarkjs vkey.json and proof.json formats
  • L1↔L2 General State Bridge — Optimism-style CrossDomainMessenger with arbitrary message relay, EVM execution on target domain, replay protection, force inclusion (anti-censorship with 50-block window), escape hatch (L1 value recovery)
  • Bridge Watcher — Automated outbox drain, bidirectional message relay, force queue processing

Supported EIPs

EIPDescription
EIP-155Replay protection (chain ID)
EIP-1559Base fee, dynamic fees
EIP-2718Typed transaction envelope
EIP-2929Cold/warm storage access gas
EIP-2930Access list transactions
EIP-2200/3529SSTORE gas refund
EIP-2565ModExp gas cost
EIP-152BLAKE2f precompile
EIP-196/197BN128 elliptic curve add, mul, pairing
EIP-4844Blob transactions, KZG point evaluation precompile
EIP-7702Set EOA account code (Prague)

Execution Hook System

The EVM includes built-in hook points for L2 extensibility. Extending to L2 requires no changes to the EVM core — just implement ExecutionHook:

from ethclient.vm.hooks import ExecutionHook

class L2Hook(ExecutionHook):
    def before_execution(self, tx, env): ...
    def before_call(self, msg, env): ...
    def on_state_change(self, addr, key, value, env): ...

Project Stats

Source Code

ModuleFilesLOCDescription
l2/243,291App-specific ZK rollup: STF, sequencer, prover, L1 backend, rollup orchestrator, RPC, CLI, production DA (S3/Calldata/Blob), native prover, LMDB state, middleware
common/72,444RLP, types, trie (+ proofs), crypto, Poseidon hash, config
vm/82,690EVM, opcodes, precompiles, gas
storage/41,431Store interface, in-memory & LMDB backends
blockchain/41,291Block validation, mempool, fork choice, simulate_call
networking/195,075RLPx, discovery, eth/68, snap/1, protocol registry, sync, server
zk/82,062Groth16 circuit builder, prover, verifier, EVM verifier, Poseidon circuit, snarkjs compat, R1CS export
bridge/61,241CrossDomainMessenger, BridgeWatcher, BridgeEnvironment, force inclusion, escape hatch
rpc/61,832JSON-RPC server, eth API, Engine API, ZK API
main.py1647CLI entry point
Total8822,050

Test Code

Test FileLOCTestsCovers
test_l2_types.py15320L2 tx types, encoding/decoding, state snapshots, input validation
test_l2_da.py568Data availability provider
test_l2_da_providers.py61140Production DA providers (S3, Calldata, Blob)
test_l2_runtime.py999Python STF runtime wrapper
test_l2_sequencer.py19812Sequencer, mempool, batch assembly, auto-seal, nonce gap, multi-sender
test_l2_sequencer_hardening.py17312Sequencer input validation, defensive checks
test_l2_prover.py19317Groth16 proof backend, field truncation, tampering rejection
test_l2_native_prover.py24314Native prover backend (rapidsnark/snarkjs subprocess)
test_l2_l1.py866L1 backend, proof verification, batch tracking
test_l2_eth_l1_backend.py22912Real Ethereum L1 backend (EIP-1559 tx, EVMVerifier)
test_l2_rpc.py13314L2 RPC API (l2_* methods), input validation
test_l2_state.py323State store boundary checks, state root determinism
test_l2_persistent_state.py26934LMDB persistent state, overlay, WAL, batch/proof persistence
test_l2_health.py563Health/ready endpoints
test_l2_middleware.py14113RPC middleware (API key, rate limit, request size)
test_l2_integration.py27413Full cycle: counter STF, balance transfer, multi-batch, state persistence
test_l2_framework_hardening.py62944Framework hardening: config validation, thread safety, liveness, LMDB resize, WAL extension, retry, L1 finality
test_rlp.py20756RLP encoding/decoding
test_trie.py21326Merkle Patricia Trie
test_trie_proofs.py25423Trie proof generation/verification, range proofs
test_crypto.py11314keccak256, ECDSA, addresses
test_evm.py82188Stack, memory, opcodes, precompiles
test_storage.py38765Store CRUD, state root (both backends parametrized)
test_blockchain.py61737Header validation, block execution, mempool, fork choice
test_p2p.py1,62490RLPx, handshake, eth messages, head discovery
test_rpc.py90976JSON-RPC endpoints, eth_call/estimateGas, Engine API, tx/receipt lookup
test_protocol_registry.py17717Multi-protocol capability negotiation
test_snap_messages.py26721snap/1 message encode/decode roundtrip
test_snap_sync.py44629Snap sync state machine, response handlers
test_zk_circuit.py29226ZK circuit builder, R1CS, field arithmetic
test_zk_groth16.py26718Groth16 prove/verify, debug verify, snarkjs compat
test_zk_evm.py16213EVM verification, gas profiling, execution trace
test_poseidon.py37144Poseidon hash, circuit (R1CS), trie integration, L2 config/state/rollup
test_bridge_messenger.py22511Bridge messenger send/relay, replay protection
test_bridge_e2e.py17410Bridge E2E: deposit, withdraw, roundtrip, state relay
test_bridge_censorship.py27014Force inclusion + escape hatch (anti-censorship)
test_bridge_proof_relay.py58528Proof-based relay handlers (EVM, Merkle, ZK, TinyDB, Direct)
test_integration.py27214Cross-module integration
test_disk_backend.py54331LMDB persistence, flush, overlay, state root consistency
integration/686Archive mode, chaindata, Fusaka compliance
Total12,8391,031

FAQ

Can I build an application-specific rollup with py-ethclient? Yes — py-ethclient includes a complete application-specific ZK rollup framework. Define your state transition logic as a plain Python function, and the framework handles sequencing, batching, Groth16 proving, and L1 verification. See the L2 Rollup Framework section.

How does the rollup framework work? You write a State Transition Function (STF) — a Python function that takes (state, tx) and mutates state. The Sequencer collects transactions, executes the STF, and assembles batches. The Groth16 prover generates a ZK proof over the state transition, and the L1 backend verifies it. All 4 components (STF, DA, Prover, L1) are pluggable interfaces.

Is there a Python Ethereum execution client? Yes — py-ethclient is a fully functional Ethereum execution client written entirely in Python. It implements the EVM with 140+ opcodes, connects to the Ethereum P2P network via RLPx (eth/68, snap/1), and supports both full sync and snap sync for Mainnet and Sepolia.

Can py-ethclient sync with Ethereum mainnet? Yes. py-ethclient connects to Ethereum mainnet and Sepolia testnet peers, performs peer discovery via Discovery v4, and synchronizes using either full sync (sequential block execution) or snap sync (parallel state download). It has been live-tested against Geth nodes on both networks.

How does py-ethclient compare to geth? geth (Go Ethereum) is the most widely used production execution client. py-ethclient implements the same core protocols (EVM, eth/68, snap/1, Engine API) but is written in Python for readability and research purposes. While geth is optimized for production performance, py-ethclient prioritizes code clarity, making it ideal for learning how Ethereum works at the protocol level.

What is the L2 bridge? The L2 bridge is an Optimism-style CrossDomainMessenger that relays arbitrary messages between L1 and L2. Messages are executed on the target domain's EVM, producing real state changes. It includes force inclusion (bypass censoring operators after a 50-block window) and an escape hatch (recover deposited value on L1 when L2 is unresponsive). See the L2 Bridge section.

What relay modes are available? The bridge supports 5 relay handlers: EVMRelayHandler (default, full EVM execution), MerkleProofHandler (Merkle proof against trusted L1 state root), ZKProofHandler (Groth16 zero-knowledge proof verification), TinyDBHandler (document DB backend for non-EVM L2), and DirectStateHandler (trusted relayer, direct state application). With proof-based relay, L2 can use any runtime — not just EVM.

Can I use py-ethclient for ZK development? Yes. py-ethclient includes a built-in Groth16 ZK proving toolkit. You can define R1CS circuits using Python expressions, generate proofs, verify them natively or on the in-memory EVM, profile gas costs, and export to snarkjs format — all without installing circom, snarkjs, or Solidity toolchains. See the ZK Toolkit section.

Is the ZK prover production-ready? The prover is implemented in pure Python (using py_ecc for BN128 curve operations), so it's best suited for education, prototyping, and small circuits (< 1000 constraints). For production proving, use snarkjs or rapidsnark for proof generation, then verify the proofs with py-ethclient's native or EVM verifier.

What EIPs does py-ethclient support? py-ethclient supports EIP-155 (replay protection), EIP-1559 (dynamic fees), EIP-2718 (typed transactions), EIP-2929/2930 (access lists), EIP-4844 (blob transactions with KZG), and EIP-7702 (Prague EOA code). See the Supported EIPs section for the full list.

Current Limitations

  • Engine API — V1/V2/V3 implemented; block production flow operational but ongoing optimization
  • eth_getLogs — Stub implementation; log filtering not yet implemented
  • contractAddress — Transaction receipt does not yet derive the contract address for CREATE transactions

License

MIT