Skip to content

Commit ad040cb

Browse files
committed
Merge branch 'main' into ssz-update-v2
# Conflicts: # Cargo.lock # Cargo.toml # crates/common/src/config/signer.rs # crates/common/src/pbs/types/mod.rs # crates/pbs/src/mev_boost/get_header.rs # tests/data/configs/pbs.happy.toml # tests/src/utils.rs
2 parents 4bc807b + efda6a6 commit ad040cb

46 files changed

Lines changed: 2410 additions & 926 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent/CONTEXT.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Project Context
2+
3+
## Domain Glossary
4+
5+
**Commit-Boost** — A modular sidecar for Ethereum validators. Runs alongside the beacon node, standardizing the last mile of communication between validators and third-party protocols (MEV-Boost relays, preconfirmation services, inclusion list services, etc.).
6+
7+
**Proposer-Builder Separation (PBS)** — The Ethereum protocol split where validators propose blocks but builders construct them. Commit-Boost acts as middleware between the beacon node's Builder API calls and external relays/builders.
8+
9+
**PBS Service** — The core service (`cb-pbs` crate) that implements the [Builder API](https://ethereum.github.io/builder-specs/). Receives `get_header`, `submit_block`, `register_validator`, and `get_status` calls from the beacon node, fans them out to configured relays, selects the best bid, and returns it.
10+
11+
**Signer Service** — A separate HTTP service (`cb-signer` crate) that holds validator consensus keys and creates signatures. Modules request signatures (BLS or ECDSA, via consensus keys or proxy keys) using JWT-authenticated HTTP calls. The signer *never* exposes private keys to modules.
12+
13+
**Commit Module** — A plugin (Docker container) that implements a specific proposer commitment protocol (e.g., preconfirmations, inclusion lists). Communicates with the Signer via the Signer Client (`SignerClient` in `cb-common`). Loaded as a separate process; configured in the main TOML config under `[modules]`.
14+
15+
**Builder API Module** — Like a commit module but plugs into the PBS request pipeline. Can add custom routes and override the default MEV-Boost behavior via the `BuilderApi` trait.
16+
17+
**Mux (Multiplexer)** — A configuration construct that routes different validator pubkeys to different sets of relays with different timing/config overrides. A "pubkey → mux" mapping. Supports loading pubkeys from: static file, HTTP endpoint, or Registry (Lido/SSV operator registries, with optional auto-refresh).
18+
19+
**Relay** — An external MEV-Boost relay that receives builder API calls. Defined by `scheme://pubkey@host` URL format. Supports timing games (delayed header requests), custom headers, GET params, and retry limits.
20+
21+
**Consensus Signer** — A BLS keypair loaded into the Signer (from keystore, Dirk, or other loader). Used to sign consensus-layer messages (e.g., preconfirmation commitments).
22+
23+
**Proxy Key** — A derived key (BLS or ECDSA) that a commit module generates via the Signer. The Signer creates a proxy delegation from a consensus key, allowing the module to sign messages without holding the consensus key. Proxy delegations can be persisted via a Proxy Store.
24+
25+
**Proxy Store** — Persistence for proxy key delegations. Ensures proxy keys survive restarts. Supports ERC-2335 keystore format or raw file.
26+
27+
**Dirk** — A remote signer backend (Attestant's Dirk). The Signer can delegate to Dirk for consensus signing, while still generating proxy keys locally. Note: ECDSA proxy signing is not supported with Dirk.
28+
29+
**Timing Games** — A relay-specific setting where the PBS service delays `get_header` requests until a specific time in the slot, then polls at a configured frequency. Used to give late builders an advantage.
30+
31+
**Registry Mux** — A mux whose pubkey list comes from a live operator registry (Lido CSM, Lido curated module, SSV network). The pubkey list auto-refreshes at a configurable interval.
32+
33+
**Fork-Versioned Response** — The pattern used for `get_header` and `submit_block` responses. Responses are wrapped in `ForkVersionedResponse<T>` (from lighthouse) which carries a fork version discriminator. The PBS service multiplexes across Electra and Fulu fork request/response shapes.
34+
35+
**Builder API Version**`V1` for the standard Builder API, `V2` for the extended API (supports execution requests). Routes are nested under `/eth/v1/builder/` and `/eth/v2/builder/`.
36+
37+
## Architecture Map
38+
39+
```
40+
┌─────────────────────────────────────────────────────────────────┐
41+
│ commit-boost binary │
42+
│ Subcommands: pbs | signer | init │
43+
├─────────────────────────────────────────────────────────────────┤
44+
│ │
45+
│ ┌──────────────────────┐ ┌──────────────────────┐ │
46+
│ │ PBS Service │ │ Signer Service │ │
47+
│ │ (cb-pbs crate) │ │ (cb-signer crate) │ │
48+
│ │ │ │ │ │
49+
│ │ Routes: │ │ Routes: │ │
50+
│ │ GET get_header ─────│───▶│ POST request_sig_bls │ │
51+
│ │ GET get_status │ │ POST request_sig_* │ │
52+
│ │ POST register_val │ │ GET get_pubkeys │ │
53+
│ │ POST submit_block │ │ POST generate_proxy │ │
54+
│ │ POST reload │ │ POST reload │ │
55+
│ │ │ │ POST revoke_module │ │
56+
│ │ State: │ │ GET status │ │
57+
│ │ ┌─────────────────┐ │ │ │ │
58+
│ │ │ PbsState<S> │ │ │ State: │ │
59+
│ │ │ config: PbsModCfg│ │ │ ┌───────────────┐ │ │
60+
│ │ │ mux_lookup: K→V │ │ │ │ SigningState │ │ │
61+
│ │ │ relays: vec │ │ │ │ manager: Mgr │ │ │
62+
│ │ │ data: S (ext) │ │ │ │ jwts: K→V │ │ │
63+
│ │ └─────────────────┘ │ │ │ admin_secret │ │ │
64+
│ │ │ │ └───────────────┘ │ │
65+
│ │ BuilderApi trait: │ │ │ │
66+
│ │ ┌─────────────────┐ │ │ SigningManager enum: │ │
67+
│ │ │ get_header │ │ │ ┌── LocalManager │ │
68+
│ │ │ get_status │ │ │ └── DirkManager │ │
69+
│ │ │ submit_block │ │ │ │ │
70+
│ │ │ register_val │ │ │ LocalManager: │ │
71+
│ │ │ reload │ │ │ ┌── consensus keys │ │
72+
│ │ │ extra_routes() │ │ │ └── proxy keys │ │
73+
│ │ └─────────────────┘ │ │ │ │
74+
│ │ │ │ Proxy store: │ │
75+
│ │ Fan-out to N relays │ │ ┌── ERC2335 keystore │ │
76+
│ │ Best bid selection │ │ └── raw file │ │
77+
│ └───────────────────────┘ └───────────────────────┘ │
78+
│ │ │ │
79+
│ │ SignerClient │ JWT auth middleware │
80+
│ │ (cb-common) │ rate limiter │
81+
│ ▼ ▼ │
82+
│ ┌────────────────────────────────────────────────────────┐ │
83+
│ │ cb-common crate │ │
84+
│ │ ┌──────────┬──────────┬──────────┬──────────────────┐ │ │
85+
│ │ │ config/ │ pbs/ │ commit/ │ signer/ │ │ │
86+
│ │ │ Config │ types │ client │ schemes (BLS/ECD) │ │ │
87+
│ │ │ Mux │ relay │ request │ loader │ │ │
88+
│ │ │ Module │ builder │ response │ store (ERC2335) │ │ │
89+
│ │ │ Signer │ constants│ error │ types │ │ │
90+
│ │ ├──────────┼──────────┼──────────┼──────────────────┤ │ │
91+
│ │ │ interop/ │ types │ utils │ signature │ │ │
92+
│ │ │ lido/ssv │ Chain │ JWT │ verify_signed_msg │ │ │
93+
│ │ └──────────┴──────────┴──────────┴──────────────────┘ │ │
94+
│ └────────────────────────────────────────────────────────┘ │
95+
└─────────────────────────────────────────────────────────────────┘
96+
97+
External:
98+
Beacon Node ──Builder API──▶ PBS Service
99+
PBS Service ──Builder API──▶ Relays (MEV-Boost)
100+
PBS Service ──Commit API───▶ Commit Modules (sidecars)
101+
Commit Modules ──Signer API─▶ Signer Service
102+
Signer Service ──gRPC──────▶ Dirk (optional remote signer)
103+
```
104+
105+
## Crate Responsibilities
106+
107+
| Crate | Purpose | Key Public API |
108+
|-------|---------|---------------|
109+
| `cb-common` | Shared types, config, PBS types, signer types, commit client, interop | `Chain`, `PbsConfig`, `RelayClient`, `SignerClient`, `ModuleId`, `Jwt`, `get_header`, `submit_block` |
110+
| `cb-pbs` | PBS service: routes, MEV-Boost relay fan-out, state management | `PbsService`, `PbsState`, `BuilderApi` trait, `DefaultBuilderApi` |
111+
| `cb-signer` | Signer service: key management, signing endpoints, JWT auth | `SigningService`, `SigningManager`, `LocalSigningManager`, `DirkManager` |
112+
| `cb-metrics` | Prometheus metrics provider | `MetricsProvider` |
113+
| `cb-cli` | Docker compose init helper | `handle_docker_init` |
114+
115+
## Data Flow: get_header Request
116+
117+
```
118+
Beacon Node
119+
│ GET /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}
120+
121+
create_app_router (routes/router.rs)
122+
│ Match GET_HEADER_PATH → handle_get_header
123+
124+
handle_get_header (routes/get_header.rs)
125+
│ Extract GetHeaderParams, req headers
126+
│ Look up state.mux_config_and_relays(pubkey)
127+
│ → returns (PbsConfig, &[RelayClient], Option<mux_id>)
128+
129+
mev_boost::get_header (mev_boost/get_header.rs)
130+
│ For each relay:
131+
│ If timing_games: delay then poll at frequency
132+
│ If not: immediate request
133+
│ Track best bid by value
134+
│ Optional extra validation: check EL block validity
135+
│ Return best GetHeaderResponse or None (204)
136+
137+
Response to Beacon Node
138+
```
139+
140+
## Key Configuration Flow
141+
142+
```
143+
config.example.toml / CB_CONFIG env
144+
145+
├── [chain] → Chain (Mainnet/Holesky/Sepolia/Hoodi/Custom)
146+
├── [[relays]] → Vec<RelayConfig> → Vec<RelayClient>
147+
├── [pbs] → StaticPbsConfig → PbsConfig
148+
├── [[mux]] → PbsMuxes → HashMap<BlsPublicKey, RuntimeMuxConfig>
149+
├── [[modules]] → Vec<StaticModuleConfig> → module Docker containers
150+
└── [signer] → SignerConfig → StartSignerConfig
151+
```
152+
153+
## Relationships
154+
155+
- **PBS Service → Signer Service**: Uses `SignerClient` (from `cb-common`) to request signatures. Only when `with_signer = true` in PBS config.
156+
- **Commit Module → Signer Service**: Each module gets its own JWT. Calls signer endpoints for consensus/proxy BLS/ECDSA signatures.
157+
- **PBS Service → Relays**: Fans out Builder API calls. One HTTP client per relay. Timing games per relay. Mux routing per validator pubkey.
158+
- **Signer Service → Dirk**: Delegates consensus signing to Dirk gRPC backend. Proxy key generation still local.
159+
- **PBS Mux → Registry**: Lido CSM/module or SSV operator registries queried for validator pubkey lists. Auto-refresh for dynamic sets.
160+
161+
## Flagged Ambiguities
162+
163+
- **Module vs sidecar vs plugin**: The codebase uses "module" for the signer auth system (`ModuleId`, `ModuleSigningConfig`), but the PBS service also refers to "modules" (builder API plugins like preconfirmations). These are different concepts sharing one term.
164+
- **PBS Config reload**: The PBS service watches the config file and hot-reloads on change. The signer also supports reload via admin API. These are independent mechanisms.
165+
- **Custom chain support**: `Chain::Custom` allows any chain with genesis params. Some interop features (SSV) only support Mainnet/Holesky/Hoodi.
166+
167+
## Example Dialogue
168+
169+
**"How does a validator use Commit-Boost with MEV-Boost?"**
170+
→ Configure relays in `[[relays]]`, set `[pbs]` options, run `commit-boost pbs`. Beacon node points Builder API at Commit-Boost. Commit-Boost fans out to relays, returns best bid. Optional: add timing games per relay.
171+
172+
**"How do I add a preconfirmation module?"**
173+
→ Add `[[modules]]` entry with `type = "commit"`, `id`, `docker_image`, and `signing_id`. The module container gets `MODULE_JWT` and `SIGNER_URL` env vars. It calls `SignerClient` to request consensus/proxy signatures. Add `jwt_secret` to JWT secrets file.
174+
175+
**"What is a mux and when do I need it?"**
176+
→ A mux routes specific validator pubkeys to specific relay sets with custom timeouts. Use when different validators under the same node operator need different relay configurations, or when integrating with SSV/Lido operator registries for automatic pubkey discovery.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: 0001
3+
title: "Signer as separate service with JWT-auth and proxy key model"
4+
status: "accepted" # proposed | accepted | deprecated | superseded
5+
date: 2026-05-11T20:08:58.555Z
6+
---
7+
8+
# ADR-0001: Signer as separate service with JWT-auth and proxy key model
9+
10+
Signer runs as a separate service holding consensus keys. Modules authenticate via per-module JWT secrets. Signer exposes HTTP endpoints for: BLS consensus signing, BLS proxy signing, ECDSA proxy signing, proxy key generation. Proxy keys are derived from consensus keys using a signing ID + module ID. Optional Dirk backend for remote signing with local proxy key generation fallback.
11+
12+
## Context
13+
14+
Commit-Boost modules (preconfirmations, inclusion lists, etc.) need to sign messages with validator consensus keys. Giving modules direct access to keys is insecure — modules are third-party Docker containers. Need a way to sign without key exposure, and to derive per-module keys for non-consensus signing.
15+
16+
## Consequences
17+
18+
Modules never hold or see consensus private keys. Signer can be a separate process with its own security boundary. JWT auth per module enables access control and revocation. Rate limiting on JWT failure prevents brute-force. Proxy key model allows modules to generate delegated keys without exposing consensus keys. Trade-off: extra network hop for every signature request, operational complexity of running signer sidecar.
19+
20+
## Alternatives considered
21+
22+
- Embed signing in each module (each module holds its own keys)
23+
- Use a shared in-process signing library
24+
- Use an external Web3Signer-compatible service only
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: 0002
3+
title: "Mux-based relay routing with pubkey-to-config HashMap"
4+
status: "accepted" # proposed | accepted | deprecated | superseded
5+
date: 2026-05-11T20:09:15.478Z
6+
---
7+
8+
# ADR-0002: Mux-based relay routing with pubkey-to-config HashMap
9+
10+
PBS Mux system: each mux has an id, a list of validator pubkeys, a relay list, and optional timeout overrides. `PbsState::mux_config_and_relays(pubkey)` does O(1) HashMap lookup to find the mux for a given validator. Mux pubkeys loaded from: static JSON file, HTTP endpoint, or Registry (Lido CSM/curated, SSV operator). Registry muxes support auto-refresh on a configurable interval. Default relay list used for pubkeys not in any mux.
11+
12+
## Context
13+
14+
Node operators run validators with different needs: some need fast relay response, others run timing games, some belong to SSV clusters or Lido curated modules. A single relay configuration for all validators is too coarse. Need to route different validator pubkeys to different relay sets with different timing/config.
15+
16+
## Consequences
17+
18+
Validators can be grouped into different relay/timing configurations without running multiple sidecars. Registry-based muxes (Lido, SSV) auto-discover pubkeys, reducing config maintenance. Mux pubkey sets must be disjoint (validated at config load). Config reload updates mux mappings live. Trade-off: config complexity increases with mux count; mux config is eagerly loaded at startup (blocking).
19+
20+
## Alternatives considered
21+
22+
- Single flat relay list for all validators
23+
- Per-validator config file with all relay settings
24+
- Dynamic relay selection based on bid history/performance
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: 0003
3+
title: "Lighthouse types as canonical Ethereum data model"
4+
status: "accepted" # proposed | accepted | deprecated | superseded
5+
date: 2026-05-11T20:09:30.453Z
6+
---
7+
8+
# ADR-0003: Lighthouse types as canonical Ethereum data model
9+
10+
Use Lighthouse (`lh_types`, `lh_eth2`) as the canonical source for Ethereum beacon chain types. Types are aliased through `cb-common::pbs::types` (e.g., `SignedBlindedBeaconBlock = lh_types::SignedBlindedBeaconBlock<MainnetEthSpec>`). This ensures wire-format compatibility with Lighthouse beacon nodes and benefits from Lighthouse's fork-aware dispatch.
11+
12+
## Context
13+
14+
Commit-Boost implements the Ethereum Builder API which uses SSZ-serialized beacon chain types (blinded blocks, execution payloads, builder bids). Need canonical type definitions that match the beacon node's encoding. Types must handle fork transitions (Electra → Fulu) where block body shape changes.
15+
16+
## Consequences
17+
18+
All Beacon API types (SignedBlindedBeaconBlock, ExecutionPayloadHeader, etc.) are lighthouse type aliases. Fork-aware types like ForkVersionedResponse come from lighthouse. SSZ serialization, tree-hash derivation, and fork dispatch all use lighthouse infrastructure. Trade-off: lighthouse dependency is heavyweight; version coupling means lighthouse upgrades may force Commit-Boost upgrades even if Builder API didn't change.
19+
20+
## Alternatives considered
21+
22+
- Define own Ethereum types from spec
23+
- Use alloy/ethereum-consensus crate
24+
- Use reth primitives

0 commit comments

Comments
 (0)