Build a custom consensus layer for go-ethereum (Geth) from scratch. This tutorial accompanies the blog series on writing custom consensus mechanisms.
- Writing Custom Consensus for Geth: A Practical Guide - Engine API fundamentals
- Single Node Consensus: Building a Complete Implementation - Production-ready single node
- Distributed Consensus with Redis, PostgreSQL, and Member Nodes - Leader election, storage, and horizontal scaling
- CometBFT Integration: BFT Finality for Geth - Byzantine fault tolerance
├── 01-engine-api/ # Part 1: Minimal Engine API client
├── 02-single-node/ # Part 2: Complete single-node consensus
├── 03-member-nodes/ # Part 3: Distributed consensus with member nodes
├── 04-cometbft-consensus/ # Part 4: CometBFT BFT consensus
└── docker-compose.yml # Run Geth + Redis + PostgreSQL locally
Each directory is self-contained and progressively builds on the previous part.
- Go 1.24+
- Docker & Docker Compose
- Make (optional)
# Start Geth, Redis, and PostgreSQL
docker compose up -d
# Wait for Geth to initialize (~10 seconds)
docker compose logs -f geth# Part 1: Engine API basics
cd 01-engine-api
go run main.go
# Part 2: Single node consensus
cd 02-single-node
go run ./cmd/main.go --instance-id node-1
# Part 3: Member nodes (leader + members)
cd 03-member-nodes
go run ./cmd/main.go --instance-id leader-1 --mode leader # Terminal 1
go run ./cmd/main.go --instance-id member-1 --mode member \
--eth-client-url http://localhost:8552 \
--postgres-url "postgres://postgres:postgres@localhost:5433/consensus?sslmode=disable" \
--health-addr :8081 # Terminal 2
# Part 4: CometBFT consensus (requires CometBFT installed)
cometbft init --home ~/.cometbft # Initialize once
cd 04-cometbft-consensus
go run ./cmd/main.go --cmt-home ~/.cometbft┌─────────────────┐
│ Your Code │
│ (main.go) │
└────────┬────────┘
│ Engine API (HTTP + JWT)
┌────────▼────────┐
│ Geth │
└─────────────────┘
┌─────────────────────────────────────┐
│ SingleNodeApp │
│ ┌────────────┐ ┌───────────────┐ │
│ │BlockBuilder│ │ StateManager │ │
│ └────────────┘ └───────────────┘ │
└────────────────┬────────────────────┘
│
┌───────▼───────┐
│ Geth │
└───────────────┘
┌─────────────────────────────┐
│ Leader Node │
│ ┌──────┐ ┌─────┐ ┌─────┐ │
│ │ Geth │ │Redis│ │ PG │ │
│ └──────┘ └─────┘ └──┬──┘ │
└────────────────────────┼────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Member 1 │ │Member 2 │ │Member 3 │
│ Syncer │ │ Syncer │ │ Syncer │
│ + Geth │ │ + Geth │ │ + Geth │
│ + PG │ │ + PG │ │ + PG │
└─────────┘ └─────────┘ └─────────┘
CometBFT P2P Network
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Validator 1 │ │ Validator 2 │ │ Validator 3 │
│ CometBFT ├─┤ CometBFT ├─┤ CometBFT │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ ABCI │ ABCI │ ABCI
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ App + DB │ │ App + DB │ │ App + DB │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ Engine │ Engine │ Engine
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Geth 1 │ │ Geth 2 │ │ Geth 3 │
└─────────────┘ └─────────────┘ └─────────────┘
| Concept | Description |
|---|---|
| Engine API | HTTP/JSON-RPC interface for consensus-execution communication |
| JWT Auth | Stateless authentication for Engine API requests |
| ForkchoiceUpdated | Set chain head and trigger block building |
| GetPayload | Retrieve a built block from Geth |
| NewPayload | Submit a block for execution |
| Leader Election | TTL-based distributed lock using Redis with atomic Lua scripts |
| Variable | Default | Description |
|---|---|---|
ETH_CLIENT_URL |
http://localhost:8551 |
Geth Engine API endpoint |
JWT_SECRET |
(see docker compose) | 32-byte hex-encoded secret |
REDIS_ADDR |
localhost:6379 |
Redis address |
POSTGRES_URL |
(see docker compose) | PostgreSQL connection string |
The included genesis.json creates a local PoS-ready chain with:
- Chain ID: 1337
- Pre-funded test account:
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266(Foundry default) - Instant block times (controlled by consensus layer)
This tutorial code is simplified for learning. For production, consider:
- TLS: Enable HTTPS for Engine API and inter-node communication
- Secrets Management: Use Vault or similar for JWT secrets
- Monitoring: Add comprehensive Prometheus metrics
- Rate Limiting: Protect API endpoints
- Circuit Breakers: Handle Geth unavailability gracefully
MIT