erigontech / erigontech/erigon

caplin: embedded dev validator for single-node development testing

Open
#20,191 2 comments 0 reactions 1 assignee Claimed by @domiwei View on GitHub
Caplin
Dominant language
Go
Stars
3.6k
Forks
1.5k
Avg merge
1d 16h
Merged PRs (30d)
455

Description

## Summary

Add an embedded \"dev validator\" to Caplin so that a single `erigon` process can run a self-contained consensus network without an external validator client (e.g. Lighthouse). Enabled by a `--dev-validator-seed` flag; intended for development and integration testing only.

**Prerequisite:** #20190 (minimal preset support) should be merged first, as the dev validator will target the minimal preset for fast slot times.

---

## Motivation

Today, running a local Caplin devnet requires:
1. Erigon EL
2. Caplin CL (embedded)
3. External Lighthouse VC (separate process, keystore setup)
4. Genesis state generation via external tooling

The goal is to reduce this to a single command:
\`\`\`bash
erigon --chain=dev --dev-validator-seed=devnet
\`\`\`

This makes it easy to test consensus logic, block production, finality, and Beacon API behaviour without orchestrating multiple processes.

---

## Design

The embedded VC runs as a goroutine inside the Caplin process. It uses the **same Beacon API endpoints** that Lighthouse uses today — no new internal paths needed. The only new surface is the signing loop and key management.

### Architecture

\`\`\`
Caplin beacon node
├── Beacon API (existing)
│ ├── GET /eth/v3/validator/blocks/{slot} ← block template
│ ├── GET /eth/v1/validator/attestation_data ← attestation data
│ ├── GET /eth/v1/validator/duties/* ← duty schedules
│ ├── POST /eth/v2/beacon/blocks ← submit signed block
│ ├── POST /eth/v1/beacon/pool/attestations ← submit attestations
│ └── POST /eth/v1/beacon/pool/sync_committees
└── DevValidatorService (NEW — same process, HTTP to localhost)
├── Deterministic BLS key(s) from seed
├── Duty scheduler (epoch boundary polling)
├── Block proposer (sign + submit)
├── Attestation signer (sign + submit)
└── Sync committee signer (sign + submit)
\`\`\`

### Key derivation

No EIP-2333 needed for dev. Use:
\`\`\`go
keyBytes := sha256.Sum256(append([]byte(seed), binary.BigEndian.AppendUint64(nil, index)...))
privKey, _ := bls.NewPrivateKeyFromBytes(keyBytes[:])
\`\`\`

### Genesis

The dev validator's public key(s) must be in the genesis state. Two options:
1. Programmatic genesis builder (preferred, see Phase 0 below)
2. Pre-generated genesis SSZ committed to the repo for known seeds

---

## Implementation Plan

### Phase 0 — Genesis builder (prerequisite, ~2 days)

A small CLI tool or library function that produces a valid beacon genesis state SSZ for a given set of BLS pubkeys and config:

\`\`\`
cmd/devtools/gen-beacon-genesis/main.go
\`\`\`

Inputs: validator pubkeys, BeaconChainConfig (minimal preset), genesis time
Output: `genesis.ssz` (SSZ-snappy encoded `BeaconState`)

This unblocks both the dev validator and the Kurtosis single-node setup.

---

### Phase 1 — MVP: proposals + attestations (~1 week)

New package: `cl/validator/dev_validator/`

| File | Responsibility | ~Lines |
|------|---------------|--------|
| `service.go` | Orchestrator: start/stop, wires components together | 300 |
| `keys.go` | Derive N deterministic BLS keys from a seed string | 50 |
| `scheduler.go` | Slot timer; poll duties at epoch boundaries; schedule signing | 200 |
| `block_proposer.go` | Compute RANDAO reveal, call GetBlock, sign, POST | 150 |
| `attestation_signer.go` | Get attestation data, build aggbits, sign, POST | 200 |
| `types.go` | Internal structs | 100 |

**Wiring changes (existing files):**

- `cl/beacon/handler/handler.go` — add optional `devValidator DevValidatorService` field (~30 lines)
- `cmd/caplin/main.go` (or `caplincli/config.go`) — add `--dev-validator-seed` and `--dev-validator-count` flags (~20 lines)

**Non-obvious implementation notes:**

- **RANDAO reveal**: sign `compute_signing_root(epoch, DOMAIN_RANDAO)` — not the block
- **Aggregation bits**: set the bit at `validatorCommitteeIndex` in a `(committeeLength+7)/8`-byte bitfield
- **Timing**: proposals fire at `slotStart + 4s`, attestations at `slotStart + 4s–8s`
- **Validator index lookup**: on startup, resolve pubkey → index via `GET /eth/v1/beacon/states/head/validators`
- **Electra attestation**: `AggregationBits` limit must use `MaxCommitteesPerSlot * MaxValidatorsPerCommittee` (already handled by #20190)

---

### Phase 2 — Sync committee duties (~3–4 days)

Add `sync_signer.go` (~250 lines):
- Subscribe to sync committee duties once per period (256 epochs)
- Per slot: create `SyncCommitteeMessage`, sign with `DOMAIN_SYNC_COMMITTEE`, POST to pool
- Aggregator selection: compute `SelectionProof`, create `SignedContributionAndProof`, POST

---

### Phase 3 — Integration + docs (~2 days)

- End-to-end test: start Erigon + Caplin with `--dev-validator-seed=test`, assert finality within N epochs
- Add to Hive test suite as a single-process smoke test
- CLI docs / `--help` text

---

## Out of scope (for now)

- **Slashing protection DB** — single process, dev-only; in-memory guard sufficient
- **EIP-2333 / encrypted keystores** — not needed for deterministic dev keys
- **MEV-Boost / builder** — not needed
- **Validator lifecycle** (deposits, exits) — genesis validator only
- **Multiple validator clients** — this is intentionally a dev-only feature

---

## Relevant code pointers

| What | File | Lines |
|------|------|-------|
| BLS `PrivateKey.Sign()` | `cl/utils/bls/private_key.go` | 56–59 |
| `ComputeSigningRoot` + domains | `cl/fork/fork.go` | 42–69 |
| Block template handler | `cl/beacon/handler/block_production.go` | 200–343 |
| Attestation data producer | `cl/validator/attestation_producer/attestation_producer.go` | 140–205 |
| Sync contribution pool | `cl/validator/sync_contribution_pool/sync_contribution_pool.go` | 74–194 |
| Proposer duties handler | `cl/beacon/handler/duties_proposer.go` | 45–159 |
| Attester duties handler | `cl/beacon/handler/duties_attester.go` | 74–219 |
| Sync duties handler | `cl/beacon/handler/duties_sync.go` | 38–144 |
| `ApiHandler` struct | `cl/beacon/handler/handler.go` | 66–119 |
| Devnet config detection | `cl/clparams/config.go` (`IsDevnet`) | ~89 |
| Custom genesis loading | `cmd/caplin/caplin1/run.go` | 167–175 |

---

## Estimated effort

| Phase | Scope | Effort |
|-------|-------|--------|
| 0 — Genesis builder | New CLI tool | ~2 days |
| 1 — Proposals + attestations | Core dev validator | ~1 week |
| 2 — Sync committees | Complete VC duties | ~3–4 days |
| 3 — Integration + tests | Wiring, docs, Hive | ~2 days |
| **Total** | | **~3 weeks** |

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.