Skip to content

kamilsa/blobsim

Repository files navigation

blob-sim

A Shadow-compatible network simulator for Ethereum blob propagation under EIP-7732 (ePBS) and EIP-8070 (Sparse Blobpool) rule sets.

Built with real libp2p (QUIC transport) and mocked devp2p message flows — no actual cryptographic verification or EVM execution. Designed to run inside the Shadow discrete-event network simulator.

Architecture

src/
├── main.rs           # CLI entry point + swarm bootstrap
├── network.rs        # libp2p swarm, QUIC transport, Gossipsub + Req-Res
├── state_machine.rs  # 12-second slot ticker with persona-driven logic
└── types.rs          # Mock Ethereum message types (dummy crypto fields)

The networking layer (network.rs) is strictly decoupled from the state machine (state_machine.rs). The network module builds and configures the swarm; the state machine drives it via the event loop.

Gossipsub Topics

Topic Layer Message
/cl/bids/1 CL ExecutionPayloadBid
/cl/payload_envelope/1 CL SignedExecutionPayloadEnvelope
/cl/blob_sidecar/1 CL BlobSidecar
/cl/ptc_attestation/1 CL PayloadAttestationMessage
/el/blob_hash/1 EL BlobHashAnnounce (simulated eth/71)

Request-Response Protocol

Protocol /sim/devp2p/1 with JSON codec, carrying CustodyCellRequest/FullPayloadRequest and their responses.

Node Roles

Each node is configured at startup with one or more roles:

Role Behaviour
Proposer Publishes beacon block proposals
Builder Releases payload envelope + blob sidecars, always requests full EL blobs. Under --blocks-in-blobs (EIP-8142) it also encodes the execution payload into payload-blobs and seeds them onto the column subnets
Validator Non-builder CL node; for each announced EL blob independently chooses sampler behavior (85%) or provider behavior (15%)
ZK Attester A validator that verifies via zkEVM proofs (EIP-8142): does not subscribe to the payload-envelope topic and instead receives only the payload-blob cells for its custody columns (partial payload — a non-supernode does not reconstruct the full payload). Combined with --role validator
Blob Spammer EL-only load generator that originates blobs

12-Second Slot Timeline

t=0s    Builder publishes bid + blob hash announce
t=4s    Standard attestation window (mocked)
t=4-6s  Builder releases payload envelope + blob sidecars
t=8s    PTC members vote on payload timeliness
t=12s   Slot boundary → next slot

Usage

blob-sim --role <proposer|builder|validator|zk-attester|blob-spammer> \
         [--port <u16>] \
         [--el-port <u16>] \
         [--seed <u64>] \
         [--peer <multiaddr> ...] \
         [--el-peer <socket-addr> ...] \
         [--slots <u64>]
Flag Default Description
--role (required) Node role, repeatable
--port 0 (OS-assigned) QUIC listen port
--el-port 0 (OS-assigned) EL TCP listen port
--seed 42 Deterministic RNG seed (keypair + random decisions)
--peer (none) Bootstrap peer multiaddrs (repeatable)
--el-peer (none) EL peer socket addresses (repeatable)
--slots 10 Number of 12-second slots to simulate

Local Smoke Test

# Terminal 1 — Proposer + builder
cargo run -- --role proposer --role builder --port 9000 --seed 1 --slots 2

# Terminal 2 — Validator
cargo run -- --role validator --port 9001 --seed 2 --slots 2 \
  --peer /ip4/127.0.0.1/udp/9000/quic-v1

Running under Shadow (Docker)

shadow-sim.py is a convenient uv launcher that generates a geo-realistic network topology + a shadow.yaml and runs the whole simulation with a single command. It supports two runners (set [run].runner in blobsim.toml):

  • docker (default) — builds blob-sim into a Shadow-capable image and runs Shadow in a container. Zero local setup beyond Docker.
  • native — builds blob-sim with cargo build --release and runs a shadow binary from your PATH. Requires Shadow installed locally (Linux).
# Build the image (if needed) and run the sim described by blobsim.toml
uv run shadow-sim.py

# Use a different config file (positional argument, defaults to blobsim.toml)
uv run shadow-sim.py my-config.toml

# Generate shadow.yaml + topology.gml only (no Docker) — inspect before running
uv run shadow-sim.py --dry-run

# Force a rebuild of the image (after changing Rust source), and wipe old results
uv run shadow-sim.py --rebuild --clean

Requirements: uv, plus — for the docker runner — Docker (arm64 / Apple Silicon); the patched Shadow base image (kamilsa/shadow-arm:tcpfix) is pulled automatically on first build. For the native runner: a local Shadow install (equally patched — see below) and a Rust toolchain.

Why patched? Stock Shadow's tcp_sendUserData caps every send at 65535 bytes even when the socket buffer has space, so a partial write happens on a non-full buffer. Edge-triggered epoll users (tokio) then wait for an EPOLLOUT edge that never fires, permanently deadlocking any connection that pushes a ≥64 KiB frame — like blob-sim's 128 KiB full-payload responses. The fix (in the local shadow fork) removes the cap so a short write means what POSIX implies: the buffer is full.

Configuration

Everything is driven by blobsim.toml — edit it to change a run (pass a different file as the positional argument; the launcher itself only takes --dry-run, --rebuild, --clean). Key knobs:

  • [topology]validators, zk_attesters (how many validators are zk-attesters, EIP-8142), blob_spammers, and how many CL/EL peers each node dials.
  • [network] — a geo-realistic model: each host is assigned a region and a bandwidth tier by weight, and inter-host latency comes from an inter-region latency matrix + per-edge jitter (ported from lean-shadow-fuzzer). Tune [network.regions], [network.bandwidths], and jitter_ratio.
  • [sim]slots, seed, blobs_per_slot, exec_payload_size_kib, enable_partial_columns, disable_get_blobs, blocks_in_blobs (EIP-8142; also propagate the payload as payload-blobs over the column subnets, implies enable_partial_columns), rust_log.

The whole topology is deterministic in [sim].seed: same config → byte-identical shadow.yaml/topology.gml.

How it works

Every node runs the single blob-sim binary (baked into the image at /opt/blobsim/blob-sim for the docker runner, or target/release/blob-sim for the native runner) as its own Shadow host with a distinct IP. The launcher wires both layers: CL peers over QUIC (--peer) and — unlike run_network.shEL peers over TCP (--el-peer, always including ≥1 blob-spammer) so blobs originate at the spammers and propagate through the sparse blobpool. Outputs land in [output].dir (default shadow-output/):

shadow-output/
├── shadow.yaml        # generated Shadow config
├── topology.gml       # generated geo network graph
├── regions.json       # per-host region assignment (debug)
├── bandwidths.json    # per-host bandwidth tier (debug)
└── shadow.data/hosts/<host>/blob-sim.1000.stdout   # per-node logs

Analysis

After each run the launcher prints a summary — blocks produced, per-slot block / payload-envelope propagation reach, blob commitments, and EL/CL traffic totals — so you can see at a glance whether a run actually exercised the network (and it flags common issues like blocks that committed zero blobs). Re-print it for an existing run without re-simulating:

uv run shadow-sim.py --summary-only

For interactive per-node bandwidth charts:

cd shadow-output && python ../create_notebook.py   # → Analysis.ipynb

Note: create_notebook.py's per-node example cells reference host names from the old role model (plot_per_second("builder"), "sampler4", "provider1"). Point them at the current host names instead ("proposer", "validator000", …); the aggregate charts key off roles= in the logs and work unchanged.

Building the binary directly

If you just want the release binary (e.g. to reference from your own Shadow config):

cargo build --release   # → target/release/blob-sim

Design Constraints

  • No cryptography — KZG commitments, proofs, and BLS signatures are fixed-size dummy byte vectors.
  • Deterministic — Keypairs derived from --seed; all randomness via StdRng::seed_from_u64. No hardware entropy.
  • Shadow-friendly — Uses standard tokio::time::sleep / Instant so Shadow's libc interposition can control the simulated clock.

Dependencies

Crate Version Purpose
libp2p 0.54 Gossipsub, Request-Response, QUIC transport
tokio 1.x Async runtime
tracing 0.1 Structured logging (Shadow log parsing)
clap 4.x CLI argument parsing
serde / serde_json 1.x Message serialization
rand 0.8 Deterministic RNG
futures 0.3 Stream combinators

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors