Synchronous cross-chain rollup composer. Atomic L1↔L2 synchronous composability.
A rollup node built on reth with synchronous cross-chain composability — L2 contracts can call L1 and get return values in the same transaction. L1 is the sequencer, Rollups.sol on L1 is the canonical source of truth. Any node can re-derive the full L2 chain from L1 events alone.
Theoretical basis: Synchronous composability between rollups via realtime proving
L2 blocks follow a deterministic 12-second timestamp schedule. The builder posts batches to Rollups.sol via postBatch(entries, blobCount, callData, proof) — the sole interface between L1 and L2. Fullnodes derive the canonical chain from BatchPosted events independently and verify builder preconfirmations over WebSocket.
Cross-chain composability is native: L1 contracts can call L2 contracts and back within a single synchronous execution context. The protocol tracks state transitions for each cross-chain call as chained StateDelta entries in the execution table. Multi-call continuations (flash loans spanning L1 and L2) are supported via scope navigation.
Batch submission carries an ECDSA proof of the publicInputsHash (development-grade; production will use ZK proofs). The on-chain tmpECDSAVerifier recovers the signer and checks it against the rollup's registered verification key.
L1 (reth --dev)
|
+----------------+----------------+
| |
Rollups.sol Bridge L1
tmpECDSAVerifier CrossChainProxy L1
| |
| BatchPosted events | hold-then-forward
| |
+---------+---------+ +-----------+-----------+
| Builder | | L1 Proxy (9556) |
| | | deposit detection, |
| builds L2 blocks | | debug_traceCallMany |
| posts postBatch | +-----------+-----------+
| sends triggers | |
| signs proofs | v
+---------+---------+ (entries queued, user tx forwarded)
| |
| WS preconfirmations (9550) |
| |
+---------+--------+ +-----------+----------+
| Fullnode 1 | | L2 (9545) |
| Fullnode 2 | | CrossChainManagerL2 |
| | | Bridge L2 |
| derive from L1 | | CrossChainProxy L2 |
| verify against | | |
| preconfirmations | | L2 Proxy (9548) |
+------------------+ | withdrawal detection |
+----------------------+
| Mode | Description |
|---|---|
| Sync | Catching up: reads BatchPosted events from L1, re-derives L2 blocks |
| Builder | Caught up: builds blocks from mempool, posts batches to L1, sends cross-chain triggers |
| Fullnode | Caught up (non-builder): derives from L1, receives preconfirmations from builder WS |
- L1→L2 deposits:
bridgeEtheron L1 →executeIncomingCrossChainCallon L2 viaCrossChainManagerL2. ETH comes from CCM's 1M ETH genesis allocation; no runtime minting. - L2→L1 withdrawals:
bridgeEther(0)on L2 detected by L2 proxy → withdrawal trigger sent to L1 Bridge viapostBatch+ nonce-linked trigger tx. - Multi-call continuations (flash loans): L1 proxy traces multi-call transactions via
debug_traceCallMany, builds L1+L2 entry chains. On L2,loadExecutionTableloads continuation entries; a singleexecuteIncomingCrossChainCalldrives the full chain (receive → process → bridge back) vianewScope(). - Scope navigation: Nested cross-chain calls navigate scopes hierarchically.
ExecutionEntry.nextAction.scopecarries the path. L1 consumption uses_processCallAtScopefor withdrawal entries. - Hold-then-forward: Both proxies queue entries, await L1 confirmation, then forward the user tx. Prevents timing races between entry loading and execution.
- Entry verification with rewind: After
MAX_ENTRY_VERIFY_DEFERRALS=3retries,verify_local_block_matches_l1returnsErrto trigger a rewind toentry_block - 1for re-derivation. - Unified intermediate roots:
attach_unified_chained_state_deltas()builds a single D+W+1 root chain covering all deposits and withdrawals in a block. Deposits and withdrawals can coexist. - Explicit nonces: All L1 submissions use
send_l1_tx_with_nonce().reset_nonce()is called after any L1 tx failure to prevent permanent livelock.
- Rust 1.85+ (nightly for
cargo fmt) - Docker and Docker Compose
- Foundry (for E2E tests only)
# Build the binary
cargo build --release
# Start devnet (first time)
sudo docker compose -f deployments/testnet-eez/docker-compose.yml \
-f deployments/testnet-eez/docker-compose.dev.yml up -d
# Check health
curl http://localhost:9560/health
# Iterate after code changes (no rebuild of Docker images needed)
cargo build --release
sudo docker compose -f deployments/testnet-eez/docker-compose.yml \
-f deployments/testnet-eez/docker-compose.dev.yml \
restart builder fullnode1 fullnode2# Block numbers should match across nodes
cast block-number --rpc-url http://localhost:9545 # builder
cast block-number --rpc-url http://localhost:9546 # fullnode1
cast block-number --rpc-url http://localhost:9547 # fullnode2
# Block hashes should be identical
cast block --rpc-url http://localhost:9545 latest --field hash
cast block --rpc-url http://localhost:9546 latest --field hashsync-rollup-composer/
├── docs/
│ ├── DERIVATION.md # Normative consensus & derivation spec
│ └── architecture.excalidraw # Architecture diagram
├── CLAUDE.md # Development guide (architecture, lessons)
├── .claude/agents/ # Subagent definitions
├── contracts/sync-rollups-protocol/ # Solidity contracts submodule (includes docs/SYNC_ROLLUPS_PROTOCOL_SPEC.md)
├── deployments/
│ ├── shared/ # Shared Dockerfiles, genesis, explorer compose, service scripts
│ ├── testnet-eez/ # Local devnet (reth --dev L1, chain 42069 L2)
│ ├── gnosis-100/ # Gnosis Chain deployment
│ ├── chiado-10200/ # Chiado testnet deployment
│ ├── kurtosis-1337/ # Kurtosis PoS devnet
│ └── ethereum-1/ # Mainnet placeholder
├── scripts/ # Host-only: E2E tests, tooling
├── ui/ # React dashboard (port 8080)
└── crates/sync-rollup-composer/src/
├── driver.rs # Mode orchestration, flush_to_l1, hold, triggers
├── derivation.rs # L1 sync, §4e/§4f filtering
├── cross_chain.rs # Entry types, ABI, filtering, continuations
├── table_builder.rs # Flash loan analysis, L1/L2 entry building
├── evm_config.rs # EVM config (thin wrapper around EthBlockExecutor)
├── proposer.rs # L1 submission, explicit nonces, recovery
├── proxy.rs # L2 proxy: withdrawal detection, hold-then-forward
├── l1_proxy.rs # L1 proxy: deposit detection, hold-then-forward
├── execution_planner.rs # Tx simulation, action hash computation
└── rpc.rs # syncrollups_* RPC namespace
| Port | Service | Description |
|---|---|---|
| 9555 | l1 | L1 JSON-RPC (reth --dev) |
| 9545 | builder | L2 JSON-RPC |
| 9546 | fullnode1 | L2 JSON-RPC |
| 9547 | fullnode2 | L2 JSON-RPC |
| 9548 | builder | L2 RPC Proxy (cross-chain detection) |
| 9550 | builder | L2 WebSocket (preconfirmations) |
| 9556 | builder | L1 RPC Proxy (deposit detection) |
| 9560 | builder | Health endpoint |
| 8080 | sync-ui | Dashboard |
Explorer ports (requires explorer overlay): L1 frontend 4000, L2 frontend 4001, L1 API 4002, L2 API 4003.
Startup order: l1 → deploy → builder → deploy-l2 → complex-tx-sender
cargo build --release
cargo nextest run --workspace # ~529 tests across unit, EVM integration, and E2E
cargo clippy --workspace --all-features
cargo +nightly fmt --allThe 81 e2e_anvil tests and 9 evm_executor tests require anvil running locally and compiled contract artifacts in contracts/sync-rollups-protocol/out/. ~439 unit tests pass standalone.
| Contract | Role |
|---|---|
Rollups.sol |
Sole L1 interface: postBatch, BatchPosted, state root registry |
tmpECDSAVerifier |
Development proof verifier: ecrecover against registered key |
Bridge |
ETH bridging between L1 and L2 |
CrossChainProxy (L1) |
Authorized proxy for L1-side cross-chain calls (created on demand via createCrossChainProxy) |
Deployed by the builder in block 1 protocol transactions (deterministic CREATE addresses from builder nonce 0-3):
| Contract | Address | Role |
|---|---|---|
L2Context |
0x5FbDB231… (nonce 0) |
Per-block context: setContext(l1ParentBlockNumber, l1ParentBlockHash) |
CrossChainManagerL2 |
0xe7f1725E… (nonce 1) |
Execution table, scope navigation, entry consumption |
Bridge (L2) |
0x9fE46736… (nonce 2) |
ETH bridging, bridgeEther (initialized at nonce 3) |
CrossChainProxy (L2) |
created on demand | Authorized proxy for L2-side cross-chain calls |
| Document | Purpose |
|---|---|
| DERIVATION.md | Normative spec — consensus rules, block timing, derivation, §4f filtering |
| SYNC_ROLLUPS_PROTOCOL_SPEC.md | Formal protocol spec — data model, entry lifecycle, scope navigation, bridge flows |
| CLAUDE.md | Development guide — architecture, Docker workflow, dev accounts, lessons learned |
| deployments/testnet-eez/README.md | Devnet setup and port reference |
MIT OR Apache-2.0