MQT
Home

Case study · Backend & Systems

ChainForge

Reorg-safe Ethereum event indexing and delivery infrastructure in Go

ChainForge operator dashboard showing a labeled demo snapshot of reorg-safe indexing and delivery

The problem

If a system tells a customer a deposit arrived based on a block that later gets orphaned, that is a correctness incident. Unfinalized chain data is provisional: parent-hash divergence, orphaned branches, and webhook fans-out all have to stay consistent after a reorg.

What I built

Canonical-chain coordinator

A single writer ingests heads, detects parent-hash divergence, finds the common ancestor, orphans the dead branch and its events, then indexes the replacement, atomically with the checkpoint.

Event lifecycle

Downstream deliveries are typed: event.created, event.confirmed, event.finalized, and event.removed for previously delivered non-finalized events on an orphaned branch.

PostgreSQL invariants

At most one canonical block per height, parent continuity, and a halt if a reorg would rewrite finalized range instead of silently corrupting state.

Delivery and backfill

HMAC-signed, idempotency-keyed webhooks with retries and a DLQ, SSRF-guarded URLs, Prometheus metrics, resumable/idempotent backfill, and RPC provider failover.

Engineering decisions

At-least-once delivery

Consumers must deduplicate. Stable event IDs are hashed from chain ID, block hash, tx hash, and log index. Finalized blocks are never rewritten.

Deterministic fake RPC

Integration tests use an in-process Ethereum RPC and testcontainers Postgres, including a flagship reorg scene and restart-after-reorg convergence, without a live Ethereum dependency.

Explicit demo vs live

The dashboard defaults to a labeled DEMO SNAPSHOT of a synthetic reorg. Switching to live RPC data is explicit and never automatic.

Testing & CI

go test ./... with testcontainers; race detector on the full suite. Coverage includes decode/HMAC/retry/SSRF unit tests plus depth-1/2 reorgs, MAX_REORG_DEPTH halt, backfill resume, webhook 500→DLQ, and finality monotonicity.

Screenshots

Operator dashboard

Operator dashboard

Labeled demo snapshot of indexing, reorg handling, and delivery, never implied to be mainnet data

Go / PostgreSQL / testcontainers / Prometheus / HMAC webhooks