Skip to content

ops: refactor layer1 for devnet tools#802

Merged
FletcherMan merged 8 commits intomainfrom
refactor_layer1
Nov 7, 2025
Merged

ops: refactor layer1 for devnet tools#802
FletcherMan merged 8 commits intomainfrom
refactor_layer1

Conversation

@FletcherMan
Copy link
Copy Markdown
Collaborator

@FletcherMan FletcherMan commented Nov 6, 2025

Summary by CodeRabbit

  • New Features

    • Added a built-in Layer 1 local network (execution, consensus, validator) with start, genesis generation, and clean workflows.
    • Integrated Layer 1 cleanup into devnet build/down workflows via new build targets.
  • Chores

    • Updated environment templates and compose configs for the new Layer 1 topology.
    • Added keystore, validator definitions and secret tooling to support local validator setup.

@FletcherMan FletcherMan requested a review from a team as a code owner November 6, 2025 09:33
@FletcherMan FletcherMan requested review from Web3Jumb0 and removed request for a team November 6, 2025 09:33
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Replaced legacy L1 beacon/validator stack with a new Layer1 stack (execution, consensus, validator), added genesis generation/start/clean scripts and Makefile targets, introduced many validator keystores/secrets and templates, updated devnet startup to use docker-compose and poll EL readiness and first mined block. (34 words)

Changes

Cohort / File(s) Summary
Build & Make targets
Makefile, ops/docker/Makefile.layer1
Added devnet-l1-clean target and .PHONY; introduced Makefile.layer1 with help, generate, start, stop, restart, logs, clean targets for Layer1 management.
Devnet init & startup logic
ops/devnet-morph/devnet/__init__.py
Added genesis existence check and optional generation; switched to docker-compose startup for Layer1 services (layer1-el, layer1-cl, layer1-vc); added eth_blockNumber() helper and polling for EL readiness and first block.
Environment updates
ops/docker/.env
Updated L1 RPC variables to reference new layer1-el / layer1-cl service names.
Docker Compose & Layer1 manifests
ops/docker/docker-compose-4nodes.yml, ops/docker/layer1/docker-compose.yml
Replaced legacy beacon/validator services with layer1-el (geth), layer1-cl (Lighthouse beacon), layer1-vc (Lighthouse validator); added named volumes and network; updated L2 service dependencies and flags to use new Layer1 endpoints.
Layer1 configs & templates
ops/docker/layer1/configs/values.env.template
Added environment template with chain parameters, fork settings, balances, and feature flags used for genesis/config generation.
Layer1 scripts
ops/docker/layer1/scripts/generate-genesis.sh, ops/docker/layer1/scripts/start.sh, ops/docker/layer1/scripts/clean.sh
Added genesis generation (timestamping, Docker-based generator), start script (prerequisite checks, compose start, EL readiness polling), and clean script (stop/remove containers, volumes, generated files).
Layer1 keystores & secrets
ops/docker/layer1/keystores/..., ops/docker/layer1/keystores/layer1/keys/*.json, ops/docker/layer1/keystores/layer1/secrets/*, ops/docker/layer1/keystores/layer1/pubkeys.json, .../validator_definitions.yml, .../validator_key_cache.json, .../api-token.txt
Added numerous validator artifacts: keystore JSON files, secret files (base64 values), pubkeys list, validator definitions YAML, key cache, and API token to provision validators.
Layer1 JWT & credentials
ops/docker/layer1/jwt/jwtsecret, ops/docker/layer1/keystores/layer1-password.txt
Added JWT secret and validator password file used by clients.
Repo layout
ops/docker/layer1/.gitignore
Added .gitignore to exclude generated genesis, keystores, configs, and jwt directories from VCS.
tx-submitter deps
tx-submitter/go.mod
Made github.com/consensys/gnark-crypto and github.com/crate-crypto/go-eth-kzg explicit direct requires (removed them as indirect).

Sequence Diagram(s)

sequenceDiagram
    participant Init as Devnet Init
    participant FS as Filesystem
    participant Gen as generate-genesis.sh
    participant Compose as Docker Compose
    participant EL as layer1-el (geth)
    participant CL as layer1-cl (beacon)
    participant VC as layer1-vc (validator)
    participant RPC as JSON-RPC

    rect `#dfefff`
      Init->>FS: check for genesis.json / jwt
      alt missing
        Init->>Gen: run generate-genesis.sh
        Gen->>FS: write genesis.json, genesis.ssz, configs, keystores
      end
    end

    rect `#e9f7ea`
      Init->>Compose: docker-compose up layer1-el, layer1-cl, layer1-vc
      Compose->>EL: start geth
      Compose->>CL: start beacon (depends on EL)
      Compose->>VC: start validator (depends on CL)
    end

    rect `#ffe9e9`
      Init->>EL: poll health / RPC
      loop until block > 0
        Init->>RPC: eth_blockNumber()
        RPC-->>Init: blockNumber
      end
      Init->>Init: continue devnet startup
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files/areas needing extra attention:
    • ops/devnet-morph/devnet/init.py — startup sequencing, timeouts, error handling.
    • ops/docker/layer1/scripts/generate-genesis.sh — path handling, timestamping, external generator invocation.
    • ops/docker/layer1/scripts/clean.sh — destructive removal globs and preserved items.
    • ops/docker/docker-compose-4nodes.yml & ops/docker/layer1/docker-compose.yml — service ports, volumes, env/JWT wiring, restart/health semantics.
    • Bulk keystore/secret additions — ensure no sensitive production secrets were committed and file permissions are appropriate.

Suggested reviewers

  • Web3Jumb0
  • Kukoomomo
  • twcctop

Poem

🐇 I hopped through genesis, keys in a row,
I tucked a jwt and scripts where devnets grow,
Three nodes now hum — EL, CL, VC too,
I nudged the first block, then winked: "Ready — woo!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ops: refactor layer1 for devnet tools' accurately reflects the primary change—a comprehensive refactoring of the Layer1 infrastructure for devnet tooling, including new Makefile targets, Docker Compose configurations, genesis generation, and supporting scripts.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor_layer1

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 996d233 and 4762e7c.

📒 Files selected for processing (1)
  • tx-submitter/go.mod (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
tx-submitter/go.mod (1)

8-9: Verify gnark-crypto version pairing with go-eth-kzg v1.4.0 and consider upgrading to v0.19.0.

Dependencies are legitimately used in tx-submitter/types/blob_test.go (BLS12-381 imports). However, gnark-crypto v0.16.0 was released Jan 31, 2025, while latest v0.19.0 (Aug 22, 2025) includes performance improvements and bug fixes. Your code uses v0.16.0, which is 7 months old—released before go-eth-kzg v1.4.0 (Aug 30, 2025).

Verify: (1) go-eth-kzg v1.4.0 is compatible with gnark-crypto v0.16.0, and (2) whether upgrading to v0.19.0 is feasible. v0.16.0 → v0.19.0 shows no explicit API breaking changes but includes assembly/codegen refactors. Since your code imports BLS12-381 (not stark-curve), you're unaffected by the reported DoS issue in stark-curve/fr.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 48

♻️ Duplicate comments (7)
ops/docker/layer1/keystores/layer1/keys/0x8aa5bbee21e98c7b9e7a4c8ea45aa99f89e22992fa4fc2d73869d77da4cc8a05b25b61931ff521986677dd7f7159e8e6/voting-keystore.json (1)

1-1: Same security issue as other keystore files.

This keystore file has the same critical security issue noted in the review of the first voting-keystore.json file—cryptographic key material should not be committed to version control, even for devnet purposes.

ops/docker/layer1/keystores/layer1/keys/0x996323af7e545fb6363ace53f1538c7ddc3eb0d985b2479da3ee4ace10cbc393b518bf02d1a2ddb2f5bdf09b473933ea/voting-keystore.json (1)

1-1: Same security issue as other keystore files.

This keystore file has the same critical security issue noted in the review of the first voting-keystore.json file—cryptographic key material should not be committed to version control, even for devnet purposes.

ops/docker/layer1/keystores/layer1/keys/0xaaf6c1251e73fb600624937760fef218aace5b253bf068ed45398aeb29d821e4d2899343ddcbbe37cb3f6cf500dff26c/voting-keystore.json (1)

1-1: Same security issue as other keystore files.

This keystore file has the same critical security issue noted in the review of the first voting-keystore.json file—cryptographic key material should not be committed to version control, even for devnet purposes.

ops/docker/layer1/keystores/layer1/keys/0xab72cbc6575c3179680a58c0ecd5de46d2678ccbafc016746348ee5688edcb21b4e15bd37c70c508e3ea73103c2d566b/voting-keystore.json (1)

1-1: Same security issue as other keystore files.

This keystore file has the same critical security issue noted in the review of the first voting-keystore.json file—cryptographic key material should not be committed to version control, even for devnet purposes.

ops/docker/layer1/keystores/layer1/keys/0x8de5a6200cebb09b2198e69fed84bcd512ec5cf317c5f1ee99aad03d2a9a8564bf3807c08da2664222268d59c34a06e4/voting-keystore.json (1)

1-1: Same weak KDF parameters as other keystores.

This keystore has the same c=2 PBKDF2 iteration count. See earlier comments about devnet-only usage.

ops/docker/layer1/keystores/layer1/keys/0x81ea9f74ef7d935b807474e38954ae3934856219a23e074954b2e860c5a3c400f9aedb42cd27cb4ceb697ca36d1e58cb/voting-keystore.json (1)

1-1: Keystore follows same pattern.

Same considerations as other keystore files in this PR.

ops/docker/layer1/keystores/layer1/keys/0x87231421a08ed28e7d357e2b37a26a458155c8d822d829344bd1029e5d175b5edfaa78f16f784f724a2caef124944c4f/voting-keystore.json (1)

1-1: Keystore configuration consistent with others.

Same structure and security considerations as other keystores in this PR.

🧹 Nitpick comments (12)
ops/docker/layer1/keystores/layer1/keys/0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e/voting-keystore.json (1)

1-1: Add trailing newline to the file.

JSON files should end with a newline character for consistency with POSIX standards and text editor conventions.

-{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"8e4d0293c4cb76bf079c26ffcbec614fdeef98a8dccac7f3625f755677f2561c"},"message":""},"checksum":{"function":"sha256","params":{},"message":"f0775f94d9f44868e103f6f72b147d8f11426120ec951e608e00f518904f67c6"},"cipher":{"function":"aes-128-ctr","params":{"iv":"aa16d15abd4560bce198fe1394741fe9"},"message":"75e717379b4f8c5b1ce255bd5671067687948f3c26392cdf4857c977bb33295c"}},"description":"0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e","pubkey":"abd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e","path":"","uuid":"ca0eeeb8-8a3b-4157-9b4c-43a6e5607269","version":4}
\ No newline at end of file
+{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"8e4d0293c4cb76bf079c26ffcbec614fdeef98a8dccac7f3625f755677f2561c"},"message":""},"checksum":{"function":"sha256","params":{},"message":"f0775f94d9f44868e103f6f72b147d8f11426120ec951e608e00f518904f67c6"},"cipher":{"function":"aes-128-ctr","params":{"iv":"aa16d15abd4560bce198fe1394741fe9"},"message":"75e717379b4f8c5b1ce255bd5671067687948f3c26392cdf4857c977bb33295c"}},"description":"0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e","pubkey":"abd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e","path":"","uuid":"ca0eeeb8-8a3b-4157-9b4c-43a6e5607269","version":4}
ops/docker/layer1/keystores/layer1/keys/0xae940a07850cf904b44f31cbf0e44824bae5ec36dcfdb7fad858f2a39dba38de82ca12b0ae939a34fce7a02e4b9789f8/voting-keystore.json (1)

1-1: Weak PBKDF2 key derivation parameters may slow test initialization (optional observation).

The KDF uses "c": 2 iterations, which is unusually low compared to standard recommendations (typically 2^18 or higher). For devnet/testing purposes this likely intentionally optimizes for fast key derivation during validator startup, but confirm this aligns with project devnet testing practices.

ops/docker/layer1/keystores/layer1/keys/0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64/voting-keystore.json (1)

1-1: Minor: Add trailing newline to JSON file.

The file ends without a trailing newline. While functionally correct, JSON files in repositories should follow the convention of ending with a newline character for consistency and better git diff behavior.

Apply this diff:

-{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"63e05898e457ab85e2dee0c8da10fdda630558a0a3900ea09da484be8dbd9967"},"message":""},"checksum":{"function":"sha256","params":{},"message":"936dfff7aaed5a48bbc15e308f4079ea18127803be71a974a2acc60db980bef1"},"cipher":{"function":"aes-128-ctr","params":{"iv":"59357db2000d776034a0c5e89d36cd22"},"message":"75ab389e3d57401013883972c5f23602fc2909cc1c7a2ad8c22bd26f59f56aa1"}},"description":"0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64","pubkey":"96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64","path":"","uuid":"00c54fed-7953-439f-ac28-4d4bad382a41","version":4}
+{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"63e05898e457ab85e2dee0c8da10fdda630558a0a3900ea09da484be8dbd9967"},"message":""},"checksum":{"function":"sha256","params":{},"message":"936dfff7aaed5a48bbc15e308f4079ea18127803be71a974a2acc60db980bef1"},"cipher":{"function":"aes-128-ctr","params":{"iv":"59357db2000d776034a0c5e89d36cd22"},"message":"75ab389e3d57401013883972c5f23602fc2909cc1c7a2ad8c22bd26f59f56aa1"}},"description":"0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64","pubkey":"96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64","path":"","uuid":"00c54fed-7953-439f-ac28-4d4bad382a41","version":4}
ops/docker/layer1/keystores/layer1/keys/0xa03c2a82374e04b2e0594c4ce14fb3f225b46f13188f0d8002a523c7dcfb939ae4856053c2c9c695374d7c3685df1ca5/voting-keystore.json (1)

1-1: Missing trailing newline.

The file should end with a newline character for consistency with POSIX text file conventions and to avoid Git warnings.

ops/docker/layer1/keystores/layer1/keys/validator_definitions.yml (1)

1-192: Verify validator configuration for devnet requirements.

All 32 validators are configured as enabled. For a devnet environment, confirm this is the intended configuration. Running all validators on a single node may have resource implications.

Additionally, the description field duplicates the voting_public_key value for all entries. Consider whether more descriptive names (e.g., "validator-01", "validator-02") would improve maintainability.

ops/docker/layer1/scripts/generate-genesis.sh (1)

97-97: Consider safer pattern for listing files.

The current pattern ls -1 ... | xargs can fail with filenames containing spaces or special characters, though this is unlikely with the specific file patterns used (*.yaml, *.ssz, *.txt).

Apply this diff for a more robust approach:

-ls -1 "$GENESIS_DIR"/*.{yaml,ssz,txt} 2>/dev/null | xargs -n 1 basename | head -10
+find "$GENESIS_DIR" -maxdepth 1 \( -name "*.yaml" -o -name "*.ssz" -o -name "*.txt" \) -exec basename {} \; 2>/dev/null | head -10
ops/devnet-morph/devnet/__init__.py (1)

380-401: Consider minor refinement to exception handling.

The function is well-implemented. The return None on line 398 could be moved to an else block for slightly clearer flow, though the current implementation is functional.

Apply this diff if you prefer more explicit flow:

         result = json.loads(data)
         if 'result' in result:
             # Convert hex string (e.g., "0x1") to integer
             block_number_hex = result['result']
             return int(block_number_hex, 16)
-        return None
+        else:
+            return None
     except Exception as e:
         log.debug(f'Error calling eth_blockNumber: {e}')
         return None
ops/docker/layer1/scripts/clean.sh (2)

56-56: Consider safer pattern for find with xargs.

The current find | xargs pattern without -print0 can fail with filenames containing spaces or newlines, though this is unlikely in practice for database files.

Apply this diff for better safety:

-    find layer1/keystores/layer1 -name "*.db" -o -name "*.sqlite" -o -name "*.sqlite3" | xargs rm -f 2>/dev/null || true
+    find layer1/keystores/layer1 \( -name "*.db" -o -name "*.sqlite" -o -name "*.sqlite3" \) -exec rm -f {} + 2>/dev/null || true

67-69: Consider simplifying volume cleanup pipeline.

The current grep | awk pipeline works but could be simplified using grep -o or awk alone.

Apply this diff for a cleaner approach:

-docker volume ls | grep -E "simple-ethereum-network|layer1-" | awk '{print $2}' | while read volume; do
+docker volume ls --format '{{.Name}}' | grep -E "simple-ethereum-network|layer1-" | while read volume; do
     docker volume rm "$volume" 2>/dev/null || true
 done
ops/docker/docker-compose-4nodes.yml (1)

38-46: Improve readability of the geth initialization and startup command.

The geth command at line 46 is a single long line with ~23 flags, making it difficult to read, maintain, and debug. Consider refactoring into a clearer format.

Break the command into a more readable multi-line format:

     command:
       - |
         mkdir -p /data/geth/execution-data
         if [ ! -f /data/geth/execution-data/geth/chaindata/CURRENT ]; then
           echo "Initializing genesis..."
           geth init --datadir=/data/geth/execution-data /network-configs/genesis.json || exit 1
         else
           echo "Genesis already initialized, skipping init"
         fi
-        exec geth --networkid=900 --datadir=/data/geth/execution-data --http --http.addr=0.0.0.0 --http.port=8545 --http.api=admin,engine,net,eth,web3,debug,txpool --http.vhosts=* --http.corsdomain=* --ws --ws.addr=0.0.0.0 --ws.port=8546 --ws.api=admin,engine,net,eth,web3,debug,txpool --ws.origins=* --allow-insecure-unlock --authrpc.port=8551 --authrpc.addr=0.0.0.0 --authrpc.vhosts=* --authrpc.jwtsecret=/jwt/jwtsecret --syncmode=full --miner.gasprice=1 --rpc.allow-unprotected-txs --metrics --metrics.addr=0.0.0.0 --metrics.port=9001 --discovery.port=30303 --port=30303
+        exec geth \
+          --networkid=900 \
+          --datadir=/data/geth/execution-data \
+          --http \
+          --http.addr=0.0.0.0 \
+          --http.port=8545 \
+          --http.api=admin,engine,net,eth,web3,debug,txpool \
+          --http.vhosts=* \
+          --http.corsdomain=* \
+          --ws \
+          --ws.addr=0.0.0.0 \
+          --ws.port=8546 \
+          --ws.api=admin,engine,net,eth,web3,debug,txpool \
+          --ws.origins=* \
+          --allow-insecure-unlock \
+          --authrpc.port=8551 \
+          --authrpc.addr=0.0.0.0 \
+          --authrpc.vhosts=* \
+          --authrpc.jwtsecret=/jwt/jwtsecret \
+          --syncmode=full \
+          --miner.gasprice=1 \
+          --rpc.allow-unprotected-txs \
+          --metrics \
+          --metrics.addr=0.0.0.0 \
+          --metrics.port=9001 \
+          --discovery.port=30303 \
+          --port=30303
ops/docker/layer1/docker-compose.yml (1)

24-32: Improve readability of the geth initialization and startup command.

The geth command on line 32 is a single long line with multiple flags, making it difficult to read and maintain (same issue as in docker-compose-4nodes.yml).

Break the command into a multi-line format for clarity (see the refactored example in the comment for docker-compose-4nodes.yml lines 38-46).

ops/docker/layer1/keystores/layer1/pubkeys.json (1)

1-1: Add a README or schema documentation.

Consider adding a README.md or JSON schema file in the ops/docker/layer1/keystores/ directory explaining:

  • The purpose of pubkeys.json (validator public key registry for Lighthouse)
  • How this file is generated and maintained
  • How downstream systems (genesis generation, validator startup) consume this file
  • Expected format and key material lifecycle

This improves maintainability and prevents operational confusion, especially for new contributors working on devnet infrastructure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e02ce8 and d631249.

📒 Files selected for processing (81)
  • Makefile (2 hunks)
  • ops/devnet-morph/devnet/__init__.py (4 hunks)
  • ops/docker/.env (1 hunks)
  • ops/docker/Makefile.layer1 (1 hunks)
  • ops/docker/docker-compose-4nodes.yml (2 hunks)
  • ops/docker/layer1/.gitignore (1 hunks)
  • ops/docker/layer1/configs/values.env.template (1 hunks)
  • ops/docker/layer1/docker-compose.yml (1 hunks)
  • ops/docker/layer1/jwt/jwtsecret (1 hunks)
  • ops/docker/layer1/keystores/layer1-password.txt (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x81ea9f74ef7d935b807474e38954ae3934856219a23e074954b2e860c5a3c400f9aedb42cd27cb4ceb697ca36d1e58cb/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x8419cf00f2783c430dc861a710984d0429d3b3a7f6db849b4f5c05e0d87339704c5c7f5eede6adfc8776d666587b5932/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x84a687ffdf21a0ad754d0164d1e2c03035613ab76359e7f5cf51ea4a425a6ee026725ec0a0dbd336f7dab759596f0bf8/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x87231421a08ed28e7d357e2b37a26a458155c8d822d829344bd1029e5d175b5edfaa78f16f784f724a2caef124944c4f/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x8aa5bbee21e98c7b9e7a4c8ea45aa99f89e22992fa4fc2d73869d77da4cc8a05b25b61931ff521986677dd7f7159e8e6/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x8d46e9aa0c1986056e407efc7013b7f271027d3c98ce96667faa98074ab0588a61681faf78644c11819a459a95689dab/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x8de5a6200cebb09b2198e69fed84bcd512ec5cf317c5f1ee99aad03d2a9a8564bf3807c08da2664222268d59c34a06e4/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x91709ee06497b9ac049325853d64947290189a8c2322e3a500d91e23ea02dc158b6db63ae558b3b7670357a151cd6071/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x996323af7e545fb6363ace53f1538c7ddc3eb0d985b2479da3ee4ace10cbc393b518bf02d1a2ddb2f5bdf09b473933ea/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0x996d10c3026b9344532b06c70a596f972a1e779a1f6106d3da9f6ba376bbf7ec82d2f52629e5dbf3f7d03b00f6b862af/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa03c2a82374e04b2e0594c4ce14fb3f225b46f13188f0d8002a523c7dcfb939ae4856053c2c9c695374d7c3685df1ca5/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa1584dfe1573df8ec88c7b74d76726b4821bfe84bf886dd3c0e3f74c2ea18aa62ca44c871fb1c63971fccf6937e6501f/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa1d9840eda3036fbf63eeea40146e4548553e6e1b2a653ab349b376f31b367c40d71fb59ff8e94b91daa99c262ec8b52/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa35c6004f387430c3797ab0157af7b824c8fe106241c7cdeb897d900c0f9e4bb945ff2a6b88cbd10e35ec48aaa554ecb/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa54fe5c26059ed60b4f0b66ef7b0bf167580504525f83c169507dc812816df41b1da6128341c23977300dffd32a32f41/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa75ca9447dca3a3745ada36731187ddd1f6a152cf15d7446b785eab381e5c8562c1202a6e7a24080bc6b619a161113db/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xa804e4fa8d1391a9d078aa93985a12503b84ce4f6f1f9e70ab7fca421e1cf972538666299d4c1bfc39327b469b2db7a8/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xaaddb0cb69ca18f14aed7054e98a24df0ff606aeff919d489f7884fd1bd183bcb46ea54bc363146e1a88db36dc20a7a4/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xaaf6c1251e73fb600624937760fef218aace5b253bf068ed45398aeb29d821e4d2899343ddcbbe37cb3f6cf500dff26c/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xab72cbc6575c3179680a58c0ecd5de46d2678ccbafc016746348ee5688edcb21b4e15bd37c70c508e3ea73103c2d566b/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xac69ae9e6c385a368df71d11ac68f45f05e005306df3c2bf98ed3577708256bd97f8c09d3f72115444077a9bb711d8d1/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xad9222dec71ff8ee6bc0426ffe7b5e66f96738225db281dd20027a1556d089fdebd040abfbc2041d6c1a0d8fdcfce183/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xae5302796cfeca685eaf37ffd5baeb32121f2f07415bee26cc0051ee513ff3932d2c365e3d9f87b0949a5980445cb64c/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xae940a07850cf904b44f31cbf0e44824bae5ec36dcfdb7fad858f2a39dba38de82ca12b0ae939a34fce7a02e4b9789f8/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xafa10af166a0dbf3a25ff86cd6f8e44cccc818c5e70cd70e4e98e226b158f3563450b3fb184d2649adbb11e53080d1ca/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xb27ad13afc8ff30e087797b344c8382bb0a84447549f1b0274059ddd652276e7b148ba8808a10cc45746762957d4efbe/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xb570dde8ee80512e3d031caf22e775c60f7f5a6cbdeb3e52e24cf8c867d38569a53dd19cdc36a03a1bbb3a8d94b03670/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/0xb72cb106b7bc1ecae219e0ae1830a509ed18a042b56a2779f4033419de69ba8ae8017090caed1f5377bfa68506157360/voting-keystore.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/api-token.txt (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/validator_definitions.yml (1 hunks)
  • ops/docker/layer1/keystores/layer1/keys/validator_key_cache.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/pubkeys.json (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x81ea9f74ef7d935b807474e38954ae3934856219a23e074954b2e860c5a3c400f9aedb42cd27cb4ceb697ca36d1e58cb (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x8419cf00f2783c430dc861a710984d0429d3b3a7f6db849b4f5c05e0d87339704c5c7f5eede6adfc8776d666587b5932 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x84a687ffdf21a0ad754d0164d1e2c03035613ab76359e7f5cf51ea4a425a6ee026725ec0a0dbd336f7dab759596f0bf8 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x87231421a08ed28e7d357e2b37a26a458155c8d822d829344bd1029e5d175b5edfaa78f16f784f724a2caef124944c4f (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x8aa5bbee21e98c7b9e7a4c8ea45aa99f89e22992fa4fc2d73869d77da4cc8a05b25b61931ff521986677dd7f7159e8e6 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x8d46e9aa0c1986056e407efc7013b7f271027d3c98ce96667faa98074ab0588a61681faf78644c11819a459a95689dab (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x8de5a6200cebb09b2198e69fed84bcd512ec5cf317c5f1ee99aad03d2a9a8564bf3807c08da2664222268d59c34a06e4 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x91709ee06497b9ac049325853d64947290189a8c2322e3a500d91e23ea02dc158b6db63ae558b3b7670357a151cd6071 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x996323af7e545fb6363ace53f1538c7ddc3eb0d985b2479da3ee4ace10cbc393b518bf02d1a2ddb2f5bdf09b473933ea (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0x996d10c3026b9344532b06c70a596f972a1e779a1f6106d3da9f6ba376bbf7ec82d2f52629e5dbf3f7d03b00f6b862af (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa03c2a82374e04b2e0594c4ce14fb3f225b46f13188f0d8002a523c7dcfb939ae4856053c2c9c695374d7c3685df1ca5 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa1584dfe1573df8ec88c7b74d76726b4821bfe84bf886dd3c0e3f74c2ea18aa62ca44c871fb1c63971fccf6937e6501f (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa1d9840eda3036fbf63eeea40146e4548553e6e1b2a653ab349b376f31b367c40d71fb59ff8e94b91daa99c262ec8b52 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa35c6004f387430c3797ab0157af7b824c8fe106241c7cdeb897d900c0f9e4bb945ff2a6b88cbd10e35ec48aaa554ecb (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa54fe5c26059ed60b4f0b66ef7b0bf167580504525f83c169507dc812816df41b1da6128341c23977300dffd32a32f41 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa75ca9447dca3a3745ada36731187ddd1f6a152cf15d7446b785eab381e5c8562c1202a6e7a24080bc6b619a161113db (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xa804e4fa8d1391a9d078aa93985a12503b84ce4f6f1f9e70ab7fca421e1cf972538666299d4c1bfc39327b469b2db7a8 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xaaddb0cb69ca18f14aed7054e98a24df0ff606aeff919d489f7884fd1bd183bcb46ea54bc363146e1a88db36dc20a7a4 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xaaf6c1251e73fb600624937760fef218aace5b253bf068ed45398aeb29d821e4d2899343ddcbbe37cb3f6cf500dff26c (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xab72cbc6575c3179680a58c0ecd5de46d2678ccbafc016746348ee5688edcb21b4e15bd37c70c508e3ea73103c2d566b (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xac69ae9e6c385a368df71d11ac68f45f05e005306df3c2bf98ed3577708256bd97f8c09d3f72115444077a9bb711d8d1 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xad9222dec71ff8ee6bc0426ffe7b5e66f96738225db281dd20027a1556d089fdebd040abfbc2041d6c1a0d8fdcfce183 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xae5302796cfeca685eaf37ffd5baeb32121f2f07415bee26cc0051ee513ff3932d2c365e3d9f87b0949a5980445cb64c (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xae940a07850cf904b44f31cbf0e44824bae5ec36dcfdb7fad858f2a39dba38de82ca12b0ae939a34fce7a02e4b9789f8 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xafa10af166a0dbf3a25ff86cd6f8e44cccc818c5e70cd70e4e98e226b158f3563450b3fb184d2649adbb11e53080d1ca (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xb27ad13afc8ff30e087797b344c8382bb0a84447549f1b0274059ddd652276e7b148ba8808a10cc45746762957d4efbe (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xb570dde8ee80512e3d031caf22e775c60f7f5a6cbdeb3e52e24cf8c867d38569a53dd19cdc36a03a1bbb3a8d94b03670 (1 hunks)
  • ops/docker/layer1/keystores/layer1/secrets/0xb72cb106b7bc1ecae219e0ae1830a509ed18a042b56a2779f4033419de69ba8ae8017090caed1f5377bfa68506157360 (1 hunks)
  • ops/docker/layer1/scripts/clean.sh (1 hunks)
  • ops/docker/layer1/scripts/generate-genesis.sh (1 hunks)
  • ops/docker/layer1/scripts/start.sh (1 hunks)
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
ops/docker/.env

[warning] 5-5: [UnorderedKey] The L1_ETH_RPC key should go before the NODE_DATA_DIR key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The L1_BEACON_CHAIN_RPC key should go before the L1_ETH_RPC key

(UnorderedKey)

🪛 Ruff (0.14.3)
ops/devnet-morph/devnet/__init__.py

93-93: Avoid specifying long messages outside the exception class

(TRY003)


93-93: f-string without any placeholders

Remove extraneous f prefix

(F541)


398-398: Consider moving this statement to an else block

(TRY300)


399-399: Do not catch blind exception: Exception

(BLE001)

🪛 Shellcheck (0.11.0)
ops/docker/layer1/scripts/generate-genesis.sh

[warning] 97-97: Use 'find .. -print0 | xargs -0 ..' or 'find .. -exec .. +' to allow non-alphanumeric filenames.

(SC2011)

ops/docker/layer1/scripts/start.sh

[warning] 37-37: i appears unused. Verify use (or export if used externally).

(SC2034)

ops/docker/layer1/scripts/clean.sh

[warning] 56-56: Use 'find .. -print0 | xargs -0 ..' or 'find .. -exec .. +' to allow non-alphanumeric filenames.

(SC2038)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (39)
ops/docker/layer1/keystores/layer1/keys/0xa1584dfe1573df8ec88c7b74d76726b4821bfe84bf886dd3c0e3f74c2ea18aa62ca44c871fb1c63971fccf6937e6501f/voting-keystore.json (1)

1-1: Verify keystore storage model and security assumptions.

Confirm:

  1. These keystores are test-only and never to be used with mainnet validators
  2. The passphrase/password management strategy for these keystores
  3. Whether alternative approaches (dynamic generation, secrets manager, environment variables) were considered
ops/docker/layer1/keystores/layer1/secrets/0xb72cb106b7bc1ecae219e0ae1830a509ed18a042b56a2779f4033419de69ba8ae8017090caed1f5377bfa68506157360 (1)

1-1: The review comment is based on a factually incorrect premise.

The secret files in ops/docker/layer1/keystores/layer1/secrets/ were committed in a previous commit (d110a38: add private layer1 building), not in this PR. Git verification shows:

  • All 32 secret files are already tracked in the repository
  • There are no staged or unstaged changes to the keystores directory in this PR
  • git diff HEAD shows no modifications to these files
  • The current branch contains these pre-existing files unmodified

The review comment incorrectly frames this as a new security vulnerability being introduced by this PR and instructs removal/history cleaning of files that this PR is not adding. While the underlying concern about secrets in version control is a legitimate security issue, it is a pre-existing repository condition, not a PR-specific problem. The remediation steps recommended (git rm, BFG, force-push) are misdirected at this PR when the issue originates from a prior commit.

Likely an incorrect or invalid review comment.

ops/docker/layer1/keystores/layer1/secrets/0xaaf6c1251e73fb600624937760fef218aace5b253bf068ed45398aeb29d821e4d2899343ddcbbe37cb3f6cf500dff26c (1)

1-1: ****

The file path referenced in the review does not exist. The files are located at ops/docker/layer1/keystores/layer1/keys/ (not secrets/). While keystores under the keys/ directory are indeed being tracked by Git and exposed, this review comment references a non-existent secrets/ directory path. Verify whether the concern applies to the actual keys/ directory files or whether you intended to reference a different set of files.

Likely an incorrect or invalid review comment.

ops/docker/layer1/keystores/layer1/keys/0xad9222dec71ff8ee6bc0426ffe7b5e66f96738225db281dd20027a1556d089fdebd040abfbc2041d6c1a0d8fdcfce183/voting-keystore.json (1)

1-1: Add .gitignore and documentation to prevent accidental production keystore commits.

While this appears to be a devnet environment (chain ID 900, hardcoded validator addresses), storing keystores directly in version control without .gitignore protection is risky and undocumented:

  1. Missing .gitignore: Keystores are committed without safeguards, creating a foothold for accidentally committing production keystores using the same pattern.
  2. Undocumented strategy: No README or comments in ops/docker/layer1/ explain that keystores are intentionally sourced from the repo for devnet reproducibility. This increases the likelihood of misuse.
  3. Minor: File missing trailing newline (line 1).

Recommended changes:

  • Add keystores/ to .gitignore at the root or ops/docker/ level, with an exception for ops/docker/layer1/keystores/ (if repo keystores are intentional) or a .gitignore-template for reference.
  • Add a README.md or section in ops/docker/layer1/ documenting: "Keystores are included in this devnet environment for reproducibility and must never be used with real validators or production networks."
  • Add a pre-commit hook or CI check to prevent non-layer1/ keystores from being committed.
  • Fix trailing newline on line 1.
ops/docker/layer1/keystores/layer1/keys/0xb27ad13afc8ff30e087797b344c8382bb0a84447549f1b0274059ddd652276e7b148ba8808a10cc45746762957d4efbe/voting-keystore.json (1)

1-1: Confirm whether storing encrypted keystores in git is intentional for this repository.

Verification shows that 69 keystore files are committed to the repository and deliberately not excluded by .gitignore. This is pre-existing architecture (keystores were added in a prior commit, not new in this PR), but no documentation explains the security rationale or acceptable risk level.

Before proceeding, confirm:

  • Is this intentional for devnet development?
  • Should this state be documented (e.g., in a SECURITY policy) to set expectations?
  • Is there a separate effort to move keystores out of version control?
ops/docker/layer1/keystores/layer1/keys/0x930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8/voting-keystore.json (1)

1-1: Document that pbkdf2 parameters (c=2) are devnet-only to prevent production replication.

All voting keystores in ops/docker/layer1/keystores/ use "c": 2 for pbkdf2 iterations—this is dangerously weak for production but acceptable for devnet. Since ops/docker is marked in .gitignore as devnet configuration and no production keystores exist elsewhere in the repository, this is not an immediate production risk.

However, add documentation (e.g., comment in the keystore directory or a README) explicitly stating these are devnet keystores with intentionally weak parameters, to prevent accidental replication of this pattern in production key generation. Consider regenerating with secure defaults (NIST minimum 10,000, industry standard 262,144+) if these keystores are used in CI/CD or shared environments.

ops/docker/layer1/keystores/layer1/secrets/0xb27ad13afc8ff30e087797b344c8382bb0a84447549f1b0274059ddd652276e7b148ba8808a10cc45746762957d4efbe (1)

1-1: Document these validator secrets as development-only and add safeguards to prevent production misuse.

The committed validator secrets are confirmed to be used in a local development network (network ID 900 in docker-compose.yml). However, no documentation, code comments, or environment safeguards currently prevent these secrets from being accidentally used with real validators on a public network.

Add:

  1. A comment in validator_definitions.yml and the secrets directory stating "For development/devnet only"
  2. A README in ops/docker/layer1/ explaining this is a local test setup
  3. Consider adding a network validation check in startup scripts to prevent cross-network misuse
ops/docker/layer1/keystores/layer1/keys/0x96947de9e6068c22a7716656a2755a9551b0b66c2d1a741bf84a088fe1e840e992dc39861bf8ba3e8d5b6d21e8f57e64/voting-keystore.json (1)

1-1: Weak pbkdf2 iteration count confirmed across all keystores—requires documentation or regeneration.

This concern is valid. All 34 keystores in ops/docker/layer1/keystores/ consistently use "c": 2 for pbkdf2 iterations, which provides virtually no brute-force protection on the encrypted key material. While these are clearly devnet/testnet fixtures (referenced in docker-compose.yml and generate-genesis.sh), the weakness combined with lack of security documentation is problematic.

Findings:

  • Systematic issue: All keystores use "c": 2 (not isolated to one file)
  • Static artifacts: Keystores are committed to the repository, not dynamically generated (generate-genesis.sh merely references them)
  • EIP-2335 context: The spec provides a test vector with c: 262144 but does not mandate a minimum; however, industry best practices strongly recommend much higher values than 2
  • Missing documentation: No README or security notes explain the devnet-only nature or intentional weakness

Required action:

Either add documentation to ops/docker/layer1/ clarifying these are test-only keystore fixtures with accepted security trade-offs for devnet, OR regenerate keystores with industry-standard iteration counts (e.g., c: 262144 or higher). Committing weak keystores without any security context risks misuse and confusion.

ops/docker/layer1/keystores/layer1/keys/0xb72cb106b7bc1ecae219e0ae1830a509ed18a042b56a2779f4033419de69ba8ae8017090caed1f5377bfa68506157360/voting-keystore.json (2)

1-1: Verify: Ensure keystores are appropriately labeled and isolated for development-only use.

Per the PR objectives mentioning "devnet tools," verify that clear documentation exists explaining:

  1. These keystores are for development/testing only
  2. They should never be used with real funds
  3. The weak PBKDF2 parameters are intentional for devnet convenience
  4. A separate, secure process exists for production validator key management

Search the repository for documentation (e.g., README.md, SECURITY.md, deployment guides) explaining the Layer 1 keystore strategy and devnet/production separation. If missing, consider adding guidance to prevent misuse.


1-1: Clarify keystore storage strategy and fix .gitignore — keystores are confirmed devnet fixtures but .gitignore rule is not properly applied.

These keystores are intentional test fixtures for a devnet setup (confirmed by: 32-validator configuration, password123 credential, docker-compose orchestration in ops/docker/layer1/, and only voting keystores without withdrawal variants). They are part of the devnet initialization strategy with genesis generation and container startup scripts in the same directory.

However, the global .gitignore entry keystores/ is not catching ops/docker/layer1/keystores/. Add an explicit entry to ops/docker/layer1/.gitignore to prevent accidental commits of keystores, or document why these are intentionally tracked. No production keystore management strategy or location was found in the repository—ensure this is addressed in a separate change.

ops/docker/layer1/keystores/layer1/keys/0xa54fe5c26059ed60b4f0b66ef7b0bf167580504525f83c169507dc812816df41b1da6128341c23977300dffd32a32f41/voting-keystore.json (1)

1-1: PBKDF2 iteration count is significantly weaker than standard, but appropriate for devnet—verify intent and document.

Verification found that all keystores consistently use c=2 iterations, which is 131,072x weaker than the Lighthouse standard of c=262,144. However, this is isolated to ops/docker/layer1/keystores/ (devnet-only infrastructure) with:

  • Hardcoded test password stored in plaintext
  • Ephemeral Docker volume mounts
  • No production deployment references

This weak iteration count is a technical deviation from best practices, but acceptable for local development. Confirm whether this is intentional and consider adding a comment in the keystore directory documenting that these are devnet-only test fixtures.

ops/docker/layer1/keystores/layer1/secrets/0xa75ca9447dca3a3745ada36731187ddd1f6a152cf15d7446b785eab381e5c8562c1202a6e7a24080bc6b619a161113db (1)

1-1: Validator secret for devnet - ensure test-only usage.

This validator keystore secret should only be used in devnet/testing environments. See security considerations in the first secret file review.

ops/docker/layer1/keystores/layer1/secrets/0xac69ae9e6c385a368df71d11ac68f45f05e005306df3c2bf98ed3577708256bd97f8c09d3f72115444077a9bb711d8d1 (1)

1-1: Validator secret for devnet.

Hardcoded validator keystore secret. Ensure this is documented as test-only for devnet environments. See detailed security considerations in the first secret file review.

ops/docker/layer1/keystores/layer1/secrets/0x996323af7e545fb6363ace53f1538c7ddc3eb0d985b2479da3ee4ace10cbc393b518bf02d1a2ddb2f5bdf09b473933ea (1)

1-1: Validator secret for devnet.

Hardcoded validator keystore secret. Ensure this is documented as test-only for devnet environments. See detailed security considerations in the first secret file review.

ops/docker/layer1/keystores/layer1/secrets/0xaaddb0cb69ca18f14aed7054e98a24df0ff606aeff919d489f7884fd1bd183bcb46ea54bc363146e1a88db36dc20a7a4 (1)

1-1: Validator secret for devnet.

Hardcoded validator keystore secret. Ensure this is documented as test-only for devnet environments. See detailed security considerations in the first secret file review.

ops/docker/layer1/keystores/layer1/secrets/0x996d10c3026b9344532b06c70a596f972a1e779a1f6106d3da9f6ba376bbf7ec82d2f52629e5dbf3f7d03b00f6b862af (1)

1-1: Validator secret for devnet.

Hardcoded validator keystore secret. Ensure this is documented as test-only for devnet environments. See detailed security considerations in the first secret file review.

ops/docker/.env (1)

5-6: Endpoint updates look correct.

The L1 RPC endpoints have been properly updated to reference the new layer1-el and layer1-cl services, which aligns with the Layer1 infrastructure refactor described in the PR objectives.

ops/docker/layer1/jwt/jwtsecret (1)

1-1: Add clear documentation indicating this JWT secret is for devnet/local development only.

The JWT secret is committed to version control as a shared devnet artifact. While this appears intentional (scripts assume pre-existence, clean.sh preserves it, multiple docker nodes share it), the lack of documentation creates security confusion. The .gitignore rule jwt/ in ops/docker/layer1/.gitignore exists but is ineffective.

Add to ops/docker/layer1/jwt/jwtsecret or nearby README:

  • Header comment: # TEST/DEVNET ONLY - DO NOT USE IN PRODUCTION
  • Documentation explaining this is a shared devnet credential
  • Either update .gitignore to properly exclude this, or document why it's intentionally committed
ops/docker/layer1/keystores/layer1/keys/0xa804e4fa8d1391a9d078aa93985a12503b84ce4f6f1f9e70ab7fca421e1cf972538666299d4c1bfc39327b469b2db7a8/voting-keystore.json (1)

1-1: Critically weak KDF iteration count for keystore encryption.

The keystore uses only c=2 PBKDF2 iterations, which provides minimal protection against brute-force attacks. Standard production keystores use tens of thousands to millions of iterations.

Since this appears to be for devnet/testing purposes based on the PR context, this is acceptable for local development where performance matters more than security. However, ensure these keystores are never used in production or any environment accessible from the internet.

Consider adding a comment or README in the keystores directory clearly indicating these are development-only credentials with weak encryption parameters.

ops/docker/layer1/keystores/layer1/secrets/0xb570dde8ee80512e3d031caf22e775c60f7f5a6cbdeb3e52e24cf8c867d38569a53dd19cdc36a03a1bbb3a8d94b03670 (1)

1-1: Keystore password committed to repository.

This file contains a keystore password committed directly to the repository. While acceptable for local devnet development, ensure:

  1. These credentials are never reused in any non-local environment
  2. The repository remains private or clearly marked as containing development-only credentials
  3. Consider documenting in a README that all keystores/secrets are for devnet only
ops/docker/layer1/configs/values.env.template (2)

62-62: Hardcoded premine addresses with substantial balances.

Two addresses are configured with 1,000,000,000 ETH each. While appropriate for devnet testing, verify:

  1. The private keys for these addresses are documented for developer access
  2. These addresses are not used in any non-devnet environment
  3. The mnemonic on line 4 likely controls these accounts

24-28: Future fork epochs set to maximum uint64.

Fork epochs for GLOAS, EIP7805, and EIP7441 are set to 18446744073709551615 (max uint64), effectively disabling them. Confirm this is intentional for the devnet configuration.

ops/docker/layer1/keystores/layer1/secrets/0x81ea9f74ef7d935b807474e38954ae3934856219a23e074954b2e860c5a3c400f9aedb42cd27cb4ceb697ca36d1e58cb (1)

1-1: Keystore password in repository.

Same security considerations as other secret files in this PR. Acceptable for devnet-only usage.

ops/docker/layer1/scripts/start.sh (1)

1-58: LGTM! Well-structured startup orchestration.

The script correctly validates prerequisites, starts the Layer1 services via docker-compose, and includes appropriate readiness checks with clear user feedback. The wait logic with retries is robust.

ops/docker/Makefile.layer1 (1)

1-27: LGTM! Clean Makefile structure.

The targets are well-organized, properly declared as PHONY, and include helpful documentation. The delegation to scripts and docker-compose commands is appropriate.

Makefile (2)

150-150: LGTM! Proper dependency chain integration.

Adding devnet-l1-clean to the devnet-clean-build dependencies ensures Layer1 cleanup is part of the build-down sequence.


166-168: LGTM! Clean integration of Layer1 cleanup.

The new devnet-l1-clean target properly delegates to the cleanup script and is correctly declared as PHONY.

ops/docker/layer1/keystores/layer1/keys/0xa1d9840eda3036fbf63eeea40146e4548553e6e1b2a653ab349b376f31b367c40d71fb59ff8e94b91daa99c262ec8b52/voting-keystore.json (1)

1-1: Weak KDF iteration count noted (acceptable for devnet).

The keystore uses PBKDF2 with only 2 iterations ("c":2), which provides minimal protection against brute-force attacks. While this is acceptable for devnet/testing environments where performance is prioritized, ensure this keystore is never used in production.

ops/docker/layer1/scripts/generate-genesis.sh (1)

1-86: LGTM! Comprehensive genesis generation workflow.

The script properly handles directory setup, template processing, genesis generation via Docker, and thorough verification with clear user feedback. The 20-second buffer for genesis timestamp is reasonable for startup coordination.

ops/devnet-morph/devnet/__init__.py (2)

96-120: LGTM! Well-structured Layer1 startup orchestration.

The changes properly integrate the new three-service Layer1 topology (layer1-el, layer1-cl, layer1-vc) with appropriate readiness checks. The block polling logic with 3-minute timeout is reasonable for devnet startup.


271-272: LGTM! Endpoint updates align with new Layer1 topology.

The RPC endpoints correctly reference the new service names layer1-el and layer1-cl instead of the previous l1 and beacon-chain.

ops/docker/layer1/keystores/layer1/keys/0x8419cf00f2783c430dc861a710984d0429d3b3a7f6db849b4f5c05e0d87339704c5c7f5eede6adfc8776d666587b5932/voting-keystore.json (1)

1-1: Weak KDF iteration count noted (acceptable for devnet).

The keystore uses PBKDF2 with only 2 iterations ("c":2), which provides minimal protection against brute-force attacks. While this is acceptable for devnet/testing environments where performance is prioritized, ensure this keystore is never used in production.

ops/docker/layer1/scripts/clean.sh (1)

1-87: LGTM! Comprehensive cleanup with good error handling.

The script properly resets the Layer1 environment by removing containers, volumes, generated files, and databases while preserving templates and keystores. The permissive error handling (using || true and stderr redirection) is appropriate for a cleanup script. The summary output is helpful for users.

ops/docker/docker-compose-4nodes.yml (2)

19-21: Add morph-network to layer1 services for consistency.

The layer1 services defined in this file are missing explicit network membership declarations, while ops/docker/layer1/docker-compose.yml explicitly places them on morph-network. For consistency and to ensure all services communicate on the same network, add network declarations to these services. If they rely on the default network, this should be documented.

Are all services (layer1-el, layer1-cl, layer1-vc, and L2 services) intended to share a single network, or should they be on separate networks? This needs clarification to ensure proper inter-service communication.


121-121: Approve the dependency update for layer1 integration.

The morph-geth-0 now correctly depends on layer1-el for proper startup orchestration. This change ensures the execution layer is ready before L2 nodes attempt to connect.

ops/docker/layer1/docker-compose.yml (2)

107-110: Approve network isolation strategy.

The explicit morph-network bridge network declaration ensures proper isolation and allows services to communicate via container names. This is a good practice for Docker Compose deployments.


89-89: Validator keystore directory structure verified.

The layer1-vc service mounts ./keystores/layer1 with the correct directory structure in place:

  • ./keystores/layer1/keys/ — contains validator key directories (confirmed)
  • ./keystores/layer1/secrets/ — contains corresponding secret files (confirmed)
ops/docker/layer1/keystores/layer1/keys/0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75/voting-keystore.json (1)

1-1: SECURITY: Weak KDF parameters in devnet keystores require documentation.

All keystores in ops/docker/layer1/keystores/ consistently use PBKDF2 with c=2 iterations, which is cryptographically insufficient. While EIP-2335 does not mandate specific iteration counts, standard Ethereum keystore generators default to c=262144 for production use. This configuration adds negligible entropy against brute-force attacks.

Given the /docker/layer1/ path structure indicates a devnet setup: confirm whether c=2 was intentional for fast key generation during development. If so, add a comment in the keystore generation documentation stating that these are devnet-only keys and must never be used with mainnet funds or real private keys.

ops/docker/layer1/keystores/layer1/pubkeys.json (1)

1-1: No action needed. The review comment's concern about key length variation is incorrect.

Verification confirms all 32 public keys in the file are valid, consistently formatted, and have identical length (98 characters: 0x + 96 hex characters), which is the expected format for BLS public keys in Lighthouse validators. The file requires no corrections.

Likely an incorrect or invalid review comment.

Comment on lines +1 to +5
genesis/
keystores/
configs/
jwt/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Contradiction: .gitignore excludes files being committed in this PR.

The .gitignore correctly excludes keystores/ (line 2), yet this PR commits numerous keystore and secret files under ops/docker/layer1/keystores/. This indicates that:

  1. The keystore files should be generated locally by users running the devnet setup
  2. Only the generation scripts and documentation should be committed, not the artifacts themselves

Remove all files under the directories listed in this .gitignore from the commit, and ensure the devnet setup documentation explains how to generate them.

Comment on lines +1 to +2
password123

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Remove hardcoded secrets from version control.

This file contains a plaintext password that will be permanently stored in repository history. This is a critical security vulnerability that compromises all validator keystores it protects.

Immediate actions required:

  1. Remove from history: Execute a force-push to purge this from git history (or request repository owner to do so):

    git filter-branch --tree-filter 'rm -f ops/docker/layer1/keystores/layer1-password.txt' -- --all
    git push -f
  2. Regenerate keystores: Since the password is now public, regenerate all affected validator keystores and secrets.

  3. Prevent recurrence: Add to .gitignore:

    ops/docker/layer1/keystores/layer1-password.txt
    
  4. Use proper secrets management: Inject the password at runtime via:

    • Environment variables passed to Docker Compose
    • Kubernetes secrets (if running on K8s)
    • External secrets management system (HashiCorp Vault, AWS Secrets Manager, etc.)
    • Docker secrets (for swarm mode)

Do not commit credentials to version control under any circumstances. This file should be git-ignored and provided through secure infrastructure-as-code secrets management.

🤖 Prompt for AI Agents
In ops/docker/layer1/keystores/layer1-password.txt around lines 1 to 2, there is
a hardcoded plaintext password committed to the repository; remove the file from
the working tree and purge it from git history, regenerate any affected
keystores/secrets, add the file path to .gitignore, and switch to injecting the
password at runtime via a secrets manager or environment/secret mechanisms
(e.g., Docker/Kubernetes/HashiCorp Vault) so no credentials are stored in
version control.

@@ -0,0 +1 @@
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"5ccefb67de4aa473b14a6e8924ebe75b7cdf0aa4b5f4eba45b3050638ebdc540"},"message":""},"checksum":{"function":"sha256","params":{},"message":"32f2505d9f610f27419c3e24e379a874f2f24dfa37505933da6faf45e0e2563c"},"cipher":{"function":"aes-128-ctr","params":{"iv":"7d130535cda9c5560abeeeaa28fa91ee"},"message":"db6d4a726e2c501cf130ed3e676881c7c92022f4e7eeb1e15b83bb6751e0fb56"}},"description":"0x8d46e9aa0c1986056e407efc7013b7f271027d3c98ce96667faa98074ab0588a61681faf78644c11819a459a95689dab","pubkey":"8d46e9aa0c1986056e407efc7013b7f271027d3c98ce96667faa98074ab0588a61681faf78644c11819a459a95689dab","path":"","uuid":"5cca9e94-3149-498d-a1f8-3cb50f5fc37d","version":4} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Increase PBKDF2 iterations to a safe value.

Line [1]: The PBKDF2 KDF is configured with c: 2, which is effectively no hardening—anyone with this keystore (and the committed password files) can brute-force the secret instantly. Please regenerate all of the new keystore files in this PR with a realistic iteration count (e.g., Lighthouse defaults in the hundreds of thousands) so the private keys remain protected.

🤖 Prompt for AI Agents
ops/docker/layer1/keystores/layer1/keys/0x8d46e9aa0c1986056e407efc7013b7f271027d3c98ce96667faa98074ab0588a61681faf78644c11819a459a95689dab/voting-keystore.json
lines 1: PBKDF2 iterations are set to c: 2 which is insecure; regenerate all
keystore files in this PR using your keystore generation tool/config with a
strong iteration count (e.g., set c to a value in the hundreds of thousands such
as 200000–500000 or follow the concrete project/default recommended value),
replace the committed keystore JSONs with the newly generated ones, ensure the
KDF section reflects the higher c value and verify decryption with the intended
password before committing.

@@ -0,0 +1 @@
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"27b69ffcaf55d9e3884a75298e5a95660d94fc53983d8a86c8aa9f19aa761670"},"message":""},"checksum":{"function":"sha256","params":{},"message":"8045df2eea34e1a326373c042b1802616549b24840183e948c08a931a6ecae9a"},"cipher":{"function":"aes-128-ctr","params":{"iv":"7a51bb36f7de674e8f05f94339acaecf"},"message":"7529f75843df106aa71dd6762e4e852ab35295ceb81e38214af95fd37041086c"}},"description":"0x91709ee06497b9ac049325853d64947290189a8c2322e3a500d91e23ea02dc158b6db63ae558b3b7670357a151cd6071","pubkey":"91709ee06497b9ac049325853d64947290189a8c2322e3a500d91e23ea02dc158b6db63ae558b3b7670357a151cd6071","path":"","uuid":"9c48003a-283e-4614-9647-44e2485b7899","version":4} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Keystore files should not be committed to version control.

This file contains encrypted private key material and is being committed to the repository. Even for devnet/testing purposes, committing keystores to git is a security anti-pattern that should be avoided:

  • Keystore files should be generated locally or provisioned through secrets management
  • The .gitignore file added in this PR already excludes keystores/, yet keystore files are being committed in the same PR
  • Even with encryption, exposing key material in version control creates unnecessary risk

Additionally, the PBKDF2 iteration count (c=2) is extremely weak—production keystores typically use 100,000+ iterations. While acceptable for devnet, this reinforces that these are test-only artifacts that should not be in the repository.

Recommendation: Remove all keystore and secret files from this commit and document in the README how to generate them locally using the provided scripts (e.g., generate-genesis.sh).

🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/keys/0x91709ee06497b9ac049325853d64947290189a8c2322e3a500d91e23ea02dc158b6db63ae558b3b7670357a151cd6071/voting-keystore.json
(lines 1-1): this keystore file containing encrypted private key material must
be removed from the commit and repository; remove the file from the branch (do
not leave secrets in history), ensure keystores/ is listed in .gitignore so
future keystores are never committed, replace any committed keystores with a
non-secret template/placeholder if needed, and add README documentation
explaining how to generate keystores locally using the provided scripts (e.g.,
generate-genesis.sh); additionally, update the generation script to use a secure
PBKDF2 iteration count (e.g., 100,000+) for production defaults while keeping
test artifacts local only.

@@ -0,0 +1 @@
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"586798b1b0983a9cd54b281283ebbe01c25e08482a2fd2c6e00f3d124a1fd5d1"},"message":""},"checksum":{"function":"sha256","params":{},"message":"dd7d2d86d574cf9a6c6f7fe3ac0de9649178a95d44f7575af22e1c500f5a3553"},"cipher":{"function":"aes-128-ctr","params":{"iv":"9009c6c729ec27470ad0bb1eeb8c1227"},"message":"749e1b3c69a7bf4733610a5df95366f15c5bcb9d6182c54c603864293069cd4c"}},"description":"0x930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8","pubkey":"930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8","path":"","uuid":"d5fa5791-b381-4e34-b5bd-d395a1e3ccc3","version":4} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Do not commit encrypted keystores with private keys to version control.

Ethereum validator keystores (even when encrypted) should never be committed to the repository. If the repository is ever leaked, exposed, or made public, attackers gain access to encrypted private keys. Combined with weak key derivation parameters (see next comment), this creates a critical security vulnerability.

Please verify:

  1. Are these keystore files tracked in .gitignore?
  2. Should they be generated at runtime or loaded from a secure, non-version-controlled location?
  3. If these are intentional hardcoded devnet credentials, consider generating them dynamically during container initialization.

For devnet environments, keystores can be generated on-the-fly using tools like staking-deposit-cli or similar, eliminating the need to commit them.


I'll verify the security concerns about the committed keystores and their parameters.


🏁 Script executed:

#!/bin/bash
# Check .gitignore for keystores
echo "=== Checking .gitignore for keystores patterns ==="
if [ -f .gitignore ]; then
  cat .gitignore | grep -i keystore || echo "No keystore patterns found in .gitignore"
else
  echo ".gitignore not found"
fi

# Find all keystore files in the repository
echo -e "\n=== Finding all keystore files ==="
find . -name "*keystore*.json" -type f 2>/dev/null | head -20

# Check if keystores directory exists and its contents
echo -e "\n=== Keystores directory structure ==="
if [ -d ops/docker/layer1/keystores ]; then
  find ops/docker/layer1/keystores -type f | head -20
fi

# Check git tracking status
echo -e "\n=== Git status for keystores ==="
git status ops/docker/layer1/keystores 2>/dev/null || echo "Unable to check git status"

Length of output: 6591


🏁 Script executed:

#!/bin/bash
# Check the password file
echo "=== Checking layer1-password.txt ==="
if [ -f ops/docker/layer1/keystores/layer1-password.txt ]; then
  echo "File exists. Content preview:"
  head -c 100 ops/docker/layer1/keystores/layer1-password.txt 2>/dev/null || echo "Unable to read"
  echo ""
  echo "File size:"
  wc -c < ops/docker/layer1/keystores/layer1-password.txt
else
  echo "File not found"
fi

# Count total keystores
echo -e "\n=== Total keystore count ==="
find ops/docker/layer1/keystores -name "*voting-keystore.json" -type f | wc -l

Length of output: 127


Remove encrypted keystores and password files from version control immediately.

This repository contains 32 committed validator keystores (ops/docker/layer1/keystores/layer1/keys/) with NO .gitignore protection, plus a plain-text password file (ops/docker/layer1/keystores/layer1-password.txt) containing "password123". Combined with dangerously weak pbkdf2 parameters (c=2 iterations), this creates a critical security vulnerability. If this repository is ever leaked or made public, attackers gain immediate access to all Layer 1 validator credentials.

Required actions:

  1. Add ops/docker/layer1/keystores/ to .gitignore immediately
  2. Remove keystores and password file from git history using git filter-branch or git filter-repo
  3. Regenerate all validator credentials (these are now compromised)
  4. Generate keystores at runtime during container initialization instead of committing them
  5. If credentials must be stored, use a secure secret management system (e.g., HashiCorp Vault, AWS Secrets Manager) separate from version control

For devnet environments, use tooling like staking-deposit-cli to generate keystores dynamically during deployment rather than committing them.

🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/keys/0x930743bfc7e18d3bd7351eaa74f477505268c1e4e1fd1ca3ccccdefb2595517343bbb8f5589c435c3c39323a4c0080f8/voting-keystore.json
(line 1), a committed encrypted keystore and accompanying plain-text password
exist in repo history and must be removed: immediately add
ops/docker/layer1/keystores/ to .gitignore and remove
ops/docker/layer1/keystores/ and ops/docker/layer1/keystores/layer1-password.txt
from the current index (git rm --cached) and then purge them from history using
git filter-repo or git filter-branch; after removal, rotate/regenerate all
compromised validator keys and passwords, stop committing keystores (generate
them at container/runtime or use staking-deposit-cli for devnet), and move any
required secrets to a proper secret manager (Vault, AWS Secrets Manager) while
ensuring any new keystores use secure KDF parameters (high iteration
count/scrypt or bcrypt) and delete any local copies of the old password file.

@@ -0,0 +1 @@
rzUo8KPugtBDRv84uGoultyzCdnxIViLekhy6qrZZL0= No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Filename extension doesn't match content format.

The file is named with a .json extension but contains plain text (a base64-encoded secret) rather than JSON format. This inconsistency could cause confusion for tooling or developers expecting JSON content.

Consider either:

  • Renaming to remove the .json extension
  • Wrapping the content in proper JSON format if required by the validator client
🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/secrets/0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e
around line 1, the file is named with a .json extension but contains a plain
base64 string; either rename the file to remove the .json extension (e.g., .txt
or no extension) and update any code/config that references this path, or
convert the file content into valid JSON (for example wrap the base64 value in
an object field like {"secret":"<base64>"}), then validate the consuming
validator/client accepts the chosen format and update any references/tests
accordingly.

⚠️ Potential issue | 🟠 Major

Avoid committing secrets to version control, even for devnet.

While this appears to be for development purposes based on the PR title, committing validator keystore passwords directly to the repository poses security risks:

  • Secrets in git history are permanent and difficult to fully remove
  • Risk of accidental reuse in non-development environments
  • Security scanners may flag these as vulnerabilities
  • Sets a precedent that could lead to production secrets being committed

Consider these alternatives:

  • Generate secrets at deployment time using a script
  • Use placeholder values with clear INSECURE_DEVNET_ONLY markers
  • Document in a README that secrets should be generated locally
  • Provide a secret generation script (e.g., openssl rand -base64 32)
🤖 Prompt for AI Agents
ops/docker/layer1/keystores/layer1/secrets/0xabd12678c73463ecea5867a80caf256d5c5e6ba53ff188b143a4d5be83365ad257edf39eaa1ba8753c4cdf4c632ff99e
lines 1-1: a private keystore password has been committed; remove the secret and
replace the file content with a clear placeholder such as
INSECURE_DEVNET_ONLY_DO_NOT_COMMIT and instructions to generate a real secret at
deploy time, add the secrets file to .gitignore, add a short README or comments
pointing to a provided secret-generation script (e.g., openssl rand -base64 32)
or environment-variable-based loading, and if this secret has already been
pushed, plan history removal (git-filter-repo or BFG) and rotate any exposed
credentials.

@@ -0,0 +1 @@
VMftlRJfjSD_8oc4maA_pV-IhJRyf1s2jBn_HwK3HjE= No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify these are test-only credentials and document accordingly.

This file contains hardcoded cryptographic secrets for validator keystores. While this may be acceptable for devnet/development environments, ensure:

  • These credentials are explicitly documented as test-only
  • They are never used in production
  • Consider adding a README or comment in the directory explaining these are devnet test credentials

🏁 Script executed:

#!/bin/bash
# Check if there's documentation indicating these are test/devnet credentials
echo "Checking for documentation about test credentials..."
fd -t f "README" ops/docker/layer1/keystores/
fd -t f "\.md$" ops/docker/layer1/

# Check if there's a .gitignore that might exclude these (indicating they should be generated)
echo -e "\nChecking .gitignore patterns..."
cat .gitignore 2>/dev/null | grep -i "keystore\|secret\|\.txt" || echo "No matching .gitignore patterns found"

# Check for any scripts that generate these files
echo -e "\nChecking for generation scripts..."
rg -l "generate.*keystore|create.*secret" ops/docker/layer1/scripts/

Length of output: 134


I need to gather more context about the repository structure and whether this is explicitly a devnet/test environment. Let me run additional verification:The web search results don't provide specific clarity about this repository context. Let me now run the deeper repository exploration script:

</script>

🏁 Script executed:

#!/bin/bash
# Get broader context about the repository
echo "=== Checking repository root documentation ==="
ls -la | grep -i "readme\|contributing\|security\|devnet\|testnet" || echo "No matching files"

echo -e "\n=== Checking for devnet indicators in directory structure ==="
find ops/ -maxdepth 2 -type d | head -20

echo -e "\n=== Checking the full keystore directory structure ==="
find ops/docker/layer1/keystores -type f | head -20

echo -e "\n=== Checking if there's any git-related documentation about these files ==="
git log --oneline -n 20 -- ops/docker/layer1/keystores/ 2>/dev/null | head -10

echo -e "\n=== Checking for any .env or config files that reference testnet ==="
fd -t f "\.(env|config)" . | head -20

echo -e "\n=== Looking for any comments or documentation in nearby files ==="
find ops/docker/layer1 -maxdepth 1 -type f -exec grep -l "test\|devnet\|credential" {} \; 2>/dev/null

Length of output: 4123


🏁 Script executed:

#!/bin/bash
# Check root-level documentation
echo "=== Root documentation files ==="
ls -la *.md *.txt *.rst 2>/dev/null | head -20

echo -e "\n=== Check git history for these files ==="
git log --oneline --all -n 5 -- ops/docker/layer1/keystores/layer1/secrets/ 2>/dev/null || echo "No git history found"

echo -e "\n=== Check directory structure context ==="
ls -la ops/docker/layer1/ 2>/dev/null | head -20

echo -e "\n=== Look for any test/devnet indicators ==="
find ops/ -maxdepth 3 -name "*.sh" -o -name "docker-compose*" | xargs grep -l "devnet\|testnet\|layer1" 2>/dev/null | head -10

echo -e "\n=== Check if there are production credentials elsewhere ==="
rg -l "keystore|secret" ops/ --type yaml --type json --type sh 2>/dev/null | head -10

Length of output: 1401



🏁 Script executed:

#!/bin/bash
# Read the README to understand repository purpose
echo "=== README.md content ==="
cat README.md

echo -e "\n=== docker-compose.yml relevant sections ==="
head -50 ops/docker/layer1/docker-compose.yml

echo -e "\n=== Check for SECURITY or other doc files ==="
find . -maxdepth 1 -type f \( -name "SECURITY*" -o -name "*.md" \) 2>/dev/null

echo -e "\n=== Check layer1 .gitignore ==="
cat ops/docker/layer1/.gitignore 2>/dev/null || echo "No .gitignore found"

echo -e "\n=== Check if keystores directory has any README ==="
ls -la ops/docker/layer1/keystores/ 2>/dev/null

Length of output: 3470



🏁 Script executed:

#!/bin/bash
# Verify if keystores are actually tracked by git despite .gitignore rule
echo "=== Is the keystore file tracked by git? ==="
git ls-files ops/docker/layer1/keystores/layer1/secrets/ 2>/dev/null | head -5

echo -e "\n=== Git status of keystores directory ==="
git status ops/docker/layer1/keystores/ 2>/dev/null || echo "Directory not shown in status (likely ignored or clean)"

echo -e "\n=== Check when .gitignore was added ==="
git log -p --all -- ops/docker/layer1/.gitignore 2>/dev/null | head -30

echo -e "\n=== Check if keystores/ is mentioned in top-level .gitignore ==="
cat .gitignore 2>/dev/null | grep -i keystore || echo "Not in top-level .gitignore"

Length of output: 995


Remove tracked test credentials from version control and document devnet setup process.

This file contains hardcoded validator secrets that are tracked in the repository despite the .gitignore rule at ops/docker/layer1/.gitignore. While these appear to be test credentials for a devnet Layer1 setup (based on directory structure and git history), they should not be in version control:

  • Remove ops/docker/layer1/keystores/layer1/secrets/* and layer1-password.txt from git history
  • Add instructions to ops/docker/layer1/README.md (create if missing) explaining how to generate these test credentials locally
  • Alternatively, ensure these are generated during container initialization rather than committed

Currently there is no documentation indicating these are test-only, which creates confusion about the credentials' purpose and safety.

🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/secrets/0xad9222dec71ff8ee6bc0426ffe7b5e66f96738225db281dd20027a1556d089fdebd040abfbc2041d6c1a0d8fdcfce183
(line 1), remove this tracked test credential from the repository and purge it
from git history (use git filter-repo or BFG to remove
ops/docker/layer1/keystores/layer1/secrets/* and layer1-password.txt from all
commits), update .gitignore to ensure those paths are ignored going forward, and
commit the removal; then add ops/docker/layer1/README.md explaining how to
generate devnet Layer1 keys locally or provide a script/entrypoint hook that
auto-generates these test credentials at container initialization (describe
commands, expected files, and any password handling), and finally rotate any
credentials that may have leaked in CI/dev environments.

@@ -0,0 +1 @@
1y8B2Q0VKtGC71U5d4hllb7wGSmxM2T8zW_PhOuneB4= No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove committed validator secret immediately.

This file includes raw validator secret material in the repository. Publishing it breaks validator security, violates secret-management policy, and requires an immediate rotate-and-purge of the exposed credentials (including cleaning Git history and re-provisioning the validator). Please delete this artifact from the PR, reissue fresh keys off-repo (e.g., via a secure secrets store), and ensure the compromised secret is revoked before merging.

@@ -0,0 +1 @@
qETVYVop1wqSdueWixO_CDeb29an1pV6pkGs3T8t2oE= No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not commit validator secrets to git.

This file contains a raw validator secret. Even for devnet usage, checking real secrets into version control breaks key hygiene, violates least‑privilege expectations, and risks accidental reuse in other environments. Please drop the concrete value from the repo and replace it with a template (e.g., instructions or an env‑supplied secret) so the secret is provisioned at runtime instead of being published here.

🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/secrets/0xae940a07850cf904b44f31cbf0e44824bae5ec36dcfdb7fad858f2a39dba38de82ca12b0ae939a34fce7a02e4b9789f8
(lines 1-1), this file contains a raw validator secret committed to git; remove
the concrete secret value and replace the file with a template or placeholder
plus instructions to load the secret at runtime (e.g., via environment variable,
Docker secret, or external secret manager). Ensure the repo contains no secret
material: delete the value from history if necessary (git rm and rotate keys),
update deployment docs and compose/stack configs to reference the runtime secret
source, and add a gitignored example/template file showing how to provide the
secret without embedding it in version control.

@@ -0,0 +1 @@
GtHF8DbPC-1zy36sQVfRz_eEXcTphPoFcczyfPal6gQ= No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not commit validator secrets to the repository.

This file adds what appears to be a live validator keystore passphrase/secret straight into source control. Even for devnet tooling, shipping real secrets in git is a security exposure (they propagate to forks, caches, CI logs, etc.) and can be mistakenly reused in less-trusted environments. Please strip the actual secret from the repo—e.g., template the file (sample value + instructions) or have the setup scripts generate/populate it locally instead.

🤖 Prompt for AI Agents
In
ops/docker/layer1/keystores/layer1/secrets/0xafa10af166a0dbf3a25ff86cd6f8e70cd70e4e98e226b158f3563450b3fb184d2649adbb11e53080d1ca
around line 1, a real validator keystore secret was committed; remove the secret
value from the repository and replace the file with a templated/sample file
(e.g., placeholder value and usage instructions). Update repository workflow to
generate or populate the real secret locally or via a secure secret manager (or
env var) during setup, add the real-secret path to .gitignore, and add a brief
setup script or docs showing how to create/populate the real keystore. If this
secret was ever used in production or shared, rotate/revoke it immediately and
purge it from git history (use git filter-repo/BFG) to fully remove sensitive
data.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +76 to +94

layer1_dir = pjoin(paths.ops_dir, 'layer1')
genesis_dir = pjoin(layer1_dir, 'genesis')
jwt_dir = pjoin(layer1_dir, 'jwt')

# Check if genesis files exist, if not generate them
genesis_json = pjoin(genesis_dir, 'genesis.json')
genesis_ssz = pjoin(genesis_dir, 'genesis.ssz')
jwt_secret = pjoin(jwt_dir, 'jwtsecret')

if not os.path.exists(genesis_json) or not os.path.exists(genesis_ssz) or not os.path.exists(jwt_secret):
log.info('Genesis files not found, generating...')
generate_script = pjoin(layer1_dir, 'scripts', 'generate-genesis.sh')
if os.path.exists(generate_script):
run_command(['bash', generate_script], check=True, cwd=layer1_dir)
else:
log.error(f'Genesis generation script not found at {generate_script}')
raise FileNotFoundError(f'Genesis generation script not found')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix f-string without placeholders.

Line 93 uses an f-string prefix without any placeholders, which is unnecessary.

Apply this diff:

-            raise FileNotFoundError(f'Genesis generation script not found')
+            raise FileNotFoundError('Genesis generation script not found')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
layer1_dir = pjoin(paths.ops_dir, 'layer1')
genesis_dir = pjoin(layer1_dir, 'genesis')
jwt_dir = pjoin(layer1_dir, 'jwt')
# Check if genesis files exist, if not generate them
genesis_json = pjoin(genesis_dir, 'genesis.json')
genesis_ssz = pjoin(genesis_dir, 'genesis.ssz')
jwt_secret = pjoin(jwt_dir, 'jwtsecret')
if not os.path.exists(genesis_json) or not os.path.exists(genesis_ssz) or not os.path.exists(jwt_secret):
log.info('Genesis files not found, generating...')
generate_script = pjoin(layer1_dir, 'scripts', 'generate-genesis.sh')
if os.path.exists(generate_script):
run_command(['bash', generate_script], check=True, cwd=layer1_dir)
else:
log.error(f'Genesis generation script not found at {generate_script}')
raise FileNotFoundError(f'Genesis generation script not found')
layer1_dir = pjoin(paths.ops_dir, 'layer1')
genesis_dir = pjoin(layer1_dir, 'genesis')
jwt_dir = pjoin(layer1_dir, 'jwt')
# Check if genesis files exist, if not generate them
genesis_json = pjoin(genesis_dir, 'genesis.json')
genesis_ssz = pjoin(genesis_dir, 'genesis.ssz')
jwt_secret = pjoin(jwt_dir, 'jwtsecret')
if not os.path.exists(genesis_json) or not os.path.exists(genesis_ssz) or not os.path.exists(jwt_secret):
log.info('Genesis files not found, generating...')
generate_script = pjoin(layer1_dir, 'scripts', 'generate-genesis.sh')
if os.path.exists(generate_script):
run_command(['bash', generate_script], check=True, cwd=layer1_dir)
else:
log.error(f'Genesis generation script not found at {generate_script}')
raise FileNotFoundError('Genesis generation script not found')
🧰 Tools
🪛 Ruff (0.14.3)

93-93: Avoid specifying long messages outside the exception class

(TRY003)


93-93: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In ops/devnet-morph/devnet/__init__.py around lines 76-94, there's an
unnecessary f-string used on the FileNotFoundError raise (line ~93) that has no
placeholders; change the raise to use a normal string literal (remove the
leading 'f') or alternatively include the missing placeholder (e.g., the
generate_script path) if you intended to show it—update the raise to either
raise FileNotFoundError('Genesis generation script not found') or raise
FileNotFoundError(f'Genesis generation script not found at {generate_script}')
so the f-string usage is either removed or actually contains a placeholder.

image: gcr.io/prysmaticlabs/prysm/validator:v4.2.1
# ========== Layer1 Ethereum Node ==========
layer1-el:
image: ethereum/client-go:latest
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pin Docker image versions for reproducibility and security.

All layer1 services use latest tags (layer1-el: ethereum/client-go:latest, layer1-cl: sigp/lighthouse:latest, layer1-vc: sigp/lighthouse:latest). Using latest is not reproducible across deployments and poses security risks.

Specify explicit version tags for all images. For example:

-    image: ethereum/client-go:latest
+    image: ethereum/client-go:v1.13.0
-    image: sigp/lighthouse:latest
+    image: sigp/lighthouse:v4.5.0

Please consult your project's versioning strategy to determine appropriate versions.

Also applies to: 50-50, 93-93

🤖 Prompt for AI Agents
In ops/docker/docker-compose-4nodes.yml around line 26 (and similarly at lines
50 and 93), the Compose services use the insecure, non-reproducible image tag
"latest"; change each image to an explicit, pinned version tag (e.g.,
ethereum/client-go:<specific-version>, sigp/lighthouse:<specific-version>)
according to our project's versioning policy, update the image fields for
layer1-el, layer1-cl, and layer1-vc to those exact tags, and if needed add an
image digest or comment documenting the chosen versions for future
reproducibility.

- --disable-packet-filter
- --execution-endpoints=http://layer1-el:8551
- --jwt-secrets=/jwt/jwtsecret
- --suggested-fee-recipient=0x8943545177806ED17B9F23F0a21ee5948eCaa776
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Externalize hardcoded fee recipient address.

The suggested fee recipient 0x8943545177806ED17B9F23F0a21ee5948eCaa776 is hardcoded in both layer1-cl (line 77) and layer1-vc (line 110). This should be configurable via environment variables for flexibility and to avoid duplication.

Define the fee recipient as an environment variable in your .env file (e.g., L1_FEE_RECIPIENT=0x8943545177806ED17B9F23F0a21ee5948eCaa776), then reference it in both services:

-      - --suggested-fee-recipient=0x8943545177806ED17B9F23F0a21ee5948eCaa776
+      - --suggested-fee-recipient=${L1_FEE_RECIPIENT}

Apply this change to both layer1-cl (line 77) and layer1-vc (line 110).

Also applies to: 110-110

🤖 Prompt for AI Agents
In ops/docker/docker-compose-4nodes.yml around lines 77 and 110, the suggested
fee recipient is hardcoded; remove the literal
0x8943545177806ED17B9F23F0a21ee5948eCaa776 and instead reference an environment
variable (e.g., L1_FEE_RECIPIENT) for both layer1-cl (line 77) and layer1-vc
(line 110); add L1_FEE_RECIPIENT to your .env file (with the current address as
default), ensure the compose file loads that .env (or an env_file) and replace
the flag values with the environment variable reference for both services so
they share the same configurable value.

export PRESET_BASE=minimal
export CHAIN_ID=900
export DEPOSIT_CONTRACT_ADDRESS=0x00000000219ab540356cBB839Cbe05303d7705Fa
export EL_AND_CL_MNEMONIC="giant issue aisle success illegal bike spike question tent bar rely arctic volcano long crawl hungry vocal artwork sniff fantasy very lucky have athlete"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded mnemonic phrase in configuration template.

The mnemonic EL_AND_CL_MNEMONIC is hardcoded in this template. This is a critical security concern if:

  1. This mnemonic is used anywhere beyond local devnet
  2. Accounts derived from this mnemonic receive real value
  3. This template is copied without modification for other environments

Ensure this is clearly documented as a devnet-only mnemonic and add warnings in comments or documentation that this must be changed for any non-local deployment.

🤖 Prompt for AI Agents
In ops/docker/layer1/configs/values.env.template around line 4, the
EL_AND_CL_MNEMONIC is hardcoded — replace this literal mnemonic with a clear
placeholder (e.g., EL_AND_CL_MNEMONIC="<REPLACE_WITH_DEVNET_MNEMONIC>") and add
an inline comment above it stating this value is for local devnet only and must
be replaced for any non-local or production environment; additionally, update
repository docs/README to warn against committing real mnemonics, recommend
using environment-specific secret management (env vars, key vaults, or .env
excluded from VCS), and provide steps to generate or supply a secure mnemonic
for other environments.

# Note: Kurtosis executes "geth init && geth run" in the same container
# with datadir=/data/geth/execution-data
layer1-el:
image: ethereum/client-go:latest
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pin Docker image versions for reproducibility and security.

All layer1 services use latest tags. Using latest is not reproducible across deployments and poses security risks.

Specify explicit version tags for all images:

-    image: ethereum/client-go:latest
+    image: ethereum/client-go:v1.13.0
-    image: sigp/lighthouse:latest
+    image: sigp/lighthouse:v4.5.0

Ensure the version tags are consistent across both ops/docker/docker-compose-4nodes.yml and ops/docker/layer1/docker-compose.yml.

Also applies to: 36-36, 81-81

🤖 Prompt for AI Agents
In ops/docker/layer1/docker-compose.yml around lines 8, 36 and 81, replace all
image tags currently set to "latest" with explicit, fixed version tags (use the
specific tested client-go/Geth version your CI or deploys expect), and ensure
the same explicit tags are applied identically in
ops/docker/docker-compose-4nodes.yml; update the three lines to reference the
chosen semantic version (not “latest”), commit both files so deployments are
reproducible and consistent across environments.

- --disable-packet-filter
- --execution-endpoints=http://layer1-el:8551
- --jwt-secrets=/jwt/jwtsecret
- --suggested-fee-recipient=0x8943545177806ED17B9F23F0a21ee5948eCaa776
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Externalize hardcoded fee recipient address.

The suggested fee recipient 0x8943545177806ED17B9F23F0a21ee5948eCaa776 is hardcoded in both layer1-cl (line 65) and layer1-vc (line 100). This should be configurable via environment variables (same issue as in docker-compose-4nodes.yml).

Define the fee recipient as an environment variable and reference it in both services:

-      - --suggested-fee-recipient=0x8943545177806ED17B9F23F0a21ee5948eCaa776
+      - --suggested-fee-recipient=${L1_FEE_RECIPIENT}

Apply this change to both layer1-cl (line 65) and layer1-vc (line 100).

Also applies to: 100-100

🤖 Prompt for AI Agents
In ops/docker/layer1/docker-compose.yml around lines 65 and 100, the suggested
fee recipient address is hardcoded; make it configurable by adding an
environment variable (e.g. FEE_RECIPIENT) in the compose file’s top-level or
each service env block and replace the literal
--suggested-fee-recipient=0x8943... occurrences with a reference to that
variable (e.g. --suggested-fee-recipient=${FEE_RECIPIENT}); ensure both
layer1-cl (line 65) and layer1-vc (line 100) use the same env var and update any
.env or documentation to include the new FEE_RECIPIENT entry.

@@ -0,0 +1 @@
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"22c551538a5126f6d8acdfcc7ce8da3e1f31dd3bc6bb18b311c4ebe612cd6953"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1598d17ff4307d245c8c078e1c150f45e36284ac8a412699530cf02ecc4e513"},"cipher":{"function":"aes-128-ctr","params":{"iv":"657cdbcafd3cb6ab495d90916a42e09c"},"message":"24f00f8db95ccddb572a01c0645227a8d5d314a615904fd6d041428c94b83af7"}},"description":"0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","pubkey":"8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","path":"","uuid":"38b7eba0-0c08-4b88-a6eb-8a6b8ebcc871","version":4} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: Add trailing newline to keystore JSON.

POSIX text files should end with a newline character. While functionally correct, this will trigger Git warnings and violates file format standards.

Apply this diff to add a trailing newline:

-{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"22c551538a5126f6d8acdfcc7ce8da3e1f31dd3bc6bb18b311c4ebe612cd6953"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1598d17ff4307d245c8c078e1c150f45e36284ac8a412699530cf02ecc4e513"},"cipher":{"function":"aes-128-ctr","params":{"iv":"657cdbcafd3cb6ab495d90916a42e09c"},"message":"24f00f8db95ccddb572a01c0645227a8d5d314a615904fd6d041428c94b83af7"}},"description":"0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","pubkey":"8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","path":"","uuid":"38b7eba0-0c08-4b88-a6eb-8a6b8ebcc871","version":4}
+{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"22c551538a5126f6d8acdfcc7ce8da3e1f31dd3bc6bb18b311c4ebe612cd6953"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1598d17ff4307d245c8c078e1c150f45e36284ac8a412699530cf02ecc4e513"},"cipher":{"function":"aes-128-ctr","params":{"iv":"657cdbcafd3cb6ab495d90916a42e09c"},"message":"24f00f8db95ccddb572a01c0645227a8d5d314a615904fd6d041428c94b83af7"}},"description":"0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","pubkey":"8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","path":"","uuid":"38b7eba0-0c08-4b88-a6eb-8a6b8ebcc871","version":4}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"22c551538a5126f6d8acdfcc7ce8da3e1f31dd3bc6bb18b311c4ebe612cd6953"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1598d17ff4307d245c8c078e1c150f45e36284ac8a412699530cf02ecc4e513"},"cipher":{"function":"aes-128-ctr","params":{"iv":"657cdbcafd3cb6ab495d90916a42e09c"},"message":"24f00f8db95ccddb572a01c0645227a8d5d314a615904fd6d041428c94b83af7"}},"description":"0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","pubkey":"8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","path":"","uuid":"38b7eba0-0c08-4b88-a6eb-8a6b8ebcc871","version":4}
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":2,"prf":"hmac-sha256","salt":"22c551538a5126f6d8acdfcc7ce8da3e1f31dd3bc6bb18b311c4ebe612cd6953"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1598d17ff4307d245c8c078e1c150f45e36284ac8a412699530cf02ecc4e513"},"cipher":{"function":"aes-128-ctr","params":{"iv":"657cdbcafd3cb6ab495d90916a42e09c"},"message":"24f00f8db95ccddb572a01c0645227a8d5d314a615904fd6d041428c94b83af7"}},"description":"0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","pubkey":"8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75","path":"","uuid":"38b7eba0-0c08-4b88-a6eb-8a6b8ebcc871","version":4}
🤖 Prompt for AI Agents
ops/docker/layer1/keystores/layer1/keys/0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75/voting-keystore.json
lines 1-1: the file is missing a trailing newline which triggers Git/POSIX
warnings; open the JSON file and add a single '\n' at the end of the file
(ensure no other changes to content or whitespace), save and commit so the file
ends with a newline character.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

SECURITY: Do not commit validator keystores to version control.

Even for devnet/testnet environments, committing encrypted keystores to Git poses significant security risks. Once in Git history, the keystores remain accessible even if later deleted. If the repository is ever exposed or access is compromised, keystores are available for offline brute-force attacks.

Recommended approach:

  • Add keystores directory to .gitignore
  • Generate keystores dynamically at deployment time or provide them via secure secrets management (environment variables, encrypted vaults, Kubernetes secrets, etc.)
  • Document the process for developers to generate their own keystores locally without committing them

Please verify:

  1. Is there a .gitignore rule protecting keystore files/directories?
  2. What is the intended secrets management strategy for this devnet setup?
  3. Are these non-production/throwaway keys created specifically for development?

🏁 Script executed:

#!/bin/bash
# Check for .gitignore rules and keystores directory structure
echo "=== .gitignore files and content ===" 
fd -t f '.gitignore' | head -5 | xargs -I {} sh -c 'echo "File: {}"; cat "{}"'

echo ""
echo "=== Keystores directory structure ===" 
fd -t f 'voting-keystore.json' -o -t d 'keystores' | head -20

echo ""
echo "=== Check for secrets or deployment documentation ==="
fd -t f '(README|DEPLOY|SECRET|CONFIG|docs)' -e md -e txt -e yml -e yaml | grep -iE '(secret|key|deploy|config)' | head -10

Length of output: 383


🏁 Script executed:

#!/bin/bash
# Count keystore files and check project structure
echo "=== Count of voting-keystore.json files ===" 
fd -t f 'voting-keystore.json' | wc -l

echo ""
echo "=== Sample of keystore file paths ===" 
fd -t f 'voting-keystore.json' | head -5

echo ""
echo "=== Check for trailing newline in the specific file ===" 
KEYSTORE_FILE="ops/docker/layer1/keystores/layer1/keys/0x8a8bb292bcc481070d3afdbbc8789e2ab4b29c9603936e6d85f5ff71e23fc5b6d61009f0fa636b5d5b2dc309d39e3d75/voting-keystore.json"
if [ -f "$KEYSTORE_FILE" ]; then
  if [ -z "$(tail -c 1 "$KEYSTORE_FILE")" ]; then
    echo "File HAS trailing newline"
  else
    echo "File MISSING trailing newline"
  fi
else
  echo "File not found: $KEYSTORE_FILE"
fi

Length of output: 171


🏁 Script executed:

#!/bin/bash
# Look for deployment, secrets, or key management documentation
echo "=== Searching for deployment/secrets related files ==="
fd -t f . ops/docker | grep -iE '(readme|doc|deploy|secret|compose|dockerfile)' | head -15

echo ""
echo "=== Check docker-compose or deployment scripts ==="
fd -t f '(docker-compose|Dockerfile|entrypoint)' | xargs -I {} sh -c 'echo "=== File: {} ==="; head -20 "{}"' 2>/dev/null | head -60

Length of output: 2151


🏁 Script executed:

#!/bin/bash
# Search for comments or docs about key management
echo "=== Search for key/secret management references ==="
rg -i '(keystore|secret|vault|env.*key|key.*management)' --type md --type txt --type yaml --max-count=3 -C 2 | head -50

Length of output: 4198


🌐 Web query:

What are the recommended KDF parameters for EIP-2335 Ethereum validator keystores?

💡 Result:

Recommended/commonly used KDF parameters for EIP‑2335 validator keystores:

  • scrypt (default used by the deposit CLI): dklen=32, n=262144, r=8, p=1. [1][2]
  • PBKDF2‑HMAC‑SHA256 (optional): dklen=32, c (iterations)=262144, prf="hmac‑sha256". [1][2]

(See EIP‑2335 for the keystore format and the staking-deposit-cli documentation/examples for the practical defaults above.) [1][2]

Sources:
[1] EIP‑2335 spec (keystore/KDF module).
[2] staking-deposit-cli / deposit‑cli keystore documentation and examples.


CRITICAL: Do not commit validator keystores to version control, and regenerate with correct KDF parameters.

Adding keystores to Git permanently compromises security. Once in history, they remain accessible even if deleted. Additionally, the keystore uses dangerously weak KDF parameters (c=2 iterations vs. c=262144 required by EIP-2335), making it vulnerable to brute-force attacks.

Required actions:

  1. Remove this keystore file from the commit
  2. Add ops/docker/layer1/keystores/ to .gitignore
  3. Regenerate keystores using a proper EIP-2335 compliant tool (e.g., staking-deposit-cli) with correct PBKDF2 parameters (c=262144)
  4. Document the secure process for developers to generate keystores locally without committing them
  5. Fix missing trailing newline if file is retained

Alternative: Mount keystores via secrets management (environment variables, encrypted vaults, or Kubernetes secrets) instead of local files.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
ops/docker/docker-compose-4nodes.yml (2)

26-26: [DUPLICATE] Pin Docker image versions for reproducibility and security.

As flagged in earlier review(s), all three layer1 services use latest tags (ethereum/client-go:latest, sigp/lighthouse:latest). This is not reproducible across deployments and poses security risks. Specify explicit version tags according to your project's versioning strategy (e.g., ethereum/client-go:v1.13.0, sigp/lighthouse:v4.5.0).

Also applies to: 50-50, 93-93


78-78: [DUPLICATE] Externalize hardcoded fee recipient address.

As flagged in earlier review(s), the suggested fee recipient 0x8943545177806ED17B9F23F0a21ee5948eCaa776 is hardcoded in both layer1-cl (line 78) and layer1-vc (line 110). This should be configurable via environment variables to avoid duplication and improve maintainability.

Define L1_FEE_RECIPIENT in your .env file, then reference it in both services:

-      - --suggested-fee-recipient=0x8943545177806ED17B9F23F0a21ee5948eCaa776
+      - --suggested-fee-recipient=${L1_FEE_RECIPIENT}

Also applies to: 110-110

🧹 Nitpick comments (1)
ops/docker/docker-compose-4nodes.yml (1)

52-53: Inconsistent depends_on usage between Layer1 and L2 services.

layer1-cl (line 52) and layer1-vc (line 96) use simple dependency lists, whereas morph-geth-0 (line 121) uses the condition-based format (service_started). This inconsistency may leave dependent services vulnerable to race conditions if they attempt to connect before upstream services are fully ready.

For consistency and reliability, consider adding conditions to all depends_on blocks:

  layer1-cl:
    ...
    depends_on:
-     - layer1-el
+     layer1-el:
+       condition: service_started
  layer1-vc:
    ...
    depends_on:
-     - layer1-cl
+     layer1-cl:
+       condition: service_started

Also applies to: 96-97

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d631249 and 996d233.

📒 Files selected for processing (1)
  • ops/docker/docker-compose-4nodes.yml (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
ops/docker/docker-compose-4nodes.yml (3)

19-21: New Layer1 volumes defined.

The addition of dedicated volumes for Layer1 services follows good Docker practices. These persistent volumes ensure data survives container restarts.


25-48: layer1-el execution service setup looks solid.

The geth initialization is idempotent—checking for an existing chaindata before initializing genesis is a good safeguard against unnecessary reinitializations on container restarts. Command-line flags (RPC APIs, metrics, etc.) are well-configured for a devnet environment.


121-123: Layer1 dependency on morph-geth-0 established correctly.

The update to morph-geth-0's depends_on block now correctly references layer1-el with a service_started condition. This ensures the Layer1 execution layer is available before L2 geth starts.

@FletcherMan FletcherMan merged commit 415ca28 into main Nov 7, 2025
9 checks passed
@FletcherMan FletcherMan deleted the refactor_layer1 branch November 7, 2025 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants