Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ target

# Used for generating test vectors
/leanSpec

# Personal Claude Code overrides (not shared with team)
CLAUDE.local.md

# Log output by make run-devnet
devnet.log
71 changes: 62 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@ Not to be confused with Ethereum consensus clients AKA Beacon Chain clients AKA

```
bin/ethlambda/ # Entry point, CLI, orchestration
└─ src/version.rs # Build-time version info (vergen-git2)
crates/
blockchain/ # State machine actor (GenServer pattern)
├─ src/lib.rs # BlockChain actor, tick events, validator duties
├─ src/store.rs # Fork choice store, block/attestation processing
├─ src/key_manager.rs # Validator key management and signing
├─ src/metrics.rs # Blockchain-level Prometheus metrics
├─ fork_choice/ # LMD GHOST implementation (3SF-mini)
└─ state_transition/ # STF: process_slots, process_block, attestations
└─ src/metrics.rs # State transition timing + counters
common/
├─ types/ # Core types (State, Block, Attestation, Checkpoint)
├─ crypto/ # XMSS aggregation (leansig wrapper)
└─ metrics/ # Prometheus metrics
└─ metrics/ # Prometheus re-exports, TimingGuard, gather utilities
net/
├─ p2p/ # libp2p: gossipsub + req-resp (Status, BlocksByRoot)
└─ rpc/ # Axum HTTP endpoints (/lean/v0/* and /metrics)
│ ├─ src/gossipsub/ # Topic encoding, message handling
│ ├─ src/req_resp/ # Request/response codec and handlers
│ └─ src/metrics.rs # Peer connection/disconnection tracking
└─ rpc/ # Axum HTTP: /lean/v0/{states,checkpoints,health} + /metrics
storage/ # RocksDB backend, in-memory for tests
└─ src/api/ # StorageBackend trait + Table enum
```

## Key Architecture Patterns
Expand Down Expand Up @@ -64,7 +72,7 @@ Fork choice head update

### Before Committing
```bash
cargo fmt # Format code
make fmt # Format code (cargo fmt --all)
make lint # Clippy with -D warnings
make test # All tests + forkchoice (with skip-signature-verification)
```
Expand All @@ -73,6 +81,8 @@ make test # All tests + forkchoice (with skip
```bash
.claude/skills/test-pr-devnet/scripts/test-branch.sh # Test branch in multi-client devnet
rm -rf leanSpec && make leanSpec/fixtures # Regenerate test fixtures (requires uv)
make docker-build # Build Docker image (DOCKER_TAG=local)
make run-devnet # Run local devnet with lean-quickstart
```

### Testing with Local Devnet
Expand All @@ -95,10 +105,10 @@ let byte: u8 = code.into();
### Ownership for Large Structures
```rust
// Prefer taking ownership to avoid cloning large data (signatures ~3KB)
pub fn consume_signed_block(signed_block: SignedBlockWithAttestation) { ... }
pub fn insert_signed_block(&mut self, root: H256, signed_block: SignedBlockWithAttestation) { ... }

// Add .clone() at call site if needed - makes cost explicit
store.insert_signed_block(root, signed_block.clone());
store.insert_signed_block(block_root, signed_block.clone());
```

### Formatting Patterns
Expand Down Expand Up @@ -157,12 +167,34 @@ let result = operation()
.map_err(|err| CustomError::from(err))?;
```

### Metrics (RAII Pattern)
### Metrics Patterns

**Registration with `LazyLock`:**
```rust
// Module-scoped statics (preferred for state_transition metrics)
static LEAN_STATE_TRANSITION_TIME_SECONDS: LazyLock<Histogram> = LazyLock::new(|| {
register_histogram!("lean_metric_name", "Description", vec![...]).unwrap()
});

// Function-scoped statics (used in blockchain metrics)
pub fn update_head_slot(slot: u64) {
static LEAN_HEAD_SLOT: LazyLock<IntGauge> = LazyLock::new(|| {
register_int_gauge!("lean_head_slot", "Latest slot").unwrap()
});
LEAN_HEAD_SLOT.set(slot.try_into().unwrap());
}
```

**RAII timing guard (auto-observes duration on drop):**
```rust
// Timing guard automatically observes duration on drop
let _timing = metrics::time_state_transition();
```

**All metrics use `ethlambda_metrics::*` re-exports** — the `ethlambda-metrics` crate re-exports
prometheus types (`IntGauge`, `IntCounter`, `Histogram`, etc.) and provides `TimingGuard` + `gather_default_metrics()`.

**Naming convention:** All metrics use `lean_` prefix (e.g., `lean_head_slot`, `lean_state_transition_time_seconds`).

### Logging Patterns

**Use tracing shorthand syntax for cleaner logs:**
Expand Down Expand Up @@ -281,8 +313,27 @@ cargo test -p ethlambda-blockchain --features skip-signature-verification --test
- Crypto tests marked `#[ignore]` (slow leanVM operations)

### Storage Architecture
- Genesis block has no signatures - stored in Blocks table only, not BlockSignatures
- All other blocks must have entries in both tables
- Blocks are split into three tables: `BlockHeaders`, `BlockBodies`, `BlockSignatures`
- Genesis/anchor blocks have empty bodies (detected via `EMPTY_BODY_ROOT`) — no entry in `BlockBodies`
- Genesis block has no signatures — no entry in `BlockSignatures`
- All other blocks must have entries in all three tables
- `LiveChain` table provides fast `(slot||root) → parent_root` index for fork choice
- Storage uses trait-based API: `StorageBackend` → `StorageReadView` (reads) + `StorageWriteBatch` (atomic writes)

### Storage Tables (10)

| Table | Key → Value | Purpose |
|-------|-------------|---------|
| `BlockHeaders` | H256 → BlockHeader | Block headers by root |
| `BlockBodies` | H256 → BlockBody | Block bodies (empty for genesis) |
| `BlockSignatures` | H256 → BlockSignaturesWithAttestation | Signatures (absent for genesis) |
| `States` | H256 → State | Beacon states by root |
| `LatestKnownAttestations` | u64 → AttestationData | Fork-choice-active attestations |
| `LatestNewAttestations` | u64 → AttestationData | Pending (pre-promotion) attestations |
| `GossipSignatures` | SignatureKey → ValidatorSignature | Individual validator signatures |
| `AggregatedPayloads` | SignatureKey → Vec\<AggregatedSignatureProof\> | Aggregated proofs |
| `Metadata` | string → various | Store state (head, config, checkpoints) |
| `LiveChain` | (slot\|\|root) → parent\_root | Fast fork choice traversal index |

### State Root Computation
- Always computed via `tree_hash_root()` after full state transition
Expand All @@ -304,6 +355,7 @@ cargo test -p ethlambda-blockchain --features skip-signature-verification --test
- `tree_hash`: Merkle tree hashing
- `spawned-concurrency`: Actor model
- `libp2p`: P2P networking (custom LambdaClass fork)
- `vergen-git2`: Build-time git commit/branch info embedded in binary

**Storage:**
- `rocksdb`: Persistent backend
Expand All @@ -313,6 +365,7 @@ cargo test -p ethlambda-blockchain --features skip-signature-verification --test

**Specs:** `leanSpec/src/lean_spec/` (Python reference implementation)
**Devnet:** `lean-quickstart` (github.com/blockblaz/lean-quickstart)
**Releases:** See `RELEASE.md` for release process documentation

## Other implementations

Expand Down
24 changes: 15 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.PHONY: help lint docker-build run-devnet test
.PHONY: help fmt lint docker-build run-devnet test

help: ## 📚 Show help for each of the Makefile recipes
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

fmt: ## 🎨 Format all code using rustfmt
cargo fmt --all

lint: ## 🔍 Run clippy on all workspace crates
cargo clippy --workspace --all-targets -- -D warnings

Expand All @@ -20,6 +23,7 @@ docker-build: ## 🐳 Build the Docker image
--build-arg GIT_COMMIT=$(GIT_COMMIT) \
--build-arg GIT_BRANCH=$(GIT_BRANCH) \
-t ghcr.io/lambdaclass/ethlambda:$(DOCKER_TAG) .
@echo

LEAN_SPEC_COMMIT_HASH:=4edcf7bc9271e6a70ded8aff17710d68beac4266

Expand All @@ -33,12 +37,14 @@ leanSpec/fixtures: leanSpec
lean-quickstart:
git clone https://github.com/blockblaz/lean-quickstart.git --depth 1 --single-branch


# TODO: start metrics too
run-devnet: docker-build lean-quickstart ## 🚀 Run a local devnet using lean-quickstart
# Go to lean-quickstart/local-devnet/genesis/validator-config.yaml to modify
# the validator configuration for the local devnet.
# NOTE: to run the local image of ethlambda, make sure to set the image tag
# in lean-quickstart/client-cmds/ethlambda-cmd.sh to "ghcr.io/lambdaclass/ethlambda:local"
cd lean-quickstart \
&& NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics
@echo "Starting local devnet with ethlambda client (\"$(DOCKER_TAG)\" tag). Logs will be dumped in devnet.log, and metrics served in http://localhost:3000"
@echo
@echo "Devnet will be using the current configuration. For custom configurations, modify lean-quickstart/local-devnet/genesis/validator-config.yaml and restart the devnet."
@echo
@# Use temp file instead of sed -i for macOS/GNU portability
@sed 's|ghcr.io/lambdaclass/ethlambda:[^ ]*|ghcr.io/lambdaclass/ethlambda:$(DOCKER_TAG)|' lean-quickstart/client-cmds/ethlambda-cmd.sh > lean-quickstart/client-cmds/ethlambda-cmd.sh.tmp \
&& mv lean-quickstart/client-cmds/ethlambda-cmd.sh.tmp lean-quickstart/client-cmds/ethlambda-cmd.sh
@echo "Starting local devnet. Press Ctrl+C to stop all nodes."
@cd lean-quickstart \
&& NETWORK_DIR=local-devnet ./spin-node.sh --node all --generateGenesis --metrics > ../devnet.log 2>&1
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ Minimalist, fast and modular implementation of the Lean Ethereum client written

## Getting started

We use `cargo` as our build system. To build and run the client, simply run:
We use `cargo` as our build system, but prefer `make` as a convenient wrapper for common tasks. These are some common targets:

```sh
cargo run
# Formats all code
make fmt
# Checks and lints the code
make lint
# Runs all tests
make test
# Builds a docker image tagged as "ghcr.io/lambdaclass/ethlambda:local"
make docker-build DOCKER_TAG=local
```

Run `make help` or take a look at our [`Makefile`](./Makefile) for other useful commands.
Expand All @@ -21,9 +28,11 @@ To run a local devnet with multiple clients using [lean-quickstart](https://gith
make run-devnet
```

This generates fresh genesis files and starts all three clients with metrics enabled.
This generates fresh genesis files and starts all configured clients with metrics enabled.
Press `Ctrl+C` to stop all nodes.

For custom devnet configurations, go to `lean-quickstart/local-devnet/genesis/validator-config.yaml` and edit the file before running the command above. See `lean-quickstart`'s documentation for more details on how to configure the devnet.

## Philosophy

Many long-established clients accumulate bloat over time. This often occurs due to the need to support legacy features for existing users or through attempts to implement overly ambitious software. The result is often complex, difficult-to-maintain, and error-prone systems.
Expand Down
2 changes: 1 addition & 1 deletion docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

We collect various metrics and serve them via a Prometheus-compatible HTTP endpoint at `http://<metrics_address>:<metrics_port>/metrics` (default: `http://127.0.0.1:5054/metrics`).

We provide a ready-to-use Grafana + Prometheus monitoring stack in the [`metrics/`](../metrics/) directory. It includes pre-configured dashboards from the [leanMetrics](https://github.com/leanEthereum/leanMetrics) repository for visualizing the metrics described below. See the [metrics README](../metrics/README.md) for setup instructions.
A ready-to-use Grafana + Prometheus monitoring stack with pre-configured [leanMetrics](https://github.com/leanEthereum/leanMetrics) dashboards is available in [lean-quickstart](https://github.com/blockblaz/lean-quickstart).

The exposed metrics follow [the leanMetrics specification](https://github.com/leanEthereum/leanMetrics/blob/3b32b300cca5ed7a7a2b3f142273fae9dbc171bf/metrics.md), with some metrics not yet implemented. We have a full list of implemented metrics below, with a checkbox indicating whether each metric is currently supported or not.

Expand Down
57 changes: 0 additions & 57 deletions metrics/README.md

This file was deleted.

51 changes: 0 additions & 51 deletions metrics/docker-compose-metrics.yaml

This file was deleted.

Loading