Arseniy Klempner 695ecb42bc
feat: add mix+LEZ chat simulation with Docker support
End-to-end simulation: 4 mix nodes with RLN spam protection, LEZ
sequencer, gifter service, and two logos-chat-module clients exchanging
double-ratchet-encrypted messages through 3-hop Sphinx onion routes.

Simulation scripts:
- run_simulation.sh: orchestrates sequencer, mix nodes, chat clients,
  with poll-based readiness checks and 15 verification checks
- setup_and_run.sh: one-shot build + run for fresh clones
- run_in_docker.sh: Docker-based Linux testing with pre-built modules
- README.md: configuration, troubleshooting, architecture guide

Configurable via SIM_* env vars (ports, timeouts, log level).
Cross-platform: macOS native + Linux via Docker.

Docker image (Dockerfile.sim) uses multi-stage build:
- Stage 1: builds all LEZ modules, sequencer, liblogoschat, chat-module
- Stage 2: copies only runtime nix closure (~1.9GB) + output artifacts
- Pre-built modules are symlinked at runtime, skipping build_all.sh
2026-05-06 10:50:14 -06:00
..

Mix + LEZ RLN Chat Simulation

End-to-end private chat between two logos-chat-module clients over a 4-node mix network with LEZ-backed RLN spam protection against a local LEZ sequencer.

Two logoscore instances (sender + receiver) load chat_module and establish an X3DH key agreement via an out-of-band intro bundle, then exchange double-ratchet-encrypted messages routed through 3-hop Sphinx onion routes with per-hop RLN proof generation and verification. Node 0 mounts the rln_gifter service; nodes 1-3 and both chat clients register RLN memberships on-chain via the gifter protocol. The chat clients run mix: true, relay: false, filter: true — the sender publishes via lightpushPublish(mixify=true), the mix exit node verifies the RLN proof before fanning out via gossipsub relay to the shard, and the receiver consumes the message via a Waku filter subscription on one of the mix nodes.

Architecture

logoscore (per mix node)                    logoscore (per chat client)
├── wallet_module (LEZ wallet)              ├── wallet_module (LEZ wallet)
├── liblogos_rln_module (RLN proofs)        ├── liblogos_rln_module (RLN proofs)
└── delivery_module (Waku mix relay)        └── chat_module (logos-chat-module)
    ├── liblogosdelivery.so                     ├── chat_module_plugin.so
    └── mix + relay + filter + gifter           └── liblogoschat.so
                                                    └── mix client + filter + gifter client

Node 0 runs the RLN gifter service. Nodes 1-3 register via gifter on startup. Chat clients also register via gifter when startChat() runs.

Quick start

Prereqs: nix (with flakes), Docker (for guest zkVM binaries), cargo-risczero, SSH access to GitHub.

Run from scratch:

git clone -b feat/logos-delivery git@github.com:adklempner/logos-chat.git
cd logos-chat && bash simulations/mix_lez_chat/setup_and_run.sh

Re-run (after initial build):

bash simulations/mix_lez_chat/run_simulation.sh --fresh

Pass = ALL 15 CHECKS PASSED.

Configuration

Override via environment variables:

Variable Default Description
SIM_NUM_NODES 4 Number of mix relay nodes
SIM_BASE_TCP_PORT 60001 First node's TCP port (increments per node)
SIM_BASE_DISC_PORT 9001 First node's discv5 UDP port (increments per node)
SIM_CLUSTER_ID 99 Waku cluster ID
SIM_LOG_LEVEL INFO Node log level (TRACE, DEBUG, INFO, WARN, ERROR)
SIM_CHAT_RECV_PORT 60010 Chat receiver TCP port
SIM_CHAT_SEND_PORT 60011 Chat sender TCP port
SIM_KADEMLIA_MIN_WAIT 30 Minimum seconds to wait for kademlia propagation
SIM_RECEIVER_MIN_WAIT 15 Minimum seconds to wait for receiver to join mix
SIM_DELIVERY_TIMEOUT 120 Max seconds to wait for message delivery

Example — fast iteration with verbose logging:

SIM_LOG_LEVEL=TRACE SIM_KADEMLIA_MIN_WAIT=10 SIM_RECEIVER_MIN_WAIT=5 \
  bash simulations/mix_lez_chat/run_simulation.sh --fresh

--fresh behavior

When --fresh is passed:

  • Kills all existing logos_host processes
  • Cleans /tmp/logos_* Qt RemoteObjects sockets
  • Removes .sim_state/ directory
  • Removes sequencer state (rocksdb/, bedrock_signing_key)
  • Rebuilds and restarts the sequencer
  • Redeploys RLN programs via run_setup

Without --fresh, reuses existing sequencer if port 3040 is already bound.

Checks (15 total)

Category Check What it verifies
Mix nodes (4) Node N mounted mix Mix protocol handler registered
RLN gifter Node 0 gifter service mounted /logos/rln-gifter/1.0.0 protocol handler
LEZ RLN LEZ root polling active Nodes polling valid Merkle roots from LEZ
Chat module (4) Receiver/Sender initialized chatInitResult event fired
Receiver/Sender started Waku client started + chatStartResult
Receiver/Sender mounted mix+LEZ Mix protocol + LEZ callbacks wired
Receiver created intro bundle X3DH pre-key bundle generated
Message exchange (2) Sender sent message chatNewPrivateConversationResult
Receiver received message chatNewMessage via filter subscription

Troubleshooting

"Sequencer failed to start" — port 3040 already in use:

kill $(lsof -ti tcp:3040) && bash simulations/mix_lez_chat/run_simulation.sh --fresh

"run_setup failed" / "Timeout waiting for account" — stale guest binaries or wallet state:

rm -rf vendor/logos-lez-rln/lez-rln/methods/guest/target
rm -f vendor/logos-lez-rln/dev/wallet_config.json vendor/logos-lez-rln/dev/storage.json
bash simulations/mix_lez_chat/setup_and_run.sh

"Sender started FAIL" — stale Qt RemoteObjects sockets:

rm -f /tmp/logos_*
bash simulations/mix_lez_chat/run_simulation.sh --fresh

"FAIL: Receiver received message (0)" — timing issue, try re-running:

bash simulations/mix_lez_chat/run_simulation.sh --fresh

Adapting for other LEZ programs

This simulation provides a complete mix network infrastructure that other logos modules can reuse for testing. To test your own module:

What the sim provides

  • 4 logoscore mix nodes with delivery_module (Waku relay + mix + RLN)
  • LEZ sequencer with deployed RLN programs
  • RLN gifter service on node 0
  • Wallet modules for on-chain transactions

What you replace

The chat_module sender/receiver instances (phase 5 of run_simulation.sh). Your module needs:

  1. A C++ Qt plugin implementing PluginInterface (see chat_module_plugin.cpp)
    • initLogos(LogosAPI*) — receive the LogosAPI instance
    • eventResponse(QString, QVariantList) signal — mandatory per logos-liblogos contract
    • Methods exposed via LOGOS_METHOD for logoscore -c invocation
  2. A shared library with your program logic (like liblogoschat.so)
  3. RLN integration — wire setRlnConfig to pass RLN credentials from the C++ plugin to your library
  4. EVENT: stderr fallback — on Linux, Qt signal forwarding from plugin to logoscore doesn't work across the FFI thread boundary. Write event data to stderr in EVENT:name:data format for cross-platform reliability.

How to stage your module

MDIR=$(mktemp -d)
mkdir -p "$MDIR/your_module"
cp your_module_plugin.so "$MDIR/your_module/"
cp libyour_library.so "$MDIR/your_module/"
echo '{"name":"your_module","version":"1.0.0","type":"core",...}' > "$MDIR/your_module/manifest.json"

logoscore -m "$MDIR" \
  -l "liblogos_execution_zone_wallet_module,liblogos_rln_module,your_module" \
  -c "liblogos_execution_zone_wallet_module.open($WALLET_CONFIG,$WALLET_STORAGE)" \
  -c "your_module.init(@config.json)" \
  -c "your_module.start()"

Reference

  • chat_module_plugin.cpp — complete working example with RLN, gifter, mix, and event emission
  • delivery_module_plugin.cpp — more complex example with full RLN fetcher integration
  • run_simulation.sh — orchestration, module staging, and verification patterns

Logs

All logs in simulations/mix_lez_chat/.sim_state/:

  • node0.log node3.log — mix relay nodes
  • chat_receiver.log — receiver chat module
  • chat_sender.log — sender chat module