A lightweight, fast Zig service that monitors finality across multiple Lean Ethereum lean nodes with consensus validation and a modern web UI. Inspired by checkpointz.
Leanpoint is a monorepo containing:
- Zig Backend (
src/) - Fast, lightweight checkpoint sync provider (8MB binary) - React Frontend (
web/) - Modern web UI for real-time monitoring - Unified Build System - Single
Makefileto build everything
It polls multiple lean nodes, requires 50%+ consensus before serving finality data, and provides:
- Real-time checkpoint status monitoring
- Per-upstream health tracking
- Prometheus metrics integration
- Web dashboard for visualization
leanpoint/
βββ src/ # Zig backend source
β βββ main.zig # Entry point
β βββ server.zig # HTTP server + API + static file serving
β βββ state.zig # Application state with upstream tracking
β βββ upstreams.zig # Upstream manager with consensus
β βββ lean_api.zig # Lean Ethereum API client
β βββ metrics.zig # Prometheus metrics
β βββ config.zig # Configuration loader
βββ web/ # React frontend
β βββ src/
β β βββ components/ # StatusCard, UpstreamsTable
β β βββ api/ # API client functions
β β βββ types/ # TypeScript interfaces
β β βββ App.tsx # Main app
β βββ package.json
β βββ README.md
βββ web-dist/ # Built frontend (generated)
βββ Makefile # Unified build system
βββ Dockerfile # Docker image with UI
βββ README.md # This file
make buildThis builds both the Zig backend and the React frontend.
# Create configuration
cp upstreams.example.json upstreams.json
# Edit upstreams.json with your lean node endpoints
# Run with web interface
./zig-out/bin/leanpoint --upstreams-config upstreams.json --static-dir web-distOpen your browser to:
http://localhost:5555
You'll see:
- Real-time checkpoint status (finalized/justified slots)
- Upstreams table with health status
- Consensus information
- Historical checkpoint tracking
# Build only backend
make build-backend
# Run backend
./zig-out/bin/leanpoint --upstreams-config upstreams.json# Start frontend dev server with hot reload
make dev
# or
cd web && npm run devThe dev server runs on http://localhost:5173 and proxies API requests to http://localhost:5555.
Important: Keep the backend running while developing the frontend!
# Terminal 1: Backend
./zig-out/bin/leanpoint --upstreams-config upstreams.json
# Terminal 2: Frontend dev server
make dev# Build everything for production
make build
# Run with static frontend
./zig-out/bin/leanpoint --upstreams-config upstreams.json --static-dir web-distThe backend exposes these endpoints:
| Endpoint | Description | Used By |
|---|---|---|
GET / |
Web UI (if --static-dir set) |
Browsers |
GET /status |
Current checkpoint status (JSON) | Frontend, Monitoring |
GET /api/upstreams |
Upstream nodes data (JSON) | Frontend |
GET /metrics |
Prometheus metrics | Monitoring |
GET /healthz |
Health check | Load balancers |
Returns current finality checkpoint:
{
"justified_slot": 12345,
"finalized_slot": 12344,
"last_updated_ms": 1705852800000,
"last_success_ms": 1705852800000,
"stale": false,
"error_count": 0,
"last_error": null
}Returns detailed upstream status:
{
"upstreams": [
{
"name": "zeam_0",
"url": "http://127.0.0.1:8081",
"path": "/v0/health",
"healthy": true,
"last_success_ms": 1705852800000,
"error_count": 0,
"last_error": null,
"last_justified_slot": 12345,
"last_finalized_slot": 12344
}
],
"consensus": {
"total_upstreams": 4,
"responding_upstreams": 4,
"has_consensus": true
}
}cd /path/to/lean-quickstart
./spin-node.sh --node allcd /path/to/leanpoint
python3 convert-validator-config.py \
../lean-quickstart/local-devnet/genesis/validator-config.yaml \
upstreams-local.json./zig-out/bin/leanpoint --upstreams-config upstreams-local.json --static-dir web-distOpen http://localhost:5555 and watch the dashboard update in real-time!
Edit web/src/styles/App.css:
:root {
--primary-color: #6366f1; /* Change primary color */
--secondary-color: #8b5cf6; /* Change secondary color */
--bg-color: #0f172a; /* Change background */
/* ... more variables ... */
}- Status Cards:
web/src/components/StatusCard.tsx - Upstreams Table:
web/src/components/UpstreamsTable.tsx - Main Layout:
web/src/App.tsx
After making changes:
# Rebuild frontend
make build-web
# Or use hot reload during development
make dev| Command | Description |
|---|---|
make build |
Build backend + frontend |
make build-backend |
Build only Zig backend |
make build-web |
Build only frontend |
make install-web |
Install frontend dependencies |
make dev |
Start frontend dev server |
make run |
Run backend with UI |
make clean |
Clean all build artifacts |
make clean-web |
Clean only frontend artifacts |
make help |
Show all commands |
leanpoint [options]
Options:
--bind <addr> Bind address (default 0.0.0.0)
--port <port> Bind port (default 5555)
--upstreams-config <file> JSON config file with multiple upstreams
--poll-ms <ms> Poll interval in milliseconds (default 10000)
--timeout-ms <ms> Request timeout in milliseconds (default 5000)
--stale-ms <ms> Stale threshold in milliseconds (default 30000)
--static-dir <dir> Static frontend directory (e.g., web-dist)
--help Show this helpExample upstreams.json:
{
"upstreams": [
{
"name": "zeam_0",
"url": "http://127.0.0.1:8081",
"path": "/v0/health"
},
{
"name": "ream_0",
"url": "http://127.0.0.1:8082",
"path": "/v0/health"
},
{
"name": "qlean_0",
"url": "http://127.0.0.1:8083",
"path": "/v0/health"
}
]
}Use the helper script to convert validator-config.yaml:
python3 convert-validator-config.py \
/path/to/validator-config.yaml \
upstreams.json# Check if port 5555 is already in use
lsof -i :5555
# Try a different port
./zig-out/bin/leanpoint --upstreams-config upstreams.json --port 5556 --static-dir web-dist- Check backend is running:
curl http://localhost:5555/status - Check browser console for errors
- Verify API proxy in
web/vite.config.ts
# Clean and rebuild
make clean
make build- Check both backend and frontend dev server are running
- Hard refresh browser (Cmd+Shift+R or Ctrl+Shift+R)
- Check browser console for errors
The Docker image includes the complete web UI:
# Build image (includes web UI)
docker build -t leanpoint:latest .
# Run with web UI
docker run -p 5555:5555 \
-v $(pwd)/upstreams.json:/etc/leanpoint/upstreams.json \
leanpoint:latest
# The image automatically serves the web UI from /usr/share/leanpoint/web
# Access at http://localhost:5555# Build everything
make build
# Deploy these files:
# - zig-out/bin/leanpoint (backend binary)
# - web-dist/ (frontend static files)
# - upstreams.json (your config)
# Run on server
./leanpoint --upstreams-config upstreams.json --static-dir web-dist- β Multi-upstream support with 50%+ consensus requirement
- β Parallel polling of all lean nodes for low latency
- β Per-upstream health tracking with error counts and timestamps
- β Modern web UI with real-time updates
- β Prometheus metrics for comprehensive monitoring
- β Health check endpoint for load balancers
- β Lightweight binary (~8MB backend + 155KB frontend)
- β Easy integration with lean-quickstart devnets
Leanpoint requires 50%+ of upstreams to agree before serving finality data.
- Poll Phase: All upstreams are polled concurrently
- Collection Phase: Justified/finalized slot pairs are collected
- Counting Phase: Each unique slot pair is counted
- Consensus Phase: Only pairs with >50% votes are accepted
- Serving Phase: Consensus data is exposed via API and UI
| Scenario | Agreement | Result |
|---|---|---|
| 3 upstreams, all agree | 3/3 = 100% | β Serve data |
| 3 upstreams, 2 agree | 2/3 = 67% | β Serve data |
| 4 upstreams, 2 agree | 2/4 = 50% | β No consensus |
| 5 upstreams, 3 agree | 3/5 = 60% | β Serve data |
Add to prometheus.yml:
scrape_configs:
- job_name: 'leanpoint'
scrape_interval: 10s
static_configs:
- targets: ['localhost:5555']
metrics_path: '/metrics'leanpoint_justified_slot # Latest justified slot
leanpoint_finalized_slot # Latest finalized slot
leanpoint_last_success_timestamp_ms # Last successful consensus
leanpoint_last_updated_timestamp_ms # Last update attempt
leanpoint_last_latency_ms # Poll latency
leanpoint_error_total # Total errors
- Frontend Development: See
web/README.mdfor frontend-specific docs - Checkpointz (inspiration): https://github.com/ethpandaops/checkpointz
- Lean Quickstart: Integration with devnets
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feat/amazing-feature) - Make your changes with clear commit messages
- Build and test:
make build - Submit a pull request
GitHub Actions CI automatically:
- Builds both backend and frontend
- Runs tests
- Checks code formatting
- Builds Docker image
MIT License - see LICENSE file for details
- Inspired by checkpointz by ethPandaOps
- Built for the Lean Ethereum ecosystem
- Written in Zig
Built with β‘ by the Lean Ethereum community