diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..671a084 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "attribution": { + "commit": "", + "pr": "" + } +} diff --git a/.github/prompts/pr_review.md b/.github/prompts/pr_review.md new file mode 100644 index 0000000..559b037 --- /dev/null +++ b/.github/prompts/pr_review.md @@ -0,0 +1,20 @@ +You are a senior code reviewer for ethlambda, a minimalist Lean Ethereum consensus client written in Rust. + +Review this PR focusing on: +- Code correctness and potential bugs +- Security vulnerabilities (critical for blockchain code) +- Performance implications +- Rust best practices and idiomatic patterns +- Memory safety and proper error handling +- Code readability and maintainability + +Consensus-layer considerations: +- Fork choice (LMD GHOST / 3SF-mini) correctness +- Attestation processing and validation +- Justification and finalization logic +- State transition functions (process_slots, process_block) +- XMSS signature verification and aggregation +- SSZ encoding/decoding correctness + +Be concise and specific. Provide line references when suggesting changes. +If the code looks good, acknowledge it briefly. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 637c299..3eea9cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Rust uses: dtolnay/rust-toolchain@master @@ -45,7 +45,7 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Download test fixtures env: diff --git a/.github/workflows/docker_publish.yaml b/.github/workflows/docker_publish.yaml index 3d3d994..a7ab50b 100644 --- a/.github/workflows/docker_publish.yaml +++ b/.github/workflows/docker_publish.yaml @@ -30,7 +30,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/pr_review_chatgpt.yaml b/.github/workflows/pr_review_chatgpt.yaml new file mode 100644 index 0000000..70affed --- /dev/null +++ b/.github/workflows/pr_review_chatgpt.yaml @@ -0,0 +1,48 @@ +name: PR Review - ChatGPT + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_review_comment: + types: [created] + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + chatgpt-review: + name: ChatGPT Code Review + if: | + github.event_name == 'pull_request' || + (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@chatgpt')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@chatgpt')) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Read review prompt + id: prompt + run: | + PROMPT=$(cat .github/prompts/pr_review.md) + echo "content<> $GITHUB_OUTPUT + echo "$PROMPT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: ChatGPT Code Review + uses: anc95/ChatGPT-CodeReview@6fdbaeafc6f9e0eaebb844f8cfafff67cb2947f0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + MODEL: gpt-4o + LANGUAGE: English + MAX_PATCH_LENGTH: 100000 + max_tokens: 4096 + PROMPT: ${{ steps.prompt.outputs.content }} diff --git a/.github/workflows/pr_review_claude.yaml b/.github/workflows/pr_review_claude.yaml new file mode 100644 index 0000000..83379e1 --- /dev/null +++ b/.github/workflows/pr_review_claude.yaml @@ -0,0 +1,49 @@ +name: PR Review - Claude + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_review_comment: + types: [created] + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + issues: write + id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + claude-review: + name: Claude Code Review + if: | + github.event_name == 'pull_request' || + (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Read review prompt + id: prompt + run: | + PROMPT=$(cat .github/prompts/pr_review.md) + echo "content<> $GITHUB_OUTPUT + echo "$PROMPT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Claude Code Review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: | + --max-turns 5 + --model claude-sonnet-4-20250514 + trigger_phrase: "@claude" + prompt: ${{ steps.prompt.outputs.content }} diff --git a/.github/workflows/pr_review_kimi.yaml b/.github/workflows/pr_review_kimi.yaml new file mode 100644 index 0000000..5599be2 --- /dev/null +++ b/.github/workflows/pr_review_kimi.yaml @@ -0,0 +1,113 @@ +name: PR Review - Kimi + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_review_comment: + types: [created] + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + kimi-review: + name: Kimi Code Review + if: | + github.event_name == 'pull_request' || + (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@kimi')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@kimi')) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Read review prompt + id: prompt + run: | + PROMPT=$(cat .github/prompts/pr_review.md) + echo "content<> $GITHUB_OUTPUT + echo "$PROMPT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Get PR diff + id: diff + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr diff ${{ github.event.pull_request.number }} > pr_diff.txt + # Truncate if too large (Kimi has context limits) + head -c 100000 pr_diff.txt > pr_diff_truncated.txt + + - name: Kimi Code Review + id: kimi_review + env: + KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} + PR_TITLE: ${{ github.event.pull_request.title }} + REVIEW_PROMPT: ${{ steps.prompt.outputs.content }} + run: | + if [ -z "$KIMI_API_KEY" ]; then + echo "Error: KIMI_API_KEY secret is not set" > kimi_review.txt + exit 0 + fi + + DIFF_CONTENT=$(cat pr_diff_truncated.txt) + + # Build the request body + REQUEST_BODY=$(jq -n \ + --arg diff "$DIFF_CONTENT" \ + --arg title "$PR_TITLE" \ + --arg prompt "$REVIEW_PROMPT" \ + '{ + "model": "moonshot-v1-128k", + "messages": [ + { + "role": "system", + "content": $prompt + }, + { + "role": "user", + "content": ("PR Title: " + $title + "\n\nDiff:\n" + $diff) + } + ], + "temperature": 0.3, + "max_tokens": 4096 + }') + + # Try the API call + HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" https://api.moonshot.ai/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $KIMI_API_KEY" \ + -d "$REQUEST_BODY") + + HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n1) + RESPONSE=$(echo "$HTTP_RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" != "200" ]; then + echo "API Error (HTTP $HTTP_CODE): $RESPONSE" > kimi_review.txt + else + # Check for API errors in response + ERROR=$(echo "$RESPONSE" | jq -r '.error.message // empty') + if [ -n "$ERROR" ]; then + echo "API Error: $ERROR" > kimi_review.txt + else + REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "Error: Unexpected API response"') + echo "$REVIEW" > kimi_review.txt + fi + fi + + - name: Post review comment + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "

Kimi AI Code Review

" > body.md + cat kimi_review.txt >> body.md + echo -e "\n---\n*Automated review by Kimi (Moonshot AI)*\n
" >> body.md + + gh pr comment ${{ github.event.pull_request.number }} --body-file body.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b3633e0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,189 @@ +# ethlambda Development Guide + +Development reference for ethlambda - minimalist Lean Ethereum consensus client. +Not to be confused with Ethereum consensus clients AKA Beacon Chain clients AKA Eth2 clients. + +## Quick Reference + +**Main branch:** `main` +**Rust version:** 1.92.0 (edition 2024) +**Test fixtures commit:** Check `LEAN_SPEC_COMMIT_HASH` in Makefile + +## Codebase Structure (10 crates) + +``` +bin/ethlambda/ # Entry point, CLI, orchestration +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 + ├─ fork_choice/ # LMD GHOST implementation (3SF-mini) + └─ state_transition/ # STF: process_slots, process_block, attestations + common/ + ├─ types/ # Core types (State, Block, Attestation, Checkpoint) + ├─ crypto/ # XMSS aggregation (leansig wrapper) + └─ metrics/ # Prometheus metrics + net/ + ├─ p2p/ # libp2p: gossipsub + req-resp (Status, BlocksByRoot) + └─ rpc/ # Axum HTTP endpoints (/lean/v0/* and /metrics) + storage/ # RocksDB backend, in-memory for tests +``` + +## Key Architecture Patterns + +### Actor Concurrency (spawned-concurrency) +- **BlockChain**: Main state machine (GenServer pattern) +- **P2P**: Network event loop with libp2p swarm +- Communication via `mpsc::unbounded_channel` +- Shared storage via `Arc` (clone Store, share backend) + +### Tick-Based Validator Duties (4-second slots, 4 intervals per slot) +``` +Interval 0: Proposer check → accept attestations → build/publish block +Interval 1: Non-proposers produce attestations +Interval 2: Safe target update (fork choice with 2/3 threshold) +Interval 3: Accept accumulated attestations +``` + +### Attestation Pipeline +``` +Gossip → Signature verification → new_attestations (pending) + ↓ (intervals 0/3) +promote → known_attestations (fork choice active) + ↓ +Fork choice head update +``` + +### State Transition Phases +1. **process_slots()**: Advance through empty slots, update historical roots +2. **process_block()**: Validate header → process attestations → update justifications/finality +3. **Justification**: 3SF-mini rules (delta ≤ 5 OR n² OR n(n+1)) +4. **Finalization**: Source with no unjustifiable gaps to target + +## Development Workflow + +### Before Committing +```bash +cargo fmt # Format code +make lint # Clippy with -D warnings +make test # All tests + forkchoice (with skip-signature-verification) +``` + +### Common Operations +```bash +make run-devnet # Docker build → lean-quickstart local devnet +rm -rf leanSpec && make leanSpec/fixtures # Regenerate test fixtures (requires uv) +``` + +### Debugging + + + +## Important Patterns & Idioms + +### Metrics (RAII Pattern) +```rust +// Timing guard automatically observes duration on drop +let _timing = metrics::time_state_transition(); +``` + +### Relative Indexing (justified_slots) +```rust +// Bounded storage: index relative to finalized_slot +actual_slot = finalized_slot + 1 + relative_index +// Helper ops in justified_slots_ops.rs +``` + +## Cryptography & Signatures + +**XMSS (eXtended Merkle Signature Scheme):** +- Post-quantum signature scheme +- 52-byte public keys, 3112-byte signatures +- Epoch-based to prevent reuse +- Aggregation via leanVM for efficiency + +**Signature Aggregation (Two-Phase):** +1. **Gossip signatures**: Fresh XMSS from network → aggregate via leanVM +2. **Fallback to proofs**: Reuse previous block proofs for missing validators + +## Networking (libp2p) + +### Protocols +- **Transport**: QUIC over UDP (TLS 1.3) +- **Gossipsub**: Blocks + Attestations (snappy raw compression) + - Topic: `/leanconsensus/{network}/{block|attestation}/ssz_snappy` + - Mesh size: 8 (6-12 bounds), heartbeat: 700ms +- **Req/Resp**: Status, BlocksByRoot (snappy frame compression + varint length) + +### Retry Strategy on Block Requests +- Exponential backoff: 10ms, 40ms, 160ms, 640ms, 2560ms +- Max 5 attempts, random peer selection on retry + +### Message IDs +- 20-byte truncated SHA256 of: domain (valid/invalid snappy) + topic + data + +## Configuration Files + +**Genesis:** `genesis.json` (JSON format, cross-client compatible) +- `GENESIS_TIME`: Unix timestamp for slot 0 +- `GENESIS_VALIDATORS`: Array of 52-byte XMSS pubkeys (hex) + +**Validators:** JSON array of `{"pubkey": "...", "index": 0}` +**Bootnodes:** ENR records (Base64-encoded, RLP decoded for QUIC port + secp256k1 pubkey) + +## Testing + +### Test Categories +1. **Unit tests**: Embedded in source files +2. **Spec tests**: From `leanSpec/fixtures/consensus/` + - `forkchoice_spectests.rs` (requires `skip-signature-verification`) + - `signature_spectests.rs` + - `stf_spectests.rs` (state transition) + +### Running Tests +```bash +cargo test --workspace --release # All workspace tests +cargo test -p ethlambda-blockchain --features skip-signature-verification --test forkchoice_spectests +``` + +## Common Gotchas + +### Signature Verification +- Tests require `skip-signature-verification` feature for performance +- Crypto tests marked `#[ignore]` (slow leanVM operations) + +### State Root Computation +- Always computed via `tree_hash_root()` after full state transition +- Must match proposer's pre-computed `block.state_root` + +### Finalization Checks +- Use `original_finalized_slot` for justifiability checks during attestation processing +- Finalization updates can occur mid-processing + +### `justified_slots` Window Shifting +- Call `shift_window()` when finalization advances +- Prunes justifications for now-finalized slots + +## External Dependencies + +**Critical:** +- `leansig`: XMSS signatures (leanEthereum project) +- `ethereum_ssz`: SSZ serialization +- `tree_hash`: Merkle tree hashing +- `spawned-concurrency`: Actor model +- `libp2p`: P2P networking (custom LambdaClass fork) + +**Storage:** +- `rocksdb`: Persistent backend +- In-memory backend for tests + +## Resources + +**Specs:** `leanSpec/src/lean_spec/` (Python reference implementation) +**Devnet:** `lean-quickstart` (github.com/blockblaz/lean-quickstart) + +## Other implementations + +- zeam (Zig): +- ream (Rust): +- qlean (C++):