diff --git a/.cargo/audit.toml b/.cargo/audit.toml deleted file mode 100644 index 38e462f5..00000000 --- a/.cargo/audit.toml +++ /dev/null @@ -1,27 +0,0 @@ -# RUSTSEC-2026-0049: CRL revocation checking bug in rustls-webpki 0.101.7. -# -# Background: CRL (Certificate Revocation List) checking is an optional TLS -# feature where a client fetches a list of revoked certificates from URLs -# embedded in the cert itself, to confirm it hasn't been invalidated since -# issuance. This is distinct from normal certificate validation. -# -# The bug: when a cert lists multiple CRL distribution point URLs, only the -# first URL is checked; the rest are silently ignored. This matters only when -# CRL checking is enabled AND the UnknownStatusPolicy is set to Allow (meaning -# "if I can't determine revocation status, accept the cert anyway"). With that -# combination, a revoked certificate from a compromised CA could be accepted. -# -# Why this does not affect Commit-Boost: the vulnerable code path is never -# reached because no code in this codebase enables CRL checking at all. -# TLS is used in four places: (1) relay communication via reqwest with -# rustls-tls uses default CA validation with no CRL configured; (2) the signer -# server presents a TLS certificate but does not check client revocation; -# (3) the signer client pins a single self-signed certificate via -# add_root_certificate — CRL is irrelevant for self-signed certs; (4) the Dirk -# remote signer uses mTLS with a custom CA but again no CRL. In all cases the -# buggy CRL code in rustls-webpki is never invoked. -# -# Blocked on sigp/lighthouse upgrading past v8.0.1 without a compilation -# regression (SseEventSource missing cfg guard in eth2 error.rs). -[advisories] -ignore = ["RUSTSEC-2026-0049"] \ No newline at end of file diff --git a/.github/workflows/release-gate.yml b/.github/workflows/release-gate.yml deleted file mode 100644 index c5f1a1a4..00000000 --- a/.github/workflows/release-gate.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Release Gate - -on: - pull_request: - types: [closed] - branches: [main] - -jobs: - release-gate: - name: Tag and update release branches - runs-on: ubuntu-latest - # Only run when a release/ branch is merged (not just closed) - if: | - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.head.ref, 'release/v') - - permissions: - contents: write - - steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - uses: actions/checkout@v4 - with: - # Full history required for version comparison against existing tags - # and for the fast-forward push to stable/beta. - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Extract and validate version - id: version - env: - BRANCH_REF: ${{ github.event.pull_request.head.ref }} - run: | - BRANCH="$BRANCH_REF" - NEW_VERSION="${BRANCH#release/}" - echo "new=${NEW_VERSION}" >> $GITHUB_OUTPUT - - # Determine if this is an RC - if echo "$NEW_VERSION" | grep -qE '\-rc[0-9]+$'; then - echo "is_rc=true" >> $GITHUB_OUTPUT - else - echo "is_rc=false" >> $GITHUB_OUTPUT - fi - - - name: Validate version is strictly increasing - env: - NEW_VERSION: ${{ steps.version.outputs.new }} - run: | - # Get the latest tag; if none exist yet, skip the comparison - LATEST_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n1) - if [ -z "$LATEST_TAG" ]; then - echo "No existing tags found — skipping version comparison" - exit 0 - fi - - LATEST_VERSION="${LATEST_TAG#v}" - - python3 - <> $GITHUB_ENV - - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -62,8 +63,8 @@ jobs: context: . push: false platforms: linux/amd64,linux/arm64 - cache-from: type=registry,ref=ghcr.io/${{ env.OWNER }}/buildcache:${{ matrix.target-crate}} - cache-to: type=registry,ref=ghcr.io/${{ env.OWNER }}/buildcache:${{ matrix.target-crate }},mode=max + cache-from: type=registry,ref=ghcr.io/commit-boost/buildcache:${{ matrix.target-crate}} + cache-to: type=registry,ref=ghcr.io/commit-boost/buildcache:${{ matrix.target-crate }},mode=max file: provisioning/build.Dockerfile outputs: type=local,dest=build build-args: | @@ -82,7 +83,7 @@ jobs: path: | ${{ matrix.name }}-${{ github.ref_name }}-linux_${{ matrix.package-suffix }}.tar.gz - # Builds the arm64 binary for Darwin natively + # Builds the arm64 binaries for Darwin, for all 3 crates, natively build-binaries-darwin: strategy: matrix: @@ -91,7 +92,9 @@ jobs: # - x86_64-apple-darwin - aarch64-apple-darwin name: - - commit-boost + - commit-boost-cli + - commit-boost-pbs + - commit-boost-signer include: # - target: x86_64-apple-darwin # os: macos-latest-large @@ -155,31 +158,6 @@ jobs: path: | ${{ matrix.name }}-${{ github.ref_name }}-darwin_${{ matrix.package-suffix }}.tar.gz - # Signs the binaries - sign-binaries: - needs: - - build-binaries-linux - - build-binaries-darwin - runs-on: ubuntu-latest - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - path: ./artifacts - pattern: "commit-boost*" - - - name: Sign binaries - uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d #v3.2.0 - with: - inputs: ./artifacts/**/*.tar.gz - - - name: Upload signatures - uses: actions/upload-artifact@v4 - with: - name: signatures-${{ github.ref_name }} - path: | - ./artifacts/**/*.sigstore.json - # Builds the PBS Docker image build-and-push-pbs-docker: needs: [build-binaries-linux] @@ -195,19 +173,16 @@ jobs: uses: actions/download-artifact@v4 with: path: ./artifacts - pattern: "commit-boost*" + pattern: "commit-boost-*" - name: Extract binaries run: | mkdir -p ./artifacts/bin/linux_amd64 mkdir -p ./artifacts/bin/linux_arm64 - tar -xzf ./artifacts/commit-boost-${{ github.ref_name }}-linux_x86-64/commit-boost-${{ github.ref_name }}-linux_x86-64.tar.gz -C ./artifacts/bin - mv ./artifacts/bin/commit-boost ./artifacts/bin/linux_amd64/commit-boost - tar -xzf ./artifacts/commit-boost-${{ github.ref_name }}-linux_arm64/commit-boost-${{ github.ref_name }}-linux_arm64.tar.gz -C ./artifacts/bin - mv ./artifacts/bin/commit-boost ./artifacts/bin/linux_arm64/commit-boost - - - name: Set lowercase owner - run: echo "OWNER=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + tar -xzf ./artifacts/commit-boost-pbs-${{ github.ref_name }}-linux_x86-64/commit-boost-pbs-${{ github.ref_name }}-linux_x86-64.tar.gz -C ./artifacts/bin + mv ./artifacts/bin/commit-boost-pbs ./artifacts/bin/linux_amd64/commit-boost-pbs + tar -xzf ./artifacts/commit-boost-pbs-${{ github.ref_name }}-linux_arm64/commit-boost-pbs-${{ github.ref_name }}-linux_arm64.tar.gz -C ./artifacts/bin + mv ./artifacts/bin/commit-boost-pbs ./artifacts/bin/linux_arm64/commit-boost-pbs - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -231,8 +206,8 @@ jobs: build-args: | BINARIES_PATH=./artifacts/bin tags: | - ghcr.io/${{ env.OWNER }}/pbs:${{ github.ref_name }} - ${{ !contains(github.ref_name, 'rc') && format('ghcr.io/{0}/pbs:latest', env.OWNER) || '' }} + ghcr.io/commit-boost/pbs:${{ github.ref_name }} + ${{ !contains(github.ref_name, 'rc') && 'ghcr.io/commit-boost/pbs:latest' || '' }} file: provisioning/pbs.Dockerfile # Builds the Signer Docker image @@ -250,19 +225,16 @@ jobs: uses: actions/download-artifact@v4 with: path: ./artifacts - pattern: "commit-boost*" + pattern: "commit-boost-*" - name: Extract binaries run: | mkdir -p ./artifacts/bin/linux_amd64 mkdir -p ./artifacts/bin/linux_arm64 - tar -xzf ./artifacts/commit-boost-${{ github.ref_name }}-linux_x86-64/commit-boost-${{ github.ref_name }}-linux_x86-64.tar.gz -C ./artifacts/bin - mv ./artifacts/bin/commit-boost ./artifacts/bin/linux_amd64/commit-boost - tar -xzf ./artifacts/commit-boost-${{ github.ref_name }}-linux_arm64/commit-boost-${{ github.ref_name }}-linux_arm64.tar.gz -C ./artifacts/bin - mv ./artifacts/bin/commit-boost ./artifacts/bin/linux_arm64/commit-boost - - - name: Set lowercase owner - run: echo "OWNER=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + tar -xzf ./artifacts/commit-boost-signer-${{ github.ref_name }}-linux_x86-64/commit-boost-signer-${{ github.ref_name }}-linux_x86-64.tar.gz -C ./artifacts/bin + mv ./artifacts/bin/commit-boost-signer ./artifacts/bin/linux_amd64/commit-boost-signer + tar -xzf ./artifacts/commit-boost-signer-${{ github.ref_name }}-linux_arm64/commit-boost-signer-${{ github.ref_name }}-linux_arm64.tar.gz -C ./artifacts/bin + mv ./artifacts/bin/commit-boost-signer ./artifacts/bin/linux_arm64/commit-boost-signer - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -286,122 +258,32 @@ jobs: build-args: | BINARIES_PATH=./artifacts/bin tags: | - ghcr.io/${{ env.OWNER }}/signer:${{ github.ref_name }} - ${{ !contains(github.ref_name, 'rc') && format('ghcr.io/{0}/signer:latest', env.OWNER) || '' }} + ghcr.io/commit-boost/signer:${{ github.ref_name }} + ${{ !contains(github.ref_name, 'rc') && 'ghcr.io/commit-boost/signer:latest' || '' }} file: provisioning/signer.Dockerfile - # Creates a release on GitHub with the binaries + # Creates a draft release on GitHub with the binaries finalize-release: needs: - build-binaries-linux - build-binaries-darwin - - sign-binaries - build-and-push-pbs-docker - build-and-push-signer-docker runs-on: ubuntu-latest steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - name: Checkout code - uses: actions/checkout@v4 - with: - token: ${{ steps.app-token.outputs.token }} - - - name: Download binaries - uses: actions/download-artifact@v4 - with: - path: ./artifacts - pattern: "commit-boost*" - - - name: Download signatures + - name: Download artifacts uses: actions/download-artifact@v4 with: path: ./artifacts - pattern: "signatures-${{ github.ref_name }}*" + pattern: "commit-boost-*" - name: Finalize Release uses: softprops/action-gh-release@v2 with: files: ./artifacts/**/* - draft: false - prerelease: ${{ contains(github.ref_name, '-rc') }} + draft: true + prerelease: false tag_name: ${{ github.ref_name }} name: ${{ github.ref_name }} - generate_release_notes: true env: - GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - - # Fast-forwards stable (full release) or beta (RC) to the new tag. - # Runs after all artifacts are built and the draft release is created, - # so stable/beta are never touched if any part of the pipeline fails. - fast-forward-branch: - needs: - - finalize-release - runs-on: ubuntu-latest - steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Configure git - run: | - git config user.name "commit-boost-release-bot[bot]" - git config user.email "commit-boost-release-bot[bot]@users.noreply.github.com" - - - name: Fast-forward beta branch (RC releases) - if: contains(github.ref_name, '-rc') - run: | - git checkout beta - git merge --ff-only "${{ github.ref_name }}" - git push origin beta - - - name: Fast-forward stable branch (full releases) - if: "!contains(github.ref_name, '-rc')" - run: | - git checkout stable - git merge --ff-only "${{ github.ref_name }}" - git push origin stable - - # Deletes the tag if any job in the release pipeline fails. - # This keeps the tag and release artifacts in sync — a tag should only - # exist if the full pipeline completed successfully. - # stable/beta are never touched on failure since fast-forward-branch - # only runs after finalize-release succeeds. - # - # Note: if finalize-release specifically fails, a draft release may already - # exist on GitHub pointing at the now-deleted tag and will need manual cleanup. - cleanup-on-failure: - needs: - - build-binaries-linux - - build-binaries-darwin - - sign-binaries - - build-and-push-pbs-docker - - build-and-push-signer-docker - - finalize-release - - fast-forward-branch - runs-on: ubuntu-latest - if: failure() - steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - uses: actions/checkout@v4 - with: - token: ${{ steps.app-token.outputs.token }} - - - name: Delete tag - run: git push origin --delete ${{ github.ref_name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 22136e88..e48792b4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,15 +16,3 @@ targets.json .idea/ logs .vscode/ -certs/ - -# Nix -.direnv/ -.devenv/ -devenv.* -devenv.lock -.devenv.flake.nix -.envrc - -# Generated from testnet -kurtosis-dump \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1bebb339..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,4 +0,0 @@ -### v0.9.4-rc1 -- Unifies the `pbs`, `signer`, and `cli` binaries into one: `commit-boost`. This change changes the CLI, notably the `init` command is now invoked as `commit-boost init --config `. -- Includes new quality of life testing improvements in the Justfile: unit test coverage tooling, local Kurtosis testnet, and microbenchmark diffing. -- Robustifies the release process to ensure no compromised maintainer can unilaterally cut a release. Additionally all binaries are now signed during CI and can easily be verified before use. diff --git a/Cargo.lock b/Cargo.lock index b8b001f2..ba26b3e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,19 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher 0.3.0", - "cpufeatures 0.2.17", - "ctr 0.8.0", - "opaque-debug", -] - [[package]] name = "aes" version = "0.8.4" @@ -37,22 +24,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures 0.2.17", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -70,9 +45,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4973038846323e4e69a433916522195dce2947770076c03078fc21c80ea0f1c4" +checksum = "50ab0cd8afe573d1f7dc2353698a51b1f93aec362c8211e28cfd3948c6adba39" dependencies = [ "alloy-consensus", "alloy-contract", @@ -96,9 +71,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives 1.5.7", "num_enum", @@ -107,9 +82,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" dependencies = [ "alloy-eips", "alloy-primitives 1.5.7", @@ -124,7 +99,7 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1", "serde", "serde_json", @@ -134,9 +109,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" dependencies = [ "alloy-consensus", "alloy-eips", @@ -148,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7" +checksum = "7ac9e0c34dc6bce643b182049cdfcca1b8ce7d9c260cbdd561f511873b7e26cd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -167,6 +142,7 @@ dependencies = [ "futures-util", "serde_json", "thiserror 2.0.18", + "tracing", ] [[package]] @@ -239,21 +215,22 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "407510740da514b694fecb44d8b3cebdc60d448f70cc5d24743e8ba273448a6e" dependencies = [ "alloy-primitives 1.5.7", "alloy-rlp", "borsh", + "once_cell", "serde", ] [[package]] name = "alloy-eips" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -267,19 +244,18 @@ dependencies = [ "c-kzg", "derive_more", "either", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", "serde", "serde_with", - "sha2 0.10.9", - "thiserror 2.0.18", + "sha2", ] [[package]] name = "alloy-genesis" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" dependencies = [ "alloy-eips", "alloy-primitives 1.5.7", @@ -304,13 +280,13 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" dependencies = [ "alloy-primitives 1.5.7", "alloy-sol-types", - "http 1.4.0", + "http", "serde", "serde_json", "thiserror 2.0.18", @@ -319,9 +295,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -345,9 +321,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" dependencies = [ "alloy-consensus", "alloy-eips", @@ -369,13 +345,13 @@ dependencies = [ "derive_more", "foldhash 0.1.5", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.8.5", + "rand 0.8.6", "ruint", "rustc-hash", "serde", @@ -397,13 +373,13 @@ dependencies = [ "foldhash 0.2.0", "getrandom 0.4.2", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.2", + "rand 0.9.4", "rapidhash", "ruint", "rustc-hash", @@ -413,9 +389,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" dependencies = [ "alloy-chains", "alloy-consensus", @@ -447,7 +423,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "thiserror 2.0.18", @@ -459,9 +435,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8bd82953194dec221aa4cbbbb0b1e2df46066fe9d0333ac25b43a311e122d13" +checksum = "5ad54073131e7292d4e03e1aa2287730f737280eb160d8b579fb31939f558c11" dependencies = [ "alloy-json-rpc", "alloy-primitives 1.5.7", @@ -481,9 +457,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -492,9 +468,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" dependencies = [ "proc-macro2", "quote", @@ -503,9 +479,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" dependencies = [ "alloy-json-rpc", "alloy-primitives 1.5.7", @@ -516,7 +492,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "tokio", @@ -529,9 +505,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" dependencies = [ "alloy-primitives 1.5.7", "alloy-rpc-types-anvil", @@ -547,9 +523,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a3100b76987c1b1dc81f3abe592b7edc29e92b1242067a69d65e0030b35cf9" +checksum = "47df51bedb3e6062cb9981187a51e86d0d64a4de66eb0855e9efe6574b044ddf" dependencies = [ "alloy-primitives 1.5.7", "alloy-rpc-types-eth", @@ -559,9 +535,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -570,29 +546,29 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a22e13215866f5dfd5d3278f4c41f1fad9410dc68ce39022f58593c873c26f8" +checksum = "f526dbd7bb039327cfd0ccf18c8a29ffd7402616b0c7a0239512bf8417d544c7" dependencies = [ "alloy-eips", "alloy-primitives 1.5.7", "alloy-rpc-types-engine", "derive_more", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", "serde", "serde_json", "serde_with", "thiserror 2.0.18", - "tree_hash", + "tree_hash 0.10.0", "tree_hash_derive 0.10.0", ] [[package]] name = "alloy-rpc-types-debug" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b21e1ad18ff1b31ff1030e046462ab8168cf8894e6778cd805c8bdfe2bd649" +checksum = "2145138f3214928f08cd13da3cb51ef7482b5920d8ac5a02ecd4e38d1a8f6d1e" dependencies = [ "alloy-primitives 1.5.7", "derive_more", @@ -602,9 +578,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ac61f03f1edabccde1c687b5b25fff28f183afee64eaa2e767def3929e4457" +checksum = "bb9b97b6e7965679ad22df297dda809b11cebc13405c1b537e5cffecc95834fa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -612,18 +588,18 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more", - "ethereum_ssz", - "ethereum_ssz_derive", - "rand 0.8.5", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", + "rand 0.8.6", "serde", "strum", ] [[package]] name = "alloy-rpc-types-eth" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -642,9 +618,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad79f1e27e161943b5a4f99fe5534ef0849876214be411e0032c12f38e94daa" +checksum = "2e5a4d010f86cd4e01e5205ec273911e538e1738e76d8bafe9ecd245910ea5a3" dependencies = [ "alloy-primitives 1.5.7", "alloy-rpc-types-eth", @@ -656,9 +632,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d459f902a2313737bc66d18ed094c25d2aeb268b74d98c26bbbda2aa44182ab0" +checksum = "942d26a2ca8891b26de4a8529d21091e21c1093e27eb99698f1a86405c76b1ff" dependencies = [ "alloy-primitives 1.5.7", "alloy-rpc-types-eth", @@ -668,9 +644,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" dependencies = [ "alloy-primitives 1.5.7", "serde", @@ -679,9 +655,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" dependencies = [ "alloy-primitives 1.5.7", "async-trait", @@ -694,9 +670,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" dependencies = [ "alloy-consensus", "alloy-network", @@ -704,7 +680,7 @@ dependencies = [ "alloy-signer", "async-trait", "k256", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", ] @@ -732,7 +708,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", @@ -783,13 +759,13 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" dependencies = [ "alloy-json-rpc", "auto_impl", - "base64 0.22.1", + "base64", "derive_more", "futures", "futures-utils-wasm", @@ -806,14 +782,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde_json", "tower 0.5.3", "tracing", @@ -822,9 +798,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ef85688e5ac2da72afc804e0a1f153a1f309f05a864b1998bbbed7804dbaab" +checksum = "a1bd98c3870b8a44b79091dde5216a81d58ffbc1fd8ed61b776f9fee0f3bdf20" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -842,18 +818,20 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f00445db69d63298e2b00a0ea1d859f00e6424a3144ffc5eba9c31da995e16" +checksum = "ec3ab7a72b180992881acc112628b7668337a19ce15293ee974600ea7b693691" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 1.4.0", + "http", + "rustls", "serde_json", "tokio", "tokio-tungstenite", "tracing", + "url", "ws_stream_wasm", ] @@ -875,11 +853,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -894,12 +872,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstream" version = "1.0.0" @@ -965,15 +937,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "arc-swap" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" -dependencies = [ - "rustversion", -] - [[package]] name = "archery" version = "0.4.0" @@ -1149,7 +1112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1159,7 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1169,7 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1184,21 +1147,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "assert_cmd" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -1268,9 +1216,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", @@ -1278,9 +1226,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -1298,8 +1246,8 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "itoa", "matchit 0.7.3", @@ -1309,7 +1257,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower 0.5.3", "tower-layer", "tower-service", @@ -1317,19 +1265,19 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core 0.5.6", "axum-macros", "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", "matchit 0.8.4", @@ -1341,7 +1289,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower 0.5.3", "tower-layer", @@ -1358,13 +1306,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", ] @@ -1377,12 +1325,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -1394,13 +1342,13 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ - "axum 0.8.8", + "axum 0.8.9", "axum-core 0.5.6", "bytes", "futures-util", "headers", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -1413,37 +1361,15 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] -[[package]] -name = "axum-server" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ab4a3ec9ea8a657c72d99a03a824af695bd0fb5ec639ccbd9cd3543b41a5f9" -dependencies = [ - "arc-swap", - "bytes", - "fs-err", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", - "hyper-util", - "pin-project-lite", - "rustls 0.23.37", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.4", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -1459,34 +1385,12 @@ dependencies = [ "windows-link", ] -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base256emoji" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" -dependencies = [ - "const-str", - "match-lookup", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1541,15 +1445,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitvec" @@ -1563,15 +1461,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -1584,20 +1473,20 @@ dependencies = [ [[package]] name = "bls" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "alloy-primitives 1.5.7", "arbitrary", "blst", - "ethereum_hashing", + "ethereum_hashing 0.8.0", "ethereum_serde_utils 0.8.0", - "ethereum_ssz", + "ethereum_ssz 0.10.3", "fixed_bytes", "hex", - "rand 0.9.2", + "rand 0.9.4", "safe_arith", "serde", - "tree_hash", + "tree_hash 0.12.1", "zeroize", ] @@ -1614,12 +1503,12 @@ dependencies = [ "hkdf", "merlin", "pairing", - "rand 0.8.5", + "rand 0.8.6", "rand_chacha 0.3.1", "rand_core 0.6.4", "serde", "serde_bare", - "sha2 0.10.9", + "sha2", "sha3", "subtle", "thiserror 1.0.69", @@ -1697,26 +1586,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -1759,36 +1628,17 @@ dependencies = [ "serde", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cb-bench-micro" -version = "0.9.3" -dependencies = [ - "alloy", - "axum 0.8.8", - "cb-common", - "cb-pbs", - "cb-tests", - "criterion", - "tokio", -] - [[package]] name = "cb-bench-pbs" -version = "0.9.3" +version = "0.9.4" dependencies = [ "alloy", "cb-common", "cb-tests", "comfy-table", "histogram", - "rand 0.9.2", - "reqwest 0.12.28", + "rand 0.9.4", + "reqwest 0.13.2", "serde", "serde_json", "tokio", @@ -1797,55 +1647,50 @@ dependencies = [ [[package]] name = "cb-cli" -version = "0.9.3" +version = "0.9.4" dependencies = [ "cb-common", "clap", "docker-compose-types", "eyre", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_yaml", - "tempfile", - "toml", ] [[package]] name = "cb-common" -version = "0.9.3" +version = "0.9.4" dependencies = [ - "aes 0.8.4", + "aes", "alloy", "async-trait", - "axum 0.8.8", - "base64 0.22.1", + "axum 0.8.9", + "base64", "bimap", + "bls", "bytes", - "cipher 0.4.4", - "const_format", - "ctr 0.9.2", + "cipher", + "ctr", "derive_more", "docker-image", "eth2", "eth2_keystore", "ethereum_serde_utils 0.7.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "eyre", "futures", "jsonwebtoken", "lazy_static", - "notify", - "pbkdf2 0.12.2", - "rand 0.9.2", + "pbkdf2", + "rand 0.9.4", "rayon", - "reqwest 0.12.28", - "reqwest-eventsource", + "reqwest 0.13.2", "serde", "serde_json", "serde_yaml", - "sha2 0.10.9", - "ssz_types", - "tempfile", + "sha2", + "ssz_types 0.11.0", "thiserror 2.0.18", "tokio", "toml", @@ -1853,8 +1698,8 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", - "tree_hash", - "tree_hash_derive 0.9.1", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", "types", "unicode-normalization", "url", @@ -1862,9 +1707,9 @@ dependencies = [ [[package]] name = "cb-metrics" -version = "0.9.3" +version = "0.9.4" dependencies = [ - "axum 0.8.8", + "axum 0.8.9", "cb-common", "eyre", "prometheus", @@ -1875,39 +1720,37 @@ dependencies = [ [[package]] name = "cb-pbs" -version = "0.9.3" +version = "0.9.4" dependencies = [ "alloy", "async-trait", - "axum 0.8.8", + "axum 0.8.9", "axum-extra", "cb-common", "cb-metrics", "eyre", "futures", "lazy_static", - "notify", "parking_lot", "prometheus", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "tokio", "tower-http", "tracing", - "tree_hash", + "tree_hash 0.12.1", "url", - "uuid 1.22.0", + "uuid 1.23.1", ] [[package]] name = "cb-signer" -version = "0.9.3" +version = "0.9.4" dependencies = [ "alloy", - "axum 0.8.8", + "axum 0.8.9", "axum-extra", - "axum-server", "bimap", "blsful", "cb-common", @@ -1920,48 +1763,43 @@ dependencies = [ "parking_lot", "prometheus", "prost", - "rand 0.9.2", - "rustls 0.23.37", + "rand 0.9.4", "thiserror 2.0.18", "tokio", "tonic", "tonic-build", "tracing", - "tree_hash", - "uuid 1.22.0", + "tree_hash 0.12.1", + "uuid 1.23.1", ] [[package]] name = "cb-tests" -version = "0.9.3" +version = "0.9.4" dependencies = [ "alloy", - "axum 0.8.8", + "axum 0.8.9", "cb-common", "cb-pbs", "cb-signer", "eyre", - "jsonwebtoken", - "rcgen", - "reqwest 0.12.28", - "serde", + "reqwest 0.13.2", "serde_json", "tempfile", "tokio", - "toml", "tracing", "tracing-subscriber", "tracing-test", - "tree_hash", + "tree_hash 0.12.1", "types", "url", ] [[package]] name = "cc" -version = "1.2.57" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", @@ -1969,6 +1807,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -1989,7 +1833,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -2004,42 +1848,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "cipher" version = "0.4.4" @@ -2052,9 +1860,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -2074,9 +1882,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -2092,9 +1900,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -2132,6 +1940,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "comfy-table" version = "7.2.2" @@ -2145,9 +1963,8 @@ dependencies = [ [[package]] name = "commit-boost" -version = "0.9.3" +version = "0.9.4" dependencies = [ - "assert_cmd", "cb-cli", "cb-common", "cb-metrics", @@ -2156,30 +1973,30 @@ dependencies = [ "clap", "color-eyre", "eyre", - "predicates", - "serde_yaml", - "tempfile", "tokio", "tracing", - "tree_hash", - "tree_hash_derive 0.9.1", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", ] [[package]] name = "compare_fields" -version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f45d0b4d61b582303179fb7a1a142bc9d647b7583db3b0d5f25a21d286fab9" dependencies = [ - "itertools 0.10.5", + "compare_fields_derive", + "itertools 0.14.0", ] [[package]] name = "compare_fields_derive" -version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ff1dbbda10d495b2c92749c002b2025e0be98f42d1741ecc9ff820d2f04dce" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2200,19 +2017,14 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-str" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" - [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -2228,22 +2040,22 @@ dependencies = [ [[package]] name = "context_deserialize" -version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c523eea4af094b5970c321f4604abc42c5549d3cbae332e98325403fbbdbf70" dependencies = [ "context_deserialize_derive", - "milhouse", "serde", - "ssz_types", ] [[package]] name = "context_deserialize_derive" -version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7bf98c48ffa511b14bb3c76202c24a8742cea1efa9570391c5d41373419a09" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2281,15 +2093,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -2324,48 +2127,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "criterion" -version = "0.5.1" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", + "crossbeam-utils", ] [[package]] @@ -2399,7 +2166,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", "document-features", "parking_lot", @@ -2444,64 +2211,18 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" -dependencies = [ - "generic-array 0.14.7", - "subtle", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher 0.3.0", -] - [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version 0.4.1", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "cipher", ] [[package]] name = "da_commit" -version = "0.9.3" +version = "0.9.4" dependencies = [ "alloy", "color-eyre", @@ -2525,16 +2246,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -2559,21 +2270,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "serde", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -2583,6 +2279,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -2598,17 +2295,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" @@ -2640,26 +2326,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" -[[package]] -name = "data-encoding-macro" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" -dependencies = [ - "data-encoding", - "data-encoding-macro-internal", -] - -[[package]] -name = "data-encoding-macro-internal" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" -dependencies = [ - "data-encoding", - "syn 2.0.117", -] - [[package]] name = "der" version = "0.7.10" @@ -2756,12 +2422,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - [[package]] name = "digest" version = "0.9.0" @@ -2777,7 +2437,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -2801,7 +2461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f5e899a3da7a90647ef302f7e3050b00ed7f3f02c7b32683a04f3fbd9052541" dependencies = [ "derive_builder", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_yaml", ] @@ -2858,31 +2518,6 @@ dependencies = [ "spki", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core 0.6.4", - "serde", - "sha2 0.10.9", - "subtle", - "zeroize", -] - [[package]] name = "educe" version = "0.6.0" @@ -2911,17 +2546,7 @@ dependencies = [ "itertools 0.14.0", "serde", "serde_json", - "sha2 0.10.9", -] - -[[package]] -name = "eip_3076" -version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" -dependencies = [ - "ethereum_serde_utils 0.8.0", - "serde", - "types", + "sha2", ] [[package]] @@ -2972,7 +2597,7 @@ dependencies = [ "ekzg-bls12-381", "ekzg-maybe-rayon", "ekzg-polynomial", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -3049,25 +2674,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enr" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" -dependencies = [ - "alloy-rlp", - "base64 0.22.1", - "bytes", - "ed25519-dalek", - "hex", - "k256", - "log", - "rand 0.8.5", - "serde", - "sha3", - "zeroize", -] - [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -3107,42 +2713,35 @@ dependencies = [ [[package]] name = "eth2" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ - "derivative", - "eip_3076", - "either", - "enr", - "eth2_keystore", + "bls", + "context_deserialize", + "educe", "ethereum_serde_utils 0.8.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "futures", "futures-util", - "libp2p-identity", "mediatype", - "multiaddr", "pretty_reqwest_error", - "proto_array", - "rand 0.9.2", - "reqwest 0.11.27", + "reqwest 0.12.28", "reqwest-eventsource", "sensitive_url", "serde", "serde_json", - "ssz_types", - "test_random_derive", + "ssz_types 0.14.1", + "superstruct", "types", - "zeroize", ] [[package]] name = "eth2_interop_keypairs" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "bls", - "ethereum_hashing", + "ethereum_hashing 0.8.0", "hex", "num-bigint", "serde", @@ -3152,32 +2751,34 @@ dependencies = [ [[package]] name = "eth2_key_derivation" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "bls", "num-bigint-dig", "ring", - "sha2 0.9.9", + "sha2", "zeroize", ] [[package]] name = "eth2_keystore" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ - "aes 0.7.5", + "aes", "bls", + "cipher", + "ctr", "eth2_key_derivation", "hex", - "hmac 0.11.0", - "pbkdf2 0.8.0", - "rand 0.9.2", + "hmac", + "pbkdf2", + "rand 0.9.4", "scrypt", "serde", "serde_json", "serde_repr", - "sha2 0.9.9", + "sha2", "unicode-normalization", "uuid 0.8.2", "zeroize", @@ -3191,7 +2792,18 @@ checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ "cpufeatures 0.2.17", "ring", - "sha2 0.10.9", + "sha2", +] + +[[package]] +name = "ethereum_hashing" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" +dependencies = [ + "cpufeatures 0.2.17", + "ring", + "sha2", ] [[package]] @@ -3235,6 +2847,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "ethereum_ssz" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" +dependencies = [ + "alloy-primitives 1.5.7", + "context_deserialize", + "ethereum_serde_utils 0.8.0", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + [[package]] name = "ethereum_ssz_derive" version = "0.9.1" @@ -3247,6 +2875,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "eventsource-stream" version = "0.2.3" @@ -3268,23 +2908,11 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -3319,12 +2947,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -3338,7 +2960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -3346,7 +2968,7 @@ dependencies = [ [[package]] name = "fixed_bytes" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "alloy-primitives 1.5.7", "safe_arith", @@ -3358,15 +2980,6 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -3409,31 +3022,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs-err" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" -dependencies = [ - "autocfg", - "tokio", -] - [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "funty" version = "2.0.0" @@ -3597,7 +3191,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -3621,31 +3215,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand 0.8.5", + "rand 0.8.6", "rand_core 0.6.4", "rand_xorshift 0.3.0", "subtle", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.13.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.13" @@ -3657,25 +3232,14 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.13.0", + "http", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -3687,10 +3251,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -3716,13 +3276,10 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.8.4" +name = "hashbrown" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown 0.14.5", -] +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "headers" @@ -3730,10 +3287,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "headers-core", - "http 1.4.0", + "http", "httpdate", "mime", "sha1", @@ -3745,7 +3302,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.0", + "http", ] [[package]] @@ -3790,17 +3347,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", + "hmac", ] [[package]] @@ -3812,17 +3359,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -3833,17 +3369,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -3851,7 +3376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -3862,8 +3387,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -3876,51 +3401,26 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3928,33 +3428,18 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.37", - "rustls-pki-types", + "rustls", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] @@ -3963,26 +3448,13 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.32", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-tls" version = "0.6.0" @@ -3991,7 +3463,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -4005,19 +3477,19 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.3", - "system-configuration 0.7.0", + "system-configuration", "tokio", "tower-service", "tracing", @@ -4050,12 +3522,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -4063,9 +3536,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -4076,9 +3549,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -4090,15 +3563,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -4110,15 +3583,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -4201,36 +3674,16 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] -[[package]] -name = "inotify" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" -dependencies = [ - "bitflags 2.11.0", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - [[package]] name = "inout" version = "0.1.4" @@ -4243,16 +3696,16 @@ dependencies = [ [[package]] name = "int_to_bytes" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "bytes", ] [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -4260,7 +3713,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4271,25 +3724,14 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -4329,6 +3771,50 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -4341,10 +3827,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -4355,7 +3843,7 @@ version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.22.1", + "base64", "js-sys", "ring", "serde", @@ -4373,8 +3861,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2 0.10.9", - "signature", + "sha2", ] [[package]] @@ -4388,53 +3875,48 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" dependencies = [ "digest 0.10.7", "sha3-asm", ] [[package]] -name = "kqueue" -version = "1.1.1" +name = "konst" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" dependencies = [ - "kqueue-sys", - "libc", + "konst_macro_rules", ] [[package]] -name = "kqueue-sys" -version = "1.0.4" +name = "konst_macro_rules" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" [[package]] name = "kzg" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "arbitrary", "c-kzg", - "derivative", - "ethereum_hashing", + "educe", + "ethereum_hashing 0.8.0", "ethereum_serde_utils 0.8.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "hex", "rayon", "rust_eth_kzg", "serde", "serde_json", "tracing", - "tree_hash", + "tree_hash 0.12.1", ] [[package]] @@ -4454,9 +3936,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libm" @@ -4464,31 +3946,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libp2p-identity" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" -dependencies = [ - "bs58", - "hkdf", - "multihash", - "sha2 0.10.9", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4497,9 +3954,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -4524,9 +3981,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -4554,17 +4011,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "match-lookup" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "matchers" version = "0.2.0" @@ -4601,10 +4047,10 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "merkle_proof" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "alloy-primitives 1.5.7", - "ethereum_hashing", + "ethereum_hashing 0.8.0", "fixed_bytes", "safe_arith", ] @@ -4646,21 +4092,22 @@ dependencies = [ [[package]] name = "milhouse" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bdb104e38d3a8c5ffb7e9d2c43c522e6bcc34070edbadba565e722f0dee56c7" +checksum = "259dd9da2ae5e0278b95da0b7ecef9c18c309d0a2d9e6db57ed33b9e8910c5e7" dependencies = [ "alloy-primitives 1.5.7", + "context_deserialize", "educe", - "ethereum_hashing", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "itertools 0.13.0", "parking_lot", "rayon", "serde", "smallvec", - "tree_hash", + "tree_hash 0.12.1", "triomphe", "typenum", "vec_map", @@ -4689,57 +4136,15 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.61.2", ] -[[package]] -name = "multiaddr" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" -dependencies = [ - "arrayref", - "byteorder", - "data-encoding", - "libp2p-identity", - "multibase", - "multihash", - "percent-encoding", - "serde", - "static_assertions", - "unsigned-varint", - "url", -] - -[[package]] -name = "multibase" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" -dependencies = [ - "base-x", - "base256emoji", - "data-encoding", - "data-encoding-macro", -] - -[[package]] -name = "multihash" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" -dependencies = [ - "core2", - "unsigned-varint", -] - [[package]] name = "multimap" version = "0.10.1" @@ -4773,39 +4178,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "notify" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" -dependencies = [ - "bitflags 2.11.0", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys 0.60.2", -] - -[[package]] -name = "notify-types" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4836,7 +4208,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", "smallvec", "zeroize", @@ -4844,9 +4216,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -4944,25 +4316,13 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -4990,18 +4350,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -5082,15 +4442,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" -dependencies = [ - "crypto-mac", -] - [[package]] name = "pbkdf2" version = "0.12.2" @@ -5098,17 +4449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", -] - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", + "hmac", ] [[package]] @@ -5134,7 +4475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -5191,43 +4532,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -5247,42 +4560,12 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - [[package]] name = "pretty_reqwest_error" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ - "reqwest 0.11.27", + "reqwest 0.12.28", "sensitive_url", ] @@ -5313,7 +4596,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.5+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -5364,15 +4647,15 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift 0.4.0", "regex-syntax", @@ -5433,20 +4716,6 @@ dependencies = [ "prost", ] -[[package]] -name = "proto_array" -version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" -dependencies = [ - "ethereum_ssz", - "ethereum_ssz_derive", - "safe_arith", - "serde", - "serde_yaml", - "superstruct", - "types", -] - [[package]] name = "protobuf" version = "3.7.2" @@ -5485,7 +4754,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.37", + "rustls", "socket2 0.6.3", "thiserror 2.0.18", "tokio", @@ -5499,13 +4768,14 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -5557,9 +4827,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -5569,9 +4839,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -5580,13 +4850,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -5630,9 +4900,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_xorshift" @@ -5663,9 +4933,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -5681,19 +4951,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - [[package]] name = "recvmsg" version = "1.0.0" @@ -5706,7 +4963,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -5760,85 +5017,81 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.21.7", + "base64", "bytes", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "hyper-tls 0.5.0", - "ipnet", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", "js-sys", "log", - "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", + "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", + "tower 0.5.3", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", - "webpki-roots 0.25.4", - "winreg", + "webpki-roots 1.0.7", ] [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", - "hyper-tls 0.6.0", + "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", - "tokio-native-tls", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-util", "tower 0.5.3", "tower-http", @@ -5846,16 +5099,15 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.5.0", "web-sys", - "webpki-roots 1.0.6", ] [[package]] name = "reqwest-eventsource" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f529a5ff327743addc322af460761dff5b50e0c826b9e6ac44c3195c50bb2026" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" dependencies = [ "eventsource-stream", "futures-core", @@ -5863,7 +5115,7 @@ dependencies = [ "mime", "nom", "pin-project-lite", - "reqwest 0.11.27", + "reqwest 0.12.28", "thiserror 1.0.69", ] @@ -5873,7 +5125,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -5912,9 +5164,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -5929,8 +5181,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -5944,20 +5196,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" -[[package]] -name = "rusqlite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" -dependencies = [ - "bitflags 1.3.2", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - [[package]] name = "rust_eth_kzg" version = "0.9.1" @@ -5983,9 +5221,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-hex" @@ -6008,7 +5246,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.28", ] [[package]] @@ -6017,7 +5255,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -6026,39 +5264,30 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki", "subtle", "zeroize", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls-native-certs" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "base64 0.21.7", + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -6081,20 +5310,37 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "ring", - "untrusted", + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -6134,11 +5380,11 @@ checksum = "b147bb6111014916d3ef9d4c85173124a8e12193a67f6176d67244afd558d6c1" [[package]] name = "salsa20" -version = "0.8.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher 0.3.0", + "cipher", ] [[package]] @@ -6191,24 +5437,13 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scrypt" -version = "0.7.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", + "pbkdf2", "salsa20", - "sha2 0.9.9", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", + "sha2", ] [[package]] @@ -6233,7 +5468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys", "serde", ] @@ -6253,7 +5488,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6281,9 +5516,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -6303,7 +5538,8 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "sensitive_url" version = "0.1.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b0221fa9905eec4163dbf7660b1876cc95663af1deddc3e19ebe49167c58c" dependencies = [ "serde", "url", @@ -6410,11 +5646,11 @@ version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -6441,7 +5677,7 @@ version = "0.9.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -6469,19 +5705,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.9" @@ -6495,9 +5718,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -6505,9 +5728,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -6607,12 +5830,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b55bedc9a18ed2860a46d6beb4f4082416ee1d60be0cc364cebdcdddc7afd4" dependencies = [ "ethereum_serde_utils 0.8.0", - "ethereum_ssz", + "ethereum_ssz 0.9.1", "itertools 0.13.0", "serde", "serde_derive", "smallvec", - "tree_hash", + "tree_hash 0.10.0", + "typenum", +] + +[[package]] +name = "ssz_types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d625e4de8e0057eefe7e0b1510ba1dd7adf10cd375fad6cc7fcceac7c39623c9" +dependencies = [ + "context_deserialize", + "educe", + "ethereum_serde_utils 0.8.0", + "ethereum_ssz 0.10.3", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "tree_hash 0.12.1", "typenum", ] @@ -6630,16 +5871,16 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "status_api" -version = "0.9.3" +version = "0.9.4" dependencies = [ "async-trait", - "axum 0.8.8", + "axum 0.8.9", "color-eyre", "commit-boost", "eyre", "lazy_static", "prometheus", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "tokio", "tracing", @@ -6695,13 +5936,19 @@ dependencies = [ [[package]] name = "swap_or_not_shuffle" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "alloy-primitives 1.5.7", - "ethereum_hashing", + "ethereum_hashing 0.8.0", "fixed_bytes", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -6736,12 +5983,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -6762,36 +6003,15 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -6823,19 +6043,13 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - [[package]] name = "test_random_derive" version = "0.2.0" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -6958,24 +6172,14 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.11.0" @@ -6993,9 +6197,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -7010,9 +6214,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -7029,23 +6233,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls", "tokio", ] @@ -7063,16 +6257,16 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tungstenite", "webpki-roots 0.26.11", ] @@ -7113,9 +6307,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -7126,7 +6320,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -7136,23 +6330,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.1+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.2", ] [[package]] @@ -7170,22 +6364,22 @@ dependencies = [ "async-stream", "async-trait", "axum 0.7.9", - "base64 0.22.1", + "base64", "bytes", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "prost", - "rustls-pemfile 2.2.0", + "rustls-pemfile", "socket2 0.5.10", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-stream", "tower 0.4.13", "tower-layer", @@ -7218,7 +6412,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand 0.8.6", "slab", "tokio", "tokio-util", @@ -7236,7 +6430,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -7249,11 +6443,11 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "iri-string", "pin-project-lite", "tower 0.5.3", @@ -7288,11 +6482,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber", @@ -7399,17 +6594,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" dependencies = [ "alloy-primitives 1.5.7", - "ethereum_hashing", - "ethereum_ssz", + "ethereum_hashing 0.7.0", + "ethereum_ssz 0.9.1", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +dependencies = [ + "alloy-primitives 1.5.7", + "ethereum_hashing 0.8.0", + "ethereum_ssz 0.10.3", "smallvec", "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699e7fb6b3fdfe0c809916f251cf5132d64966858601695c3736630a87e7166a" +checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" dependencies = [ "darling 0.20.11", "proc-macro2", @@ -7419,11 +6627,11 @@ dependencies = [ [[package]] name = "tree_hash_derive" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -7447,17 +6655,17 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", - "rand 0.9.2", - "rustls 0.23.37", + "rand 0.9.4", + "rustls", "rustls-pki-types", "sha1", "thiserror 2.0.18", @@ -7466,27 +6674,26 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "types" version = "0.2.1" -source = "git+https://github.com/sigp/lighthouse?tag=v8.0.0#e3ee7febce64c1b5a85c3ab0be0619571ee92d58" +source = "git+https://github.com/sigp/lighthouse?tag=v8.1.3#176cce585c1ba979a6210ed79b6b6528596cdb8c" dependencies = [ "alloy-primitives 1.5.7", "alloy-rlp", "bls", "compare_fields", - "compare_fields_derive", "context_deserialize", - "derivative", + "educe", "eth2_interop_keypairs", - "ethereum_hashing", + "ethereum_hashing 0.8.0", "ethereum_serde_utils 0.8.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "fixed_bytes", "hex", "int_to_bytes", @@ -7497,25 +6704,25 @@ dependencies = [ "metastruct", "milhouse", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "rand_xorshift 0.4.0", "rayon", "regex", "rpds", - "rusqlite", "safe_arith", "serde", "serde_json", "serde_yaml", "smallvec", - "ssz_types", + "ssz_types 0.14.1", "superstruct", "swap_or_not_shuffle", "tempfile", "test_random_derive", "tracing", - "tree_hash", - "tree_hash_derive 0.10.0", + "tree_hash 0.12.1", + "tree_hash_derive 0.12.1", + "typenum", ] [[package]] @@ -7538,12 +6745,9 @@ dependencies = [ [[package]] name = "uint-zigzag" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abbf77aed65cb885a8ba07138c365879be3d9a93dce82bf6cc50feca9138ec15" -dependencies = [ - "core2", -] +checksum = "61faa33dc26b2851a37da5390a1a4cac015887b1e97ecd77ce7b4f987431de9f" [[package]] name = "unarray" @@ -7568,9 +6772,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -7590,12 +6794,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "unsigned-varint" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" - [[package]] name = "untrusted" version = "0.9.0" @@ -7645,13 +6843,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", - "rand 0.10.0", + "rand 0.10.1", "serde_core", "wasm-bindgen", ] @@ -7732,11 +6930,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -7745,14 +6943,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -7763,23 +6961,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7787,9 +6981,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -7800,9 +6994,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -7824,7 +7018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -7842,16 +7036,29 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -7870,9 +7077,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -7889,10 +7096,13 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.25.4" +name = "webpki-root-certs" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "webpki-roots" @@ -7900,14 +7110,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -8021,11 +7231,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -8057,17 +7267,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -8105,9 +7315,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -8123,9 +7333,9 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -8141,9 +7351,9 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -8171,9 +7381,9 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -8189,9 +7399,9 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -8207,9 +7417,9 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -8225,9 +7435,9 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -8252,23 +7462,13 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -8278,6 +7478,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -8297,7 +7503,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -8327,8 +7533,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -8347,9 +7553,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -8359,9 +7565,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "ws_stream_wasm" @@ -8391,20 +7597,11 @@ dependencies = [ "tap", ] -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8413,9 +7610,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -8425,18 +7622,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -8445,18 +7642,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -8487,9 +7684,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -8498,9 +7695,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -8509,9 +7706,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 99621100..59c3722a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [workspace] members = ["benches/*", "bin", "crates/*", "examples/da_commit", "examples/status_api", "tests"] +exclude = ["benches/microbench"] resolver = "2" [workspace.package] edition = "2024" rust-version = "1.91" -version = "0.9.3" +version = "0.9.4" [workspace.dependencies] aes = "0.8" @@ -19,17 +20,14 @@ alloy = { version = "^1.0.35", features = [ "ssz", ] } alloy-primitives = "^1.3.1" -assert_cmd = "2.1.2" async-trait = "0.1.80" axum = { version = "0.8.1", features = ["macros"] } axum-extra = { version = "0.10.0", features = ["typed-header"] } -axum-server = { version = "0.7.2", features = ["tls-rustls"] } base64 = "0.22.1" bimap = { version = "0.6.3", features = ["serde"] } blsful = "^2.5" blst = "^0.3.15" bytes = "1.10.1" -criterion = { version = "0.5", features = ["html_reports"] } cb-cli = { path = "crates/cli" } cb-common = { path = "crates/common" } cb-metrics = { path = "crates/metrics" } @@ -38,35 +36,30 @@ cb-signer = { path = "crates/signer" } cipher = "0.4" clap = { version = "4.5.48", features = ["derive", "env"] } color-eyre = "0.6.3" -const_format = "0.2.34" ctr = "0.9.2" derive_more = { version = "2.0.1", features = ["deref", "display", "from", "into"] } docker-compose-types = "0.16.0" docker-image = "0.2.1" ethereum_serde_utils = "0.7.0" -ethereum_ssz = "0.9" -ethereum_ssz_derive = "0.9" +ethereum_ssz = "0.10" +ethereum_ssz_derive = "0.10" eyre = "0.6.12" futures = "0.3.30" headers = "0.4.0" indexmap = "2.2.6" jsonwebtoken = { version = "9.3.1", default-features = false } lazy_static = "1.5.0" -lh_eth2 = { package = "eth2", git = "https://github.com/sigp/lighthouse", tag = "v8.0.0" } -lh_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", tag = "v8.0.0" } -lh_types = { package = "types", git = "https://github.com/sigp/lighthouse", tag = "v8.0.0" } -notify = "8.2.0" +lh_eth2 = { package = "eth2", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3", features = ["events"] } +lh_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" } +lh_bls = { package = "bls", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" } +lh_types = { package = "types", git = "https://github.com/sigp/lighthouse", tag = "v8.1.3" } parking_lot = "0.12.3" pbkdf2 = "0.12.2" -predicates = "3.0.3" prometheus = "0.14.0" prost = "0.13.4" rand = { version = "0.9", features = ["os_rng"] } rayon = "1.10.0" -rcgen = "0.13.2" -reqwest = { version = "0.12.4", features = ["json", "rustls-tls", "stream"] } -reqwest-eventsource = "=0.5.0" -rustls = "0.23.23" +reqwest = { version = "0.13", features = ["json", "stream"] } serde = { version = "1.0.202", features = ["derive"] } serde_json = "1.0.117" serde_yaml = "0.9.33" @@ -84,12 +77,12 @@ tracing = "0.1.40" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } tracing-test = { version = "0.2.5", features = ["no-env-filter"] } -tree_hash = "^0.10" -tree_hash_derive = "0.9" +tree_hash = "0.12" +tree_hash_derive = "0.12" typenum = "1.17.0" unicode-normalization = "0.1.24" url = { version = "2.5.0", features = ["serde"] } uuid = { version = "1.8.0", features = ["fast-rng", "serde", "v4"] } [patch.crates-io] -blstrs_plus = { git = "https://github.com/Commit-Boost/blstrs" } \ No newline at end of file +blstrs_plus = { git = "https://github.com/Commit-Boost/blstrs" } diff --git a/README.md b/README.md index 3911db99..c28c28b5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Ci](https://github.com/Commit-Boost/commit-boost-client/actions/workflows/ci.yml/badge.svg)](https://github.com/Commit-Boost/commit-boost-client/actions/workflows/ci.yml) [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://commit-boost.github.io/commit-boost-client/) [![Release](https://img.shields.io/github/v/release/Commit-Boost/commit-boost-client)](https://github.com/Commit-Boost/commit-boost-client/releases) +[![Chat](https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2F%2BPcs9bykxK3BiMzk5)](https://t.me/+Pcs9bykxK3BiMzk5) [![X](https://img.shields.io/twitter/follow/Commit_Boost)](https://x.com/Commit_Boost) A new Ethereum validator sidecar focused on standardizing the last mile of communication between validators and third-party protocols. diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 4176edfb..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,73 +0,0 @@ -# Releasing a new version of Commit-Boost - -## Process - -1. Cut a release candidate (RC) -2. Test the RC -3. Collect signoffs -4. Cut the full release - -## How it works - -Releases are fully automated once a release PR is merged into `main`. The branch name controls what CI does: - -| Branch name | Result | -| --- | --- | -| `release/vX.Y.Z-rcQ` | Creates RC tag, fast-forwards `beta`, builds and signs artifacts | -| `release/vX.Y.Z` | Creates release tag, fast-forwards `stable`, builds and signs artifacts | - -No human pushes tags or updates `stable`/`beta` directly, the CI handles everything after the PR merges. - -## Cutting a release candidate - -1. Create a branch named `release/vX.Y.Z-rc1`. For the first RC of a new version, bump the version in `Cargo.toml` and run `cargo check` to update `Cargo.lock`. Always update `CHANGELOG.md`. -2. Open a PR targeting `main`. Get two approvals and merge. -3. CI creates the tag, fast-forwards `beta`, builds and signs binaries, Docker images, and creates a draft release on GitHub. -4. Test the RC on testnets. For subsequent RCs (`-rc2`, etc.), open a new release PR with only a `CHANGELOG.md` update (`Cargo.toml` does not change between RCs). - -## Cutting the full release - -Once testing is complete and signoffs are collected: - -1. Create a branch named `release/vX.Y.Z` and update `CHANGELOG.md` with final release notes. -2. Open a PR targeting `main`. Get two approvals and merge. -3. CI creates the tag, fast-forwards `stable`, builds and signs artifacts, and creates the release. -4. Verify the [binary was correctly signed](#verifying-release-artifacts). -5. Update the community. - -## If the pipeline fails - -CI will automatically delete the tag if any build step fails. `stable` and `beta` are only updated after all artifacts are successfully built, they are never touched on a failed run. Fix the issue and open a new release PR. - -## Verifying release artifacts - -All binaries are signed using [Sigstore cosign](https://docs.sigstore.dev/about/overview/). You can verify any binary was built by the official Commit-Boost CI pipeline from this release's commit. - -Install cosign: https://docs.sigstore.dev/cosign/system_config/installation/ - -```bash -# Set the release version and your target architecture -# Architecture options: darwin_arm64, linux_arm64, linux_x86-64 -export VERSION=vX.Y.Z -export ARCH=linux_x86-64 - -# Download the binary tarball and its signature -curl -L \ - -o "commit-boost-$VERSION-$ARCH.tar.gz" \ - "https://github.com/Commit-Boost/commit-boost-client/releases/download/$VERSION/commit-boost-$VERSION-$ARCH.tar.gz" - -curl -L \ - -o "commit-boost-$VERSION-$ARCH.tar.gz.sigstore.json" \ - "https://github.com/Commit-Boost/commit-boost-client/releases/download/$VERSION/commit-boost-$VERSION-$ARCH.tar.gz.sigstore.json" - -# Verify the binary was signed by the official CI pipeline -cosign verify-blob \ - "commit-boost-$VERSION-$ARCH.tar.gz" \ - --bundle "commit-boost-$VERSION-$ARCH.tar.gz.sigstore.json" \ - --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ - --certificate-identity="https://github.com/Commit-Boost/commit-boost-client/.github/workflows/release.yml@refs/tags/$VERSION" -``` - -A successful verification prints `Verified OK`. If the binary was modified after being built by CI, this command will fail. - -The `.sigstore.json` bundle for each binary is attached to this release alongside the binary itself. diff --git a/api/signer-api.yml b/api/signer-api.yml index be44f8fd..c876a3a2 100644 --- a/api/signer-api.yml +++ b/api/signer-api.yml @@ -1,7 +1,7 @@ -openapi: "3.1.1" +openapi: "3.0.2" info: title: Signer API - version: "0.2.0" + version: "0.1.0" description: API that allows commit modules to request generic signatures from validators tags: - name: Signer @@ -10,13 +10,6 @@ paths: /signer/v1/get_pubkeys: get: summary: Get a list of public keys for which signatures may be requested - description: > - This endpoint requires a valid JWT Bearer token. - - The token **must include** the following claims: - - `exp` (integer): Expiration timestamp - - `route` (string): The route being requested (must be `/signer/v1/get_pubkeys` for this endpoint). - - `module` (string): The ID of the module making the request, which must match a module ID in the Commit-Boost configuration file. tags: - Signer security: @@ -65,17 +58,9 @@ paths: type: string example: "Internal error" - /signer/v1/request_signature/bls: + /signer/v1/request_signature: post: - summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the BLS private key for the requested public key. - description: > - This endpoint requires a valid JWT Bearer token. - - The token **must include** the following claims: - - `exp` (integer): Expiration timestamp - - `module` (string): The ID of the module making the request, which must match a module ID in the Commit-Boost configuration file. - - `route` (string): The route being requested (must be `/signer/v1/request_signature/bls` for this endpoint). - - `payload_hash` (string): The Keccak-256 hash of the JSON-encoded request body, with optional `0x` prefix. This is required to prevent JWT replay attacks. + summary: Send a signature request tags: - Signer security: @@ -86,366 +71,63 @@ paths: application/json: schema: type: object - required: [pubkey, object_root, nonce] + required: [type, object_root] + oneOf: + - required: [pubkey] + - required: [proxy] properties: + type: + description: Type of the sign request + type: string + enum: [consensus, proxy_bls, proxy_ecdsa] pubkey: - description: The 48-byte BLS public key, with optional `0x` prefix, of the proposer key that you want to request a signature from. + description: Public key of the validator for consensus signatures $ref: "#/components/schemas/BlsPubkey" - object_root: - description: The 32-byte data you want to sign, with optional `0x` prefix. - $ref: "#/components/schemas/B256" - nonce: - $ref: "#/components/schemas/Nonce" - example: - pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - responses: - "200": - description: A successful signature response. - content: - application/json: - schema: - $ref: "#/components/schemas/BlsSignatureResponse" - example: - pubkey: "0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4" - object_root: "0x0123456789012345678901234567890123456789012345678901234567890123" - module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" - signature: "0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115" - "400": - description: | - This can occur in several scenarios: - - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. - - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. - - Something went wrong while preparing your request; the error text will provide more information. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 400 - message: - type: string - example: "Bad request: Invalid pubkey format" - "401": - description: The requesting module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to the module. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 401 - message: - type: string - example: "Unauthorized" - - "404": - description: You either requested a route that doesn't exist, or you requested a signature from a key that does not exist. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 404 - message: - type: string - example: "Unknown pubkey" - "429": - description: Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 429 - message: - type: string - example: "Too many requests" - "500": - description: Your request was valid, but something went wrong internally that prevented it from being fulfilled. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 500 - message: - type: string - example: "Internal error" - "502": - description: The signer service is running in Dirk signer mode, but Dirk could not be reached. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 502 - message: - type: string - example: "Bad gateway: Dirk signer service is unreachable" - - /signer/v1/request_signature/proxy-bls: - post: - summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the BLS private key for the requested proxy public key. - description: > - This endpoint requires a valid JWT Bearer token. - - The token **must include** the following claims: - - `exp` (integer): Expiration timestamp - - `module` (string): The ID of the module making the request, which must match a module ID in the Commit-Boost configuration file. - - `route` (string): The route being requested (must be `/signer/v1/request_signature/proxy-bls` for this endpoint). - - `payload_hash` (string): The Keccak-256 hash of the JSON-encoded request body, with optional `0x` prefix. This is required to prevent JWT replay attacks. - tags: - - Signer - security: - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [proxy, object_root, nonce] - properties: - proxy: - description: The 48-byte BLS public key (for `proxy_bls` mode) or the 20-byte Ethereum address (for `proxy_ecdsa` mode), with optional `0x` prefix, of the proxy key that you want to request a signature from. - $ref: "#/components/schemas/BlsPubkey" - object_root: - description: The 32-byte data you want to sign, with optional `0x` prefix. - $ref: "#/components/schemas/B256" - nonce: - $ref: "#/components/schemas/Nonce" - example: - pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - responses: - "200": - description: A successful signature response. - content: - application/json: - schema: - $ref: "#/components/schemas/BlsSignatureResponse" - example: - pubkey: "0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4" - object_root: "0x0123456789012345678901234567890123456789012345678901234567890123" - module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" - signature: "0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115" - "400": - description: | - This can occur in several scenarios: - - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. - - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. - - Something went wrong while preparing your request; the error text will provide more information. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 400 - message: - type: string - example: "Bad request: Invalid pubkey format" - "401": - description: The requesting module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to the module. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 401 - message: - type: string - example: "Unauthorized" - - "404": - description: You either requested a route that doesn't exist, or you requested a signature from a key that does not exist. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 404 - message: - type: string - example: "Unknown pubkey" - "429": - description: Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 429 - message: - type: string - example: "Too many requests" - "500": - description: Your request was valid, but something went wrong internally that prevented it from being fulfilled. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 500 - message: - type: string - example: "Internal error" - "502": - description: The signer service is running in Dirk signer mode, but Dirk could not be reached. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 502 - message: - type: string - example: "Bad gateway: Dirk signer service is unreachable" - - /signer/v1/request_signature/proxy-ecdsa: - post: - summary: Request a signature for a 32-byte blob of data (typically a hash), signed by the ECDSA private key for the requested proxy Ethereum address. - description: > - This endpoint requires a valid JWT Bearer token. - - The token **must include** the following claims: - - `exp` (integer): Expiration timestamp - - `module` (string): The ID of the module making the request, which must match a module ID in the Commit-Boost configuration file. - - `route` (string): The route being requested (must be `/signer/v1/request_signature/proxy-ecdsa` for this endpoint). - - `payload_hash` (string): The Keccak-256 hash of the JSON-encoded request body, with optional `0x` prefix. This is required to prevent JWT replay attacks. - tags: - - Signer - security: - - BearerAuth: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [proxy, object_root, nonce] - properties: proxy: - description: The 20-byte Ethereum address, with optional `0x` prefix, of the proxy key that you want to request a signature from. - $ref: "#/components/schemas/EcdsaAddress" + description: BLS proxy pubkey or ECDSA address for proxy signatures + oneOf: + - $ref: "#/components/schemas/BlsPubkey" + - $ref: "#/components/schemas/EcdsaAddress" object_root: - description: The 32-byte data you want to sign, with optional `0x` prefix. - $ref: "#/components/schemas/B256" - nonce: - $ref: "#/components/schemas/Nonce" - example: - proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + description: The root of the object to be signed + type: string + format: hex + pattern: "^0x[a-fA-F0-9]{64}$" + example: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + examples: + Consensus: + value: + type: "consensus" + pubkey: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + ProxyBls: + value: + type: "proxy_bls" + proxy: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" + ProxyEcdsa: + value: + type: "proxy_ecdsa" + proxy: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" + object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" responses: "200": - description: A successful signature response. - content: - application/json: - schema: - $ref: "#/components/schemas/EcdsaSignatureResponse" - example: - address: "0x71f65e9f6336770e22d148bd5e89b391a1c3b0bb" - object_root: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" - module_signing_id: "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" - signature: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" - "400": - description: | - This can occur in several scenarios: - - The Commit-Boost configuration file does not contain a signing ID for the module that made the request. - - You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation. - - Something went wrong while preparing your request; the error text will provide more information. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 400 - message: - type: string - example: "Bad request: Invalid pubkey format" - "401": - description: The requesting module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to the module. + description: Success content: application/json: schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 401 - message: - type: string - example: "Unauthorized" - + oneOf: + - $ref: "#/components/schemas/BlsSignature" + - $ref: "#/components/schemas/EcdsaSignature" + examples: + Consensus: + value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + ProxyBls: + value: "0xa3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989a3ffa9241f78279f1af04644cb8c79c2d8f02bcf0e28e2f186f6dcccac0a869c2be441fda50f0dea895cfce2e53f0989" + ProxyEcdsa: + value: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" "404": - description: You either requested a route that doesn't exist, or you requested a signature from a key that does not exist. + description: Unknown value (pubkey, etc.) content: application/json: schema: @@ -460,24 +142,8 @@ paths: message: type: string example: "Unknown pubkey" - "429": - description: Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 429 - message: - type: string - example: "Too many requests" "500": - description: Your request was valid, but something went wrong internally that prevented it from being fulfilled. + description: Internal error content: application/json: schema: @@ -492,34 +158,10 @@ paths: message: type: string example: "Internal error" - "502": - description: The signer service is running in Dirk signer mode, but Dirk could not be reached. - content: - application/json: - schema: - type: object - required: - - code - - message - properties: - code: - type: number - example: 502 - message: - type: string - example: "Bad gateway: Dirk signer service is unreachable" /signer/v1/generate_proxy_key: post: summary: Request a proxy key be generated for a specific consensus pubkey - description: > - This endpoint requires a valid JWT Bearer token. - - The token **must include** the following claims: - - `exp` (integer): Expiration timestamp - - `module` (string): The ID of the module making the request, which must match a module ID in the Commit-Boost configuration file. - - `route` (string): The route being requested (must be `/signer/v1/generate_proxy_key` for this endpoint). - - `payload_hash` (string): The Keccak-256 hash of the JSON-encoded request body, with optional `0x` prefix. This is required to prevent JWT replay attacks. tags: - Signer security: @@ -619,6 +261,20 @@ paths: type: string example: "Internal error" + /status: + get: + summary: Get the status of the Signer API module + tags: + - Management + responses: + "200": + description: Success + content: + text/plain: + schema: + type: string + example: "OK" + components: securitySchemes: BearerAuth: @@ -626,11 +282,6 @@ components: scheme: bearer bearerFormat: JWT schemas: - B256: - type: string - format: hex - pattern: "^0x[a-fA-F0-9]{64}$" - example: "0x3e9f4a78b5c21d64f0b8e3d9a7f5c02b4d1e67a3c8f29b5d6e4a3b1c8f72e6d9" BlsPubkey: type: string format: hex @@ -651,56 +302,3 @@ components: format: hex pattern: "^0x[a-fA-F0-9]{130}$" example: "0x985b495f49d1b96db3bba3f6c5dd1810950317c10d4c2042bd316f338cdbe74359072e209b85e56ac492092d7860063dd096ca31b4e164ef27e3f8d508e656801c" - BlsSignatureResponse: - type: object - properties: - pubkey: - description: The BLS public key corresponding to the private key that was used to sign the request - $ref: "#/components/schemas/BlsPubkey" - object_root: - description: The 32-byte data that was signed, with `0x` prefix - $ref: "#/components/schemas/B256" - module_signing_id: - description: The signing ID of the module that requested the signature, as specified in the Commit-Boost configuration - $ref: "#/components/schemas/B256" - nonce: - $ref: "#/components/schemas/Nonce" - chain_id: - description: The chain ID that the signature is valid for, as specified in the Commit-Boost configuration - type: integer - example: 1 - signature: - description: The BLS signature of the Merkle root hash of the provided `object_root` field and the requesting module's Signing ID. For details on this signature, see the [signature structure documentation](https://commit-boost.github.io/commit-boost-client/developing/prop-commit-signing.md#structure-of-a-signature). - $ref: "#/components/schemas/BlsSignature" - EcdsaSignatureResponse: - type: object - properties: - address: - description: The ECDSA address corresponding to the private key that was used to sign the request - $ref: "#/components/schemas/EcdsaAddress" - object_root: - description: The 32-byte data that was signed, with `0x` prefix - $ref: "#/components/schemas/B256" - module_signing_id: - description: The signing ID of the module that requested the signature, as specified in the Commit-Boost configuration - $ref: "#/components/schemas/B256" - nonce: - $ref: "#/components/schemas/Nonce" - chain_id: - description: The chain ID that the signature is valid for, as specified in the Commit-Boost configuration - type: integer - example: 1 - signature: - description: The ECDSA signature (in Ethereum RSV format) of the Merkle root hash of the provided `object_root` field and the requesting module's Signing ID. For details on this signature, see the [signature structure documentation](https://commit-boost.github.io/commit-boost-client/developing/prop-commit-signing.md#structure-of-a-signature). - $ref: "#/components/schemas/EcdsaSignature" - Nonce: - type: integer - description: | - Replay-protection nonce, always mixed into the signing root via `PropCommitSigningInfo`. It - must be an unsigned 64-bit integer between 0 and 2^64-2 (18446744073709551614), inclusive. - - Modules that track nonces for replay protection should use a monotonically increasing value - per key. Modules that do not use replay protection should always send `0`. - minimum: 0 - maximum: 18446744073709551614 - example: 1 diff --git a/benches/microbench/Cargo.toml b/benches/microbench/Cargo.toml deleted file mode 100644 index 185b51ee..00000000 --- a/benches/microbench/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -edition.workspace = true -name = "cb-bench-micro" -rust-version.workspace = true -version.workspace = true - -[dependencies] -alloy.workspace = true -axum.workspace = true -cb-common.workspace = true -cb-pbs.workspace = true -cb-tests = { path = "../../tests" } -criterion.workspace = true -tokio.workspace = true - -[[bench]] -name = "get_header" -harness = false -path = "src/get_header.rs" diff --git a/benches/microbench/src/get_header.rs b/benches/microbench/src/get_header.rs deleted file mode 100644 index 44eff329..00000000 --- a/benches/microbench/src/get_header.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Criterion benchmarks for the `get_header` PBS flow. -//! -//! # What this measures -//! -//! The full `get_header` pipeline end-to-end: HTTP fan-out to N in-process mock -//! relays, response parsing, header validation, signature verification, and bid -//! selection. This is wall-clock timing — useful for local development feedback -//! and catching latency regressions across relay counts. -//! -//! Criterion runs each benchmark hundreds of times, applies statistical -//! analysis, and reports mean ± standard deviation. Results are saved to -//! `target/criterion/` as HTML reports (open `report/index.html`). -//! -//! # Running -//! -//! ```bash -//! # Run all benchmarks -//! cargo bench --package cb-bench-micro -//! -//! # Run a specific variant by filter -//! cargo bench --package cb-bench-micro -- 3_relays -//! -//! # Save a named baseline to compare against later -//! cargo bench --package cb-bench-micro -- --save-baseline main -//! -//! # Compare against a saved baseline -//! cargo bench --package cb-bench-micro -- --load-baseline main --save-baseline current -//! ``` -//! -//! # What is NOT measured -//! -//! - PBS HTTP server overhead (we call `get_header()` directly, bypassing axum -//! routing) -//! - Mock relay startup time (servers are started once in setup, before timing -//! begins) -//! - `HeaderMap` allocation (created once in setup, cloned cheaply per -//! iteration) - -use std::{path::PathBuf, sync::Arc, time::Duration}; - -use alloy::primitives::B256; -use axum::http::HeaderMap; -use cb_common::{pbs::GetHeaderParams, signer::random_secret, types::Chain}; -use cb_pbs::{PbsState, get_header}; -use cb_tests::{ - mock_relay::{MockRelayState, start_mock_relay_service}, - utils::{generate_mock_relay, get_pbs_static_config, to_pbs_config}, -}; -use criterion::{Criterion, black_box, criterion_group, criterion_main}; - -// Ports 19201–19205 are reserved for the microbenchmark mock relays. -const BASE_PORT: u16 = 19200; -const CHAIN: Chain = Chain::Hoodi; -const MAX_RELAYS: usize = 5; -const RELAY_COUNTS: [usize; 3] = [1, 3, MAX_RELAYS]; - -/// Benchmarks `get_header` across three relay-count variants. -/// -/// # Setup (runs once, not measured) -/// -/// All MAX_RELAYS mock relays are started up-front and shared across variants. -/// Each variant gets its own `PbsState` pointing to a different relay subset. -/// The mock relays are in-process axum servers on localhost. -/// -/// # Per-iteration (measured) -/// -/// Each call to `b.iter(|| ...)` runs `get_header()` once: -/// - Fans out HTTP requests to N mock relays concurrently -/// - Parses and validates each relay response (header data + BLS signature) -/// - Selects the highest-value bid -/// -/// `black_box(...)` prevents the compiler from optimizing away inputs or the -/// return value. Without it, the optimizer could see that the result is unused -/// and eliminate the call entirely, producing a meaningless zero measurement. -fn bench_get_header(c: &mut Criterion) { - let rt = tokio::runtime::Runtime::new().expect("tokio runtime"); - - // Start all mock relays once and build one PbsState per relay-count variant. - // All relays share the same MockRelayState (and therefore the same signing - // key). - let (states, params) = rt.block_on(async { - let signer = random_secret(); - let pubkey = signer.public_key(); - let mock_state = Arc::new(MockRelayState::new(CHAIN, signer)); - - let relay_clients: Vec<_> = (0..MAX_RELAYS) - .map(|i| { - let port = BASE_PORT + 1 + i as u16; - tokio::spawn(start_mock_relay_service(mock_state.clone(), port)); - generate_mock_relay(port, pubkey.clone()).expect("relay client") - }) - .collect(); - - // Give all servers time to bind before benchmarking starts. - tokio::time::sleep(Duration::from_millis(200)).await; - - let params = GetHeaderParams { slot: 0, parent_hash: B256::ZERO, pubkey }; - - // Port 0 here is the port the PBS service itself would bind to for incoming - // validator requests. We call get_header() as a function directly, so no - // PBS server is started and this port is never used. The actual relay - // endpoints are carried inside the RelayClient objects (ports 19201–19205). - let states: Vec = RELAY_COUNTS - .iter() - .map(|&n| { - let config = - to_pbs_config(CHAIN, get_pbs_static_config(0), relay_clients[..n].to_vec()); - PbsState::new(config, PathBuf::new()) - }) - .collect(); - - (states, params) - }); - - // Empty HeaderMap matches what the PBS route handler receives for requests - // without custom headers. Created once here to avoid measuring its - // allocation per iteration. - let headers = HeaderMap::new(); - - // A BenchmarkGroup groups related functions so Criterion produces a single - // comparison table and chart. All variants share the name "get_header/". - let mut group = c.benchmark_group("get_header"); - - for (i, relay_count) in RELAY_COUNTS.iter().enumerate() { - let state = states[i].clone(); - let params = params.clone(); - let headers = headers.clone(); - - // bench_function registers one timing function. The closure receives a - // `Bencher` — calling `b.iter(|| ...)` is the measured hot loop. - // Everything outside `b.iter` is setup and not timed. - group.bench_function(format!("{relay_count}_relays"), |b| { - b.iter(|| { - // block_on drives the async future to completion on the shared - // runtime. get_header takes owned args, so we clone cheap types - // (Arc-backed state, stack-sized params) on each iteration. - rt.block_on(get_header( - black_box(params.clone()), - black_box(headers.clone()), - black_box(state.clone()), - )) - .expect("get_header failed") - }) - }); - } - - group.finish(); -} - -// criterion_group! registers bench_get_header as a benchmark group named -// "benches". criterion_main! generates the main() entry point that Criterion -// uses to run them. -criterion_group!(benches, bench_get_header); -criterion_main!(benches); diff --git a/bin/Cargo.toml b/bin/Cargo.toml index e7a25091..d50c2932 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -18,12 +18,14 @@ tracing.workspace = true tree_hash.workspace = true tree_hash_derive.workspace = true -[dev-dependencies] -assert_cmd.workspace = true -predicates.workspace = true -serde_yaml.workspace = true -tempfile.workspace = true +[[bin]] +name = "commit-boost-cli" +path = "cli.rs" [[bin]] -name = "commit-boost" -path = "commit-boost.rs" +name = "commit-boost-pbs" +path = "pbs.rs" + +[[bin]] +name = "commit-boost-signer" +path = "signer.rs" diff --git a/bin/cli.rs b/bin/cli.rs new file mode 100644 index 00000000..234dc9bd --- /dev/null +++ b/bin/cli.rs @@ -0,0 +1,24 @@ +use clap::Parser; + +/// Version string with a leading 'v' +const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); + +/// Subcommands and global arguments for the module +#[derive(Parser, Debug)] +#[command(name = "Commit-Boost CLI", version = VERSION, about, long_about = None)] +struct Cli {} + +/// Main entry point of the Commit-Boost CLI +#[tokio::main] +async fn main() -> eyre::Result<()> { + // Parse the CLI arguments (currently only used for version info, more can be + // added later) + let _cli = Cli::parse(); + + color_eyre::install()?; + // set default backtrace unless provided + + let args = cb_cli::Args::parse(); + + args.run().await +} diff --git a/bin/commit-boost.rs b/bin/commit-boost.rs deleted file mode 100644 index e424d144..00000000 --- a/bin/commit-boost.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::path::PathBuf; - -use cb_cli::docker_init::handle_docker_init; -use cb_common::{ - config::{ - LogsSettings, PBS_SERVICE_NAME, SIGNER_SERVICE_NAME, StartSignerConfig, load_pbs_config, - }, - utils::{initialize_tracing_log, print_logo, wait_for_signal}, -}; -use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; -use cb_signer::service::SigningService; -use clap::{Parser, Subcommand}; -use eyre::Result; -use tracing::{error, info}; - -/// Long about string for the CLI -const LONG_ABOUT: &str = "Commit-Boost allows Ethereum validators to safely run MEV-Boost and community-built commitment protocols"; - -/// Subcommands and global arguments for the module -#[derive(Parser, Debug)] -#[command(name = "Commit-Boost", version = commit_boost::VERSION, about, long_about = LONG_ABOUT)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand, Debug)] -enum Commands { - /// Run the PBS service - Pbs, - - /// Run the Signer service - Signer, - - /// Generate the starting docker-compose files and environment files - Init { - /// Path to config file - #[arg(long("config"))] - config_path: PathBuf, - - /// Path to output files - #[arg(short, long("output"), default_value = "./")] - output_path: PathBuf, - }, -} - -#[tokio::main] -async fn main() -> Result<()> { - // Parse the CLI arguments (currently only used for version info, more can be - // added later) - let cli = Cli::parse(); - - color_eyre::install()?; - - match cli.command { - Commands::Pbs => run_pbs_service().await?, - Commands::Signer => run_signer_service().await?, - Commands::Init { config_path, output_path } => run_init(config_path, output_path).await?, - } - - Ok(()) -} - -/// Run the PBS service -async fn run_pbs_service() -> Result<()> { - let _guard = initialize_tracing_log(PBS_SERVICE_NAME, LogsSettings::from_env_config()?); - let (pbs_config, config_path) = load_pbs_config(None).await?; - - PbsService::init_metrics(pbs_config.chain)?; - let state = PbsState::new(pbs_config, config_path); - let server = PbsService::run::<_, DefaultBuilderApi>(state); - - tokio::select! { - maybe_err = server => { - if let Err(err) = maybe_err { - error!(%err, "PBS service unexpectedly stopped"); - } - }, - _ = wait_for_signal() => { - info!("shutting down"); - } - } - Ok(()) -} - -/// Run the Signer service -async fn run_signer_service() -> Result<()> { - let _guard = initialize_tracing_log(SIGNER_SERVICE_NAME, LogsSettings::from_env_config()?); - let config = StartSignerConfig::load_from_env()?; - let server = SigningService::run(config); - - tokio::select! { - maybe_err = server => { - if let Err(err) = maybe_err { - error!(%err, "signing server unexpectedly stopped"); - } - }, - _ = wait_for_signal() => { - info!("shutting down"); - } - } - - Ok(()) -} - -async fn run_init(config_path: PathBuf, output_path: PathBuf) -> Result<()> { - print_logo(); - handle_docker_init(config_path, output_path).await -} - -#[cfg(test)] -mod tests { - use commit_boost::VERSION; - - use super::*; - - #[test] - fn version_has_v_prefix() { - assert!(VERSION.starts_with('v'), "VERSION should start with 'v', got: {VERSION}"); - } - - #[test] - fn parse_init_subcommand() { - Cli::try_parse_from(["commit-boost", "init", "--config", "/tmp/config.toml"]) - .expect("should parse init subcommand"); - } -} diff --git a/bin/pbs.rs b/bin/pbs.rs new file mode 100644 index 00000000..0b7c3f72 --- /dev/null +++ b/bin/pbs.rs @@ -0,0 +1,49 @@ +use cb_common::{ + config::{LogsSettings, PBS_MODULE_NAME, load_pbs_config}, + utils::{initialize_tracing_log, wait_for_signal}, +}; +use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; +use clap::Parser; +use eyre::Result; +use tracing::{error, info}; + +/// Version string with a leading 'v' +const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); + +/// Subcommands and global arguments for the module +#[derive(Parser, Debug)] +#[command(name = "Commit-Boost PBS Service", version = VERSION, about, long_about = None)] +struct Cli {} + +#[tokio::main] +async fn main() -> Result<()> { + // Parse the CLI arguments (currently only used for version info, more can be + // added later) + let _cli = Cli::parse(); + + color_eyre::install()?; + + let _guard = initialize_tracing_log(PBS_MODULE_NAME, LogsSettings::from_env_config()?); + + let _args = cb_cli::PbsArgs::parse(); + + let pbs_config = load_pbs_config().await?; + + PbsService::init_metrics(pbs_config.chain)?; + let state = PbsState::new(pbs_config); + let server = PbsService::run::<_, DefaultBuilderApi>(state); + + tokio::select! { + maybe_err = server => { + if let Err(err) = maybe_err { + error!(%err, "PBS service unexpectedly stopped"); + eprintln!("PBS service unexpectedly stopped: {err}"); + } + }, + _ = wait_for_signal() => { + info!("shutting down"); + } + } + + Ok(()) +} diff --git a/bin/signer.rs b/bin/signer.rs new file mode 100644 index 00000000..01f3c970 --- /dev/null +++ b/bin/signer.rs @@ -0,0 +1,46 @@ +use cb_common::{ + config::{LogsSettings, SIGNER_MODULE_NAME, StartSignerConfig}, + utils::{initialize_tracing_log, wait_for_signal}, +}; +use cb_signer::service::SigningService; +use clap::Parser; +use eyre::Result; +use tracing::{error, info}; + +/// Version string with a leading 'v' +const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); + +/// Subcommands and global arguments for the module +#[derive(Parser, Debug)] +#[command(name = "Commit-Boost Signer Service", version = VERSION, about, long_about = None)] +struct Cli {} + +#[tokio::main] +async fn main() -> Result<()> { + // Parse the CLI arguments (currently only used for version info, more can be + // added later) + let _cli = Cli::parse(); + + color_eyre::install()?; + + let _guard = initialize_tracing_log(SIGNER_MODULE_NAME, LogsSettings::from_env_config()?); + + let _args = cb_cli::SignerArgs::parse(); + + let config = StartSignerConfig::load_from_env()?; + let server = SigningService::run(config); + + tokio::select! { + maybe_err = server => { + if let Err(err) = maybe_err { + error!(%err, "signing server unexpectedly stopped"); + eprintln!("signing server unexpectedly stopped: {err}"); + } + }, + _ = wait_for_signal() => { + info!("shutting down"); + } + } + + Ok(()) +} diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 0897aa34..dd0f52c2 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -6,12 +6,9 @@ pub mod prelude { SignedProxyDelegationBls, SignedProxyDelegationEcdsa, }, config::{ - LogsSettings, PBS_SERVICE_NAME, StartCommitModuleConfig, load_builder_module_config, + LogsSettings, PBS_MODULE_NAME, StartCommitModuleConfig, load_builder_module_config, load_commit_module_config, load_pbs_config, load_pbs_custom_config, }, - signature::{ - verify_proposer_commitment_signature_bls, verify_proposer_commitment_signature_ecdsa, - }, signer::EcdsaSignature, types::{BlsPublicKey, BlsSignature, Chain}, utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us}, @@ -27,6 +24,3 @@ pub mod prelude { } pub use tree_hash_derive::TreeHash; } - -/// Version string with a leading 'v' -pub const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); diff --git a/bin/tests/binary.rs b/bin/tests/binary.rs deleted file mode 100644 index 6352589e..00000000 --- a/bin/tests/binary.rs +++ /dev/null @@ -1,202 +0,0 @@ -use assert_cmd::{Command, cargo}; -use cb_cli::docker_init::{CB_COMPOSE_FILE, CB_ENV_FILE}; - -// --------------------------------------------------------------------------- -// Fixtures -// --------------------------------------------------------------------------- - -const MINIMAL_PBS_TOML: &str = r#" -chain = "Holesky" -[pbs] -docker_image = "ghcr.io/commit-boost/pbs:latest" -"#; - -const MINIMAL_WITH_MODULE_TOML: &str = r#" -chain = "Holesky" -[pbs] -docker_image = "ghcr.io/commit-boost/pbs:latest" - -[signer.local.loader] -key_path = "/keys/keys.json" - -[[modules]] -id = "DA_COMMIT" -type = "commit" -docker_image = "test_da_commit" -signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" -"#; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -/// Returns a `Command` pointed at the `commit-boost` binary under test. -fn cmd() -> Command { - Command::new(cargo::cargo_bin!()) -} - -/// Writes `contents` to `cb.toml` inside `dir` and returns the path. -fn write_config(dir: &tempfile::TempDir, contents: &str) -> std::path::PathBuf { - let path = dir.path().join("cb.toml"); - std::fs::write(&path, contents).expect("write test config"); - path -} - -/// Returns a `commit-boost init` command configured with the given config and -/// output directory. -fn init_cmd(config: &std::path::Path, output_dir: &std::path::Path) -> Command { - let mut c = cmd(); - c.args([ - "init", - "--config", - config.to_str().expect("valid config path"), - "--output", - output_dir.to_str().expect("valid output dir"), - ]); - c -} - -// --------------------------------------------------------------------------- -// Binary smoke tests -// --------------------------------------------------------------------------- - -/// Tests that the binary can be run and returns a version string -#[test] -fn test_load_example_config() { - let expected_version = format!("Commit-Boost {}\n", commit_boost::VERSION); - cmd().arg("--version").assert().success().stdout(expected_version); -} - -/// Tests that the init command can be run and complains about not having -/// --config set -#[test] -fn test_run_init() { - cmd().args(["init"]).assert().failure().stderr(predicates::str::contains( - "error: the following required arguments were not provided:\n --config ", - )); -} - -/// Tests that PBS runs without CB_CONFIG being set and complains normally -#[test] -fn test_run_pbs_no_config() { - cmd() - .args(["pbs"]) - .assert() - .failure() - .stderr(predicates::str::contains("CB_CONFIG is not set")); -} - -/// Tests that Signer runs without CB_CONFIG being set and complains normally -#[test] -fn test_run_signer_no_config() { - cmd() - .args(["signer"]) - .assert() - .failure() - .stderr(predicates::str::contains("CB_CONFIG is not set")); -} - -// --------------------------------------------------------------------------- -// handle_docker_init (via `commit-boost init`) integration tests -// --------------------------------------------------------------------------- - -/// Minimal PBS-only config produces a compose file and no .env file. -#[test] -fn test_init_pbs_only_creates_compose_file() { - let dir = tempfile::tempdir().expect("tempdir"); - let config = write_config(&dir, MINIMAL_PBS_TOML); - - init_cmd(&config, dir.path()).assert().success(); - - assert!(dir.path().join(CB_COMPOSE_FILE).exists(), "compose file should be created"); - assert!(!dir.path().join(CB_ENV_FILE).exists(), "no .env file for PBS-only config"); -} - -/// PBS-only compose file has the expected service structure. -#[test] -fn test_init_compose_file_pbs_service_structure() { - let dir = tempfile::tempdir().expect("tempdir"); - let config = write_config(&dir, MINIMAL_PBS_TOML); - - init_cmd(&config, dir.path()).assert().success(); - - let contents = - std::fs::read_to_string(dir.path().join(CB_COMPOSE_FILE)).expect("read compose file"); - let compose: serde_yaml::Value = - serde_yaml::from_str(&contents).expect("compose file is valid YAML"); - - let pbs = &compose["services"]["cb_pbs"]; - assert!(!pbs.is_null(), "cb_pbs service must exist"); - assert_eq!(pbs["image"].as_str(), Some("ghcr.io/commit-boost/pbs:latest"), "image"); - assert_eq!(pbs["container_name"].as_str(), Some("cb_pbs"), "container_name"); - - // Config file must be mounted inside the container. - let volumes = pbs["volumes"].as_sequence().expect("volumes is a list"); - assert!( - volumes.iter().any(|v| v.as_str().map_or(false, |s| s.ends_with(":/cb-config.toml:ro"))), - "config must be mounted at /cb-config.toml" - ); - - // Required environment variables must be present. - let env = &pbs["environment"]; - assert!(!env["CB_CONFIG"].is_null(), "CB_CONFIG env var must be set"); - assert!(!env["CB_PBS_ENDPOINT"].is_null(), "CB_PBS_ENDPOINT env var must be set"); - - // Port 18550 must be exposed. - let ports = pbs["ports"].as_sequence().expect("ports is a list"); - assert!( - ports.iter().any(|p| p.as_str().map_or(false, |s| s.contains("18550"))), - "port 18550 must be exposed" - ); - - // No signer service and no extra network in a PBS-only config. - assert!(compose["services"]["cb_signer"].is_null(), "cb_signer must not exist"); - assert!(compose["networks"].is_null(), "no networks for PBS-only config"); -} - -/// Config with a commit module produces both a compose file and a .env file. -#[test] -fn test_init_with_module_creates_env_file() { - let dir = tempfile::tempdir().expect("tempdir"); - let config = write_config(&dir, MINIMAL_WITH_MODULE_TOML); - - init_cmd(&config, dir.path()).assert().success(); - - assert!(dir.path().join(CB_COMPOSE_FILE).exists(), "compose file should be created"); - assert!(dir.path().join(CB_ENV_FILE).exists(), ".env file should be created for modules"); -} - -/// .env file contains a JWT entry for the module. -#[test] -fn test_init_env_file_contains_module_jwt() { - let dir = tempfile::tempdir().expect("tempdir"); - let config = write_config(&dir, MINIMAL_WITH_MODULE_TOML); - - init_cmd(&config, dir.path()).assert().success(); - - let env_contents = - std::fs::read_to_string(dir.path().join(CB_ENV_FILE)).expect("read .env file"); - assert!(env_contents.contains("CB_JWT_DA_COMMIT="), ".env must contain module JWT"); -} - -/// Missing --config argument produces a clear error message. -#[test] -fn test_init_missing_config_flag_fails_with_message() { - cmd().args(["init"]).assert().failure().stderr(predicates::str::contains("--config")); -} - -/// Non-existent config file produces an error. -#[test] -fn test_init_nonexistent_config_file_fails() { - let dir = tempfile::tempdir().expect("tempdir"); - cmd() - .args([ - "init", - "--config", - "/nonexistent/path/cb.toml", - "--output", - dir.path().to_str().expect("valid dir"), - ]) - .assert() - .failure(); -} diff --git a/config.example.toml b/config.example.toml index 41707354..e85b98e1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -2,9 +2,9 @@ # Some fields are optional and can be omitted, in which case the default value, if present, will be used. # Chain spec ID. Supported values: -# A network ID. Supported values: Mainnet, Holesky, Sepolia, Hoodi. Lower case values e.g. "mainnet" are also accepted +# A network ID. Supported values: Mainnet, Holesky, Sepolia, Helder, Hoodi. Lower case values e.g. "mainnet" are also accepted # A custom object, e.g., chain = { genesis_time_secs = 1695902400, path = "/path/to/spec.json" }, with a path to a chain spec file, either in .json format (e.g., as returned by the beacon endpoint /eth/v1/config/spec), or in .yml format (see examples in tests/data). -# A custom object, e.g., chain = { genesis_time_secs = 1695902400, slot_time_secs = 12, genesis_fork_version = "0x01017000", chain_id = 17000 }. +# A custom object, e.g., chain = { genesis_time_secs = 1695902400, slot_time_secs = 12, genesis_fork_version = "0x01017000" }. chain = "Holesky" # Configuration for the PBS module @@ -55,13 +55,9 @@ extra_validation_enabled = false # Execution Layer RPC url to use for extra validation # OPTIONAL # rpc_url = "https://ethereum-holesky-rpc.publicnode.com" -# URL of your local SSV node API endpoint, if you have a mux that targets an SSV node operator -# OPTIONAL, DEFAULT: "http://localhost:16000/v1/" -# ssv_node_api_url = "http://localhost:16000/v1/" -# URL of the public SSV API server, if you have a mux that targets an SSV node operator. This is used as -# a fallback if the user's own SSV node is not reachable. -# OPTIONAL, DEFAULT: "https://api.ssv.network/api/v4/" -# ssv_public_api_url = "https://api.ssv.network/api/v4/" +# URL of the SSV API server to use, if you have a mux that targets an SSV node operator +# OPTIONAL, DEFAULT: "https://api.ssv.network/api/v4" +# ssv_api_url = "https://api.ssv.network/api/v4" # Timeout for any HTTP requests sent from the PBS module to other services, in seconds # OPTIONAL, DEFAULT: 10 http_timeout_seconds = 10 @@ -173,10 +169,10 @@ url = "http://0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09f # - Dirk: a remote Dirk instance # - Local: a local Signer module # More details on the docs (https://commit-boost.github.io/commit-boost-client/get_started/configuration/#signer-module) -[signer] +# [signer] # Docker image to use for the Signer module. # OPTIONAL, DEFAULT: ghcr.io/commit-boost/signer:latest -docker_image = "ghcr.io/commit-boost/signer:latest" +# docker_image = "ghcr.io/commit-boost/signer:latest" # Host to bind the Signer API server to # OPTIONAL, DEFAULT: 127.0.0.1 host = "127.0.0.1" @@ -186,33 +182,10 @@ port = 20000 # Number of JWT authentication attempts a client can fail before blocking that client temporarily from Signer access # OPTIONAL, DEFAULT: 3 jwt_auth_fail_limit = 3 -# How long to block a client from Signer access, in seconds, if it failed JWT authentication too many times. -# This also defines the interval at which failed attempts are regularly checked and expired ones are cleaned up. +# How long to block a client from Signer access, in seconds, if it failed JWT authentication too many times # OPTIONAL, DEFAULT: 300 jwt_auth_fail_timeout_seconds = 300 -# HTTP header to use to determine the real client IP, if the Signer is behind a proxy (e.g. nginx) -# OPTIONAL. If missing, the client IP will be taken directly from the TCP connection. -# [signer.reverse_proxy] -# Type of reverse proxy configuration. Supported values: -# - unique: use a single HTTP header value as the client IP. -# - rightmost: use the rightmost IP from a comma-separated list of IPs in the HTTP header. -# type = "unique" -# Unique: HTTP header name to use to determine the real client IP. If the header appears multiple times, the request will be rejected. -# header = "X-Real-IP" -# Rightmost: HTTP header name to use to determine the real client IP from a comma-separated list of IPs. If the header appears multiple times, the last value will be used. -# header = "X-Forwarded-For" -# Rightmost: number of trusted proxies in front of the Signer, whose IPs will be skipped when extracting the client IP from the rightmost side of the list. Must be greater than 0. -# trusted_count = 1 - -# [signer.tls_mode] -# How to use TLS for the Signer's HTTP server; two modes are supported: -# - type = "insecure": disable TLS, so the server runs in HTTP mode (not recommended for production). -# - type = "certificate": Use TLS. Include a property named "path" below this with the provided path; `path` should be a directory containing `cert.pem` and `key.pem` files to use. If they don't exist, they'll be automatically generated in self-signed mode. -# OPTIONAL, DEFAULT: -# type = "certificate" -# path = "./certs" - # For Remote signer: # [signer.remote] # URL of the Web3Signer instance @@ -295,8 +268,6 @@ proxy_dir = "./proxies" [[modules]] # Unique ID of the module id = "DA_COMMIT" -# Unique hash that the Signer service will combine with the incoming data in signing requests to generate a signature specific to this module -signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" # Type of the module. Supported values: commit type = "commit" # Docker image of the module diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 3e713397..2acc6a7b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -12,7 +12,3 @@ docker-compose-types.workspace = true eyre.workspace = true indexmap.workspace = true serde_yaml.workspace = true - -[dev-dependencies] -tempfile.workspace = true -toml.workspace = true diff --git a/crates/cli/src/docker_init.rs b/crates/cli/src/docker_init.rs index f2c5e2e4..b535e54d 100644 --- a/crates/cli/src/docker_init.rs +++ b/crates/cli/src/docker_init.rs @@ -6,17 +6,15 @@ use std::{ use cb_common::{ config::{ - ADMIN_JWT_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV, CommitBoostConfig, - DIRK_CA_CERT_DEFAULT, DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV, - DIRK_DIR_SECRETS_DEFAULT, DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV, JWTS_ENV, - LOGS_DIR_DEFAULT, LOGS_DIR_ENV, LogsSettings, METRICS_PORT_ENV, MODULE_ID_ENV, - MODULE_JWT_ENV, ModuleKind, PBS_ENDPOINT_ENV, PBS_SERVICE_NAME, PROXY_DIR_DEFAULT, - PROXY_DIR_ENV, PROXY_DIR_KEYS_DEFAULT, PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT, + CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV, CommitBoostConfig, DIRK_CA_CERT_DEFAULT, + DIRK_CA_CERT_ENV, DIRK_CERT_DEFAULT, DIRK_CERT_ENV, DIRK_DIR_SECRETS_DEFAULT, + DIRK_DIR_SECRETS_ENV, DIRK_KEY_DEFAULT, DIRK_KEY_ENV, JWTS_ENV, LOGS_DIR_DEFAULT, + LOGS_DIR_ENV, LogsSettings, METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, ModuleKind, + PBS_ENDPOINT_ENV, PBS_MODULE_NAME, PROXY_DIR_DEFAULT, PROXY_DIR_ENV, + PROXY_DIR_KEYS_DEFAULT, PROXY_DIR_KEYS_ENV, PROXY_DIR_SECRETS_DEFAULT, PROXY_DIR_SECRETS_ENV, SIGNER_DEFAULT, SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_DEFAULT, SIGNER_DIR_SECRETS_ENV, SIGNER_ENDPOINT_ENV, SIGNER_KEYS_ENV, - SIGNER_PORT_DEFAULT, SIGNER_SERVICE_NAME, SIGNER_TLS_CERTIFICATE_NAME, - SIGNER_TLS_CERTIFICATES_PATH_DEFAULT, SIGNER_TLS_CERTIFICATES_PATH_ENV, - SIGNER_TLS_KEY_NAME, SIGNER_URL_ENV, SignerConfig, SignerType, StaticModuleConfig, + SIGNER_MODULE_NAME, SIGNER_PORT_DEFAULT, SIGNER_URL_ENV, SignerConfig, SignerType, }, pbs::{BUILDER_V1_API_PATH, GET_STATUS_PATH}, signer::{ProxyStore, SignerLoader}, @@ -32,287 +30,217 @@ use eyre::Result; use indexmap::IndexMap; /// Name of the docker compose file -pub const CB_COMPOSE_FILE: &str = "cb.docker-compose.yml"; +pub(super) const CB_COMPOSE_FILE: &str = "cb.docker-compose.yml"; /// Name of the envs file -pub const CB_ENV_FILE: &str = ".cb.env"; +pub(super) const CB_ENV_FILE: &str = ".cb.env"; const SIGNER_NETWORK: &str = "signer_network"; -// Info about a custom chain spec to use -struct ServiceChainSpecInfo { - // Environment variable to set for the chain spec file's path - env: (String, Option), - - // Volume for binding the chain spec file into a container - volume: Volumes, -} - -// Info about the Commit-Boost config being used to create services -struct CommitBoostConfigInfo { - // Commit-Boost config - cb_config: CommitBoostConfig, - - // Volume for binding the config file into a container - config_volume: Volumes, -} - -// Information needed to create a Commit-Boost service -struct ServiceCreationInfo { - // Info about the Commit-Boost config being used - config_info: CommitBoostConfigInfo, - - // Environment variables to write in .env file - envs: IndexMap, - - // Targets to pass to prometheus - targets: Vec, - - // Warnings that need to be shown to the user - warnings: Vec, - - // JWTs for any modules owned by this service (TODO: are we going to offload modules to the - // user instead of owning them?) - jwts: IndexMap, - - // Custom chain spec info, if any - chain_spec: Option, - - // Next available port for metrics (TODO: this should be a setting in PBS and in Signer instead - // of a universal one) - metrics_port: u16, -} - /// Builds the docker compose file for the Commit-Boost services // TODO: do more validation for paths, images, etc pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Result<()> { - // Initialize variables - let mut services = IndexMap::new(); println!("Initializing Commit-Boost with config file: {}", config_path.display()); - let mut service_config = ServiceCreationInfo { - config_info: CommitBoostConfigInfo { - config_volume: Volumes::Simple(format!( - "./{}:{}:ro", - config_path.display(), - CONFIG_DEFAULT - )), - cb_config: CommitBoostConfig::from_file(&config_path)?, - }, - envs: IndexMap::new(), - targets: Vec::new(), - warnings: Vec::new(), - jwts: IndexMap::new(), - chain_spec: None, - metrics_port: 9100, - }; - service_config.config_info.cb_config.validate().await?; + let cb_config = CommitBoostConfig::from_file(&config_path)?; + cb_config.validate().await?; - // Get the custom chain spec, if any let chain_spec_path = CommitBoostConfig::chain_spec_file(&config_path); - if let Some(spec) = chain_spec_path { - let filename = spec - .file_name() - .ok_or_else(|| eyre::eyre!("Chain spec path has no filename: {}", spec.display()))? - .to_str() - .ok_or_else(|| { - eyre::eyre!("Chain spec filename is not valid UTF-8: {}", spec.display()) - })?; - let chain_spec = ServiceChainSpecInfo { - env: get_env_val(CHAIN_SPEC_ENV, &format!("/{filename}")), - volume: Volumes::Simple(format!("{}:/{}:ro", spec.display(), filename)), + + let log_to_file = cb_config.logs.file.enabled; + let mut metrics_port = cb_config.metrics.as_ref().map(|m| m.start_port).unwrap_or_default(); + + let mut services = IndexMap::new(); + + // config volume to pass to all services + let config_volume = + Volumes::Simple(format!("./{}:{}:ro", config_path.display(), CONFIG_DEFAULT)); + let chain_spec_volume = chain_spec_path.as_ref().and_then(|p| { + // this is ok since the config has already been loaded once + let file_name = p.file_name()?.to_str()?; + Some(Volumes::Simple(format!("{}:/{}:ro", p.display(), file_name))) + }); + + let chain_spec_env = chain_spec_path.and_then(|p| { + // this is ok since the config has already been loaded once + let file_name = p.file_name()?.to_str()?; + Some(get_env_val(CHAIN_SPEC_ENV, &format!("/{file_name}"))) + }); + + let mut jwts = IndexMap::new(); + // envs to write in .env file + let mut envs = IndexMap::new(); + // targets to pass to prometheus + let mut targets = Vec::new(); + + // address for signer API communication + let signer_port = cb_config.signer.as_ref().map(|s| s.port).unwrap_or(SIGNER_PORT_DEFAULT); + let signer_server = + if let Some(SignerConfig { inner: SignerType::Remote { url }, .. }) = &cb_config.signer { + url.to_string() + } else { + format!("http://cb_signer:{signer_port}") }; - service_config.chain_spec = Some(chain_spec); - } - // Set up variables - service_config.metrics_port = service_config - .config_info - .cb_config - .metrics - .as_ref() - .map(|m| m.start_port) - .unwrap_or_default(); - let needs_signer_module = service_config.config_info.cb_config.needs_signer_module(); - let signer_config = if needs_signer_module { - Some(service_config.config_info.cb_config.signer.clone().ok_or_else(|| { - eyre::eyre!( - "Signer module required but no signer config provided in Commit-Boost config" - ) - })?) - } else { - None - }; - let signer_server_url = - service_config.config_info.cb_config.signer_server_url(SIGNER_PORT_DEFAULT); + let mut warnings = Vec::new(); - // Warn if the certificates path is not set for a TLS signer - if service_config.config_info.cb_config.signer_certs_path().is_none() { - service_config.warnings.push( - "Signer TLS mode is set to Insecure, using HTTP instead of HTTPS for signer communication".to_string(), - ); - } + let needs_signer_module = cb_config.pbs.with_signer || + cb_config.modules.as_ref().is_some_and(|modules| { + modules.iter().any(|module| matches!(module.kind, ModuleKind::Commit)) + }); // setup modules - if let Some(ref modules_config) = service_config.config_info.cb_config.modules { - for module in modules_config.clone() { - let (module_cid, module_service) = - create_module_service(&module, signer_server_url.as_str(), &mut service_config)?; + if let Some(modules_config) = cb_config.modules { + for module in modules_config { + let module_cid = format!("cb_{}", module.id.to_lowercase()); + + let module_service = match module.kind { + // a commit module needs a JWT and access to the signer network + ModuleKind::Commit => { + let mut ports = vec![]; + + let jwt_secret = random_jwt_secret(); + let jwt_name = format!("CB_JWT_{}", module.id.to_uppercase()); + + // module ids are assumed unique, so envs dont override each other + let mut module_envs = IndexMap::from([ + get_env_val(MODULE_ID_ENV, &module.id), + get_env_val(CONFIG_ENV, CONFIG_DEFAULT), + get_env_interp(MODULE_JWT_ENV, &jwt_name), + get_env_val(SIGNER_URL_ENV, &signer_server), + ]); + + // Pass on the env variables + if let Some(envs) = module.env { + for (k, v) in envs { + module_envs.insert(k, Some(SingleValue::String(v))); + } + } + + // Set environment file + let env_file = module.env_file.map(EnvFile::Simple); + + if let Some((key, val)) = chain_spec_env.clone() { + module_envs.insert(key, val); + } + + if let Some(metrics_config) = &cb_config.metrics && + metrics_config.enabled + { + let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); + ports.push(format!("{host_endpoint}:{metrics_port}")); + warnings + .push(format!("{module_cid} has an exported port on {metrics_port}")); + targets.push(format!("{host_endpoint}")); + let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); + module_envs.insert(key, val); + + metrics_port += 1; + } + + if log_to_file { + let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); + module_envs.insert(key, val); + } + + envs.insert(jwt_name.clone(), jwt_secret.clone()); + jwts.insert(module.id.clone(), jwt_secret); + + // networks + let module_networks = vec![SIGNER_NETWORK.to_owned()]; + + // volumes + let mut module_volumes = vec![config_volume.clone()]; + module_volumes.extend(chain_spec_volume.clone()); + module_volumes.extend(get_log_volume(&cb_config.logs, &module.id)); + + // depends_on + let mut module_dependencies = IndexMap::new(); + module_dependencies.insert("cb_signer".into(), DependsCondition { + condition: "service_healthy".into(), + }); + + Service { + container_name: Some(module_cid.clone()), + image: Some(module.docker_image), + networks: Networks::Simple(module_networks), + ports: Ports::Short(ports), + volumes: module_volumes, + environment: Environment::KvPair(module_envs), + depends_on: if let Some(SignerConfig { + inner: SignerType::Remote { .. }, + .. + }) = &cb_config.signer + { + DependsOnOptions::Simple(vec![]) + } else { + DependsOnOptions::Conditional(module_dependencies) + }, + env_file, + ..Service::default() + } + } + }; + services.insert(module_cid, Some(module_service)); } }; // setup pbs service - let pbs_service = create_pbs_service(&mut service_config)?; - services.insert("cb_pbs".to_owned(), Some(pbs_service)); - - // setup signer service - if let Some(signer_config) = signer_config { - match &signer_config.inner { - SignerType::Local { loader, store } => { - let signer_service = create_signer_service_local( - &mut service_config, - &signer_config, - loader, - store, - )?; - services.insert("cb_signer".to_owned(), Some(signer_service)); - } - SignerType::Dirk { cert_path, key_path, secrets_path, ca_cert_path, store, .. } => { - let signer_service = create_signer_service_dirk( - &mut service_config, - &signer_config, - cert_path, - key_path, - secrets_path, - ca_cert_path, - store, - )?; - services.insert("cb_signer".to_owned(), Some(signer_service)); - } - SignerType::Remote { .. } => { - eyre::bail!( - "Signer module required but remote signer config provided; use a local or Dirk signer instead" - ); - } - } - } - - let mut compose = Compose::default(); - - if needs_signer_module { - compose.networks.0.insert( - SIGNER_NETWORK.to_owned(), - MapOrEmpty::Map(NetworkSettings { - driver: Some("bridge".to_owned()), - ..NetworkSettings::default() - }), - ); - } - - // write compose to file - compose.services = Services(services); - let compose_path = Path::new(&output_dir).join(CB_COMPOSE_FILE); - write_compose_file(&compose, &compose_path, &service_config)?; - - // Inform user about Prometheus targets - if !service_config.targets.is_empty() { - let targets = service_config.targets.join(", "); - println!("Note: Make sure to add these targets for Prometheus to scrape: {targets}"); - println!( - "Check out the docs on how to configure Prometheus/Grafana/cAdvisor: https://commit-boost.github.io/commit-boost-client/get_started/running/metrics" - ); - } - - if service_config.envs.is_empty() { - println!("Run with:\n\tdocker compose -f {compose_path:?} up -d"); - } else { - // write envs to .env file - let env_path = Path::new(&output_dir).join(CB_ENV_FILE); - write_env_file(&service_config.envs, &env_path)?; - println!(); - println!("Run with:\n\tdocker compose --env-file {env_path:?} -f {compose_path:?} up -d"); - println!("Stop with:\n\tdocker compose --env-file {env_path:?} -f {compose_path:?} down"); - } - - Ok(()) -} -// Creates a PBS service -fn create_pbs_service(service_config: &mut ServiceCreationInfo) -> eyre::Result { - let metrics_port = service_config.metrics_port; - let cb_config = &service_config.config_info.cb_config; - let config_volume = &service_config.config_info.config_volume; - let mut envs = IndexMap::from([get_env_val(CONFIG_ENV, CONFIG_DEFAULT)]); - let mut volumes = vec![config_volume.clone()]; + let mut pbs_envs = IndexMap::from([get_env_val(CONFIG_ENV, CONFIG_DEFAULT)]); + let mut pbs_volumes = vec![config_volume.clone()]; - // Bind the API to 0.0.0.0 - let container_endpoint = - SocketAddr::from((Ipv4Addr::UNSPECIFIED, cb_config.pbs.pbs_config.port)); + // ports let host_endpoint = SocketAddr::from((cb_config.pbs.pbs_config.host, cb_config.pbs.pbs_config.port)); - let (key, val) = get_env_val(PBS_ENDPOINT_ENV, &container_endpoint.to_string()); - envs.insert(key, val); - - // Exposed ports let mut ports = vec![format!("{}:{}", host_endpoint, cb_config.pbs.pbs_config.port)]; - service_config - .warnings - .push(format!("cb_pbs has an exported port on {}", cb_config.pbs.pbs_config.port)); + warnings.push(format!("cb_pbs has an exported port on {}", cb_config.pbs.pbs_config.port)); - // Volumes for file-based mux config files - if let Some(ref mux_config) = cb_config.muxes { + if let Some(mux_config) = cb_config.muxes { for mux in mux_config.muxes.iter() { if let Some((env_name, actual_path, internal_path)) = mux.loader_env()? { let (key, val) = get_env_val(&env_name, &internal_path); - envs.insert(key, val); - volumes.push(Volumes::Simple(format!("{actual_path}:{internal_path}:ro"))); + pbs_envs.insert(key, val); + pbs_volumes.push(Volumes::Simple(format!("{actual_path}:{internal_path}:ro"))); } } } - // Chain spec env/volume - if let Some(spec) = &service_config.chain_spec { - envs.insert(spec.env.0.clone(), spec.env.1.clone()); - volumes.push(spec.volume.clone()); + if let Some((key, val)) = chain_spec_env.clone() { + pbs_envs.insert(key, val); } - - // Metrics if let Some(metrics_config) = &cb_config.metrics && metrics_config.enabled { let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); ports.push(format!("{host_endpoint}:{metrics_port}")); - service_config.warnings.push(format!("cb_pbs has an exported port on {metrics_port}")); - service_config.targets.push(format!("{host_endpoint}")); + warnings.push(format!("cb_pbs has an exported port on {metrics_port}")); + targets.push(format!("{host_endpoint}")); let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); - envs.insert(key, val); + pbs_envs.insert(key, val); - service_config.metrics_port += 1; + metrics_port += 1; } - - // Logging env/volume - if cb_config.logs.file.enabled { + if log_to_file { let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); - envs.insert(key, val); + pbs_envs.insert(key, val); } - volumes.extend(get_log_volume(&cb_config.logs, PBS_SERVICE_NAME)?); - // Certs env/volume - if cb_config.needs_signer_module() && - let Some(certs_path) = cb_config.signer_certs_path() - { - volumes.push(create_cert_binding(certs_path)); - let (key, val) = - get_env_val(SIGNER_TLS_CERTIFICATES_PATH_ENV, SIGNER_TLS_CERTIFICATES_PATH_DEFAULT); - envs.insert(key, val); - } + // inside the container expose on 0.0.0.0 + let container_endpoint = + SocketAddr::from((Ipv4Addr::UNSPECIFIED, cb_config.pbs.pbs_config.port)); + let (key, val) = get_env_val(PBS_ENDPOINT_ENV, &container_endpoint.to_string()); + pbs_envs.insert(key, val); + + // volumes + pbs_volumes.extend(chain_spec_volume.clone()); + pbs_volumes.extend(get_log_volume(&cb_config.logs, PBS_MODULE_NAME)); - // Create the service let pbs_service = Service { container_name: Some("cb_pbs".to_owned()), - image: Some(cb_config.pbs.docker_image.clone()), + image: Some(cb_config.pbs.docker_image), ports: Ports::Short(ports), - volumes, - environment: Environment::KvPair(envs), + volumes: pbs_volumes, + environment: Environment::KvPair(pbs_envs), healthcheck: Some(Healthcheck { test: Some(HealthcheckTest::Single(format!( "curl -f http://localhost:{}{}{}", @@ -328,437 +256,324 @@ fn create_pbs_service(service_config: &mut ServiceCreationInfo) -> eyre::Result< ..Service::default() }; - Ok(pbs_service) -} - -// Creates a Signer service using a local signer -fn create_signer_service_local( - service_config: &mut ServiceCreationInfo, - signer_config: &SignerConfig, - loader: &SignerLoader, - store: &Option, -) -> eyre::Result { - let cb_config = &service_config.config_info.cb_config; - let config_volume = &service_config.config_info.config_volume; - let metrics_port = service_config.metrics_port; - let mut envs = IndexMap::from([ - get_env_val(CONFIG_ENV, CONFIG_DEFAULT), - get_env_same(JWTS_ENV), - get_env_same(ADMIN_JWT_ENV), - get_env_val(SIGNER_TLS_CERTIFICATES_PATH_ENV, SIGNER_TLS_CERTIFICATES_PATH_DEFAULT), - ]); - let mut volumes = vec![config_volume.clone()]; - - // Bind the API to 0.0.0.0 - let container_endpoint = SocketAddr::from((Ipv4Addr::UNSPECIFIED, signer_config.port)); - let host_endpoint = SocketAddr::from((signer_config.host, signer_config.port)); - let (key, val) = get_env_val(SIGNER_ENDPOINT_ENV, &container_endpoint.to_string()); - envs.insert(key, val); - - // Exposed ports - let mut ports = vec![format!("{}:{}", host_endpoint, signer_config.port)]; - service_config - .warnings - .push(format!("cb_signer has an exported port on {}", signer_config.port)); - - // Chain spec env/volume - if let Some(spec) = &service_config.chain_spec { - envs.insert(spec.env.0.clone(), spec.env.1.clone()); - volumes.push(spec.volume.clone()); - } - - // Metrics - if let Some(metrics_config) = &cb_config.metrics && - metrics_config.enabled - { - let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); - ports.push(format!("{host_endpoint}:{metrics_port}")); - service_config.warnings.push(format!("cb_signer has an exported port on {metrics_port}")); - service_config.targets.push(format!("{host_endpoint}")); - let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); - envs.insert(key, val); - service_config.metrics_port += 1; - } - - // Logging envs/volume - if cb_config.logs.file.enabled { - let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); - envs.insert(key, val); - } - volumes.extend(get_log_volume(&cb_config.logs, SIGNER_SERVICE_NAME)?); - - // write jwts to env - service_config.envs.insert(JWTS_ENV.into(), format_comma_separated(&service_config.jwts)); - service_config.envs.insert(ADMIN_JWT_ENV.into(), random_jwt_secret()); - - // Signer loader volumes and envs - match loader { - SignerLoader::File { key_path } => { - volumes.push(Volumes::Simple(format!("{}:{}:ro", key_path.display(), SIGNER_DEFAULT))); - let (k, v) = get_env_val(SIGNER_KEYS_ENV, SIGNER_DEFAULT); - envs.insert(k, v); - } - SignerLoader::ValidatorsDir { keys_path, secrets_path, format: _ } => { - volumes.push(Volumes::Simple(format!( - "{}:{}:ro", - keys_path.display(), - SIGNER_DIR_KEYS_DEFAULT - ))); - let (k, v) = get_env_val(SIGNER_DIR_KEYS_ENV, SIGNER_DIR_KEYS_DEFAULT); - envs.insert(k, v); - - volumes.push(Volumes::Simple(format!( - "{}:{}:ro", - secrets_path.display(), - SIGNER_DIR_SECRETS_DEFAULT - ))); - let (k, v) = get_env_val(SIGNER_DIR_SECRETS_ENV, SIGNER_DIR_SECRETS_DEFAULT); - envs.insert(k, v); - } - }; - - // Proxy keystore volumes and envs - if let Some(store) = store { - match store { - ProxyStore::File { proxy_dir } => { - volumes.push(Volumes::Simple(format!( - "{}:{}:rw", - proxy_dir.display(), - PROXY_DIR_DEFAULT - ))); - let (k, v) = get_env_val(PROXY_DIR_ENV, PROXY_DIR_DEFAULT); - envs.insert(k, v); - } - ProxyStore::ERC2335 { keys_path, secrets_path } => { - volumes.push(Volumes::Simple(format!( - "{}:{}:rw", - keys_path.display(), - PROXY_DIR_KEYS_DEFAULT - ))); - let (k, v) = get_env_val(PROXY_DIR_KEYS_ENV, PROXY_DIR_KEYS_DEFAULT); - envs.insert(k, v); - - volumes.push(Volumes::Simple(format!( - "{}:{}:rw", - secrets_path.display(), - PROXY_DIR_SECRETS_DEFAULT - ))); - let (k, v) = get_env_val(PROXY_DIR_SECRETS_ENV, PROXY_DIR_SECRETS_DEFAULT); - envs.insert(k, v); - } - } - } - - // Add TLS support if needed - if let Some(certs_path) = cb_config.signer_certs_path() { - add_tls_certs_volume(&mut volumes, certs_path)? - } - - // Create the service - let signer_networks = vec![SIGNER_NETWORK.to_owned()]; - let signer_service = Service { - container_name: Some("cb_signer".to_owned()), - image: Some(signer_config.docker_image.clone()), - networks: Networks::Simple(signer_networks), - ports: Ports::Short(ports), - volumes, - environment: Environment::KvPair(envs), - healthcheck: Some(Healthcheck { - test: Some(HealthcheckTest::Single(format!( - "curl -k -f {}/status", - cb_config.signer_server_url(SIGNER_PORT_DEFAULT), - ))), - interval: Some("30s".into()), - timeout: Some("5s".into()), - retries: 3, - start_interval: None, - start_period: Some("5s".into()), - disable: false, - }), - ..Service::default() - }; - - Ok(signer_service) -} - -// Creates a Signer service that's tied to Dirk -fn create_signer_service_dirk( - service_config: &mut ServiceCreationInfo, - signer_config: &SignerConfig, - cert_path: &Path, - key_path: &Path, - secrets_path: &Path, - ca_cert_path: &Option, - store: &Option, -) -> eyre::Result { - let cb_config = &service_config.config_info.cb_config; - let config_volume = &service_config.config_info.config_volume; - let metrics_port = service_config.metrics_port; - let mut envs = IndexMap::from([ - get_env_val(CONFIG_ENV, CONFIG_DEFAULT), - get_env_same(JWTS_ENV), - get_env_same(ADMIN_JWT_ENV), - get_env_val(SIGNER_TLS_CERTIFICATES_PATH_ENV, SIGNER_TLS_CERTIFICATES_PATH_DEFAULT), - get_env_val(DIRK_CERT_ENV, DIRK_CERT_DEFAULT), - get_env_val(DIRK_KEY_ENV, DIRK_KEY_DEFAULT), - get_env_val(DIRK_DIR_SECRETS_ENV, DIRK_DIR_SECRETS_DEFAULT), - ]); - let mut volumes = vec![ - config_volume.clone(), - Volumes::Simple(format!("{}:{}:ro", cert_path.display(), DIRK_CERT_DEFAULT)), - Volumes::Simple(format!("{}:{}:ro", key_path.display(), DIRK_KEY_DEFAULT)), - Volumes::Simple(format!("{}:{}", secrets_path.display(), DIRK_DIR_SECRETS_DEFAULT)), - ]; - - // Bind the API to 0.0.0.0 - let container_endpoint = SocketAddr::from((Ipv4Addr::UNSPECIFIED, signer_config.port)); - let host_endpoint = SocketAddr::from((signer_config.host, signer_config.port)); - let (key, val) = get_env_val(SIGNER_ENDPOINT_ENV, &container_endpoint.to_string()); - envs.insert(key, val); - - // Exposed ports - let mut ports = vec![format!("{}:{}", host_endpoint, signer_config.port)]; - service_config - .warnings - .push(format!("cb_signer has an exported port on {}", signer_config.port)); - - // Chain spec env/volume - if let Some(spec) = &service_config.chain_spec { - envs.insert(spec.env.0.clone(), spec.env.1.clone()); - volumes.push(spec.volume.clone()); - } - - // Metrics - if let Some(metrics_config) = &cb_config.metrics && - metrics_config.enabled - { - let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); - ports.push(format!("{host_endpoint}:{metrics_port}")); - service_config.warnings.push(format!("cb_signer has an exported port on {metrics_port}")); - service_config.targets.push(format!("{host_endpoint}")); - let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); - envs.insert(key, val); - service_config.metrics_port += 1; - } - - // Logging env/volume - if cb_config.logs.file.enabled { - let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); - envs.insert(key, val); - } - volumes.extend(get_log_volume(&cb_config.logs, SIGNER_SERVICE_NAME)?); - - // write jwts to env - service_config.envs.insert(JWTS_ENV.into(), format_comma_separated(&service_config.jwts)); - service_config.envs.insert(ADMIN_JWT_ENV.into(), random_jwt_secret()); - - // CA cert volume and env - if let Some(ca_cert_path) = ca_cert_path { - volumes.push(Volumes::Simple(format!( - "{}:{}:ro", - ca_cert_path.display(), - DIRK_CA_CERT_DEFAULT - ))); - let (key, val) = get_env_val(DIRK_CA_CERT_ENV, DIRK_CA_CERT_DEFAULT); - envs.insert(key, val); - } + services.insert("cb_pbs".to_owned(), Some(pbs_service)); - // Keystore volumes and envs - match store { - Some(ProxyStore::File { proxy_dir }) => { - volumes.push(Volumes::Simple(format!("{}:{}", proxy_dir.display(), PROXY_DIR_DEFAULT))); - let (key, val) = get_env_val(PROXY_DIR_ENV, PROXY_DIR_DEFAULT); - envs.insert(key, val); - } - Some(ProxyStore::ERC2335 { .. }) => { - eyre::bail!("ERC2335 proxy store is not supported with the Dirk signer"); - } - None => {} - } + // setup signer service + if needs_signer_module { + let Some(signer_config) = cb_config.signer else { + panic!("Signer module required but no signer config provided"); + }; - // Add TLS support if needed - if let Some(certs_path) = cb_config.signer_certs_path() { - add_tls_certs_volume(&mut volumes, certs_path)? - } + match signer_config.inner { + SignerType::Local { loader, store } => { + let mut signer_envs = IndexMap::from([ + get_env_val(CONFIG_ENV, CONFIG_DEFAULT), + get_env_same(JWTS_ENV), + ]); + + // Bind the signer API to 0.0.0.0 + let container_endpoint = + SocketAddr::from((Ipv4Addr::UNSPECIFIED, signer_config.port)); + let (key, val) = get_env_val(SIGNER_ENDPOINT_ENV, &container_endpoint.to_string()); + signer_envs.insert(key, val); + + let host_endpoint = SocketAddr::from((signer_config.host, signer_config.port)); + let mut ports = vec![format!("{}:{}", host_endpoint, signer_config.port)]; + warnings.push(format!("cb_signer has an exported port on {}", signer_config.port)); + + if let Some((key, val)) = chain_spec_env.clone() { + signer_envs.insert(key, val); + } + if let Some(metrics_config) = &cb_config.metrics && + metrics_config.enabled + { + let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); + ports.push(format!("{host_endpoint}:{metrics_port}")); + warnings.push(format!("cb_signer has an exported port on {metrics_port}")); + targets.push(format!("{host_endpoint}")); + let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); + signer_envs.insert(key, val); + } + if log_to_file { + let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); + signer_envs.insert(key, val); + } - // Create the service - let signer_networks = vec![SIGNER_NETWORK.to_owned()]; - let signer_service = Service { - container_name: Some("cb_signer".to_owned()), - image: Some(signer_config.docker_image.clone()), - networks: Networks::Simple(signer_networks), - ports: Ports::Short(ports), - volumes, - environment: Environment::KvPair(envs), - healthcheck: Some(Healthcheck { - test: Some(HealthcheckTest::Single(format!( - "curl -k -f {}/status", - cb_config.signer_server_url(SIGNER_PORT_DEFAULT), - ))), - interval: Some("30s".into()), - timeout: Some("5s".into()), - retries: 3, - start_interval: None, - start_period: Some("5s".into()), - disable: false, - }), - ..Service::default() - }; + // write jwts to env + envs.insert(JWTS_ENV.into(), format_comma_separated(&jwts)); + + // volumes + let mut volumes = vec![config_volume.clone()]; + volumes.extend(chain_spec_volume.clone()); + + match loader { + SignerLoader::File { key_path } => { + volumes.push(Volumes::Simple(format!( + "{}:{}:ro", + key_path.display(), + SIGNER_DEFAULT + ))); + let (k, v) = get_env_val(SIGNER_KEYS_ENV, SIGNER_DEFAULT); + signer_envs.insert(k, v); + } + SignerLoader::ValidatorsDir { keys_path, secrets_path, format: _ } => { + volumes.push(Volumes::Simple(format!( + "{}:{}:ro", + keys_path.display(), + SIGNER_DIR_KEYS_DEFAULT + ))); + let (k, v) = get_env_val(SIGNER_DIR_KEYS_ENV, SIGNER_DIR_KEYS_DEFAULT); + signer_envs.insert(k, v); + + volumes.push(Volumes::Simple(format!( + "{}:{}:ro", + secrets_path.display(), + SIGNER_DIR_SECRETS_DEFAULT + ))); + let (k, v) = + get_env_val(SIGNER_DIR_SECRETS_ENV, SIGNER_DIR_SECRETS_DEFAULT); + signer_envs.insert(k, v); + } + }; + + if let Some(store) = store { + match store { + ProxyStore::File { proxy_dir } => { + volumes.push(Volumes::Simple(format!( + "{}:{}:rw", + proxy_dir.display(), + PROXY_DIR_DEFAULT + ))); + let (k, v) = get_env_val(PROXY_DIR_ENV, PROXY_DIR_DEFAULT); + signer_envs.insert(k, v); + } + ProxyStore::ERC2335 { keys_path, secrets_path } => { + volumes.push(Volumes::Simple(format!( + "{}:{}:rw", + keys_path.display(), + PROXY_DIR_KEYS_DEFAULT + ))); + let (k, v) = get_env_val(PROXY_DIR_KEYS_ENV, PROXY_DIR_KEYS_DEFAULT); + signer_envs.insert(k, v); + + volumes.push(Volumes::Simple(format!( + "{}:{}:rw", + secrets_path.display(), + PROXY_DIR_SECRETS_DEFAULT + ))); + let (k, v) = + get_env_val(PROXY_DIR_SECRETS_ENV, PROXY_DIR_SECRETS_DEFAULT); + signer_envs.insert(k, v); + } + } + } - Ok(signer_service) -} + volumes.extend(get_log_volume(&cb_config.logs, SIGNER_MODULE_NAME)); + + // networks + let signer_networks = vec![SIGNER_NETWORK.to_owned()]; + + let signer_service = Service { + container_name: Some("cb_signer".to_owned()), + image: Some(signer_config.docker_image), + networks: Networks::Simple(signer_networks), + ports: Ports::Short(ports), + volumes, + environment: Environment::KvPair(signer_envs), + healthcheck: Some(Healthcheck { + test: Some(HealthcheckTest::Single(format!( + "curl -f http://localhost:{signer_port}/status" + ))), + interval: Some("30s".into()), + timeout: Some("5s".into()), + retries: 3, + start_interval: None, + start_period: Some("5s".into()), + disable: false, + }), + ..Service::default() + }; -/// Creates a Commit-Boost module service -fn create_module_service( - module: &StaticModuleConfig, - signer_server: &str, - service_config: &mut ServiceCreationInfo, -) -> eyre::Result<(String, Service)> { - let cb_config = &service_config.config_info.cb_config; - let config_volume = &service_config.config_info.config_volume; - let metrics_port = service_config.metrics_port; - let module_cid = format!("cb_{}", module.id.to_lowercase()); - - let module_service = match module.kind { - // a commit module needs a JWT and access to the signer network - ModuleKind::Commit => { - let mut ports = vec![]; - - let jwt_secret = random_jwt_secret(); - let jwt_name = format!("CB_JWT_{}", module.id.to_uppercase()); - - // module ids are assumed unique, so envs dont override each other - let mut module_envs = IndexMap::from([ - get_env_val(MODULE_ID_ENV, &module.id), - get_env_val(CONFIG_ENV, CONFIG_DEFAULT), - get_env_interp(MODULE_JWT_ENV, &jwt_name), - get_env_val(SIGNER_URL_ENV, signer_server), - ]); - - if cb_config.signer_uses_tls() { - let env_val = get_env_val( - SIGNER_TLS_CERTIFICATES_PATH_ENV, - SIGNER_TLS_CERTIFICATES_PATH_DEFAULT, - ); - module_envs.insert(env_val.0, env_val.1); + services.insert("cb_signer".to_owned(), Some(signer_service)); } - - // Pass on the env variables - if let Some(envs) = &module.env { - for (k, v) in envs { - module_envs.insert(k.clone(), Some(SingleValue::String(v.clone()))); + SignerType::Dirk { cert_path, key_path, secrets_path, ca_cert_path, store, .. } => { + let mut signer_envs = IndexMap::from([ + get_env_val(CONFIG_ENV, CONFIG_DEFAULT), + get_env_same(JWTS_ENV), + get_env_val(DIRK_CERT_ENV, DIRK_CERT_DEFAULT), + get_env_val(DIRK_KEY_ENV, DIRK_KEY_DEFAULT), + get_env_val(DIRK_DIR_SECRETS_ENV, DIRK_DIR_SECRETS_DEFAULT), + ]); + + // Bind the signer API to 0.0.0.0 + let container_endpoint = + SocketAddr::from((Ipv4Addr::UNSPECIFIED, signer_config.port)); + let (key, val) = get_env_val(SIGNER_ENDPOINT_ENV, &container_endpoint.to_string()); + signer_envs.insert(key, val); + + let host_endpoint = SocketAddr::from((signer_config.host, signer_config.port)); + let mut ports = vec![format!("{}:{}", host_endpoint, signer_config.port)]; + warnings.push(format!("cb_signer has an exported port on {}", signer_config.port)); + + if let Some((key, val)) = chain_spec_env.clone() { + signer_envs.insert(key, val); + } + if let Some(metrics_config) = &cb_config.metrics && + metrics_config.enabled + { + let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); + ports.push(format!("{host_endpoint}:{metrics_port}")); + warnings.push(format!("cb_signer has an exported port on {metrics_port}")); + targets.push(format!("{host_endpoint}")); + let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); + signer_envs.insert(key, val); + } + if log_to_file { + let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); + signer_envs.insert(key, val); } - }; - // volumes - let mut module_volumes = vec![config_volume.clone()]; - module_volumes.extend(get_log_volume(&cb_config.logs, &module.id)?); - if let Some(certs_path) = cb_config.signer_certs_path() { - module_volumes.push(create_cert_binding(certs_path)); - } + // write jwts to env + envs.insert(JWTS_ENV.into(), format_comma_separated(&jwts)); + + // volumes + let mut volumes = vec![ + config_volume.clone(), + Volumes::Simple(format!("{}:{}:ro", cert_path.display(), DIRK_CERT_DEFAULT)), + Volumes::Simple(format!("{}:{}:ro", key_path.display(), DIRK_KEY_DEFAULT)), + Volumes::Simple(format!( + "{}:{}", + secrets_path.display(), + DIRK_DIR_SECRETS_DEFAULT + )), + ]; + volumes.extend(chain_spec_volume.clone()); + volumes.extend(get_log_volume(&cb_config.logs, SIGNER_MODULE_NAME)); + + if let Some(ca_cert_path) = ca_cert_path { + volumes.push(Volumes::Simple(format!( + "{}:{}:ro", + ca_cert_path.display(), + DIRK_CA_CERT_DEFAULT + ))); + let (key, val) = get_env_val(DIRK_CA_CERT_ENV, DIRK_CA_CERT_DEFAULT); + signer_envs.insert(key, val); + } - // Chain spec env/volume - if let Some(spec) = &service_config.chain_spec { - module_envs.insert(spec.env.0.clone(), spec.env.1.clone()); - module_volumes.push(spec.volume.clone()); - } + match store { + Some(ProxyStore::File { proxy_dir }) => { + volumes.push(Volumes::Simple(format!( + "{}:{}", + proxy_dir.display(), + PROXY_DIR_DEFAULT + ))); + let (key, val) = get_env_val(PROXY_DIR_ENV, PROXY_DIR_DEFAULT); + signer_envs.insert(key, val); + } + Some(ProxyStore::ERC2335 { .. }) => { + panic!("ERC2335 store not supported with Dirk signer"); + } + None => {} + } - if let Some(metrics_config) = &cb_config.metrics && - metrics_config.enabled - { - let host_endpoint = SocketAddr::from((metrics_config.host, metrics_port)); - ports.push(format!("{host_endpoint}:{metrics_port}")); - service_config - .warnings - .push(format!("{module_cid} has an exported port on {metrics_port}")); - service_config.targets.push(format!("{host_endpoint}")); - let (key, val) = get_env_uval(METRICS_PORT_ENV, metrics_port as u64); - module_envs.insert(key, val); - - service_config.metrics_port += 1; - } + // networks + let signer_networks = vec![SIGNER_NETWORK.to_owned()]; + + let signer_service = Service { + container_name: Some("cb_signer".to_owned()), + image: Some(signer_config.docker_image), + networks: Networks::Simple(signer_networks), + ports: Ports::Short(ports), + volumes, + environment: Environment::KvPair(signer_envs), + healthcheck: Some(Healthcheck { + test: Some(HealthcheckTest::Single(format!( + "curl -f http://localhost:{signer_port}/status" + ))), + interval: Some("30s".into()), + timeout: Some("5s".into()), + retries: 3, + start_interval: None, + start_period: Some("5s".into()), + disable: false, + }), + ..Service::default() + }; - // Logging - if cb_config.logs.file.enabled { - let (key, val) = get_env_val(LOGS_DIR_ENV, LOGS_DIR_DEFAULT); - module_envs.insert(key, val); + services.insert("cb_signer".to_owned(), Some(signer_service)); } - - // write jwts to env - service_config.envs.insert(jwt_name.clone(), jwt_secret.clone()); - service_config.jwts.insert(module.id.clone(), jwt_secret); - - // Dependencies - let mut module_dependencies = IndexMap::new(); - module_dependencies.insert("cb_signer".into(), DependsCondition { - condition: "service_healthy".into(), - }); - - // Create the service - let module_networks = vec![SIGNER_NETWORK.to_owned()]; - Service { - container_name: Some(module_cid.clone()), - image: Some(module.docker_image.clone()), - networks: Networks::Simple(module_networks), - ports: Ports::Short(ports), - volumes: module_volumes, - environment: Environment::KvPair(module_envs), - depends_on: if let Some(SignerConfig { inner: SignerType::Remote { .. }, .. }) = - &cb_config.signer - { - DependsOnOptions::Simple(vec![]) - } else { - DependsOnOptions::Conditional(module_dependencies) - }, - env_file: module.env_file.clone().map(EnvFile::Simple), - ..Service::default() + SignerType::Remote { .. } => { + panic!("Signer module required but remote config provided"); } } - }; + } - Ok((module_cid, module_service)) -} + let mut compose = Compose::default(); + + if needs_signer_module { + compose.networks.0.insert( + SIGNER_NETWORK.to_owned(), + MapOrEmpty::Map(NetworkSettings { + driver: Some("bridge".to_owned()), + ..NetworkSettings::default() + }), + ); + } -/// Writes the docker compose file to disk and prints any warnings -fn write_compose_file( - compose: &Compose, - output_path: &Path, - service_config: &ServiceCreationInfo, -) -> Result<()> { - let compose_str = serde_yaml::to_string(compose)?; - std::fs::write(output_path, compose_str)?; - if !service_config.warnings.is_empty() { + compose.services = Services(services); + + // write compose to file + let compose_str = serde_yaml::to_string(&compose)?; + let compose_path = Path::new(&output_dir).join(CB_COMPOSE_FILE); + std::fs::write(&compose_path, compose_str)?; + if !warnings.is_empty() { println!(); - for exposed_port in &service_config.warnings { + for exposed_port in warnings { println!("Warning: {exposed_port}"); } println!() } // if file logging is enabled, warn about permissions - let cb_config = &service_config.config_info.cb_config; if cb_config.logs.file.enabled { - let log_dir = &cb_config.logs.file.dir_path; + let log_dir = cb_config.logs.file.dir_path; println!( "Warning: file logging is enabled, you may need to update permissions for the logs directory. e.g. with:\n\t`sudo chown -R 10001:10001 {}`", log_dir.display() ); println!() } - println!("Docker Compose file written to: {output_path:?}"); - Ok(()) -} -/// Writes the envs to a .env file -fn write_env_file(envs: &IndexMap, output_path: &Path) -> Result<()> { - let envs_str = { - let mut envs_str = String::new(); - for (k, v) in envs { - envs_str.push_str(&format!("{k}={v}\n")); - } - envs_str - }; - std::fs::write(output_path, envs_str)?; - println!("Env file written to: {output_path:?}"); + println!("Docker Compose file written to: {compose_path:?}"); + + // write prometheus targets to file + if !targets.is_empty() { + let targets = targets.join(", "); + println!("Note: Make sure to add these targets for Prometheus to scrape: {targets}"); + println!( + "Check out the docs on how to configure Prometheus/Grafana/cAdvisor: https://commit-boost.github.io/commit-boost-client/get_started/running/metrics" + ); + } + + if envs.is_empty() { + println!("Run with:\n\tdocker compose -f {compose_path:?} up -d"); + } else { + // write envs to .env file + let envs_str = { + let mut envs_str = String::new(); + for (k, v) in envs { + envs_str.push_str(&format!("{k}={v}\n")); + } + envs_str + }; + let env_path = Path::new(&output_dir).join(CB_ENV_FILE); + std::fs::write(&env_path, envs_str)?; + println!("Env file written to: {env_path:?}"); + + println!(); + println!("Run with:\n\tdocker compose --env-file {env_path:?} -f {compose_path:?} up -d"); + println!("Stop with:\n\tdocker compose --env-file {env_path:?} -f {compose_path:?} down"); + } + Ok(()) } @@ -781,1013 +596,22 @@ fn get_env_uval(k: &str, v: u64) -> (String, Option) { (k.into(), Some(SingleValue::Unsigned(v))) } -fn get_log_volume(config: &LogsSettings, module_id: &str) -> eyre::Result> { - if !config.file.enabled { - return Ok(None); - } - let p = config.file.dir_path.join(module_id.to_lowercase()); - let host_path = p - .to_str() - .ok_or_else(|| eyre::eyre!("Log directory path is not valid UTF-8: {}", p.display()))?; - Ok(Some(Volumes::Simple(format!("{host_path}:{LOGS_DIR_DEFAULT}")))) +// fn get_env_bool(k: &str, v: bool) -> (String, Option) { +// (k.into(), Some(SingleValue::Bool(v))) +// } + +fn get_log_volume(config: &LogsSettings, module_id: &str) -> Option { + config.file.enabled.then_some({ + let p = config.file.dir_path.join(module_id.to_lowercase()); + Volumes::Simple(format!( + "{}:{}", + p.to_str().expect("could not convert pathbuf to str"), + LOGS_DIR_DEFAULT + )) + }) } /// Formats as a comma separated list of key=value fn format_comma_separated(map: &IndexMap) -> String { map.iter().map(|(k, v)| format!("{k}={v}")).collect::>().join(",") } - -fn create_cert_binding(certs_path: &Path) -> Volumes { - Volumes::Simple(format!( - "{}:{}/{}:ro", - certs_path.join(SIGNER_TLS_CERTIFICATE_NAME).display(), - SIGNER_TLS_CERTIFICATES_PATH_DEFAULT, - SIGNER_TLS_CERTIFICATE_NAME - )) -} - -/// Adds the TLS cert and key bindings to the provided volumes list -fn add_tls_certs_volume(volumes: &mut Vec, certs_path: &Path) -> Result<()> { - if !certs_path.try_exists()? { - std::fs::create_dir(certs_path)?; - } - - if !certs_path.join(SIGNER_TLS_CERTIFICATE_NAME).try_exists()? || - !certs_path.join(SIGNER_TLS_KEY_NAME).try_exists()? - { - return Err(eyre::eyre!( - "Signer TLS certificate or key not found at {}, please provide a valid certificate and key or create them", - certs_path.display() - )); - } - - volumes.push(create_cert_binding(certs_path)); - volumes.push(Volumes::Simple(format!( - "{}:{}/{}:ro", - certs_path.join(SIGNER_TLS_KEY_NAME).display(), - SIGNER_TLS_CERTIFICATES_PATH_DEFAULT, - SIGNER_TLS_KEY_NAME - ))); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use cb_common::{ - config::{ - CommitBoostConfig, FileLogSettings, LogsSettings, MetricsConfig, StdoutLogSettings, - TlsMode, - }, - signer::{ProxyStore, SignerLoader}, - }; - use docker_compose_types::{Environment, Ports, SingleValue, Volumes}; - - use super::*; - - // ------------------------------------------------------------------------- - // Shared test fixtures - // ------------------------------------------------------------------------- - - fn logs_disabled() -> LogsSettings { - LogsSettings::default() - } - - fn logs_enabled(dir: &str) -> LogsSettings { - LogsSettings { - stdout: StdoutLogSettings::default(), - file: FileLogSettings { - enabled: true, - dir_path: dir.into(), - ..FileLogSettings::default() - }, - } - } - - /// Deserialize a minimal PBS-only `CommitBoostConfig` from inline TOML. - /// No relays, so `validate()` won't make network calls. - fn minimal_cb_config() -> CommitBoostConfig { - toml::from_str( - r#" - chain = "Holesky" - [pbs] - docker_image = "ghcr.io/commit-boost/pbs:latest" - "#, - ) - .expect("valid minimal test config") - } - - fn minimal_service_config() -> ServiceCreationInfo { - ServiceCreationInfo { - config_info: CommitBoostConfigInfo { - cb_config: minimal_cb_config(), - config_volume: Volumes::Simple("./cb.toml:/cb.toml:ro".into()), - }, - envs: IndexMap::new(), - targets: Vec::new(), - warnings: Vec::new(), - jwts: IndexMap::new(), - chain_spec: None, - metrics_port: 9100, - } - } - - fn metrics_config() -> MetricsConfig { - MetricsConfig { - enabled: true, - host: "127.0.0.1".parse().expect("valid IP"), - start_port: 9100, - } - } - - // ------------------------------------------------------------------------- - // Service inspection helpers - // ------------------------------------------------------------------------- - - fn env_str(service: &Service, key: &str) -> Option { - match &service.environment { - Environment::KvPair(map) => map.get(key).and_then(|v| match v { - Some(SingleValue::String(s)) => Some(s.clone()), - _ => None, - }), - _ => None, - } - } - - fn env_u64(service: &Service, key: &str) -> Option { - match &service.environment { - Environment::KvPair(map) => map.get(key).and_then(|v| match v { - Some(SingleValue::Unsigned(n)) => Some(*n), - _ => None, - }), - _ => None, - } - } - - fn has_env_key(service: &Service, key: &str) -> bool { - match &service.environment { - Environment::KvPair(map) => map.contains_key(key), - _ => false, - } - } - - fn has_volume(service: &Service, substr: &str) -> bool { - service.volumes.iter().any(|v| matches!(v, Volumes::Simple(s) if s.contains(substr))) - } - - fn get_healthcheck_cmd(service: &Service) -> Option { - service.healthcheck.as_ref().and_then(|hc| match &hc.test { - Some(HealthcheckTest::Single(cmd)) => Some(cmd.clone()), - _ => None, - }) - } - - fn has_port(service: &Service, substr: &str) -> bool { - match &service.ports { - Ports::Short(ports) => ports.iter().any(|p| p.contains(substr)), - _ => false, - } - } - - // --- get_env_val --- - - #[test] - fn test_get_env_val_returns_string_pair() { - let (key, val) = get_env_val("MY_KEY", "my_value"); - assert_eq!(key, "MY_KEY"); - assert_eq!(val, Some(SingleValue::String("my_value".into()))); - } - - #[test] - fn test_get_env_val_empty_value() { - let (key, val) = get_env_val("EMPTY", ""); - assert_eq!(key, "EMPTY"); - assert_eq!(val, Some(SingleValue::String("".into()))); - } - - // --- get_env_uval --- - - #[test] - fn test_get_env_uval_returns_unsigned_pair() { - let (key, val) = get_env_uval("PORT", 9100); - assert_eq!(key, "PORT"); - assert_eq!(val, Some(SingleValue::Unsigned(9100))); - } - - // --- get_env_same --- - - #[test] - fn test_get_env_same_interpolates_self() { - let (key, val) = get_env_same("JWTS_ENV"); - assert_eq!(key, "JWTS_ENV"); - assert_eq!(val, Some(SingleValue::String("${JWTS_ENV}".into()))); - } - - // --- get_env_interp --- - - #[test] - fn test_get_env_interp_different_key_and_var() { - let (key, val) = get_env_interp("MODULE_JWT_ENV", "CB_JWT_MY_MODULE"); - assert_eq!(key, "MODULE_JWT_ENV"); - assert_eq!(val, Some(SingleValue::String("${CB_JWT_MY_MODULE}".into()))); - } - - // --- format_comma_separated --- - - #[test] - fn test_format_comma_separated_empty() { - let map = IndexMap::new(); - assert_eq!(format_comma_separated(&map), ""); - } - - #[test] - fn test_format_comma_separated_single_entry() { - let mut map = IndexMap::new(); - map.insert(ModuleId::from("module_a".to_owned()), "secret123".into()); - assert_eq!(format_comma_separated(&map), "module_a=secret123"); - } - - #[test] - fn test_format_comma_separated_multiple_entries_preserves_order() { - let mut map = IndexMap::new(); - map.insert(ModuleId::from("module_a".to_owned()), "jwt_a".into()); - map.insert(ModuleId::from("module_b".to_owned()), "jwt_b".into()); - map.insert(ModuleId::from("module_c".to_owned()), "jwt_c".into()); - assert_eq!(format_comma_separated(&map), "module_a=jwt_a,module_b=jwt_b,module_c=jwt_c"); - } - - // --- get_log_volume --- - - #[test] - fn test_get_log_volume_disabled_returns_none() -> eyre::Result<()> { - let logs = logs_disabled(); - let result = get_log_volume(&logs, "cb_pbs")?; - assert!(result.is_none()); - Ok(()) - } - - #[test] - fn test_get_log_volume_enabled_returns_correct_volume() -> eyre::Result<()> { - let logs = logs_enabled("/var/log/commit-boost"); - let result = get_log_volume(&logs, "cb_pbs")?; - let volume = result.expect("expected a volume when file logging is enabled"); - assert_eq!( - volume, - Volumes::Simple(format!("/var/log/commit-boost/cb_pbs:{LOGS_DIR_DEFAULT}")) - ); - Ok(()) - } - - #[test] - fn test_get_log_volume_lowercases_module_id() -> eyre::Result<()> { - let logs = logs_enabled("/logs"); - let result = get_log_volume(&logs, "MY_MODULE")?; - let volume = result.expect("expected a volume when file logging is enabled"); - assert_eq!(volume, Volumes::Simple(format!("/logs/my_module:{LOGS_DIR_DEFAULT}"))); - Ok(()) - } - - #[test] - fn test_get_log_volume_enabled_with_nested_dir() -> eyre::Result<()> { - let logs = logs_enabled("/home/user/cb/logs"); - let result = get_log_volume(&logs, "cb_signer")?; - let volume = result.expect("expected a volume when file logging is enabled"); - assert_eq!( - volume, - Volumes::Simple(format!("/home/user/cb/logs/cb_signer:{LOGS_DIR_DEFAULT}")) - ); - Ok(()) - } - - // ------------------------------------------------------------------------- - // write_env_file - // ------------------------------------------------------------------------- - - #[test] - fn test_write_env_file_empty_map() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let path = dir.path().join(".cb.env"); - write_env_file(&IndexMap::new(), &path)?; - let contents = std::fs::read_to_string(&path)?; - assert_eq!(contents, ""); - Ok(()) - } - - #[test] - fn test_write_env_file_single_entry() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let path = dir.path().join(".cb.env"); - let mut map = IndexMap::new(); - map.insert("MY_KEY".to_owned(), "my_value".to_owned()); - write_env_file(&map, &path)?; - let contents = std::fs::read_to_string(&path)?; - assert_eq!(contents, "MY_KEY=my_value\n"); - Ok(()) - } - - #[test] - fn test_write_env_file_multiple_entries_preserves_order() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let path = dir.path().join(".cb.env"); - let mut map = IndexMap::new(); - map.insert("KEY_A".to_owned(), "val_a".to_owned()); - map.insert("KEY_B".to_owned(), "val_b".to_owned()); - map.insert("KEY_C".to_owned(), "val_c".to_owned()); - write_env_file(&map, &path)?; - let contents = std::fs::read_to_string(&path)?; - assert_eq!(contents, "KEY_A=val_a\nKEY_B=val_b\nKEY_C=val_c\n"); - Ok(()) - } - - // ------------------------------------------------------------------------- - // write_compose_file - // ------------------------------------------------------------------------- - - #[test] - fn test_write_compose_file_creates_valid_yaml() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let path = dir.path().join(CB_COMPOSE_FILE); - let compose = docker_compose_types::Compose::default(); - let service_config = minimal_service_config(); - write_compose_file(&compose, &path, &service_config)?; - assert!(path.exists()); - let contents = std::fs::read_to_string(&path)?; - assert!(!contents.is_empty()); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_pbs_service - // ------------------------------------------------------------------------- - - #[test] - fn test_create_pbs_service_basic() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let service = create_pbs_service(&mut sc)?; - - assert_eq!(service.container_name.as_deref(), Some("cb_pbs")); - assert_eq!(service.image.as_deref(), Some("ghcr.io/commit-boost/pbs:latest")); - assert!(env_str(&service, CONFIG_ENV).is_some()); - assert!(env_str(&service, PBS_ENDPOINT_ENV).is_some()); - assert!(service.healthcheck.is_some()); - Ok(()) - } - - #[test] - fn test_create_pbs_service_exposes_pbs_port() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let service = create_pbs_service(&mut sc)?; - // Default PBS port is 18550 - assert!(has_port(&service, "18550")); - Ok(()) - } - - #[test] - fn test_create_pbs_service_with_metrics() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - sc.config_info.cb_config.metrics = Some(metrics_config()); - sc.metrics_port = 9100; - let service = create_pbs_service(&mut sc)?; - - assert_eq!(env_u64(&service, METRICS_PORT_ENV), Some(9100)); - assert!(has_port(&service, "9100")); - // port counter incremented - assert_eq!(sc.metrics_port, 9101); - // target added for prometheus - assert!(!sc.targets.is_empty()); - Ok(()) - } - - #[test] - fn test_create_pbs_service_with_file_logging() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - sc.config_info.cb_config.logs = logs_enabled("/var/log/cb"); - let service = create_pbs_service(&mut sc)?; - - assert!(env_str(&service, LOGS_DIR_ENV).is_some()); - assert!(has_volume(&service, "pbs")); - Ok(()) - } - - #[test] - fn test_create_pbs_service_with_chain_spec() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - sc.chain_spec = Some(ServiceChainSpecInfo { - env: get_env_val(CHAIN_SPEC_ENV, "/chain.json"), - volume: Volumes::Simple("/host/chain.json:/chain.json:ro".into()), - }); - let service = create_pbs_service(&mut sc)?; - - assert_eq!(env_str(&service, CHAIN_SPEC_ENV), Some("/chain.json".into())); - assert!(has_volume(&service, "chain.json")); - Ok(()) - } - - #[test] - fn test_create_pbs_service_no_metrics_no_metrics_env() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let service = create_pbs_service(&mut sc)?; - assert!(!has_env_key(&service, METRICS_PORT_ENV)); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_signer_service_local - // ------------------------------------------------------------------------- - - fn local_signer_config() -> SignerConfig { - toml::from_str( - r#" - [local.loader] - key_path = "/keys/keys.json" - "#, - ) - .expect("valid local signer config") - } - - #[test] - fn test_create_signer_service_local_file_loader() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = local_signer_config(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &None)?; - - assert_eq!(service.container_name.as_deref(), Some("cb_signer")); - assert!(env_str(&service, SIGNER_KEYS_ENV).is_some()); - assert!(has_volume(&service, "keys.json")); - Ok(()) - } - - #[test] - fn test_create_signer_service_local_validators_dir_loader() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = local_signer_config(); - let loader = SignerLoader::ValidatorsDir { - keys_path: "/keys".into(), - secrets_path: "/secrets".into(), - format: cb_common::signer::ValidatorKeysFormat::Lighthouse, - }; - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &None)?; - - assert!(env_str(&service, SIGNER_DIR_KEYS_ENV).is_some()); - assert!(env_str(&service, SIGNER_DIR_SECRETS_ENV).is_some()); - assert!(has_volume(&service, "/keys")); - assert!(has_volume(&service, "/secrets")); - Ok(()) - } - - #[test] - fn test_create_signer_service_local_with_file_proxy_store() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = local_signer_config(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - let store = Some(ProxyStore::File { proxy_dir: "/proxies".into() }); - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &store)?; - - assert!(env_str(&service, PROXY_DIR_ENV).is_some()); - assert!(has_volume(&service, "/proxies")); - Ok(()) - } - - #[test] - fn test_create_signer_service_local_with_erc2335_proxy_store() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = local_signer_config(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - let store = Some(ProxyStore::ERC2335 { - keys_path: "/proxy/keys".into(), - secrets_path: "/proxy/secrets".into(), - }); - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &store)?; - - assert!(env_str(&service, PROXY_DIR_KEYS_ENV).is_some()); - assert!(env_str(&service, PROXY_DIR_SECRETS_ENV).is_some()); - assert!(has_volume(&service, "/proxy/keys")); - assert!(has_volume(&service, "/proxy/secrets")); - Ok(()) - } - - #[test] - fn test_create_signer_service_local_jwts_written_to_envs() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - sc.jwts.insert(ModuleId::from("MY_MODULE".to_owned()), "jwt_secret_abc".into()); - let signer_config = local_signer_config(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - create_signer_service_local(&mut sc, &signer_config, &loader, &None)?; - - // JWTS_ENV written as comma-separated to service_config.envs - let jwts_val = sc.envs.get(JWTS_ENV).expect("JWTS_ENV must be set in envs"); - assert!(jwts_val.contains("MY_MODULE=jwt_secret_abc")); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_signer_service_dirk - // ------------------------------------------------------------------------- - - fn dirk_signer_config() -> SignerConfig { - toml::from_str( - r#" - docker_image = "commitboost_signer" - [dirk] - cert_path = "/certs/client.crt" - key_path = "/certs/client.key" - secrets_path = "/dirk_secrets" - [[dirk.hosts]] - url = "https://gateway.dirk.url" - wallets = ["wallet1"] - "#, - ) - .expect("valid dirk signer config") - } - - #[test] - fn test_create_signer_service_dirk_basic() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - assert_eq!(service.container_name.as_deref(), Some("cb_signer")); - assert!(env_str(&service, DIRK_CERT_ENV).is_some()); - assert!(env_str(&service, DIRK_KEY_ENV).is_some()); - assert!(env_str(&service, DIRK_DIR_SECRETS_ENV).is_some()); - assert!(has_env_key(&service, ADMIN_JWT_ENV)); - assert!(has_env_key(&service, SIGNER_TLS_CERTIFICATES_PATH_ENV)); - assert!(has_volume(&service, "client.crt")); - assert!(has_volume(&service, "client.key")); - assert!(has_volume(&service, "dirk_secrets")); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_generates_admin_jwt() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - let admin_jwt = sc.envs.get(ADMIN_JWT_ENV).expect("ADMIN_JWT_ENV must be set"); - assert!(!admin_jwt.is_empty(), "admin JWT secret must not be empty"); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_with_ca_cert() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let ca_cert = Some(PathBuf::from("/certs/ca.crt")); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &ca_cert, - &None, - )?; - - assert!(env_str(&service, DIRK_CA_CERT_ENV).is_some()); - assert!(has_volume(&service, "ca.crt")); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_without_ca_cert() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - assert!(!has_env_key(&service, DIRK_CA_CERT_ENV)); - assert!(!has_volume(&service, "ca.crt")); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_with_file_proxy_store() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let store = Some(ProxyStore::File { proxy_dir: "/proxies".into() }); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &store, - )?; - - assert!(env_str(&service, PROXY_DIR_ENV).is_some()); - assert!(has_volume(&service, "/proxies")); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_erc2335_store_returns_error() { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let store = Some(ProxyStore::ERC2335 { - keys_path: "/proxy/keys".into(), - secrets_path: "/proxy/secrets".into(), - }); - let result = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &store, - ); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("ERC2335")); - } - - // ------------------------------------------------------------------------- - // create_module_service - // ------------------------------------------------------------------------- - - fn commit_module() -> StaticModuleConfig { - toml::from_str( - r#" - id = "DA_COMMIT" - type = "commit" - docker_image = "test_da_commit" - signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" - "#, - ) - .expect("valid module config") - } - - #[test] - fn test_create_module_service_container_name_format() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = minimal_service_config(); - let (cid, _) = create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - assert_eq!(cid, "cb_da_commit"); - Ok(()) - } - - #[test] - fn test_create_module_service_sets_required_envs() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = minimal_service_config(); - let (_, service) = create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - - assert!(env_str(&service, MODULE_ID_ENV).is_some()); - assert!(env_str(&service, CONFIG_ENV).is_some()); - assert!(env_str(&service, SIGNER_URL_ENV) == Some("http://cb_signer:20000".into())); - Ok(()) - } - - #[test] - fn test_create_module_service_jwt_written_to_service_config_envs() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = minimal_service_config(); - create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - - // JWT env var should be in the outer service_config.envs (for .env file) - let jwt_key = format!("CB_JWT_{}", "DA_COMMIT".to_uppercase()); - assert!(sc.envs.contains_key(&jwt_key)); - // and also recorded in jwts map - assert!(sc.jwts.contains_key(&ModuleId::from("DA_COMMIT".to_owned()))); - Ok(()) - } - - #[test] - fn test_create_module_service_custom_env_forwarded() -> eyre::Result<()> { - let mut module = commit_module(); - let mut env_map = std::collections::HashMap::new(); - env_map.insert("SOME_ENV_VAR".to_owned(), "some_value".to_owned()); - module.env = Some(env_map); - - let mut sc = minimal_service_config(); - let (_, service) = create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - - assert_eq!(env_str(&service, "SOME_ENV_VAR"), Some("some_value".into())); - Ok(()) - } - - #[test] - fn test_create_module_service_depends_on_signer() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = minimal_service_config(); - let (_, service) = create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - - match &service.depends_on { - docker_compose_types::DependsOnOptions::Conditional(deps) => { - assert!(deps.contains_key("cb_signer")); - } - docker_compose_types::DependsOnOptions::Simple(deps) => { - // Remote signer path returns empty depends_on — but this is a local signer - // config (signer is None), so it still depends on cb_signer - assert!(deps.is_empty(), "unexpected empty depends_on for local signer"); - } - } - Ok(()) - } - - // ------------------------------------------------------------------------- - // Helpers for TLS tests - // ------------------------------------------------------------------------- - - fn local_signer_config_with_tls(certs_path: PathBuf) -> SignerConfig { - let mut config = local_signer_config(); - config.tls_mode = TlsMode::Certificate(certs_path); - config - } - - /// Returns a `ServiceCreationInfo` whose CB config has `pbs.with_signer = - /// true` and a local signer with `TlsMode::Certificate(certs_path)`. - fn service_config_with_tls(certs_path: PathBuf) -> ServiceCreationInfo { - let mut sc = minimal_service_config(); - sc.config_info.cb_config.pbs.with_signer = true; - sc.config_info.cb_config.signer = Some(local_signer_config_with_tls(certs_path)); - sc - } - - // ------------------------------------------------------------------------- - // create_cert_binding - // ------------------------------------------------------------------------- - - #[test] - fn test_create_cert_binding_volume_string() { - let certs_path = Path::new("/my/certs"); - let vol = create_cert_binding(certs_path); - let expected = format!( - "/my/certs/{}:{}/{}:ro", - SIGNER_TLS_CERTIFICATE_NAME, - SIGNER_TLS_CERTIFICATES_PATH_DEFAULT, - SIGNER_TLS_CERTIFICATE_NAME - ); - assert_eq!(vol, Volumes::Simple(expected)); - } - - // ------------------------------------------------------------------------- - // add_tls_certs_volume - // ------------------------------------------------------------------------- - - #[test] - fn test_add_tls_certs_volume_happy_path() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path(); - std::fs::write(certs_path.join(SIGNER_TLS_CERTIFICATE_NAME), b"cert")?; - std::fs::write(certs_path.join(SIGNER_TLS_KEY_NAME), b"key")?; - - let mut volumes = vec![]; - add_tls_certs_volume(&mut volumes, certs_path)?; - - assert_eq!(volumes.len(), 2); - assert!( - matches!(&volumes[0], Volumes::Simple(s) if s.contains(SIGNER_TLS_CERTIFICATE_NAME)) - ); - assert!(matches!(&volumes[1], Volumes::Simple(s) if s.contains(SIGNER_TLS_KEY_NAME))); - Ok(()) - } - - #[test] - fn test_add_tls_certs_volume_missing_cert_returns_error() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path(); - std::fs::write(certs_path.join(SIGNER_TLS_KEY_NAME), b"key")?; - - let result = add_tls_certs_volume(&mut vec![], certs_path); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("certificate or key not found")); - Ok(()) - } - - #[test] - fn test_add_tls_certs_volume_missing_key_returns_error() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path(); - std::fs::write(certs_path.join(SIGNER_TLS_CERTIFICATE_NAME), b"cert")?; - - let result = add_tls_certs_volume(&mut vec![], certs_path); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("certificate or key not found")); - Ok(()) - } - - #[test] - fn test_add_tls_certs_volume_missing_both_returns_error() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let result = add_tls_certs_volume(&mut vec![], dir.path()); - assert!(result.is_err()); - Ok(()) - } - - #[test] - fn test_add_tls_certs_volume_creates_missing_directory() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path().join("new_certs_dir"); - assert!(!certs_path.exists(), "pre-condition: directory must not exist yet"); - - let result = add_tls_certs_volume(&mut vec![], &certs_path); - - // Directory created even though cert/key are absent - assert!(certs_path.exists(), "directory should have been created"); - // cert/key still missing → error - assert!(result.is_err()); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_pbs_service – TLS cert volume/env - // ------------------------------------------------------------------------- - - #[test] - fn test_create_pbs_service_with_tls_adds_cert_env_and_volume() -> eyre::Result<()> { - let mut sc = service_config_with_tls(PathBuf::from("/my/certs")); - let service = create_pbs_service(&mut sc)?; - - assert!(has_env_key(&service, SIGNER_TLS_CERTIFICATES_PATH_ENV)); - assert!(has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - Ok(()) - } - - #[test] - fn test_create_pbs_service_without_tls_no_cert_env() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let service = create_pbs_service(&mut sc)?; - - assert!(!has_env_key(&service, SIGNER_TLS_CERTIFICATES_PATH_ENV)); - assert!(!has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_signer_service_local – TLS cert volumes - // ------------------------------------------------------------------------- - - #[test] - fn test_create_signer_service_local_with_tls_adds_cert_and_key_volumes() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path().to_path_buf(); - std::fs::write(certs_path.join(SIGNER_TLS_CERTIFICATE_NAME), b"cert")?; - std::fs::write(certs_path.join(SIGNER_TLS_KEY_NAME), b"key")?; - - let mut sc = service_config_with_tls(certs_path); - let signer_config = sc.config_info.cb_config.signer.clone().unwrap(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &None)?; - - assert!(has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - assert!(has_volume(&service, SIGNER_TLS_KEY_NAME)); - Ok(()) - } - - #[test] - fn test_create_signer_service_local_without_tls_no_cert_key_volumes() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = local_signer_config(); - let loader = SignerLoader::File { key_path: "/keys/keys.json".into() }; - let service = create_signer_service_local(&mut sc, &signer_config, &loader, &None)?; - - // SIGNER_TLS_CERTIFICATES_PATH_ENV is always emitted by the signer service, - // but no cert.pem / key.pem volume bindings should exist in insecure mode. - assert!(!has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - assert!(!has_volume(&service, SIGNER_TLS_KEY_NAME)); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_signer_service_dirk – TLS cert volumes - // ------------------------------------------------------------------------- - - #[test] - fn test_create_signer_service_dirk_with_tls_adds_cert_and_key_volumes() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path().to_path_buf(); - std::fs::write(certs_path.join(SIGNER_TLS_CERTIFICATE_NAME), b"cert")?; - std::fs::write(certs_path.join(SIGNER_TLS_KEY_NAME), b"key")?; - - let mut sc = service_config_with_tls(certs_path); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - assert!(has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - assert!(has_volume(&service, SIGNER_TLS_KEY_NAME)); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_without_tls_no_cert_key_volumes() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - assert!(!has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - assert!(!has_volume(&service, SIGNER_TLS_KEY_NAME)); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_healthcheck_uses_https_with_tls() -> eyre::Result<()> { - let dir = tempfile::tempdir()?; - let certs_path = dir.path().to_path_buf(); - std::fs::write(certs_path.join(SIGNER_TLS_CERTIFICATE_NAME), b"cert")?; - std::fs::write(certs_path.join(SIGNER_TLS_KEY_NAME), b"key")?; - - let mut sc = service_config_with_tls(certs_path); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - let cmd = get_healthcheck_cmd(&service).expect("healthcheck must be set"); - assert!(cmd.contains("https://"), "healthcheck must use https with TLS: {cmd}"); - assert!(cmd.contains("-k"), "healthcheck must use -k flag for self-signed certs: {cmd}"); - Ok(()) - } - - #[test] - fn test_create_signer_service_dirk_healthcheck_uses_http_without_tls() -> eyre::Result<()> { - let mut sc = minimal_service_config(); - let signer_config = dirk_signer_config(); - let service = create_signer_service_dirk( - &mut sc, - &signer_config, - Path::new("/certs/client.crt"), - Path::new("/certs/client.key"), - Path::new("/dirk_secrets"), - &None, - &None, - )?; - - let cmd = get_healthcheck_cmd(&service).expect("healthcheck must be set"); - assert!(cmd.contains("http://"), "healthcheck must use http without TLS: {cmd}"); - Ok(()) - } - - // ------------------------------------------------------------------------- - // create_module_service – TLS cert env/volume - // ------------------------------------------------------------------------- - - #[test] - fn test_create_module_service_with_signer_tls_adds_cert_env_and_volume() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = service_config_with_tls(PathBuf::from("/my/certs")); - let (_, service) = create_module_service(&module, "https://cb_signer:20000", &mut sc)?; - - assert!(has_env_key(&service, SIGNER_TLS_CERTIFICATES_PATH_ENV)); - assert!(has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - Ok(()) - } - - #[test] - fn test_create_module_service_without_signer_tls_no_cert_env() -> eyre::Result<()> { - let module = commit_module(); - let mut sc = minimal_service_config(); - let (_, service) = create_module_service(&module, "http://cb_signer:20000", &mut sc)?; - - assert!(!has_env_key(&service, SIGNER_TLS_CERTIFICATES_PATH_ENV)); - assert!(!has_volume(&service, SIGNER_TLS_CERTIFICATE_NAME)); - Ok(()) - } -} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 7257d53b..34170470 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1 +1,49 @@ -pub mod docker_init; +use std::path::PathBuf; + +use cb_common::utils::print_logo; +use clap::{Parser, Subcommand}; + +mod docker_init; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-cli")] +pub struct Args { + #[command(subcommand)] + pub cmd: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Generate the starting docker-compose file + Init { + /// Path to config file + #[arg(long("config"))] + config_path: PathBuf, + + /// Path to output files + #[arg(short, long("output"), default_value = "./")] + output_path: PathBuf, + }, +} + +impl Args { + pub async fn run(self) -> eyre::Result<()> { + print_logo(); + + match self.cmd { + Command::Init { config_path, output_path } => { + docker_init::handle_docker_init(config_path, output_path).await + } + } + } +} + +const LONG_ABOUT: &str = "Commit-Boost allows Ethereum validators to safely run MEV-Boost and community-built commitment protocols"; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-pbs")] +pub struct PbsArgs; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-signer")] +pub struct SignerArgs; diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index cbb62fb4..34b2f83d 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,7 +17,6 @@ base64.workspace = true bimap.workspace = true bytes.workspace = true cipher.workspace = true -const_format.workspace = true ctr.workspace = true derive_more.workspace = true docker-image.workspace = true @@ -28,10 +27,10 @@ eyre.workspace = true futures.workspace = true jsonwebtoken.workspace = true lazy_static.workspace = true +lh_bls.workspace = true lh_eth2.workspace = true lh_eth2_keystore.workspace = true lh_types.workspace = true -notify.workspace = true pbkdf2.workspace = true rand.workspace = true rayon.workspace = true @@ -52,7 +51,3 @@ tree_hash.workspace = true tree_hash_derive.workspace = true unicode-normalization.workspace = true url.workspace = true -reqwest-eventsource.workspace = true - -[dev-dependencies] - tempfile.workspace = true \ No newline at end of file diff --git a/crates/common/src/commit/client.rs b/crates/common/src/commit/client.rs index 98d8c26d..4e8e0961 100644 --- a/crates/common/src/commit/client.rs +++ b/crates/common/src/commit/client.rs @@ -1,29 +1,24 @@ -use std::path::PathBuf; +use std::time::{Duration, Instant}; use alloy::primitives::Address; use eyre::WrapErr; -use reqwest::Certificate; -use serde::{Deserialize, Serialize}; +use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue}; +use serde::Deserialize; use url::Url; use super::{ - constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH}, + constants::{GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH}, error::SignerClientError, request::{ EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ProxyId, SignConsensusRequest, - SignProxyRequest, SignedProxyDelegation, + SignProxyRequest, SignRequest, SignedProxyDelegation, }, }; use crate::{ DEFAULT_REQUEST_TIMEOUT, - commit::{ - constants::{ - REQUEST_SIGNATURE_BLS_PATH, REQUEST_SIGNATURE_PROXY_BLS_PATH, - REQUEST_SIGNATURE_PROXY_ECDSA_PATH, - }, - response::{BlsSignResponse, EcdsaSignResponse}, - }, - types::{BlsPublicKey, Jwt, ModuleId}, + constants::SIGNER_JWT_EXPIRATION, + signer::EcdsaSignature, + types::{BlsPublicKey, BlsSignature, Jwt, ModuleId}, utils::create_jwt, }; @@ -33,51 +28,65 @@ pub struct SignerClient { /// Url endpoint of the Signer Module url: Url, client: reqwest::Client, + last_jwt_refresh: Instant, module_id: ModuleId, jwt_secret: Jwt, } impl SignerClient { /// Create a new SignerClient - pub fn new( - signer_server_url: Url, - cert_path: Option, - jwt_secret: Jwt, - module_id: ModuleId, - ) -> eyre::Result { - let mut builder = reqwest::Client::builder().timeout(DEFAULT_REQUEST_TIMEOUT); - - // If a certificate path is provided, use it - if let Some(cert_path) = cert_path { - builder = builder - .use_rustls_tls() - .add_root_certificate(Certificate::from_pem(&std::fs::read(cert_path)?)?); - } - - Ok(Self { url: signer_server_url, client: builder.build()?, module_id, jwt_secret }) + pub fn new(signer_server_url: Url, jwt_secret: Jwt, module_id: ModuleId) -> eyre::Result { + let jwt = create_jwt(&module_id, &jwt_secret)?; + + let mut auth_value = + HeaderValue::from_str(&format!("Bearer {jwt}")).wrap_err("invalid jwt")?; + auth_value.set_sensitive(true); + + let mut headers = HeaderMap::new(); + headers.insert(AUTHORIZATION, auth_value); + + let client = reqwest::Client::builder() + .timeout(DEFAULT_REQUEST_TIMEOUT) + .default_headers(headers) + .build()?; + + Ok(Self { + url: signer_server_url, + client, + last_jwt_refresh: Instant::now(), + module_id, + jwt_secret, + }) } - fn create_jwt_for_payload( - &mut self, - route: &str, - payload: &T, - ) -> Result { - let payload_vec = serde_json::to_vec(payload)?; - create_jwt(&self.module_id, &self.jwt_secret, route, Some(&payload_vec)) - .wrap_err("failed to create JWT for payload") - .map_err(SignerClientError::JWTError) + fn refresh_jwt(&mut self) -> Result<(), SignerClientError> { + if self.last_jwt_refresh.elapsed() > Duration::from_secs(SIGNER_JWT_EXPIRATION) { + let jwt = create_jwt(&self.module_id, &self.jwt_secret)?; + + let mut auth_value = + HeaderValue::from_str(&format!("Bearer {jwt}")).wrap_err("invalid jwt")?; + auth_value.set_sensitive(true); + + let mut headers = HeaderMap::new(); + headers.insert(AUTHORIZATION, auth_value); + + self.client = reqwest::Client::builder() + .timeout(DEFAULT_REQUEST_TIMEOUT) + .default_headers(headers) + .build()?; + } + + Ok(()) } /// Request a list of validator pubkeys for which signatures can be /// requested. // TODO: add more docs on how proxy keys work pub async fn get_pubkeys(&mut self) -> Result { - let jwt = create_jwt(&self.module_id, &self.jwt_secret, GET_PUBKEYS_PATH, None) - .wrap_err("failed to create JWT for payload") - .map_err(SignerClientError::JWTError)?; + self.refresh_jwt()?; let url = self.url.join(GET_PUBKEYS_PATH)?; - let res = self.client.get(url).bearer_auth(jwt).send().await?; + let res = self.client.get(url).send().await?; if !res.status().is_success() { return Err(SignerClientError::FailedRequest { @@ -90,19 +99,14 @@ impl SignerClient { } /// Send a signature request - async fn request_signature( - &mut self, - route: &str, - request: &Q, - ) -> Result + async fn request_signature(&mut self, request: &SignRequest) -> Result where - Q: Serialize, T: for<'de> Deserialize<'de>, { - let jwt = self.create_jwt_for_payload(route, request)?; + self.refresh_jwt()?; - let url = self.url.join(route)?; - let res = self.client.post(url).json(&request).bearer_auth(jwt).send().await?; + let url = self.url.join(REQUEST_SIGNATURE_PATH)?; + let res = self.client.post(url).json(&request).send().await?; let status = res.status(); let response_bytes = res.bytes().await?; @@ -122,22 +126,22 @@ impl SignerClient { pub async fn request_consensus_signature( &mut self, request: SignConsensusRequest, - ) -> Result { - self.request_signature(REQUEST_SIGNATURE_BLS_PATH, &request).await + ) -> Result { + self.request_signature(&request.into()).await } pub async fn request_proxy_signature_ecdsa( &mut self, request: SignProxyRequest
, - ) -> Result { - self.request_signature(REQUEST_SIGNATURE_PROXY_ECDSA_PATH, &request).await + ) -> Result { + self.request_signature(&request.into()).await } pub async fn request_proxy_signature_bls( &mut self, request: SignProxyRequest, - ) -> Result { - self.request_signature(REQUEST_SIGNATURE_PROXY_BLS_PATH, &request).await + ) -> Result { + self.request_signature(&request.into()).await } async fn generate_proxy_key( @@ -147,10 +151,10 @@ impl SignerClient { where T: ProxyId + for<'de> Deserialize<'de>, { - let jwt = self.create_jwt_for_payload(GENERATE_PROXY_KEY_PATH, request)?; + self.refresh_jwt()?; let url = self.url.join(GENERATE_PROXY_KEY_PATH)?; - let res = self.client.post(url).json(&request).bearer_auth(jwt).send().await?; + let res = self.client.post(url).json(&request).send().await?; let status = res.status(); let response_bytes = res.bytes().await?; diff --git a/crates/common/src/commit/constants.rs b/crates/common/src/commit/constants.rs index f2d5e94c..7c9f948c 100644 --- a/crates/common/src/commit/constants.rs +++ b/crates/common/src/commit/constants.rs @@ -1,13 +1,5 @@ -use const_format::concatcp; - pub const GET_PUBKEYS_PATH: &str = "/signer/v1/get_pubkeys"; -pub const REQUEST_SIGNATURE_BASE_PATH: &str = "/signer/v1/request_signature"; -pub const REQUEST_SIGNATURE_BLS_PATH: &str = concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/bls"); -pub const REQUEST_SIGNATURE_PROXY_BLS_PATH: &str = - concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/proxy-bls"); -pub const REQUEST_SIGNATURE_PROXY_ECDSA_PATH: &str = - concatcp!(REQUEST_SIGNATURE_BASE_PATH, "/proxy-ecdsa"); +pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature"; pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key"; pub const STATUS_PATH: &str = "/status"; pub const RELOAD_PATH: &str = "/reload"; -pub const REVOKE_MODULE_PATH: &str = "/revoke_jwt"; diff --git a/crates/common/src/commit/mod.rs b/crates/common/src/commit/mod.rs index 193db630..205785ff 100644 --- a/crates/common/src/commit/mod.rs +++ b/crates/common/src/commit/mod.rs @@ -2,4 +2,3 @@ pub mod client; pub mod constants; pub mod error; pub mod request; -pub mod response; diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index cd780446..afa01807 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -1,22 +1,21 @@ use std::{ - collections::HashMap, fmt::{self, Debug, Display}, str::FromStr, }; use alloy::{ hex, - primitives::{Address, B256, aliases::B32}, + primitives::{Address, B256}, }; -use serde::{Deserialize, Deserializer, Serialize}; +use derive_more::derive::From; +use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - config::decode_string_to_map, constants::COMMIT_BOOST_DOMAIN, signature::verify_signed_message, - types::{BlsPublicKey, BlsSignature, Chain, ModuleId}, + types::{BlsPublicKey, BlsSignature, Chain}, }; pub trait ProxyId: Debug + Clone + TreeHash + Display { @@ -68,8 +67,7 @@ impl SignedProxyDelegation { &self.message.delegator, &self.message, &self.signature, - None, - &B32::from(COMMIT_BOOST_DOMAIN), + COMMIT_BOOST_DOMAIN, ) } } @@ -80,24 +78,53 @@ impl fmt::Display for SignedProxyDelegation { } } +// TODO(David): This struct shouldn't be visible to module authors +#[derive(Debug, Clone, Serialize, Deserialize, From)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SignRequest { + Consensus(SignConsensusRequest), + ProxyBls(SignProxyRequest), + ProxyEcdsa(SignProxyRequest
), +} + +impl Display for SignRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SignRequest::Consensus(req) => write!( + f, + "Consensus(pubkey: {}, object_root: {})", + req.pubkey, + hex::encode_prefixed(req.object_root) + ), + SignRequest::ProxyBls(req) => write!( + f, + "BLS(proxy: {}, object_root: {})", + req.proxy, + hex::encode_prefixed(req.object_root) + ), + SignRequest::ProxyEcdsa(req) => write!( + f, + "ECDSA(proxy: {}, object_root: {})", + req.proxy, + hex::encode_prefixed(req.object_root) + ), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignConsensusRequest { pub pubkey: BlsPublicKey, pub object_root: B256, - /// Replay-protection nonce mixed into the signing root via - /// `PropCommitSigningInfo`. Modules that do not track nonces should - /// send `0`. Modules that do track nonces should use a monotonically - /// increasing value per key to prevent signature reuse. - pub nonce: u64, } impl SignConsensusRequest { - pub fn new(pubkey: BlsPublicKey, object_root: B256, nonce: u64) -> Self { - Self { pubkey, object_root, nonce } + pub fn new(pubkey: BlsPublicKey, object_root: B256) -> Self { + Self { pubkey, object_root } } pub fn builder(pubkey: BlsPublicKey) -> Self { - Self::new(pubkey, B256::ZERO, 0) + Self::new(pubkey, B256::ZERO) } pub fn with_root>(self, object_root: R) -> Self { @@ -107,42 +134,21 @@ impl SignConsensusRequest { pub fn with_msg(self, msg: &impl TreeHash) -> Self { self.with_root(msg.tree_hash_root().0) } - - pub fn with_nonce(self, nonce: u64) -> Self { - Self { nonce, ..self } - } -} - -impl Display for SignConsensusRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Consensus(pubkey: {}, object_root: {}, nonce: {})", - self.pubkey, - hex::encode_prefixed(self.object_root), - self.nonce - ) - } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SignProxyRequest { pub proxy: T, pub object_root: B256, - /// Replay-protection nonce mixed into the signing root via - /// `PropCommitSigningInfo`. Modules that do not track nonces should - /// send `0`. Modules that do track nonces should use a monotonically - /// increasing value per key to prevent signature reuse. - pub nonce: u64, } impl SignProxyRequest { - pub fn new(proxy: T, object_root: B256, nonce: u64) -> Self { - Self { proxy, object_root, nonce } + pub fn new(proxy: T, object_root: B256) -> Self { + Self { proxy, object_root } } pub fn builder(proxy: T) -> Self { - Self::new(proxy, B256::ZERO, 0) + Self::new(proxy, B256::ZERO) } pub fn with_root>(self, object_root: R) -> Self { @@ -152,34 +158,6 @@ impl SignProxyRequest { pub fn with_msg(self, msg: &impl TreeHash) -> Self { self.with_root(msg.tree_hash_root().0) } - - pub fn with_nonce(self, nonce: u64) -> Self { - Self { nonce, ..self } - } -} - -impl Display for SignProxyRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "BLS(proxy: {}, object_root: {}, nonce: {})", - self.proxy, - hex::encode_prefixed(self.object_root), - self.nonce - ) - } -} - -impl Display for SignProxyRequest
{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ECDSA(proxy: {}, object_root: {}, nonce: {})", - self.proxy, - hex::encode_prefixed(self.object_root), - self.nonce - ) - } } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -230,31 +208,6 @@ pub struct GetPubkeysResponse { pub keys: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ReloadRequest { - #[serde(default, deserialize_with = "deserialize_jwt_secrets")] - pub jwt_secrets: Option>, - pub admin_secret: Option, -} - -pub fn deserialize_jwt_secrets<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let raw: String = Deserialize::deserialize(deserializer)?; - - decode_string_to_map(&raw) - .map(Some) - .map_err(|_| serde::de::Error::custom("Invalid format".to_string())) -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RevokeModuleRequest { - pub module_id: ModuleId, -} - /// Map of consensus pubkeys to proxies #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ConsensusProxyMap { @@ -275,6 +228,36 @@ mod tests { use super::*; use crate::signer::EcdsaSignature; + #[test] + fn test_decode_request_signature() { + let data = r#"{ + "type": "consensus", + "pubkey": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" + }"#; + + let request: SignRequest = serde_json::from_str(data).unwrap(); + assert!(matches!(request, SignRequest::Consensus(..))); + + let data = r#"{ + "type": "proxy_bls", + "proxy": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", + "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" + }"#; + + let request: SignRequest = serde_json::from_str(data).unwrap(); + assert!(matches!(request, SignRequest::ProxyBls(..))); + + let data = r#"{ + "type": "proxy_ecdsa", + "proxy": "0x4ca9939a8311a7cab3dde201b70157285fa81a9d", + "object_root": "0x5c89913beafa0472168e0ec05e349b4ceb9985d25ab9fa8de53a60208c85b3a5" + }"#; + + let request: SignRequest = serde_json::from_str(data).unwrap(); + assert!(matches!(request, SignRequest::ProxyEcdsa(..))); + } + #[test] fn test_decode_response_signature() { let data = r#""0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000""#; @@ -315,7 +298,7 @@ mod tests { let _: SignedProxyDelegationBls = serde_json::from_str(data).unwrap(); - let data = r#"{ + let data = r#"{ "message": { "delegator": "0xa3366b54f28e4bf1461926a3c70cdb0ec432b5c92554ecaae3742d33fb33873990cbed1761c68020e6d3c14d30a22050", "proxy": "0x4ca9939a8311a7cab3dde201b70157285fa81a9d" @@ -326,29 +309,6 @@ mod tests { let _: SignedProxyDelegationEcdsa = serde_json::from_str(data).unwrap(); } - #[test] - fn test_reload_request_jwt_secrets_present() { - let data = r#"{"jwt_secrets": "module_a=secret1,module_b=secret2"}"#; - let req: ReloadRequest = serde_json::from_str(data).unwrap(); - let secrets = req.jwt_secrets.expect("should have secrets"); - assert_eq!(secrets.get(&ModuleId("module_a".into())), Some(&"secret1".to_string())); - assert_eq!(secrets.get(&ModuleId("module_b".into())), Some(&"secret2".to_string())); - } - - #[test] - fn test_reload_request_jwt_secrets_absent() { - let data = r#"{}"#; - let req: ReloadRequest = serde_json::from_str(data).unwrap(); - assert!(req.jwt_secrets.is_none()); - } - - #[test] - fn test_reload_request_jwt_secrets_invalid_format() { - // Missing '=' separator — decode_string_to_map should fail - let data = r#"{"jwt_secrets": "bad_value_no_equals"}"#; - assert!(serde_json::from_str::(data).is_err()); - } - #[test] fn test_decode_response_proxy_map() { let data = r#"{ diff --git a/crates/common/src/commit/response.rs b/crates/common/src/commit/response.rs deleted file mode 100644 index 0e984144..00000000 --- a/crates/common/src/commit/response.rs +++ /dev/null @@ -1,53 +0,0 @@ -use alloy::primitives::{Address, B256, U256}; -use serde::{Deserialize, Serialize}; - -use crate::{ - signer::EcdsaSignature, - types::{BlsPublicKey, BlsSignature}, -}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct BlsSignResponse { - pub pubkey: BlsPublicKey, - pub object_root: B256, - pub module_signing_id: B256, - pub nonce: u64, - pub chain_id: U256, - pub signature: BlsSignature, -} - -impl BlsSignResponse { - pub fn new( - pubkey: BlsPublicKey, - object_root: B256, - module_signing_id: B256, - nonce: u64, - chain_id: U256, - signature: BlsSignature, - ) -> Self { - Self { pubkey, object_root, module_signing_id, nonce, chain_id, signature } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct EcdsaSignResponse { - pub address: Address, - pub object_root: B256, - pub module_signing_id: B256, - pub nonce: u64, - pub chain_id: U256, - pub signature: EcdsaSignature, -} - -impl EcdsaSignResponse { - pub fn new( - address: Address, - object_root: B256, - module_signing_id: B256, - nonce: u64, - chain_id: U256, - signature: EcdsaSignature, - ) -> Self { - Self { address, object_root, module_signing_id, nonce, chain_id, signature } - } -} diff --git a/crates/common/src/config/constants.rs b/crates/common/src/config/constants.rs index fb5f3b08..67248596 100644 --- a/crates/common/src/config/constants.rs +++ b/crates/common/src/config/constants.rs @@ -17,7 +17,7 @@ pub const LOGS_DIR_DEFAULT: &str = "/var/logs/commit-boost"; ///////////////////////// PBS ///////////////////////// pub const PBS_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/pbs:latest"; -pub const PBS_SERVICE_NAME: &str = "pbs"; +pub const PBS_MODULE_NAME: &str = "pbs"; /// Where to receive BuilderAPI calls from beacon node pub const PBS_ENDPOINT_ENV: &str = "CB_PBS_ENDPOINT"; @@ -27,7 +27,7 @@ pub const MUX_PATH_ENV: &str = "CB_MUX_PATH"; ///////////////////////// SIGNER ///////////////////////// pub const SIGNER_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/signer:latest"; -pub const SIGNER_SERVICE_NAME: &str = "signer"; +pub const SIGNER_MODULE_NAME: &str = "signer"; /// Where the signer module should open the server pub const SIGNER_ENDPOINT_ENV: &str = "CB_SIGNER_ENDPOINT"; @@ -44,14 +44,7 @@ pub const SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT: u32 = 5 * 60; /// Comma separated list module_id=jwt_secret pub const JWTS_ENV: &str = "CB_JWTS"; -pub const ADMIN_JWT_ENV: &str = "CB_SIGNER_ADMIN_JWT"; - -/// Path to the certificates folder where the cert.pem and key.pem files are -/// stored/generated -pub const SIGNER_TLS_CERTIFICATES_PATH_ENV: &str = "CB_SIGNER_TLS_CERTIFICATES"; -pub const SIGNER_TLS_CERTIFICATES_PATH_DEFAULT: &str = "/certs"; -pub const SIGNER_TLS_CERTIFICATE_NAME: &str = "cert.pem"; -pub const SIGNER_TLS_KEY_NAME: &str = "key.pem"; + /// Path to json file with plaintext keys (testing only) pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_LOADER_FILE"; pub const SIGNER_DEFAULT: &str = "/keys.json"; diff --git a/crates/common/src/config/log.rs b/crates/common/src/config/log.rs index a792ebc8..595a81a1 100644 --- a/crates/common/src/config/log.rs +++ b/crates/common/src/config/log.rs @@ -16,7 +16,7 @@ pub struct LogsSettings { impl LogsSettings { pub fn from_env_config() -> Result { - let (mut config, _) = CommitBoostConfig::from_env_path()?; + let mut config = CommitBoostConfig::from_env_path()?; // Override log dir path if env var is set if let Some(log_dir) = load_optional_env_var(LOGS_DIR_ENV) { diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs index e0958342..67a13cb0 100644 --- a/crates/common/src/config/mod.rs +++ b/crates/common/src/config/mod.rs @@ -41,7 +41,7 @@ pub struct CommitBoostConfig { impl CommitBoostConfig { /// Validate config pub async fn validate(&self) -> Result<()> { - self.pbs.validate(self.chain).await?; + self.pbs.pbs_config.validate(self.chain).await?; if let Some(signer) = &self.signer { signer.validate().await?; } @@ -56,19 +56,19 @@ impl CommitBoostConfig { } pub fn from_file(path: &PathBuf) -> Result { - let (config, _): (Self, _) = load_from_file(path)?; + let config: Self = load_from_file(path)?; Ok(config) } // When loading the config from the environment, it's important that every path // is replaced with the correct value if the config is loaded inside a container - pub fn from_env_path() -> Result<(Self, PathBuf)> { - let (helper_config, config_path): (HelperConfig, PathBuf) = load_file_from_env(CONFIG_ENV)?; + pub fn from_env_path() -> Result { + let helper_config: HelperConfig = load_file_from_env(CONFIG_ENV)?; let chain = match helper_config.chain { ChainLoader::Path { path, genesis_time_secs } => { // check if the file path is overridden by env var - let (slot_time_secs, genesis_fork_version, fulu_fork_slot, chain_id) = + let (slot_time_secs, genesis_fork_version, fulu_fork_slot) = if let Some(path) = load_optional_env_var(CHAIN_SPEC_ENV) { load_chain_from_file(path.parse()?)? } else { @@ -79,7 +79,6 @@ impl CommitBoostConfig { slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } } ChainLoader::Known(known) => Chain::from(known), @@ -88,7 +87,6 @@ impl CommitBoostConfig { slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } => { let genesis_fork_version: ForkVersion = genesis_fork_version.as_ref().try_into()?; Chain::Custom { @@ -96,7 +94,6 @@ impl CommitBoostConfig { slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } } }; @@ -112,13 +109,13 @@ impl CommitBoostConfig { logs: helper_config.logs, }; - Ok((config, config_path)) + Ok(config) } /// Returns the path to the chain spec file if any pub fn chain_spec_file(path: &PathBuf) -> Option { match load_from_file::<_, ChainConfig>(path) { - Ok((config, _)) => { + Ok(config) => { if let ChainLoader::Path { path, genesis_time_secs: _ } = config.chain { Some(path) } else { @@ -128,40 +125,6 @@ impl CommitBoostConfig { Err(_) => None, } } - - /// Helper to return if the signer module is needed based on the config - pub fn needs_signer_module(&self) -> bool { - self.pbs.with_signer || - self.modules.as_ref().is_some_and(|modules| { - modules.iter().any(|module| matches!(module.kind, ModuleKind::Commit)) - }) - } - - pub fn signer_uses_tls(&self) -> bool { - self.signer - .as_ref() - .is_some_and(|signer_config| matches!(signer_config.tls_mode, TlsMode::Certificate(_))) - } - - pub fn signer_server_url(&self, default_port: u16) -> String { - if let Some(SignerConfig { inner: SignerType::Remote { url }, .. }) = &self.signer { - url.to_string() - } else { - let signer_http_prefix = if self.signer_uses_tls() { "https" } else { "http" }; - let port = self.signer.as_ref().map(|s| s.port).unwrap_or(default_port); - format!("{signer_http_prefix}://cb_signer:{port}") - } - } - - pub fn signer_certs_path(&self) -> Option<&PathBuf> { - self.signer - .as_ref() - .map(|config| match &config.tls_mode { - TlsMode::Insecure => None, - TlsMode::Certificate(path) => Some(path), - }) - .unwrap_or_default() - } } /// Helper struct to load the chain spec file diff --git a/crates/common/src/config/module.rs b/crates/common/src/config/module.rs index aec45289..02fa90da 100644 --- a/crates/common/src/config/module.rs +++ b/crates/common/src/config/module.rs @@ -1,6 +1,5 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::collections::HashMap; -use alloy::primitives::B256; use eyre::{ContextCompat, Result}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use toml::Table; @@ -8,7 +7,6 @@ use toml::Table; use crate::{ commit::client::SignerClient, config::{ - SIGNER_TLS_CERTIFICATE_NAME, SIGNER_TLS_CERTIFICATES_PATH_ENV, SignerConfig, TlsMode, constants::{CONFIG_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, SIGNER_URL_ENV}, load_env_var, utils::load_file_from_env, @@ -16,14 +14,14 @@ use crate::{ types::{Chain, Jwt, ModuleId}, }; -#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[derive(Debug, Deserialize, Serialize)] pub enum ModuleKind { #[serde(alias = "commit")] Commit, } /// Static module config from config file -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize)] pub struct StaticModuleConfig { /// Unique id of the module pub id: ModuleId, @@ -36,8 +34,6 @@ pub struct StaticModuleConfig { /// Type of the module #[serde(rename = "type")] pub kind: ModuleKind, - /// Signing ID for the module to use when requesting signatures - pub signing_id: B256, } /// Runtime config to start a module @@ -83,11 +79,10 @@ pub fn load_commit_module_config() -> Result { chain: Chain, modules: Vec>, - signer: Option, } // load module config including the extra data (if any) - let (cb_config, _): (StubConfig, _) = load_file_from_env(CONFIG_ENV)?; + let cb_config: StubConfig = load_file_from_env(CONFIG_ENV)?; // find all matching modules config let matches: Vec> = cb_config @@ -106,16 +101,7 @@ pub fn load_commit_module_config() -> Result None, - TlsMode::Certificate(path) => Some( - load_env_var(SIGNER_TLS_CERTIFICATES_PATH_ENV) - .map(PathBuf::from) - .unwrap_or(path) - .join(SIGNER_TLS_CERTIFICATE_NAME), - ), - }; - let signer_client = SignerClient::new(signer_server_url, certs_path, module_jwt, module_id)?; + let signer_client = SignerClient::new(signer_server_url, module_jwt, module_id)?; Ok(StartCommitModuleConfig { id: module_config.static_config.id, @@ -162,7 +148,7 @@ pub fn load_builder_module_config() -> eyre::Result, _) = load_file_from_env(CONFIG_ENV)?; + let cb_config: StubConfig = load_file_from_env(CONFIG_ENV)?; // find all matching modules config let matches: Vec> = cb_config diff --git a/crates/common/src/config/mux.rs b/crates/common/src/config/mux.rs index 8a9dab73..b5436ae2 100644 --- a/crates/common/src/config/mux.rs +++ b/crates/common/src/config/mux.rs @@ -62,8 +62,7 @@ impl PbsMuxes { .load( &mux.id, chain, - default_pbs.ssv_node_api_url.clone(), - default_pbs.ssv_public_api_url.clone(), + default_pbs.ssv_api_url.clone(), default_pbs.rpc_url.clone(), http_timeout, ) @@ -213,8 +212,7 @@ impl MuxKeysLoader { &self, mux_id: &str, chain: Chain, - ssv_node_api_url: Url, - ssv_public_api_url: Url, + ssv_api_url: Url, rpc_url: Option, http_timeout: Duration, ) -> eyre::Result> { @@ -260,8 +258,7 @@ impl MuxKeysLoader { } NORegistry::SSV => { fetch_ssv_pubkeys( - ssv_node_api_url, - ssv_public_api_url, + ssv_api_url, chain, U256::from(*node_operator_id), http_timeout, @@ -394,62 +391,11 @@ async fn fetch_lido_registry_keys( } async fn fetch_ssv_pubkeys( - node_url: Url, - public_url: Url, + mut api_url: Url, chain: Chain, node_operator_id: U256, http_timeout: Duration, ) -> eyre::Result> { - // Try the node API first - match fetch_ssv_pubkeys_from_ssv_node(node_url.clone(), node_operator_id, http_timeout).await { - Ok(pubkeys) => Ok(pubkeys), - Err(e) => { - // Fall back to public API - warn!( - "failed to fetch pubkeys from SSV node API at {node_url}: {e}; falling back to public API", - ); - fetch_ssv_pubkeys_from_public_api(public_url, chain, node_operator_id, http_timeout) - .await - } - } -} - -/// Ensures that the SSV API URL has a trailing slash -fn ensure_ssv_api_url(url: &mut Url) -> eyre::Result<()> { - // Validate the URL - this appends a trailing slash if missing as efficiently as - // possible - if !url.path().ends_with('/') { - match url.path_segments_mut() { - Ok(mut segments) => segments.push(""), // Analogous to a trailing slash - Err(_) => bail!("SSV API URL is not a valid base URL"), - }; - } - Ok(()) -} - -/// Fetches SSV pubkeys from the user's SSV node -async fn fetch_ssv_pubkeys_from_ssv_node( - mut url: Url, - node_operator_id: U256, - http_timeout: Duration, -) -> eyre::Result> { - ensure_ssv_api_url(&mut url)?; - let route = "validators"; - let url = url.join(route).wrap_err("failed to construct SSV API URL")?; - - let response = request_ssv_pubkeys_from_ssv_node(url, node_operator_id, http_timeout).await?; - let pubkeys = response.data.into_iter().map(|v| v.public_key).collect::>(); - Ok(pubkeys) -} - -/// Fetches SSV pubkeys from the public SSV network API with pagination -async fn fetch_ssv_pubkeys_from_public_api( - mut url: Url, - chain: Chain, - node_operator_id: U256, - http_timeout: Duration, -) -> eyre::Result> { - ensure_ssv_api_url(&mut url)?; const MAX_PER_PAGE: usize = 100; let chain_name = match chain { @@ -463,13 +409,22 @@ async fn fetch_ssv_pubkeys_from_public_api( let mut page = 1; let mut expected_total: Option = None; + // Validate the URL - this appends a trailing slash if missing as efficiently as + // possible + if !api_url.path().ends_with('/') { + match api_url.path_segments_mut() { + Ok(mut segments) => segments.push(""), // Analogous to a trailing slash + Err(_) => bail!("SSV API URL is not a valid base URL"), + }; + } + loop { let route = format!( "{chain_name}/validators/in_operator/{node_operator_id}?perPage={MAX_PER_PAGE}&page={page}", ); - let url = url.join(&route).wrap_err("failed to construct SSV API URL")?; + let url = api_url.join(&route).wrap_err("failed to construct SSV API URL")?; - let response = request_ssv_pubkeys_from_public_api(url, http_timeout).await?; + let response = fetch_ssv_pubkeys_from_url(url, http_timeout).await?; let fetched = response.validators.len(); if expected_total.is_none() && fetched > 0 { expected_total = Some(response.pagination.total); diff --git a/crates/common/src/config/pbs.rs b/crates/common/src/config/pbs.rs index 907fbecf..7bcf91e3 100644 --- a/crates/common/src/config/pbs.rs +++ b/crates/common/src/config/pbs.rs @@ -3,7 +3,6 @@ use std::{ collections::HashMap, net::{Ipv4Addr, SocketAddr}, - path::PathBuf, sync::Arc, }; @@ -11,21 +10,19 @@ use alloy::{ primitives::{U256, utils::format_ether}, providers::{Provider, ProviderBuilder}, }; -use docker_image::DockerImage; use eyre::{Result, ensure}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use url::Url; use super::{ CommitBoostConfig, HTTP_TIMEOUT_SECONDS_DEFAULT, PBS_ENDPOINT_ENV, RuntimeMuxConfig, - load_optional_env_var, + constants::PBS_IMAGE_DEFAULT, load_optional_env_var, }; use crate::{ commit::client::SignerClient, config::{ - CONFIG_ENV, MODULE_JWT_ENV, MuxKeysLoader, PBS_IMAGE_DEFAULT, PBS_SERVICE_NAME, PbsMuxes, - SIGNER_TLS_CERTIFICATE_NAME, SIGNER_TLS_CERTIFICATES_PATH_ENV, SIGNER_URL_ENV, - SignerConfig, TlsMode, load_env_var, load_file_from_env, + CONFIG_ENV, MODULE_JWT_ENV, MuxKeysLoader, PBS_MODULE_NAME, PbsMuxes, SIGNER_URL_ENV, + load_env_var, load_file_from_env, }, pbs::{ DEFAULT_PBS_PORT, DEFAULT_REGISTRY_REFRESH_SECONDS, DefaultTimeout, LATE_IN_SLOT_TIME_MS, @@ -126,12 +123,9 @@ pub struct PbsConfig { pub extra_validation_enabled: bool, /// Execution Layer RPC url to use for extra validation pub rpc_url: Option, - /// URL for the user's own SSV node API endpoint - #[serde(default = "default_ssv_node_api_url")] - pub ssv_node_api_url: Url, - /// URL for the public SSV network API server - #[serde(default = "default_public_ssv_api_url")] - pub ssv_public_api_url: Url, + /// URL for the SSV network API + #[serde(default = "default_ssv_api_url")] + pub ssv_api_url: Url, /// Timeout for HTTP requests in seconds #[serde(default = "default_u64::")] pub http_timeout_seconds: u64, @@ -183,16 +177,18 @@ impl PbsConfig { } if let Some(rpc_url) = &self.rpc_url { - let provider = ProviderBuilder::new().connect_http(rpc_url.clone()); - let chain_id = provider.get_chain_id().await?; - let chain_id_big = U256::from(chain_id); - ensure!( - chain_id_big == chain.id(), - "Rpc url is for the wrong chain, expected: {} ({:?}) got {}", - chain.id(), - chain, - chain_id_big - ); + // TODO: remove this once we support chain ids for custom chains + if !matches!(chain, Chain::Custom { .. }) { + let provider = ProviderBuilder::new().connect_http(rpc_url.clone()); + let chain_id = provider.get_chain_id().await?; + ensure!( + chain_id == chain.id(), + "Rpc url is for the wrong chain, expected: {} ({:?}) got {}", + chain.id(), + chain, + chain_id + ); + } } ensure!( @@ -218,21 +214,6 @@ pub struct StaticPbsConfig { pub with_signer: bool, } -impl StaticPbsConfig { - /// Validate static pbs config - pub async fn validate(&self, chain: Chain) -> Result<()> { - // The Docker tag must parse - ensure!(!self.docker_image.is_empty(), "Docker image is empty"); - ensure!( - DockerImage::parse(&self.docker_image).is_ok(), - format!("Invalid Docker image: {}", self.docker_image) - ); - - // Validate the inner pbs config - self.pbs_config.validate(chain).await - } -} - /// Runtime config for the pbs module #[derive(Debug, Clone)] pub struct PbsModuleConfig { @@ -261,11 +242,8 @@ fn default_pbs() -> String { } /// Loads the default pbs config, i.e. with no signer client or custom data -pub async fn load_pbs_config(config_path: Option) -> Result<(PbsModuleConfig, PathBuf)> { - let (config, config_path) = match config_path { - Some(path) => (CommitBoostConfig::from_file(&path)?, path), - None => CommitBoostConfig::from_env_path()?, - }; +pub async fn load_pbs_config() -> Result { + let config = CommitBoostConfig::from_env_path()?; config.validate().await?; // Make sure relays isn't empty - since the config is still technically valid if @@ -317,19 +295,16 @@ pub async fn load_pbs_config(config_path: Option) -> Result<(PbsModuleC let all_relays = all_relays.into_values().collect(); - Ok(( - PbsModuleConfig { - chain: config.chain, - endpoint, - pbs_config: Arc::new(config.pbs.pbs_config), - relays: relay_clients, - all_relays, - signer_client: None, - registry_muxes, - mux_lookup, - }, - config_path, - )) + Ok(PbsModuleConfig { + chain: config.chain, + endpoint, + pbs_config: Arc::new(config.pbs.pbs_config), + relays: relay_clients, + all_relays, + signer_client: None, + registry_muxes, + mux_lookup, + }) } /// Loads a custom pbs config, i.e. with signer client and/or custom data @@ -347,13 +322,12 @@ pub async fn load_pbs_custom_config() -> Result<(PbsModuleC chain: Chain, relays: Vec, pbs: CustomPbsConfig, - signer: Option, muxes: Option, } // load module config including the extra data (if any) - let (cb_config, _): (StubConfig, _) = load_file_from_env(CONFIG_ENV)?; - cb_config.pbs.static_config.validate(cb_config.chain).await?; + let cb_config: StubConfig = load_file_from_env(CONFIG_ENV)?; + cb_config.pbs.static_config.pbs_config.validate(cb_config.chain).await?; // use endpoint from env if set, otherwise use default host and port let endpoint = if let Some(endpoint) = load_optional_env_var(PBS_ENDPOINT_ENV) { @@ -404,24 +378,10 @@ pub async fn load_pbs_custom_config() -> Result<(PbsModuleC // if custom pbs requires a signer client, load jwt let module_jwt = Jwt(load_env_var(MODULE_JWT_ENV)?); let signer_server_url = load_env_var(SIGNER_URL_ENV)?.parse()?; - let certs_path = match cb_config - .signer - .ok_or_else(|| eyre::eyre!("with_signer = true but no [signer] section in config"))? - .tls_mode - { - TlsMode::Insecure => None, - TlsMode::Certificate(path) => Some( - load_env_var(SIGNER_TLS_CERTIFICATES_PATH_ENV) - .map(PathBuf::from) - .unwrap_or(path) - .join(SIGNER_TLS_CERTIFICATE_NAME), - ), - }; Some(SignerClient::new( signer_server_url, - certs_path, module_jwt, - ModuleId(PBS_SERVICE_NAME.to_string()), + ModuleId(PBS_MODULE_NAME.to_string()), )?) } else { None @@ -442,12 +402,7 @@ pub async fn load_pbs_custom_config() -> Result<(PbsModuleC )) } -/// Default URL for the user's SSV node API endpoint (/v1/validators). -fn default_ssv_node_api_url() -> Url { - Url::parse("http://localhost:16000/v1/").expect("default URL is valid") -} - -/// Default URL for the public SSV network API. -fn default_public_ssv_api_url() -> Url { +/// Default URL for the SSV network API +fn default_ssv_api_url() -> Url { Url::parse("https://api.ssv.network/api/v4/").expect("default URL is valid") } diff --git a/crates/common/src/config/signer.rs b/crates/common/src/config/signer.rs index 95110958..8ab8186f 100644 --- a/crates/common/src/config/signer.rs +++ b/crates/common/src/config/signer.rs @@ -1,111 +1,28 @@ use std::{ collections::HashMap, - fmt::Display, net::{Ipv4Addr, SocketAddr}, - num::NonZeroUsize, path::PathBuf, }; -use alloy::primitives::B256; use docker_image::DockerImage; -use eyre::{Context, OptionExt, Result, bail, ensure}; +use eyre::{OptionExt, Result, bail, ensure}; use serde::{Deserialize, Serialize}; use tonic::transport::{Certificate, Identity}; use url::Url; use super::{ - CommitBoostConfig, SIGNER_ENDPOINT_ENV, SIGNER_JWT_AUTH_FAIL_LIMIT_DEFAULT, - SIGNER_JWT_AUTH_FAIL_LIMIT_ENV, SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT, - SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_ENV, SIGNER_PORT_DEFAULT, SIGNER_TLS_CERTIFICATE_NAME, - SIGNER_TLS_CERTIFICATES_PATH_ENV, SIGNER_TLS_KEY_NAME, load_jwt_secrets, load_optional_env_var, - utils::load_env_var, + CommitBoostConfig, SIGNER_ENDPOINT_ENV, SIGNER_IMAGE_DEFAULT, + SIGNER_JWT_AUTH_FAIL_LIMIT_DEFAULT, SIGNER_JWT_AUTH_FAIL_LIMIT_ENV, + SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT, SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_ENV, + SIGNER_PORT_DEFAULT, load_jwt_secrets, load_optional_env_var, utils::load_env_var, }; use crate::{ - config::{ - DIRK_CA_CERT_ENV, DIRK_CERT_ENV, DIRK_DIR_SECRETS_ENV, DIRK_KEY_ENV, SIGNER_IMAGE_DEFAULT, - }, + config::{DIRK_CA_CERT_ENV, DIRK_CERT_ENV, DIRK_DIR_SECRETS_ENV, DIRK_KEY_ENV}, signer::{ProxyStore, SignerLoader}, types::{Chain, ModuleId}, utils::{default_host, default_u16, default_u32}, }; -/// The signing configuration for a commitment module. -#[derive(Clone, Debug, PartialEq)] -pub struct ModuleSigningConfig { - /// Human-readable name of the module. - pub module_name: ModuleId, - - /// The JWT secret for the module to communicate with the signer module. - pub jwt_secret: String, - - /// A unique identifier for the module, which is used when signing requests - /// to generate signatures for this module. Must be a 32-byte hex string. - /// A leading 0x prefix is optional. - pub signing_id: B256, -} - -impl ModuleSigningConfig { - pub fn validate(&self) -> Result<()> { - if self.jwt_secret.is_empty() { - bail!("JWT secret cannot be empty"); - } - - if self.signing_id.is_zero() { - bail!("Signing ID cannot be zero"); - } - - Ok(()) - } -} - -/// Mode to use for TLS support when starting the signer service -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "type", content = "path", rename_all = "snake_case")] -pub enum TlsMode { - /// Don't use TLS (regular HTTP) - Insecure, - - /// Use TLS with a certificate and key file in the provided directory - Certificate(PathBuf), -} - -/// Reverse proxy setup, used to extract real client's IP -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "snake_case", tag = "type")] -pub enum ReverseProxyHeaderSetup { - #[default] - None, - Unique { - header: String, - }, - Rightmost { - header: String, - trusted_count: NonZeroUsize, - }, -} - -impl Display for ReverseProxyHeaderSetup { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ReverseProxyHeaderSetup::None => write!(f, "None"), - ReverseProxyHeaderSetup::Unique { header } => { - write!(f, "\"{header} (unique)\"") - } - ReverseProxyHeaderSetup::Rightmost { header, trusted_count } => { - let n = trusted_count.get(); - let suffix = match (n % 100, n % 10) { - (11..=13, _) => "th", - (_, 1) => "st", - (_, 2) => "nd", - (_, 3) => "rd", - _ => "th", - }; - write!(f, "\"{header} ({trusted_count}{suffix} from the right)\"") - } - } - } -} - #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] pub struct SignerConfig { @@ -115,7 +32,6 @@ pub struct SignerConfig { /// Port to listen for signer API calls on #[serde(default = "default_u16::")] pub port: u16, - /// Docker image of the module #[serde(default = "default_signer_image")] pub docker_image: String, @@ -126,21 +42,10 @@ pub struct SignerConfig { pub jwt_auth_fail_limit: u32, /// Duration in seconds to rate limit an endpoint after the JWT auth failure - /// limit has been reached. This also defines the interval at which failed - /// attempts are regularly checked and expired ones are cleaned up. + /// limit has been reached #[serde(default = "default_u32::")] pub jwt_auth_fail_timeout_seconds: u32, - /// Mode to use for TLS support. - /// If using Certificate mode, this must include a path to the TLS - /// certificates directory (with a `cert.pem` and a `key.pem` file). - #[serde(default = "default_tls_mode")] - pub tls_mode: TlsMode, - - /// Reverse proxy setup to extract real client's IP - #[serde(default)] - pub reverse_proxy: ReverseProxyHeaderSetup, - /// Inner type-specific configuration #[serde(flatten)] pub inner: SignerType, @@ -167,11 +72,6 @@ fn default_signer_image() -> String { SIGNER_IMAGE_DEFAULT.to_string() } -fn default_tls_mode() -> TlsMode { - TlsMode::Insecure // To make the default use TLS, do - // TlsMode::Certificate(PathBuf::from("./certs")) -} - #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] pub struct DirkHostConfig { @@ -234,23 +134,17 @@ pub struct StartSignerConfig { pub loader: Option, pub store: Option, pub endpoint: SocketAddr, - pub mod_signing_configs: HashMap, - pub admin_secret: String, + pub jwts: HashMap, pub jwt_auth_fail_limit: u32, pub jwt_auth_fail_timeout_seconds: u32, pub dirk: Option, - pub tls_certificates: Option<(Vec, Vec)>, - pub reverse_proxy: ReverseProxyHeaderSetup, } impl StartSignerConfig { pub fn load_from_env() -> Result { - let (config, _) = CommitBoostConfig::from_env_path()?; + let config = CommitBoostConfig::from_env_path()?; - let (admin_secret, jwt_secrets) = load_jwt_secrets()?; - - let mod_signing_configs = load_module_signing_configs(&config, &jwt_secrets) - .wrap_err("Failed to load module signing configs")?; + let jwts = load_jwt_secrets()?; let signer_config = config.signer.ok_or_eyre("Signer config is missing")?; @@ -279,35 +173,16 @@ impl StartSignerConfig { signer_config.jwt_auth_fail_timeout_seconds }; - // Load the TLS certificates if requested, generating self-signed ones if - // necessary - let tls_certificates = match signer_config.tls_mode { - TlsMode::Insecure => None, - TlsMode::Certificate(path) => { - let certs_path = load_env_var(SIGNER_TLS_CERTIFICATES_PATH_ENV) - .map(PathBuf::from) - .unwrap_or(path); - let cert_path = certs_path.join(SIGNER_TLS_CERTIFICATE_NAME); - let key_path = certs_path.join(SIGNER_TLS_KEY_NAME); - Some((std::fs::read(cert_path)?, std::fs::read(key_path)?)) - } - }; - - let reverse_proxy = signer_config.reverse_proxy; - match signer_config.inner { SignerType::Local { loader, store, .. } => Ok(StartSignerConfig { chain: config.chain, loader: Some(loader), endpoint, - mod_signing_configs, - admin_secret, + jwts, jwt_auth_fail_limit, jwt_auth_fail_timeout_seconds, store, dirk: None, - tls_certificates, - reverse_proxy, }), SignerType::Dirk { @@ -333,8 +208,7 @@ impl StartSignerConfig { Ok(StartSignerConfig { chain: config.chain, endpoint, - mod_signing_configs, - admin_secret, + jwts, jwt_auth_fail_limit, jwt_auth_fail_timeout_seconds, loader: None, @@ -354,8 +228,6 @@ impl StartSignerConfig { }, max_response_size_bytes, }), - tls_certificates, - reverse_proxy, }) } @@ -365,548 +237,3 @@ impl StartSignerConfig { } } } - -/// Loads the signing configurations for each module defined in the Commit Boost -/// config, coupling them with their JWT secrets and handling any potential -/// duplicates or missing values. -pub fn load_module_signing_configs( - config: &CommitBoostConfig, - jwt_secrets: &HashMap, -) -> Result> { - let mut mod_signing_configs = HashMap::new(); - let modules = config.modules.as_ref().ok_or_eyre("No modules defined in the config")?; - - let mut seen_jwt_secrets = HashMap::new(); - let mut seen_signing_ids = HashMap::new(); - for module in modules { - ensure!(!module.id.is_empty(), "Module ID cannot be empty"); - - ensure!( - !mod_signing_configs.contains_key(&module.id), - "Duplicate module config detected: ID {} is already used", - module.id - ); - - let jwt_secret = match jwt_secrets.get(&module.id) { - Some(secret) => secret.clone(), - None => bail!("JWT secret for module {} is missing", module.id), - }; - let module_signing_config = ModuleSigningConfig { - module_name: module.id.clone(), - jwt_secret, - signing_id: module.signing_id, - }; - module_signing_config - .validate() - .wrap_err(format!("Invalid signing config for module {}", module.id))?; - - if let Some(existing_module) = - seen_jwt_secrets.insert(module_signing_config.jwt_secret.clone(), &module.id) - { - bail!("Duplicate JWT secret detected for modules {} and {}", existing_module, module.id) - }; - if let Some(existing_module) = - seen_signing_ids.insert(module_signing_config.signing_id, &module.id) - { - bail!("Duplicate signing ID detected for modules {} and {}", existing_module, module.id) - }; - - mod_signing_configs.insert(module.id.clone(), module_signing_config); - } - - Ok(mod_signing_configs) -} - -#[cfg(test)] -mod tests { - use std::num::NonZeroUsize; - - use alloy::primitives::{Uint, b256}; - - use super::*; - use crate::config::{LogsSettings, ModuleKind, PbsConfig, StaticModuleConfig, StaticPbsConfig}; - - // Wrapper needed because TOML requires a top-level struct (can't serialize - // a bare enum). - #[derive(Serialize, Deserialize, Debug)] - struct TlsWrapper { - tls_mode: TlsMode, - } - - fn make_local_signer_config(tls_mode: TlsMode) -> SignerConfig { - SignerConfig { - host: Ipv4Addr::LOCALHOST, - port: 20000, - docker_image: SIGNER_IMAGE_DEFAULT.to_string(), - jwt_auth_fail_limit: 3, - jwt_auth_fail_timeout_seconds: 300, - tls_mode, - reverse_proxy: ReverseProxyHeaderSetup::None, - inner: SignerType::Local { - loader: SignerLoader::File { key_path: PathBuf::from("/keys.json") }, - store: None, - }, - } - } - - async fn get_config_with_signer(tls_mode: TlsMode) -> CommitBoostConfig { - let mut cfg = get_base_config().await; - cfg.signer = Some(make_local_signer_config(tls_mode)); - cfg - } - - async fn get_base_config() -> CommitBoostConfig { - CommitBoostConfig { - chain: Chain::Hoodi, - relays: vec![], - pbs: StaticPbsConfig { - docker_image: String::from("cb-fake-repo/fake-cb:latest"), - pbs_config: PbsConfig { - host: Ipv4Addr::LOCALHOST, - port: 0, - relay_check: false, - wait_all_registrations: false, - timeout_get_header_ms: 0, - timeout_get_payload_ms: 0, - timeout_register_validator_ms: 0, - skip_sigverify: false, - min_bid_wei: Uint::<256, 4>::from(0), - late_in_slot_time_ms: 0, - extra_validation_enabled: false, - rpc_url: None, - http_timeout_seconds: 30, - register_validator_retry_limit: 3, - validator_registration_batch_size: None, - mux_registry_refresh_interval_seconds: 5, - ssv_node_api_url: Url::parse("https://example.net").unwrap(), - ssv_public_api_url: Url::parse("https://example.net").unwrap(), - }, - with_signer: true, - }, - muxes: None, - modules: Some(vec![]), - signer: None, - metrics: None, - logs: LogsSettings::default(), - } - } - - async fn create_module_config(id: ModuleId, signing_id: B256) -> StaticModuleConfig { - StaticModuleConfig { - id: id.clone(), - signing_id, - docker_image: String::from(""), - env: None, - env_file: None, - kind: ModuleKind::Commit, - } - } - - #[tokio::test] - async fn test_good_config() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let second_module_id = ModuleId("2nd_test_module".to_string()); - let second_signing_id = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - cfg.modules = Some(vec![ - create_module_config(first_module_id.clone(), first_signing_id).await, - create_module_config(second_module_id.clone(), second_signing_id).await, - ]); - - let jwts = HashMap::from([ - (first_module_id.clone(), "supersecret".to_string()), - (second_module_id.clone(), "another-secret".to_string()), - ]); - - // Load the mod signing configuration - let mod_signing_configs = load_module_signing_configs(&cfg, &jwts) - .wrap_err("Failed to load module signing configs")?; - assert!(mod_signing_configs.len() == 2, "Expected 2 mod signing configurations"); - - // Check the first module - let module_1 = mod_signing_configs - .get(&first_module_id) - .unwrap_or_else(|| panic!("Missing '{first_module_id}' in mod signing configs")); - assert_eq!(module_1.module_name, first_module_id, "Module name mismatch for 'test_module'"); - assert_eq!( - module_1.jwt_secret, jwts[&first_module_id], - "JWT secret mismatch for '{first_module_id}'" - ); - assert_eq!( - module_1.signing_id, first_signing_id, - "Signing ID mismatch for '{first_module_id}'" - ); - - // Check the second module - let module_2 = mod_signing_configs - .get(&second_module_id) - .unwrap_or_else(|| panic!("Missing '{second_module_id}' in mod signing configs")); - assert_eq!( - module_2.module_name, second_module_id, - "Module name mismatch for '{second_module_id}'" - ); - assert_eq!( - module_2.jwt_secret, jwts[&second_module_id], - "JWT secret mismatch for '{second_module_id}'" - ); - assert_eq!( - module_2.signing_id, second_signing_id, - "Signing ID mismatch for '{second_module_id}'" - ); - - Ok(()) - } - - #[tokio::test] - async fn test_duplicate_module_names() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let second_module_id = ModuleId("2nd_test_module".to_string()); - let second_signing_id = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - cfg.modules = Some(vec![ - create_module_config(first_module_id.clone(), first_signing_id).await, - create_module_config(first_module_id.clone(), second_signing_id).await, /* Duplicate - * module - * name */ - ]); - - let jwts = HashMap::from([ - (first_module_id.clone(), "supersecret".to_string()), - (second_module_id.clone(), "another-secret".to_string()), - ]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to duplicate module names"); - if let Err(e) = result { - assert_eq!( - e.to_string(), - format!("Duplicate module config detected: ID {first_module_id} is already used") - ); - } - Ok(()) - } - - #[tokio::test] - async fn test_duplicate_jwt_secrets() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let second_module_id = ModuleId("2nd_test_module".to_string()); - let second_signing_id = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - cfg.modules = Some(vec![ - create_module_config(first_module_id.clone(), first_signing_id).await, - create_module_config(second_module_id.clone(), second_signing_id).await, - ]); - - let jwts = HashMap::from([ - (first_module_id.clone(), "supersecret".to_string()), - (second_module_id.clone(), "supersecret".to_string()), /* Duplicate JWT secret */ - ]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to duplicate JWT secrets"); - if let Err(e) = result { - assert_eq!( - e.to_string(), - format!( - "Duplicate JWT secret detected for modules {first_module_id} and {second_module_id}", - ) - ); - } - Ok(()) - } - - #[tokio::test] - async fn test_duplicate_signing_ids() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let second_module_id = ModuleId("2nd_test_module".to_string()); - - cfg.modules = Some(vec![ - create_module_config(first_module_id.clone(), first_signing_id).await, - create_module_config(second_module_id.clone(), first_signing_id).await, /* Duplicate signing ID */ - ]); - - let jwts = HashMap::from([ - (first_module_id.clone(), "supersecret".to_string()), - (second_module_id.clone(), "another-secret".to_string()), - ]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to duplicate signing IDs"); - if let Err(e) = result { - assert_eq!( - e.to_string(), - format!( - "Duplicate signing ID detected for modules {first_module_id} and {second_module_id}", - ) - ); - } - Ok(()) - } - - #[tokio::test] - async fn test_missing_jwt_secret() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let second_module_id = ModuleId("2nd_test_module".to_string()); - let second_signing_id = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - cfg.modules = Some(vec![ - create_module_config(first_module_id.clone(), first_signing_id).await, - create_module_config(second_module_id.clone(), second_signing_id).await, - ]); - - let jwts = HashMap::from([(second_module_id.clone(), "another-secret".to_string())]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to missing JWT secret"); - if let Err(e) = result { - assert_eq!( - e.to_string(), - format!("JWT secret for module {first_module_id} is missing") - ); - } - Ok(()) - } - - #[tokio::test] - async fn test_empty_jwt_secret() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - cfg.modules = - Some(vec![create_module_config(first_module_id.clone(), first_signing_id).await]); - - let jwts = HashMap::from([(first_module_id.clone(), "".to_string())]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to empty JWT secret"); - if let Err(e) = result { - assert!(format!("{:?}", e).contains("JWT secret cannot be empty")); - } - - Ok(()) - } - - #[tokio::test] - async fn test_zero_signing_id() -> Result<()> { - let mut cfg = get_base_config().await; - let first_module_id = ModuleId("test_module".to_string()); - let first_signing_id = - b256!("0000000000000000000000000000000000000000000000000000000000000000"); - - cfg.modules = - Some(vec![create_module_config(first_module_id.clone(), first_signing_id).await]); - - let jwts = HashMap::from([(first_module_id.clone(), "supersecret".to_string())]); - - // Make sure there was an error - let result = load_module_signing_configs(&cfg, &jwts); - assert!(result.is_err(), "Expected error due to zero signing ID"); - if let Err(e) = result { - assert!(format!("{:?}", e).contains("Signing ID cannot be zero")); - } - Ok(()) - } - - // ── TlsMode serde ──────────────────────────────────────────────────────── - - #[test] - fn test_tls_mode_insecure_roundtrip() -> Result<()> { - let original = TlsWrapper { tls_mode: TlsMode::Insecure }; - let toml_str = toml::to_string(&original)?; - let parsed: TlsWrapper = toml::from_str(&toml_str)?; - assert!(matches!(parsed.tls_mode, TlsMode::Insecure)); - Ok(()) - } - - #[test] - fn test_tls_mode_certificate_roundtrip() -> Result<()> { - let path = PathBuf::from("/certs"); - let original = TlsWrapper { tls_mode: TlsMode::Certificate(path.clone()) }; - let toml_str = toml::to_string(&original)?; - let parsed: TlsWrapper = toml::from_str(&toml_str)?; - match parsed.tls_mode { - TlsMode::Certificate(p) => assert_eq!(p, path), - TlsMode::Insecure => panic!("Expected Certificate variant"), - } - Ok(()) - } - - #[test] - fn test_tls_mode_insecure_from_toml() -> Result<()> { - let toml_str = r#" - [tls_mode] - type = "insecure" - "#; - let parsed: TlsWrapper = toml::from_str(toml_str)?; - assert!(matches!(parsed.tls_mode, TlsMode::Insecure)); - Ok(()) - } - - #[test] - fn test_tls_mode_certificate_from_toml() -> Result<()> { - let toml_str = r#" - [tls_mode] - type = "certificate" - path = "/custom/certs" - "#; - let parsed: TlsWrapper = toml::from_str(toml_str)?; - match parsed.tls_mode { - TlsMode::Certificate(p) => assert_eq!(p, PathBuf::from("/custom/certs")), - TlsMode::Insecure => panic!("Expected Certificate variant"), - } - Ok(()) - } - - // ── signer_uses_tls ─────────────────────────────────────────────────────── - - #[tokio::test] - async fn test_signer_uses_tls_no_signer() { - let cfg = get_base_config().await; - assert!(!cfg.signer_uses_tls()); - } - - #[tokio::test] - async fn test_signer_uses_tls_insecure() { - let cfg = get_config_with_signer(TlsMode::Insecure).await; - assert!(!cfg.signer_uses_tls()); - } - - #[tokio::test] - async fn test_signer_uses_tls_certificate() { - let cfg = get_config_with_signer(TlsMode::Certificate(PathBuf::from("/certs"))).await; - assert!(cfg.signer_uses_tls()); - } - - // ── signer_certs_path ───────────────────────────────────────────────────── - - #[tokio::test] - async fn test_signer_certs_path_no_signer() { - let cfg = get_base_config().await; - assert!(cfg.signer_certs_path().is_none()); - } - - #[tokio::test] - async fn test_signer_certs_path_insecure() { - let cfg = get_config_with_signer(TlsMode::Insecure).await; - assert!(cfg.signer_certs_path().is_none()); - } - - #[tokio::test] - async fn test_signer_certs_path_certificate() { - let certs_path = PathBuf::from("/my/certs"); - let cfg = get_config_with_signer(TlsMode::Certificate(certs_path.clone())).await; - assert_eq!(cfg.signer_certs_path(), Some(&certs_path)); - } - - // ── signer_server_url ───────────────────────────────────────────────────── - - #[tokio::test] - async fn test_signer_server_url_no_signer_uses_default_port() { - let cfg = get_base_config().await; - assert_eq!(cfg.signer_server_url(12345), "http://cb_signer:12345"); - } - - #[tokio::test] - async fn test_signer_server_url_insecure_uses_http() { - let cfg = get_config_with_signer(TlsMode::Insecure).await; - assert_eq!(cfg.signer_server_url(9999), "http://cb_signer:20000"); - } - - #[tokio::test] - async fn test_signer_server_url_certificate_uses_https() { - let cfg = get_config_with_signer(TlsMode::Certificate(PathBuf::from("/certs"))).await; - assert_eq!(cfg.signer_server_url(9999), "https://cb_signer:20000"); - } - - #[tokio::test] - async fn test_signer_server_url_remote_returned_as_is() { - let remote_url = Url::parse("https://remote-signer.example.com:8080").unwrap(); - let mut cfg = get_base_config().await; - cfg.signer = Some(SignerConfig { - host: Ipv4Addr::new(127, 0, 0, 1), - port: 20000, - docker_image: SIGNER_IMAGE_DEFAULT.to_string(), - jwt_auth_fail_limit: 3, - jwt_auth_fail_timeout_seconds: 300, - tls_mode: TlsMode::Insecure, - reverse_proxy: ReverseProxyHeaderSetup::None, - inner: SignerType::Remote { url: remote_url.clone() }, - }); - assert_eq!(cfg.signer_server_url(9999), remote_url.to_string()); - } - - // ── ReverseProxyHeaderSetup Display ────────────────────────────────────── - - #[test] - fn test_reverse_proxy_display_none() { - assert_eq!(ReverseProxyHeaderSetup::None.to_string(), "None"); - } - - #[test] - fn test_reverse_proxy_display_unique() { - let rp = ReverseProxyHeaderSetup::Unique { header: "X-Forwarded-For".to_string() }; - assert_eq!(rp.to_string(), r#""X-Forwarded-For (unique)""#); - } - - #[test] - fn test_reverse_proxy_display_rightmost_1st() { - let rp = ReverseProxyHeaderSetup::Rightmost { - header: "X-Real-IP".to_string(), - trusted_count: NonZeroUsize::new(1).unwrap(), - }; - assert_eq!(rp.to_string(), r#""X-Real-IP (1st from the right)""#); - } - - #[test] - fn test_reverse_proxy_display_rightmost_2nd() { - let rp = ReverseProxyHeaderSetup::Rightmost { - header: "X-Real-IP".to_string(), - trusted_count: NonZeroUsize::new(2).unwrap(), - }; - assert_eq!(rp.to_string(), r#""X-Real-IP (2nd from the right)""#); - } - - #[test] - fn test_reverse_proxy_display_rightmost_3rd() { - let rp = ReverseProxyHeaderSetup::Rightmost { - header: "X-Real-IP".to_string(), - trusted_count: NonZeroUsize::new(3).unwrap(), - }; - assert_eq!(rp.to_string(), r#""X-Real-IP (3rd from the right)""#); - } - - #[test] - fn test_reverse_proxy_display_rightmost_nth() { - let rp = ReverseProxyHeaderSetup::Rightmost { - header: "CF-Connecting-IP".to_string(), - trusted_count: NonZeroUsize::new(5).unwrap(), - }; - assert_eq!(rp.to_string(), r#""CF-Connecting-IP (5th from the right)""#); - } -} diff --git a/crates/common/src/config/utils.rs b/crates/common/src/config/utils.rs index 579825b6..b956df59 100644 --- a/crates/common/src/config/utils.rs +++ b/crates/common/src/config/utils.rs @@ -1,13 +1,11 @@ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; +use std::{collections::HashMap, path::Path}; use eyre::{Context, Result, bail}; use serde::de::DeserializeOwned; +use super::JWTS_ENV; use crate::{ - config::{ADMIN_JWT_ENV, JWTS_ENV, MUXER_HTTP_MAX_LENGTH}, + config::MUXER_HTTP_MAX_LENGTH, types::{BlsPublicKey, ModuleId}, utils::read_chunked_body_with_max, }; @@ -19,27 +17,21 @@ pub fn load_optional_env_var(env: &str) -> Option { std::env::var(env).ok() } -pub fn load_from_file + std::fmt::Debug, T: DeserializeOwned>( - path: P, -) -> Result<(T, PathBuf)> { +pub fn load_from_file + std::fmt::Debug, T: DeserializeOwned>(path: P) -> Result { let config_file = std::fs::read_to_string(path.as_ref()) .wrap_err(format!("Unable to find config file: {path:?}"))?; - match toml::from_str(&config_file).wrap_err("could not deserialize toml from string") { - Ok(config) => Ok((config, path.as_ref().to_path_buf())), - Err(e) => Err(e), - } + toml::from_str(&config_file).wrap_err("could not deserialize toml from string") } -pub fn load_file_from_env(env: &str) -> Result<(T, PathBuf)> { +pub fn load_file_from_env(env: &str) -> Result { let path = std::env::var(env).wrap_err(format!("{env} is not set"))?; load_from_file(&path) } /// Loads a map of module id -> jwt secret from a json env -pub fn load_jwt_secrets() -> Result<(String, HashMap)> { - let admin_jwt = std::env::var(ADMIN_JWT_ENV).wrap_err(format!("{ADMIN_JWT_ENV} is not set"))?; +pub fn load_jwt_secrets() -> Result> { let jwt_secrets = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?; - decode_string_to_map(&jwt_secrets).map(|secrets| (admin_jwt, secrets)) + decode_string_to_map(&jwt_secrets) } /// Reads an HTTP response safely, erroring out if it failed or if the body is @@ -82,7 +74,7 @@ pub fn remove_duplicate_keys(keys: Vec) -> Vec { unique_keys } -pub fn decode_string_to_map(raw: &str) -> Result> { +fn decode_string_to_map(raw: &str) -> Result> { // trim the string and split for comma raw.trim() .split(',') @@ -98,68 +90,19 @@ pub fn decode_string_to_map(raw: &str) -> Result> { #[cfg(test)] mod tests { - use std::sync::Mutex; - use super::*; use crate::utils::TestRandomSeed; - // Serializes all tests that read/write environment variables. - // std::env::set_var is unsafe (Rust 1.81+) because mutating `environ` - // while another thread reads it is UB at the OS level. Holding this - // lock ensures our Rust threads don't race each other. - static ENV_LOCK: Mutex<()> = Mutex::new(()); - - /// Sets or removes env vars for the duration of `f`, then restores the - /// original values. Pass `Some("val")` to set, `None` to ensure absent. - fn with_env(vars: &[(&str, Option<&str>)], f: impl FnOnce() -> R) -> R { - let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); - let saved: Vec<(&str, Option)> = - vars.iter().map(|(k, _)| (*k, std::env::var(k).ok())).collect(); - for (k, v) in vars { - match v { - Some(val) => unsafe { std::env::set_var(k, val) }, - None => unsafe { std::env::remove_var(k) }, - } - } - let result = f(); - for (k, old) in &saved { - match old { - Some(v) => unsafe { std::env::set_var(k, v) }, - None => unsafe { std::env::remove_var(k) }, - } - } - result - } - - // Minimal TOML-deserializable type used by load_from_file / load_file_from_env - // tests. - #[derive(serde::Deserialize, Debug, PartialEq)] - struct TestConfig { - value: String, - } - - // ── decode_string_to_map ───────────────────────────────────────────────── - #[test] - fn test_decode_string_to_map_single_pair() { - let map = decode_string_to_map("ONLY=ONE").unwrap(); - assert_eq!(map.len(), 1); - assert_eq!(map.get(&ModuleId("ONLY".into())), Some(&"ONE".to_string())); - } + fn test_decode_string_to_map() { + let raw = " KEY=VALUE , KEY2=value2 "; - #[test] - fn test_decode_string_to_map_empty_string() { - // An empty string yields one token with no `=`, which is invalid. - assert!(decode_string_to_map("").is_err()); - } + let map = decode_string_to_map(raw).unwrap(); - #[test] - fn test_decode_string_to_map_malformed_no_equals() { - assert!(decode_string_to_map("KEYONLY").is_err()); + assert_eq!(map.get(&ModuleId("KEY".into())), Some(&"VALUE".to_string())); + assert_eq!(map.get(&ModuleId("KEY2".into())), Some(&"value2".to_string())); } - // ── remove_duplicate_keys ──────────────────────────────────────────────── - #[test] fn test_remove_duplicate_keys() { let key1 = BlsPublicKey::test_random(); @@ -171,134 +114,4 @@ mod tests { assert!(unique_keys.contains(&key1)); assert!(unique_keys.contains(&key2)); } - - // ── load_env_var ───────────────────────────────────────────────────────── - - #[test] - fn test_load_env_var_present() { - with_env(&[("CB_TEST_LOAD_ENV_VAR", Some("hello"))], || { - assert_eq!(load_env_var("CB_TEST_LOAD_ENV_VAR").unwrap(), "hello"); - }); - } - - #[test] - fn test_load_env_var_absent() { - with_env(&[("CB_TEST_LOAD_ENV_VAR_ABSENT", None)], || { - let err = load_env_var("CB_TEST_LOAD_ENV_VAR_ABSENT").unwrap_err(); - assert!(err.to_string().contains("CB_TEST_LOAD_ENV_VAR_ABSENT")); - }); - } - - // ── load_optional_env_var ──────────────────────────────────────────────── - - #[test] - fn test_load_optional_env_var_present() { - with_env(&[("CB_TEST_OPT_VAR", Some("world"))], || { - assert_eq!(load_optional_env_var("CB_TEST_OPT_VAR"), Some("world".to_string())); - }); - } - - #[test] - fn test_load_optional_env_var_absent() { - with_env(&[("CB_TEST_OPT_VAR_ABSENT", None)], || { - assert_eq!(load_optional_env_var("CB_TEST_OPT_VAR_ABSENT"), None); - }); - } - - // ── load_from_file ─────────────────────────────────────────────────────── - - #[test] - fn test_load_from_file_valid() { - use std::io::Write as _; - let mut file = tempfile::NamedTempFile::new().unwrap(); - file.write_all(b"value = \"hello\"").unwrap(); - let path = file.path().to_path_buf(); - - let (config, returned_path): (TestConfig, _) = load_from_file(&path).unwrap(); - assert_eq!(config.value, "hello"); - assert_eq!(returned_path, path); - } - - #[test] - fn test_load_from_file_missing() { - let result: eyre::Result<(TestConfig, _)> = - load_from_file("/nonexistent/cb_test_path/file.toml"); - assert!(result.is_err()); - } - - #[test] - fn test_load_from_file_invalid_toml() { - use std::io::Write as _; - let mut file = tempfile::NamedTempFile::new().unwrap(); - file.write_all(b"not valid toml !!!{{").unwrap(); - - let result: eyre::Result<(TestConfig, _)> = load_from_file(file.path()); - assert!(result.is_err()); - } - - // ── load_file_from_env ─────────────────────────────────────────────────── - - #[test] - fn test_load_file_from_env_ok() { - use std::io::Write as _; - let mut file = tempfile::NamedTempFile::new().unwrap(); - file.write_all(b"value = \"from_env\"").unwrap(); - let path = file.path().to_str().unwrap().to_owned(); - - with_env(&[("CB_TEST_FILE_ENV", Some(&path))], || { - let (config, _): (TestConfig, _) = load_file_from_env("CB_TEST_FILE_ENV").unwrap(); - assert_eq!(config.value, "from_env"); - }); - } - - #[test] - fn test_load_file_from_env_var_not_set() { - with_env(&[("CB_TEST_FILE_ENV_ABSENT", None)], || { - let result: eyre::Result<(TestConfig, _)> = - load_file_from_env("CB_TEST_FILE_ENV_ABSENT"); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("CB_TEST_FILE_ENV_ABSENT")); - }); - } - - // ── load_jwt_secrets ───────────────────────────────────────────────────── - - #[test] - fn test_load_jwt_secrets_ok() { - with_env( - &[ - (ADMIN_JWT_ENV, Some("admin_secret")), - (JWTS_ENV, Some("MODULE1=secret1,MODULE2=secret2")), - ], - || { - let (admin_jwt, secrets) = load_jwt_secrets().unwrap(); - assert_eq!(admin_jwt, "admin_secret"); - assert_eq!(secrets.get(&ModuleId("MODULE1".into())), Some(&"secret1".to_string())); - assert_eq!(secrets.get(&ModuleId("MODULE2".into())), Some(&"secret2".to_string())); - }, - ); - } - - #[test] - fn test_load_jwt_secrets_missing_admin_jwt() { - with_env(&[(ADMIN_JWT_ENV, None), (JWTS_ENV, Some("MODULE1=secret1"))], || { - let err = load_jwt_secrets().unwrap_err(); - assert!(err.to_string().contains(ADMIN_JWT_ENV)); - }); - } - - #[test] - fn test_load_jwt_secrets_missing_jwts() { - with_env(&[(ADMIN_JWT_ENV, Some("admin_secret")), (JWTS_ENV, None)], || { - let err = load_jwt_secrets().unwrap_err(); - assert!(err.to_string().contains(JWTS_ENV)); - }); - } - - #[test] - fn test_load_jwt_secrets_malformed_jwts() { - with_env(&[(ADMIN_JWT_ENV, Some("admin_secret")), (JWTS_ENV, Some("MALFORMED"))], || { - assert!(load_jwt_secrets().is_err()); - }); - } } diff --git a/crates/common/src/interop/ssv/types.rs b/crates/common/src/interop/ssv/types.rs index 0a133393..b8ac2e23 100644 --- a/crates/common/src/interop/ssv/types.rs +++ b/crates/common/src/interop/ssv/types.rs @@ -2,60 +2,11 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::types::BlsPublicKey; -/// Response from the SSV API for validators (the new way, relies on using SSV -/// node API) +/// Response from the SSV API for validators #[derive(Deserialize, Serialize)] -pub struct SSVNodeResponse { +pub struct SSVResponse { /// List of validators returned by the SSV API - pub data: Vec, -} - -/// Representation of a validator in the SSV API -#[derive(Clone)] -pub struct SSVNodeValidator { - /// The public key of the validator - pub public_key: BlsPublicKey, -} - -impl<'de> Deserialize<'de> for SSVNodeValidator { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct SSVValidator { - public_key: String, - } - - let s = SSVValidator::deserialize(deserializer)?; - let bytes = alloy::hex::decode(&s.public_key).map_err(serde::de::Error::custom)?; - let pubkey = BlsPublicKey::deserialize(&bytes) - .map_err(|e| serde::de::Error::custom(format!("invalid BLS public key: {e:?}")))?; - - Ok(Self { public_key: pubkey }) - } -} - -impl Serialize for SSVNodeValidator { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - #[derive(Serialize)] - struct SSVValidator { - public_key: String, - } - - let s = SSVValidator { public_key: self.public_key.as_hex_string() }; - s.serialize(serializer) - } -} - -/// Response from the SSV API for validators from the public api.ssv.network URL -#[derive(Deserialize, Serialize)] -pub struct SSVPublicResponse { - /// List of validators returned by the SSV API - pub validators: Vec, + pub validators: Vec, /// Pagination information pub pagination: SSVPagination, @@ -63,12 +14,12 @@ pub struct SSVPublicResponse { /// Representation of a validator in the SSV API #[derive(Clone)] -pub struct SSVPublicValidator { +pub struct SSVValidator { /// The public key of the validator pub pubkey: BlsPublicKey, } -impl<'de> Deserialize<'de> for SSVPublicValidator { +impl<'de> Deserialize<'de> for SSVValidator { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -87,7 +38,7 @@ impl<'de> Deserialize<'de> for SSVPublicValidator { } } -impl Serialize for SSVPublicValidator { +impl Serialize for SSVValidator { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/crates/common/src/interop/ssv/utils.rs b/crates/common/src/interop/ssv/utils.rs index 4a262d69..e443e018 100644 --- a/crates/common/src/interop/ssv/utils.rs +++ b/crates/common/src/interop/ssv/utils.rs @@ -1,52 +1,24 @@ use std::time::Duration; -use alloy::primitives::U256; use eyre::Context; -use serde_json::json; use url::Url; -use crate::{ - config::safe_read_http_response, - interop::ssv::types::{SSVNodeResponse, SSVPublicResponse}, -}; +use crate::{config::safe_read_http_response, interop::ssv::types::SSVResponse}; -pub async fn request_ssv_pubkeys_from_ssv_node( +pub async fn fetch_ssv_pubkeys_from_url( url: Url, - node_operator_id: U256, http_timeout: Duration, -) -> eyre::Result { - let client = reqwest::ClientBuilder::new().timeout(http_timeout).build()?; - let body = json!({ - "operators": [node_operator_id] - }); - let response = client.get(url).json(&body).send().await.map_err(|e| { - if e.is_timeout() { - eyre::eyre!("Request to SSV node timed out: {e}") - } else { - eyre::eyre!("Error sending request to SSV node: {e}") - } - })?; - - // Parse the response as JSON - let body_bytes = safe_read_http_response(response).await?; - serde_json::from_slice::(&body_bytes).wrap_err("failed to parse SSV response") -} - -pub async fn request_ssv_pubkeys_from_public_api( - url: Url, - http_timeout: Duration, -) -> eyre::Result { +) -> eyre::Result { let client = reqwest::ClientBuilder::new().timeout(http_timeout).build()?; let response = client.get(url).send().await.map_err(|e| { if e.is_timeout() { - eyre::eyre!("Request to SSV public API timed out: {e}") + eyre::eyre!("Request to SSV network API timed out: {e}") } else { - eyre::eyre!("Error sending request to SSV public API: {e}") + eyre::eyre!("Error sending request to SSV network API: {e}") } })?; // Parse the response as JSON let body_bytes = safe_read_http_response(response).await?; - serde_json::from_slice::(&body_bytes) - .wrap_err("failed to parse SSV response") + serde_json::from_slice::(&body_bytes).wrap_err("failed to parse SSV response") } diff --git a/crates/common/src/pbs/types/mod.rs b/crates/common/src/pbs/types/mod.rs index 8ad87c08..b79f8f01 100644 --- a/crates/common/src/pbs/types/mod.rs +++ b/crates/common/src/pbs/types/mod.rs @@ -1,6 +1,7 @@ use alloy::primitives::{B256, U256, b256}; +use lh_eth2::ForkVersionedResponse; +pub use lh_types::ForkName; use lh_types::{BlindedPayload, ExecPayload, MainnetEthSpec}; -pub use lh_types::{ForkName, ForkVersionedResponse}; use serde::{Deserialize, Serialize}; use crate::types::BlsPublicKey; @@ -8,22 +9,21 @@ use crate::types::BlsPublicKey; pub const EMPTY_TX_ROOT_HASH: B256 = b256!("7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"); -pub type ExecutionRequests = lh_types::execution_requests::ExecutionRequests; +pub type ExecutionRequests = lh_types::ExecutionRequests; /// Request object of POST `/eth/v1/builder/blinded_blocks` -pub type SignedBlindedBeaconBlock = - lh_types::signed_beacon_block::SignedBlindedBeaconBlock; +pub type SignedBlindedBeaconBlock = lh_types::SignedBlindedBeaconBlock; pub type BlindedBeaconBlock<'a> = - lh_types::beacon_block::BeaconBlockRef<'a, MainnetEthSpec, BlindedPayload>; + lh_types::BeaconBlockRef<'a, MainnetEthSpec, BlindedPayload>; pub type BlindedBeaconBlockElectra = - lh_types::beacon_block::BeaconBlockElectra>; + lh_types::BeaconBlockElectra>; pub type BlindedBeaconBlockFulu = - lh_types::beacon_block::BeaconBlockFulu>; + lh_types::BeaconBlockFulu>; pub type BlobsBundle = lh_eth2::types::BlobsBundle; pub type PayloadAndBlobs = lh_eth2::types::ExecutionPayloadAndBlobs; /// Response object of POST `/eth/v1/builder/blinded_blocks` -pub type SubmitBlindedBlockResponse = lh_types::ForkVersionedResponse; +pub type SubmitBlindedBlockResponse = ForkVersionedResponse; pub type ExecutionPayloadHeader = lh_types::ExecutionPayloadHeader; pub type ExecutionPayloadHeaderElectra = lh_types::ExecutionPayloadHeaderElectra; @@ -32,15 +32,15 @@ pub type ExecutionPayloadHeaderRef<'a> = lh_types::ExecutionPayloadHeaderRef<'a, pub type ExecutionPayload = lh_types::ExecutionPayload; pub type ExecutionPayloadElectra = lh_types::ExecutionPayloadElectra; pub type ExecutionPayloadFulu = lh_types::ExecutionPayloadFulu; -pub type SignedBuilderBid = lh_types::builder_bid::SignedBuilderBid; -pub type BuilderBid = lh_types::builder_bid::BuilderBid; -pub type BuilderBidElectra = lh_types::builder_bid::BuilderBidElectra; +pub type SignedBuilderBid = lh_types::SignedBuilderBid; +pub type BuilderBid = lh_types::BuilderBid; +pub type BuilderBidElectra = lh_types::BuilderBidElectra; /// Response object of GET /// `/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}` -pub type GetHeaderResponse = lh_types::ForkVersionedResponse; +pub type GetHeaderResponse = ForkVersionedResponse; -pub type KzgCommitments = lh_types::beacon_block_body::KzgCommitments; +pub type KzgCommitments = lh_types::KzgCommitments; /// Response params of GET /// `/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}` diff --git a/crates/common/src/signature.rs b/crates/common/src/signature.rs index 41631e33..9753a9a1 100644 --- a/crates/common/src/signature.rs +++ b/crates/common/src/signature.rs @@ -1,44 +1,32 @@ -use alloy::primitives::{Address, B256, aliases::B32}; +use alloy::primitives::B256; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ constants::{COMMIT_BOOST_DOMAIN, GENESIS_VALIDATORS_ROOT}, - signer::{EcdsaSignature, verify_bls_signature, verify_ecdsa_signature}, - types::{self, BlsPublicKey, BlsSecretKey, BlsSignature, Chain, SignatureRequestInfo}, + signer::verify_bls_signature, + types::{BlsPublicKey, BlsSecretKey, BlsSignature, Chain}, }; pub fn sign_message(secret_key: &BlsSecretKey, msg: B256) -> BlsSignature { secret_key.sign(msg) } -pub fn compute_prop_commit_signing_root( - chain: Chain, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, - domain_mask: &B32, -) -> B256 { - let domain = compute_domain(chain, domain_mask); - match signature_request_info { - Some(SignatureRequestInfo { module_signing_id, nonce }) => { - let object_root = types::PropCommitSigningInfo { - data: *object_root, - module_signing_id: *module_signing_id, - nonce: *nonce, - chain_id: chain.id(), - } - .tree_hash_root(); - types::SigningData { object_root, signing_domain: domain }.tree_hash_root() - } - None => types::SigningData { object_root: *object_root, signing_domain: domain } - .tree_hash_root(), +pub fn compute_signing_root(object_root: B256, signing_domain: B256) -> B256 { + #[derive(Default, Debug, TreeHash)] + struct SigningData { + object_root: B256, + signing_domain: B256, } + + let signing_data = SigningData { object_root, signing_domain }; + signing_data.tree_hash_root() } // NOTE: this currently works only for builder domain signatures and // verifications // ref: https://github.com/ralexstokes/ethereum-consensus/blob/cf3c404043230559660810bc0c9d6d5a8498d819/ethereum-consensus/src/builder/mod.rs#L26-L29 -pub fn compute_domain(chain: Chain, domain_mask: &B32) -> B256 { +pub fn compute_domain(chain: Chain, domain_mask: [u8; 4]) -> B256 { #[derive(Debug, TreeHash)] struct ForkData { fork_version: [u8; 4], @@ -46,7 +34,7 @@ pub fn compute_domain(chain: Chain, domain_mask: &B32) -> B256 { } let mut domain = [0u8; 32]; - domain[..4].copy_from_slice(&domain_mask.0); + domain[..4].copy_from_slice(&domain_mask); let fork_version = chain.genesis_fork_version(); let fd = ForkData { fork_version, genesis_validators_root: GENESIS_VALIDATORS_ROOT.into() }; @@ -54,7 +42,7 @@ pub fn compute_domain(chain: Chain, domain_mask: &B32) -> B256 { domain[4..].copy_from_slice(&fork_data_root[..28]); - B256::from(domain) + domain.into() } pub fn verify_signed_message( @@ -62,172 +50,69 @@ pub fn verify_signed_message( pubkey: &BlsPublicKey, msg: &T, signature: &BlsSignature, - signature_request_info: Option<&SignatureRequestInfo>, - domain_mask: &B32, + domain_mask: [u8; 4], ) -> bool { - let signing_root = compute_prop_commit_signing_root( - chain, - &msg.tree_hash_root(), - signature_request_info, - domain_mask, - ); + let domain = compute_domain(chain, domain_mask); + let signing_root = compute_signing_root(msg.tree_hash_root(), domain); + verify_bls_signature(pubkey, signing_root, signature) } -/// Signs a message with the Beacon builder domain. pub fn sign_builder_message( chain: Chain, secret_key: &BlsSecretKey, msg: &impl TreeHash, ) -> BlsSignature { - sign_builder_root(chain, secret_key, &msg.tree_hash_root()) + sign_builder_root(chain, secret_key, msg.tree_hash_root()) } pub fn sign_builder_root( chain: Chain, secret_key: &BlsSecretKey, - object_root: &B256, + object_root: B256, ) -> BlsSignature { - let signing_domain = chain.builder_domain(); - let signing_data = - types::SigningData { object_root: object_root.tree_hash_root(), signing_domain }; - let signing_root = signing_data.tree_hash_root(); + let domain = chain.builder_domain(); + let signing_root = compute_signing_root(object_root, domain); sign_message(secret_key, signing_root) } pub fn sign_commit_boost_root( chain: Chain, secret_key: &BlsSecretKey, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> BlsSignature { - let signing_root = compute_prop_commit_signing_root( - chain, - object_root, - signature_request_info, - &B32::from(COMMIT_BOOST_DOMAIN), - ); + let domain = compute_domain(chain, COMMIT_BOOST_DOMAIN); + let signing_root = compute_signing_root(object_root, domain); sign_message(secret_key, signing_root) } -// ============================== -// === Signature Verification === -// ============================== - -/// Verifies that a proposer commitment signature was generated by the given BLS -/// key for the provided message, chain ID, and module signing ID. -pub fn verify_proposer_commitment_signature_bls( - chain: Chain, - pubkey: &BlsPublicKey, - msg: &impl TreeHash, - signature: &BlsSignature, - module_signing_id: &B256, - nonce: u64, -) -> bool { - let signing_domain = compute_domain(chain, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: msg.tree_hash_root(), - module_signing_id: *module_signing_id, - nonce, - chain_id: chain.id(), - } - .tree_hash_root(); - let signing_root = types::SigningData { object_root, signing_domain }.tree_hash_root(); - verify_bls_signature(pubkey, signing_root, signature) -} - -/// Verifies that a proposer commitment signature was generated by the given -/// ECDSA key for the provided message, chain ID, and module signing ID. -pub fn verify_proposer_commitment_signature_ecdsa( - chain: Chain, - address: &Address, - msg: &impl TreeHash, - signature: &EcdsaSignature, - module_signing_id: &B256, - nonce: u64, -) -> Result<(), eyre::Report> { - let signing_domain = compute_domain(chain, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: msg.tree_hash_root(), - module_signing_id: *module_signing_id, - nonce, - chain_id: chain.id(), - } - .tree_hash_root(); - let signing_root = types::SigningData { object_root, signing_domain }.tree_hash_root(); - verify_ecdsa_signature(address, &signing_root, signature) -} - -// =============== -// === Testing === -// =============== - #[cfg(test)] mod tests { - use alloy::primitives::{U256, aliases::B32}; - - use super::{compute_domain, sign_builder_message, verify_signed_message}; - use crate::{ - constants::APPLICATION_BUILDER_DOMAIN, - pbs::{ - BlindedBeaconBlockElectra, BuilderBid, BuilderBidElectra, - ExecutionPayloadHeaderElectra, ExecutionRequests, - }, - types::{BlsSecretKey, Chain}, - utils::TestRandomSeed, - }; + use super::compute_domain; + use crate::{constants::APPLICATION_BUILDER_DOMAIN, types::Chain}; #[test] fn test_builder_domains() { - let domain = &B32::from(APPLICATION_BUILDER_DOMAIN); - assert_eq!(compute_domain(Chain::Mainnet, domain), Chain::Mainnet.builder_domain()); - assert_eq!(compute_domain(Chain::Holesky, domain), Chain::Holesky.builder_domain()); - assert_eq!(compute_domain(Chain::Sepolia, domain), Chain::Sepolia.builder_domain()); - assert_eq!(compute_domain(Chain::Hoodi, domain), Chain::Hoodi.builder_domain()); - } - - #[test] - fn test_builder_bid_sign_and_verify() { - let secret_key = BlsSecretKey::test_random(); - let pubkey = secret_key.public_key(); - - let message = BuilderBid::Electra(BuilderBidElectra { - header: ExecutionPayloadHeaderElectra::test_random(), - blob_kzg_commitments: Default::default(), - execution_requests: ExecutionRequests::default(), - value: U256::from(10), - pubkey: pubkey.clone().into(), - }); - - let sig = sign_builder_message(Chain::Mainnet, &secret_key, &message); - - assert!(verify_signed_message( - Chain::Mainnet, - &pubkey, - &message, - &sig, - None, - &B32::from(APPLICATION_BUILDER_DOMAIN), - )); - } - - #[test] - fn test_blinded_block_sign_and_verify() { - let secret_key = BlsSecretKey::test_random(); - let pubkey = secret_key.public_key(); - - let block = BlindedBeaconBlockElectra::test_random(); - - let sig = sign_builder_message(Chain::Mainnet, &secret_key, &block); - - assert!(verify_signed_message( - Chain::Mainnet, - &pubkey, - &block, - &sig, - None, - &B32::from(APPLICATION_BUILDER_DOMAIN), - )); + assert_eq!( + compute_domain(Chain::Mainnet, APPLICATION_BUILDER_DOMAIN), + Chain::Mainnet.builder_domain() + ); + assert_eq!( + compute_domain(Chain::Holesky, APPLICATION_BUILDER_DOMAIN), + Chain::Holesky.builder_domain() + ); + assert_eq!( + compute_domain(Chain::Sepolia, APPLICATION_BUILDER_DOMAIN), + Chain::Sepolia.builder_domain() + ); + assert_eq!( + compute_domain(Chain::Helder, APPLICATION_BUILDER_DOMAIN), + Chain::Helder.builder_domain() + ); + assert_eq!( + compute_domain(Chain::Hoodi, APPLICATION_BUILDER_DOMAIN), + Chain::Hoodi.builder_domain() + ); } } diff --git a/crates/common/src/signer/schemes/bls.rs b/crates/common/src/signer/schemes/bls.rs index 07f5e6dd..8525f015 100644 --- a/crates/common/src/signer/schemes/bls.rs +++ b/crates/common/src/signer/schemes/bls.rs @@ -3,7 +3,7 @@ use tree_hash::TreeHash; use crate::{ signature::sign_commit_boost_root, - types::{BlsPublicKey, BlsSecretKey, BlsSignature, Chain, SignatureRequestInfo}, + types::{BlsPublicKey, BlsSecretKey, BlsSignature, Chain}, }; #[derive(Clone)] @@ -28,32 +28,20 @@ impl BlsSigner { } } - pub fn secret(&self) -> B256 { + pub fn secret(&self) -> [u8; 32] { match self { BlsSigner::Local(secret) => secret.serialize().as_bytes().try_into().unwrap(), } } - pub async fn sign( - &self, - chain: Chain, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, - ) -> BlsSignature { + pub async fn sign(&self, chain: Chain, object_root: B256) -> BlsSignature { match self { - BlsSigner::Local(sk) => { - sign_commit_boost_root(chain, sk, object_root, signature_request_info) - } + BlsSigner::Local(sk) => sign_commit_boost_root(chain, sk, object_root), } } - pub async fn sign_msg( - &self, - chain: Chain, - msg: &impl TreeHash, - signature_request_info: Option<&SignatureRequestInfo>, - ) -> BlsSignature { - self.sign(chain, &msg.tree_hash_root(), signature_request_info).await + pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> BlsSignature { + self.sign(chain, msg.tree_hash_root()).await } } diff --git a/crates/common/src/signer/schemes/ecdsa.rs b/crates/common/src/signer/schemes/ecdsa.rs index a597200e..37fc18b4 100644 --- a/crates/common/src/signer/schemes/ecdsa.rs +++ b/crates/common/src/signer/schemes/ecdsa.rs @@ -1,7 +1,7 @@ use std::{ops::Deref, str::FromStr}; use alloy::{ - primitives::{Address, B256, Signature, aliases::B32}, + primitives::{Address, B256, Signature}, signers::{SignerSync, local::PrivateKeySigner}, }; use eyre::ensure; @@ -9,8 +9,8 @@ use tree_hash::TreeHash; use crate::{ constants::COMMIT_BOOST_DOMAIN, - signature::compute_prop_commit_signing_root, - types::{Chain, SignatureRequestInfo}, + signature::{compute_domain, compute_signing_root}, + types::Chain, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -86,37 +86,32 @@ impl EcdsaSigner { pub async fn sign( &self, chain: Chain, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { match self { EcdsaSigner::Local(sk) => { - let signing_root = compute_prop_commit_signing_root( - chain, - object_root, - signature_request_info, - &B32::from(COMMIT_BOOST_DOMAIN), - ); + let domain = compute_domain(chain, COMMIT_BOOST_DOMAIN); + let signing_root = compute_signing_root(object_root, domain); sk.sign_hash_sync(&signing_root).map(EcdsaSignature::from) } } } + pub async fn sign_msg( &self, chain: Chain, msg: &impl TreeHash, - signature_request_info: Option<&SignatureRequestInfo>, ) -> Result { - self.sign(chain, &msg.tree_hash_root(), signature_request_info).await + self.sign(chain, msg.tree_hash_root()).await } } pub fn verify_ecdsa_signature( address: &Address, - msg: &B256, + msg: &[u8; 32], signature: &EcdsaSignature, ) -> eyre::Result<()> { - let recovered = signature.recover_address_from_prehash(msg)?; + let recovered = signature.recover_address_from_prehash(msg.into())?; ensure!(recovered == *address, "invalid signature"); Ok(()) } @@ -124,25 +119,20 @@ pub fn verify_ecdsa_signature( #[cfg(test)] mod test { - use alloy::{ - hex, - primitives::{b256, bytes}, - }; + use alloy::{hex, primitives::bytes}; use super::*; - use crate::{signature::compute_domain, types}; #[tokio::test] - async fn test_ecdsa_signer_noncommit() { + async fn test_ecdsa_signer() { let pk = bytes!("88bcd6672d95bcba0d52a3146494ed4d37675af4ed2206905eb161aa99a6c0d1"); let signer = EcdsaSigner::new_from_bytes(&pk).unwrap(); let object_root = B256::from([1; 32]); - let signature = signer.sign(Chain::Holesky, &object_root, None).await.unwrap(); + let signature = signer.sign(Chain::Holesky, object_root).await.unwrap(); - let domain = compute_domain(Chain::Holesky, &B32::from(COMMIT_BOOST_DOMAIN)); - let signing_data = types::SigningData { object_root, signing_domain: domain }; - let msg = signing_data.tree_hash_root(); + let domain = compute_domain(Chain::Holesky, COMMIT_BOOST_DOMAIN); + let msg = compute_signing_root(object_root, domain); assert_eq!(msg, hex!("219ca7a673b2cbbf67bec6c9f60f78bd051336d57b68d1540190f30667e86725")); @@ -150,41 +140,4 @@ mod test { let verified = verify_ecdsa_signature(&address, &msg, &signature); assert!(verified.is_ok()); } - - #[tokio::test] - async fn test_ecdsa_signer_prop_commit() { - let pk = bytes!("88bcd6672d95bcba0d52a3146494ed4d37675af4ed2206905eb161aa99a6c0d1"); - let signer = EcdsaSigner::new_from_bytes(&pk).unwrap(); - - let object_root = B256::from([1; 32]); - let module_signing_id = B256::from([2; 32]); - let nonce = 42; - let signature = signer - .sign( - Chain::Hoodi, - &object_root, - Some(&SignatureRequestInfo { module_signing_id, nonce }), - ) - .await - .unwrap(); - - let signing_domain = compute_domain(Chain::Hoodi, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: object_root, - module_signing_id, - nonce, - chain_id: Chain::Hoodi.id(), - } - .tree_hash_root(); - let msg = types::SigningData { object_root, signing_domain }.tree_hash_root(); - - assert_eq!( - msg, - b256!("0x0b95fcdb3f003fc6f0fd3238d906f359809e97fe7ec71f56771cb05bee4150bd") - ); - - let address = signer.address(); - let verified = verify_ecdsa_signature(&address, &msg, &signature); - assert!(verified.is_ok()); - } } diff --git a/crates/common/src/signer/store.rs b/crates/common/src/signer/store.rs index d70ea8a0..7cc0fc17 100644 --- a/crates/common/src/signer/store.rs +++ b/crates/common/src/signer/store.rs @@ -244,14 +244,14 @@ impl ProxyStore { serde_json::from_str(&file_content)?; let signer = EcdsaSigner::new_from_bytes(&key_and_delegation.secret)?; - let address = signer.address(); + let pubkey = signer.address(); let proxy_signer = EcdsaProxySigner { signer, delegation: key_and_delegation.delegation, }; - proxy_signers.ecdsa_signers.insert(address, proxy_signer); - ecdsa_map.entry(module_id.clone()).or_default().push(address); + proxy_signers.ecdsa_signers.insert(pubkey, proxy_signer); + ecdsa_map.entry(module_id.clone()).or_default().push(pubkey); } } } @@ -564,8 +564,7 @@ mod test { delegator: consensus_signer.pubkey(), proxy: proxy_signer.pubkey(), }; - let signature = - consensus_signer.sign(Chain::Mainnet, &message.tree_hash_root(), None).await; + let signature = consensus_signer.sign(Chain::Mainnet, message.tree_hash_root()).await; let delegation = SignedProxyDelegationBls { signature: signature.clone(), message }; let proxy_signer = BlsProxySigner { signer: proxy_signer, delegation }; @@ -680,8 +679,7 @@ mod test { delegator: consensus_signer.pubkey(), proxy: proxy_signer.pubkey(), }; - let signature = - consensus_signer.sign(Chain::Mainnet, &message.tree_hash_root(), None).await; + let signature = consensus_signer.sign(Chain::Mainnet, message.tree_hash_root()).await; let delegation = SignedProxyDelegationBls { signature, message }; let proxy_signer = BlsProxySigner { signer: proxy_signer, delegation }; diff --git a/crates/common/src/types.rs b/crates/common/src/types.rs index b347c187..6b06d040 100644 --- a/crates/common/src/types.rs +++ b/crates/common/src/types.rs @@ -1,18 +1,17 @@ use std::path::PathBuf; -use alloy::primitives::{B256, Bytes, U256, aliases::B32, b256, hex}; +use alloy::primitives::{B256, Bytes, b256, hex}; use derive_more::{Deref, Display, From, Into}; use eyre::{Context, bail}; use lh_types::ForkName; use serde::{Deserialize, Serialize}; -use tree_hash_derive::TreeHash; use crate::{constants::APPLICATION_BUILDER_DOMAIN, signature::compute_domain}; -pub type BlsPublicKeyBytes = lh_types::PublicKeyBytes; -pub type BlsPublicKey = lh_types::PublicKey; -pub type BlsSignature = lh_types::Signature; -pub type BlsSecretKey = lh_types::SecretKey; +pub type BlsPublicKeyBytes = lh_bls::PublicKeyBytes; +pub type BlsPublicKey = lh_bls::PublicKey; +pub type BlsSignature = lh_bls::Signature; +pub type BlsSecretKey = lh_bls::SecretKey; #[derive(Clone, Debug, Display, PartialEq, Eq, Hash, Deref, From, Into, Serialize, Deserialize)] #[into(owned, ref, ref_mut)] @@ -27,17 +26,7 @@ pub struct Jwt(pub String); #[derive(Debug, Serialize, Deserialize)] pub struct JwtClaims { pub exp: u64, - pub module: ModuleId, - pub route: String, - pub payload_hash: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JwtAdminClaims { - pub exp: u64, - pub admin: bool, - pub route: String, - pub payload_hash: Option, + pub module: String, } #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -45,13 +34,13 @@ pub enum Chain { Mainnet, Holesky, Sepolia, + Helder, Hoodi, Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: ForkVersion, fulu_fork_slot: u64, - chain_id: U256, }, } @@ -80,7 +69,7 @@ pub type ForkVersion = [u8; 4]; impl std::fmt::Display for Chain { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Mainnet | Self::Holesky | Self::Sepolia | Self::Hoodi => { + Self::Mainnet | Self::Holesky | Self::Sepolia | Self::Helder | Self::Hoodi => { write!(f, "{self:?}") } Self::Custom { .. } => write!(f, "Custom"), @@ -94,35 +83,35 @@ impl std::fmt::Debug for Chain { Self::Mainnet => write!(f, "Mainnet"), Self::Holesky => write!(f, "Holesky"), Self::Sepolia => write!(f, "Sepolia"), + Self::Helder => write!(f, "Helder"), Self::Hoodi => write!(f, "Hoodi"), Self::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } => f .debug_struct("Custom") .field("genesis_time_secs", genesis_time_secs) .field("slot_time_secs", slot_time_secs) .field("genesis_fork_version", &hex::encode_prefixed(genesis_fork_version)) .field("fulu_fork_slot", fulu_fork_slot) - .field("chain_id", chain_id) .finish(), } } } impl Chain { - // Chain IDs are 256-bit unsigned integers because they need to support - // Keccak256 hashes - pub fn id(&self) -> U256 { + pub fn id(&self) -> u64 { match self { Chain::Mainnet => KnownChain::Mainnet.id(), Chain::Holesky => KnownChain::Holesky.id(), Chain::Sepolia => KnownChain::Sepolia.id(), + Chain::Helder => KnownChain::Helder.id(), Chain::Hoodi => KnownChain::Hoodi.id(), - Chain::Custom { chain_id, .. } => *chain_id, + Chain::Custom { .. } => { + unimplemented!("chain id is not supported on custom chains, please file an issue") + } } } @@ -131,8 +120,9 @@ impl Chain { Chain::Mainnet => KnownChain::Mainnet.builder_domain(), Chain::Holesky => KnownChain::Holesky.builder_domain(), Chain::Sepolia => KnownChain::Sepolia.builder_domain(), + Chain::Helder => KnownChain::Helder.builder_domain(), Chain::Hoodi => KnownChain::Hoodi.builder_domain(), - Chain::Custom { .. } => compute_domain(*self, &B32::from(APPLICATION_BUILDER_DOMAIN)), + Chain::Custom { .. } => compute_domain(*self, APPLICATION_BUILDER_DOMAIN), } } @@ -141,6 +131,7 @@ impl Chain { Chain::Mainnet => KnownChain::Mainnet.genesis_fork_version(), Chain::Holesky => KnownChain::Holesky.genesis_fork_version(), Chain::Sepolia => KnownChain::Sepolia.genesis_fork_version(), + Chain::Helder => KnownChain::Helder.genesis_fork_version(), Chain::Hoodi => KnownChain::Hoodi.genesis_fork_version(), Chain::Custom { genesis_fork_version, .. } => *genesis_fork_version, } @@ -151,6 +142,7 @@ impl Chain { Chain::Mainnet => KnownChain::Mainnet.genesis_time_sec(), Chain::Holesky => KnownChain::Holesky.genesis_time_sec(), Chain::Sepolia => KnownChain::Sepolia.genesis_time_sec(), + Chain::Helder => KnownChain::Helder.genesis_time_sec(), Chain::Hoodi => KnownChain::Hoodi.genesis_time_sec(), Chain::Custom { genesis_time_secs, .. } => *genesis_time_secs, } @@ -161,6 +153,7 @@ impl Chain { Chain::Mainnet => KnownChain::Mainnet.slot_time_sec(), Chain::Holesky => KnownChain::Holesky.slot_time_sec(), Chain::Sepolia => KnownChain::Sepolia.slot_time_sec(), + Chain::Helder => KnownChain::Helder.slot_time_sec(), Chain::Hoodi => KnownChain::Hoodi.slot_time_sec(), Chain::Custom { slot_time_secs, .. } => *slot_time_secs, } @@ -171,6 +164,7 @@ impl Chain { Chain::Mainnet => KnownChain::Mainnet.fulu_fork_slot(), Chain::Holesky => KnownChain::Holesky.fulu_fork_slot(), Chain::Sepolia => KnownChain::Sepolia.fulu_fork_slot(), + Chain::Helder => KnownChain::Helder.fulu_fork_slot(), Chain::Hoodi => KnownChain::Hoodi.fulu_fork_slot(), Chain::Custom { slot_time_secs, .. } => *slot_time_secs, } @@ -189,18 +183,21 @@ pub enum KnownChain { Holesky, #[serde(alias = "sepolia")] Sepolia, + #[serde(alias = "helder")] + Helder, #[serde(alias = "hoodi")] Hoodi, } // Constants impl KnownChain { - pub fn id(&self) -> U256 { + pub fn id(&self) -> u64 { match self { - KnownChain::Mainnet => U256::from(1), - KnownChain::Holesky => U256::from(17000), - KnownChain::Sepolia => U256::from(11155111), - KnownChain::Hoodi => U256::from(560048), + KnownChain::Mainnet => 1, + KnownChain::Holesky => 17000, + KnownChain::Sepolia => 11155111, + KnownChain::Helder => 167000, + KnownChain::Hoodi => 560048, } } @@ -215,6 +212,9 @@ impl KnownChain { KnownChain::Sepolia => { b256!("0x00000001d3010778cd08ee514b08fe67b6c503b510987a4ce43f42306d97c67c") } + KnownChain::Helder => { + b256!("0x0000000194c41af484fff7964969e0bdd922f82dff0f4be87a60d0664cc9d1ff") + } KnownChain::Hoodi => { b256!("0x00000001719103511efa4f1362ff2a50996cccf329cc84cb410c5e5c7d351d03") } @@ -226,6 +226,7 @@ impl KnownChain { KnownChain::Mainnet => hex!("00000000"), KnownChain::Holesky => hex!("01017000"), KnownChain::Sepolia => hex!("90000069"), + KnownChain::Helder => hex!("10000000"), KnownChain::Hoodi => hex!("10000910"), } } @@ -235,21 +236,25 @@ impl KnownChain { KnownChain::Mainnet => 1606824023, KnownChain::Holesky => 1695902400, KnownChain::Sepolia => 1655733600, + KnownChain::Helder => 1718967660, KnownChain::Hoodi => 1742213400, } } pub fn slot_time_sec(&self) -> u64 { match self { - KnownChain::Mainnet | KnownChain::Holesky | KnownChain::Sepolia | KnownChain::Hoodi => { - 12 - } + KnownChain::Mainnet | + KnownChain::Holesky | + KnownChain::Sepolia | + KnownChain::Helder | + KnownChain::Hoodi => 12, } } pub fn fulu_fork_slot(&self) -> u64 { match self { KnownChain::Mainnet => 13164544, + KnownChain::Helder => u64::MAX, KnownChain::Holesky => 5283840, KnownChain::Sepolia => 8724480, KnownChain::Hoodi => 1622016, @@ -263,6 +268,7 @@ impl From for Chain { KnownChain::Mainnet => Chain::Mainnet, KnownChain::Holesky => Chain::Holesky, KnownChain::Sepolia => Chain::Sepolia, + KnownChain::Helder => Chain::Helder, KnownChain::Hoodi => Chain::Hoodi, } } @@ -285,7 +291,6 @@ pub enum ChainLoader { slot_time_secs: u64, genesis_fork_version: Bytes, fulu_fork_slot: u64, - chain_id: U256, }, } @@ -298,19 +303,18 @@ impl Serialize for Chain { Chain::Mainnet => ChainLoader::Known(KnownChain::Mainnet), Chain::Holesky => ChainLoader::Known(KnownChain::Holesky), Chain::Sepolia => ChainLoader::Known(KnownChain::Sepolia), + Chain::Helder => ChainLoader::Known(KnownChain::Helder), Chain::Hoodi => ChainLoader::Known(KnownChain::Hoodi), Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } => ChainLoader::Custom { genesis_time_secs: *genesis_time_secs, slot_time_secs: *slot_time_secs, genesis_fork_version: Bytes::from(*genesis_fork_version), fulu_fork_slot: *fulu_fork_slot, - chain_id: *chain_id, }, }; @@ -328,14 +332,13 @@ impl<'de> Deserialize<'de> for Chain { match loader { ChainLoader::Known(known) => Ok(Chain::from(known)), ChainLoader::Path { genesis_time_secs, path } => { - let (slot_time_secs, genesis_fork_version, fulu_fork_slot, chain_id) = + let (slot_time_secs, genesis_fork_version, fulu_fork_slot) = load_chain_from_file(path).map_err(serde::de::Error::custom)?; Ok(Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, }) } ChainLoader::Custom { @@ -343,7 +346,6 @@ impl<'de> Deserialize<'de> for Chain { slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, } => { let genesis_fork_version: ForkVersion = genesis_fork_version.as_ref().try_into().map_err(serde::de::Error::custom)?; @@ -352,45 +354,19 @@ impl<'de> Deserialize<'de> for Chain { slot_time_secs, genesis_fork_version, fulu_fork_slot, - chain_id, }) } } } } -/// Structure for signatures used in Beacon chain operations -#[derive(Default, Debug, TreeHash)] -pub struct SigningData { - pub object_root: B256, - pub signing_domain: B256, -} - -/// Structure for signatures used for proposer commitments in Commit Boost. -/// The signing root of this struct must be used as the object_root of a -/// SigningData for signatures. -#[derive(Default, Debug, TreeHash)] -pub struct PropCommitSigningInfo { - pub data: B256, - pub module_signing_id: B256, - pub nonce: u64, // As per https://eips.ethereum.org/EIPS/eip-2681 - pub chain_id: U256, -} - -/// Information about a signature request, including the module signing ID and -/// nonce. -pub struct SignatureRequestInfo { - pub module_signing_id: B256, - pub nonce: u64, -} - -/// Returns seconds_per_slot, genesis_fork_version, fulu_fork_epoch, and -/// deposit_chain_id from a spec, such as returned by /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec +/// Returns seconds_per_slot and genesis_fork_version from a spec, such as +/// returned by /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec /// Try to load two formats: /// - JSON as return the getSpec endpoint, either with or without the `data` /// field /// - YAML as used e.g. in Kurtosis/Ethereum Package -pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion, u64, U256)> { +pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion, u64)> { #[derive(Deserialize)] #[serde(rename_all = "UPPERCASE")] struct QuotedSpecFile { @@ -401,16 +377,14 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion, u6 slots_per_epoch: u64, #[serde(with = "serde_utils::quoted_u64")] fulu_fork_epoch: u64, - #[serde(with = "serde_utils::quoted_u256")] - deposit_chain_id: U256, } impl QuotedSpecFile { - fn to_chain(&self) -> eyre::Result<(u64, ForkVersion, u64, U256)> { + fn to_chain(&self) -> eyre::Result<(u64, ForkVersion, u64)> { let genesis_fork_version: ForkVersion = self.genesis_fork_version.as_ref().try_into()?; let fulu_fork_slot = self.fulu_fork_epoch.saturating_mul(self.slots_per_epoch); - Ok((self.seconds_per_slot, genesis_fork_version, fulu_fork_slot, self.deposit_chain_id)) + Ok((self.seconds_per_slot, genesis_fork_version, fulu_fork_slot)) } } @@ -426,15 +400,14 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion, u6 genesis_fork_version: u32, slots_per_epoch: Option, fulu_fork_epoch: u64, - deposit_chain_id: U256, } impl SpecFile { - fn to_chain(&self) -> (u64, ForkVersion, u64, U256) { + fn to_chain(&self) -> (u64, ForkVersion, u64) { let genesis_fork_version: ForkVersion = self.genesis_fork_version.to_be_bytes(); let fulu_fork_slot = self.fulu_fork_epoch.saturating_mul(self.slots_per_epoch.unwrap_or(32)); - (self.seconds_per_slot, genesis_fork_version, fulu_fork_slot, self.deposit_chain_id) + (self.seconds_per_slot, genesis_fork_version, fulu_fork_slot) } } @@ -470,14 +443,13 @@ mod tests { #[test] fn test_load_custom() { - let s = r#"chain = { genesis_time_secs = 1, slot_time_secs = 2, genesis_fork_version = "0x01000000", fulu_fork_slot = 1, chain_id = "123" }"#; + let s = r#"chain = { genesis_time_secs = 1, slot_time_secs = 2, genesis_fork_version = "0x01000000", fulu_fork_slot = 1 }"#; let decoded: MockConfig = toml::from_str(s).unwrap(); assert_eq!(decoded.chain, Chain::Custom { genesis_time_secs: 1, slot_time_secs: 2, genesis_fork_version: [1, 0, 0, 0], - fulu_fork_slot: 1, - chain_id: U256::from(123), + fulu_fork_slot: 1 }) } @@ -520,7 +492,6 @@ mod tests { slot_time_secs: KnownChain::Holesky.slot_time_sec(), genesis_fork_version: KnownChain::Holesky.genesis_fork_version(), fulu_fork_slot: KnownChain::Holesky.fulu_fork_slot(), - chain_id: KnownChain::Holesky.id(), }) } @@ -536,13 +507,12 @@ mod tests { let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}"); let decoded: MockConfig = toml::from_str(&s).unwrap(); - assert_eq!(decoded.chain.slot_time_sec(), KnownChain::Sepolia.slot_time_sec()); + assert_eq!(decoded.chain.slot_time_sec(), KnownChain::Helder.slot_time_sec()); assert_eq!(decoded.chain, Chain::Custom { genesis_time_secs: 1, slot_time_secs: KnownChain::Sepolia.slot_time_sec(), genesis_fork_version: KnownChain::Sepolia.genesis_fork_version(), fulu_fork_slot: KnownChain::Sepolia.fulu_fork_slot(), - chain_id: KnownChain::Sepolia.id(), }) } @@ -564,29 +534,27 @@ mod tests { slot_time_secs: KnownChain::Hoodi.slot_time_sec(), genesis_fork_version: KnownChain::Hoodi.genesis_fork_version(), fulu_fork_slot: KnownChain::Hoodi.fulu_fork_slot(), - chain_id: KnownChain::Hoodi.id(), }) } #[test] - fn test_spec_kurtosis_data_json() { + fn test_spec_helder_yml() { let a = env!("CARGO_MANIFEST_DIR"); let mut path = PathBuf::from(a); path.pop(); path.pop(); - path.push("tests/data/kurtosis_spec.json"); + path.push("tests/data/helder_spec.yml"); let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}"); let decoded: MockConfig = toml::from_str(&s).unwrap(); - assert_eq!(decoded.chain.slot_time_sec(), 12); + assert_eq!(decoded.chain.slot_time_sec(), KnownChain::Helder.slot_time_sec()); assert_eq!(decoded.chain, Chain::Custom { genesis_time_secs: 1, - slot_time_secs: 12, - genesis_fork_version: hex!("0x10000038"), - fulu_fork_slot: 0, - chain_id: U256::from(3151908), + slot_time_secs: KnownChain::Helder.slot_time_sec(), + genesis_fork_version: KnownChain::Helder.genesis_fork_version(), + fulu_fork_slot: KnownChain::Helder.fulu_fork_slot(), }) } } diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index e504e477..764ab188 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -5,10 +5,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use alloy::{ - hex, - primitives::{U256, keccak256}, -}; +use alloy::{hex, primitives::U256}; use axum::http::HeaderValue; use futures::StreamExt; use lh_types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; @@ -30,7 +27,7 @@ use crate::{ config::LogsSettings, constants::SIGNER_JWT_EXPIRATION, pbs::HEADER_VERSION_VALUE, - types::{BlsPublicKey, Chain, Jwt, JwtAdminClaims, JwtClaims, ModuleId}, + types::{BlsPublicKey, Chain, Jwt, JwtClaims, ModuleId}, }; const MILLIS_PER_SECOND: u64 = 1_000; @@ -347,19 +344,12 @@ pub fn print_logo() { } /// Create a JWT for the given module id with expiration -pub fn create_jwt( - module_id: &ModuleId, - secret: &str, - route: &str, - payload: Option<&[u8]>, -) -> eyre::Result { +pub fn create_jwt(module_id: &ModuleId, secret: &str) -> eyre::Result { jsonwebtoken::encode( &jsonwebtoken::Header::default(), &JwtClaims { - module: module_id.clone(), - route: route.to_string(), + module: module_id.to_string(), exp: jsonwebtoken::get_current_timestamp() + SIGNER_JWT_EXPIRATION, - payload_hash: payload.map(keccak256), }, &jsonwebtoken::EncodingKey::from_secret(secret.as_ref()), ) @@ -367,134 +357,36 @@ pub fn create_jwt( .map(Jwt::from) } -// Creates a JWT for module administration -pub fn create_admin_jwt( - admin_secret: String, - route: &str, - payload: Option<&[u8]>, -) -> eyre::Result { - jsonwebtoken::encode( - &jsonwebtoken::Header::default(), - &JwtAdminClaims { - admin: true, - route: route.to_string(), - exp: jsonwebtoken::get_current_timestamp() + SIGNER_JWT_EXPIRATION, - payload_hash: payload.map(keccak256), - }, - &jsonwebtoken::EncodingKey::from_secret(admin_secret.as_ref()), - ) - .map_err(Into::into) - .map(Jwt::from) -} - -/// Decode a JWT and return the JWT claims. IMPORTANT: This function does not -/// validate the JWT, it only obtains the claims. -pub fn decode_jwt(jwt: Jwt) -> eyre::Result { - let mut validation = jsonwebtoken::Validation::default(); - validation.insecure_disable_signature_validation(); - - let claims = jsonwebtoken::decode::( - jwt.as_str(), - &jsonwebtoken::DecodingKey::from_secret(&[]), - &validation, - )? - .claims; - - Ok(claims) -} - -/// Decode an administrator JWT and return the JWT claims. IMPORTANT: This -/// function does not validate the JWT, it only obtains the claims. -pub fn decode_admin_jwt(jwt: Jwt) -> eyre::Result { +/// Decode a JWT and return the module id. IMPORTANT: This function does not +/// validate the JWT, it only obtains the module id from the claims. +pub fn decode_jwt(jwt: Jwt) -> eyre::Result { let mut validation = jsonwebtoken::Validation::default(); validation.insecure_disable_signature_validation(); - let claims = jsonwebtoken::decode::( + let module = jsonwebtoken::decode::( jwt.as_str(), &jsonwebtoken::DecodingKey::from_secret(&[]), &validation, )? - .claims; + .claims + .module + .into(); - Ok(claims) + Ok(module) } -pub fn validate_jwt( - jwt: Jwt, - secret: &str, - route: &str, - payload: Option<&[u8]>, -) -> eyre::Result<()> { +/// Validate a JWT with the given secret +pub fn validate_jwt(jwt: Jwt, secret: &str) -> eyre::Result<()> { let mut validation = jsonwebtoken::Validation::default(); validation.leeway = 10; - let claims = jsonwebtoken::decode::( + jsonwebtoken::decode::( jwt.as_str(), &jsonwebtoken::DecodingKey::from_secret(secret.as_ref()), &validation, - )? - .claims; - - // Validate the route - if claims.route != route { - eyre::bail!("Token route does not match"); - } - - // Validate the payload hash if provided - if let Some(payload_bytes) = payload { - if let Some(expected_hash) = claims.payload_hash { - let actual_hash = keccak256(payload_bytes); - if actual_hash != expected_hash { - eyre::bail!("Payload hash does not match"); - } - } else { - eyre::bail!("JWT does not contain a payload hash"); - } - } else if claims.payload_hash.is_some() { - eyre::bail!("JWT contains a payload hash but no payload was provided"); - } - Ok(()) -} - -pub fn validate_admin_jwt( - jwt: Jwt, - secret: &str, - route: &str, - payload: Option<&[u8]>, -) -> eyre::Result<()> { - let mut validation = jsonwebtoken::Validation::default(); - validation.leeway = 10; - - let claims = jsonwebtoken::decode::( - jwt.as_str(), - &jsonwebtoken::DecodingKey::from_secret(secret.as_ref()), - &validation, - )? - .claims; - - if !claims.admin { - eyre::bail!("Token is not admin") - } - - // Validate the route - if claims.route != route { - eyre::bail!("Token route does not match"); - } - - // Validate the payload hash if provided - if let Some(payload_bytes) = payload { - if let Some(expected_hash) = claims.payload_hash { - let actual_hash = keccak256(payload_bytes); - if actual_hash != expected_hash { - eyre::bail!("Payload hash does not match"); - } - } else { - eyre::bail!("JWT does not contain a payload hash"); - } - } else if claims.payload_hash.is_some() { - eyre::bail!("JWT contains a payload hash but no payload was provided"); - } - Ok(()) + ) + .map(|_| ()) + .map_err(From::from) } /// Generates a random string @@ -568,219 +460,28 @@ pub fn bls_pubkey_from_hex_unchecked(hex: &str) -> BlsPublicKey { #[cfg(test)] mod test { - use alloy::primitives::keccak256; - - use super::{ - create_admin_jwt, create_jwt, decode_admin_jwt, decode_jwt, random_jwt_secret, - validate_admin_jwt, validate_jwt, - }; - use crate::{ - constants::SIGNER_JWT_EXPIRATION, - types::{Jwt, JwtAdminClaims, ModuleId}, - }; - - #[test] - fn test_jwt_validation_no_payload_hash() { - // Check valid JWT - let jwt = - create_jwt(&ModuleId("DA_COMMIT".to_string()), "secret", "/test/route", None).unwrap(); - let claims = decode_jwt(jwt.clone()).unwrap(); - let module_id = claims.module; - let payload_hash = claims.payload_hash; - assert_eq!(module_id, ModuleId("DA_COMMIT".to_string())); - assert!(payload_hash.is_none()); - let response = validate_jwt(jwt, "secret", "/test/route", None); - assert!(response.is_ok()); - - // Check expired JWT - let expired_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTgyOTkxNzIsIm1vZHVsZSI6IkRBX0NPTU1JVCIsInJvdXRlIjoiL3Rlc3Qvcm91dGUiLCJwYXlsb2FkX2hhc2giOm51bGx9._OBsNC67KLkk6f6ZQ2_CDbhYUJ2OtZ9egKAmi1L-ymA".to_string()); - let response = validate_jwt(expired_jwt, "secret", "/test/route", None); - assert!(response.is_err()); - assert_eq!(response.unwrap_err().to_string(), "ExpiredSignature"); - - // Check invalid signature JWT - let invalid_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTgyOTkxMzQsIm1vZHVsZSI6IkRBX0NPTU1JVCIsInJvdXRlIjoiL3Rlc3Qvcm91dGUiLCJwYXlsb2FkX2hhc2giOm51bGx9.58QXayg2XeX5lXhIPw-a8kl04DWBEj5wBsqsedTeClo".to_string()); - let response = validate_jwt(invalid_jwt, "secret", "/test/route", None); - assert!(response.is_err()); - assert_eq!(response.unwrap_err().to_string(), "InvalidSignature"); - } + use super::{create_jwt, decode_jwt, validate_jwt}; + use crate::types::{Jwt, ModuleId}; #[test] - fn test_jwt_validation_with_payload() { - // Pretend payload - let payload = serde_json::json!({ - "data": "test" - }); - let payload_bytes = serde_json::to_vec(&payload).unwrap(); - + fn test_jwt_validation() { // Check valid JWT - let jwt = create_jwt( - &ModuleId("DA_COMMIT".to_string()), - "secret", - "/test/route", - Some(&payload_bytes), - ) - .unwrap(); - let claims = decode_jwt(jwt.clone()).unwrap(); - let module_id = claims.module; - let payload_hash = claims.payload_hash; + let jwt = create_jwt(&ModuleId("DA_COMMIT".to_string()), "secret").unwrap(); + let module_id = decode_jwt(jwt.clone()).unwrap(); assert_eq!(module_id, ModuleId("DA_COMMIT".to_string())); - assert_eq!(payload_hash, Some(keccak256(&payload_bytes))); - let response = validate_jwt(jwt, "secret", "/test/route", Some(&payload_bytes)); + let response = validate_jwt(jwt, "secret"); assert!(response.is_ok()); // Check expired JWT - let expired_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTgyOTgzNDQsIm1vZHVsZSI6IkRBX0NPTU1JVCIsInJvdXRlIjoiL3Rlc3Qvcm91dGUiLCJwYXlsb2FkX2hhc2giOiIweGFmODk2MjY0MzUzNTFmYzIwMDBkYmEwM2JiNTlhYjcyZWE0ODJiOWEwMDBmZWQzNmNkMjBlMDU0YjE2NjZmZjEifQ.PYrSxLXadKBgYZlmLam8RBSL32I1T_zAxlZpG6xnnII".to_string()); - let response = validate_jwt(expired_jwt, "secret", "/test/route", Some(&payload_bytes)); + let expired_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDI5OTU5NDYsIm1vZHVsZSI6IkRBX0NPTU1JVCJ9.iiq4Z2ed2hk3c3c-cn2QOQJWE5XUOc5BoaIPT-I8q-s".to_string()); + let response = validate_jwt(expired_jwt, "secret"); assert!(response.is_err()); assert_eq!(response.unwrap_err().to_string(), "ExpiredSignature"); // Check invalid signature JWT - let invalid_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NTgyOTkwMDAsIm1vZHVsZSI6IkRBX0NPTU1JVCIsInJvdXRlIjoiL3Rlc3Qvcm91dGUiLCJwYXlsb2FkX2hhc2giOiIweGFmODk2MjY0MzUzNTFmYzIwMDBkYmEwM2JiNTlhYjcyZWE0ODJiOWEwMDBmZWQzNmNkMjBlMDU0YjE2NjZmZjEifQ.mnC-AexkLlR9l98SJbln3DmV6r9XyHYdbjcUVcWdi_8".to_string()); - let response = validate_jwt(invalid_jwt, "secret", "/test/route", Some(&payload_bytes)); + let invalid_jwt = Jwt::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3NDI5OTU5NDYsIm1vZHVsZSI6IkRBX0NPTU1JVCJ9.w9WYdDNzgDjYTvjBkk4GGzywGNBYPxnzU2uJWzPUT1s".to_string()); + let response = validate_jwt(invalid_jwt, "secret"); assert!(response.is_err()); assert_eq!(response.unwrap_err().to_string(), "InvalidSignature"); } - - // ── validate_jwt: route and secret errors ──────────────────────────────── - - #[test] - fn test_validate_jwt_wrong_route() { - let jwt = create_jwt(&ModuleId("MOD".into()), "secret", "/correct/route", None).unwrap(); - let err = validate_jwt(jwt, "secret", "/wrong/route", None).unwrap_err(); - assert!(err.to_string().contains("Token route does not match")); - } - - #[test] - fn test_validate_jwt_wrong_secret() { - let jwt = create_jwt(&ModuleId("MOD".into()), "correct_secret", "/route", None).unwrap(); - let err = validate_jwt(jwt, "wrong_secret", "/route", None).unwrap_err(); - assert_eq!(err.to_string(), "InvalidSignature"); - } - - // ── validate_jwt: payload hash mismatch branches ───────────────────────── - - #[test] - fn test_validate_jwt_payload_hash_mismatch() { - let payload_a = b"payload_a"; - let payload_b = b"payload_b"; - let jwt = create_jwt(&ModuleId("MOD".into()), "secret", "/route", Some(payload_a)).unwrap(); - let err = validate_jwt(jwt, "secret", "/route", Some(payload_b)).unwrap_err(); - assert!(err.to_string().contains("Payload hash does not match")); - } - - #[test] - fn test_validate_jwt_hash_present_but_no_payload_provided() { - let payload = b"some payload"; - let jwt = create_jwt(&ModuleId("MOD".into()), "secret", "/route", Some(payload)).unwrap(); - let err = validate_jwt(jwt, "secret", "/route", None).unwrap_err(); - assert!( - err.to_string().contains("JWT contains a payload hash but no payload was provided") - ); - } - - #[test] - fn test_validate_jwt_no_hash_but_payload_provided() { - let jwt = create_jwt(&ModuleId("MOD".into()), "secret", "/route", None).unwrap(); - let err = validate_jwt(jwt, "secret", "/route", Some(b"unexpected")).unwrap_err(); - assert!(err.to_string().contains("JWT does not contain a payload hash")); - } - - // ── admin JWT roundtrip ────────────────────────────────────────────────── - - #[test] - fn test_admin_jwt_roundtrip_no_payload() { - let jwt = create_admin_jwt("admin_secret".into(), "/admin/route", None).unwrap(); - let claims = decode_admin_jwt(jwt.clone()).unwrap(); - assert!(claims.admin); - assert_eq!(claims.route, "/admin/route"); - assert!(claims.payload_hash.is_none()); - validate_admin_jwt(jwt, "admin_secret", "/admin/route", None).unwrap(); - } - - #[test] - fn test_admin_jwt_roundtrip_with_payload() { - let payload = b"admin payload"; - let jwt = create_admin_jwt("admin_secret".into(), "/admin/route", Some(payload)).unwrap(); - let claims = decode_admin_jwt(jwt.clone()).unwrap(); - assert!(claims.admin); - assert_eq!(claims.payload_hash, Some(keccak256(payload))); - validate_admin_jwt(jwt, "admin_secret", "/admin/route", Some(payload)).unwrap(); - } - - // ── validate_admin_jwt: route, secret, admin flag errors ───────────────── - - #[test] - fn test_validate_admin_jwt_wrong_route() { - let jwt = create_admin_jwt("admin_secret".into(), "/correct/route", None).unwrap(); - let err = validate_admin_jwt(jwt, "admin_secret", "/wrong/route", None).unwrap_err(); - assert!(err.to_string().contains("Token route does not match")); - } - - #[test] - fn test_validate_admin_jwt_wrong_secret() { - let jwt = create_admin_jwt("correct_secret".into(), "/route", None).unwrap(); - let err = validate_admin_jwt(jwt, "wrong_secret", "/route", None).unwrap_err(); - assert_eq!(err.to_string(), "InvalidSignature"); - } - - #[test] - fn test_validate_admin_jwt_admin_false() { - // Craft a JWT whose claims have admin: false — something create_admin_jwt - // never produces — to exercise the explicit admin flag guard. - let claims = JwtAdminClaims { - admin: false, - route: "/route".into(), - exp: jsonwebtoken::get_current_timestamp() + SIGNER_JWT_EXPIRATION, - payload_hash: None, - }; - let token = jsonwebtoken::encode( - &jsonwebtoken::Header::default(), - &claims, - &jsonwebtoken::EncodingKey::from_secret(b"secret"), - ) - .unwrap(); - let jwt = Jwt::from(token); - let err = validate_admin_jwt(jwt, "secret", "/route", None).unwrap_err(); - assert!(err.to_string().contains("Token is not admin")); - } - - // ── validate_admin_jwt: payload hash mismatch branches ─────────────────── - - #[test] - fn test_validate_admin_jwt_payload_hash_mismatch() { - let payload_a = b"admin_payload_a"; - let payload_b = b"admin_payload_b"; - let jwt = create_admin_jwt("secret".into(), "/route", Some(payload_a)).unwrap(); - let err = validate_admin_jwt(jwt, "secret", "/route", Some(payload_b)).unwrap_err(); - assert!(err.to_string().contains("Payload hash does not match")); - } - - #[test] - fn test_validate_admin_jwt_hash_present_but_no_payload_provided() { - let payload = b"admin payload"; - let jwt = create_admin_jwt("secret".into(), "/route", Some(payload)).unwrap(); - let err = validate_admin_jwt(jwt, "secret", "/route", None).unwrap_err(); - assert!( - err.to_string().contains("JWT contains a payload hash but no payload was provided") - ); - } - - #[test] - fn test_validate_admin_jwt_no_hash_but_payload_provided() { - let jwt = create_admin_jwt("secret".into(), "/route", None).unwrap(); - let err = validate_admin_jwt(jwt, "secret", "/route", Some(b"unexpected")).unwrap_err(); - assert!(err.to_string().contains("JWT does not contain a payload hash")); - } - - // ── random_jwt_secret ──────────────────────────────────────────────────── - - #[test] - fn test_random_jwt_secret() { - let secret = random_jwt_secret(); - assert_eq!(secret.len(), 32); - assert!(secret.chars().all(|c| c.is_ascii_alphanumeric())); - // Two calls should produce distinct values with overwhelming probability. - assert_ne!(secret, random_jwt_secret()); - } } diff --git a/crates/pbs/Cargo.toml b/crates/pbs/Cargo.toml index a9124c06..e8cb0b31 100644 --- a/crates/pbs/Cargo.toml +++ b/crates/pbs/Cargo.toml @@ -15,7 +15,6 @@ cb-metrics.workspace = true eyre.workspace = true futures.workspace = true lazy_static.workspace = true -notify.workspace = true parking_lot.workspace = true prometheus.workspace = true reqwest.workspace = true diff --git a/crates/pbs/src/mev_boost/get_header.rs b/crates/pbs/src/mev_boost/get_header.rs index d25df53c..1914de8b 100644 --- a/crates/pbs/src/mev_boost/get_header.rs +++ b/crates/pbs/src/mev_boost/get_header.rs @@ -4,7 +4,7 @@ use std::{ }; use alloy::{ - primitives::{B256, U256, aliases::B32, utils::format_ether}, + primitives::{B256, U256, utils::format_ether}, providers::Provider, rpc::types::Block, }; @@ -27,7 +27,7 @@ use futures::future::join_all; use parking_lot::RwLock; use reqwest::{StatusCode, header::USER_AGENT}; use tokio::time::sleep; -use tracing::{Instrument, debug, error, info, warn}; +use tracing::{Instrument, debug, error, warn}; use tree_hash::TreeHash; use url::Url; @@ -131,7 +131,7 @@ pub async fn get_header( .unwrap_or_default(); RELAY_HEADER_VALUE.with_label_values(&[relay_id]).set(value_gwei); - relay_bids.push((relay_id, res)) + relay_bids.push(res) } Ok(_) => {} Err(err) if err.is_timeout() => error!(err = "Timed Out", relay_id), @@ -139,18 +139,9 @@ pub async fn get_header( } } - let max_bid = relay_bids.into_iter().max_by_key(|(_, bid)| *bid.value()); + let max_bid = relay_bids.into_iter().max_by_key(|bid| *bid.value()); - if let Some((winning_relay_id, ref bid)) = max_bid { - info!( - relay_id = winning_relay_id, - value_eth = format_ether(*bid.value()), - block_hash = %bid.block_hash(), - "auction winner" - ); - } - - Ok(max_bid.map(|(_, bid)| bid)) + Ok(max_bid) } /// Fetch the parent block from the RPC URL for extra validation of the header. @@ -382,7 +373,7 @@ async fn send_one_get_header( } }; - info!( + debug!( relay_id = relay.id.as_ref(), header_size_bytes, latency = ?request_latency, @@ -395,8 +386,7 @@ async fn send_one_get_header( match &get_header_response.data.message.header() { ExecutionPayloadHeaderRef::Bellatrix(_) | ExecutionPayloadHeaderRef::Capella(_) | - ExecutionPayloadHeaderRef::Deneb(_) | - ExecutionPayloadHeaderRef::Gloas(_) => { + ExecutionPayloadHeaderRef::Deneb(_) => { return Err(PbsError::Validation(ValidationError::UnsupportedFork)) } ExecutionPayloadHeaderRef::Electra(res) => { @@ -534,8 +524,7 @@ fn validate_signature( expected_relay_pubkey, &message, signature, - None, - &B32::from(APPLICATION_BUILDER_DOMAIN), + APPLICATION_BUILDER_DOMAIN, ) { return Err(ValidationError::Sigverify); } diff --git a/crates/pbs/src/mev_boost/reload.rs b/crates/pbs/src/mev_boost/reload.rs index adfab89f..0a0555b6 100644 --- a/crates/pbs/src/mev_boost/reload.rs +++ b/crates/pbs/src/mev_boost/reload.rs @@ -6,8 +6,8 @@ use crate::{BuilderApiState, PbsState}; /// Reload the PBS state with the latest configuration in the config file /// Returns 200 if successful or 500 if failed pub async fn reload(state: PbsState) -> eyre::Result> { - let (pbs_config, config_path) = load_pbs_config(None).await?; - let new_state = PbsState::new(pbs_config, config_path).with_data(state.data); + let pbs_config = load_pbs_config().await?; + let new_state = PbsState::new(pbs_config).with_data(state.data); if state.config.pbs_config.host != new_state.config.pbs_config.host { warn!( diff --git a/crates/pbs/src/service.rs b/crates/pbs/src/service.rs index 8be422ca..6659ae85 100644 --- a/crates/pbs/src/service.rs +++ b/crates/pbs/src/service.rs @@ -5,14 +5,13 @@ use std::{ }; use cb_common::{ - config::{MuxKeysLoader, PbsModuleConfig, load_pbs_config}, + config::{MuxKeysLoader, PbsModuleConfig}, constants::{COMMIT_BOOST_COMMIT, COMMIT_BOOST_VERSION}, pbs::{BUILDER_V1_API_PATH, GET_STATUS_PATH}, types::Chain, }; use cb_metrics::provider::MetricsProvider; use eyre::{Context, Result, bail}; -use notify::{Error, Event, RecommendedWatcher, RecursiveMode, Watcher}; use parking_lot::RwLock; use prometheus::core::Collector; use tokio::net::TcpListener; @@ -41,7 +40,6 @@ impl PbsService { }) }); - let config_path = state.config_path.clone(); let state: Arc>> = RwLock::new(state).into(); let app = create_app_router::(state.clone()); let listener = TcpListener::bind(addr).await?; @@ -61,47 +59,6 @@ impl PbsService { bail!("PBS server failed to start. Are the relays properly configured?"); } - // Set up the filesystem watcher for the config file - let mut watcher: RecommendedWatcher; - if config_path.to_str() != Some("") { - let state_for_watcher = state.clone(); - let config_path_for_watcher = config_path.clone(); - watcher = RecommendedWatcher::new( - move |result: Result| { - match result { - Err(e) => { - warn!(%e, "error watching PBS config file for changes"); - return; - } - Ok(event) => { - if !event.kind.is_modify() { - return; - } - } - } - - // Reload the configuration when the file is modified - info!("detected change in PBS config file, reloading configuration"); - let result = futures::executor::block_on(load_pbs_config(Some( - config_path_for_watcher.to_path_buf(), - ))); - match result { - Ok((new_config, _)) => { - let mut state = state_for_watcher.write(); - state.config = Arc::new(new_config); - info!("configuration reloaded from file after update"); - } - Err(err) => { - warn!(%err, "failed to reload configuration from file after update"); - } - } - }, - notify::Config::default(), - )?; - watcher.watch(config_path.as_path(), RecursiveMode::Recursive)?; - info!("watching PBS config file for changes: {:?}", config_path); - } - // Run the registry refresher task if is_refreshing_required { let mut interval = tokio::time::interval(Duration::from_secs(registry_refresh_time)); @@ -160,8 +117,7 @@ impl PbsService { .load( &runtime_config.id, config.chain, - default_pbs.ssv_node_api_url.clone(), - default_pbs.ssv_public_api_url.clone(), + default_pbs.ssv_api_url.clone(), default_pbs.rpc_url.clone(), http_timeout, ) diff --git a/crates/pbs/src/state.rs b/crates/pbs/src/state.rs index bd683e5f..dd0e118e 100644 --- a/crates/pbs/src/state.rs +++ b/crates/pbs/src/state.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; use cb_common::{ config::{PbsConfig, PbsModuleConfig}, @@ -19,19 +19,17 @@ pub type PbsStateGuard = Arc>>; pub struct PbsState { /// Config data for the Pbs service pub config: Arc, - /// Path of the config file, for watching changes - pub config_path: Arc, /// Opaque extra data for library use pub data: S, } impl PbsState<()> { - pub fn new(config: PbsModuleConfig, config_path: PathBuf) -> Self { - Self { config: Arc::new(config), config_path: Arc::new(config_path), data: () } + pub fn new(config: PbsModuleConfig) -> Self { + Self { config: Arc::new(config), data: () } } pub fn with_data(self, data: S) -> PbsState { - PbsState { data, config: self.config, config_path: self.config_path } + PbsState { data, config: self.config } } } diff --git a/crates/signer/Cargo.toml b/crates/signer/Cargo.toml index 7c6e63fa..569797ac 100644 --- a/crates/signer/Cargo.toml +++ b/crates/signer/Cargo.toml @@ -9,7 +9,6 @@ version.workspace = true alloy.workspace = true axum.workspace = true axum-extra.workspace = true -axum-server.workspace = true bimap.workspace = true blsful.workspace = true cb-common.workspace = true @@ -23,7 +22,6 @@ parking_lot.workspace = true prometheus.workspace = true prost.workspace = true rand.workspace = true -rustls.workspace = true thiserror.workspace = true tokio.workspace = true tonic.workspace = true diff --git a/crates/signer/src/constants.rs b/crates/signer/src/constants.rs index e5884d27..268cd2e2 100644 --- a/crates/signer/src/constants.rs +++ b/crates/signer/src/constants.rs @@ -1,5 +1,3 @@ pub const GET_PUBKEYS_ENDPOINT_TAG: &str = "get_pubkeys"; pub const GENERATE_PROXY_KEY_ENDPOINT_TAG: &str = "generate_proxy_key"; -pub const REQUEST_SIGNATURE_BLS_ENDPOINT_TAG: &str = "request_signature_bls"; -pub const REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG: &str = "request_signature_proxy_bls"; -pub const REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG: &str = "request_signature_proxy_ecdsa"; +pub const REQUEST_SIGNATURE_ENDPOINT_TAG: &str = "request_signature"; diff --git a/crates/signer/src/error.rs b/crates/signer/src/error.rs index 64a3e5b8..a2a113f3 100644 --- a/crates/signer/src/error.rs +++ b/crates/signer/src/error.rs @@ -25,17 +25,11 @@ pub enum SignerModuleError { #[error("Dirk signer does not support this operation")] DirkNotSupported, - #[error("module id not found")] - ModuleIdNotFound, - #[error("internal error: {0}")] Internal(String), #[error("rate limited for {0} more seconds")] RateLimited(f64), - - #[error("request error: {0}")] - RequestError(String), } impl IntoResponse for SignerModuleError { @@ -54,13 +48,9 @@ impl IntoResponse for SignerModuleError { (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string()) } SignerModuleError::SignerError(err) => (StatusCode::BAD_REQUEST, err.to_string()), - SignerModuleError::ModuleIdNotFound => (StatusCode::NOT_FOUND, self.to_string()), SignerModuleError::RateLimited(duration) => { (StatusCode::TOO_MANY_REQUESTS, format!("rate limited for {duration:?}")) } - SignerModuleError::RequestError(err) => { - (StatusCode::BAD_REQUEST, format!("bad request: {err}")) - } } .into_response() } diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index b4b9ecc4..4b5e1451 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -4,4 +4,3 @@ pub mod manager; mod metrics; mod proto; pub mod service; -mod utils; diff --git a/crates/signer/src/manager/dirk.rs b/crates/signer/src/manager/dirk.rs index 45dcc733..fe438ef0 100644 --- a/crates/signer/src/manager/dirk.rs +++ b/crates/signer/src/manager/dirk.rs @@ -1,9 +1,6 @@ use std::{collections::HashMap, io::Write, path::PathBuf}; -use alloy::{ - hex, - primitives::{B256, aliases::B32}, -}; +use alloy::{hex, primitives::B256}; use blsful::inner_types::{Field, G2Affine, G2Projective, Group, Scalar}; use cb_common::{ commit::request::{ConsensusProxyMap, ProxyDelegation, SignedProxyDelegation}, @@ -11,7 +8,7 @@ use cb_common::{ constants::COMMIT_BOOST_DOMAIN, signature::compute_domain, signer::ProxyStore, - types::{self, BlsPublicKey, BlsSignature, Chain, ModuleId, SignatureRequestInfo}, + types::{BlsPublicKey, BlsSignature, Chain, ModuleId}, }; use eyre::{OptionExt, bail}; use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; @@ -154,11 +151,6 @@ impl DirkManager { }) } - /// Get the chain config for the manager - pub fn get_chain(&self) -> Chain { - self.chain - } - /// Set the proxy store to use for storing proxy delegations pub fn with_proxy_store(self, store: ProxyStore) -> eyre::Result { if let ProxyStore::ERC2335 { .. } = store { @@ -207,16 +199,14 @@ impl DirkManager { pub async fn request_consensus_signature( &self, pubkey: &BlsPublicKey, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { match self.consensus_accounts.get(pubkey) { Some(Account::Simple(account)) => { - self.request_simple_signature(account, object_root, signature_request_info).await + self.request_simple_signature(account, object_root).await } Some(Account::Distributed(account)) => { - self.request_distributed_signature(account, object_root, signature_request_info) - .await + self.request_distributed_signature(account, object_root).await } None => Err(SignerModuleError::UnknownConsensusSigner(pubkey.serialize().to_vec())), } @@ -226,16 +216,14 @@ impl DirkManager { pub async fn request_proxy_signature( &self, pubkey: &BlsPublicKey, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { match self.proxy_accounts.get(pubkey) { Some(ProxyAccount { inner: Account::Simple(account), .. }) => { - self.request_simple_signature(account, object_root, signature_request_info).await + self.request_simple_signature(account, object_root).await } Some(ProxyAccount { inner: Account::Distributed(account), .. }) => { - self.request_distributed_signature(account, object_root, signature_request_info) - .await + self.request_distributed_signature(account, object_root).await } None => Err(SignerModuleError::UnknownProxySigner(pubkey.serialize().to_vec())), } @@ -245,28 +233,13 @@ impl DirkManager { async fn request_simple_signature( &self, account: &SimpleAccount, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { - let domain = compute_domain(self.chain, &B32::from(COMMIT_BOOST_DOMAIN)); - - let data = match signature_request_info { - Some(SignatureRequestInfo { module_signing_id, nonce }) => { - types::PropCommitSigningInfo { - data: *object_root, - module_signing_id: *module_signing_id, - nonce: *nonce, - chain_id: self.chain.id(), - } - .tree_hash_root() - .to_vec() - } - None => object_root.to_vec(), - }; + let domain = compute_domain(self.chain, COMMIT_BOOST_DOMAIN); let response = SignerClient::new(account.connection.clone()) .sign(SignRequest { - data, + data: object_root.to_vec(), domain: domain.to_vec(), id: Some(sign_request::Id::PublicKey(account.public_key.serialize().to_vec())), }) @@ -290,34 +263,17 @@ impl DirkManager { async fn request_distributed_signature( &self, account: &DistributedAccount, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { let mut partials = Vec::with_capacity(account.participants.len()); let mut requests = Vec::with_capacity(account.participants.len()); - let data = match signature_request_info { - Some(SignatureRequestInfo { module_signing_id, nonce }) => { - types::PropCommitSigningInfo { - data: *object_root, - module_signing_id: *module_signing_id, - nonce: *nonce, - chain_id: self.chain.id(), - } - .tree_hash_root() - .to_vec() - } - None => object_root.to_vec(), - }; - for (id, channel) in account.participants.iter() { - let data_copy = data.clone(); let request = async move { SignerClient::new(channel.clone()) .sign(SignRequest { - data: data_copy, - domain: compute_domain(self.chain, &B32::from(COMMIT_BOOST_DOMAIN)) - .to_vec(), + data: object_root.to_vec(), + domain: compute_domain(self.chain, COMMIT_BOOST_DOMAIN).to_vec(), id: Some(sign_request::Id::Account(account.name.clone())), }) .map(|res| (res, *id)) @@ -372,9 +328,9 @@ impl DirkManager { pub async fn generate_proxy_key( &mut self, module: &ModuleId, - consensus: &BlsPublicKey, + consensus: BlsPublicKey, ) -> Result, SignerModuleError> { - let proxy_account = match self.consensus_accounts.get(consensus) { + let proxy_account = match self.consensus_accounts.get(&consensus) { Some(Account::Simple(account)) => { self.generate_simple_proxy_account(account, module).await? } @@ -393,7 +349,7 @@ impl DirkManager { proxy: proxy_account.inner.public_key().clone(), }; let delegation_signature = - self.request_consensus_signature(consensus, &message.tree_hash_root(), None).await?; + self.request_consensus_signature(&consensus, message.tree_hash_root()).await?; let delegation = SignedProxyDelegation { message, signature: delegation_signature }; diff --git a/crates/signer/src/manager/local.rs b/crates/signer/src/manager/local.rs index fc2eabae..17832fdf 100644 --- a/crates/signer/src/manager/local.rs +++ b/crates/signer/src/manager/local.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use alloy::primitives::{Address, B256}; use cb_common::{ commit::request::{ - ConsensusProxyMap, ProxyDelegationBls, ProxyDelegationEcdsa, SignedProxyDelegationBls, - SignedProxyDelegationEcdsa, + ConsensusProxyMap, ProxyDelegationBls, ProxyDelegationEcdsa, ProxyId, + SignedProxyDelegationBls, SignedProxyDelegationEcdsa, }, signer::{ BlsProxySigner, BlsSigner, ConsensusSigner, EcdsaProxySigner, EcdsaSignature, EcdsaSigner, ProxySigners, ProxyStore, }, - types::{BlsPublicKey, BlsSignature, Chain, ModuleId, SignatureRequestInfo}, + types::{BlsPublicKey, BlsSignature, Chain, ModuleId}, }; use tree_hash::TreeHash; @@ -50,11 +50,6 @@ impl LocalSigningManager { Ok(manager) } - /// Get the chain config for the manager - pub fn get_chain(&self) -> Chain { - self.chain - } - pub fn add_consensus_signer(&mut self, signer: ConsensusSigner) { self.consensus_signers.insert(signer.pubkey(), signer); } @@ -94,13 +89,13 @@ impl LocalSigningManager { pub async fn create_proxy_bls( &mut self, module_id: ModuleId, - delegator: &BlsPublicKey, + delegator: BlsPublicKey, ) -> Result { let signer = BlsSigner::new_random(); let proxy_pubkey = signer.pubkey(); let message = ProxyDelegationBls { delegator: delegator.clone(), proxy: proxy_pubkey }; - let signature = self.sign_consensus(delegator, &message.tree_hash_root(), None).await?; + let signature = self.sign_consensus(&delegator, message.tree_hash_root()).await?; let delegation = SignedProxyDelegationBls { signature, message }; let proxy_signer = BlsProxySigner { signer, delegation: delegation.clone() }; @@ -113,13 +108,13 @@ impl LocalSigningManager { pub async fn create_proxy_ecdsa( &mut self, module_id: ModuleId, - delegator: &BlsPublicKey, + delegator: BlsPublicKey, ) -> Result { let signer = EcdsaSigner::new_random(); let proxy_address = signer.address(); let message = ProxyDelegationEcdsa { delegator: delegator.clone(), proxy: proxy_address }; - let signature = self.sign_consensus(delegator, &message.tree_hash_root(), None).await?; + let signature = self.sign_consensus(&delegator, message.tree_hash_root()).await?; let delegation = SignedProxyDelegationEcdsa { signature, message }; let proxy_signer = EcdsaProxySigner { signer, delegation: delegation.clone() }; @@ -134,14 +129,13 @@ impl LocalSigningManager { pub async fn sign_consensus( &self, pubkey: &BlsPublicKey, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { let signer = self .consensus_signers .get(pubkey) - .ok_or(SignerModuleError::UnknownConsensusSigner(pubkey.serialize().to_vec()))?; - let signature = signer.sign(self.chain, object_root, signature_request_info).await; + .ok_or(SignerModuleError::UnknownConsensusSigner(pubkey.to_bytes()))?; + let signature = signer.sign(self.chain, object_root).await; Ok(signature) } @@ -149,30 +143,28 @@ impl LocalSigningManager { pub async fn sign_proxy_bls( &self, pubkey: &BlsPublicKey, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { let bls_proxy = self .proxy_signers .bls_signers .get(pubkey) .ok_or(SignerModuleError::UnknownProxySigner(pubkey.serialize().to_vec()))?; - let signature = bls_proxy.sign(self.chain, object_root, signature_request_info).await; + let signature = bls_proxy.sign(self.chain, object_root).await; Ok(signature) } pub async fn sign_proxy_ecdsa( &self, address: &Address, - object_root: &B256, - signature_request_info: Option<&SignatureRequestInfo>, + object_root: B256, ) -> Result { let ecdsa_proxy = self .proxy_signers .ecdsa_signers .get(address) .ok_or(SignerModuleError::UnknownProxySigner(address.to_vec()))?; - let signature = ecdsa_proxy.sign(self.chain, object_root, signature_request_info).await?; + let signature = ecdsa_proxy.sign(self.chain, object_root).await?; Ok(signature) } @@ -273,6 +265,7 @@ impl LocalSigningManager { #[cfg(test)] mod tests { use alloy::primitives::B256; + use cb_common::signature::compute_signing_root; use lazy_static::lazy_static; use super::*; @@ -294,54 +287,10 @@ mod tests { (signing_manager, consensus_pk) } - mod test_bls { - use alloy::primitives::aliases::B32; - use cb_common::{ - constants::COMMIT_BOOST_DOMAIN, signature::compute_domain, - signer::verify_bls_signature, types, - }; - - use super::*; - - #[tokio::test] - async fn test_key_signs_message() { - let (signing_manager, consensus_pk) = init_signing_manager(); - - let data_root = B256::random(); - let module_signing_id = B256::random(); - let nonce = 43; - - let sig = signing_manager - .sign_consensus( - &consensus_pk, - &data_root, - Some(&SignatureRequestInfo { module_signing_id, nonce }), - ) - .await - .unwrap(); - - // Verify signature - let signing_domain = compute_domain(CHAIN, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: data_root.tree_hash_root(), - module_signing_id, - nonce, - chain_id: CHAIN.id(), - } - .tree_hash_root(); - let signing_root = types::SigningData { object_root, signing_domain }.tree_hash_root(); - - let validation_result = verify_bls_signature(&consensus_pk, signing_root, &sig); - - assert!(validation_result, "Keypair must produce valid signatures of messages.") - } - } - mod test_proxy_bls { - use alloy::primitives::aliases::B32; use cb_common::{ constants::COMMIT_BOOST_DOMAIN, signature::compute_domain, - signer::verify_bls_signature, types, utils::TestRandomSeed, + signer::verify_bls_signature, utils::TestRandomSeed, }; use super::*; @@ -351,7 +300,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = - signing_manager.create_proxy_bls(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_bls(MODULE_ID.clone(), consensus_pk).await.unwrap(); let validation_result = signed_delegation.validate(CHAIN); @@ -372,7 +321,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let mut signed_delegation = - signing_manager.create_proxy_bls(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_bls(MODULE_ID.clone(), consensus_pk).await.unwrap(); signed_delegation.signature = BlsSignature::test_random(); @@ -386,32 +335,16 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = - signing_manager.create_proxy_bls(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_bls(MODULE_ID.clone(), consensus_pk).await.unwrap(); let proxy_pk = signed_delegation.message.proxy; let data_root = B256::random(); - let module_signing_id = B256::random(); - let nonce = 44; - - let sig = signing_manager - .sign_proxy_bls( - &proxy_pk, - &data_root, - Some(&SignatureRequestInfo { module_signing_id, nonce }), - ) - .await - .unwrap(); + + let sig = signing_manager.sign_proxy_bls(&proxy_pk, data_root).await.unwrap(); // Verify signature - let signing_domain = compute_domain(CHAIN, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: data_root.tree_hash_root(), - module_signing_id, - nonce, - chain_id: CHAIN.id(), - } - .tree_hash_root(); - let signing_root = types::SigningData { object_root, signing_domain }.tree_hash_root(); + let domain = compute_domain(CHAIN, COMMIT_BOOST_DOMAIN); + let signing_root = compute_signing_root(data_root.tree_hash_root(), domain); let validation_result = verify_bls_signature(&proxy_pk, signing_root, &sig); @@ -420,10 +353,9 @@ mod tests { } mod test_proxy_ecdsa { - use alloy::primitives::aliases::B32; use cb_common::{ constants::COMMIT_BOOST_DOMAIN, signature::compute_domain, - signer::verify_ecdsa_signature, types, utils::TestRandomSeed, + signer::verify_ecdsa_signature, utils::TestRandomSeed, }; use super::*; @@ -433,7 +365,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = - signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk).await.unwrap(); let validation_result = signed_delegation.validate(CHAIN); @@ -454,7 +386,7 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let mut signed_delegation = - signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk).await.unwrap(); signed_delegation.signature = BlsSignature::test_random(); @@ -468,32 +400,16 @@ mod tests { let (mut signing_manager, consensus_pk) = init_signing_manager(); let signed_delegation = - signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), &consensus_pk).await.unwrap(); + signing_manager.create_proxy_ecdsa(MODULE_ID.clone(), consensus_pk).await.unwrap(); let proxy_pk = signed_delegation.message.proxy; let data_root = B256::random(); - let module_signing_id = B256::random(); - let nonce = 45; - - let sig = signing_manager - .sign_proxy_ecdsa( - &proxy_pk, - &data_root, - Some(&SignatureRequestInfo { module_signing_id, nonce }), - ) - .await - .unwrap(); + + let sig = signing_manager.sign_proxy_ecdsa(&proxy_pk, data_root).await.unwrap(); // Verify signature - let signing_domain = compute_domain(CHAIN, &B32::from(COMMIT_BOOST_DOMAIN)); - let object_root = types::PropCommitSigningInfo { - data: data_root.tree_hash_root(), - module_signing_id, - nonce, - chain_id: CHAIN.id(), - } - .tree_hash_root(); - let signing_root = types::SigningData { object_root, signing_domain }.tree_hash_root(); + let domain = compute_domain(CHAIN, COMMIT_BOOST_DOMAIN); + let signing_root = compute_signing_root(data_root.tree_hash_root(), domain); let validation_result = verify_ecdsa_signature(&proxy_pk, &signing_root, &sig); diff --git a/crates/signer/src/metrics.rs b/crates/signer/src/metrics.rs index 4110ec72..beaeefce 100644 --- a/crates/signer/src/metrics.rs +++ b/crates/signer/src/metrics.rs @@ -2,15 +2,13 @@ use axum::http::Uri; use cb_common::commit::constants::{ - GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_BLS_PATH, - REQUEST_SIGNATURE_PROXY_BLS_PATH, REQUEST_SIGNATURE_PROXY_ECDSA_PATH, + GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH, }; use lazy_static::lazy_static; use prometheus::{IntCounterVec, Registry, register_int_counter_vec_with_registry}; use crate::constants::{ - GENERATE_PROXY_KEY_ENDPOINT_TAG, GET_PUBKEYS_ENDPOINT_TAG, REQUEST_SIGNATURE_BLS_ENDPOINT_TAG, - REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG, REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG, + GENERATE_PROXY_KEY_ENDPOINT_TAG, GET_PUBKEYS_ENDPOINT_TAG, REQUEST_SIGNATURE_ENDPOINT_TAG, }; lazy_static! { @@ -30,9 +28,7 @@ pub fn uri_to_tag(uri: &Uri) -> &str { match uri.path() { GET_PUBKEYS_PATH => GET_PUBKEYS_ENDPOINT_TAG, GENERATE_PROXY_KEY_PATH => GENERATE_PROXY_KEY_ENDPOINT_TAG, - REQUEST_SIGNATURE_BLS_PATH => REQUEST_SIGNATURE_BLS_ENDPOINT_TAG, - REQUEST_SIGNATURE_PROXY_BLS_PATH => REQUEST_SIGNATURE_PROXY_BLS_ENDPOINT_TAG, - REQUEST_SIGNATURE_PROXY_ECDSA_PATH => REQUEST_SIGNATURE_PROXY_ECDSA_ENDPOINT_TAG, + REQUEST_SIGNATURE_PATH => REQUEST_SIGNATURE_ENDPOINT_TAG, _ => "unknown endpoint", } } diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index b3a3045b..af96c51e 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -5,53 +5,45 @@ use std::{ time::{Duration, Instant}, }; -use alloy::primitives::{Address, B256, U256}; use axum::{ Extension, Json, - body::{Body, to_bytes}, extract::{ConnectInfo, Request, State}, - http::{HeaderMap, StatusCode}, + http::StatusCode, middleware::{self, Next}, response::{IntoResponse, Response}, routing::{get, post}, }; use axum_extra::TypedHeader; -use axum_server::tls_rustls::RustlsConfig; use cb_common::{ commit::{ constants::{ - GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, RELOAD_PATH, REQUEST_SIGNATURE_BLS_PATH, - REQUEST_SIGNATURE_PROXY_BLS_PATH, REQUEST_SIGNATURE_PROXY_ECDSA_PATH, - REVOKE_MODULE_PATH, STATUS_PATH, + GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, RELOAD_PATH, REQUEST_SIGNATURE_PATH, + STATUS_PATH, }, request::{ - EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, ReloadRequest, - RevokeModuleRequest, SignConsensusRequest, SignProxyRequest, + EncryptionScheme, GenerateProxyRequest, GetPubkeysResponse, SignConsensusRequest, + SignProxyRequest, SignRequest, }, - response::{BlsSignResponse, EcdsaSignResponse}, }, - config::{ModuleSigningConfig, ReverseProxyHeaderSetup, StartSignerConfig}, + config::StartSignerConfig, constants::{COMMIT_BOOST_COMMIT, COMMIT_BOOST_VERSION}, - types::{BlsPublicKey, Chain, Jwt, ModuleId, SignatureRequestInfo}, - utils::{decode_jwt, validate_admin_jwt, validate_jwt}, + types::{Chain, Jwt, ModuleId}, + utils::{decode_jwt, validate_jwt}, }; use cb_metrics::provider::MetricsProvider; use eyre::Context; use headers::{Authorization, authorization::Bearer}; use parking_lot::RwLock as ParkingRwLock; -use rustls::crypto::{CryptoProvider, aws_lc_rs}; -use tokio::sync::RwLock; +use tokio::{net::TcpListener, sync::RwLock}; use tracing::{debug, error, info, warn}; +use uuid::Uuid; use crate::{ error::SignerModuleError, manager::{SigningManager, dirk::DirkManager, local::LocalSigningManager}, metrics::{SIGNER_METRICS_REGISTRY, SIGNER_STATUS, uri_to_tag}, - utils::get_true_ip, }; -pub const REQUEST_MAX_BODY_LENGTH: usize = 1024 * 1024; // 1 MB - /// Implements the Signer API and provides a service for signing requests pub struct SigningService; @@ -69,12 +61,9 @@ struct SigningState { /// Manager handling different signing methods manager: Arc>, - /// Map of modules ids to JWT configurations. This also acts as registry of - /// all modules running - jwts: Arc>>, - - /// Secret for the admin JWT - admin_secret: Arc>, + /// Map of modules ids to JWT secrets. This also acts as registry of all + /// modules running + jwts: Arc>, /// Map of JWT failures per peer jwt_auth_failures: Arc>>, @@ -82,29 +71,23 @@ struct SigningState { // JWT auth failure settings jwt_auth_fail_limit: u32, jwt_auth_fail_timeout: Duration, - - /// Header to extract the trusted client IP from - reverse_proxy: ReverseProxyHeaderSetup, } impl SigningService { pub async fn run(config: StartSignerConfig) -> eyre::Result<()> { - if config.mod_signing_configs.is_empty() { + if config.jwts.is_empty() { warn!("Signing service was started but no module is registered. Exiting"); return Ok(()); } - let module_ids: Vec = - config.mod_signing_configs.keys().cloned().map(Into::into).collect(); + let module_ids: Vec = config.jwts.keys().cloned().map(Into::into).collect(); let state = SigningState { manager: Arc::new(RwLock::new(start_manager(config.clone()).await?)), - jwts: Arc::new(ParkingRwLock::new(config.mod_signing_configs)), - admin_secret: Arc::new(ParkingRwLock::new(config.admin_secret)), + jwts: config.jwts.into(), jwt_auth_failures: Arc::new(ParkingRwLock::new(HashMap::new())), jwt_auth_fail_limit: config.jwt_auth_fail_limit, jwt_auth_fail_timeout: Duration::from_secs(config.jwt_auth_fail_timeout_seconds as u64), - reverse_proxy: config.reverse_proxy, }; // Get the signer counts @@ -125,91 +108,25 @@ impl SigningService { loaded_proxies, jwt_auth_fail_limit =? state.jwt_auth_fail_limit, jwt_auth_fail_timeout =? state.jwt_auth_fail_timeout, - reverse_proxy =% state.reverse_proxy, "Starting signing service" ); SigningService::init_metrics(config.chain)?; - let signer_app = axum::Router::new() - .route(REQUEST_SIGNATURE_BLS_PATH, post(handle_request_signature_bls)) - .route(REQUEST_SIGNATURE_PROXY_BLS_PATH, post(handle_request_signature_proxy_bls)) - .route(REQUEST_SIGNATURE_PROXY_ECDSA_PATH, post(handle_request_signature_proxy_ecdsa)) + let app = axum::Router::new() + .route(REQUEST_SIGNATURE_PATH, post(handle_request_signature)) .route(GET_PUBKEYS_PATH, get(handle_get_pubkeys)) .route(GENERATE_PROXY_KEY_PATH, post(handle_generate_proxy)) .route_layer(middleware::from_fn_with_state(state.clone(), jwt_auth)) - .with_state(state.clone()) - .route_layer(middleware::from_fn(log_request)); - - let admin_app = axum::Router::new() .route(RELOAD_PATH, post(handle_reload)) - .route(REVOKE_MODULE_PATH, post(handle_revoke_module)) - .route_layer(middleware::from_fn_with_state(state.clone(), admin_auth)) .with_state(state.clone()) .route_layer(middleware::from_fn(log_request)) - .route(STATUS_PATH, get(handle_status)); - - // Run the JWT cleaning task - let jwt_cleaning_task = tokio::spawn(async move { - let mut interval = tokio::time::interval(state.jwt_auth_fail_timeout); - loop { - interval.tick().await; - let mut failures = state.jwt_auth_failures.write(); - let before = failures.len(); - failures - .retain(|_, info| info.last_failure.elapsed() < state.jwt_auth_fail_timeout); - let after = failures.len(); - if before != after { - debug!("Cleaned up {} old JWT auth failure entries", before - after); - } - } - }); - - let server_result = if let Some(tls_config) = config.tls_certificates { - if CryptoProvider::get_default().is_none() { - // Install the AWS-LC provider if no default is set, usually for CI - debug!("Installing AWS-LC as default TLS provider"); - let mut attempts = 0; - loop { - match aws_lc_rs::default_provider().install_default() { - Ok(_) => { - debug!("Successfully installed AWS-LC as default TLS provider"); - break; - } - Err(e) => { - if attempts >= 3 { - return Err(eyre::eyre!( - "Exceeded maximum attempts to install AWS-LC as default TLS provider: {e:?}" - )); - } - error!( - "Failed to install AWS-LC as default TLS provider: {e:?}. Retrying..." - ); - attempts += 1; - } - } - } - } - - let tls_config = RustlsConfig::from_pem(tls_config.0, tls_config.1).await?; - axum_server::bind_rustls(config.endpoint, tls_config) - .serve( - signer_app.merge(admin_app).into_make_service_with_connect_info::(), - ) - .await - } else { - warn!("Running in insecure HTTP mode, no TLS certificates provided"); - axum_server::bind(config.endpoint) - .serve( - signer_app.merge(admin_app).into_make_service_with_connect_info::(), - ) - .await - }; + .route(STATUS_PATH, get(handle_status)) + .into_make_service_with_connect_info::(); - // Shutdown the JWT cleaning task - jwt_cleaning_task.abort(); + let listener = TcpListener::bind(config.endpoint).await?; - server_result.wrap_err("signer service exited") + axum::serve(listener, app).await.wrap_err("signer server exited") } fn init_metrics(network: Chain) -> eyre::Result<()> { @@ -217,55 +134,34 @@ impl SigningService { } } -/// Marks a JWT authentication failure for a given client IP -fn mark_jwt_failure(state: &SigningState, client_ip: IpAddr) { - let mut failures = state.jwt_auth_failures.write(); - let failure_info = failures - .entry(client_ip) - .or_insert(JwtAuthFailureInfo { failure_count: 0, last_failure: Instant::now() }); - failure_info.failure_count += 1; - failure_info.last_failure = Instant::now(); -} - /// Authentication middleware layer async fn jwt_auth( State(state): State, - req_headers: HeaderMap, TypedHeader(auth): TypedHeader>, addr: ConnectInfo, - req: Request, + mut req: Request, next: Next, ) -> Result { // Check if the request needs to be rate limited - let client_ip = get_true_ip(&req_headers, &addr, &state.reverse_proxy).map_err(|e| { - error!("Failed to get client IP: {e}"); - SignerModuleError::RequestError("failed to get client IP".to_string()) - })?; + let client_ip = addr.ip(); check_jwt_rate_limit(&state, &client_ip)?; - // Clone the request so we can read the body - let (parts, body) = req.into_parts(); - let path = parts.uri.path(); - let bytes = to_bytes(body, REQUEST_MAX_BODY_LENGTH).await.map_err(|e| { - error!("Failed to read request body: {e}"); - SignerModuleError::RequestError(e.to_string()) - })?; - // Process JWT authorization - match check_jwt_auth(&auth, &state, path, &bytes) { + match check_jwt_auth(&auth, &state) { Ok(module_id) => { - let mut req = Request::from_parts(parts, Body::from(bytes)); req.extensions_mut().insert(module_id); Ok(next.run(req).await) } Err(SignerModuleError::Unauthorized) => { - mark_jwt_failure(&state, client_ip); + let mut failures = state.jwt_auth_failures.write(); + let failure_info = failures + .entry(client_ip) + .or_insert(JwtAuthFailureInfo { failure_count: 0, last_failure: Instant::now() }); + failure_info.failure_count += 1; + failure_info.last_failure = Instant::now(); Err(SignerModuleError::Unauthorized) } - Err(err) => { - mark_jwt_failure(&state, client_ip); - Err(err) - } + Err(err) => Err(err), } } @@ -310,68 +206,26 @@ fn check_jwt_rate_limit(state: &SigningState, client_ip: &IpAddr) -> Result<(), fn check_jwt_auth( auth: &Authorization, state: &SigningState, - path: &str, - body: &[u8], ) -> Result { let jwt: Jwt = auth.token().to_string().into(); // We first need to decode it to get the module id and then validate it // with the secret stored in the state - let claims = decode_jwt(jwt.clone()).map_err(|e| { + let module_id = decode_jwt(jwt.clone()).map_err(|e| { error!("Unauthorized request. Invalid JWT: {e}"); SignerModuleError::Unauthorized })?; - let guard = state.jwts.read(); - let jwt_config = guard.get(&claims.module).ok_or_else(|| { + let jwt_secret = state.jwts.get(&module_id).ok_or_else(|| { error!("Unauthorized request. Was the module started correctly?"); SignerModuleError::Unauthorized })?; - let body_bytes = if body.is_empty() { None } else { Some(body) }; - validate_jwt(jwt, &jwt_config.jwt_secret, path, body_bytes).map_err(|e| { - error!("Unauthorized request. Invalid JWT: {e}"); - SignerModuleError::Unauthorized - })?; - - Ok(claims.module) -} - -async fn admin_auth( - State(state): State, - req_headers: HeaderMap, - TypedHeader(auth): TypedHeader>, - addr: ConnectInfo, - req: Request, - next: Next, -) -> Result { - // Check if the request needs to be rate limited - let client_ip = get_true_ip(&req_headers, &addr, &state.reverse_proxy).map_err(|e| { - error!("Failed to get client IP: {e}"); - SignerModuleError::RequestError("failed to get client IP".to_string()) - })?; - check_jwt_rate_limit(&state, &client_ip)?; - - // Clone the request so we can read the body - let (parts, body) = req.into_parts(); - let path = parts.uri.path(); - let bytes = to_bytes(body, REQUEST_MAX_BODY_LENGTH).await.map_err(|e| { - error!("Failed to read request body: {e}"); - SignerModuleError::RequestError(e.to_string()) - })?; - - let jwt: Jwt = auth.token().to_string().into(); - - // Validate the admin JWT - let body_bytes: Option<&[u8]> = if bytes.is_empty() { None } else { Some(&bytes) }; - validate_admin_jwt(jwt, &state.admin_secret.read(), path, body_bytes).map_err(|e| { + validate_jwt(jwt, jwt_secret).map_err(|e| { error!("Unauthorized request. Invalid JWT: {e}"); - mark_jwt_failure(&state, client_ip); SignerModuleError::Unauthorized })?; - - let req = Request::from_parts(parts, Body::from(bytes)); - Ok(next.run(req).await) + Ok(module_id) } /// Requests logging middleware layer @@ -392,7 +246,9 @@ async fn handle_get_pubkeys( Extension(module_id): Extension, State(state): State, ) -> Result { - debug!(event = "get_pubkeys", ?module_id, "New request"); + let req_id = Uuid::new_v4(); + + debug!(event = "get_pubkeys", ?req_id, "New request"); let keys = state .manager @@ -406,184 +262,62 @@ async fn handle_get_pubkeys( Ok((StatusCode::OK, Json(res)).into_response()) } -/// Validates a BLS key signature request and returns the signature -async fn handle_request_signature_bls( +/// Implements request_signature from the Signer API +async fn handle_request_signature( Extension(module_id): Extension, State(state): State, - Json(request): Json, + Json(request): Json, ) -> Result { - debug!(event = "bls_request_signature", ?module_id, %request, "New request"); - handle_request_signature_bls_impl( - module_id, - state, - false, - request.pubkey, - request.object_root, - request.nonce, - ) - .await -} + let req_id = Uuid::new_v4(); -/// Validates a BLS key signature request using a proxy key and returns the -/// signature -async fn handle_request_signature_proxy_bls( - Extension(module_id): Extension, - State(state): State, - Json(request): Json>, -) -> Result { - debug!(event = "proxy_bls_request_signature", ?module_id, %request, "New request"); - handle_request_signature_bls_impl( - module_id, - state, - true, - request.proxy, - request.object_root, - request.nonce, - ) - .await -} - -/// Implementation for handling a BLS signature request -async fn handle_request_signature_bls_impl( - module_id: ModuleId, - state: SigningState, - is_proxy: bool, - signing_pubkey: BlsPublicKey, - object_root: B256, - nonce: u64, -) -> Result { - let Some(signing_id) = state.jwts.read().get(&module_id).map(|m| m.signing_id) else { - error!( - event = "proxy_bls_request_signature", - ?module_id, - %signing_pubkey, - %object_root, - nonce, - "Module signing ID not found" - ); - return Err(SignerModuleError::RequestError("Module signing ID not found".to_string())); - }; + debug!(event = "request_signature", ?module_id, %request, ?req_id, "New request"); - let (chain_id, signature) = match &*state.manager.read().await { - SigningManager::Local(local_manager) => { - let sig = if is_proxy { + let manager = state.manager.read().await; + let res = match &*manager { + SigningManager::Local(local_manager) => match request { + SignRequest::Consensus(SignConsensusRequest { object_root, pubkey }) => local_manager + .sign_consensus(&pubkey, object_root) + .await + .map(|sig| Json(sig).into_response()), + SignRequest::ProxyBls(SignProxyRequest { object_root, proxy: bls_key }) => { local_manager - .sign_proxy_bls( - &signing_pubkey, - &object_root, - Some(&SignatureRequestInfo { module_signing_id: signing_id, nonce }), - ) + .sign_proxy_bls(&bls_key, object_root) .await - } else { + .map(|sig| Json(sig).into_response()) + } + SignRequest::ProxyEcdsa(SignProxyRequest { object_root, proxy: ecdsa_key }) => { local_manager - .sign_consensus( - &signing_pubkey, - &object_root, - Some(&SignatureRequestInfo { module_signing_id: signing_id, nonce }), - ) - .await - }; - (local_manager.get_chain().id(), sig) - } - SigningManager::Dirk(dirk_manager) => { - let sig = if is_proxy { - dirk_manager - .request_proxy_signature( - &signing_pubkey, - &object_root, - Some(&SignatureRequestInfo { module_signing_id: signing_id, nonce }), - ) + .sign_proxy_ecdsa(&ecdsa_key, object_root) .await - } else { - dirk_manager - .request_consensus_signature( - &signing_pubkey, - &object_root, - Some(&SignatureRequestInfo { module_signing_id: signing_id, nonce }), - ) - .await - }; - (dirk_manager.get_chain().id(), sig) - } + .map(|sig| Json(sig).into_response()) + } + }, + SigningManager::Dirk(dirk_manager) => match request { + SignRequest::Consensus(SignConsensusRequest { object_root, pubkey }) => dirk_manager + .request_consensus_signature(&pubkey, object_root) + .await + .map(|sig| Json(sig).into_response()), + SignRequest::ProxyBls(SignProxyRequest { object_root, proxy: bls_key }) => dirk_manager + .request_proxy_signature(&bls_key, object_root) + .await + .map(|sig| Json(sig).into_response()), + SignRequest::ProxyEcdsa(_) => { + error!( + event = "request_signature", + ?module_id, + ?req_id, + "ECDSA proxy sign request not supported with Dirk" + ); + Err(SignerModuleError::DirkNotSupported) + } + }, }; - signature - .inspect_err(|err| { - error!(event = "request_signature", ?module_id, %signing_pubkey, %object_root, nonce, "{err}") - }) - .map(|sig| { - Json(BlsSignResponse::new( - signing_pubkey.clone(), - object_root, - signing_id, - nonce, - chain_id, - sig, - )) - .into_response() - }) -} - -/// Validates an ECDSA key signature request using a proxy key and returns the -/// signature -async fn handle_request_signature_proxy_ecdsa( - Extension(module_id): Extension, - State(state): State, - Json(request): Json>, -) -> Result { - let Some(signing_id) = state.jwts.read().get(&module_id).map(|m| m.signing_id) else { - error!( - event = "proxy_ecdsa_request_signature", - ?module_id, - proxy = %request.proxy, - object_root = %request.object_root, - nonce = request.nonce, - "Module signing ID not found" - ); - return Err(SignerModuleError::RequestError("Module signing ID not found".to_string())); - }; - debug!(event = "proxy_ecdsa_request_signature", ?module_id, %request, "New request"); + if let Err(err) = &res { + error!(event = "request_signature", ?module_id, ?req_id, "{err}"); + } - let (chain_id, signature) = match &*state.manager.read().await { - SigningManager::Local(local_manager) => { - let sig = local_manager - .sign_proxy_ecdsa( - &request.proxy, - &request.object_root, - Some(&SignatureRequestInfo { - module_signing_id: signing_id, - nonce: request.nonce, - }), - ) - .await; - (local_manager.get_chain().id(), sig) - } - SigningManager::Dirk(_) => { - // Dirk does not support ECDSA proxy signing - error!( - event = "request_signature", - ?module_id, - proxy = %request.proxy, - object_root = %request.object_root, - nonce = request.nonce, - "ECDSA proxy sign request not supported with Dirk" - ); - (U256::ZERO, Err(SignerModuleError::DirkNotSupported)) - } - }; - signature - .inspect_err(|err| error!(event = "request_signature", ?module_id, proxy = %request.proxy, object_root = %request.object_root, nonce = request.nonce, "{err}")) - .map(|sig| { - Json(EcdsaSignResponse::new( - request.proxy, - request.object_root, - signing_id, - request.nonce, - chain_id, - sig, - )) - .into_response() - }) + res } async fn handle_generate_proxy( @@ -591,23 +325,25 @@ async fn handle_generate_proxy( State(state): State, Json(request): Json, ) -> Result { - debug!(event = "generate_proxy", ?module_id, scheme=?request.scheme, pubkey=%request.consensus_pubkey, "New request"); + let req_id = Uuid::new_v4(); + + debug!(event = "generate_proxy", ?module_id, scheme=?request.scheme, pubkey=%request.consensus_pubkey, ?req_id, "New request"); let mut manager = state.manager.write().await; let res = match &mut *manager { SigningManager::Local(local_manager) => match request.scheme { EncryptionScheme::Bls => local_manager - .create_proxy_bls(module_id.clone(), &request.consensus_pubkey) + .create_proxy_bls(module_id.clone(), request.consensus_pubkey) .await .map(|proxy_delegation| Json(proxy_delegation).into_response()), EncryptionScheme::Ecdsa => local_manager - .create_proxy_ecdsa(module_id.clone(), &request.consensus_pubkey) + .create_proxy_ecdsa(module_id.clone(), request.consensus_pubkey) .await .map(|proxy_delegation| Json(proxy_delegation).into_response()), }, SigningManager::Dirk(dirk_manager) => match request.scheme { EncryptionScheme::Bls => dirk_manager - .generate_proxy_key(&module_id, &request.consensus_pubkey) + .generate_proxy_key(&module_id, request.consensus_pubkey) .await .map(|proxy_delegation| Json(proxy_delegation).into_response()), EncryptionScheme::Ecdsa => { @@ -618,97 +354,40 @@ async fn handle_generate_proxy( }; if let Err(err) = &res { - error!(event = "generate_proxy", ?module_id, scheme=?request.scheme, pubkey=%request.consensus_pubkey, "{err}"); + error!(event = "generate_proxy", module_id=?module_id, ?req_id, "{err}"); } res } async fn handle_reload( - State(state): State, - Json(request): Json, + State(mut state): State, ) -> Result { - debug!(event = "reload", "New request"); + let req_id = Uuid::new_v4(); + + debug!(event = "reload", ?req_id, "New request"); let config = match StartSignerConfig::load_from_env() { Ok(config) => config, Err(err) => { - error!(event = "reload", error = ?err, "Failed to reload config"); + error!(event = "reload", ?req_id, error = ?err, "Failed to reload config"); return Err(SignerModuleError::Internal("failed to reload config".to_string())); } }; - // Extract the fields we need before start_manager consumes the config - let new_signing_configs = config.mod_signing_configs.clone(); - let new_admin_secret = config.admin_secret.clone(); - let new_manager = match start_manager(config).await { Ok(manager) => manager, Err(err) => { - error!(event = "reload", error = ?err, "Failed to reload manager"); + error!(event = "reload", ?req_id, error = ?err, "Failed to reload manager"); return Err(SignerModuleError::Internal("failed to reload config".to_string())); } }; - apply_reload(state, request, new_manager, new_signing_configs, new_admin_secret).await -} - -/// Applies a reload request to the signing state. Separated from -/// `handle_reload` so the business logic can be tested without requiring a -/// live environment (config file, env vars, keystore on disk). -/// -/// The reload follows a two-layer approach: -/// 1. Sync the baseline from the freshly loaded config (adds new modules, -/// removes deleted ones, resets secrets to their env var / config values). -/// 2. Apply any optional overrides from the request body on top (for remote -/// secret rotation without editing config files). -async fn apply_reload( - state: SigningState, - request: ReloadRequest, - new_manager: SigningManager, - new_signing_configs: HashMap, - new_admin_secret: String, -) -> Result { - // Build the new JWT map from config, then apply any body overrides. - let mut new_jwts = new_signing_configs; - - if let Some(jwt_secrets) = request.jwt_secrets { - // Validate all overrides before applying any - for module_id in jwt_secrets.keys() { - if !new_jwts.contains_key(module_id) { - let error_message = - format!("Module {module_id} not found in config, cannot override JWT secret"); - error!(event = "reload", module_id = %module_id, error = %error_message); - return Err(SignerModuleError::RequestError(error_message)); - } - } - - for (module_id, jwt_secret) in jwt_secrets { - if let Some(cfg) = new_jwts.get_mut(&module_id) { - cfg.jwt_secret = jwt_secret; - } - } - } - - *state.jwts.write() = new_jwts; - *state.admin_secret.write() = request.admin_secret.unwrap_or(new_admin_secret); - - *state.manager.write().await = new_manager; + state.manager = Arc::new(RwLock::new(new_manager)); Ok(StatusCode::OK) } -async fn handle_revoke_module( - State(state): State, - Json(request): Json, -) -> Result { - let mut guard = state.jwts.write(); - guard - .remove(&request.module_id) - .ok_or(SignerModuleError::ModuleIdNotFound) - .map(|_| StatusCode::OK) -} - async fn start_manager(config: StartSignerConfig) -> eyre::Result { let proxy_store = if let Some(store) = config.store.clone() { Some(store.init_from_env()?) @@ -739,435 +418,3 @@ async fn start_manager(config: StartSignerConfig) -> eyre::Result ModuleSigningConfig { - ModuleSigningConfig { - module_name: ModuleId(module_name.to_string()), - jwt_secret: secret.to_string(), - signing_id, - } - } - - fn make_state(jwts: HashMap) -> SigningState { - SigningState { - manager: Arc::new(RwLock::new(SigningManager::Local( - LocalSigningManager::new(Chain::Holesky, None).unwrap(), - ))), - jwts: Arc::new(ParkingRwLock::new(jwts)), - admin_secret: Arc::new(ParkingRwLock::new("admin".to_string())), - jwt_auth_failures: Arc::new(ParkingRwLock::new(HashMap::new())), - jwt_auth_fail_limit: 3, - jwt_auth_fail_timeout: Duration::from_secs(60), - reverse_proxy: ReverseProxyHeaderSetup::None, - } - } - - fn empty_manager() -> SigningManager { - SigningManager::Local(LocalSigningManager::new(Chain::Holesky, None).unwrap()) - } - - /// Helper to call apply_reload with the config baseline matching the - /// current state (simulates a reload where the config file hasn't changed). - async fn reload_with_same_config( - state: &SigningState, - request: ReloadRequest, - ) -> Result { - let new_signing_configs = state.jwts.read().clone(); - let new_admin_secret = state.admin_secret.read().clone(); - apply_reload(state.clone(), request, empty_manager(), new_signing_configs, new_admin_secret) - .await - } - - /// Partial reload must update only the provided modules and leave omitted - /// modules with their config-baseline secrets. - #[tokio::test] - async fn test_partial_reload_preserves_omitted_modules() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - let state = make_state(HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "secret-b", signing_id_b)), - ])); - - let request = ReloadRequest { - jwt_secrets: Some(HashMap::from([(module_a.clone(), "rotated-secret-a".to_string())])), - admin_secret: None, - }; - - let result = reload_with_same_config(&state, request).await; - assert!(result.is_ok(), "apply_reload should succeed"); - - let jwts = state.jwts.read(); - assert_eq!( - jwts[&module_a].jwt_secret, "rotated-secret-a", - "module_a secret should be updated" - ); - assert_eq!( - jwts[&module_b].jwt_secret, "secret-b", - "module_b secret must be preserved when omitted" - ); - } - - /// A full reload (all modules provided) should update every module. - #[tokio::test] - async fn test_full_reload_updates_all_modules() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - let state = make_state(HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "secret-b", signing_id_b)), - ])); - - let request = ReloadRequest { - jwt_secrets: Some(HashMap::from([ - (module_a.clone(), "new-secret-a".to_string()), - (module_b.clone(), "new-secret-b".to_string()), - ])), - admin_secret: None, - }; - - reload_with_same_config(&state, request).await.unwrap(); - - let jwts = state.jwts.read(); - assert_eq!(jwts[&module_a].jwt_secret, "new-secret-a"); - assert_eq!(jwts[&module_b].jwt_secret, "new-secret-b"); - } - - /// Override for a module not in the config should return an error. - /// The baseline config has already been applied, but overrides are - /// validated against it. - #[tokio::test] - async fn test_reload_override_unknown_module_returns_error() { - let module_a = ModuleId("module-a".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - let config_baseline = HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "secret-a", signing_id_a), - )]); - - let state = make_state(config_baseline.clone()); - - let request = ReloadRequest { - jwt_secrets: Some(HashMap::from([( - ModuleId("unknown-module".to_string()), - "some-secret".to_string(), - )])), - admin_secret: None, - }; - - let result = apply_reload( - state.clone(), - request, - empty_manager(), - config_baseline, - "admin".to_string(), - ) - .await; - assert!(result.is_err(), "unknown module override should return an error"); - - // Nothing should have been applied — old state is preserved - let jwts = state.jwts.read(); - assert_eq!(jwts[&module_a].jwt_secret, "secret-a"); - } - - /// An override request containing both a valid and an unknown module must - /// fail atomically — the config baseline is applied but no overrides take - /// effect. - #[tokio::test] - async fn test_reload_mixed_known_and_unknown_override_is_atomic() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - let config_baseline = HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "secret-b", signing_id_b)), - ]); - - let state = make_state(config_baseline.clone()); - - let request = ReloadRequest { - jwt_secrets: Some(HashMap::from([ - (module_a.clone(), "rotated-secret-a".to_string()), - (ModuleId("unknown-module".to_string()), "some-secret".to_string()), - ])), - admin_secret: None, - }; - - let result = apply_reload( - state.clone(), - request, - empty_manager(), - config_baseline, - "admin".to_string(), - ) - .await; - assert!(result.is_err(), "mixed known+unknown should return an error"); - - // Nothing should have been applied — old state is fully preserved - let jwts = state.jwts.read(); - assert_eq!( - jwts[&module_a].jwt_secret, "secret-a", - "module_a must retain its original secret" - ); - assert_eq!( - jwts[&module_b].jwt_secret, "secret-b", - "module_b must retain its original secret" - ); - } - - /// Reload with no jwt_secrets should reset all secrets to config baseline. - #[tokio::test] - async fn test_reload_without_jwt_secrets_resets_to_config() { - let module_a = ModuleId("module-a".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - // State has a previously-rotated secret - let state = make_state(HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "rotated-secret", signing_id_a), - )])); - - // Config baseline has the original secret - let config_baseline = HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "config-secret", signing_id_a), - )]); - - let request = ReloadRequest { jwt_secrets: None, admin_secret: None }; - - apply_reload(state.clone(), request, empty_manager(), config_baseline, "admin".to_string()) - .await - .unwrap(); - - let jwts = state.jwts.read(); - assert_eq!( - jwts[&module_a].jwt_secret, "config-secret", - "secret should be reset to config baseline" - ); - } - - /// A new module added to the config should appear in state after reload. - #[tokio::test] - async fn test_reload_adds_new_module_from_config() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - // Old state only has module-a - let state = make_state(HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "secret-a", signing_id_a), - )])); - - // Config now includes module-b as well - let config_baseline = HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "secret-b", signing_id_b)), - ]); - - let request = ReloadRequest { jwt_secrets: None, admin_secret: None }; - - apply_reload(state.clone(), request, empty_manager(), config_baseline, "admin".to_string()) - .await - .unwrap(); - - let jwts = state.jwts.read(); - assert_eq!(jwts.len(), 2, "should have both modules"); - assert_eq!(jwts[&module_a].jwt_secret, "secret-a"); - assert_eq!(jwts[&module_b].jwt_secret, "secret-b"); - } - - /// A module removed from config should be dropped from state after reload. - #[tokio::test] - async fn test_reload_removes_module_not_in_config() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - // Old state has both modules - let state = make_state(HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "secret-b", signing_id_b)), - ])); - - // Config only has module-a now - let config_baseline = HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "secret-a", signing_id_a), - )]); - - let request = ReloadRequest { jwt_secrets: None, admin_secret: None }; - - apply_reload(state.clone(), request, empty_manager(), config_baseline, "admin".to_string()) - .await - .unwrap(); - - let jwts = state.jwts.read(); - assert_eq!(jwts.len(), 1, "should only have module-a"); - assert!(jwts.contains_key(&module_a)); - assert!(!jwts.contains_key(&module_b), "module-b should be removed"); - } - - /// Body override should work on a newly added module from config. - #[tokio::test] - async fn test_reload_override_on_newly_added_module() { - let module_a = ModuleId("module-a".to_string()); - let module_b = ModuleId("module-b".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - let signing_id_b = - b256!("0202020202020202020202020202020202020202020202020202020202020202"); - - // Old state only has module-a - let state = make_state(HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "secret-a", signing_id_a), - )])); - - // Config adds module-b - let config_baseline = HashMap::from([ - (module_a.clone(), make_signing_config("module-a", "secret-a", signing_id_a)), - (module_b.clone(), make_signing_config("module-b", "config-secret-b", signing_id_b)), - ]); - - // Override the new module's secret via body - let request = ReloadRequest { - jwt_secrets: Some(HashMap::from([( - module_b.clone(), - "overridden-secret-b".to_string(), - )])), - admin_secret: None, - }; - - apply_reload(state.clone(), request, empty_manager(), config_baseline, "admin".to_string()) - .await - .unwrap(); - - let jwts = state.jwts.read(); - assert_eq!(jwts[&module_a].jwt_secret, "secret-a"); - assert_eq!( - jwts[&module_b].jwt_secret, "overridden-secret-b", - "new module secret should be overridden by body" - ); - } - - /// Admin secret should be synced from config, then overridden by body. - #[tokio::test] - async fn test_reload_admin_secret_config_then_override() { - let state = make_state(HashMap::new()); - - // Config has new admin secret - let config_baseline = HashMap::new(); - - // Body overrides it further - let request = ReloadRequest { - jwt_secrets: None, - admin_secret: Some("body-admin-secret".to_string()), - }; - - apply_reload( - state.clone(), - request, - empty_manager(), - config_baseline, - "config-admin-secret".to_string(), - ) - .await - .unwrap(); - - assert_eq!( - *state.admin_secret.read(), - "body-admin-secret", - "body override should take precedence over config" - ); - } - - /// Admin secret should be reset to config value when no body override. - #[tokio::test] - async fn test_reload_admin_secret_resets_to_config() { - let state = make_state(HashMap::new()); - *state.admin_secret.write() = "old-rotated-admin".to_string(); - - let request = ReloadRequest { jwt_secrets: None, admin_secret: None }; - - apply_reload( - state.clone(), - request, - empty_manager(), - HashMap::new(), - "config-admin-secret".to_string(), - ) - .await - .unwrap(); - - assert_eq!( - *state.admin_secret.read(), - "config-admin-secret", - "admin secret should reset to config baseline" - ); - } - - #[tokio::test] - async fn test_revoke_module() { - let module_a = ModuleId("module-a".to_string()); - let signing_id_a = - b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - let state = make_state(HashMap::from([( - module_a.clone(), - make_signing_config("module-a", "secret-a", signing_id_a), - )])); - - { - let jwts = state.jwts.read(); - assert_eq!(jwts[&module_a].jwt_secret, "secret-a"); - } - - let revoke_request = RevokeModuleRequest { module_id: module_a.clone() }; - - let revoke_response = - handle_revoke_module(State(state.clone()), Json(revoke_request)).await; - - assert!(revoke_response.is_ok(), "revoke_module should succeed"); - - { - let jwts = state.jwts.read(); - assert!(!jwts.contains_key(&module_a), "module-a should be removed from JWT configs"); - } - } -} diff --git a/crates/signer/src/utils.rs b/crates/signer/src/utils.rs deleted file mode 100644 index bfc28f9f..00000000 --- a/crates/signer/src/utils.rs +++ /dev/null @@ -1,242 +0,0 @@ -use std::net::{IpAddr, SocketAddr}; - -use axum::http::HeaderMap; -use cb_common::config::ReverseProxyHeaderSetup; - -#[derive(Debug, thiserror::Error)] -pub enum IpError { - #[error("header `{0}` is not present")] - NotPresent(String), - #[error("header value has invalid characters")] - HasInvalidCharacters, - #[error("header value is not a valid IP address")] - InvalidValue, - #[error("header `{0}` appears multiple times but expected to be unique")] - NotUnique(String), - #[error("header does not contain enough values: found {found}, required {required}")] - NotEnoughValues { found: usize, required: usize }, -} - -/// Get the true client IP from the request headers or fallback to the socket -/// address -pub fn get_true_ip( - headers: &HeaderMap, - addr: &SocketAddr, - reverse_proxy: &ReverseProxyHeaderSetup, -) -> Result { - match reverse_proxy { - ReverseProxyHeaderSetup::None => Ok(addr.ip()), - ReverseProxyHeaderSetup::Unique { header } => get_ip_from_unique_header(headers, header), - ReverseProxyHeaderSetup::Rightmost { header, trusted_count } => { - get_ip_from_rightmost_value(headers, header, trusted_count.get()) - } - } -} - -fn get_ip_from_unique_header(headers: &HeaderMap, header_name: &str) -> Result { - let mut values = headers.get_all(header_name).iter(); - - let first_value = values.next().ok_or(IpError::NotPresent(header_name.to_string()))?; - - if values.next().is_some() { - return Err(IpError::NotUnique(header_name.to_string())); - } - - let ip = first_value - .to_str() - .map_err(|_| IpError::HasInvalidCharacters)? - .parse::() - .map_err(|_| IpError::InvalidValue)?; - - Ok(ip) -} - -fn get_ip_from_rightmost_value( - headers: &HeaderMap, - header_name: &str, - trusted_count: usize, -) -> Result { - let joined_values = headers - .get_all(header_name) - .iter() - .map(|x| x.to_str().map_err(|_| IpError::HasInvalidCharacters)) - .collect::, IpError>>()? - .join(","); - - if joined_values.is_empty() { - return Err(IpError::NotPresent(header_name.to_string())) - } - - // Selecting the first untrusted IP from the right according to: - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#selecting_an_ip_address - joined_values - .rsplit(",") - .nth(trusted_count - 1) - .ok_or(IpError::NotEnoughValues { - found: joined_values.split(",").count(), - required: trusted_count, - })? - .trim() - .parse::() - .map_err(|_| IpError::InvalidValue) -} - -#[cfg(test)] -mod tests { - use std::net::Ipv4Addr; - - use super::*; - - #[test] - fn test_unique_header_pass() { - let header_name = "X-Real-IP"; - let real_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); - - let mut headers = HeaderMap::new(); - headers.insert(header_name, real_ip.to_string().parse().unwrap()); - - let ip = get_ip_from_unique_header(&headers, header_name).unwrap(); - assert_eq!(ip, real_ip); - } - - #[test] - fn test_unique_header_duplicated() { - let header_name = "X-Real-IP"; - let real_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); - let fake_ip = IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)); - - let mut headers = HeaderMap::new(); - headers.insert(header_name, real_ip.to_string().parse().unwrap()); - headers.append(header_name, fake_ip.to_string().parse().unwrap()); - - let err = get_ip_from_unique_header(&headers, header_name) - .expect_err("Not unique header should fail"); - assert!(matches!(err, IpError::NotUnique(_))); - } - #[test] - fn test_unique_header_not_present() { - let header_name = "X-Real-IP"; - let headers = HeaderMap::new(); - - let err = get_ip_from_unique_header(&headers, header_name) - .expect_err("Missing header should fail"); - assert!(matches!(err, IpError::NotPresent(_))); - } - - #[test] - fn test_unique_header_invalid_value() { - let header_name = "X-Real-IP"; - let mut headers = HeaderMap::new(); - headers.insert(header_name, "invalid-ip".parse().unwrap()); - - let err = - get_ip_from_unique_header(&headers, header_name).expect_err("Invalid IP should fail"); - assert!(matches!(err, IpError::InvalidValue)); - } - - #[test] - fn test_unique_header_empty_value() { - let header_name = "X-Real-IP"; - let mut headers = HeaderMap::new(); - headers.insert(header_name, "".parse().unwrap()); - - let err = - get_ip_from_unique_header(&headers, header_name).expect_err("Invalid IP should fail"); - assert!(matches!(err, IpError::InvalidValue)); - } - - #[test] - fn test_rightmost_header_comma_separated() { - let header_name = "X-Forwarded-For"; - let ip1 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); - let ip2 = IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)); - let ip3 = IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3)); - - let mut headers = HeaderMap::new(); - headers.insert(header_name, format!("{},{},{}", ip1, ip2, ip3).parse().unwrap()); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 1).unwrap(); - assert_eq!(ip, ip3); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 2).unwrap(); - assert_eq!(ip, ip2); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 3).unwrap(); - assert_eq!(ip, ip1); - - let err = get_ip_from_rightmost_value(&headers, header_name, 4) - .expect_err("Not enough values should fail"); - assert!(matches!(err, IpError::NotEnoughValues { .. })); - } - - #[test] - fn test_rightmost_header_comma_space_separated() { - let header_name = "X-Forwarded-For"; - let ip1 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); - let ip2 = IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)); - let ip3 = IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3)); - - let mut headers = HeaderMap::new(); - headers.insert(header_name, format!("{}, {}, {}", ip1, ip2, ip3).parse().unwrap()); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 1).unwrap(); - assert_eq!(ip, ip3); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 2).unwrap(); - assert_eq!(ip, ip2); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 3).unwrap(); - assert_eq!(ip, ip1); - - let err = get_ip_from_rightmost_value(&headers, header_name, 4) - .expect_err("Not enough values should fail"); - assert!(matches!(err, IpError::NotEnoughValues { .. })); - } - - #[test] - fn test_rightmost_header_duplicated() { - // If the header appears multiple times, they should be joined together - // as if they were a single value. - let header_name = "X-Forwarded-For"; - let ip1 = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)); - let ip2 = IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)); - let ip3 = IpAddr::V4(Ipv4Addr::new(3, 3, 3, 3)); - let ip4 = IpAddr::V4(Ipv4Addr::new(4, 4, 4, 4)); - let ip5 = IpAddr::V4(Ipv4Addr::new(5, 5, 5, 5)); - - let mut headers = HeaderMap::new(); - headers.insert(header_name, format!("{},{},{}", ip1, ip2, ip3).parse().unwrap()); - headers.append(header_name, format!("{},{}", ip4, ip5).parse().unwrap()); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 1).unwrap(); - assert_eq!(ip, ip5); - - let ip = get_ip_from_rightmost_value(&headers, header_name, 5).unwrap(); - assert_eq!(ip, ip1); - - let err = get_ip_from_rightmost_value(&headers, header_name, 6) - .expect_err("Not enough values should fail"); - assert!(matches!(err, IpError::NotEnoughValues { .. })); - } - - #[test] - fn test_rightmost_header_not_present() { - let header_name = "X-Forwarded-For"; - let headers = HeaderMap::new(); - - let err = get_ip_from_rightmost_value(&headers, header_name, 1) - .expect_err("Missing header should fail"); - assert!(matches!(err, IpError::NotPresent(_))); - } - - #[test] - fn test_rightmost_header_invalid_value() { - let header_name = "X-Forwarded-For"; - let mut headers = HeaderMap::new(); - headers.insert(header_name, "invalid-ip".parse().unwrap()); - - let err = get_ip_from_rightmost_value(&headers, header_name, 1) - .expect_err("Invalid IP should fail"); - assert!(matches!(err, IpError::InvalidValue)); - } -} diff --git a/docs/docs/developing/prop-commit-signing.md b/docs/docs/developing/prop-commit-signing.md deleted file mode 100644 index 30f70413..00000000 --- a/docs/docs/developing/prop-commit-signing.md +++ /dev/null @@ -1,76 +0,0 @@ -# Requesting Proposer Commitment Signatures with Commit-Boost - -When you create a new validator on the Ethereum network, one of the steps is the generation of a new BLS private key (commonly known as the "validator key" or the "signer key") and its corresponding BLS public key (the "validator pubkey", used as an identifier). Typically this private key will be used by an Ethereum consensus client to sign things such as attestations and blocks for publication on the Beacon chain. These signatures prove that you, as the owner of that private key, approve of the data being signed. However, as general-purpose private keys, they can also be used to sign *other* arbitrary messages not destined for the Beacon chain. - -Commit-Boost takes advantage of this by offering a standard known as **proposer commitments**. These are arbitrary messages (albeit with some important rules), similar to the kind used on the Beacon chain, that have been signed by one of the owner's private keys. Modules interested in leveraging Commit-Boost's proposer commitments can construct their own data in whatever format they like and request that Commit-Boost's **signer service** generate a signature for it with a particular private key. The module can then use that signature to verify the data was signed by that user. - -Commit-Boost supports proposer commitment signatures for both BLS private keys (identified by their public key) and ECDSA private keys (identified by their Ethereum address). - - -## Rules of Proposer Commitment Signatures - -Proposer commitment signatures produced by Commit-Boost's signer service conform to the following rules: - -- Signatures are **unique** to a given EVM chain (identified by its [chain ID](https://chainlist.org/)). Signatures generated for one chain will not work on a different chain. -- Signatures are **unique** to Commit-Boost proposer commitments. The signer service **cannot** be used to create signatures that could be used for other applications, such as for attestations on the Beacon chain. While the signer service has access to the same validator private keys used to attest on the Beacon chain, it cannot create signatures that would get you slashed on the Beacon chain. -- Signatures are **unique** to a particular module. One module cannot, for example, request an identical payload as another module and effectively "forge" a signature for the second module; identical payloads from two separate modules will result in two separate signatures. -- The data payload being signed must be a **32-byte array**, typically serializd as a 64-character hex string with an optional `0x` prefix. The value itself is arbitrary, as long as it has meaning to the requester - though it is typically the 256-bit hash of some kind of data. -- If requesting a signature from a BLS key, the resulting signature will be a standard BLS signature (96 bytes in length). -- If requesting a signature from an ECDSA key, the resulting signature will be a standard Ethereum RSV signature (65 bytes in length). -- Signatures **may** be **unique** per request, using the optional `nonce` field in their requests to indicate a unique sequence that this signature belongs to. - - -## Configuring a Module for Proposer Commitments - -Commit-Boost's signer service must be configured prior to launching to expect requests from your module. There are two main parts: - -1. An entry for your module into [Commit-Boost's configuration file](../get_started/configuration.md#custom-module). This must include a unique ID for your module, the line `type = "commit"`, and include a unique [signing ID](#the-signing-id) for your module. Generally you should provide values for these in your documentation, so your users can reference it when configuring their own Commit-Boost node. - -2. A JWT secret used by your module to authenticate with the signer in HTTP requests. This must be a string that both the Commit-Boost signer can read and your module can read, but no other modules should be allowed to access it. The user should be responsible for determining an appropriate secret and providing it to the Commit-Boost signer service securely; your module will need some way to accept this, typically via a command line argument that accepts a path to a file with the secret or as an environment variable. - -Once the user has configured both Commit-Boost and your module with these settings, your module will be able to authenticate with the signer service and request signatures. - - -## The Signing ID - -Your module's signing ID is a 32-byte value that is used as a unique identifier within the signing process. Proposer commitment signatures incorporate this value along with the data being signed as a way to create signatures that are exclusive to your module, so other modules can't maliciously construct signatures that appear to be from your module. Your module must have this ID incorporated into itself ahead of time, and the user must include this same ID within their Commit-Boost configuration file section for your module. Commit-Boost does not maintain a global registry of signing IDs, so this is a value you should provide to your users in your documentation. - -The Signing ID is decoupled from your module's human-readable name (the `module_id` field in the Commit-Boost configuration file) so that any changes to your module name will not invalidate signatures from previous versions. Similarly, if you don't change the module ID but *want* to invalidate previous signatures, you can modify the signing ID and it will do so. Just ensure your users are made aware of the change, so they can update it in their Commit-Boost configuration files accordingly. - - -## Nonces - -Your module has the option of using **Nonces** for each of its signature requests. Nonces are intended to be unique values that establish a sequence of signature requests, distinguishing one signature from another - even if all of their other payload information is identical. When making a request for a signature, you may include a unique nonce as part of the request; the signature will include it in its data, ensuring that things like replay attacks cannot be used for that signature. - -If you want to use them within your module, your module (or whatever remote backend system it connects to) **will be responsible** for storing, comparing, validating, and otherwise using the nonces. Commit-Boost's signer service by itself **does not** store nonces or track which ones have already been used by a given module. - -In terms of implementation, the nonce format conforms to the specification in [EIP-2681](https://eips.ethereum.org/EIPS/eip-2681). It is an unsigned 64-bit integer, with a minimum value of 0 and a maximum value of `2^64-2`. The field is required and is always mixed into the signing root. Modules that do not use nonces for replay protection should always send `0`; modules that do should use a monotonically increasing value per key. - - -## Structure of a Signature - -The form proposer commitment signatures take depends on the type of signature being requested. BLS signatures take the [standard form](https://eth2book.info/latest/part2/building_blocks/signatures/) (96-byte values). ECDSA (Ethereum EL) signatures take the [standard Ethereum ECDSA `r,s,v` signature form](https://forum.openzeppelin.com/t/sign-it-like-you-mean-it-creating-and-verifying-ethereum-signatures/697). In both cases, the data being signed is a 32-byte hash - the root hash of a composite two-stage [SSZ Merkle tree](https://thogiti.github.io/2024/05/02/Merkleization.html), described below: - -
- - - -
- -where, for the sub-tree in blue: - -- `Request Data` is a 32-byte array that serves as the data you want to sign. This is typically a hash of some more complex data on its own that your module constructs. - -- `Signing ID` is your module's 32-byte signing ID. The signer service will load this for your module from its configuration file. - -- `Nonce` is the nonce value for the signature request. This field is required. Modules that do not use replay protection should always send `0`; modules that do should use a monotonically increasing value per key. Conforming with the tree specification, it must be added as a 256-bit unsigned little-endian integer. Most libraries will be able to do this conversion automatically if you specify the field as the language's primitive for 64-bit unsigned integers (e.g., `uint64`, `u64`, `ulong`, etc.). - -- `Chain ID` is the ID of the chain that the Signer service is currently configured to use, as indicated by the [Commit-Boost configuration file](../get_started/configuration.md). This must also be a 256-bit unsigned little-endian integer. - -A Merkle tree must be constructed from these four leaf nodes, and its root hash calculated according to the standard SSZ hash computation rules. This result will be called the "sub-tree root". With this, a second Merkle tree is created using this sub-tree root and a value called the Domain: - -- `Domain` is the 32-byte output of the [compute_domain()](https://eth2book.info/capella/part2/building_blocks/signatures/#domain-separation-and-forks) function in the Beacon specification. The 4-byte domain type in this case is not a standard Beacon domain type, but rather Commit Boost's own domain type: `0x6D6D6F43`. - -The data signed in a proposer commitment is the 32-byte hash root of this new tree (the green `Root` box). - -Many languages provide libraries for computing the root of an SSZ Merkle tree, such as [fastssz for Go](https://github.com/ferranbt/fastssz) or [tree_hash for Rust](https://docs.rs/tree_hash/latest/tree_hash/). When verifying proposer commitment signatures, use a library that supports Merkle tree root hashing, the `compute_domain()` operation, and validation for signatures generated by your key of choice. diff --git a/docs/docs/get_started/building.md b/docs/docs/get_started/building.md index dd860be2..3a0a964a 100644 --- a/docs/docs/get_started/building.md +++ b/docs/docs/get_started/building.md @@ -14,15 +14,15 @@ The build system assumes that you've added your user account to the `docker` gro The Docker builder is built into the project's `justfile` which is used to invoke many facets of Commit Boost development. To use it, you'll need to install [Just](https://github.com/casey/just) on your system. -Use `just --list` to show all of the actions - there are many. The `justfile` provides granular actions, called "recipes", for building just the binaries of a specific crate (such as the CLI, `pbs`, or `signer`), as well as actions to build the Docker images for the PBS and Signer services. +Use `just --list` to show all of the actions - there are many. The `justfile` provides granular actions, called "recipes", for building just the binaries of a specific crate (such as the CLI, `pbs`, or `signer`), as well as actions to build the Docker images for the PBS and Signer modules. Below is a brief summary of the relevant ones for building the Commit-Boost artifacts: -- `build-all ` will build the `commit-boost` binary for your local system architecture. It will also create Docker images called `commit-boost/pbs:` and `commit-boost/signer:` and load them into your local Docker registry for use. -- `build-bin ` can be used to create the `commit-boost` binary itself. -- `build-pbs-img ` and `build-signer-img ` can be used to create the Docker images for the PBS and Signer services, respectively. +- `build-all ` will build the `commit-boost-cli`, `commit-boost-pbs`, and `commit-boost-signer` binaries for your local system architecture. It will also create Docker images called `commit-boost/pbs:` and `commit-boost/signer:` and load them into your local Docker registry for use. +- `build-cli-bin `, `build-pbs-bin `, and `build-signer-bin ` can be used to create the `commit-boost-cli`, `commit-boost-pbs`, and `commit-boost-signer` binaries, respectively. +- `build-pbs-img ` and `build-signer-img ` can be used to create the Docker images for the PBS and Signer modules, respectively. -The `version` provided will be used to house the output binaries in `./build/`, and act as the version tag for the Docker images when they're added to your local system or uploaded to your local Docker repository. For example, using `$(git rev-parse --short HEAD)` will set the version to the current commit hash. +The `version` provided will be used to house the output binaries in `./build/`, and act as the version tag for the Docker images when they're added to your local system or uploaded to your local Docker repository. If you're interested in building the binaries and/or Docker images for multiple architectures (currently Linux `amd64` and `arm64`), use the variants of those recipes that have the `-multiarch` suffix. Note that building a multiarch Docker image manifest will require the use of a [custom Docker registry](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-20-04), as the local registry built into Docker does not have multiarch manifest support. @@ -34,7 +34,7 @@ If you don't want to use the Docker builder, you can compile the Commit-Boost ar Requirements: -- Rust 1.91+ +- Rust 1.89+ - GCC (or another C compiler of your choice) - OpenSSL development libraries - Protobuf Compiler (`protoc`) @@ -81,25 +81,31 @@ git submodule update --init --recursive Your build environment should now be ready to use. -### Building the Binary +### Building the CLI -To build the binary, run: +To build the CLI, run: ``` -just build-bin +cargo build --release --bin commit-boost-cli ``` -This will create a binary in `build//`, for example `build/206658b/linux_amd64/`. Confirm that it works: +This will create a binary in `./target/release/commit-boost-cli`. Confirm that it works: ``` -./build///commit-boost --version +./target/release/commit-boost-cli --version ``` You can now use this to generate the Docker Compose file to drive the other modules if desired. See the [configuration](./configuration.md) guide for more information. -### Verifying the PBS Service +### Building the PBS Module -To verify the PBS service works, create [a TOML configuration](./configuration.md) for the PBS module (e.g., `cb-config.toml`). +To build PBS, run: + +``` +cargo build --release --bin commit-boost-pbs +``` + +This will create a binary in `./target/release/commit-boost-pbs`. To verify it works, create [a TOML configuration](./configuration.md) for the PBS module (e.g., `cb-config.toml`). As a quick example, we'll use this configuration that connects to the Flashbots relay on the Hoodi network: @@ -128,7 +134,7 @@ secrets_path = "/tmp/secrets" Set the path to it in the `CB_CONFIG` environment variable and run the binary: ``` -CB_CONFIG=cb-config.toml ./build///commit-boost pbs +CB_CONFIG=cb-config.toml ./target/release/commit-boost-pbs ``` If it works, you should see output like this: @@ -140,11 +146,17 @@ If it works, you should see output like this: 2025-05-07T21:09:17.896196Z INFO : relay check successful method=/eth/v1/builder/status req_id=5c405c33-0496-42ea-a35d-a7a01dbba356 ``` -If you do, then the PBS service works. +If you do, then the binary works. -### Verifying the Signer Module +### Building the Signer Module + +To build the Signer, run: + +``` +cargo build --release --bin commit-boost-signer +``` -To verify the Signer service works, create [a TOML configuration](./configuration.md) for the Signer module (e.g., `cb-config.toml`). We'll use the example in the PBS section above. +This will create a binary in `./target/release/commit-boost-signer`. To verify it works, create [a TOML configuration](./configuration.md) for the Signer module (e.g., `cb-config.toml`). We'll use the example in the PBS build section above. The signer needs the following environment variables set: @@ -155,7 +167,7 @@ Set these values, create the `keys` and `secrets` directories listed in the conf ``` mkdir -p /tmp/keys && mkdir -p /tmp/secrets -CB_CONFIG=cb-config.toml CB_JWTS="test_jwts=dummy" ./build///commit-boost signer +CB_CONFIG=cb-config.toml CB_JWTS="test_jwts=dummy" ./target/release/commit-boost-signer ``` You should see output like this: diff --git a/docs/docs/get_started/configuration.md b/docs/docs/get_started/configuration.md index 7eefb277..9764f821 100644 --- a/docs/docs/get_started/configuration.md +++ b/docs/docs/get_started/configuration.md @@ -358,70 +358,6 @@ Delegation signatures will be stored in files with the format `/deleg A full example of a config file with Dirk can be found [here](https://github.com/Commit-Boost/commit-boost-client/blob/main/examples/configs/dirk_signer.toml). - -### TLS - -By default, the Signer service runs in **insecure** mode, so its API service uses HTTP without any TLS encryption. This is sufficient for testing or if you're running locally within your machine's isolated Docker network and only intend to access it within the confines of your machine. However, for larger production setups, it's recommended to enable TLS - especially for traffic that spans across multiple machines. - -The Signer service in TLS mode supports **TLS 1.2** and **TLS 1.3**. Older protocol versions are not supported. - -To enable TLS, you must first create a **certificate / key pair**. We **strongly advise** using a well-known Certificate Authority to create and sign the certificate, such as [Let's Encrypt](https://letsencrypt.org/getting-started/) (a free service) or [Bluehost](https://www.bluehost.com/help/article/how-to-set-up-an-ssl-certificate-for-website-security) (free but requires an account). We do not recommend using a self-signed ceriticate / key pair for production environments. - -When configuring TLS support, the Signer service expects a single folder (which you can specify) that contains the following two files: -- `cert.pem`: The SSL certificate file signed by a certificate authority, in PEM format -- `key.pem`: The private key corresponding to `cert.pem` that will be used for signing TLS traffic, in PEM format - -Specifying it is done within Commit-Boost's configuration file using the `[signer.tls_mode]` table as follows: - -```toml -[pbs] -... -with_signer = true - -[signer] -port = 20000 -... - -[signer.tls_mode] -type = "certificate" -path = "path/to/your/cert/folder" -``` - -Where `path` is the aforementioned folder. It defaults to `./certs` but can be replaced with whichever directory your certificate and private key file reside in, as long as they're readable by the Signer service (or its Docker container, if using Docker). - -### Rate limit - -The Signer service implements a rate limit system of 3 failed authentications every 5 minutes. These values can be modified in the config file: - -```toml -[signer] -... -jwt_auth_fail_limit = 3 # The amount of failed requests allowed -jwt_auth_fail_timeout_seconds = 300 # The time window in seconds -``` - -The rate limit is applied to the IP address of the client making the request. By default, the IP is extracted directly from the TCP connection. If you're running the Signer service behind a reverse proxy (e.g. Nginx), you can configure it to extract the IP from a custom HTTP header instead. There're two options: - -- unique: Provides an HTTP header that contains the IP. This header is expected to appear only once in the request. This is common when using `X-Real-IP`, `True-Client-IP`, etc. If a request has multiple values for this header, it will be considered invalid and rejected. -- `rightmost`: Provides an HTTP header that contains a comma-separated list of IPs. The nth rightmost IP in the list is used. If the header appears multiple times, the last occurrence is used. This is common when using `X-Forwarded-For`. - -Examples: - -```toml -[signer.reverse_proxy] -type = "unique" -header = "X-Real-IP" -``` - -```toml -[signer.reverse_proxy] -type = "rightmost" -header = "X-Forwarded-For" -trusted_count = 1 -``` - -Note: `trusted_count` is the number of trusted proxies in front of the Signer service, but the last proxy won't add its address, so the number of skipped IPs is `trusted_count - 1`. See [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For#trusted_proxy_count) for more info. - ## Custom module We currently provide a test module that needs to be built locally. To build the module run: @@ -460,18 +396,16 @@ enabled = true id = "DA_COMMIT" type = "commit" docker_image = "test_da_commit" -signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" sleep_secs = 5 ``` A few things to note: - We now added a `signer` section which will be used to create the Signer module. -- There is now a `[[modules]]` section which at a minimum needs to specify the module `id`, `type` and `docker_image`. For modules with type `commit`, which will be used to access the Signer service and request signatures for preconfs, you will also need to specify the module's unique `signing_id` (see [the propser commitment documentation](../developing/prop-commit-signing.md)). Additional parameters needed for the business logic of the module will also be here. +- There is now a `[[modules]]` section which at a minimum needs to specify the module `id`, `type` and `docker_image`. Additional parameters needed for the business logic of the module will also be here, To learn more about developing modules, check out [here](/category/developing). - ## Vouch [Vouch](https://github.com/attestantio/vouch) is a multi-node validator client built by [Attestant](https://www.attestant.io/). Vouch is particular in that it also integrates an MEV-Boost client to interact with relays. The Commit-Boost PBS module is compatible with the Vouch `blockrelay` since it implements the same Builder-API as relays. For example, depending on your setup and preference, you may want to fetch headers from a given relay using Commit-Boost vs using the built-in Vouch `blockrelay`. @@ -518,42 +452,6 @@ Commit-Boost supports hot-reloading the configuration file. This means that you docker compose -f cb.docker-compose.yml exec cb_signer curl -X POST http://localhost:20000/reload ``` -### Signer module reload - -When the signer receives a reload request it re-reads the configuration file and environment variables, rebuilding its internal state to match: - -- **New modules** added to the config are registered with the signer. -- **Removed modules** are dropped from the signer's access list. -- **JWT secrets and admin secret** are reset to their current values from the environment variables. -- Any runtime changes from previous `/revoke_jwt` or `/reload` calls are reverted. - -The request body accepts 2 optional override parameters, applied on top of the config: - -- `jwt_secrets`: a comma-separated list of `=` pairs to override specific module secrets. Only modules present in the config can be overridden. -- `admin_secret`: a string to override the admin JWT secret. - -If the body is empty, the signer state is simply synced to match the config. - -#### Common patterns - -**Add a new module without restarting:** -1. Add the `[[modules]]` entry to `cb-config.toml`. -2. Set the new module's JWT secret in the signer's environment (`CB_SIGNER_JWT_SECRETS`). -3. Send `POST /reload` with an empty body. The signer picks up the new module from config. -4. Start the new module container with the matching JWT secret. - -**Rotate a JWT secret remotely:** -Send `POST /reload` with the new secret in the body. The module must already exist in the config. This is useful for scripted rotation without SSH access to edit config files. - -**Revoke a compromised module immediately:** -Send `POST /revoke_jwt` with the module ID. This removes the module from the signer's access list without touching the config. The next `/reload` will restore the module if it is still in the config, so remove it from config as well if the revocation should be permanent. - -#### Footguns - -- **Body overrides are not persisted.** If the signer crashes or restarts after a body-based secret rotation, it falls back to the config/environment values. The module container will still have the rotated secret and authentication will fail. To avoid this, update the environment variable to match after rotating via the body. -- **Reload reverts revocations.** If you revoke a module with `/revoke_jwt` but leave it in the config, the next `/reload` without a body override will re-add it from the config baseline. Always remove revoked modules from the config to make the revocation permanent. -- **Override validation is strict.** If the body references a module ID that does not exist in the config, the entire reload request is rejected and no changes are applied. This prevents typos from silently failing. - ### Notes - The hot reload feature is available for PBS modules (both default and custom) and signer module. diff --git a/docs/docs/get_started/overview.md b/docs/docs/get_started/overview.md index b5719567..e5209f09 100644 --- a/docs/docs/get_started/overview.md +++ b/docs/docs/get_started/overview.md @@ -7,14 +7,14 @@ description: Initial setup Commit-Boost is primarily based on [Docker](https://www.docker.com/) to enable modularity, sandboxing and cross-platform compatibility. It is also possible to run Commit-Boost [natively](/get_started/running/binary) without Docker. Each component roughly maps to a container: from a single `.toml` config file, the node operator can specify which modules they want to run, and Commit-Boost takes care of spinning up the services and creating links between them. -Commit-Boost ships with two core services: +Commit-Boost ships with two core modules: - A PBS module which implements the [BuilderAPI](https://ethereum.github.io/builder-specs/) for [MEV Boost](https://docs.flashbots.net/flashbots-mev-boost/architecture-overview/specifications). - A signer module, which implements the [Signer API](/api) and provides the interface for modules to request proposer commitments. ## Setup -The Commit-Boost program can create a dynamic `docker-compose` file, with services and ports already set up. +The Commit-Boost CLI creates a dynamic `docker-compose` file, with services and ports already set up. Whether you're using Docker or running the binaries natively, you can compile from source directly from the repo, or download binaries and fetch docker images from the official releases. @@ -22,13 +22,13 @@ Whether you're using Docker or running the binaries natively, you can compile fr Find the latest releases at https://github.com/Commit-Boost/commit-boost-client/releases. -The services are also published at [each release](https://github.com/orgs/Commit-Boost/packages?repo_name=commit-boost-client). +The modules are also published at [each release](https://github.com/orgs/Commit-Boost/packages?repo_name=commit-boost-client). ### From source Requirements: -- Rust 1.91 +- Rust 1.89 :::note Run `rustup update` to update Rust and Cargo to the latest version @@ -49,24 +49,35 @@ git submodule update --init --recursive If you get an `openssl` related error try running: `apt-get update && apt-get install -y openssl ca-certificates libssl3 libssl-dev build-essential pkg-config` ::: -Now, build the binary, which will be stored in `build//`, for example `build/206658b/linux_amd64/`: +### Docker + +You will need to build the CLI to create the `docker-compose` file: ```bash -just build-bin $(git rev-parse --short HEAD) +# Build the CLI +cargo build --release --bin commit-boost-cli + +# Check that it works +./target/release/commit-boost-cli --version ``` -You can confirm the binary was built successfully by navigating to the build directory and checking its version: +and the modules as Docker images: + ```bash -./commit-boost --version +docker build -t commitboost_pbs_default . -f ./provisioning/pbs.Dockerfile +docker build -t commitboost_signer . -f ./provisioning/signer.Dockerfile ``` -### Docker +This will create two local images called `commitboost_pbs_default` and `commitboost_signer` for the Pbs and Signer module respectively. Make sure to use these images in the `docker_image` field in the `[pbs]` and `[signer]` sections of the `.toml` config file, respectively. + +### Binaries -Building the service images requires the binary to be built using the above instructions first, since it will be copied into those images. Once it's built, create the images with the following: +Alternatively, you can also build the modules from source and run them without Docker, in which case you can skip the CLI and only compile the modules: ```bash -just build-pbs-img $(git rev-parse --short HEAD) -just build-signer-img $(git rev-parse --short HEAD) -``` +# Build the PBS module +cargo build --release --bin commit-boost-pbs -This will create two local images called `commit_boost/pbs:` and `commit_boost/signer:` for the PBS and Signer services respectively. Make sure to use these images in the `docker_image` field in the `[pbs]` and `[signer]` sections of the `.toml` config file, respectively. +# Build the Signer module +cargo build --release --bin commit-boost-signer +``` diff --git a/docs/docs/get_started/running/binary.md b/docs/docs/get_started/running/binary.md index 8f51fe65..10815d6e 100644 --- a/docs/docs/get_started/running/binary.md +++ b/docs/docs/get_started/running/binary.md @@ -28,9 +28,7 @@ Modules need some environment variables to work correctly. ### Signer Module -- `CB_SIGNER_ADMIN_JWT`: secret to use for admin JWT. - `CB_SIGNER_ENDPOINT`: optional, override to specify the `IP:port` endpoint to bind the signer server to. -- `CB_SIGNER_TLS_CERTIFICATES`: path to the TLS certificates for the server. - For loading keys we currently support: - `CB_SIGNER_LOADER_FILE`: path to a `.json` with plaintext keys (for testing purposes only). - `CB_SIGNER_LOADER_FORMAT`, `CB_SIGNER_LOADER_KEYS_DIR` and `CB_SIGNER_LOADER_SECRETS_DIR`: paths to the `keys` and `secrets` directories or files (ERC-2335 style keystores, see [Signer config](../configuration/#signer-module) for more info). @@ -59,7 +57,7 @@ Modules might also have additional envs required, which should be detailed by th After creating the `cb-config.toml` file, setup the required envs and run the binary. For example: ```bash -CB_CONFIG=./cb-config.toml commit-boost pbs +CB_CONFIG=./cb-config.toml commit-boost-pbs ``` ## Security diff --git a/docs/docs/get_started/running/docker.md b/docs/docs/get_started/running/docker.md index 89465a44..396fdede 100644 --- a/docs/docs/get_started/running/docker.md +++ b/docs/docs/get_started/running/docker.md @@ -3,13 +3,13 @@ description: Run Commit-Boost with Docker --- # Docker -The Commit-Boost program generates a dynamic `docker-compose.yml` file using the provided `.toml` config file. This is the recommended approach as Docker provides sandboxing of the containers from the rest of your system. +The Commit-Boost CLI generates a dynamic `docker-compose.yml` file using the provided `.toml` config file. This is the recommended approach as Docker provides sandboxing of the containers from the rest of your system. ## Init First run: ```bash -commit-boost init --config cb-config.toml +commit-boost-cli init --config cb-config.toml ``` This will create up to three files: - `cb.docker-compose.yml` which contains the full setup of the Commit-Boost services. @@ -73,9 +73,9 @@ Note that there are many more parameters that Commit-Boost supports, but they ar The relays here are placeholder for the sake of the example; for a list of actual relays, visit [the EthStaker relay list](https://github.com/eth-educators/ethstaker-guides/blob/main/MEV-relay-list.md). -### Commit-Boost Init Output +### Commit-Boost CLI Output -Run `commit-boost init --config cb-config.toml` with the above configuration, the program will produce the following Docker Compose file: +Run `commit-boost-cli init --config cb-config.toml` with the above configuration, the CLI will produce the following Docker Compose file: ``` services: @@ -102,14 +102,14 @@ This will run the PBS service in a container named `cb_pbs`. ### Configuration File Volume -The program creates a read-only volume binding for the config file, which the PBS service needs to run. The Docker compose file that it creates with the `init` command, `cb.docker-compose.yml`, will be placed into your current working directory when you run the program. The volume source will be specified as a *relative path* to that working directory, so it's ideal if the config file is directly within your working directory (or a subdirectory). If you need to specify an absolute path for the config file, you can adjust the `volumes` entry within the Docker compose file manually after its creation. +The CLI creates a read-only volume binding for the config file, which the PBS service needs to run. The Docker compose file that it creates with the `init` command, `cb.docker-compose.yml`, will be placed into your current working directory when you run the CLI. The volume source will be specified as a *relative path* to that working directory, so it's ideal if the config file is directly within your working directory (or a subdirectory). If you need to specify an absolute path for the config file, you can adjust the `volumes` entry within the Docker compose file manually after its creation. Since this is a volume, the PBS service container will reload the file from disk any time it's restarted. That means you can change the file any time after the Docker compose file is created to tweak PBS's parameters, but it also means the config file must stay in the same location; if you move it, the PBS container won't be able to mount it anymore and fail to start unless you manually adjust the volume's source location. ### Networking -The program will force the PBS service to bind to `0.0.0.0` within Docker's internal network so other Docker containers can access it, but it will only expose the API port (default `18550`) to `127.0.0.1` on your host machine. That way any processes running on the same machine can access it on that port. If you want to open the port for access across your entire network, not just your local machine, you can add the line: +The CLI will force the PBS service to bind to `0.0.0.0` within Docker's internal network so other Docker containers can access it, but it will only expose the API port (default `18550`) to `127.0.0.1` on your host machine. That way any processes running on the same machine can access it on that port. If you want to open the port for access across your entire network, not just your local machine, you can add the line: ``` host = "0.0.0.0" @@ -124,7 +124,7 @@ to the `[pbs]` section in the configuration. This will cause the resulting `port though you will need to add an entry to your local machine's firewall software (if applicable) for other machines to access it. -Currently, the program will always export the PBS service's API port in one of these two ways. If you don't want to expose it at all, so it can only be accessed by other Docker containers running within Docker's internal network, you will need to manually remove the `ports` entry from the Docker compose file after it's been created: +Currently, the CLI will always export the PBS service's API port in one of these two ways. If you don't want to expose it at all, so it can only be accessed by other Docker containers running within Docker's internal network, you will need to manually remove the `ports` entry from the Docker compose file after it's been created: ``` ports: [] @@ -177,9 +177,9 @@ The relays here are placeholder for the sake of the example; for a list of actua In this scenario there are two folders in the same directory as the configuration file (the working directory): `keys` and `secrets`. These correspond to the folders containing the [EIP-2335 keystores](../configuration.md#local-signer) and secrets in Lighthouse format. For your own keys, adjust the `format` parameter within the configuration and directory paths accordingly. -### Commit-Boost Init Output +### Commit-Boost CLI Output -Run `commit-boost init --config cb-config.toml` with the above configuration, the program will produce two files: +Run `commit-boost-cli init --config cb-config.toml` with the above configuration, the CLI will produce two files: - `cb.docker-compose.yml` - `.cb.env` @@ -261,7 +261,7 @@ CB_JWT_DA_COMMIT=mwDSSr7chwy9eFf7RhedBoyBtrwFUjSQ CB_JWTS=DA_COMMIT=mwDSSr7chwy9eFf7RhedBoyBtrwFUjSQ ``` -The Signer service needs JWT authentication from each of its modules. The program creates these and embeds them into the containers via environment variables automatically for convenience. This is demonstrated for the Signer module within the `environment` compose block: the `CB_JWTS: ${CB_JWTS}` forwards the `CB_JWTS` environment variable that's present when running Docker compose. The program requests that you do so via the command `docker compose --env-file "./.cb.env" -f "./cb.docker-compose.yml" up -d`; the `--env-file "./.cb.env"` handles loading the program's JWT output into this environment variable. +The Signer service needs JWT authentication from each of its modules. The CLI creates these and embeds them into the containers via environment variables automatically for convenience. This is demonstrated for the Signer module within the `environment` compose block: the `CB_JWTS: ${CB_JWTS}` forwards the `CB_JWTS` environment variable that's present when running Docker compose. The CLI requests that you do so via the command `docker compose --env-file "./.cb.env" -f "./cb.docker-compose.yml" up -d`; the `--env-file "./.cb.env"` handles loading the CLI's JWT output into this environment variable. Similarly, for the `cb_da_commit` module, the `CB_SIGNER_JWT: ${CB_JWT_DA_COMMIT}` line within its `environment` block will set the JWT that it should use to authenticate with the Signer service. @@ -273,7 +273,7 @@ As with the PBS-only example, the configuration file is placed into a read-only ### Networking -The program will force both the PBS and Signer API endpoints to bind to `0.0.0.0` within Docker's internal network so other Docker containers can access them, but it will only expose the API port (default `18550` for PBS and `20000` for the Signer) to `127.0.0.1` on your host machine. That way any processes running on the same machine can access them on their respective ports. If you want to open the ports for access across your entire network, not just your local machine, you can add the line: +The CLI will force both the PBS and Signer API endpoints to bind to `0.0.0.0` within Docker's internal network so other Docker containers can access them, but it will only expose the API port (default `18550` for PBS and `20000` for the Signer) to `127.0.0.1` on your host machine. That way any processes running on the same machine can access them on their respective ports. If you want to open the ports for access across your entire network, not just your local machine, you can add the line: ``` host = "0.0.0.0" @@ -296,7 +296,7 @@ to both the `[pbs]` and `[signer]` sections in the configuration. This will caus though you will need to add entries to your local machine's firewall software (if applicable) for other machines to access them. -Currently, the program will always export the PBS and Signer services' API ports in one of these two ways. If you don't want to expose them at all, so they can only be accessed by other Docker containers running within Docker's internal network, you will need to manually remove the `ports` entries from the Docker compose files after they've been created: +Currently, the CLI will always export the PBS and Signer services' API ports in one of these two ways. If you don't want to expose them at all, so they can only be accessed by other Docker containers running within Docker's internal network, you will need to manually remove the `ports` entries from the Docker compose files after they've been created: ``` ports: [] diff --git a/docs/docs/get_started/running/metrics.md b/docs/docs/get_started/running/metrics.md index 58200195..38e8534b 100644 --- a/docs/docs/get_started/running/metrics.md +++ b/docs/docs/get_started/running/metrics.md @@ -12,7 +12,7 @@ Make sure to add the `[metrics]` section to your config file: [metrics] enabled = true ``` -If the section is missing, metrics collection will be disabled. If you generated the `docker-compose.yml` file with `commit-boost init`, metrics ports will be automatically configured, and a sample `target.json` file will be created. If you're running the binaries directly, you will need to set the correct environment variables, as described in the [previous section](/get_started/running/binary#common). +If the section is missing, metrics collection will be disabled. If you generated the `docker-compose.yml` file with `commit-boost-cli`, metrics ports will be automatically configured, and a sample `target.json` file will be created. If you're running the binaries directly, you will need to set the correct environment variables, as described in the [previous section](/get_started/running/binary#common). ## Example setup diff --git a/docs/docs/get_started/troubleshooting.md b/docs/docs/get_started/troubleshooting.md index bb8623b8..7dc9f648 100644 --- a/docs/docs/get_started/troubleshooting.md +++ b/docs/docs/get_started/troubleshooting.md @@ -4,7 +4,7 @@ description: Common issues # Troubleshooting -Commit-Boost was recently audited and going through a phased approach for validators to move to production. If you find any or have any question, please reach out on [X (Twitter)](https://x.com/Commit_Boost). If there are any security related items, please see [here](https://github.com/Commit-Boost/commit-boost-client/blob/main/SECURITY.md). +Commit-Boost was recently audited and going through a phased approach for validators to move to production. If you find any or have any question, please reach out on [X (Twitter)](https://x.com/Commit_Boost) or [Telegram](https://t.me/+Pcs9bykxK3BiMzk5). If there are any security related items, please see [here](https://github.com/Commit-Boost/commit-boost-client/blob/main/SECURITY.md). If you started the modules correctly you should see the following logs. diff --git a/docs/docs/res/img/prop_commit_tree.png b/docs/docs/res/img/prop_commit_tree.png deleted file mode 100644 index 2c0b1815..00000000 Binary files a/docs/docs/res/img/prop_commit_tree.png and /dev/null differ diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 8b100e2f..f78ae69a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -99,6 +99,10 @@ const config = { label: 'X (Twitter)', href: 'https://x.com/Commit_Boost', }, + { + label: 'Telegram', + href: 'https://t.me/+Pcs9bykxK3BiMzk5', + }, ], }, ], diff --git a/docs/sidebars.js b/docs/sidebars.js index 7b3fc68d..cba043c2 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -82,6 +82,11 @@ const sidebars = { label: 'X (Twitter)', href: 'https://x.com/Commit_Boost', }, + { + type: 'link', + label: 'Telegram', + href: 'https://t.me/+Pcs9bykxK3BiMzk5', + }, ], // But you can create a sidebar manually diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 646ace45..27b5ce86 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use alloy::primitives::{Address, B256, b256}; +use alloy::primitives::Address; use commit_boost::prelude::*; use eyre::{OptionExt, Result}; use lazy_static::lazy_static; @@ -9,13 +9,6 @@ use serde::Deserialize; use tokio::time::sleep; use tracing::{error, info}; -// This is the signing ID used for the DA Commit module. -// Signatures produced by the signer service will incorporate this ID as part of -// the signature, preventing other modules from using the same signature for -// different purposes. -pub const DA_COMMIT_SIGNING_ID: B256 = - b256!("0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b"); - // You can define custom metrics and a custom registry for the business logic of // your module. These will be automatically scaped by the Prometheus server lazy_static! { @@ -32,7 +25,6 @@ struct Datagram { struct DaCommitService { config: StartCommitModuleConfig, - nonce: u64, } // Extra configurations parameters can be set here and will be automatically @@ -92,65 +84,26 @@ impl DaCommitService { ) -> Result<()> { let datagram = Datagram { data }; - // Request a signature directly from a BLS key - let request = SignConsensusRequest::builder(pubkey.clone()).with_msg(&datagram); - let response = self.config.signer_client.request_consensus_signature(request).await?; - info!("Proposer commitment (consensus): {}", response.signature); - if verify_proposer_commitment_signature_bls( - self.config.chain, - &pubkey, - &datagram, - &response.signature, - &DA_COMMIT_SIGNING_ID, - self.nonce, - ) { - info!("Signature verified successfully"); - } else { - error!("Signature verification failed"); - } - self.nonce += 1; + let request = SignConsensusRequest::builder(pubkey).with_msg(&datagram); + let signature = self.config.signer_client.request_consensus_signature(request).await?; + + info!("Proposer commitment (consensus): {}", signature); - // Request a signature from a proxy BLS key - let proxy_request_bls = SignProxyRequest::builder(proxy_bls.clone()).with_msg(&datagram); - let proxy_response_bls = + let proxy_request_bls = SignProxyRequest::builder(proxy_bls).with_msg(&datagram); + let proxy_signature_bls = self.config.signer_client.request_proxy_signature_bls(proxy_request_bls).await?; - info!("Proposer commitment (proxy BLS): {}", proxy_response_bls.signature); - if verify_proposer_commitment_signature_bls( - self.config.chain, - &proxy_bls, - &datagram, - &proxy_response_bls.signature, - &DA_COMMIT_SIGNING_ID, - self.nonce, - ) { - info!("Signature verified successfully"); - } else { - error!("Signature verification failed"); - } - self.nonce += 1; - // If ECDSA keys are enabled, request a signature from a proxy ECDSA key + info!("Proposer commitment (proxy BLS): {}", proxy_signature_bls); + if let Some(proxy_ecdsa) = proxy_ecdsa { let proxy_request_ecdsa = SignProxyRequest::builder(proxy_ecdsa).with_msg(&datagram); - let proxy_response_ecdsa = self + let proxy_signature_ecdsa = self .config .signer_client .request_proxy_signature_ecdsa(proxy_request_ecdsa) .await?; - info!("Proposer commitment (proxy ECDSA): {}", proxy_response_ecdsa.signature); - match verify_proposer_commitment_signature_ecdsa( - self.config.chain, - &proxy_ecdsa, - &datagram, - &proxy_response_ecdsa.signature, - &DA_COMMIT_SIGNING_ID, - self.nonce, - ) { - Ok(_) => info!("Signature verified successfully"), - Err(err) => error!(%err, "Signature verification failed"), - }; + info!("Proposer commitment (proxy ECDSA): {}", proxy_signature_ecdsa); } - self.nonce += 1; SIG_RECEIVED_COUNTER.inc(); @@ -178,7 +131,7 @@ async fn main() -> Result<()> { "Starting module with custom data" ); - let mut service = DaCommitService { config, nonce: 0 }; + let mut service = DaCommitService { config }; if let Err(err) = service.run().await { error!(%err, "Service failed"); diff --git a/examples/status_api/src/main.rs b/examples/status_api/src/main.rs index aa65f4d6..7ad9b533 100644 --- a/examples/status_api/src/main.rs +++ b/examples/status_api/src/main.rs @@ -1,9 +1,6 @@ -use std::{ - path::PathBuf, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, +use std::sync::{ + Arc, + atomic::{AtomicU64, Ordering}, }; use async_trait::async_trait; @@ -73,8 +70,7 @@ impl BuilderApi for MyBuilderApi { let mut data = state.data.clone(); data.inc_amount = extra_config.inc_amount; - let empty_config_path = PathBuf::new(); - Ok(PbsState::new(pbs_config, empty_config_path).with_data(data)) + Ok(PbsState::new(pbs_config).with_data(data)) } fn extra_routes() -> Option>> { @@ -95,11 +91,10 @@ async fn main() -> Result<()> { let (pbs_config, extra) = load_pbs_custom_config::().await?; let chain = pbs_config.chain; - let _guard = initialize_tracing_log(PBS_SERVICE_NAME, LogsSettings::from_env_config()?)?; + let _guard = initialize_tracing_log(PBS_MODULE_NAME, LogsSettings::from_env_config()?)?; let custom_state = MyBuilderState::from_config(extra); - let empty_config_path = PathBuf::new(); - let state = PbsState::new(pbs_config, empty_config_path).with_data(custom_state); + let state = PbsState::new(pbs_config).with_data(custom_state); PbsService::register_metric(Box::new(CHECK_RECEIVED_COUNTER.clone())); PbsService::init_metrics(chain)?; diff --git a/justfile b/justfile index b4bd1b14..70475ad9 100644 --- a/justfile +++ b/justfile @@ -11,31 +11,28 @@ fmt-check: clippy: cargo +{{toolchain}} clippy --all-features --no-deps -- -D warnings -# Everything needed to run before pushing -checklist: - cargo check - just fmt - just clippy - just test - cargo audit - # =================================== # === Build Commands for Services === # =================================== [doc(""" - Builds the commit-boost binary to './build/'. + Builds the commit-boost-cli binary to './build/'. """)] -build-bin version: \ - (_docker-build-binary version "commit-boost") +build-cli version: \ + (_docker-build-binary version "cli") [doc(""" - Builds amd64 and arm64 binaries for the commit-boost crate to './build//', where '' is the - OS / arch platform of the binary (linux_amd64 and linux_arm64). - Used when creating the pbs Docker image. + Builds amd64 and arm64 binaries for the commit-boost-cli crate to './build//', where '' is + the OS / arch platform of the binary (linux_amd64 and linux_arm64). +""")] +build-cli-multiarch version: \ + (_docker-build-binary-multiarch version "cli") + +[doc(""" + Builds the commit-boost-pbs binary to './build/'. """)] -build-bin-multiarch version: \ - (_docker-build-binary-multiarch version "commit-boost") +build-pbs-bin version: \ + (_docker-build-binary version "pbs") [doc(""" Creates a Docker image named 'commit-boost/pbs:' and loads it to the local Docker repository. @@ -46,12 +43,20 @@ build-pbs-img version: \ (_docker-build-image version "pbs") [doc(""" - Builds the commit-boost binary to './build/' and creates a Docker image named 'commit-boost/pbs:'. + Builds the commit-boost-pbs binary to './build/' and creates a Docker image named 'commit-boost/pbs:'. """)] build-pbs version: \ - (build-bin version) \ + (build-pbs-bin version) \ (build-pbs-img version) +[doc(""" + Builds amd64 and arm64 binaries for the commit-boost-pbs crate to './build//', where '' is the + OS / arch platform of the binary (linux_amd64 and linux_arm64). + Used when creating the pbs Docker image. +""")] +build-pbs-bin-multiarch version: \ + (_docker-build-binary-multiarch version "pbs") + [doc(""" Creates a multiarch Docker image manifest named 'commit-boost/pbs:' and pushes it to a custom Docker registry (such as '192.168.1.10:5000'). @@ -61,16 +66,22 @@ build-pbs-img-multiarch version local-docker-registry: \ (_docker-build-image-multiarch version "pbs" local-docker-registry) [doc(""" - Builds amd64 and arm64 binaries for the commit-boost crate to './build//', where '' is the + Builds amd64 and arm64 binaries for the commit-boost-pbs crate to './build//', where '' is the OS / arch platform of the binary (linux_amd64 and linux_arm64). Creates a multiarch Docker image manifest named 'commit-boost/pbs:' and pushes it to a custom Docker registry (such as '192.168.1.10:5000'). Used for testing multiarch images locally instead of using a public registry like GHCR or Docker Hub. """)] build-pbs-multiarch version local-docker-registry: \ - (build-bin-multiarch version) \ + (build-pbs-bin-multiarch version) \ (build-pbs-img-multiarch version local-docker-registry) +[doc(""" + Builds the commit-boost-signer binary to './build/'. +""")] +build-signer-bin version: \ + (_docker-build-binary version "signer") + [doc(""" Creates a Docker image named 'commit-boost/signer:' and loads it to the local Docker repository. Requires the binary to be built first, but this command won't build it automatically if you just need to build the @@ -80,12 +91,20 @@ build-signer-img version: \ (_docker-build-image version "signer") [doc(""" - Builds the commit-boost binary to './build/' and creates a Docker image named 'commit-boost/signer:'. + Builds the commit-boost-signer binary to './build/' and creates a Docker image named 'commit-boost/signer:'. """)] build-signer version: \ - (build-bin version) \ + (build-signer-bin version) \ (build-signer-img version) +[doc(""" + Builds amd64 and arm64 binaries for the commit-boost-signer crate to './build//', where '' is + the OS / arch platform of the binary (linux_amd64 and linux_arm64). + Used when creating the signer Docker image. +""")] +build-signer-bin-multiarch version: \ + (_docker-build-binary-multiarch version "signer") + [doc(""" Creates a multiarch Docker image manifest named 'commit-boost/signer:' and pushes it to a custom Docker registry (such as '192.168.1.10:5000'). @@ -95,14 +114,14 @@ build-signer-img-multiarch version local-docker-registry: \ (_docker-build-image-multiarch version "signer" local-docker-registry) [doc(""" - Builds amd64 and arm64 binaries for the commit-boost crate to './build//', where '' is + Builds amd64 and arm64 binaries for the commit-boost-signer crate to './build//', where '' is the OS / arch platform of the binary (linux_amd64 and linux_arm64). Creates a multiarch Docker image manifest named 'commit-boost/signer:' and pushes it to a custom Docker registry (such as '192.168.1.10:5000'). Used for testing multiarch images locally instead of using a public registry like GHCR or Docker Hub. """)] build-signer-multiarch version local-docker-registry: \ - (build-bin-multiarch version) \ + (build-signer-bin-multiarch version) \ (build-signer-img-multiarch version local-docker-registry) [doc(""" @@ -112,9 +131,9 @@ build-signer-multiarch version local-docker-registry: \ 'commit-boost/signer:'. """)] build-all version: \ - (build-bin version) \ - (build-pbs-img version) \ - (build-signer-img version) + (build-cli version) \ + (build-pbs version) \ + (build-signer version) [doc(""" Builds amd64 and arm64 flavors of the CLI, PBS, and Signer binaries and Docker images for the specified version. @@ -125,9 +144,9 @@ build-all version: \ Used for testing multiarch images locally instead of using a public registry like GHCR or Docker Hub. """)] build-all-multiarch version local-docker-registry: \ - (build-bin-multiarch version) \ - (build-pbs-img-multiarch version local-docker-registry) \ - (build-signer-img-multiarch version local-docker-registry) + (build-cli-multiarch version) \ + (build-pbs-multiarch version local-docker-registry) \ + (build-signer-multiarch version local-docker-registry) # =============================== # === Builder Implementations === @@ -140,7 +159,7 @@ _create-docker-builder: # Builds a binary for a specific crate and version _docker-build-binary version crate: _create-docker-builder export PLATFORM=$(docker buildx inspect --bootstrap | awk -F': ' '/Platforms/ {print $2}' | cut -d',' -f1 | xargs | tr '/' '_'); \ - docker buildx build --rm --platform=local -f provisioning/build.Dockerfile --output "build/{{version}}/$PLATFORM" --target output --build-arg TARGET_CRATE=commit-boost . + docker buildx build --rm --platform=local -f provisioning/build.Dockerfile --output "build/{{version}}/$PLATFORM" --target output --build-arg TARGET_CRATE=commit-boost-{{crate}} . # Builds a Docker image for a specific crate and version _docker-build-image version crate: _create-docker-builder @@ -148,7 +167,7 @@ _docker-build-image version crate: _create-docker-builder # Builds multiple binaries (for Linux amd64 and arm64 architectures) for a specific crate and version _docker-build-binary-multiarch version crate: _create-docker-builder - docker buildx build --rm --platform=linux/amd64,linux/arm64 -f provisioning/build.Dockerfile --output build/{{version}} --target output --build-arg TARGET_CRATE=commit-boost . + docker buildx build --rm --platform=linux/amd64,linux/arm64 -f provisioning/build.Dockerfile --output build/{{version}} --target output --build-arg TARGET_CRATE=commit-boost-{{crate}} . # Builds a multi-architecture (Linux amd64 and arm64) Docker manifest for a specific crate and version. # Uploads to the custom Docker registry (such as '192.168.1.10:5000') instead of a public registry like GHCR or Docker Hub. @@ -174,99 +193,3 @@ clean: # Runs the suite of tests for all commit-boost crates. test: cargo test --all-features - -# ===================== -# === Test Coverage === -# ===================== - -# Generate an HTML test coverage report and open it in the browser. -# Recompiles the workspace with LLVM coverage instrumentation, runs all tests, -# and writes the report to target/llvm-cov/html/index.html. -# Incremental recompilation works normally — no need to clean between runs. -# If results look wrong after upgrading cargo-llvm-cov, run `just coverage-clean` first. -# Requires: cargo install cargo-llvm-cov && rustup component add llvm-tools-preview -coverage: - cargo llvm-cov --all-features --html --open - -# Print a quick coverage summary to the terminal without opening a browser. -coverage-summary: - cargo llvm-cov --all-features --summary-only - -# Remove all coverage instrumentation artifacts produced by cargo-llvm-cov. -coverage-clean: - cargo llvm-cov clean --workspace - -# ======================= -# === Microbenchmarks === -# ======================= -# -# Development Loop: -# 1. Run the current bench: just bench dev -# 2. Update code -# 3. Re-run the bench, logging the diff from the last run: just bench dev - -# Regression Test: -# 1. Save a baseline on the main branch: just bench main -# 2. On a PR branch, compare against it: just bench-compare main - -[doc(""" - Install tools required by the bench-* commands. - - cargo-criterion: CLI runner for Criterion benchmarks with richer output - - critcmp: baseline diffing tool used by bench-compare -""")] -bench-install-tools: - cargo install cargo-criterion critcmp - -[doc(""" - Run microbenchmarks and save results as a named baseline. Example: just bench main - - Compares against the last benchmark run of any kind, not the previous save of - this baseline name. Useful for tracking incremental changes since your last run. - For accurate baseline comparisons, use bench-compare instead. -""")] -bench baseline: - cargo bench --package cb-bench-micro -- --save-baseline {{baseline}} - -[doc(""" - Run microbenchmarks, save results as "current", then diff against a named baseline. - Example: just bench-compare main -""")] -bench-compare baseline: - cargo bench --package cb-bench-micro -- --save-baseline current - critcmp {{baseline}} current - -# ================= -# === Kurtosis === -# ================= - -# Tear down and clean up all enclaves -kurtosis-clean: - kurtosis clean -a - -# Clean all enclaves and restart the testnet -kurtosis-restart: - just kurtosis-clean - kurtosis run github.com/ethpandaops/ethereum-package \ - --enclave CB-Testnet \ - --args-file provisioning/kurtosis-config.yml - -# Build local docker images and restart testnet -kurtosis-build: - just build-all kurtosis - just kurtosis-restart - -# Inspect running enclave -kurtosis-inspect: - kurtosis enclave inspect CB-Testnet - -# Tail logs for a specific service: just kurtosis-logs -kurtosis-logs service: - kurtosis service logs CB-Testnet {{service}} --follow - -# Shell into a specific service: just kurtosis-shell -kurtosis-shell service: - kurtosis service shell CB-Testnet {{service}} - -# Dump enclave state to disk for post-mortem -kurtosis-dump: - kurtosis enclave dump CB-Testnet ./kurtosis-dump diff --git a/provisioning/build.Dockerfile b/provisioning/build.Dockerfile index 21b42eb0..b28b62ff 100644 --- a/provisioning/build.Dockerfile +++ b/provisioning/build.Dockerfile @@ -1,5 +1,5 @@ # This will be the main build image -FROM --platform=${BUILDPLATFORM} rust:1.91-slim-bookworm AS chef +FROM --platform=${BUILDPLATFORM} rust:1.89-slim-bookworm AS chef ARG TARGETOS TARGETARCH BUILDPLATFORM TARGET_CRATE ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse WORKDIR /app diff --git a/provisioning/grafana/signer_public_dashboard.json b/provisioning/grafana/signer_public_dashboard.json index 4b904b1a..327d48bb 100644 --- a/provisioning/grafana/signer_public_dashboard.json +++ b/provisioning/grafana/signer_public_dashboard.json @@ -539,19 +539,13 @@ "list": [ { "current": { - "selected": true, - "text": "All", + "text": "$__all", "value": "$__all" }, "description": "SignerAPI endpoint", "includeAll": true, "name": "endpoint", "options": [ - { - "selected": true, - "text": "All", - "value": "$__all" - }, { "selected": false, "text": "get_pubkeys", @@ -564,21 +558,11 @@ }, { "selected": false, - "text": "request_signature_bls", - "value": "request_signature_bls" - }, - { - "selected": false, - "text": "request_signature_proxy_bls", - "value": "request_signature_proxy_bls" - }, - { - "selected": false, - "text": "request_signature_proxy_ecdsa", - "value": "request_signature_proxy_ecdsa" + "text": "request_signature", + "value": "request_signature" } ], - "query": "get_pubkeys, generate_proxy_key, request_signature_bls, request_signature_proxy_bls, request_signature_proxy_ecdsa", + "query": "get_pubkeys, generate_proxy_key, request_signature", "type": "custom" } ] diff --git a/provisioning/kurtosis-config.yml b/provisioning/kurtosis-config.yml deleted file mode 100644 index 093534b0..00000000 --- a/provisioning/kurtosis-config.yml +++ /dev/null @@ -1,104 +0,0 @@ -# ELs: geth, nethermind, erigon, besu, reth, ethrex -# CLs: nimbus, lighthouse, lodestar, teku, prysm, and grandine -participants: - - el_type: geth - cl_type: nimbus - - - el_type: nethermind - cl_type: lighthouse - - - el_type: erigon - cl_type: lodestar - - - el_type: besu - cl_type: teku - - - el_type: reth - cl_type: prysm - - - el_type: ethrex - cl_type: grandine - -additional_services: - - dora - - spamoor -mev_type: commit-boost - -mev_params: - mev_relay_image: ethpandaops/mev-boost-relay:main - mev_boost_image: commit-boost/pbs:kurtosis - mev_builder_cl_image: sigp/lighthouse:latest - mev_builder_image: ethpandaops/reth-rbuilder:develop - -network_params: - network: kurtosis - network_id: "3151908" - deposit_contract_address: "0x00000000219ab540356cBB839Cbe05303d7705Fa" - seconds_per_slot: 12 - slot_duration_ms: 12000 - num_validator_keys_per_node: 128 - preregistered_validator_keys_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" - preregistered_validator_count: 0 - additional_mnemonics: [] - genesis_delay: 20 - genesis_time: 0 - genesis_gaslimit: 60000000 - max_per_epoch_activation_churn_limit: 8 - churn_limit_quotient: 65536 - ejection_balance: 16000000000 - eth1_follow_distance: 2048 - min_validator_withdrawability_delay: 256 - shard_committee_period: 256 - attestation_due_bps_gloas: 2500 - aggregate_due_bps_gloas: 5000 - sync_message_due_bps_gloas: 2500 - contribution_due_bps_gloas: 5000 - payload_attestation_due_bps: 7500 - view_freeze_cutoff_bps: 7500 - inclusion_list_submission_due_bps: 6667 - proposer_inclusion_list_cutoff_bps: 9167 - deneb_fork_epoch: 0 - electra_fork_epoch: 0 - fulu_fork_epoch: 0 - gloas_fork_epoch: 18446744073709551615 - network_sync_base_url: https://snapshots.ethpandaops.io/ - force_snapshot_sync: false - data_column_sidecar_subnet_count: 128 - samples_per_slot: 8 - custody_requirement: 4 - max_blobs_per_block_electra: 9 - max_request_blocks_deneb: 128 - max_request_blob_sidecars_electra: 1152 - target_blobs_per_block_electra: 6 - base_fee_update_fraction_electra: 5007716 - additional_preloaded_contracts: {} - devnet_repo: ethpandaops - prefunded_accounts: {} - bpo_1_epoch: 0 - bpo_1_max_blobs: 15 - bpo_1_target_blobs: 10 - bpo_1_base_fee_update_fraction: 8346193 - bpo_2_epoch: 18446744073709551615 - bpo_2_max_blobs: 21 - bpo_2_target_blobs: 14 - bpo_2_base_fee_update_fraction: 11684671 - bpo_3_epoch: 18446744073709551615 - bpo_3_max_blobs: 0 - bpo_3_target_blobs: 0 - bpo_3_base_fee_update_fraction: 0 - bpo_4_epoch: 18446744073709551615 - bpo_4_max_blobs: 0 - bpo_4_target_blobs: 0 - bpo_4_base_fee_update_fraction: 0 - bpo_5_epoch: 18446744073709551615 - bpo_5_max_blobs: 0 - bpo_5_target_blobs: 0 - bpo_5_base_fee_update_fraction: 0 - withdrawal_type: "0x00" - withdrawal_address: "0x8943545177806ED17B9F23F0a21ee5948eCaa776" - validator_balance: 32 - min_epochs_for_data_column_sidecars_requests: 4096 - min_epochs_for_block_requests: 33024 \ No newline at end of file diff --git a/provisioning/pbs.Dockerfile b/provisioning/pbs.Dockerfile index 6a4c4646..6b9496ec 100644 --- a/provisioning/pbs.Dockerfile +++ b/provisioning/pbs.Dockerfile @@ -1,6 +1,6 @@ FROM debian:bookworm-slim ARG BINARIES_PATH TARGETOS TARGETARCH -COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost /usr/local/bin/commit-boost +COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost-pbs /usr/local/bin/commit-boost-pbs RUN apt-get update && apt-get install -y \ openssl \ ca-certificates \ @@ -16,5 +16,4 @@ RUN groupadd -g 10001 commitboost && \ useradd -u 10001 -g commitboost -s /sbin/nologin commitboost USER commitboost -ENTRYPOINT ["/usr/local/bin/commit-boost"] -CMD ["pbs"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/commit-boost-pbs"] \ No newline at end of file diff --git a/provisioning/pectra-config.yml b/provisioning/pectra-config.yml new file mode 100644 index 00000000..a78d55a2 --- /dev/null +++ b/provisioning/pectra-config.yml @@ -0,0 +1,56 @@ +participants: + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=pawanRocks"] + cl_type: lighthouse + cl_image: ethpandaops/lighthouse:unstable-95cec45 + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=TekuFromLocal"] + cl_type: teku + cl_image: consensys/teku:develop + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=lodestarFromLocal"] + cl_type: lodestar + cl_image: ethpandaops/lodestar:unstable-7982031 + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=prysmFromLocal"] + cl_type: prysm + cl_image: ethpandaops/prysm-beacon-chain:develop-910609a + vc_image: ethpandaops/prysm-validator:develop-910609a + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=nimbusFromLocal"] + cl_type: nimbus + cl_image: ethpandaops/nimbus-eth2:unstable-dec1cd3 + - el_type: geth + el_image: ethpandaops/geth:prague-devnet-6-c070db6 + el_extra_params: ["--miner.extradata=grandineFromLocal"] + cl_type: grandine + cl_image: ethpandaops/grandine:devnet5-7ba51a4 + +additional_services: + - dora + - tx_spammer + - spamoor_blob +mev_type: commit-boost + +mev_params: + mev_relay_image: jtraglia/mev-boost-relay:electra + mev_boost_image: commitboost_pbs_default # build this locally with scripts/build_local_images.sh + mev_builder_cl_image: ethpandaops/lighthouse:unstable-a1b7d61 + mev_builder_image: ethpandaops/reth-rbuilder:devnet6-fdeb4d6 + +network_params: + electra_fork_epoch: 1 + min_validator_withdrawability_delay: 1 + shard_committee_period: 1 + churn_limit_quotient: 16 + genesis_delay: 120 + +spamoor_blob_params: + throughput: 10 + max_blobs: 2 + max_pending: 40 \ No newline at end of file diff --git a/provisioning/signer.Dockerfile b/provisioning/signer.Dockerfile index d6c3d498..5ea619b2 100644 --- a/provisioning/signer.Dockerfile +++ b/provisioning/signer.Dockerfile @@ -1,6 +1,6 @@ FROM debian:bookworm-slim ARG BINARIES_PATH TARGETOS TARGETARCH -COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost /usr/local/bin/commit-boost +COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost-signer /usr/local/bin/commit-boost-signer RUN apt-get update && apt-get install -y \ openssl \ ca-certificates \ @@ -16,5 +16,4 @@ RUN groupadd -g 10001 commitboost && \ useradd -u 10001 -g commitboost -s /sbin/nologin commitboost USER commitboost -ENTRYPOINT ["/usr/local/bin/commit-boost"] -CMD ["signer"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/commit-boost-signer"] \ No newline at end of file diff --git a/tests/Cargo.toml b/tests/Cargo.toml index c1c51f58..5e8e1596 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -11,15 +11,11 @@ cb-common.workspace = true cb-pbs.workspace = true cb-signer.workspace = true eyre.workspace = true -jsonwebtoken.workspace = true lh_types.workspace = true -rcgen.workspace = true reqwest.workspace = true -serde.workspace = true serde_json.workspace = true tempfile.workspace = true tokio.workspace = true -toml.workspace = true tracing.workspace = true tracing-subscriber.workspace = true tracing-test.workspace = true @@ -27,5 +23,4 @@ tree_hash.workspace = true url.workspace = true [dev-dependencies] -tracing-test.workspace = true -cb-common = { path = "../crates/common", features = ["testing-flags"] } +cb-common = { path = "../crates/common", features = ["testing-flags"] } \ No newline at end of file diff --git a/tests/data/configs/signer.happy.toml b/tests/data/configs/signer.happy.toml deleted file mode 100644 index 6fb76445..00000000 --- a/tests/data/configs/signer.happy.toml +++ /dev/null @@ -1,52 +0,0 @@ -chain = "Hoodi" - -[pbs] -docker_image = "ghcr.io/commit-boost/pbs:latest" -with_signer = true -host = "127.0.0.1" -port = 18550 -relay_check = true -wait_all_registrations = true -timeout_get_header_ms = 950 -timeout_get_payload_ms = 4000 -timeout_register_validator_ms = 3000 -skip_sigverify = false -min_bid_eth = 0.5 -late_in_slot_time_ms = 2000 -extra_validation_enabled = false -rpc_url = "https://ethereum-holesky-rpc.publicnode.com" - -[[relays]] -id = "example-relay" -url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" -headers = { X-MyCustomHeader = "MyCustomHeader" } -enable_timing_games = false -target_first_request_ms = 200 -frequency_get_header_ms = 300 - -[signer] -docker_image = "ghcr.io/commit-boost/signer:latest" -host = "127.0.0.1" -port = 20000 -jwt_auth_fail_limit = 3 -jwt_auth_fail_timeout_seconds = 300 - -[signer.local.loader] -key_path = "./tests/data/keys.example.json" - -[signer.local.store] -proxy_dir = "./proxies" - -[[modules]] -id = "test-module" -signing_id = "0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b" -type = "commit" -docker_image = "test_da_commit" -env_file = ".cb.env" - -[[modules]] -id = "another-module" -signing_id = "0x61fe00135d7b4912a8c63ada215ac2e62326e6e7b30f49a29fcf9779d7ad800d" -type = "commit" -docker_image = "test_da_commit" -env_file = ".cb.env" diff --git a/tests/data/helder_spec.yml b/tests/data/helder_spec.yml new file mode 100644 index 00000000..7d3f9b04 --- /dev/null +++ b/tests/data/helder_spec.yml @@ -0,0 +1,150 @@ +# Extends the mainnet preset +PRESET_BASE: mainnet +CONFIG_NAME: testnet # needs to exist because of Prysm. Otherwise it conflicts with mainnet genesis + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 1000 +# Mar-01-2021 08:53:32 AM +UTC +# This is an invalid valid and should be updated when you create the genesis +MIN_GENESIS_TIME: 1718967600 +GENESIS_FORK_VERSION: 0x10000000 +GENESIS_DELAY: 60 + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x20000000 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x30000000 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x40000000 +CAPELLA_FORK_EPOCH: 0 + +# DENEB +DENEB_FORK_VERSION: 0x50132736 +DENEB_FORK_EPOCH: 0 + +# Electra +ELECTRA_FORK_VERSION: 0x60132736 +ELECTRA_FORK_EPOCH: 999999 + +FULU_FORK_EPOCH: 18446744073709551615 + +# EIP7594 - Peerdas +EIP7594_FORK_VERSION: 0x70132736 +EIP7594_FORK_EPOCH: 999999 + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 12 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 65536 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 7014190335 +DEPOSIT_NETWORK_ID: 7014190335 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 + +# Whisk +# `Epoch(2**8)` +WHISK_EPOCHS_PER_SHUFFLING_PHASE: 256 +# `Epoch(2)` +WHISK_PROPOSER_SELECTION_GAP: 2 + +# EIP7594 +NUMBER_OF_COLUMNS: 128 +MAX_CELLS_IN_EXTENDED_MATRIX: 768 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 1 +TARGET_NUMBER_OF_PEERS: 70 + +# [New in Electra:EIP7251] +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) diff --git a/tests/data/kurtosis_spec.json b/tests/data/kurtosis_spec.json deleted file mode 100644 index 9129f573..00000000 --- a/tests/data/kurtosis_spec.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "data": { - "CONFIG_NAME": "testnet", - "PRESET_BASE": "mainnet", - "TERMINAL_TOTAL_DIFFICULTY": "0", - "TERMINAL_BLOCK_HASH": "0x0000000000000000000000000000000000000000000000000000000000000000", - "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH": "18446744073709551615", - "MIN_GENESIS_ACTIVE_VALIDATOR_COUNT": "4", - "MIN_GENESIS_TIME": "1772130224", - "GENESIS_FORK_VERSION": "0x10000038", - "GENESIS_DELAY": "0", - "ALTAIR_FORK_VERSION": "0x20000038", - "ALTAIR_FORK_EPOCH": "0", - "BELLATRIX_FORK_VERSION": "0x30000038", - "BELLATRIX_FORK_EPOCH": "0", - "CAPELLA_FORK_VERSION": "0x40000038", - "CAPELLA_FORK_EPOCH": "0", - "DENEB_FORK_VERSION": "0x50000038", - "DENEB_FORK_EPOCH": "0", - "ELECTRA_FORK_VERSION": "0x60000038", - "ELECTRA_FORK_EPOCH": "0", - "FULU_FORK_VERSION": "0x70000038", - "FULU_FORK_EPOCH": "0", - "GLOAS_FORK_VERSION": "0x80000038", - "GLOAS_FORK_EPOCH": "18446744073709551615", - "SECONDS_PER_SLOT": "12", - "SECONDS_PER_ETH1_BLOCK": "12", - "MIN_VALIDATOR_WITHDRAWABILITY_DELAY": "256", - "SHARD_COMMITTEE_PERIOD": "256", - "ETH1_FOLLOW_DISTANCE": "2048", - "SUBNETS_PER_NODE": "2", - "INACTIVITY_SCORE_BIAS": "4", - "INACTIVITY_SCORE_RECOVERY_RATE": "16", - "EJECTION_BALANCE": "16000000000", - "MIN_PER_EPOCH_CHURN_LIMIT": "4", - "MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT": "8", - "CHURN_LIMIT_QUOTIENT": "65536", - "PROPOSER_SCORE_BOOST": "40", - "DEPOSIT_CHAIN_ID": "3151908", - "DEPOSIT_NETWORK_ID": "3151908", - "DEPOSIT_CONTRACT_ADDRESS": "0x00000000219ab540356cbb839cbe05303d7705fa", - "GAS_LIMIT_ADJUSTMENT_FACTOR": "1024", - "MAX_PAYLOAD_SIZE": "10485760", - "MAX_REQUEST_BLOCKS": "1024", - "MIN_EPOCHS_FOR_BLOCK_REQUESTS": "33024", - "TTFB_TIMEOUT": "5", - "RESP_TIMEOUT": "10", - "ATTESTATION_PROPAGATION_SLOT_RANGE": "32", - "MAXIMUM_GOSSIP_CLOCK_DISPARITY": "500", - "MESSAGE_DOMAIN_INVALID_SNAPPY": "0x00000000", - "MESSAGE_DOMAIN_VALID_SNAPPY": "0x01000000", - "ATTESTATION_SUBNET_PREFIX_BITS": "6", - "MAX_REQUEST_BLOCKS_DENEB": "128", - "MAX_REQUEST_BLOB_SIDECARS": "768", - "MAX_REQUEST_DATA_COLUMN_SIDECARS": "16384", - "MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS": "4096", - "BLOB_SIDECAR_SUBNET_COUNT": "6", - "MAX_BLOBS_PER_BLOCK": "6", - "MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA": "128000000000", - "MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT": "256000000000", - "MAX_BLOBS_PER_BLOCK_ELECTRA": "9", - "BLOB_SIDECAR_SUBNET_COUNT_ELECTRA": "9", - "MAX_REQUEST_BLOB_SIDECARS_ELECTRA": "1152", - "NUMBER_OF_CUSTODY_GROUPS": "128", - "DATA_COLUMN_SIDECAR_SUBNET_COUNT": "128", - "SAMPLES_PER_SLOT": "8", - "CUSTODY_REQUIREMENT": "4", - "BLOB_SCHEDULE": [ - { - "EPOCH": "0", - "MAX_BLOBS_PER_BLOCK": "15" - } - ], - "VALIDATOR_CUSTODY_REQUIREMENT": "8", - "BALANCE_PER_ADDITIONAL_CUSTODY_GROUP": "32000000000", - "MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS": "4096", - "MAX_COMMITTEES_PER_SLOT": "64", - "TARGET_COMMITTEE_SIZE": "128", - "MAX_VALIDATORS_PER_COMMITTEE": "2048", - "SHUFFLE_ROUND_COUNT": "90", - "HYSTERESIS_QUOTIENT": "4", - "HYSTERESIS_DOWNWARD_MULTIPLIER": "1", - "HYSTERESIS_UPWARD_MULTIPLIER": "5", - "MIN_DEPOSIT_AMOUNT": "1000000000", - "MAX_EFFECTIVE_BALANCE": "32000000000", - "EFFECTIVE_BALANCE_INCREMENT": "1000000000", - "MIN_ATTESTATION_INCLUSION_DELAY": "1", - "SLOTS_PER_EPOCH": "32", - "MIN_SEED_LOOKAHEAD": "1", - "MAX_SEED_LOOKAHEAD": "4", - "EPOCHS_PER_ETH1_VOTING_PERIOD": "64", - "SLOTS_PER_HISTORICAL_ROOT": "8192", - "MIN_EPOCHS_TO_INACTIVITY_PENALTY": "4", - "EPOCHS_PER_HISTORICAL_VECTOR": "65536", - "EPOCHS_PER_SLASHINGS_VECTOR": "8192", - "HISTORICAL_ROOTS_LIMIT": "16777216", - "VALIDATOR_REGISTRY_LIMIT": "1099511627776", - "BASE_REWARD_FACTOR": "64", - "WHISTLEBLOWER_REWARD_QUOTIENT": "512", - "PROPOSER_REWARD_QUOTIENT": "8", - "INACTIVITY_PENALTY_QUOTIENT": "67108864", - "MIN_SLASHING_PENALTY_QUOTIENT": "128", - "PROPORTIONAL_SLASHING_MULTIPLIER": "1", - "MAX_PROPOSER_SLASHINGS": "16", - "MAX_ATTESTER_SLASHINGS": "2", - "MAX_ATTESTATIONS": "128", - "MAX_DEPOSITS": "16", - "MAX_VOLUNTARY_EXITS": "16", - "INACTIVITY_PENALTY_QUOTIENT_ALTAIR": "50331648", - "MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR": "64", - "PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR": "2", - "SYNC_COMMITTEE_SIZE": "512", - "EPOCHS_PER_SYNC_COMMITTEE_PERIOD": "256", - "MIN_SYNC_COMMITTEE_PARTICIPANTS": "1", - "INACTIVITY_PENALTY_QUOTIENT_BELLATRIX": "16777216", - "MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX": "32", - "PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX": "3", - "MAX_BYTES_PER_TRANSACTION": "1073741824", - "MAX_TRANSACTIONS_PER_PAYLOAD": "1048576", - "BYTES_PER_LOGS_BLOOM": "256", - "MAX_EXTRA_DATA_BYTES": "32", - "MAX_BLS_TO_EXECUTION_CHANGES": "16", - "MAX_WITHDRAWALS_PER_PAYLOAD": "16", - "MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP": "16384", - "MAX_BLOB_COMMITMENTS_PER_BLOCK": "4096", - "KZG_COMMITMENT_INCLUSION_PROOF_DEPTH": "17", - "FIELD_ELEMENTS_PER_BLOB": "4096", - "MIN_ACTIVATION_BALANCE": "32000000000", - "MAX_EFFECTIVE_BALANCE_ELECTRA": "2048000000000", - "MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA": "4096", - "WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA": "4096", - "PENDING_DEPOSITS_LIMIT": "134217728", - "PENDING_PARTIAL_WITHDRAWALS_LIMIT": "134217728", - "PENDING_CONSOLIDATIONS_LIMIT": "262144", - "MAX_ATTESTER_SLASHINGS_ELECTRA": "1", - "MAX_ATTESTATIONS_ELECTRA": "8", - "MAX_DEPOSIT_REQUESTS_PER_PAYLOAD": "8192", - "MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD": "16", - "MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD": "2", - "MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP": "8", - "MAX_PENDING_DEPOSITS_PER_EPOCH": "16", - "FIELD_ELEMENTS_PER_CELL": "64", - "FIELD_ELEMENTS_PER_EXT_BLOB": "8192", - "KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH": "4", - "CELLS_PER_EXT_BLOB": "128", - "NUMBER_OF_COLUMNS": "128", - "DOMAIN_DEPOSIT": "0x03000000", - "DOMAIN_BEACON_ATTESTER": "0x01000000", - "ETH1_ADDRESS_WITHDRAWAL_PREFIX": "0x01", - "BLS_WITHDRAWAL_PREFIX": "0x00", - "DOMAIN_AGGREGATE_AND_PROOF": "0x06000000", - "DOMAIN_SELECTION_PROOF": "0x05000000", - "TARGET_AGGREGATORS_PER_COMMITTEE": "16", - "DOMAIN_CONTRIBUTION_AND_PROOF": "0x09000000", - "SYNC_COMMITTEE_SUBNET_COUNT": "4", - "UNSET_DEPOSIT_REQUESTS_START_INDEX": "18446744073709551615", - "DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF": "0x08000000", - "DOMAIN_APPLICATION_MASK": "0x00000001", - "DOMAIN_VOLUNTARY_EXIT": "0x04000000", - "DOMAIN_BEACON_PROPOSER": "0x00000000", - "VERSIONED_HASH_VERSION_KZG": "1", - "COMPOUNDING_WITHDRAWAL_PREFIX": "0x02", - "DOMAIN_SYNC_COMMITTEE": "0x07000000", - "FULL_EXIT_REQUEST_AMOUNT": "0", - "DOMAIN_RANDAO": "0x02000000", - "TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE": "16" - } -} \ No newline at end of file diff --git a/tests/data/ssv_valid_public.json b/tests/data/ssv_valid.json similarity index 100% rename from tests/data/ssv_valid_public.json rename to tests/data/ssv_valid.json diff --git a/tests/data/ssv_valid_node.json b/tests/data/ssv_valid_node.json deleted file mode 100644 index 59ad205f..00000000 --- a/tests/data/ssv_valid_node.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "data": [ - { - "public_key": "aa370f6250d421d00437b9900407a7ad93b041aeb7259d99b55ab8b163277746680e93e841f87350737bceee46aa104d", - "index": "1311498", - "status": "active_ongoing", - "activation_epoch": "273156", - "exit_epoch": "18446744073709551615", - "owner": "5e33db0b37622f7e6b2f0654aa7b985d854ea9cb", - "committee": [ - 1, - 2, - 3, - 4 - ], - "quorum": 0, - "partial_quorum": 0, - "graffiti": "", - "liquidated": false - } - ] -} \ No newline at end of file diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 42eec95a..42e36a8e 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,6 +1,4 @@ pub mod mock_relay; -pub mod mock_ssv_node; -pub mod mock_ssv_public; +pub mod mock_ssv; pub mod mock_validator; -pub mod signer_service; pub mod utils; diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index 4d7f0fc1..75532666 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -153,7 +153,7 @@ async fn handle_get_header( }); let object_root = message.tree_hash_root(); - let signature = sign_builder_root(state.chain, &state.signer, &object_root); + let signature = sign_builder_root(state.chain, &state.signer, object_root); let response = SignedBuilderBid { message, signature }; let response = GetHeaderResponse { diff --git a/tests/src/mock_ssv_public.rs b/tests/src/mock_ssv.rs similarity index 83% rename from tests/src/mock_ssv_public.rs rename to tests/src/mock_ssv.rs index a014db42..7ed8eb23 100644 --- a/tests/src/mock_ssv_public.rs +++ b/tests/src/mock_ssv.rs @@ -7,7 +7,7 @@ use axum::{ }; use cb_common::{ config::MUXER_HTTP_MAX_LENGTH, - interop::ssv::types::{SSVPagination, SSVPublicResponse, SSVPublicValidator}, + interop::ssv::types::{SSVPagination, SSVResponse, SSVValidator}, }; use tokio::{net::TcpListener, sync::RwLock, task::JoinHandle}; use tracing::info; @@ -16,9 +16,9 @@ pub const TEST_HTTP_TIMEOUT: u64 = 2; /// State for the mock server #[derive(Clone)] -pub struct PublicSsvMockState { +pub struct SsvMockState { /// List of pubkeys for the mock server to return - pub validators: Arc>>, + pub validators: Arc>>, /// Whether to force a timeout response to simulate a server error pub force_timeout: Arc>, @@ -26,14 +26,13 @@ pub struct PublicSsvMockState { /// Creates a simple mock server to simulate the SSV API endpoint under /// various conditions for testing. Note this ignores -pub async fn create_mock_public_ssv_server( +pub async fn create_mock_ssv_server( port: u16, - state: Option, + state: Option, ) -> Result, axum::Error> { - let data = include_str!("../../tests/data/ssv_valid_public.json"); - let response = - serde_json::from_str::(data).expect("failed to parse test data"); - let state = state.unwrap_or(PublicSsvMockState { + let data = include_str!("../../tests/data/ssv_valid.json"); + let response = serde_json::from_str::(data).expect("failed to parse test data"); + let state = state.unwrap_or(SsvMockState { validators: Arc::new(RwLock::new(response.validators)), force_timeout: Arc::new(RwLock::new(false)), }); @@ -63,7 +62,7 @@ pub async fn create_mock_public_ssv_server( /// Returns a valid SSV validators response, or a timeout if requested in /// the server state async fn handle_validators( - State(state): State, + State(state): State, Path((_, _)): Path<(String, u64)>, ) -> Response { // Time out if requested @@ -72,11 +71,11 @@ async fn handle_validators( } // Generate the response based on the current validators - let response: SSVPublicResponse; + let response: SSVResponse; { let validators = state.validators.read().await; let pagination = SSVPagination { total: validators.len() }; - response = SSVPublicResponse { validators: validators.clone(), pagination }; + response = SSVResponse { validators: validators.clone(), pagination }; } // Create a valid response diff --git a/tests/src/mock_ssv_node.rs b/tests/src/mock_ssv_node.rs deleted file mode 100644 index 7f24569d..00000000 --- a/tests/src/mock_ssv_node.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::{net::SocketAddr, sync::Arc}; - -use alloy::primitives::U256; -use axum::{ - extract::{Json, State}, - response::Response, - routing::get, -}; -use cb_common::{ - config::MUXER_HTTP_MAX_LENGTH, - interop::ssv::types::{SSVNodeResponse, SSVNodeValidator}, -}; -use serde::Deserialize; -use tokio::{net::TcpListener, sync::RwLock, task::JoinHandle}; -use tracing::info; - -pub const TEST_HTTP_TIMEOUT: u64 = 2; - -/// State for the mock server -#[derive(Clone)] -pub struct SsvNodeMockState { - /// List of pubkeys for the mock server to return - pub validators: Arc>>, - - /// Whether to force a timeout response to simulate a server error - pub force_timeout: Arc>, -} - -#[derive(Deserialize)] -struct SsvNodeValidatorsRequestBody { - pub operators: Vec, -} - -/// Creates a simple mock server to simulate the SSV API endpoint under -/// various conditions for testing. Note this ignores -pub async fn create_mock_ssv_node_server( - port: u16, - state: Option, -) -> Result, axum::Error> { - let data = include_str!("../../tests/data/ssv_valid_node.json"); - let response = - serde_json::from_str::(data).expect("failed to parse test data"); - let state = state.unwrap_or(SsvNodeMockState { - validators: Arc::new(RwLock::new(response.data)), - force_timeout: Arc::new(RwLock::new(false)), - }); - let router = axum::Router::new() - .route("/v1/validators", get(handle_validators)) - .route("/big_data", get(handle_big_data)) - .with_state(state) - .into_make_service(); - - let address = SocketAddr::from(([127, 0, 0, 1], port)); - let listener = TcpListener::bind(address).await.map_err(axum::Error::new)?; - let server = axum::serve(listener, router).with_graceful_shutdown(async { - tokio::signal::ctrl_c().await.expect("Failed to listen for shutdown signal"); - }); - let result = Ok(tokio::spawn(async move { - if let Err(e) = server.await { - eprintln!("Server error: {e}"); - } - })); - info!("Mock server started on http://localhost:{port}/"); - result -} - -/// Returns a valid SSV validators response, or a timeout if requested in -/// the server state -async fn handle_validators( - State(state): State, - Json(body): Json, -) -> Response { - // Time out if requested - if *state.force_timeout.read().await { - return handle_timeout().await; - } - - // Make sure the request deserialized properly - let _operators = body.operators; - - // Generate the response based on the current validators - let response: SSVNodeResponse; - { - let validators = state.validators.read().await; - response = SSVNodeResponse { data: validators.clone() }; - } - - // Create a valid response - Response::builder() - .status(200) - .header("Content-Type", "application/json") - .body(serde_json::to_string(&response).unwrap().into()) - .unwrap() -} - -/// Sends a response with a large body - larger than the maximum allowed. -/// Note that hyper overwrites the content-length header automatically, so -/// setting it here wouldn't actually change the value that ultimately -/// gets sent to the server. -async fn handle_big_data() -> Response { - let body = "f".repeat(2 * MUXER_HTTP_MAX_LENGTH); - Response::builder() - .status(200) - .header("Content-Type", "application/text") - .body(body.into()) - .unwrap() -} - -/// Simulates a timeout by sleeping for a long time -async fn handle_timeout() -> Response { - // Sleep for a long time to simulate a timeout - tokio::time::sleep(std::time::Duration::from_secs(2 * TEST_HTTP_TIMEOUT)).await; - Response::builder() - .status(200) - .header("Content-Type", "application/text") - .body("Timeout response".into()) - .unwrap() -} diff --git a/tests/src/signer_service.rs b/tests/src/signer_service.rs deleted file mode 100644 index 550ac4ce..00000000 --- a/tests/src/signer_service.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{collections::HashMap, time::Duration}; - -use cb_common::{ - commit::{constants::STATUS_PATH, request::GetPubkeysResponse}, - config::{ModuleSigningConfig, StartSignerConfig}, - signer::{SignerLoader, ValidatorKeysFormat}, - types::{Chain, ModuleId}, - utils::bls_pubkey_from_hex, -}; -use cb_signer::service::SigningService; -use eyre::Result; -use reqwest::{Certificate, Response, StatusCode}; -use tracing::info; - -use crate::utils::{get_signer_config, get_start_signer_config}; - -// Starts the signer moduler server on a separate task and returns its -// configuration -pub async fn start_server( - port: u16, - mod_signing_configs: &HashMap, - admin_secret: String, - use_tls: bool, -) -> Result { - let chain = Chain::Hoodi; - - // Create a signer config - let loader = SignerLoader::ValidatorsDir { - keys_path: "data/keystores/keys".into(), - secrets_path: "data/keystores/secrets".into(), - format: ValidatorKeysFormat::Lighthouse, - }; - let mut config = get_signer_config(loader, use_tls); - config.port = port; - config.jwt_auth_fail_limit = 3; // Set a low fail limit for testing - config.jwt_auth_fail_timeout_seconds = 3; // Set a short timeout for testing - let start_config = get_start_signer_config(config, chain, mod_signing_configs, admin_secret); - - // Run the Signer - let server_handle = tokio::spawn(SigningService::run(start_config.clone())); - - // Wait for the server to start - let (url, client) = match start_config.tls_certificates { - Some(ref certificates) => { - let url = format!("https://{}{}", start_config.endpoint, STATUS_PATH); - let client = reqwest::Client::builder() - .add_root_certificate(Certificate::from_pem(&certificates.0)?) - .build()?; - (url, client) - } - None => { - let url = format!("http://{}{}", start_config.endpoint, STATUS_PATH); - (url, reqwest::Client::new()) - } - }; - - let sleep_duration = Duration::from_millis(100); - for i in 0..100 { - // 10 second max wait - if i > 0 { - tokio::time::sleep(sleep_duration).await; - } - match client.get(&url).send().await { - Ok(_) => { - return Ok(start_config); - } - Err(e) => { - info!("Waiting for signer service to start: {}", e); - } - } - } - Err(eyre::eyre!("Signer service failed to start: {}", server_handle.await.unwrap_err())) -} - -// Verifies that the pubkeys returned by the server match the pubkeys in the -// test data -pub async fn verify_pubkeys(response: Response) -> Result<()> { - // Verify the expected pubkeys are returned - assert!(response.status() == StatusCode::OK); - let pubkey_json = response.json::().await?; - assert_eq!(pubkey_json.keys.len(), 2); - let expected_pubkeys = vec![ - bls_pubkey_from_hex( - "883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4", - )?, - bls_pubkey_from_hex( - "b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9", - )?, - ]; - for expected in expected_pubkeys { - assert!( - pubkey_json.keys.iter().any(|k| k.consensus == expected), - "Expected pubkey not found: {expected}" - ); - info!("Server returned expected pubkey: {:?}", expected); - } - Ok(()) -} diff --git a/tests/src/utils.rs b/tests/src/utils.rs index e079ef11..58ef42cf 100644 --- a/tests/src/utils.rs +++ b/tests/src/utils.rs @@ -1,18 +1,15 @@ use std::{ collections::HashMap, net::{Ipv4Addr, SocketAddr}, - path::PathBuf, sync::{Arc, Once}, }; -use alloy::primitives::{B256, U256}; +use alloy::primitives::U256; use cb_common::{ config::{ - CommitBoostConfig, LogsSettings, ModuleKind, ModuleSigningConfig, PbsConfig, - PbsModuleConfig, RelayConfig, ReverseProxyHeaderSetup, SIGNER_IMAGE_DEFAULT, + PbsConfig, PbsModuleConfig, RelayConfig, SIGNER_IMAGE_DEFAULT, SIGNER_JWT_AUTH_FAIL_LIMIT_DEFAULT, SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT, - SIGNER_PORT_DEFAULT, SignerConfig, SignerType, StartSignerConfig, StaticModuleConfig, - StaticPbsConfig, TlsMode, + SIGNER_PORT_DEFAULT, SignerConfig, SignerType, StartSignerConfig, }, pbs::{RelayClient, RelayEntry}, signer::SignerLoader, @@ -20,7 +17,6 @@ use cb_common::{ utils::{bls_pubkey_from_hex, default_host}, }; use eyre::Result; -use rcgen::generate_simple_self_signed; use url::Url; pub fn get_local_address(port: u16) -> String { @@ -70,7 +66,7 @@ pub fn generate_mock_relay_with_batch_size( RelayClient::new(config) } -pub fn get_pbs_config(port: u16) -> PbsConfig { +pub fn get_pbs_static_config(port: u16) -> PbsConfig { PbsConfig { host: Ipv4Addr::UNSPECIFIED, port, @@ -83,8 +79,7 @@ pub fn get_pbs_config(port: u16) -> PbsConfig { min_bid_wei: U256::ZERO, late_in_slot_time_ms: u64::MAX, extra_validation_enabled: false, - ssv_node_api_url: Url::parse("http://localhost:0").unwrap(), - ssv_public_api_url: Url::parse("http://localhost:0").unwrap(), + ssv_api_url: Url::parse("https://example.net").unwrap(), rpc_url: None, http_timeout_seconds: 10, register_validator_retry_limit: u32::MAX, @@ -93,23 +88,6 @@ pub fn get_pbs_config(port: u16) -> PbsConfig { } } -pub fn get_pbs_static_config(pbs_config: PbsConfig) -> StaticPbsConfig { - StaticPbsConfig { docker_image: String::from(""), pbs_config, with_signer: true } -} - -pub fn get_commit_boost_config(pbs_static_config: StaticPbsConfig) -> CommitBoostConfig { - CommitBoostConfig { - chain: Chain::Hoodi, - relays: vec![], - pbs: pbs_static_config, - muxes: None, - modules: Some(vec![]), - signer: None, - metrics: None, - logs: LogsSettings::default(), - } -} - pub fn to_pbs_config( chain: Chain, pbs_config: PbsConfig, @@ -127,7 +105,7 @@ pub fn to_pbs_config( } } -pub fn get_signer_config(loader: SignerLoader, tls: bool) -> SignerConfig { +pub fn get_signer_config(loader: SignerLoader) -> SignerConfig { SignerConfig { host: default_host(), port: SIGNER_PORT_DEFAULT, @@ -135,60 +113,29 @@ pub fn get_signer_config(loader: SignerLoader, tls: bool) -> SignerConfig { jwt_auth_fail_limit: SIGNER_JWT_AUTH_FAIL_LIMIT_DEFAULT, jwt_auth_fail_timeout_seconds: SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT, inner: SignerType::Local { loader, store: None }, - tls_mode: if tls { TlsMode::Certificate(PathBuf::new()) } else { TlsMode::Insecure }, - reverse_proxy: ReverseProxyHeaderSetup::None, } } pub fn get_start_signer_config( signer_config: SignerConfig, chain: Chain, - mod_signing_configs: &HashMap, - admin_secret: String, + jwts: HashMap, ) -> StartSignerConfig { - let tls_certificates = match signer_config.tls_mode { - TlsMode::Insecure => None, - TlsMode::Certificate(_) => Some( - generate_simple_self_signed(vec![signer_config.host.to_string()]) - .map(|x| { - ( - x.cert.pem().as_bytes().to_vec(), - x.key_pair.serialize_pem().as_bytes().to_vec(), - ) - }) - .expect("Failed to generate TLS certificate"), - ), - }; - match signer_config.inner { SignerType::Local { loader, .. } => StartSignerConfig { chain, loader: Some(loader), store: None, endpoint: SocketAddr::new(signer_config.host.into(), signer_config.port), - mod_signing_configs: mod_signing_configs.clone(), - admin_secret, + jwts, jwt_auth_fail_limit: signer_config.jwt_auth_fail_limit, jwt_auth_fail_timeout_seconds: signer_config.jwt_auth_fail_timeout_seconds, dirk: None, - tls_certificates, - reverse_proxy: ReverseProxyHeaderSetup::None, }, _ => panic!("Only local signers are supported in tests"), } } -pub fn create_module_config(id: ModuleId, signing_id: B256) -> StaticModuleConfig { - StaticModuleConfig { - id, - signing_id, - docker_image: String::from(""), - env: None, - env_file: None, - kind: ModuleKind::Commit, - } -} - pub fn bls_pubkey_from_hex_unchecked(hex: &str) -> BlsPublicKey { bls_pubkey_from_hex(hex).unwrap() } diff --git a/tests/tests/pbs_cfg_file_update.rs b/tests/tests/pbs_cfg_file_update.rs deleted file mode 100644 index 770421a3..00000000 --- a/tests/tests/pbs_cfg_file_update.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::{net::Ipv4Addr, sync::Arc, time::Duration}; - -use alloy::primitives::U256; -use cb_common::{ - config::{CommitBoostConfig, LogsSettings, PbsConfig, RelayConfig, StaticPbsConfig}, - pbs::RelayEntry, - signer::random_secret, - types::Chain, -}; -use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; -use cb_tests::{ - mock_relay::{MockRelayState, start_mock_relay_service}, - mock_validator::MockValidator, - utils::{generate_mock_relay, get_pbs_config, setup_test_env, to_pbs_config}, -}; -use eyre::Result; -use reqwest::StatusCode; -use tracing::info; -use url::Url; - -/// Updates the config file that was used to load the PBS config, and ensures -/// the filesystem watcher triggers a reload of the configuration. -#[tokio::test] -async fn test_cfg_file_update() -> Result<()> { - // Random keys needed for the relays to start - setup_test_env(); - let signer = random_secret(); - let pubkey = signer.public_key(); - - let chain = Chain::Hoodi; - let pbs_port = 3730; - - // Start relay 1 - let relay1_port = pbs_port + 1; - let relay1 = generate_mock_relay(relay1_port, pubkey.clone())?; - let relay1_state = Arc::new(MockRelayState::new(chain, signer.clone())); - tokio::spawn(start_mock_relay_service(relay1_state.clone(), relay1_port)); - - // Start relay 2 - let relay2_port = relay1_port + 1; - let relay2 = generate_mock_relay(relay2_port, pubkey.clone())?; - let relay2_id = relay2.id.clone().to_string(); - let relay2_state = Arc::new(MockRelayState::new(chain, signer)); - tokio::spawn(start_mock_relay_service(relay2_state.clone(), relay2_port)); - - // Make a config with relay 1 only - let pbs_config = PbsConfig { - // get_pbs_static_config(pbs_port); - host: Ipv4Addr::LOCALHOST, - port: pbs_port, - relay_check: false, - wait_all_registrations: false, - timeout_get_header_ms: 950, - timeout_get_payload_ms: 4000, - timeout_register_validator_ms: 3000, - skip_sigverify: true, - min_bid_wei: U256::ZERO, - late_in_slot_time_ms: u64::MAX / 2, /* serde gets very upset about serializing u64::MAX - * or anything close to it */ - extra_validation_enabled: false, - rpc_url: None, - ssv_node_api_url: Url::parse("http://example.com").unwrap(), - ssv_public_api_url: Url::parse("http://example.com").unwrap(), - http_timeout_seconds: 10, - register_validator_retry_limit: 3, - validator_registration_batch_size: None, - mux_registry_refresh_interval_seconds: 384, - }; - let cb_config = CommitBoostConfig { - chain, - pbs: StaticPbsConfig { - docker_image: "cb-fake-repo/cb-fake-image:latest".to_string(), - pbs_config: pbs_config.clone(), - with_signer: false, - }, - muxes: None, - modules: None, - signer: None, - logs: LogsSettings::default(), - metrics: None, - relays: vec![RelayConfig { - id: Some(relay1.id.to_string()), - enable_timing_games: false, - frequency_get_header_ms: None, - get_params: None, - headers: None, - target_first_request_ms: None, - validator_registration_batch_size: None, - entry: RelayEntry { - id: relay1.id.to_string(), - url: Url::parse(&format!("http://localhost:{relay1_port}"))?, - pubkey: pubkey.clone(), - }, - }], - }; - - // Save to a file - let temp_file = tempfile::NamedTempFile::new()?; - let config_path = temp_file.path().to_path_buf(); - let config_toml = toml::to_string_pretty(&cb_config)?; - info!("Writing initial config to {:?}", config_path); - std::fs::write(config_path.clone(), config_toml.as_bytes())?; - - // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), vec![relay1.clone()]); - let state = PbsState::new(config, config_path.clone()); - tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); - - // leave some time to start servers - extra time for the file watcher - tokio::time::sleep(Duration::from_millis(1000)).await; - - // Send a get header request - should go to relay 1 only - let mock_validator = MockValidator::new(pbs_port)?; - info!("Sending get header"); - let res = mock_validator.do_get_header(None).await?; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(relay1_state.received_get_header(), 1); - assert_eq!(relay2_state.received_get_header(), 0); - - // Update the config to only have relay 2 - let cb_config = CommitBoostConfig { - chain, - pbs: StaticPbsConfig { - docker_image: "cb-fake-repo/cb-fake-image:latest".to_string(), - pbs_config, - with_signer: false, - }, - muxes: None, - modules: None, - signer: None, - logs: LogsSettings::default(), - metrics: None, - relays: vec![RelayConfig { - id: Some(relay2_id.clone()), - enable_timing_games: false, - frequency_get_header_ms: None, - get_params: None, - headers: None, - target_first_request_ms: None, - validator_registration_batch_size: None, - entry: RelayEntry { - id: relay2_id, - url: Url::parse(&format!("http://{pubkey}@localhost:{relay2_port}"))?, - pubkey, - }, - }], - }; - let config_toml = toml::to_string_pretty(&cb_config)?; - info!("Writing updated config to {:?}", config_path); - std::fs::write(config_path, config_toml.as_bytes())?; - - // leave some time for the watcher to pick up the change and reload - tokio::time::sleep(Duration::from_millis(1000)).await; - - // Send another get header request - should go to relay 2 only - info!("Sending get header after config update"); - let res = mock_validator.do_get_header(None).await?; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(relay1_state.received_get_header(), 1); // no change - assert_eq!(relay2_state.received_get_header(), 1); // incremented - - Ok(()) -} diff --git a/tests/tests/pbs_get_header.rs b/tests/tests/pbs_get_header.rs index 1cfdc3bb..d44d70ce 100644 --- a/tests/tests/pbs_get_header.rs +++ b/tests/tests/pbs_get_header.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use alloy::primitives::{B256, U256}; use cb_common::{ @@ -12,7 +12,7 @@ use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, mock_validator::MockValidator, - utils::{generate_mock_relay, get_pbs_config, setup_test_env, to_pbs_config}, + utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, }; use eyre::Result; use lh_types::ForkName; @@ -36,8 +36,8 @@ async fn test_get_header() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_port)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), vec![mock_relay.clone()]); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), vec![mock_relay.clone()]); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -59,7 +59,7 @@ async fn test_get_header() -> Result<()> { assert_eq!(res.data.message.header().timestamp(), timestamp_of_slot_start_sec(0, chain)); assert_eq!( res.data.signature, - sign_builder_root(chain, &mock_state.signer, &res.data.message.tree_hash_root()) + sign_builder_root(chain, &mock_state.signer, res.data.message.tree_hash_root()) ); Ok(()) } @@ -82,8 +82,8 @@ async fn test_get_header_returns_204_if_relay_down() -> Result<()> { // tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_port)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), vec![mock_relay.clone()]); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), vec![mock_relay.clone()]); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -114,8 +114,8 @@ async fn test_get_header_returns_400_if_request_is_invalid() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_port)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), vec![mock_relay.clone()]); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), vec![mock_relay.clone()]); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers diff --git a/tests/tests/pbs_get_status.rs b/tests/tests/pbs_get_status.rs index cd2ab51d..0ca09bf5 100644 --- a/tests/tests/pbs_get_status.rs +++ b/tests/tests/pbs_get_status.rs @@ -1,11 +1,11 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use cb_common::{signer::random_secret, types::Chain}; use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, mock_validator::MockValidator, - utils::{generate_mock_relay, get_pbs_config, setup_test_env, to_pbs_config}, + utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, }; use eyre::Result; use reqwest::StatusCode; @@ -30,8 +30,8 @@ async fn test_get_status() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_0_port)); tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_1_port)); - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays.clone()); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays.clone()); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -63,8 +63,8 @@ async fn test_get_status_returns_502_if_relay_down() -> Result<()> { // Don't start the relay // tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_port)); - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays.clone()); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays.clone()); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers diff --git a/tests/tests/pbs_mux.rs b/tests/tests/pbs_mux.rs index 4f842d56..3a15b49b 100644 --- a/tests/tests/pbs_mux.rs +++ b/tests/tests/pbs_mux.rs @@ -1,15 +1,8 @@ -use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use alloy::primitives::U256; use cb_common::{ - config::{ - HTTP_TIMEOUT_SECONDS_DEFAULT, MUXER_HTTP_MAX_LENGTH, MuxConfig, MuxKeysLoader, PbsMuxes, - RuntimeMuxConfig, - }, - interop::ssv::{ - types::{SSVNodeValidator, SSVPublicValidator}, - utils::{request_ssv_pubkeys_from_public_api, request_ssv_pubkeys_from_ssv_node}, - }, + config::{HTTP_TIMEOUT_SECONDS_DEFAULT, MUXER_HTTP_MAX_LENGTH, RuntimeMuxConfig}, + interop::ssv::utils::fetch_ssv_pubkeys_from_url, signer::random_secret, types::Chain, utils::{ResponseReadError, set_ignore_content_length}, @@ -17,11 +10,10 @@ use cb_common::{ use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, - mock_ssv_node::{SsvNodeMockState, create_mock_ssv_node_server}, - mock_ssv_public::{PublicSsvMockState, TEST_HTTP_TIMEOUT, create_mock_public_ssv_server}, + mock_ssv::{SsvMockState, TEST_HTTP_TIMEOUT, create_mock_ssv_server}, mock_validator::MockValidator, utils::{ - bls_pubkey_from_hex_unchecked, generate_mock_relay, get_pbs_config, setup_test_env, + bls_pubkey_from_hex_unchecked, generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config, }, }; @@ -33,20 +25,18 @@ use url::Url; #[tokio::test] /// Tests that a successful SSV network fetch is handled and parsed properly -/// from the public API -async fn test_ssv_public_network_fetch() -> Result<()> { +async fn test_ssv_network_fetch() -> Result<()> { // Start the mock server let port = 30100; - let server_handle = create_mock_public_ssv_server(port, None).await?; + let _server_handle = create_mock_ssv_server(port, None).await?; let url = Url::parse(&format!("http://localhost:{port}/api/v4/test_chain/validators/in_operator/1")) .unwrap(); let response = - request_ssv_pubkeys_from_public_api(url, Duration::from_secs(HTTP_TIMEOUT_SECONDS_DEFAULT)) - .await?; + fetch_ssv_pubkeys_from_url(url, Duration::from_secs(HTTP_TIMEOUT_SECONDS_DEFAULT)).await?; // Make sure the response is correct - // NOTE: requires that ssv_valid_public.json doesn't change + // NOTE: requires that ssv_data.json dpesn't change assert_eq!(response.validators.len(), 3); let expected_pubkeys = [ bls_pubkey_from_hex_unchecked( @@ -64,7 +54,7 @@ async fn test_ssv_public_network_fetch() -> Result<()> { } // Clean up the server handle - server_handle.abort(); + _server_handle.abort(); Ok(()) } @@ -75,10 +65,9 @@ async fn test_ssv_public_network_fetch() -> Result<()> { async fn test_ssv_network_fetch_big_data() -> Result<()> { // Start the mock server let port = 30101; - let server_handle = - cb_tests::mock_ssv_public::create_mock_public_ssv_server(port, None).await?; + let server_handle = cb_tests::mock_ssv::create_mock_ssv_server(port, None).await?; let url = Url::parse(&format!("http://localhost:{port}/big_data")).unwrap(); - let response = request_ssv_pubkeys_from_public_api(url, Duration::from_secs(120)).await; + let response = fetch_ssv_pubkeys_from_url(url, Duration::from_secs(120)).await; // The response should fail due to content length being too big match response { @@ -107,16 +96,15 @@ async fn test_ssv_network_fetch_big_data() -> Result<()> { async fn test_ssv_network_fetch_timeout() -> Result<()> { // Start the mock server let port = 30102; - let state = PublicSsvMockState { + let state = SsvMockState { validators: Arc::new(RwLock::new(vec![])), force_timeout: Arc::new(RwLock::new(true)), }; - let server_handle = create_mock_public_ssv_server(port, Some(state)).await?; + let server_handle = create_mock_ssv_server(port, Some(state)).await?; let url = Url::parse(&format!("http://localhost:{port}/api/v4/test_chain/validators/in_operator/1")) .unwrap(); - let response = - request_ssv_pubkeys_from_public_api(url, Duration::from_secs(TEST_HTTP_TIMEOUT)).await; + let response = fetch_ssv_pubkeys_from_url(url, Duration::from_secs(TEST_HTTP_TIMEOUT)).await; // The response should fail due to timeout assert!(response.is_err(), "Expected timeout error, but got success"); @@ -137,9 +125,9 @@ async fn test_ssv_network_fetch_big_data_without_content_length() -> Result<()> // Start the mock server let port = 30103; set_ignore_content_length(true); - let server_handle = create_mock_public_ssv_server(port, None).await?; + let server_handle = create_mock_ssv_server(port, None).await?; let url = Url::parse(&format!("http://localhost:{port}/big_data")).unwrap(); - let response = request_ssv_pubkeys_from_public_api(url, Duration::from_secs(120)).await; + let response = fetch_ssv_pubkeys_from_url(url, Duration::from_secs(120)).await; // The response should fail due to the body being too big match response { @@ -162,37 +150,6 @@ async fn test_ssv_network_fetch_big_data_without_content_length() -> Result<()> Ok(()) } -#[tokio::test] -/// Tests that a successful SSV network fetch is handled and parsed properly -/// from the node API -async fn test_ssv_node_network_fetch() -> Result<()> { - // Start the mock server - let port = 30104; - let _server_handle = create_mock_ssv_node_server(port, None).await?; - let url = Url::parse(&format!("http://localhost:{port}/v1/validators")).unwrap(); - let response = request_ssv_pubkeys_from_ssv_node( - url, - U256::from(1), - Duration::from_secs(HTTP_TIMEOUT_SECONDS_DEFAULT), - ) - .await?; - - // Make sure the response is correct - // NOTE: requires that ssv_valid_node.json doesn't change - assert_eq!(response.data.len(), 1); - let expected_pubkeys = [bls_pubkey_from_hex_unchecked( - "aa370f6250d421d00437b9900407a7ad93b041aeb7259d99b55ab8b163277746680e93e841f87350737bceee46aa104d", - )]; - for (i, validator) in response.data.iter().enumerate() { - assert_eq!(validator.public_key, expected_pubkeys[i]); - } - - // Clean up the server handle - _server_handle.abort(); - - Ok(()) -} - #[tokio::test] async fn test_mux() -> Result<()> { setup_test_env(); @@ -214,7 +171,7 @@ async fn test_mux() -> Result<()> { // Register all relays in PBS config let relays = vec![default_relay.clone()]; - let mut config = to_pbs_config(chain, get_pbs_config(pbs_port), relays); + let mut config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); config.all_relays = vec![mux_relay_1.clone(), mux_relay_2.clone(), default_relay.clone()]; // Configure mux for two relays @@ -229,7 +186,7 @@ async fn test_mux() -> Result<()> { config.mux_lookup = Some(HashMap::from([(validator_pubkey.clone(), mux)])); // Run PBS service - let state = PbsState::new(config, PathBuf::new()); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -271,196 +228,3 @@ async fn test_mux() -> Result<()> { Ok(()) } - -/// Tests the SSV mux with dynamic registry fetching from an SSV node -#[tokio::test] -async fn test_ssv_multi_with_node() -> Result<()> { - // Generate keys - let signer = random_secret(); - let pubkey = signer.public_key(); - let signer2 = random_secret(); - let pubkey2 = signer2.public_key(); - - let chain = Chain::Hoodi; - let pbs_port = 3711; - - // Start the mock SSV node - let ssv_node_port = pbs_port + 1; - let ssv_node_url = Url::parse(&format!("http://localhost:{ssv_node_port}/v1/"))?; - let mock_ssv_node_state = SsvNodeMockState { - validators: Arc::new(RwLock::new(vec![ - SSVNodeValidator { public_key: pubkey.clone() }, - SSVNodeValidator { public_key: pubkey2.clone() }, - ])), - force_timeout: Arc::new(RwLock::new(false)), - }; - let ssv_node_handle = - create_mock_ssv_node_server(ssv_node_port, Some(mock_ssv_node_state.clone())).await?; - - // Start the mock SSV public API - let ssv_public_port = ssv_node_port + 1; - let ssv_public_url = Url::parse(&format!("http://localhost:{ssv_public_port}/api/v4/"))?; - let mock_ssv_public_state = PublicSsvMockState { - validators: Arc::new(RwLock::new(vec![SSVPublicValidator { pubkey: pubkey.clone() }])), - force_timeout: Arc::new(RwLock::new(false)), - }; - let ssv_public_handle = - create_mock_public_ssv_server(ssv_public_port, Some(mock_ssv_public_state.clone())).await?; - - // Start a mock relay to be used by the mux - let relay_port = ssv_public_port + 1; - let relay = generate_mock_relay(relay_port, pubkey.clone())?; - let relay_id = relay.id.clone().to_string(); - let relay_state = Arc::new(MockRelayState::new(chain, signer)); - let relay_task = tokio::spawn(start_mock_relay_service(relay_state.clone(), relay_port)); - - // Create the registry mux - let loader = MuxKeysLoader::Registry { - enable_refreshing: true, - node_operator_id: 1, - lido_module_id: None, - registry: cb_common::config::NORegistry::SSV, - }; - let muxes = PbsMuxes { - muxes: vec![MuxConfig { - id: relay_id.clone(), - loader: Some(loader), - late_in_slot_time_ms: Some(u64::MAX), - relays: vec![(*relay.config).clone()], - timeout_get_header_ms: Some(u64::MAX - 1), - validator_pubkeys: vec![], - }], - }; - - // Set up the PBS config - let mut pbs_config = get_pbs_config(pbs_port); - pbs_config.ssv_node_api_url = ssv_node_url.clone(); - pbs_config.ssv_public_api_url = ssv_public_url.clone(); - pbs_config.mux_registry_refresh_interval_seconds = 1; // Refresh the mux every second - let (mux_lookup, registry_muxes) = muxes.validate_and_fill(chain, &pbs_config).await?; - let relays = vec![relay.clone()]; // Default relay only - let mut config = to_pbs_config(chain, pbs_config, relays); - config.all_relays.push(relay.clone()); // Add the mux relay to just this field - config.mux_lookup = Some(mux_lookup); - config.registry_muxes = Some(registry_muxes); - - // Run PBS service - let state = PbsState::new(config, PathBuf::new()); - let pbs_server = tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); - info!("Started PBS server with pubkey {pubkey}"); - - // Wait for the server to start - tokio::time::sleep(Duration::from_millis(100)).await; - - // Try to run a get_header on the new pubkey, which should use the default - // relay only since it hasn't been seen in the mux yet - let mock_validator = MockValidator::new(pbs_port)?; - info!("Sending get header"); - let res = mock_validator.do_get_header(Some(pubkey2.clone())).await?; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(relay_state.received_get_header(), 1); // pubkey2 was loaded from the SSV node - - // Shut down the server handles - pbs_server.abort(); - ssv_node_handle.abort(); - ssv_public_handle.abort(); - relay_task.abort(); - - Ok(()) -} - -/// Tests the SSV mux with dynamic registry fetching from the public SSV API -/// when the local node is down -#[tokio::test] -async fn test_ssv_multi_with_public() -> Result<()> { - // Generate keys - let signer = random_secret(); - let pubkey = signer.public_key(); - let signer2 = random_secret(); - let pubkey2 = signer2.public_key(); - - let chain = Chain::Hoodi; - let pbs_port = 3720; - - // Start the mock SSV node - let ssv_node_port = pbs_port + 1; - let ssv_node_url = Url::parse(&format!("http://localhost:{ssv_node_port}/v1/"))?; - - // Don't start the SSV node server to simulate it being down - // let ssv_node_handle = create_mock_ssv_node_server(ssv_node_port, - // Some(mock_ssv_node_state.clone())).await?; - - // Start the mock SSV public API - let ssv_public_port = ssv_node_port + 1; - let ssv_public_url = Url::parse(&format!("http://localhost:{ssv_public_port}/api/v4/"))?; - let mock_ssv_public_state = PublicSsvMockState { - validators: Arc::new(RwLock::new(vec![ - SSVPublicValidator { pubkey: pubkey.clone() }, - SSVPublicValidator { pubkey: pubkey2.clone() }, - ])), - force_timeout: Arc::new(RwLock::new(false)), - }; - let ssv_public_handle = - create_mock_public_ssv_server(ssv_public_port, Some(mock_ssv_public_state.clone())).await?; - - // Start a mock relay to be used by the mux - let relay_port = ssv_public_port + 1; - let relay = generate_mock_relay(relay_port, pubkey.clone())?; - let relay_id = relay.id.clone().to_string(); - let relay_state = Arc::new(MockRelayState::new(chain, signer)); - let relay_task = tokio::spawn(start_mock_relay_service(relay_state.clone(), relay_port)); - - // Create the registry mux - let loader = MuxKeysLoader::Registry { - enable_refreshing: true, - node_operator_id: 1, - lido_module_id: None, - registry: cb_common::config::NORegistry::SSV, - }; - let muxes = PbsMuxes { - muxes: vec![MuxConfig { - id: relay_id.clone(), - loader: Some(loader), - late_in_slot_time_ms: Some(u64::MAX), - relays: vec![(*relay.config).clone()], - timeout_get_header_ms: Some(u64::MAX - 1), - validator_pubkeys: vec![], - }], - }; - - // Set up the PBS config - let mut pbs_config = get_pbs_config(pbs_port); - pbs_config.ssv_node_api_url = ssv_node_url.clone(); - pbs_config.ssv_public_api_url = ssv_public_url.clone(); - pbs_config.mux_registry_refresh_interval_seconds = 1; // Refresh the mux every second - let (mux_lookup, registry_muxes) = muxes.validate_and_fill(chain, &pbs_config).await?; - let relays = vec![relay.clone()]; // Default relay only - let mut config = to_pbs_config(chain, pbs_config, relays); - config.all_relays.push(relay.clone()); // Add the mux relay to just this field - config.mux_lookup = Some(mux_lookup); - config.registry_muxes = Some(registry_muxes); - - // Run PBS service - let state = PbsState::new(config, PathBuf::new()); - let pbs_server = tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); - info!("Started PBS server with pubkey {pubkey}"); - - // Wait for the server to start - tokio::time::sleep(Duration::from_millis(100)).await; - - // Try to run a get_header on the new pubkey, which should use the default - // relay only since it hasn't been seen in the mux yet - let mock_validator = MockValidator::new(pbs_port)?; - info!("Sending get header"); - let res = mock_validator.do_get_header(Some(pubkey2.clone())).await?; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(relay_state.received_get_header(), 1); // pubkey2 was loaded from the SSV public API - - // Shut down the server handles - pbs_server.abort(); - //ssv_node_handle.abort(); - ssv_public_handle.abort(); - relay_task.abort(); - - Ok(()) -} diff --git a/tests/tests/pbs_mux_refresh.rs b/tests/tests/pbs_mux_refresh.rs index 1d590a49..da582ec7 100644 --- a/tests/tests/pbs_mux_refresh.rs +++ b/tests/tests/pbs_mux_refresh.rs @@ -1,17 +1,17 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use cb_common::{ config::{MuxConfig, MuxKeysLoader, PbsMuxes}, - interop::ssv::types::SSVPublicValidator, + interop::ssv::types::SSVValidator, signer::random_secret, types::Chain, }; use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, - mock_ssv_public::{PublicSsvMockState, create_mock_public_ssv_server}, + mock_ssv::{SsvMockState, create_mock_ssv_server}, mock_validator::MockValidator, - utils::{generate_mock_relay, get_pbs_config, to_pbs_config}, + utils::{generate_mock_relay, get_pbs_static_config, to_pbs_config}, }; use eyre::Result; use reqwest::StatusCode; @@ -44,14 +44,14 @@ async fn test_auto_refresh() -> Result<()> { let ssv_api_port = pbs_port + 1; // Intentionally missing a trailing slash to ensure this is handled properly let ssv_api_url = Url::parse(&format!("http://localhost:{ssv_api_port}/api/v4"))?; - let mock_ssv_state = PublicSsvMockState { - validators: Arc::new(RwLock::new(vec![SSVPublicValidator { + let mock_ssv_state = SsvMockState { + validators: Arc::new(RwLock::new(vec![SSVValidator { pubkey: existing_mux_pubkey.clone(), }])), force_timeout: Arc::new(RwLock::new(false)), }; let ssv_server_handle = - create_mock_public_ssv_server(ssv_api_port, Some(mock_ssv_state.clone())).await?; + create_mock_ssv_server(ssv_api_port, Some(mock_ssv_state.clone())).await?; // Start a default relay for non-mux keys let default_relay_port = ssv_api_port + 1; @@ -87,8 +87,8 @@ async fn test_auto_refresh() -> Result<()> { }; // Set up the PBS config - let mut pbs_config = get_pbs_config(pbs_port); - pbs_config.ssv_public_api_url = ssv_api_url.clone(); + let mut pbs_config = get_pbs_static_config(pbs_port); + pbs_config.ssv_api_url = ssv_api_url.clone(); pbs_config.mux_registry_refresh_interval_seconds = 1; // Refresh the mux every second let (mux_lookup, registry_muxes) = muxes.validate_and_fill(chain, &pbs_config).await?; let relays = vec![default_relay.clone()]; // Default relay only @@ -98,7 +98,7 @@ async fn test_auto_refresh() -> Result<()> { config.registry_muxes = Some(registry_muxes); // Run PBS service - let state = PbsState::new(config, PathBuf::new()); + let state = PbsState::new(config); let pbs_server = tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); info!("Started PBS server with pubkey {default_pubkey}"); @@ -126,7 +126,7 @@ async fn test_auto_refresh() -> Result<()> { // Add another validator { let mut validators = mock_ssv_state.validators.write().await; - validators.push(SSVPublicValidator { pubkey: new_mux_pubkey.clone() }); + validators.push(SSVValidator { pubkey: new_mux_pubkey.clone() }); info!("Added new validator {new_mux_pubkey} to the SSV mock server"); } diff --git a/tests/tests/pbs_post_blinded_blocks.rs b/tests/tests/pbs_post_blinded_blocks.rs index bf4703c2..9c70b562 100644 --- a/tests/tests/pbs_post_blinded_blocks.rs +++ b/tests/tests/pbs_post_blinded_blocks.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use cb_common::{ pbs::{BuilderApiVersion, GetPayloadInfo, SubmitBlindedBlockResponse}, @@ -9,7 +9,7 @@ use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, mock_validator::{MockValidator, load_test_signed_blinded_block}, - utils::{generate_mock_relay, get_pbs_config, setup_test_env, to_pbs_config}, + utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, }; use eyre::Result; use reqwest::{Response, StatusCode}; @@ -70,8 +70,8 @@ async fn test_submit_block_too_large() -> Result<()> { let mock_state = Arc::new(MockRelayState::new(chain, signer).with_large_body()); tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -112,8 +112,8 @@ async fn submit_block_impl( tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers diff --git a/tests/tests/pbs_post_validators.rs b/tests/tests/pbs_post_validators.rs index 12601cda..35e6c5be 100644 --- a/tests/tests/pbs_post_validators.rs +++ b/tests/tests/pbs_post_validators.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use alloy::rpc::types::beacon::relay::ValidatorRegistration; use cb_common::{ @@ -9,7 +9,7 @@ use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use cb_tests::{ mock_relay::{MockRelayState, start_mock_relay_service}, mock_validator::MockValidator, - utils::{generate_mock_relay, get_pbs_config, setup_test_env, to_pbs_config}, + utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, }; use eyre::Result; use reqwest::StatusCode; @@ -30,8 +30,8 @@ async fn test_register_validators() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); // leave some time to start servers @@ -79,8 +79,8 @@ async fn test_register_validators_does_not_retry_on_429() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); // Run the PBS service - let config = to_pbs_config(chain, get_pbs_config(pbs_port), relays); - let state = PbsState::new(config, PathBuf::new()); + let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state.clone())); // Leave some time to start servers @@ -131,11 +131,11 @@ async fn test_register_validators_retries_on_500() -> Result<()> { tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); // Set retry limit to 3 - let mut pbs_config = get_pbs_config(pbs_port); + let mut pbs_config = get_pbs_static_config(pbs_port); pbs_config.register_validator_retry_limit = 3; let config = to_pbs_config(chain, pbs_config, relays); - let state = PbsState::new(config, PathBuf::new()); + let state = PbsState::new(config); tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state.clone())); tokio::time::sleep(Duration::from_millis(100)).await; diff --git a/tests/tests/signer_jwt_auth.rs b/tests/tests/signer_jwt_auth.rs index d1b65b3f..683a268a 100644 --- a/tests/tests/signer_jwt_auth.rs +++ b/tests/tests/signer_jwt_auth.rs @@ -1,51 +1,29 @@ use std::{collections::HashMap, time::Duration}; -use alloy::primitives::b256; use cb_common::{ - commit::{ - constants::{GET_PUBKEYS_PATH, REVOKE_MODULE_PATH}, - request::RevokeModuleRequest, - }, - config::{ModuleSigningConfig, load_module_signing_configs}, - types::ModuleId, - utils::{create_admin_jwt, create_jwt}, -}; -use cb_tests::{ - signer_service::{start_server, verify_pubkeys}, - utils::{self, setup_test_env}, + commit::{constants::GET_PUBKEYS_PATH, request::GetPubkeysResponse}, + config::StartSignerConfig, + signer::{SignerLoader, ValidatorKeysFormat}, + types::{Chain, ModuleId}, + utils::{bls_pubkey_from_hex, create_jwt}, }; +use cb_signer::service::SigningService; +use cb_tests::utils::{get_signer_config, get_start_signer_config, setup_test_env}; use eyre::Result; -use reqwest::StatusCode; +use reqwest::{Response, StatusCode}; use tracing::info; const JWT_MODULE: &str = "test-module"; const JWT_SECRET: &str = "test-jwt-secret"; -const ADMIN_SECRET: &str = "test-admin-secret"; - -async fn create_mod_signing_configs() -> HashMap { - let mut cfg = - utils::get_commit_boost_config(utils::get_pbs_static_config(utils::get_pbs_config(0))); - - let module_id = ModuleId(JWT_MODULE.to_string()); - let signing_id = b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - cfg.modules = Some(vec![utils::create_module_config(module_id.clone(), signing_id)]); - - let jwts = HashMap::from([(module_id.clone(), JWT_SECRET.to_string())]); - - load_module_signing_configs(&cfg, &jwts).unwrap() -} #[tokio::test] async fn test_signer_jwt_auth_success() -> Result<()> { setup_test_env(); let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20100, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for test module not found"); + let start_config = start_server(20100).await?; // Run a pubkeys request - let jwt = create_jwt(&module_id, &jwt_config.jwt_secret, GET_PUBKEYS_PATH, None)?; + let jwt = create_jwt(&module_id, JWT_SECRET)?; let client = reqwest::Client::new(); let url = format!("http://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); let response = client.get(&url).bearer_auth(&jwt).send().await?; @@ -60,11 +38,10 @@ async fn test_signer_jwt_auth_success() -> Result<()> { async fn test_signer_jwt_auth_fail() -> Result<()> { setup_test_env(); let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20101, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; + let start_config = start_server(20200).await?; // Run a pubkeys request - this should fail due to invalid JWT - let jwt = create_jwt(&module_id, "incorrect secret", GET_PUBKEYS_PATH, None)?; + let jwt = create_jwt(&module_id, "incorrect secret")?; let client = reqwest::Client::new(); let url = format!("http://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); let response = client.get(&url).bearer_auth(&jwt).send().await?; @@ -81,12 +58,10 @@ async fn test_signer_jwt_auth_fail() -> Result<()> { async fn test_signer_jwt_rate_limit() -> Result<()> { setup_test_env(); let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20102, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let mod_cfg = mod_cfgs.get(&module_id).expect("JWT config for test module not found"); + let start_config = start_server(20300).await?; // Run as many pubkeys requests as the fail limit - let jwt = create_jwt(&module_id, "incorrect secret", GET_PUBKEYS_PATH, None)?; + let jwt = create_jwt(&module_id, "incorrect secret")?; let client = reqwest::Client::new(); let url = format!("http://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); for _ in 0..start_config.jwt_auth_fail_limit { @@ -95,7 +70,7 @@ async fn test_signer_jwt_rate_limit() -> Result<()> { } // Run another request - this should fail due to rate limiting now - let jwt = create_jwt(&module_id, &mod_cfg.jwt_secret, GET_PUBKEYS_PATH, None)?; + let jwt = create_jwt(&module_id, JWT_SECRET)?; let response = client.get(&url).bearer_auth(&jwt).send().await?; assert!(response.status() == StatusCode::TOO_MANY_REQUESTS); @@ -110,101 +85,65 @@ async fn test_signer_jwt_rate_limit() -> Result<()> { Ok(()) } -#[tokio::test] -async fn test_signer_revoked_jwt_fail() -> Result<()> { +// Starts the signer moduler server on a separate task and returns its +// configuration +async fn start_server(port: u16) -> Result { setup_test_env(); - let admin_secret = ADMIN_SECRET.to_string(); - let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20400, &mod_cfgs, admin_secret.clone(), false).await?; - - // Run as many pubkeys requests as the fail limit - let jwt = create_jwt(&module_id, JWT_SECRET, GET_PUBKEYS_PATH, None)?; - let client = reqwest::Client::new(); - - // At first, test module should be allowed to request pubkeys - let url = format!("http://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); - let response = client.get(&url).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::OK); - - let revoke_body = RevokeModuleRequest { module_id: ModuleId(JWT_MODULE.to_string()) }; - let body_bytes = serde_json::to_vec(&revoke_body)?; - let admin_jwt = create_admin_jwt(admin_secret, REVOKE_MODULE_PATH, Some(&body_bytes))?; - - let revoke_url = format!("http://{}{}", start_config.endpoint, REVOKE_MODULE_PATH); - let response = - client.post(&revoke_url).json(&revoke_body).bearer_auth(&admin_jwt).send().await?; - assert!(response.status() == StatusCode::OK); - - // After revoke, test module shouldn't be allowed anymore - let response = client.get(&url).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::UNAUTHORIZED); - - Ok(()) -} - -#[tokio::test] -async fn test_signer_only_admin_can_revoke() -> Result<()> { - setup_test_env(); - let admin_secret = ADMIN_SECRET.to_string(); - let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20500, &mod_cfgs, admin_secret.clone(), false).await?; - - let revoke_body = RevokeModuleRequest { module_id: ModuleId(JWT_MODULE.to_string()) }; - let body_bytes = serde_json::to_vec(&revoke_body)?; + let chain = Chain::Hoodi; - // Run as many pubkeys requests as the fail limit - let jwt = create_jwt(&module_id, JWT_SECRET, REVOKE_MODULE_PATH, Some(&body_bytes))?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REVOKE_MODULE_PATH); - - // Module JWT shouldn't be able to revoke modules - let response = client.post(&url).json(&revoke_body).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::UNAUTHORIZED); - - // Admin should be able to revoke modules - let admin_jwt = create_admin_jwt(admin_secret, REVOKE_MODULE_PATH, Some(&body_bytes))?; - let response = client.post(&url).json(&revoke_body).bearer_auth(&admin_jwt).send().await?; - assert!(response.status() == StatusCode::OK); - - Ok(()) -} - -#[tokio::test] -async fn test_signer_admin_jwt_rate_limit() -> Result<()> { - setup_test_env(); - let admin_secret = ADMIN_SECRET.to_string(); + // Mock JWT secrets let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20510, &mod_cfgs, admin_secret.clone(), false).await?; - - let revoke_body = RevokeModuleRequest { module_id: ModuleId(JWT_MODULE.to_string()) }; - let body_bytes = serde_json::to_vec(&revoke_body)?; - - // Run as many pubkeys requests as the fail limit - let jwt = create_jwt(&module_id, JWT_SECRET, REVOKE_MODULE_PATH, Some(&body_bytes))?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REVOKE_MODULE_PATH); - - // Module JWT shouldn't be able to revoke modules - for _ in 0..start_config.jwt_auth_fail_limit { - let response = client.post(&url).json(&revoke_body).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::UNAUTHORIZED); + let mut jwts = HashMap::new(); + jwts.insert(module_id.clone(), JWT_SECRET.to_string()); + + // Create a signer config + let loader = SignerLoader::ValidatorsDir { + keys_path: "data/keystores/keys".into(), + secrets_path: "data/keystores/secrets".into(), + format: ValidatorKeysFormat::Lighthouse, + }; + let mut config = get_signer_config(loader); + config.port = port; + config.jwt_auth_fail_limit = 3; // Set a low fail limit for testing + config.jwt_auth_fail_timeout_seconds = 3; // Set a short timeout for testing + let start_config = get_start_signer_config(config, chain, jwts); + + // Run the Signer + let server_handle = tokio::spawn(SigningService::run(start_config.clone())); + + // Make sure the server is running + tokio::time::sleep(Duration::from_millis(100)).await; + if server_handle.is_finished() { + return Err(eyre::eyre!( + "Signer service failed to start: {}", + server_handle.await.unwrap_err() + )); } + Ok(start_config) +} - // Run another request - this should fail due to rate limiting now - let admin_jwt = create_admin_jwt(admin_secret, REVOKE_MODULE_PATH, Some(&body_bytes))?; - let response = client.post(&url).json(&revoke_body).bearer_auth(&admin_jwt).send().await?; - assert!(response.status() == StatusCode::TOO_MANY_REQUESTS); - - // Wait for the rate limit timeout - tokio::time::sleep(Duration::from_secs(start_config.jwt_auth_fail_timeout_seconds as u64)) - .await; - - // Now the next request should succeed - let response = client.post(&url).json(&revoke_body).bearer_auth(&admin_jwt).send().await?; +// Verifies that the pubkeys returned by the server match the pubkeys in the +// test data +async fn verify_pubkeys(response: Response) -> Result<()> { + // Verify the expected pubkeys are returned assert!(response.status() == StatusCode::OK); - + let pubkey_json = response.json::().await?; + assert_eq!(pubkey_json.keys.len(), 2); + let expected_pubkeys = vec![ + bls_pubkey_from_hex( + "883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4", + )?, + bls_pubkey_from_hex( + "b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9", + )?, + ]; + for expected in expected_pubkeys { + assert!( + pubkey_json.keys.iter().any(|k| k.consensus == expected), + "Expected pubkey not found: {:?}", + expected + ); + info!("Server returned expected pubkey: {:?}", expected); + } Ok(()) } diff --git a/tests/tests/signer_jwt_auth_cleanup.rs b/tests/tests/signer_jwt_auth_cleanup.rs deleted file mode 100644 index d6fde2a4..00000000 --- a/tests/tests/signer_jwt_auth_cleanup.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{collections::HashMap, time::Duration}; - -use alloy::primitives::b256; -use cb_common::{ - commit::constants::GET_PUBKEYS_PATH, - config::{ModuleSigningConfig, load_module_signing_configs}, - types::ModuleId, - utils::create_jwt, -}; -use cb_tests::{ - signer_service::start_server, - utils::{self}, -}; -use eyre::Result; -use reqwest::StatusCode; - -const JWT_MODULE: &str = "test-module"; -const JWT_SECRET: &str = "test-jwt-secret"; -const ADMIN_SECRET: &str = "test-admin-secret"; - -async fn create_mod_signing_configs() -> HashMap { - let mut cfg = - utils::get_commit_boost_config(utils::get_pbs_static_config(utils::get_pbs_config(0))); - - let module_id = ModuleId(JWT_MODULE.to_string()); - let signing_id = b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - cfg.modules = Some(vec![utils::create_module_config(module_id.clone(), signing_id)]); - - let jwts = HashMap::from([(module_id.clone(), JWT_SECRET.to_string())]); - - load_module_signing_configs(&cfg, &jwts).unwrap() -} - -#[tokio::test] -#[tracing_test::traced_test] -async fn test_signer_jwt_fail_cleanup() -> Result<()> { - // setup_test_env() isn't used because we want to capture logs with tracing_test - let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20102, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let mod_cfg = mod_cfgs.get(&module_id).expect("JWT config for test module not found"); - - // Run as many pubkeys requests as the fail limit - let jwt = create_jwt(&module_id, "incorrect secret", GET_PUBKEYS_PATH, None)?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); - for _ in 0..start_config.jwt_auth_fail_limit { - let response = client.get(&url).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::UNAUTHORIZED); - } - - // Run another request - this should fail due to rate limiting now - let jwt = create_jwt(&module_id, &mod_cfg.jwt_secret, GET_PUBKEYS_PATH, None)?; - let response = client.get(&url).bearer_auth(&jwt).send().await?; - assert!(response.status() == StatusCode::TOO_MANY_REQUESTS); - - // Wait until the cleanup task should have run properly, takes a while for the - // timing to work out - tokio::time::sleep(Duration::from_secs( - (start_config.jwt_auth_fail_timeout_seconds * 3) as u64, - )) - .await; - - // Make sure the cleanup message was logged - it's all internal state so without - // refactoring or exposing it, this is the easiest way to check if it triggered - assert!(logs_contain("Cleaned up 1 old JWT auth failure entries")); - - Ok(()) -} diff --git a/tests/tests/signer_request_sig.rs b/tests/tests/signer_request_sig.rs deleted file mode 100644 index 78efbf9e..00000000 --- a/tests/tests/signer_request_sig.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::collections::HashMap; - -use alloy::primitives::{b256, hex}; -use cb_common::{ - commit::{ - constants::REQUEST_SIGNATURE_BLS_PATH, request::SignConsensusRequest, - response::BlsSignResponse, - }, - config::{ModuleSigningConfig, load_module_signing_configs}, - types::{BlsPublicKey, BlsSignature, Chain, ModuleId}, - utils::create_jwt, -}; -use cb_tests::{ - signer_service::start_server, - utils::{self, setup_test_env}, -}; -use eyre::Result; -use reqwest::StatusCode; - -const MODULE_ID_1: &str = "test-module"; -const MODULE_ID_2: &str = "another-module"; -const PUBKEY_1: [u8; 48] = hex!( - "883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4" -); -const ADMIN_SECRET: &str = "test-admin-secret"; - -async fn create_mod_signing_configs() -> HashMap { - let mut cfg = - utils::get_commit_boost_config(utils::get_pbs_static_config(utils::get_pbs_config(0))); - - let module_id_1 = ModuleId(MODULE_ID_1.to_string()); - let signing_id_1 = b256!("0x6a33a23ef26a4836979edff86c493a69b26ccf0b4a16491a815a13787657431b"); - let module_id_2 = ModuleId(MODULE_ID_2.to_string()); - let signing_id_2 = b256!("0x61fe00135d7b4912a8c63ada215ac2e62326e6e7b30f49a29fcf9779d7ad800d"); - - cfg.modules = Some(vec![ - utils::create_module_config(module_id_1.clone(), signing_id_1), - utils::create_module_config(module_id_2.clone(), signing_id_2), - ]); - - let jwts = HashMap::from([ - (module_id_1.clone(), "supersecret".to_string()), - (module_id_2.clone(), "anothersecret".to_string()), - ]); - - load_module_signing_configs(&cfg, &jwts).unwrap() -} - -/// Makes sure the signer service signs requests correctly, using the module's -/// signing ID -#[tokio::test] -async fn test_signer_sign_request_good() -> Result<()> { - setup_test_env(); - let module_id = ModuleId(MODULE_ID_1.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20200, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for test module not found"); - - // Send a signing request - let object_root = b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let nonce: u64 = 101; - let pubkey = BlsPublicKey::deserialize(&PUBKEY_1).unwrap(); - let request = SignConsensusRequest { pubkey: pubkey.clone(), object_root, nonce }; - let payload_bytes = serde_json::to_vec(&request)?; - let jwt = create_jwt( - &module_id, - &jwt_config.jwt_secret, - REQUEST_SIGNATURE_BLS_PATH, - Some(&payload_bytes), - )?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); - let response = client.post(&url).json(&request).bearer_auth(&jwt).send().await?; - - // Verify the response is successful - assert!(response.status() == StatusCode::OK); - - // Verify the signature is returned - let sig_response = response.json::().await?; - let expected = BlsSignResponse::new( - pubkey, - object_root, - mod_cfgs.get(&module_id).unwrap().signing_id, - nonce, - Chain::Hoodi.id(), - BlsSignature::deserialize(&hex!("0xb653034a6da0e516cb999d6bbcd5ddd8dde9695322a94aefcd3049e6235e0f4f63b13d81ddcd80d4e1e698c3f88c3b440ae696650ccef2f22329afb4ffecec85a34523e25920ceced54c5bc31168174a3b352977750c222c1c25f72672467e5c")).unwrap()); - assert_eq!(sig_response, expected, "Signature response does not match expected value"); - - Ok(()) -} - -/// Makes sure the signer service returns a signature that is different for each -/// module -#[tokio::test] -async fn test_signer_sign_request_different_module() -> Result<()> { - setup_test_env(); - let module_id = ModuleId(MODULE_ID_2.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20201, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for 2nd test module not found"); - - // Send a signing request - let object_root = b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let nonce: u64 = 101; - let pubkey = BlsPublicKey::deserialize(&PUBKEY_1).unwrap(); - let request = SignConsensusRequest { pubkey: pubkey.clone(), object_root, nonce }; - let payload_bytes = serde_json::to_vec(&request)?; - let jwt = create_jwt( - &module_id, - &jwt_config.jwt_secret, - REQUEST_SIGNATURE_BLS_PATH, - Some(&payload_bytes), - )?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); - let response = client.post(&url).json(&request).bearer_auth(&jwt).send().await?; - - // Verify the response is successful - assert!(response.status() == StatusCode::OK); - - // Verify the signature is returned - let sig_response = response.json::().await?; - assert_eq!(sig_response.pubkey, pubkey, "Public key does not match expected value"); - assert_eq!(sig_response.object_root, object_root, "Object root does not match expected value"); - assert_eq!( - sig_response.module_signing_id, - mod_cfgs.get(&module_id).unwrap().signing_id, - "Module signing ID does not match expected value" - ); - assert_ne!( - sig_response.signature, BlsSignature::deserialize(&hex!("0xb653034a6da0e516cb999d6bbcd5ddd8dde9695322a94aefcd3049e6235e0f4f63b13d81ddcd80d4e1e698c3f88c3b440ae696650ccef2f22329afb4ffecec85a34523e25920ceced54c5bc31168174a3b352977750c222c1c25f72672467e5c")).unwrap(), - "Signature matches the reference signature, which should not happen" - ); - - Ok(()) -} - -/// Makes sure the signer service does not allow requests for JWTs that do -/// not match the JWT hash -#[tokio::test] -async fn test_signer_sign_request_incorrect_hash() -> Result<()> { - setup_test_env(); - let module_id = ModuleId(MODULE_ID_2.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20202, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for 2nd test module not found"); - - // Send a signing request - let fake_object_root = - b256!("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"); - let nonce: u64 = 101; - let pubkey = BlsPublicKey::deserialize(&PUBKEY_1).unwrap(); - let fake_request = - SignConsensusRequest { pubkey: pubkey.clone(), object_root: fake_object_root, nonce }; - let fake_payload_bytes = serde_json::to_vec(&fake_request)?; - let true_object_root = - b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let true_request = SignConsensusRequest { pubkey, object_root: true_object_root, nonce }; - let jwt = create_jwt( - &module_id, - &jwt_config.jwt_secret, - REQUEST_SIGNATURE_BLS_PATH, - Some(&fake_payload_bytes), - )?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); - let response = client.post(&url).json(&true_request).bearer_auth(&jwt).send().await?; - - // Verify that authorization failed - assert!(response.status() == StatusCode::UNAUTHORIZED); - Ok(()) -} - -/// Makes sure the signer service does not allow signer requests for JWTs that -/// do not include a payload hash -#[tokio::test] -async fn test_signer_sign_request_missing_hash() -> Result<()> { - setup_test_env(); - let module_id = ModuleId(MODULE_ID_2.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20203, &mod_cfgs, ADMIN_SECRET.to_string(), false).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for 2nd test module not found"); - - // Send a signing request - let nonce: u64 = 101; - let pubkey = BlsPublicKey::deserialize(&PUBKEY_1).unwrap(); - let object_root = b256!("0x0123456789012345678901234567890123456789012345678901234567890123"); - let request = SignConsensusRequest { pubkey, object_root, nonce }; - let jwt = create_jwt(&module_id, &jwt_config.jwt_secret, REQUEST_SIGNATURE_BLS_PATH, None)?; - let client = reqwest::Client::new(); - let url = format!("http://{}{}", start_config.endpoint, REQUEST_SIGNATURE_BLS_PATH); - let response = client.post(&url).json(&request).bearer_auth(&jwt).send().await?; - - // Verify that authorization failed - assert!(response.status() == StatusCode::UNAUTHORIZED); - Ok(()) -} diff --git a/tests/tests/signer_tls.rs b/tests/tests/signer_tls.rs deleted file mode 100644 index 2df98d73..00000000 --- a/tests/tests/signer_tls.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::collections::HashMap; - -use alloy::primitives::b256; -use cb_common::{ - commit::constants::GET_PUBKEYS_PATH, - config::{ModuleSigningConfig, load_module_signing_configs}, - types::ModuleId, - utils::create_jwt, -}; -use cb_tests::{ - signer_service::{start_server, verify_pubkeys}, - utils::{self, setup_test_env}, -}; -use eyre::{Result, bail}; -use reqwest::Certificate; - -const JWT_MODULE: &str = "test-module"; -const JWT_SECRET: &str = "test-jwt-secret"; -const ADMIN_SECRET: &str = "test-admin-secret"; - -async fn create_mod_signing_configs() -> HashMap { - let mut cfg = - utils::get_commit_boost_config(utils::get_pbs_static_config(utils::get_pbs_config(0))); - - let module_id = ModuleId(JWT_MODULE.to_string()); - let signing_id = b256!("0101010101010101010101010101010101010101010101010101010101010101"); - - cfg.modules = Some(vec![utils::create_module_config(module_id.clone(), signing_id)]); - - let jwts = HashMap::from([(module_id.clone(), JWT_SECRET.to_string())]); - - load_module_signing_configs(&cfg, &jwts).unwrap() -} - -#[tokio::test] -async fn test_signer_tls() -> Result<()> { - setup_test_env(); - let module_id = ModuleId(JWT_MODULE.to_string()); - let mod_cfgs = create_mod_signing_configs().await; - let start_config = start_server(20100, &mod_cfgs, ADMIN_SECRET.to_string(), true).await?; - let jwt_config = mod_cfgs.get(&module_id).expect("JWT config for test module not found"); - - // Run a pubkeys request - let jwt = create_jwt(&module_id, &jwt_config.jwt_secret, GET_PUBKEYS_PATH, None)?; - let cert = match start_config.tls_certificates { - Some(ref certificates) => &certificates.0, - None => bail!("TLS certificates not found in start config"), - }; - let client = - reqwest::Client::builder().add_root_certificate(Certificate::from_pem(cert)?).build()?; - let url = format!("https://{}{}", start_config.endpoint, GET_PUBKEYS_PATH); - let response = client.get(&url).bearer_auth(&jwt).send().await?; - - // Verify the expected pubkeys are returned - verify_pubkeys(response).await?; - - Ok(()) -}