├── .dockerignore ├── .github └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── release.yml │ ├── security-audit.yml │ └── test-docs.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── SECURITY.md ├── TERMS-OF-USE.md ├── api └── signer-api.yml ├── audit └── Sigma_Prime_Commit_Boost_Client_Security_Assessment_Report_v2_0.pdf ├── benches └── pbs │ ├── Cargo.toml │ ├── README.md │ ├── bench-config.toml │ ├── bench.docker-compose.yml │ └── src │ ├── config.rs │ └── main.rs ├── bin ├── Cargo.toml ├── cli.rs ├── pbs.rs ├── signer.rs └── src │ └── lib.rs ├── config.example.toml ├── crates ├── cli │ ├── Cargo.toml │ └── src │ │ ├── docker_init.rs │ │ └── lib.rs ├── common │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── abi │ │ └── LidoNORegistry.json │ │ ├── commit │ │ ├── client.rs │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── mod.rs │ │ └── request.rs │ │ ├── config │ │ ├── constants.rs │ │ ├── log.rs │ │ ├── metrics.rs │ │ ├── mod.rs │ │ ├── module.rs │ │ ├── mux.rs │ │ ├── pbs.rs │ │ ├── signer.rs │ │ └── utils.rs │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── pbs │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── event.rs │ │ ├── mod.rs │ │ ├── relay.rs │ │ └── types │ │ │ ├── beacon_block.rs │ │ │ ├── blinded_block_body.rs │ │ │ ├── blobs_bundle.rs │ │ │ ├── execution_payload.rs │ │ │ ├── execution_requests.rs │ │ │ ├── get_header.rs │ │ │ ├── kzg.rs │ │ │ ├── mod.rs │ │ │ ├── spec.rs │ │ │ ├── testdata │ │ │ ├── execution-payload-deneb.json │ │ │ ├── execution-payload-deneb.ssz │ │ │ ├── get-header-response.json │ │ │ ├── get-header-response.ssz │ │ │ ├── signed-blinded-beacon-block-deneb-2.json │ │ │ ├── signed-blinded-beacon-block-deneb.json │ │ │ ├── signed-blinded-beacon-block-electra-2.json │ │ │ ├── signed-blinded-beacon-block-electra-2.ssz │ │ │ └── signed-blinded-beacon-block-electra.json │ │ │ └── utils.rs │ │ ├── signature.rs │ │ ├── signer │ │ ├── loader.rs │ │ ├── mod.rs │ │ ├── schemes │ │ │ ├── bls.rs │ │ │ ├── ecdsa.rs │ │ │ └── mod.rs │ │ ├── store.rs │ │ └── types.rs │ │ ├── types.rs │ │ └── utils.rs ├── metrics │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── provider.rs ├── pbs │ ├── Cargo.toml │ └── src │ │ ├── api.rs │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── metrics.rs │ │ ├── mev_boost │ │ ├── get_header.rs │ │ ├── mod.rs │ │ ├── register_validator.rs │ │ ├── reload.rs │ │ ├── status.rs │ │ └── submit_block.rs │ │ ├── routes │ │ ├── get_header.rs │ │ ├── mod.rs │ │ ├── register_validator.rs │ │ ├── reload.rs │ │ ├── router.rs │ │ ├── status.rs │ │ └── submit_block.rs │ │ ├── service.rs │ │ ├── state.rs │ │ └── utils.rs └── signer │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ ├── constants.rs │ ├── error.rs │ ├── lib.rs │ ├── manager │ ├── dirk.rs │ ├── local.rs │ └── mod.rs │ ├── metrics.rs │ ├── proto │ ├── google.api.rs │ ├── mod.rs │ └── v1.rs │ └── service.rs ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── architecture │ │ ├── img │ │ │ └── architecture.png │ │ └── overview.md │ ├── developing │ │ ├── commit-module.md │ │ ├── custom-modules.md │ │ └── environment-setup.md │ ├── get_started │ │ ├── building.md │ │ ├── configuration.md │ │ ├── overview.md │ │ ├── running │ │ │ ├── binary.md │ │ │ ├── docker.md │ │ │ └── metrics.md │ │ └── troubleshooting.md │ ├── intro.md │ └── overview.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── SwaggerUI.js │ ├── css │ │ └── custom.css │ └── pages │ │ └── api.js ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── icon.png │ │ ├── logo.png │ │ └── overview.png └── yarn.lock ├── examples ├── builder_log │ ├── Cargo.toml │ ├── Dockerfile │ └── src │ │ └── main.rs ├── configs │ ├── custom_chain.toml │ ├── dirk_signer.toml │ ├── minimal.toml │ ├── pbs_metrics.toml │ └── pbs_mux.toml ├── da_commit │ ├── Cargo.toml │ ├── Dockerfile │ └── src │ │ └── main.rs └── status_api │ ├── Cargo.toml │ ├── Dockerfile │ └── src │ └── main.rs ├── justfile ├── provisioning ├── build.Dockerfile ├── grafana │ ├── pbs_public_dashboard.json │ ├── signer_public_dashboard.json │ └── system_public_dashboard.json ├── k8s │ ├── README.md │ └── commit-boost │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ ├── hpa.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ ├── servicemonitor.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ │ ├── values.examples.yaml │ │ └── values.yaml ├── pbs.Dockerfile ├── pectra-config.yml ├── protoc.sh └── signer.Dockerfile ├── rust-toolchain.toml ├── rustfmt.toml ├── taplo.toml └── tests ├── Cargo.toml ├── data ├── configs │ └── pbs.happy.toml ├── helder_spec.yml ├── holesky_spec.json ├── hoodi_spec.json ├── keys.example.json ├── keystores │ ├── keys │ │ ├── 0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4 │ │ │ └── voting-keystore.json │ │ └── 0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9 │ │ │ └── voting-keystore.json │ ├── lodestar-secrets │ │ ├── 0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4 │ │ └── 0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9 │ ├── prysm │ │ ├── direct │ │ │ └── accounts │ │ │ │ └── all-accounts.keystore.json │ │ ├── empty_pass │ │ └── keymanageropts.json │ ├── pubkeys.json │ ├── secrets │ │ ├── 0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4 │ │ └── 0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9 │ ├── teku-keys │ │ ├── 0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4.json │ │ └── 0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9.json │ └── teku-secrets │ │ ├── 0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4.txt │ │ └── 0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9.txt ├── mainnet_spec_data.json ├── mux_keys.example.json ├── proxy │ ├── keys │ │ └── 0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118 │ │ │ └── TEST_MODULE │ │ │ └── bls │ │ │ ├── 0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba.json │ │ │ └── 0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba.sig │ └── secrets │ │ └── 0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118 │ │ └── TEST_MODULE │ │ └── bls │ │ └── 0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba ├── registration_holesky.json ├── sepolia_spec_data.json ├── signed_blinded_block_holesky.json └── submit_block_response_holesky.json ├── src ├── lib.rs ├── mock_relay.rs ├── mock_validator.rs └── utils.rs └── tests ├── config.rs ├── payloads.rs ├── pbs_get_header.rs ├── pbs_get_status.rs ├── pbs_mux.rs ├── pbs_post_blinded_blocks.rs └── pbs_post_validators.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | Dockerfile 3 | .dockerignore 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: ["**"] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | lint: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v4 17 | with: 18 | submodules: true 19 | 20 | - name: Setup Rust cache 21 | uses: Swatinem/rust-cache@v2 22 | with: 23 | cache-on-failure: true 24 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 25 | 26 | - name: Install Rust toolchain 27 | uses: dtolnay/rust-toolchain@master 28 | with: 29 | toolchain: nightly-2025-02-26 30 | components: clippy, rustfmt 31 | 32 | - name: Install protoc 33 | run: sudo provisioning/protoc.sh 34 | 35 | - name: Setup just 36 | uses: extractions/setup-just@v3 37 | with: 38 | just-version: 1.40.0 39 | 40 | - name: Check compilation 41 | run: cargo check 42 | 43 | - name: Check formatting 44 | run: just fmt-check 45 | 46 | - name: Check clippy 47 | run: just clippy 48 | 49 | - name: Run tests 50 | run: just test 51 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | build: 12 | name: Build Docusaurus 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | working-directory: ./docs 26 | 27 | - name: Build website 28 | run: npm run build 29 | working-directory: ./docs 30 | 31 | - name: Upload Build Artifact 32 | uses: actions/upload-pages-artifact@v3 33 | with: 34 | path: docs/build 35 | 36 | deploy: 37 | name: Deploy to GitHub Pages 38 | needs: build 39 | 40 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 41 | permissions: 42 | pages: write # to deploy to Pages 43 | id-token: write # to verify the deployment originates from an appropriate source 44 | 45 | # Deploy to the github-pages environment 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 55 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | on: 3 | pull_request: 4 | paths: [Cargo.lock] 5 | push: 6 | branches: [main] 7 | paths: [Cargo.lock] 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | issues: write 13 | checks: write 14 | pull-requests: read 15 | contents: read 16 | 17 | jobs: 18 | security_audit: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions-rs/audit-check@v1.2.0 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test-docs.yml: -------------------------------------------------------------------------------- 1 | name: Test Deploy Docs 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | test-deploy: 12 | name: Test deployment 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 20 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | working-directory: ./docs 26 | 27 | - name: Test build website 28 | run: npm run build 29 | working-directory: ./docs 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | build/ 6 | 7 | # These are backup files generated by rustfmt 8 | **/*.rs.bk 9 | 10 | # MSVC Windows builds of rustc generate these, which store debugging information 11 | *.pdb 12 | 13 | *.env 14 | cb.docker-compose.yml 15 | targets.json 16 | .idea/ 17 | logs 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crates/signer/proto"] 2 | path = crates/signer/proto 3 | url = https://github.com/wealdtech/eth2-signer-api.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors Guide 2 | 3 | Commit-Boost is community driven and we welcome all contributions. 4 | 5 | If you contribute to this repo, your contributions will be made to the project under both [Apache 2.0](/LICENSE-APACHE) and the [MIT](/LICENSE-MIT) 6 | license. 7 | 8 | There are fundamentally three ways you can contribute: 9 | 10 | 1. **By opening an issue:** For example, if you believe that you have uncovered a bug 11 | 2. **By adding context:** Providing additional context to existing issues 12 | 3. **By resolving issues:** For example by opening a PR 13 | 14 | ### Contributions Related to Spelling and Grammar 15 | 16 | For first-time contributors we will not be accepting PRs that only fix spelling or grammatical errors in documentation, code or 17 | elsewhere. 18 | 19 | ### Submitting a bug report 20 | 21 | If you believe you have found a security issues, please **do not** open a public issue but check out our [Security](/SECURITY.md) policy instead. 22 | 23 | If you find a non-security related bug, please file an issue detailing: 24 | - Release version you are using 25 | - Code snippets and/or logs that can help identifying the bug 26 | - Concrete steps to reproduce the bug 27 | 28 | ### Submitting a feature request 29 | If you have a suggestion for a new feature or an improvement to an existing one, please open a new issue and provide: 30 | 31 | - A clear and concise description of the feature you’d like to see implemented. 32 | - An explanation of why this feature would be helpful or what problem it would solve. 33 | - Any relevant examples, mock-ups, or references that can help illustrate your idea. 34 | 35 | We value your input and appreciate all suggestions to help make this project better! 36 | 37 | ### Resolving an issue 38 | We welcome PRs from any community member. Before making a large change, it is usually a good idea to first open an issue describing the change to solicit feedback and guidance. 39 | This will increase the likelihood of the PR getting merged. 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["benches/*", "bin", "crates/*", "examples/builder_log", "examples/da_commit", "examples/status_api", "tests"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | edition = "2021" 7 | rust-version = "1.83" 8 | version = "0.8.0-rc.2" 9 | 10 | [workspace.dependencies] 11 | aes = "0.8" 12 | alloy = { version = "0.12", features = [ 13 | "full", 14 | "getrandom", 15 | "providers", 16 | "rpc-types-beacon", 17 | "serde", 18 | "signer-local", 19 | "ssz", 20 | ] } 21 | async-trait = "0.1.80" 22 | axum = { version = "0.8.1", features = ["macros"] } 23 | axum-extra = { version = "0.10.0", features = ["typed-header"] } 24 | base64 = "0.22.1" 25 | bimap = { version = "0.6.3", features = ["serde"] } 26 | blsful = "2.5" 27 | blst = "0.3.11" 28 | cb-cli = { path = "crates/cli" } 29 | cb-common = { path = "crates/common" } 30 | cb-metrics = { path = "crates/metrics" } 31 | cb-pbs = { path = "crates/pbs" } 32 | cb-signer = { path = "crates/signer" } 33 | cipher = "0.4" 34 | clap = { version = "4.5.4", features = ["derive", "env"] } 35 | color-eyre = "0.6.3" 36 | ctr = "0.9.2" 37 | derive_more = { version = "2.0.1", features = ["deref", "display", "from", "into"] } 38 | docker-compose-types = "0.16.0" 39 | eth2_keystore = { git = "https://github.com/sigp/lighthouse", rev = "8d058e4040b765a96aa4968f4167af7571292be2" } 40 | ethereum_serde_utils = "0.7.0" 41 | ethereum_ssz = "0.8" 42 | ethereum_ssz_derive = "0.8" 43 | eyre = "0.6.12" 44 | futures = "0.3.30" 45 | headers = "0.4.0" 46 | indexmap = "2.2.6" 47 | jsonwebtoken = { version = "9.3.1", default-features = false } 48 | lazy_static = "1.5.0" 49 | parking_lot = "0.12.3" 50 | pbkdf2 = "0.12.2" 51 | prometheus = "0.13.4" 52 | prost = "0.13.4" 53 | rand = { version = "0.9", features = ["os_rng"] } 54 | reqwest = { version = "0.12.4", features = ["json", "stream"] } 55 | serde = { version = "1.0.202", features = ["derive"] } 56 | serde_json = "1.0.117" 57 | serde_yaml = "0.9.33" 58 | sha2 = "0.10.8" 59 | ssz_types = "0.10" 60 | thiserror = "2.0.12" 61 | tokio = { version = "1.37.0", features = ["full"] } 62 | toml = "0.8.13" 63 | tonic = { version = "0.12.3", features = ["channel", "prost", "tls"] } 64 | tonic-build = "0.12.3" 65 | tracing = "0.1.40" 66 | tracing-appender = "0.2.3" 67 | tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } 68 | tree_hash = "0.9" 69 | tree_hash_derive = "0.9" 70 | typenum = "1.17.0" 71 | unicode-normalization = "0.1.24" 72 | url = { version = "2.5.0", features = ["serde"] } 73 | uuid = { version = "1.8.0", features = ["fast-rng", "serde", "v4"] } 74 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Commit-Boost 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commit-Boost 2 | 3 | [![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) 4 | [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://commit-boost.github.io/commit-boost-client/) 5 | [![Release](https://img.shields.io/github/v/release/Commit-Boost/commit-boost-client)](https://github.com/Commit-Boost/commit-boost-client/releases) 6 | [![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) 7 | [![X](https://img.shields.io/twitter/follow/Commit_Boost)](https://x.com/Commit_Boost) 8 | 9 | A new Ethereum validator sidecar focused on standardizing the last mile of communication between validators and third-party protocols. 10 | 11 | ## Overview 12 | Commit-Boost is a modular sidecar that allows Ethereum validators to opt-in to different commitment protocols 13 | 14 | ### For node operators 15 | - Run a single sidecar with support for MEV-Boost and other proposer commitments protocols, such as preconfirmations and inclusion lists 16 | - Out-of-the-box support for metrics reporting and dashboards to have clear insight into what is happening in your validator 17 | - Plug-in system to add custom modules, e.g. receive a notification on Telegram if a relay fails to deliver a block 18 | 19 | ### For developers 20 | - A modular platform to develop and distribute proposer commitments protocols 21 | - A single API to interact with validators 22 | - Support for hard-forks and new protocol requirements 23 | 24 | ## Get started 25 | - [Node operators](https://commit-boost.github.io/commit-boost-client/category/get-started) 26 | - [Developers](https://commit-boost.github.io/commit-boost-client/category/developing). Check out also the [examples](/examples) 27 | 28 | ## Audit 29 | Commit-Boost received an audit from [Sigma Prime](https://sigmaprime.io/). Find the report [here](/audit/Sigma_Prime_Commit_Boost_Client_Security_Assessment_Report_v2_0.pdf). 30 | 31 | ## Acknowledgements 32 | - [MEV boost](https://github.com/flashbots/mev-boost) 33 | - [Reth](https://github.com/paradigmxyz/reth) 34 | - [Lighthouse](https://github.com/sigp/lighthouse) 35 | 36 | ## License 37 | MIT + Apache-2.0 38 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Please see [Releases](https://github.com/Commit-Boost/commit-boost-client/releases). We recommend using the [most recently released version](https://github.com/Commit-Boost/commit-boost-client/releases/latest). 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please send vulnerability reports to commit.boost@gmail.com 10 | 11 | **Please do not file a public ticket** mentioning the vulnerability, as doing so could increase the likelihood of the vulnerability being used before a fix has been created, released and installed on the network. 12 | -------------------------------------------------------------------------------- /audit/Sigma_Prime_Commit_Boost_Client_Security_Assessment_Report_v2_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/audit/Sigma_Prime_Commit_Boost_Client_Security_Assessment_Report_v2_0.pdf -------------------------------------------------------------------------------- /benches/pbs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-bench-pbs" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | alloy.workspace = true 9 | cb-common.workspace = true 10 | cb-tests = { path = "../../tests" } 11 | comfy-table = "7.1.1" 12 | histogram = "0.11.0" 13 | rand.workspace = true 14 | reqwest.workspace = true 15 | serde.workspace = true 16 | serde_json.workspace = true 17 | tokio.workspace = true 18 | toml.workspace = true 19 | -------------------------------------------------------------------------------- /benches/pbs/bench-config.toml: -------------------------------------------------------------------------------- 1 | chain = "Holesky" 2 | 3 | [pbs] 4 | port = 18750 5 | late_in_slot_time_ms = 1000000000000 # skip late in slot checks 6 | 7 | [[relays]] 8 | id = "bench_mock_relay" 9 | url = "http://0xb060572f535ba5615b874ebfef757fbe6825352ad257e31d724e57fe25a067a13cfddd0f00cb17bf3a3d2e901a380c17@172.17.0.1:18450" # do not change this 10 | 11 | 12 | [benchmark] 13 | n_slots = 5 14 | headers_per_slot = 1000 15 | 16 | [[bench]] 17 | id = "mev_boost" 18 | url = "http://0.0.0.0:18650" 19 | 20 | [[bench]] 21 | id = "commit_boost" 22 | url = "http://0.0.0.0:18750" 23 | -------------------------------------------------------------------------------- /benches/pbs/bench.docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | cb_pbs: 3 | image: ghcr.io/commit-boost/pbs:v0.1.0 4 | container_name: cb_pbs 5 | ports: 6 | - 18750:18750 7 | environment: 8 | CB_CONFIG: /cb-config.toml 9 | volumes: 10 | - ./bench-config.toml:/cb-config.toml:ro 11 | -------------------------------------------------------------------------------- /benches/pbs/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use cb_common::config::CommitBoostConfig; 4 | use reqwest::Url; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Config { 9 | #[serde(flatten)] 10 | pub commit_boost: CommitBoostConfig, 11 | pub benchmark: BenchmarkConfig, 12 | pub bench: Vec, 13 | } 14 | 15 | #[derive(Debug, Serialize, Deserialize)] 16 | pub struct BenchmarkConfig { 17 | pub n_slots: u64, 18 | pub headers_per_slot: u64, 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize)] 22 | pub struct BenchConfig { 23 | pub id: String, 24 | pub url: Url, 25 | } 26 | 27 | pub fn load_static_config() -> Config { 28 | let path = 29 | std::env::args().nth(1).expect("missing config path. Add config eg. `bench-config.toml'"); 30 | let config_file = fs::read_to_string(&path) 31 | .unwrap_or_else(|_| panic!("Unable to find config file: '{}'", path)); 32 | let config: Config = toml::from_str(&config_file).expect("failed to parse toml"); 33 | 34 | config 35 | } 36 | -------------------------------------------------------------------------------- /bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "commit-boost" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | cb-cli.workspace = true 9 | cb-common.workspace = true 10 | cb-metrics.workspace = true 11 | cb-pbs.workspace = true 12 | cb-signer.workspace = true 13 | clap.workspace = true 14 | color-eyre.workspace = true 15 | eyre.workspace = true 16 | tokio.workspace = true 17 | tracing.workspace = true 18 | tree_hash.workspace = true 19 | tree_hash_derive.workspace = true 20 | 21 | [[bin]] 22 | name = "commit-boost-cli" 23 | path = "cli.rs" 24 | 25 | [[bin]] 26 | name = "commit-boost-pbs" 27 | path = "pbs.rs" 28 | 29 | [[bin]] 30 | name = "commit-boost-signer" 31 | path = "signer.rs" 32 | -------------------------------------------------------------------------------- /bin/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Main entry point of the Commit-Boost CLI 4 | #[tokio::main] 5 | async fn main() -> eyre::Result<()> { 6 | color_eyre::install()?; 7 | // set default backtrace unless provided 8 | if std::env::var_os("RUST_BACKTRACE").is_none() { 9 | std::env::set_var("RUST_BACKTRACE", "1"); 10 | } 11 | 12 | let args = cb_cli::Args::parse(); 13 | 14 | args.run().await 15 | } 16 | -------------------------------------------------------------------------------- /bin/pbs.rs: -------------------------------------------------------------------------------- 1 | use cb_common::{ 2 | config::{load_pbs_config, LogsSettings, PBS_MODULE_NAME}, 3 | utils::{initialize_tracing_log, wait_for_signal}, 4 | }; 5 | use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; 6 | use clap::Parser; 7 | use eyre::Result; 8 | use tracing::{error, info}; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | color_eyre::install()?; 13 | 14 | // set default backtrace unless provided 15 | if std::env::var_os("RUST_BACKTRACE").is_none() { 16 | std::env::set_var("RUST_BACKTRACE", "1"); 17 | } 18 | let _guard = initialize_tracing_log(PBS_MODULE_NAME, LogsSettings::from_env_config()?); 19 | 20 | let _args = cb_cli::PbsArgs::parse(); 21 | 22 | let pbs_config = load_pbs_config().await?; 23 | 24 | PbsService::init_metrics(pbs_config.chain)?; 25 | let state = PbsState::new(pbs_config); 26 | let server = PbsService::run::<_, DefaultBuilderApi>(state); 27 | 28 | tokio::select! { 29 | maybe_err = server => { 30 | if let Err(err) = maybe_err { 31 | error!(%err, "PBS service unexpectedly stopped"); 32 | eprintln!("PBS service unexpectedly stopped: {}", err); 33 | } 34 | }, 35 | _ = wait_for_signal() => { 36 | info!("shutting down"); 37 | } 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /bin/signer.rs: -------------------------------------------------------------------------------- 1 | use cb_common::{ 2 | config::{LogsSettings, StartSignerConfig, SIGNER_MODULE_NAME}, 3 | utils::{initialize_tracing_log, wait_for_signal}, 4 | }; 5 | use cb_signer::service::SigningService; 6 | use clap::Parser; 7 | use eyre::Result; 8 | use tracing::{error, info}; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | color_eyre::install()?; 13 | 14 | // set default backtrace unless provided 15 | if std::env::var_os("RUST_BACKTRACE").is_none() { 16 | std::env::set_var("RUST_BACKTRACE", "1"); 17 | } 18 | let _guard = initialize_tracing_log(SIGNER_MODULE_NAME, LogsSettings::from_env_config()?); 19 | 20 | let _args = cb_cli::SignerArgs::parse(); 21 | 22 | let config = StartSignerConfig::load_from_env()?; 23 | let server = SigningService::run(config); 24 | 25 | tokio::select! { 26 | maybe_err = server => { 27 | if let Err(err) = maybe_err { 28 | error!(%err, "signing server unexpectedly stopped"); 29 | eprintln!("signing server unexpectedly stopped: {}", err); 30 | } 31 | }, 32 | _ = wait_for_signal() => { 33 | info!("shutting down"); 34 | } 35 | } 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /bin/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude { 2 | pub use cb_common::{ 3 | commit, 4 | commit::request::{ 5 | SignConsensusRequest, SignProxyRequest, SignedProxyDelegation, 6 | SignedProxyDelegationBls, SignedProxyDelegationEcdsa, 7 | }, 8 | config::{ 9 | load_builder_module_config, load_commit_module_config, load_pbs_config, 10 | load_pbs_custom_config, LogsSettings, StartCommitModuleConfig, PBS_MODULE_NAME, 11 | }, 12 | pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, 13 | signer::{BlsPublicKey, BlsSignature, EcdsaSignature}, 14 | types::Chain, 15 | utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us}, 16 | }; 17 | pub use cb_metrics::provider::MetricsProvider; 18 | pub use cb_pbs::{ 19 | get_header, get_status, register_validator, submit_block, BuilderApi, BuilderApiState, 20 | DefaultBuilderApi, PbsService, PbsState, PbsStateGuard, 21 | }; 22 | // The TreeHash derive macro requires tree_hash as import 23 | pub mod tree_hash { 24 | pub use tree_hash::*; 25 | } 26 | pub use tree_hash_derive::TreeHash; 27 | } 28 | -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-cli" 4 | publish = false 5 | rust-version.workspace = true 6 | version.workspace = true 7 | 8 | [dependencies] 9 | cb-common.workspace = true 10 | clap.workspace = true 11 | docker-compose-types.workspace = true 12 | eyre.workspace = true 13 | indexmap.workspace = true 14 | serde_yaml.workspace = true 15 | -------------------------------------------------------------------------------- /crates/cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use cb_common::utils::print_logo; 4 | use clap::{Parser, Subcommand}; 5 | 6 | mod docker_init; 7 | 8 | #[derive(Parser, Debug)] 9 | #[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-cli")] 10 | pub struct Args { 11 | #[command(subcommand)] 12 | pub cmd: Command, 13 | } 14 | 15 | #[derive(Debug, Subcommand)] 16 | pub enum Command { 17 | /// Generate the starting docker-compose file 18 | Init { 19 | /// Path to config file 20 | #[arg(long("config"))] 21 | config_path: PathBuf, 22 | 23 | /// Path to output files 24 | #[arg(short, long("output"), default_value = "./")] 25 | output_path: PathBuf, 26 | }, 27 | } 28 | 29 | impl Args { 30 | pub async fn run(self) -> eyre::Result<()> { 31 | print_logo(); 32 | 33 | match self.cmd { 34 | Command::Init { config_path, output_path } => { 35 | docker_init::handle_docker_init(config_path, output_path).await 36 | } 37 | } 38 | } 39 | } 40 | 41 | const LONG_ABOUT: &str = "Commit-Boost allows Ethereum validators to safely run MEV-Boost and community-built commitment protocols"; 42 | 43 | #[derive(Parser, Debug)] 44 | #[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-pbs")] 45 | pub struct PbsArgs; 46 | 47 | #[derive(Parser, Debug)] 48 | #[command(version, about, long_about = LONG_ABOUT, name = "commit-boost-signer")] 49 | pub struct SignerArgs; 50 | -------------------------------------------------------------------------------- /crates/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-common" 4 | publish = false 5 | rust-version.workspace = true 6 | version.workspace = true 7 | 8 | [dependencies] 9 | aes.workspace = true 10 | alloy.workspace = true 11 | async-trait.workspace = true 12 | axum.workspace = true 13 | base64.workspace = true 14 | bimap.workspace = true 15 | blst.workspace = true 16 | cipher.workspace = true 17 | ctr.workspace = true 18 | derive_more.workspace = true 19 | eth2_keystore.workspace = true 20 | ethereum_serde_utils.workspace = true 21 | ethereum_ssz.workspace = true 22 | ethereum_ssz_derive.workspace = true 23 | eyre.workspace = true 24 | pbkdf2.workspace = true 25 | rand.workspace = true 26 | reqwest.workspace = true 27 | serde.workspace = true 28 | serde_json.workspace = true 29 | serde_yaml.workspace = true 30 | sha2.workspace = true 31 | ssz_types.workspace = true 32 | thiserror.workspace = true 33 | tokio.workspace = true 34 | toml.workspace = true 35 | tonic.workspace = true 36 | tracing.workspace = true 37 | tracing-appender.workspace = true 38 | tracing-subscriber.workspace = true 39 | tree_hash.workspace = true 40 | tree_hash_derive.workspace = true 41 | unicode-normalization.workspace = true 42 | url.workspace = true 43 | jsonwebtoken.workspace = true 44 | -------------------------------------------------------------------------------- /crates/common/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | fn main() { 4 | let target = std::env::var("TARGET").unwrap(); 5 | let host = std::env::var("HOST").unwrap(); 6 | 7 | if target != host { 8 | println!("cargo:warning=Skipping build script because TARGET != HOST"); 9 | return; 10 | } 11 | 12 | let output = Command::new("git").args(["rev-parse", "HEAD"]).output().unwrap(); 13 | let git_hash = String::from_utf8(output.stdout).unwrap(); 14 | println!("cargo:rustc-env=GIT_HASH={git_hash}"); 15 | } 16 | -------------------------------------------------------------------------------- /crates/common/src/abi/LidoNORegistry.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "_nodeOperatorId", 7 | "type": "uint256" 8 | } 9 | ], 10 | "name": "getTotalSigningKeyCount", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "uint256" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "view", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": true, 23 | "inputs": [ 24 | { 25 | "name": "_nodeOperatorId", 26 | "type": "uint256" 27 | }, 28 | { 29 | "name": "_offset", 30 | "type": "uint256" 31 | }, 32 | { 33 | "name": "_limit", 34 | "type": "uint256" 35 | } 36 | ], 37 | "name": "getSigningKeys", 38 | "outputs": [ 39 | { 40 | "name": "pubkeys", 41 | "type": "bytes" 42 | }, 43 | { 44 | "name": "signatures", 45 | "type": "bytes" 46 | }, 47 | { 48 | "name": "used", 49 | "type": "bool[]" 50 | } 51 | ], 52 | "payable": false, 53 | "stateMutability": "view", 54 | "type": "function" 55 | } 56 | ] -------------------------------------------------------------------------------- /crates/common/src/commit/constants.rs: -------------------------------------------------------------------------------- 1 | pub const GET_PUBKEYS_PATH: &str = "/signer/v1/get_pubkeys"; 2 | pub const REQUEST_SIGNATURE_PATH: &str = "/signer/v1/request_signature"; 3 | pub const GENERATE_PROXY_KEY_PATH: &str = "/signer/v1/generate_proxy_key"; 4 | pub const STATUS_PATH: &str = "/status"; 5 | pub const RELOAD_PATH: &str = "/reload"; 6 | -------------------------------------------------------------------------------- /crates/common/src/commit/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, thiserror::Error)] 2 | pub enum SignerClientError { 3 | #[error("reqwest error: {0}")] 4 | ReqwestError(#[from] reqwest::Error), 5 | 6 | #[error("invalid header value: {0}")] 7 | InvalidHeader(#[from] reqwest::header::InvalidHeaderValue), 8 | 9 | #[error("failed request: status {status}; message: \"{error_msg}\"")] 10 | FailedRequest { status: u16, error_msg: String }, 11 | 12 | #[error("serde decode error: {0}")] 13 | SerdeDecodeError(#[from] serde_json::Error), 14 | 15 | #[error("url parse error: {0}")] 16 | ParseError(#[from] url::ParseError), 17 | 18 | #[error("JWT error: {0}")] 19 | JWTError(#[from] eyre::Error), 20 | } 21 | -------------------------------------------------------------------------------- /crates/common/src/commit/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod constants; 3 | pub mod error; 4 | pub mod request; 5 | -------------------------------------------------------------------------------- /crates/common/src/config/constants.rs: -------------------------------------------------------------------------------- 1 | ///////////////////////// COMMON ///////////////////////// 2 | 3 | /// Path to the main toml config file 4 | pub const CONFIG_ENV: &str = "CB_CONFIG"; 5 | pub const CONFIG_DEFAULT: &str = "/cb-config.toml"; 6 | 7 | /// Path to the chain spec file 8 | pub const CHAIN_SPEC_ENV: &str = "CB_CHAIN_SPEC"; 9 | 10 | /// Where to receive scrape requests from Prometheus 11 | pub const METRICS_PORT_ENV: &str = "CB_METRICS_PORT"; 12 | 13 | /// Path to logs directory 14 | pub const LOGS_DIR_ENV: &str = "CB_LOGS_DIR"; 15 | pub const LOGS_DIR_DEFAULT: &str = "/var/logs/commit-boost"; 16 | 17 | ///////////////////////// PBS ///////////////////////// 18 | 19 | pub const PBS_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/pbs:latest"; 20 | pub const PBS_MODULE_NAME: &str = "pbs"; 21 | 22 | /// Urls the pbs modules should post events to (comma separated) 23 | pub const BUILDER_URLS_ENV: &str = "CB_BUILDER_URLS"; 24 | 25 | /// Where to receive BuilderAPI calls from beacon node 26 | pub const PBS_ENDPOINT_ENV: &str = "CB_PBS_ENDPOINT"; 27 | 28 | pub const MUX_PATH_ENV: &str = "CB_MUX_PATH"; 29 | 30 | ///////////////////////// SIGNER ///////////////////////// 31 | 32 | pub const SIGNER_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/signer:latest"; 33 | pub const SIGNER_MODULE_NAME: &str = "signer"; 34 | 35 | /// Where the signer module should open the server 36 | pub const SIGNER_PORT_ENV: &str = "CB_SIGNER_PORT"; 37 | 38 | /// Comma separated list module_id=jwt_secret 39 | pub const JWTS_ENV: &str = "CB_JWTS"; 40 | 41 | /// Path to json file with plaintext keys (testing only) 42 | pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_LOADER_FILE"; 43 | pub const SIGNER_DEFAULT: &str = "/keys.json"; 44 | /// Path to `keys` folder 45 | pub const SIGNER_DIR_KEYS_ENV: &str = "CB_SIGNER_LOADER_KEYS_DIR"; 46 | pub const SIGNER_DIR_KEYS_DEFAULT: &str = "/keys"; 47 | /// Path to `secrets` folder 48 | pub const SIGNER_DIR_SECRETS_ENV: &str = "CB_SIGNER_LOADER_SECRETS_DIR"; 49 | pub const SIGNER_DIR_SECRETS_DEFAULT: &str = "/secrets"; 50 | /// Path to Dirk certificate 51 | pub const DIRK_CERT_ENV: &str = "CB_SIGNER_DIRK_CERT_FILE"; 52 | pub const DIRK_CERT_DEFAULT: &str = "/certificates/dirk.crt"; 53 | pub const DIRK_KEY_ENV: &str = "CB_SIGNER_DIRK_KEY_FILE"; 54 | pub const DIRK_KEY_DEFAULT: &str = "/certificates/dirk.key"; 55 | pub const DIRK_CA_CERT_ENV: &str = "CB_SIGNER_DIRK_CA_CERT_FILE"; 56 | pub const DIRK_CA_CERT_DEFAULT: &str = "/certificates/ca.crt"; 57 | /// Path to Dirk `secrets` folder 58 | pub const DIRK_DIR_SECRETS_ENV: &str = "CB_SIGNER_DIRK_SECRETS_DIR"; 59 | pub const DIRK_DIR_SECRETS_DEFAULT: &str = "/dirk_secrets"; 60 | /// Path to store proxies with plaintext keys (testing only) 61 | pub const PROXY_DIR_ENV: &str = "CB_PROXY_STORE_DIR"; 62 | pub const PROXY_DIR_DEFAULT: &str = "/proxies"; 63 | /// Path to store proxy keys 64 | pub const PROXY_DIR_KEYS_ENV: &str = "CB_PROXY_KEYS_DIR"; 65 | pub const PROXY_DIR_KEYS_DEFAULT: &str = "/proxy_keys"; 66 | /// Path to store proxy secrets 67 | pub const PROXY_DIR_SECRETS_ENV: &str = "CB_PROXY_SECRETS_DIR"; 68 | pub const PROXY_DIR_SECRETS_DEFAULT: &str = "/proxy_secrets"; 69 | 70 | ///////////////////////// MODULES ///////////////////////// 71 | 72 | /// The unique ID of the module 73 | pub const MODULE_ID_ENV: &str = "CB_MODULE_ID"; 74 | 75 | // Commit modules 76 | /// The JWT secret for the module to communicate with the signer module 77 | pub const MODULE_JWT_ENV: &str = "CB_SIGNER_JWT"; 78 | /// Where to send signature request 79 | pub const SIGNER_URL_ENV: &str = "CB_SIGNER_URL"; 80 | 81 | /// Events modules 82 | /// Where to receive builder events 83 | pub const BUILDER_PORT_ENV: &str = "CB_BUILDER_PORT"; 84 | -------------------------------------------------------------------------------- /crates/common/src/config/log.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use eyre::Result; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::{load_optional_env_var, CommitBoostConfig, LOGS_DIR_DEFAULT, LOGS_DIR_ENV}; 7 | use crate::utils::default_bool; 8 | 9 | #[derive(Clone, Default, Debug, Deserialize, Serialize)] 10 | pub struct LogsSettings { 11 | pub stdout: StdoutLogSettings, 12 | pub file: FileLogSettings, 13 | } 14 | 15 | impl LogsSettings { 16 | pub fn from_env_config() -> Result { 17 | let mut config = CommitBoostConfig::from_env_path()?; 18 | 19 | // Override log dir path if env var is set 20 | if let Some(log_dir) = load_optional_env_var(LOGS_DIR_ENV) { 21 | config.logs.file.dir_path = log_dir.into(); 22 | } 23 | 24 | Ok(config.logs) 25 | } 26 | } 27 | 28 | fn default_log_dir_path() -> PathBuf { 29 | LOGS_DIR_DEFAULT.into() 30 | } 31 | 32 | fn default_level() -> String { 33 | "info".into() 34 | } 35 | 36 | #[derive(Clone, Debug, Deserialize, Serialize)] 37 | pub struct StdoutLogSettings { 38 | #[serde(default = "default_bool::")] 39 | pub enabled: bool, 40 | #[serde(default = "default_level")] 41 | pub level: String, 42 | #[serde(default = "default_bool::")] 43 | pub use_json: bool, 44 | #[serde(default = "default_bool::")] 45 | pub color: bool, 46 | } 47 | 48 | impl Default for StdoutLogSettings { 49 | fn default() -> Self { 50 | Self { enabled: true, level: "info".into(), use_json: false, color: true } 51 | } 52 | } 53 | 54 | #[derive(Clone, Debug, Deserialize, Serialize)] 55 | pub struct FileLogSettings { 56 | #[serde(default = "default_bool::")] 57 | pub enabled: bool, 58 | #[serde(default = "default_level")] 59 | pub level: String, 60 | #[serde(default = "default_bool::")] 61 | pub use_json: bool, 62 | #[serde(default = "default_log_dir_path")] 63 | pub dir_path: PathBuf, 64 | #[serde(default)] 65 | pub max_files: Option, 66 | } 67 | 68 | impl Default for FileLogSettings { 69 | fn default() -> Self { 70 | Self { 71 | enabled: false, 72 | level: "info".into(), 73 | use_json: true, 74 | dir_path: default_log_dir_path(), 75 | max_files: None, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/common/src/config/metrics.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use eyre::Result; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::{constants::METRICS_PORT_ENV, load_optional_env_var}; 7 | use crate::utils::{default_bool, default_host, default_u16}; 8 | 9 | #[derive(Debug, Serialize, Deserialize, Clone)] 10 | pub struct MetricsConfig { 11 | /// Whether to collect metrics 12 | #[serde(default = "default_bool::")] 13 | pub enabled: bool, 14 | /// Host for metrics servers 15 | #[serde(default = "default_host")] 16 | pub host: Ipv4Addr, 17 | /// Port to listen on for metrics, following ports will be port+1, port+2, 18 | /// etc. 19 | #[serde(default = "default_u16::<10000>")] 20 | pub start_port: u16, 21 | } 22 | 23 | /// Module runtime config set after init 24 | pub struct ModuleMetricsConfig { 25 | /// Where to open metrics server 26 | pub server_port: u16, 27 | } 28 | 29 | impl ModuleMetricsConfig { 30 | pub fn load_from_env() -> Result> { 31 | if let Some(server_port) = load_optional_env_var(METRICS_PORT_ENV) { 32 | Ok(Some(ModuleMetricsConfig { server_port: server_port.parse()? })) 33 | } else { 34 | Ok(None) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/common/src/config/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::Path}; 2 | 3 | use eyre::{bail, Context, Result}; 4 | use serde::de::DeserializeOwned; 5 | 6 | use super::JWTS_ENV; 7 | use crate::types::ModuleId; 8 | 9 | pub fn load_env_var(env: &str) -> Result { 10 | std::env::var(env).wrap_err(format!("{env} is not set")) 11 | } 12 | pub fn load_optional_env_var(env: &str) -> Option { 13 | std::env::var(env).ok() 14 | } 15 | 16 | pub fn load_from_file + std::fmt::Debug, T: DeserializeOwned>(path: P) -> Result { 17 | let config_file = std::fs::read_to_string(path.as_ref()) 18 | .wrap_err(format!("Unable to find config file: {path:?}"))?; 19 | toml::from_str(&config_file).wrap_err("could not deserialize toml from string") 20 | } 21 | 22 | pub fn load_file_from_env(env: &str) -> Result { 23 | let path = std::env::var(env).wrap_err(format!("{env} is not set"))?; 24 | load_from_file(&path) 25 | } 26 | 27 | /// Loads a map of module id -> jwt secret from a json env 28 | pub fn load_jwt_secrets() -> Result> { 29 | let jwt_secrets = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?; 30 | decode_string_to_map(&jwt_secrets) 31 | } 32 | 33 | fn decode_string_to_map(raw: &str) -> Result> { 34 | // trim the string and split for comma 35 | raw.trim() 36 | .split(',') 37 | .map(|pair| { 38 | let mut parts = pair.trim().split('='); 39 | match (parts.next(), parts.next()) { 40 | (Some(key), Some(value)) => Ok((ModuleId(key.into()), value.into())), 41 | _ => bail!("Invalid key-value pair: {pair}"), 42 | } 43 | }) 44 | .collect() 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | 51 | #[test] 52 | fn test_decode_string_to_map() { 53 | let raw = " KEY=VALUE , KEY2=value2 "; 54 | 55 | let map = decode_string_to_map(raw).unwrap(); 56 | 57 | assert_eq!(map.get(&ModuleId("KEY".into())), Some(&"VALUE".to_string())); 58 | assert_eq!(map.get(&ModuleId("KEY2".into())), Some(&"value2".to_string())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/common/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const APPLICATION_BUILDER_DOMAIN: [u8; 4] = [0, 0, 0, 1]; 2 | pub const GENESIS_VALIDATORS_ROOT: [u8; 32] = [0; 32]; 3 | pub const COMMIT_BOOST_DOMAIN: [u8; 4] = [109, 109, 111, 67]; 4 | pub const COMMIT_BOOST_VERSION: &str = env!("CARGO_PKG_VERSION"); 5 | pub const COMMIT_BOOST_COMMIT: &str = env!("GIT_HASH"); 6 | pub const SIGNER_JWT_EXPIRATION: u64 = 300; // 5 minutes 7 | -------------------------------------------------------------------------------- /crates/common/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use blst::BLST_ERROR; 4 | use thiserror::Error; 5 | #[derive(Debug, Error, PartialEq, Eq)] 6 | pub enum BlstErrorWrapper { 7 | BlstSuccess(BLST_ERROR), 8 | BlstBadEncoding(BLST_ERROR), 9 | BlstPointNotOnCurve(BLST_ERROR), 10 | BlstPointNotInGroup(BLST_ERROR), 11 | BlstAggrTypeMismatch(BLST_ERROR), 12 | BlstVerifyFail(BLST_ERROR), 13 | BlstPkIsInfinity(BLST_ERROR), 14 | BlstBadScalar(BLST_ERROR), 15 | } 16 | 17 | impl Display for BlstErrorWrapper { 18 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | BlstErrorWrapper::BlstSuccess(_) => write!(f, "BLST_SUCCESS"), 21 | BlstErrorWrapper::BlstBadEncoding(_) => write!(f, "BLST_BAD_ENCODING"), 22 | BlstErrorWrapper::BlstPointNotOnCurve(_) => write!(f, "BLST_POINT_NOT_ON_CURVE"), 23 | BlstErrorWrapper::BlstPointNotInGroup(_) => write!(f, "BLST_POINT_NOT_IN_GROUP"), 24 | BlstErrorWrapper::BlstAggrTypeMismatch(_) => write!(f, "BLST_AGGR_TYPE_MISMATCH"), 25 | BlstErrorWrapper::BlstVerifyFail(_) => write!(f, "BLST_VERIFY_FAIL"), 26 | BlstErrorWrapper::BlstPkIsInfinity(_) => write!(f, "BLST_PK_IS_INFINITY"), 27 | BlstErrorWrapper::BlstBadScalar(_) => write!(f, "BLST_BAD_SCALAR"), 28 | } 29 | } 30 | } 31 | impl From for BlstErrorWrapper { 32 | fn from(value: BLST_ERROR) -> Self { 33 | match value { 34 | BLST_ERROR::BLST_SUCCESS => BlstErrorWrapper::BlstSuccess(BLST_ERROR::BLST_SUCCESS), 35 | BLST_ERROR::BLST_BAD_ENCODING => { 36 | BlstErrorWrapper::BlstBadEncoding(BLST_ERROR::BLST_BAD_ENCODING) 37 | } 38 | BLST_ERROR::BLST_POINT_NOT_ON_CURVE => { 39 | BlstErrorWrapper::BlstPointNotOnCurve(BLST_ERROR::BLST_POINT_NOT_ON_CURVE) 40 | } 41 | BLST_ERROR::BLST_POINT_NOT_IN_GROUP => { 42 | BlstErrorWrapper::BlstPointNotInGroup(BLST_ERROR::BLST_POINT_NOT_IN_GROUP) 43 | } 44 | BLST_ERROR::BLST_AGGR_TYPE_MISMATCH => { 45 | BlstErrorWrapper::BlstAggrTypeMismatch(BLST_ERROR::BLST_AGGR_TYPE_MISMATCH) 46 | } 47 | BLST_ERROR::BLST_VERIFY_FAIL => { 48 | BlstErrorWrapper::BlstVerifyFail(BLST_ERROR::BLST_VERIFY_FAIL) 49 | } 50 | BLST_ERROR::BLST_PK_IS_INFINITY => { 51 | BlstErrorWrapper::BlstPkIsInfinity(BLST_ERROR::BLST_PK_IS_INFINITY) 52 | } 53 | BLST_ERROR::BLST_BAD_SCALAR => { 54 | BlstErrorWrapper::BlstBadScalar(BLST_ERROR::BLST_BAD_SCALAR) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub mod commit; 4 | pub mod config; 5 | pub mod constants; 6 | pub mod error; 7 | pub mod pbs; 8 | pub mod signature; 9 | pub mod signer; 10 | pub mod types; 11 | pub mod utils; 12 | 13 | pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); 14 | -------------------------------------------------------------------------------- /crates/common/src/pbs/constants.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::COMMIT_BOOST_VERSION; 2 | 3 | pub const BUILDER_API_PATH: &str = "/eth/v1/builder"; 4 | 5 | pub const GET_HEADER_PATH: &str = "/header/{slot}/{parent_hash}/{pubkey}"; 6 | pub const GET_STATUS_PATH: &str = "/status"; 7 | pub const REGISTER_VALIDATOR_PATH: &str = "/validators"; 8 | pub const SUBMIT_BLOCK_PATH: &str = "/blinded_blocks"; 9 | pub const RELOAD_PATH: &str = "/reload"; 10 | 11 | // https://ethereum.github.io/builder-specs/#/Builder 12 | 13 | // Currently unused to enable a stateless default PBS module 14 | // const HEADER_SLOT_UUID_KEY: &str = "X-MEVBoost-SlotID"; 15 | pub const HEADER_VERSION_KEY: &str = "X-CommitBoost-Version"; 16 | pub const HEADER_VERSION_VALUE: &str = COMMIT_BOOST_VERSION; 17 | pub const HEADER_START_TIME_UNIX_MS: &str = "Date-Milliseconds"; 18 | 19 | pub const BUILDER_EVENTS_PATH: &str = "/builder_events"; 20 | pub const DEFAULT_PBS_JWT_KEY: &str = "DEFAULT_PBS"; 21 | 22 | pub const DEFAULT_PBS_PORT: u16 = 18550; 23 | 24 | #[non_exhaustive] 25 | pub struct DefaultTimeout; 26 | impl DefaultTimeout { 27 | pub const GET_HEADER_MS: u64 = 950; 28 | pub const GET_PAYLOAD_MS: u64 = 4000; 29 | pub const REGISTER_VALIDATOR_MS: u64 = 3000; 30 | } 31 | 32 | pub const LATE_IN_SLOT_TIME_MS: u64 = 2000; 33 | -------------------------------------------------------------------------------- /crates/common/src/pbs/error.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | primitives::{B256, U256}, 3 | rpc::types::beacon::BlsPublicKey, 4 | }; 5 | use thiserror::Error; 6 | 7 | use crate::error::BlstErrorWrapper; 8 | 9 | #[derive(Debug, Error)] 10 | pub enum PbsError { 11 | #[error("axum error: {0:?}")] 12 | AxumError(#[from] axum::Error), 13 | 14 | #[error("reqwest error: {0:?}")] 15 | Reqwest(#[from] reqwest::Error), 16 | 17 | #[error("json decode error: {err:?}, raw: {raw}")] 18 | JsonDecode { err: serde_json::Error, raw: String }, 19 | 20 | #[error("relay response error. Code: {code}, err: {error_msg:?}")] 21 | RelayResponse { error_msg: String, code: u16 }, 22 | 23 | #[error("response size exceeds max size: max: {max} raw: {raw}")] 24 | PayloadTooLarge { max: usize, raw: String }, 25 | 26 | #[error("failed validating relay response: {0}")] 27 | Validation(#[from] ValidationError), 28 | 29 | #[error("URL parsing error: {0}")] 30 | UrlParsing(#[from] url::ParseError), 31 | } 32 | 33 | impl PbsError { 34 | pub fn is_timeout(&self) -> bool { 35 | matches!(self, PbsError::Reqwest(err) if err.is_timeout()) 36 | } 37 | 38 | /// Whether the error is retryable in requests to relays 39 | pub fn should_retry(&self) -> bool { 40 | matches!(self, PbsError::RelayResponse { .. } | PbsError::Reqwest { .. }) 41 | } 42 | } 43 | 44 | #[derive(Debug, Error, PartialEq, Eq)] 45 | pub enum ValidationError { 46 | #[error("empty blockhash")] 47 | EmptyBlockhash, 48 | 49 | #[error("pubkey mismatch: expected {expected} got {got}")] 50 | PubkeyMismatch { expected: BlsPublicKey, got: BlsPublicKey }, 51 | 52 | #[error("parent hash mismatch: expected {expected} got {got}")] 53 | ParentHashMismatch { expected: B256, got: B256 }, 54 | 55 | #[error("block hash mismatch: expected {expected} got {got}")] 56 | BlockHashMismatch { expected: B256, got: B256 }, 57 | 58 | #[error("mismatch in KZG commitments: exepcted_blobs: {expected_blobs} got_blobs: {got_blobs} got_commitments: {got_commitments} got_proofs: {got_proofs}")] 59 | KzgCommitments { 60 | expected_blobs: usize, 61 | got_blobs: usize, 62 | got_commitments: usize, 63 | got_proofs: usize, 64 | }, 65 | 66 | #[error("mismatch in KZG blob commitment: expected: {expected} got: {got} index: {index}")] 67 | KzgMismatch { expected: String, got: String, index: usize }, 68 | 69 | #[error("bid below minimum: min: {min} got {got}")] 70 | BidTooLow { min: U256, got: U256 }, 71 | 72 | #[error("empty tx root")] 73 | EmptyTxRoot, 74 | 75 | #[error("failed signature verification: {0:?}")] 76 | Sigverify(#[from] BlstErrorWrapper), 77 | 78 | #[error("wrong timestamp: expected {expected} got {got}")] 79 | TimestampMismatch { expected: u64, got: u64 }, 80 | 81 | #[error("wrong block number: parent: {parent} header: {header}")] 82 | BlockNumberMismatch { parent: u64, header: u64 }, 83 | 84 | #[error("invalid gas limit: parent: {parent} header: {header}")] 85 | GasLimit { parent: u64, header: u64 }, 86 | 87 | #[error("payload mismatch: request: {request} response: {response}")] 88 | PayloadVersionMismatch { request: &'static str, response: &'static str }, 89 | } 90 | -------------------------------------------------------------------------------- /crates/common/src/pbs/mod.rs: -------------------------------------------------------------------------------- 1 | mod constants; 2 | pub mod error; 3 | mod event; 4 | mod relay; 5 | mod types; 6 | 7 | pub use constants::*; 8 | pub use event::*; 9 | pub use relay::*; 10 | pub use types::*; 11 | -------------------------------------------------------------------------------- /crates/common/src/pbs/types/blobs_bundle.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use ssz_derive::{Decode, Encode}; 3 | use ssz_types::{FixedVector, VariableList}; 4 | 5 | use super::{ 6 | kzg::{KzgCommitments, KzgProofs}, 7 | spec::EthSpec, 8 | }; 9 | 10 | #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode)] 11 | #[serde(bound = "T: EthSpec")] 12 | pub struct BlobsBundle { 13 | pub commitments: KzgCommitments, 14 | pub proofs: KzgProofs, 15 | #[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")] 16 | pub blobs: VariableList, ::MaxBlobCommitmentsPerBlock>, 17 | } 18 | 19 | pub type Blob = FixedVector::BytesPerBlob>; 20 | -------------------------------------------------------------------------------- /crates/common/src/pbs/types/execution_requests.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | primitives::{Address, B256}, 3 | rpc::types::beacon::{BlsPublicKey, BlsSignature}, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | use ssz_derive::{Decode, Encode}; 7 | use ssz_types::VariableList; 8 | use tree_hash_derive::TreeHash; 9 | 10 | use super::spec::EthSpec; 11 | 12 | #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] 13 | pub struct ExecutionRequests { 14 | pub deposits: VariableList, 15 | pub withdrawals: VariableList, 16 | pub consolidations: VariableList, 17 | } 18 | 19 | #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] 20 | pub struct DepositRequest { 21 | pub pubkey: BlsPublicKey, 22 | pub withdrawal_credentials: B256, 23 | #[serde(with = "serde_utils::quoted_u64")] 24 | pub amount: u64, 25 | pub signature: BlsSignature, 26 | #[serde(with = "serde_utils::quoted_u64")] 27 | pub index: u64, 28 | } 29 | 30 | #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] 31 | pub struct WithdrawalRequest { 32 | pub source_address: Address, 33 | pub validator_pubkey: BlsPublicKey, 34 | #[serde(with = "serde_utils::quoted_u64")] 35 | pub amount: u64, 36 | } 37 | 38 | #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] 39 | pub struct ConsolidationRequest { 40 | pub source_address: Address, 41 | pub source_pubkey: BlsPublicKey, 42 | pub target_pubkey: BlsPublicKey, 43 | } 44 | -------------------------------------------------------------------------------- /crates/common/src/pbs/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod beacon_block; 2 | mod blinded_block_body; 3 | mod blobs_bundle; 4 | mod execution_payload; 5 | mod execution_requests; 6 | mod get_header; 7 | mod kzg; 8 | mod spec; 9 | mod utils; 10 | 11 | pub use beacon_block::{ 12 | BlindedBeaconBlock, BlindedBeaconBlockDeneb, BlindedBeaconBlockElectra, PayloadAndBlobsDeneb, 13 | PayloadAndBlobsElectra, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, 14 | }; 15 | pub use blobs_bundle::{Blob, BlobsBundle}; 16 | pub use execution_payload::{ 17 | ExecutionPayload, ExecutionPayloadHeader, Transaction, Transactions, Withdrawal, 18 | EMPTY_TX_ROOT_HASH, 19 | }; 20 | pub use execution_requests::{ 21 | ConsolidationRequest, DepositRequest, ExecutionRequests, WithdrawalRequest, 22 | }; 23 | pub use get_header::{ 24 | ExecutionPayloadHeaderMessageDeneb, ExecutionPayloadHeaderMessageElectra, GetHeaderParams, 25 | GetHeaderResponse, SignedExecutionPayloadHeader, 26 | }; 27 | pub use kzg::{ 28 | KzgCommitment, KzgCommitments, KzgProof, KzgProofs, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, 29 | }; 30 | pub use spec::{DenebSpec, ElectraSpec, EthSpec}; 31 | pub use utils::VersionedResponse; 32 | -------------------------------------------------------------------------------- /crates/common/src/pbs/types/spec.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use ssz_types::typenum; 3 | 4 | pub trait EthSpec { 5 | type MaxProposerSlashings: typenum::Unsigned + std::fmt::Debug; 6 | type MaxValidatorsPerCommittee: typenum::Unsigned + std::fmt::Debug; 7 | type MaxDeposits: typenum::Unsigned + std::fmt::Debug; 8 | type MaxVoluntaryExits: typenum::Unsigned + std::fmt::Debug; 9 | type SyncCommitteeSize: typenum::Unsigned + std::fmt::Debug; 10 | type BytesPerLogsBloom: typenum::Unsigned + std::fmt::Debug; 11 | type MaxExtraDataBytes: typenum::Unsigned + std::fmt::Debug; 12 | type MaxBlsToExecutionChanges: typenum::Unsigned + std::fmt::Debug; 13 | type MaxBlobCommitmentsPerBlock: typenum::Unsigned + std::fmt::Debug; 14 | type MaxWithdrawalsPerPayload: typenum::Unsigned + std::fmt::Debug; 15 | type MaxBytesPerTransaction: typenum::Unsigned + std::fmt::Debug; 16 | type MaxTransactionsPerPayload: typenum::Unsigned + std::fmt::Debug; 17 | type BytesPerBlob: typenum::Unsigned + std::fmt::Debug; 18 | type MaxCommitteesPerSlot: typenum::Unsigned + std::fmt::Debug; 19 | 20 | // Updated in Electra 21 | type MaxAttesterSlashings: typenum::Unsigned + std::fmt::Debug; 22 | type MaxAttestations: typenum::Unsigned + std::fmt::Debug; 23 | 24 | // New in Electra 25 | type MaxValidatorsPerSlot: typenum::Unsigned + std::fmt::Debug; 26 | 27 | type MaxConsolidationRequestsPerPayload: typenum::Unsigned + std::fmt::Debug; 28 | type MaxDepositRequestsPerPayload: typenum::Unsigned + std::fmt::Debug; 29 | type MaxWithdrawalRequestsPerPayload: typenum::Unsigned + std::fmt::Debug; 30 | } 31 | 32 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 33 | pub struct DenebSpec; 34 | 35 | impl EthSpec for DenebSpec { 36 | type MaxValidatorsPerCommittee = typenum::U2048; 37 | type MaxProposerSlashings = typenum::U16; 38 | type MaxDeposits = typenum::U16; 39 | type MaxVoluntaryExits = typenum::U16; 40 | type SyncCommitteeSize = typenum::U512; 41 | type MaxExtraDataBytes = typenum::U32; 42 | type MaxBlobCommitmentsPerBlock = typenum::U4096; 43 | type BytesPerLogsBloom = typenum::U256; 44 | type MaxBlsToExecutionChanges = typenum::U16; 45 | type MaxWithdrawalsPerPayload = typenum::U16; 46 | type MaxBytesPerTransaction = typenum::U1073741824; 47 | type MaxTransactionsPerPayload = typenum::U1048576; 48 | type BytesPerBlob = typenum::U131072; 49 | type MaxCommitteesPerSlot = typenum::U64; 50 | 51 | // Updated in Electra 52 | type MaxAttesterSlashings = typenum::U2; 53 | type MaxAttestations = typenum::U128; 54 | 55 | // Electra 56 | type MaxValidatorsPerSlot = typenum::U131072; 57 | 58 | type MaxConsolidationRequestsPerPayload = typenum::U0; 59 | type MaxDepositRequestsPerPayload = typenum::U0; 60 | type MaxWithdrawalRequestsPerPayload = typenum::U0; 61 | } 62 | 63 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 64 | pub struct ElectraSpec; 65 | 66 | impl EthSpec for ElectraSpec { 67 | type MaxValidatorsPerCommittee = typenum::U2048; 68 | type MaxProposerSlashings = typenum::U16; 69 | type MaxDeposits = typenum::U16; 70 | type MaxVoluntaryExits = typenum::U16; 71 | type SyncCommitteeSize = typenum::U512; 72 | type MaxExtraDataBytes = typenum::U32; 73 | type MaxBlobCommitmentsPerBlock = typenum::U4096; 74 | type BytesPerLogsBloom = typenum::U256; 75 | type MaxBlsToExecutionChanges = typenum::U16; 76 | type MaxWithdrawalsPerPayload = typenum::U16; 77 | type MaxBytesPerTransaction = typenum::U1073741824; 78 | type MaxTransactionsPerPayload = typenum::U1048576; 79 | type BytesPerBlob = typenum::U131072; 80 | type MaxCommitteesPerSlot = typenum::U64; 81 | 82 | // Updated in Electra 83 | type MaxAttesterSlashings = typenum::U1; 84 | type MaxAttestations = typenum::U8; 85 | 86 | // Electra 87 | type MaxValidatorsPerSlot = typenum::U131072; 88 | 89 | type MaxConsolidationRequestsPerPayload = typenum::U2; 90 | type MaxDepositRequestsPerPayload = typenum::U8192; 91 | type MaxWithdrawalRequestsPerPayload = typenum::U16; 92 | } 93 | -------------------------------------------------------------------------------- /crates/common/src/pbs/types/testdata/get-header-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": { 3 | "header": { 4 | "parent_hash": "0x0100000000000000000000000000000000000000000000000000000000000000", 5 | "fee_recipient": "0x0200000000000000000000000000000000000000", 6 | "state_root": "0x0300000000000000000000000000000000000000000000000000000000000000", 7 | "receipts_root": "0x0400000000000000000000000000000000000000000000000000000000000000", 8 | "logs_bloom": "0x05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 9 | "prev_randao": "0x0600000000000000000000000000000000000000000000000000000000000000", 10 | "block_number": "123456", 11 | "gas_limit": "4000000", 12 | "gas_used": "2000000", 13 | "timestamp": "1234567890", 14 | "extra_data": "0x74657374", 15 | "base_fee_per_gas": "13", 16 | "block_hash": "0x0b00000000000000000000000000000000000000000000000000000000000000", 17 | "transactions_root": "0x0c00000000000000000000000000000000000000000000000000000000000000", 18 | "withdrawals_root": "0x0c00000000000000000000000000000000000000000000000000000000000000", 19 | "blob_gas_used": "100", 20 | "excess_blob_gas": "200" 21 | }, 22 | "blob_kzg_commitments": [ 23 | "0x010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 24 | ], 25 | "execution_requests": { 26 | "deposits": [ 27 | { 28 | "pubkey": "0x0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 29 | "withdrawal_credentials": "0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 30 | "amount": "100", 31 | "signature": "0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 32 | "index": "1" 33 | } 34 | ], 35 | "withdrawals": [ 36 | { 37 | "source_address": "0x1100000000000000000000000000000000000000", 38 | "validator_pubkey": "0x120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 39 | "amount": "1" 40 | } 41 | ], 42 | "consolidations": [ 43 | { 44 | "source_address": "0x1200000000000000000000000000000000000000", 45 | "source_pubkey": "0x120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 46 | "target_pubkey": "0x110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 47 | } 48 | ] 49 | }, 50 | "value": "11", 51 | "pubkey": "0x120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 52 | }, 53 | "signature": "0x010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 54 | } -------------------------------------------------------------------------------- /crates/common/src/pbs/types/testdata/get-header-response.ssz: -------------------------------------------------------------------------------- 1 | 640000000102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c000000a8020000d80200000b00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000040e201000000000000093d000000000080841e0000000000d202964900000000480200000d000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000006400000000000000c800000000000000746573740102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000cc000000180100000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f64000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000110000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000001200000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -------------------------------------------------------------------------------- /crates/common/src/pbs/types/utils.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub mod quoted_variable_list_u64 { 4 | use serde::{ser::SerializeSeq, Deserializer, Serializer}; 5 | use serde_utils::quoted_u64_vec::{QuotedIntVecVisitor, QuotedIntWrapper}; 6 | use ssz_types::{typenum::Unsigned, VariableList}; 7 | 8 | pub fn serialize(value: &VariableList, serializer: S) -> Result 9 | where 10 | S: Serializer, 11 | T: Unsigned, 12 | { 13 | let mut seq = serializer.serialize_seq(Some(value.len()))?; 14 | for &int in value.iter() { 15 | seq.serialize_element(&QuotedIntWrapper { int })?; 16 | } 17 | seq.end() 18 | } 19 | 20 | pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> 21 | where 22 | D: Deserializer<'de>, 23 | T: Unsigned, 24 | { 25 | deserializer.deserialize_any(QuotedIntVecVisitor).and_then(|vec| { 26 | VariableList::new(vec) 27 | .map_err(|e| serde::de::Error::custom(format!("invalid length: {:?}", e))) 28 | }) 29 | } 30 | } 31 | 32 | #[derive(Debug, Clone, Serialize, Deserialize)] 33 | #[serde(tag = "version", content = "data")] 34 | pub enum VersionedResponse { 35 | #[serde(rename = "deneb")] 36 | Deneb(D), 37 | #[serde(rename = "electra")] 38 | Electra(E), 39 | } 40 | 41 | impl Default for VersionedResponse { 42 | fn default() -> Self { 43 | Self::Deneb(D::default()) 44 | } 45 | } 46 | 47 | impl VersionedResponse { 48 | pub fn version(&self) -> &str { 49 | match self { 50 | VersionedResponse::Deneb(_) => "deneb", 51 | VersionedResponse::Electra(_) => "electra", 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/common/src/signer/mod.rs: -------------------------------------------------------------------------------- 1 | mod loader; 2 | mod schemes; 3 | mod store; 4 | mod types; 5 | 6 | pub use loader::*; 7 | pub use schemes::*; 8 | pub use store::*; 9 | pub use types::*; 10 | 11 | pub type ConsensusSigner = BlsSigner; 12 | -------------------------------------------------------------------------------- /crates/common/src/signer/schemes/bls.rs: -------------------------------------------------------------------------------- 1 | use alloy::rpc::types::beacon::constants::BLS_DST_SIG; 2 | pub use alloy::rpc::types::beacon::BlsSignature; 3 | use blst::BLST_ERROR; 4 | use tree_hash::TreeHash; 5 | 6 | use crate::{ 7 | error::BlstErrorWrapper, signature::sign_commit_boost_root, types::Chain, 8 | utils::blst_pubkey_to_alloy, 9 | }; 10 | 11 | pub type BlsSecretKey = blst::min_pk::SecretKey; 12 | pub type BlsPublicKey = alloy::rpc::types::beacon::BlsPublicKey; 13 | 14 | #[derive(Clone)] 15 | pub enum BlsSigner { 16 | Local(BlsSecretKey), 17 | } 18 | 19 | impl BlsSigner { 20 | pub fn new_random() -> Self { 21 | Self::Local(random_secret()) 22 | } 23 | 24 | pub fn new_from_bytes(bytes: &[u8]) -> eyre::Result { 25 | let secret = BlsSecretKey::from_bytes(bytes).map_err(BlstErrorWrapper::from)?; 26 | Ok(Self::Local(secret)) 27 | } 28 | 29 | pub fn pubkey(&self) -> BlsPublicKey { 30 | match self { 31 | BlsSigner::Local(secret) => blst_pubkey_to_alloy(&secret.sk_to_pk()), 32 | } 33 | } 34 | 35 | pub fn secret(&self) -> [u8; 32] { 36 | match self { 37 | BlsSigner::Local(secret) => secret.clone().to_bytes(), 38 | } 39 | } 40 | 41 | pub async fn sign(&self, chain: Chain, object_root: [u8; 32]) -> BlsSignature { 42 | match self { 43 | BlsSigner::Local(sk) => sign_commit_boost_root(chain, sk, object_root), 44 | } 45 | } 46 | 47 | pub async fn sign_msg(&self, chain: Chain, msg: &impl TreeHash) -> BlsSignature { 48 | self.sign(chain, msg.tree_hash_root().0).await 49 | } 50 | } 51 | 52 | pub fn random_secret() -> BlsSecretKey { 53 | use rand::RngCore; 54 | 55 | let mut rng = rand::rng(); 56 | let mut ikm = [0u8; 32]; 57 | rng.fill_bytes(&mut ikm); 58 | 59 | match BlsSecretKey::key_gen(&ikm, &[]) { 60 | Ok(key) => key, 61 | // Key material is always valid (32 `u8`s), so `key_gen` can't return Err. 62 | Err(_) => unreachable!(), 63 | } 64 | } 65 | 66 | pub fn verify_bls_signature( 67 | pubkey: &BlsPublicKey, 68 | msg: &[u8], 69 | signature: &BlsSignature, 70 | ) -> Result<(), BlstErrorWrapper> { 71 | use crate::utils::{alloy_pubkey_to_blst, alloy_sig_to_blst}; 72 | 73 | let pubkey = alloy_pubkey_to_blst(pubkey)?; 74 | let signature = alloy_sig_to_blst(signature)?; 75 | 76 | let res = signature.verify(true, msg, BLS_DST_SIG, &[], &pubkey, true); 77 | if res == BLST_ERROR::BLST_SUCCESS { 78 | Ok(()) 79 | } else { 80 | Err(res.into()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/common/src/signer/schemes/mod.rs: -------------------------------------------------------------------------------- 1 | mod bls; 2 | mod ecdsa; 3 | 4 | pub use bls::*; 5 | pub use ecdsa::*; 6 | -------------------------------------------------------------------------------- /crates/metrics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-metrics" 4 | publish = false 5 | rust-version.workspace = true 6 | version.workspace = true 7 | 8 | [dependencies] 9 | axum.workspace = true 10 | cb-common.workspace = true 11 | eyre.workspace = true 12 | prometheus.workspace = true 13 | thiserror.workspace = true 14 | tokio.workspace = true 15 | tracing.workspace = true 16 | -------------------------------------------------------------------------------- /crates/metrics/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod provider; 2 | -------------------------------------------------------------------------------- /crates/metrics/src/provider.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use axum::{ 4 | body::Body, 5 | extract::State, 6 | http::{header::CONTENT_TYPE, StatusCode}, 7 | response::{IntoResponse, Response}, 8 | routing::get, 9 | }; 10 | use cb_common::{ 11 | config::ModuleMetricsConfig, 12 | constants::{COMMIT_BOOST_COMMIT, COMMIT_BOOST_VERSION}, 13 | types::Chain, 14 | }; 15 | use eyre::bail; 16 | use prometheus::{Encoder, IntGauge, Opts, Registry, TextEncoder}; 17 | use tokio::net::TcpListener; 18 | use tracing::{error, info, warn}; 19 | 20 | pub struct MetricsProvider { 21 | network: Chain, 22 | config: ModuleMetricsConfig, 23 | registry: Registry, 24 | } 25 | 26 | impl MetricsProvider { 27 | pub fn new(network: Chain, config: ModuleMetricsConfig, registry: Registry) -> Self { 28 | MetricsProvider { network, config, registry } 29 | } 30 | 31 | pub fn from_registry(network: Chain, registry: Registry) -> eyre::Result> { 32 | Ok(ModuleMetricsConfig::load_from_env()?.map(|config| Self::new(network, config, registry))) 33 | } 34 | 35 | pub fn load_and_run(network: Chain, registry: Registry) -> eyre::Result<()> { 36 | if let Some(provider) = MetricsProvider::from_registry(network, registry)? { 37 | tokio::spawn(async move { 38 | if let Err(err) = provider.run().await { 39 | error!("Metrics server error: {:?}", err); 40 | } 41 | }); 42 | } else { 43 | warn!("No metrics server configured"); 44 | } 45 | 46 | Ok(()) 47 | } 48 | 49 | pub async fn run(self) -> eyre::Result<()> { 50 | info!("Starting metrics server on port {}", self.config.server_port); 51 | 52 | let opts = Opts::new("info", "Commit Boost info") 53 | .const_label("version", COMMIT_BOOST_VERSION) 54 | .const_label("commit", COMMIT_BOOST_COMMIT) 55 | .const_label("network", self.network.to_string()); 56 | let info = IntGauge::with_opts(opts).unwrap(); 57 | info.set(1); 58 | 59 | self.registry.register(Box::new(info)).unwrap(); 60 | 61 | let router = axum::Router::new() 62 | .route("/metrics", get(handle_metrics)) 63 | .route("/status", get(|| async { StatusCode::OK })) 64 | .with_state(self.registry); 65 | let address = SocketAddr::from(([0, 0, 0, 0], self.config.server_port)); 66 | let listener = TcpListener::bind(&address).await?; 67 | 68 | axum::serve(listener, router).await?; 69 | 70 | bail!("Metrics server stopped") 71 | } 72 | } 73 | 74 | async fn handle_metrics(State(registry): State) -> Response { 75 | match prepare_metrics(registry) { 76 | Ok(response) => response, 77 | Err(err) => { 78 | error!("Failed to prepare metrics: {:?}", err); 79 | StatusCode::INTERNAL_SERVER_ERROR.into_response() 80 | } 81 | } 82 | } 83 | 84 | fn prepare_metrics(registry: Registry) -> Result { 85 | let encoder = TextEncoder::new(); 86 | let mut buffer = vec![]; 87 | let metrics = registry.gather(); 88 | 89 | encoder.encode(&metrics, &mut buffer)?; 90 | 91 | Response::builder() 92 | .status(200) 93 | .header(CONTENT_TYPE, encoder.format_type()) 94 | .body(Body::from(buffer)) 95 | .map_err(MetricsError::FailedBody) 96 | } 97 | 98 | #[derive(Debug, thiserror::Error)] 99 | enum MetricsError { 100 | #[error("failed encoding metrics {0}")] 101 | FailedEncoding(#[from] prometheus::Error), 102 | 103 | #[error("failed encoding body {0}")] 104 | FailedBody(#[from] axum::http::Error), 105 | } 106 | -------------------------------------------------------------------------------- /crates/pbs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-pbs" 4 | publish = false 5 | rust-version.workspace = true 6 | version.workspace = true 7 | 8 | [dependencies] 9 | alloy.workspace = true 10 | async-trait.workspace = true 11 | axum.workspace = true 12 | axum-extra.workspace = true 13 | blst.workspace = true 14 | cb-common.workspace = true 15 | cb-metrics.workspace = true 16 | eyre.workspace = true 17 | futures.workspace = true 18 | lazy_static.workspace = true 19 | parking_lot.workspace = true 20 | prometheus.workspace = true 21 | reqwest.workspace = true 22 | serde_json.workspace = true 23 | tokio.workspace = true 24 | tracing.workspace = true 25 | tree_hash.workspace = true 26 | url.workspace = true 27 | uuid.workspace = true 28 | -------------------------------------------------------------------------------- /crates/pbs/src/api.rs: -------------------------------------------------------------------------------- 1 | use alloy::rpc::types::beacon::relay::ValidatorRegistration; 2 | use async_trait::async_trait; 3 | use axum::{http::HeaderMap, Router}; 4 | use cb_common::pbs::{ 5 | GetHeaderParams, GetHeaderResponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, 6 | }; 7 | 8 | use crate::{ 9 | mev_boost, 10 | state::{BuilderApiState, PbsState, PbsStateGuard}, 11 | }; 12 | 13 | #[async_trait] 14 | pub trait BuilderApi: 'static { 15 | /// Use to extend the BuilderApi 16 | fn extra_routes() -> Option>> { 17 | None 18 | } 19 | 20 | /// https://ethereum.github.io/builder-specs/#/Builder/getHeader 21 | async fn get_header( 22 | params: GetHeaderParams, 23 | req_headers: HeaderMap, 24 | state: PbsState, 25 | ) -> eyre::Result> { 26 | mev_boost::get_header(params, req_headers, state).await 27 | } 28 | 29 | /// https://ethereum.github.io/builder-specs/#/Builder/status 30 | async fn get_status(req_headers: HeaderMap, state: PbsState) -> eyre::Result<()> { 31 | mev_boost::get_status(req_headers, state).await 32 | } 33 | 34 | /// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock 35 | async fn submit_block( 36 | signed_blinded_block: SignedBlindedBeaconBlock, 37 | req_headers: HeaderMap, 38 | state: PbsState, 39 | ) -> eyre::Result { 40 | mev_boost::submit_block(signed_blinded_block, req_headers, state).await 41 | } 42 | 43 | /// https://ethereum.github.io/builder-specs/#/Builder/registerValidator 44 | async fn register_validator( 45 | registrations: Vec, 46 | req_headers: HeaderMap, 47 | state: PbsState, 48 | ) -> eyre::Result<()> { 49 | mev_boost::register_validator(registrations, req_headers, state).await 50 | } 51 | 52 | async fn reload(state: PbsState) -> eyre::Result> { 53 | mev_boost::reload(state).await 54 | } 55 | } 56 | 57 | pub struct DefaultBuilderApi; 58 | impl BuilderApi<()> for DefaultBuilderApi {} 59 | -------------------------------------------------------------------------------- /crates/pbs/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const STATUS_ENDPOINT_TAG: &str = "status"; 2 | pub const REGISTER_VALIDATOR_ENDPOINT_TAG: &str = "register_validator"; 3 | pub const SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG: &str = "submit_blinded_block"; 4 | pub const GET_HEADER_ENDPOINT_TAG: &str = "get_header"; 5 | pub const RELOAD_ENDPOINT_TAG: &str = "reload"; 6 | 7 | /// For metrics recorded when a request times out 8 | pub const TIMEOUT_ERROR_CODE: u16 = 555; 9 | pub const TIMEOUT_ERROR_CODE_STR: &str = "555"; 10 | 11 | /// 20 MiB to cover edge cases for heavy blocks and also add a bit of slack for 12 | /// any Ethereum upgrades in the near future 13 | pub const MAX_SIZE_SUBMIT_BLOCK_RESPONSE: usize = 20 * 1024 * 1024; 14 | 15 | /// 20 MiB, enough to process ~45000 registrations in one request 16 | pub const MAX_SIZE_REGISTER_VALIDATOR_REQUEST: usize = 20 * 1024 * 1024; 17 | 18 | /// 5 MiB, to account for max execution requests / commitments 19 | pub const MAX_SIZE_GET_HEADER_RESPONSE: usize = 5 * 1024 * 1024; 20 | 21 | pub const MAX_SIZE_DEFAULT: usize = 1024; 22 | -------------------------------------------------------------------------------- /crates/pbs/src/error.rs: -------------------------------------------------------------------------------- 1 | use axum::{http::StatusCode, response::IntoResponse}; 2 | 3 | #[derive(Debug)] 4 | /// Errors that the PbsService returns to client 5 | pub enum PbsClientError { 6 | NoResponse, 7 | NoPayload, 8 | Internal, 9 | } 10 | 11 | impl PbsClientError { 12 | pub fn status_code(&self) -> StatusCode { 13 | match self { 14 | PbsClientError::NoResponse => StatusCode::BAD_GATEWAY, 15 | PbsClientError::NoPayload => StatusCode::BAD_GATEWAY, 16 | PbsClientError::Internal => StatusCode::INTERNAL_SERVER_ERROR, 17 | } 18 | } 19 | } 20 | 21 | impl IntoResponse for PbsClientError { 22 | fn into_response(self) -> axum::response::Response { 23 | let msg = match &self { 24 | PbsClientError::NoResponse => "no response from relays".to_string(), 25 | PbsClientError::NoPayload => "no payload from relays".to_string(), 26 | PbsClientError::Internal => "internal server error".to_string(), 27 | }; 28 | 29 | (self.status_code(), msg).into_response() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/pbs/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod constants; 3 | mod error; 4 | mod metrics; 5 | mod mev_boost; 6 | mod routes; 7 | mod service; 8 | mod state; 9 | mod utils; 10 | 11 | pub use api::*; 12 | pub use constants::*; 13 | pub use mev_boost::*; 14 | pub use service::PbsService; 15 | pub use state::{BuilderApiState, PbsState, PbsStateGuard}; 16 | -------------------------------------------------------------------------------- /crates/pbs/src/metrics.rs: -------------------------------------------------------------------------------- 1 | //! Metrics for PBS module 2 | //! We collect two types of metrics within the PBS module: 3 | //! - what PBS receives from relays 4 | //! - what PBS returns to the beacon node 5 | 6 | use lazy_static::lazy_static; 7 | use prometheus::{ 8 | register_histogram_vec_with_registry, register_int_counter_vec_with_registry, 9 | register_int_gauge_vec_with_registry, HistogramVec, IntCounterVec, IntGaugeVec, Registry, 10 | }; 11 | 12 | lazy_static! { 13 | pub static ref PBS_METRICS_REGISTRY: Registry = 14 | Registry::new_custom(Some("cb_pbs".to_string()), None).unwrap(); 15 | 16 | // FROM RELAYS 17 | /// Status code received by relay by endpoint 18 | pub static ref RELAY_STATUS_CODE: IntCounterVec = register_int_counter_vec_with_registry!( 19 | "relay_status_code_total", 20 | "HTTP status code received by relay", 21 | &["http_status_code", "endpoint", "relay_id"], 22 | PBS_METRICS_REGISTRY 23 | ) 24 | .unwrap(); 25 | 26 | /// Latency by relay by endpoint 27 | pub static ref RELAY_LATENCY: HistogramVec = register_histogram_vec_with_registry!( 28 | "relay_latency", 29 | "HTTP latency by relay", 30 | &["endpoint", "relay_id"], 31 | PBS_METRICS_REGISTRY 32 | ) 33 | .unwrap(); 34 | 35 | /// Latest slot for which relay delivered a header 36 | pub static ref RELAY_LAST_SLOT: IntGaugeVec = register_int_gauge_vec_with_registry!( 37 | "relay_last_slot", 38 | "Latest slot for which relay delivered a header", 39 | &["relay_id"], 40 | PBS_METRICS_REGISTRY 41 | ) 42 | .unwrap(); 43 | 44 | /// Latest slot for which relay delivered a header 45 | // Don't store slot number to avoid creating high cardinality, if needed can just aggregate for 12sec 46 | pub static ref RELAY_HEADER_VALUE: IntGaugeVec = register_int_gauge_vec_with_registry!( 47 | "relay_header_value", 48 | "Header value in gwei delivered by relay", 49 | &["relay_id"], 50 | PBS_METRICS_REGISTRY 51 | ) 52 | .unwrap(); 53 | 54 | 55 | // TO BEACON NODE 56 | /// Status code returned to beacon node by endpoint 57 | pub static ref BEACON_NODE_STATUS: IntCounterVec = register_int_counter_vec_with_registry!( 58 | "beacon_node_status_code_total", 59 | "HTTP status code returned to beacon node", 60 | &["http_status_code", "endpoint"], 61 | PBS_METRICS_REGISTRY 62 | ).unwrap(); 63 | } 64 | -------------------------------------------------------------------------------- /crates/pbs/src/mev_boost/mod.rs: -------------------------------------------------------------------------------- 1 | mod get_header; 2 | mod register_validator; 3 | mod reload; 4 | mod status; 5 | mod submit_block; 6 | 7 | pub use get_header::get_header; 8 | pub use register_validator::register_validator; 9 | pub use reload::reload; 10 | pub use status::get_status; 11 | pub use submit_block::submit_block; 12 | -------------------------------------------------------------------------------- /crates/pbs/src/mev_boost/reload.rs: -------------------------------------------------------------------------------- 1 | use cb_common::config::load_pbs_config; 2 | use tracing::warn; 3 | 4 | use crate::{BuilderApiState, PbsState}; 5 | 6 | /// Reload the PBS state with the latest configuration in the config file 7 | /// Returns 200 if successful or 500 if failed 8 | pub async fn reload(state: PbsState) -> eyre::Result> { 9 | let pbs_config = load_pbs_config().await?; 10 | let new_state = PbsState::new(pbs_config).with_data(state.data); 11 | 12 | if state.config.pbs_config.host != new_state.config.pbs_config.host { 13 | warn!( 14 | "Host change for PBS module require a full restart. Old: {}, New: {}", 15 | state.config.pbs_config.host, new_state.config.pbs_config.host 16 | ); 17 | } 18 | 19 | if state.config.pbs_config.port != new_state.config.pbs_config.port { 20 | warn!( 21 | "Port change for PBS module require a full restart. Old: {}, New: {}", 22 | state.config.pbs_config.port, new_state.config.pbs_config.port 23 | ); 24 | } 25 | 26 | Ok(new_state) 27 | } 28 | -------------------------------------------------------------------------------- /crates/pbs/src/mev_boost/status.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use axum::http::HeaderMap; 4 | use cb_common::{ 5 | pbs::{error::PbsError, RelayClient}, 6 | utils::get_user_agent_with_version, 7 | }; 8 | use futures::future::select_ok; 9 | use reqwest::header::USER_AGENT; 10 | use tracing::{debug, error}; 11 | 12 | use crate::{ 13 | constants::{MAX_SIZE_DEFAULT, STATUS_ENDPOINT_TAG, TIMEOUT_ERROR_CODE_STR}, 14 | metrics::{RELAY_LATENCY, RELAY_STATUS_CODE}, 15 | state::{BuilderApiState, PbsState}, 16 | utils::read_chunked_body_with_max, 17 | }; 18 | 19 | /// Implements https://ethereum.github.io/builder-specs/#/Builder/status 20 | /// Broadcasts a status check to all relays and returns 200 if at least one 21 | /// relay returns 200 22 | pub async fn get_status( 23 | req_headers: HeaderMap, 24 | state: PbsState, 25 | ) -> eyre::Result<()> { 26 | // If no relay check, return early 27 | if !state.config.pbs_config.relay_check { 28 | Ok(()) 29 | } else { 30 | // prepare headers 31 | let mut send_headers = HeaderMap::new(); 32 | send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?); 33 | 34 | let relays = state.all_relays(); 35 | let mut handles = Vec::with_capacity(relays.len()); 36 | for relay in relays { 37 | handles.push(Box::pin(send_relay_check(relay, send_headers.clone()))); 38 | } 39 | 40 | // return ok if at least one relay returns 200 41 | let results = select_ok(handles).await; 42 | match results { 43 | Ok(_) => Ok(()), 44 | Err(err) => Err(err.into()), 45 | } 46 | } 47 | } 48 | 49 | async fn send_relay_check(relay: &RelayClient, headers: HeaderMap) -> Result<(), PbsError> { 50 | let url = relay.get_status_url()?; 51 | 52 | let start_request = Instant::now(); 53 | let res = match relay 54 | .client 55 | .get(url) 56 | .timeout(Duration::from_secs(30)) 57 | .headers(headers) 58 | .send() 59 | .await 60 | { 61 | Ok(res) => res, 62 | Err(err) => { 63 | RELAY_STATUS_CODE 64 | .with_label_values(&[TIMEOUT_ERROR_CODE_STR, STATUS_ENDPOINT_TAG, &relay.id]) 65 | .inc(); 66 | return Err(err.into()); 67 | } 68 | }; 69 | let request_latency = start_request.elapsed(); 70 | RELAY_LATENCY 71 | .with_label_values(&[STATUS_ENDPOINT_TAG, &relay.id]) 72 | .observe(request_latency.as_secs_f64()); 73 | 74 | let code = res.status(); 75 | RELAY_STATUS_CODE.with_label_values(&[code.as_str(), STATUS_ENDPOINT_TAG, &relay.id]).inc(); 76 | 77 | if !code.is_success() { 78 | let response_bytes = read_chunked_body_with_max(res, MAX_SIZE_DEFAULT).await?; 79 | let err = PbsError::RelayResponse { 80 | error_msg: String::from_utf8_lossy(&response_bytes).into_owned(), 81 | code: code.as_u16(), 82 | }; 83 | 84 | error!(relay_id = relay.id.as_ref(),%err, "status failed"); 85 | return Err(err); 86 | }; 87 | 88 | debug!(relay_id = relay.id.as_ref(),?code, latency = ?request_latency, "status passed"); 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/get_header.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::utils::format_ether; 2 | use axum::{ 3 | extract::{Path, State}, 4 | http::HeaderMap, 5 | response::IntoResponse, 6 | }; 7 | use cb_common::{ 8 | pbs::{BuilderEvent, GetHeaderParams}, 9 | utils::{get_user_agent, ms_into_slot}, 10 | }; 11 | use reqwest::StatusCode; 12 | use tracing::{error, info}; 13 | 14 | use crate::{ 15 | api::BuilderApi, 16 | constants::GET_HEADER_ENDPOINT_TAG, 17 | error::PbsClientError, 18 | metrics::BEACON_NODE_STATUS, 19 | state::{BuilderApiState, PbsStateGuard}, 20 | }; 21 | 22 | pub async fn handle_get_header>( 23 | State(state): State>, 24 | req_headers: HeaderMap, 25 | Path(params): Path, 26 | ) -> Result { 27 | tracing::Span::current().record("slot", params.slot); 28 | tracing::Span::current().record("parent_hash", tracing::field::debug(params.parent_hash)); 29 | tracing::Span::current().record("validator", tracing::field::debug(params.pubkey)); 30 | 31 | let state = state.read().clone(); 32 | 33 | state.publish_event(BuilderEvent::GetHeaderRequest(params)); 34 | 35 | let ua = get_user_agent(&req_headers); 36 | let ms_into_slot = ms_into_slot(params.slot, state.config.chain); 37 | 38 | info!(ua, ms_into_slot, "new request"); 39 | 40 | match A::get_header(params, req_headers, state.clone()).await { 41 | Ok(res) => { 42 | state.publish_event(BuilderEvent::GetHeaderResponse(Box::new(res.clone()))); 43 | 44 | if let Some(max_bid) = res { 45 | info!(value_eth = format_ether(max_bid.value()), block_hash =% max_bid.block_hash(), "received header"); 46 | 47 | BEACON_NODE_STATUS.with_label_values(&["200", GET_HEADER_ENDPOINT_TAG]).inc(); 48 | Ok((StatusCode::OK, axum::Json(max_bid)).into_response()) 49 | } else { 50 | // spec: return 204 if request is valid but no bid available 51 | info!("no header available for slot"); 52 | 53 | BEACON_NODE_STATUS.with_label_values(&["204", GET_HEADER_ENDPOINT_TAG]).inc(); 54 | Ok(StatusCode::NO_CONTENT.into_response()) 55 | } 56 | } 57 | Err(err) => { 58 | error!(%err, "no header available from relays"); 59 | 60 | let err = PbsClientError::NoPayload; 61 | BEACON_NODE_STATUS 62 | .with_label_values(&[err.status_code().as_str(), GET_HEADER_ENDPOINT_TAG]) 63 | .inc(); 64 | Err(err) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/mod.rs: -------------------------------------------------------------------------------- 1 | mod get_header; 2 | mod register_validator; 3 | mod reload; 4 | mod router; 5 | mod status; 6 | mod submit_block; 7 | 8 | use get_header::handle_get_header; 9 | use register_validator::handle_register_validator; 10 | pub use router::create_app_router; 11 | use status::handle_get_status; 12 | use submit_block::handle_submit_block; 13 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/register_validator.rs: -------------------------------------------------------------------------------- 1 | use alloy::rpc::types::beacon::relay::ValidatorRegistration; 2 | use axum::{extract::State, http::HeaderMap, response::IntoResponse, Json}; 3 | use cb_common::{pbs::BuilderEvent, utils::get_user_agent}; 4 | use reqwest::StatusCode; 5 | use tracing::{error, info, trace}; 6 | 7 | use crate::{ 8 | api::BuilderApi, 9 | constants::REGISTER_VALIDATOR_ENDPOINT_TAG, 10 | error::PbsClientError, 11 | metrics::BEACON_NODE_STATUS, 12 | state::{BuilderApiState, PbsStateGuard}, 13 | }; 14 | 15 | pub async fn handle_register_validator>( 16 | State(state): State>, 17 | req_headers: HeaderMap, 18 | Json(registrations): Json>, 19 | ) -> Result { 20 | let state = state.read().clone(); 21 | 22 | trace!(?registrations); 23 | state.publish_event(BuilderEvent::RegisterValidatorRequest(registrations.clone())); 24 | 25 | let ua = get_user_agent(&req_headers); 26 | 27 | info!(ua, num_registrations = registrations.len(), "new request"); 28 | 29 | if let Err(err) = A::register_validator(registrations, req_headers, state.clone()).await { 30 | state.publish_event(BuilderEvent::RegisterValidatorResponse); 31 | error!(%err, "all relays failed registration"); 32 | 33 | let err = PbsClientError::NoResponse; 34 | BEACON_NODE_STATUS 35 | .with_label_values(&[err.status_code().as_str(), REGISTER_VALIDATOR_ENDPOINT_TAG]) 36 | .inc(); 37 | Err(err) 38 | } else { 39 | info!("register validator successful"); 40 | 41 | BEACON_NODE_STATUS.with_label_values(&["200", REGISTER_VALIDATOR_ENDPOINT_TAG]).inc(); 42 | Ok(StatusCode::OK) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/reload.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::State, http::HeaderMap, response::IntoResponse}; 2 | use cb_common::{pbs::BuilderEvent, utils::get_user_agent}; 3 | use reqwest::StatusCode; 4 | use tracing::{error, info}; 5 | 6 | use crate::{ 7 | error::PbsClientError, 8 | metrics::BEACON_NODE_STATUS, 9 | state::{BuilderApiState, PbsStateGuard}, 10 | BuilderApi, RELOAD_ENDPOINT_TAG, 11 | }; 12 | 13 | pub async fn handle_reload>( 14 | req_headers: HeaderMap, 15 | State(state): State>, 16 | ) -> Result { 17 | let prev_state = state.read().clone(); 18 | 19 | prev_state.publish_event(BuilderEvent::ReloadEvent); 20 | 21 | let ua = get_user_agent(&req_headers); 22 | 23 | info!(ua, relay_check = prev_state.config.pbs_config.relay_check); 24 | 25 | match A::reload(prev_state.clone()).await { 26 | Ok(new_state) => { 27 | prev_state.publish_event(BuilderEvent::ReloadResponse); 28 | info!("config reload successful"); 29 | 30 | *state.write() = new_state; 31 | 32 | BEACON_NODE_STATUS.with_label_values(&["200", RELOAD_ENDPOINT_TAG]).inc(); 33 | Ok((StatusCode::OK, "OK")) 34 | } 35 | Err(err) => { 36 | error!(%err, "config reload failed"); 37 | 38 | let err = PbsClientError::Internal; 39 | BEACON_NODE_STATUS 40 | .with_label_values(&[err.status_code().as_str(), RELOAD_ENDPOINT_TAG]) 41 | .inc(); 42 | Err(err) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/router.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | extract::{DefaultBodyLimit, MatchedPath, Request}, 3 | middleware::{self, Next}, 4 | response::Response, 5 | routing::{get, post}, 6 | Router, 7 | }; 8 | use axum_extra::headers::{ContentType, HeaderMapExt, UserAgent}; 9 | use cb_common::pbs::{ 10 | BUILDER_API_PATH, GET_HEADER_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, RELOAD_PATH, 11 | SUBMIT_BLOCK_PATH, 12 | }; 13 | use tracing::trace; 14 | use uuid::Uuid; 15 | 16 | use super::{ 17 | handle_get_header, handle_get_status, handle_register_validator, handle_submit_block, 18 | reload::handle_reload, 19 | }; 20 | use crate::{ 21 | api::BuilderApi, 22 | state::{BuilderApiState, PbsStateGuard}, 23 | MAX_SIZE_REGISTER_VALIDATOR_REQUEST, MAX_SIZE_SUBMIT_BLOCK_RESPONSE, 24 | }; 25 | 26 | pub fn create_app_router>(state: PbsStateGuard) -> Router { 27 | // DefaultBodyLimit is 2Mib by default, so we only increase it for a few routes 28 | // thay may need more 29 | 30 | let builder_routes = Router::new() 31 | .route(GET_HEADER_PATH, get(handle_get_header::)) 32 | .route(GET_STATUS_PATH, get(handle_get_status::)) 33 | .route( 34 | REGISTER_VALIDATOR_PATH, 35 | post(handle_register_validator::) 36 | .route_layer(DefaultBodyLimit::max(MAX_SIZE_REGISTER_VALIDATOR_REQUEST)), 37 | ) 38 | .route( 39 | SUBMIT_BLOCK_PATH, 40 | post(handle_submit_block::) 41 | .route_layer(DefaultBodyLimit::max(MAX_SIZE_SUBMIT_BLOCK_RESPONSE)), 42 | ); // header is smaller than the response but err on the safe side 43 | let reload_router = Router::new().route(RELOAD_PATH, post(handle_reload::)); 44 | let builder_api = Router::new().nest(BUILDER_API_PATH, builder_routes).merge(reload_router); 45 | 46 | let app = if let Some(extra_routes) = A::extra_routes() { 47 | builder_api.merge(extra_routes) 48 | } else { 49 | builder_api 50 | }; 51 | 52 | app.layer(middleware::from_fn(tracing_middleware)).with_state(state) 53 | } 54 | 55 | #[tracing::instrument( 56 | name = "", 57 | skip_all, 58 | fields( 59 | method = %req.extensions().get::().map(|m| m.as_str()).unwrap_or("unknown"), 60 | req_id = ?Uuid::new_v4(), 61 | slot = tracing::field::Empty, 62 | block_hash = tracing::field::Empty, 63 | block_number = tracing::field::Empty, 64 | parent_hash = tracing::field::Empty, 65 | validator = tracing::field::Empty, 66 | ), 67 | )] 68 | pub async fn tracing_middleware(req: Request, next: Next) -> Response { 69 | trace!( 70 | http.method = %req.method(), 71 | http.user_agent = req.headers().typed_get::().map(|ua| ua.to_string()).unwrap_or_default(), 72 | http.content_type = req.headers().typed_get::().map(|ua| ua.to_string()).unwrap_or_default(), 73 | "start request"); 74 | 75 | let response = next.run(req).await; 76 | 77 | let status = response.status(); 78 | 79 | trace!(http.response.status_code = ?status, "end request"); 80 | 81 | response 82 | } 83 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/status.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::State, http::HeaderMap, response::IntoResponse}; 2 | use cb_common::{pbs::BuilderEvent, utils::get_user_agent}; 3 | use reqwest::StatusCode; 4 | use tracing::{error, info}; 5 | 6 | use crate::{ 7 | api::BuilderApi, 8 | constants::STATUS_ENDPOINT_TAG, 9 | error::PbsClientError, 10 | metrics::BEACON_NODE_STATUS, 11 | state::{BuilderApiState, PbsStateGuard}, 12 | }; 13 | 14 | pub async fn handle_get_status>( 15 | req_headers: HeaderMap, 16 | State(state): State>, 17 | ) -> Result { 18 | let state = state.read().clone(); 19 | 20 | state.publish_event(BuilderEvent::GetStatusEvent); 21 | 22 | let ua = get_user_agent(&req_headers); 23 | 24 | info!(ua, relay_check = state.config.pbs_config.relay_check, "new request"); 25 | 26 | match A::get_status(req_headers, state.clone()).await { 27 | Ok(_) => { 28 | state.publish_event(BuilderEvent::GetStatusResponse); 29 | info!("relay check successful"); 30 | 31 | BEACON_NODE_STATUS.with_label_values(&["200", STATUS_ENDPOINT_TAG]).inc(); 32 | Ok(StatusCode::OK) 33 | } 34 | Err(err) => { 35 | error!(%err, "all relays failed get_status"); 36 | 37 | let err = PbsClientError::NoResponse; 38 | BEACON_NODE_STATUS 39 | .with_label_values(&[err.status_code().as_str(), STATUS_ENDPOINT_TAG]) 40 | .inc(); 41 | Err(err) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/pbs/src/routes/submit_block.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::State, http::HeaderMap, response::IntoResponse, Json}; 2 | use cb_common::{ 3 | pbs::{BuilderEvent, SignedBlindedBeaconBlock}, 4 | utils::{get_user_agent, timestamp_of_slot_start_millis, utcnow_ms}, 5 | }; 6 | use reqwest::StatusCode; 7 | use tracing::{error, info, trace}; 8 | 9 | use crate::{ 10 | api::BuilderApi, 11 | constants::SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG, 12 | error::PbsClientError, 13 | metrics::BEACON_NODE_STATUS, 14 | state::{BuilderApiState, PbsStateGuard}, 15 | }; 16 | 17 | pub async fn handle_submit_block>( 18 | State(state): State>, 19 | req_headers: HeaderMap, 20 | Json(signed_blinded_block): Json, 21 | ) -> Result { 22 | tracing::Span::current().record("slot", signed_blinded_block.slot()); 23 | tracing::Span::current() 24 | .record("block_hash", tracing::field::debug(signed_blinded_block.block_hash())); 25 | tracing::Span::current().record("block_number", signed_blinded_block.block_number()); 26 | tracing::Span::current() 27 | .record("parent_hash", tracing::field::debug(signed_blinded_block.parent_hash())); 28 | 29 | let state = state.read().clone(); 30 | 31 | state.publish_event(BuilderEvent::SubmitBlockRequest(Box::new(signed_blinded_block.clone()))); 32 | 33 | let now = utcnow_ms(); 34 | let slot = signed_blinded_block.slot(); 35 | let block_hash = signed_blinded_block.block_hash(); 36 | let slot_start_ms = timestamp_of_slot_start_millis(slot, state.config.chain); 37 | let ua = get_user_agent(&req_headers); 38 | 39 | info!(ua, ms_into_slot = now.saturating_sub(slot_start_ms), "new request"); 40 | 41 | match A::submit_block(signed_blinded_block, req_headers, state.clone()).await { 42 | Ok(res) => { 43 | trace!(?res); 44 | state.publish_event(BuilderEvent::SubmitBlockResponse(Box::new(res.clone()))); 45 | info!("received unblinded block"); 46 | 47 | BEACON_NODE_STATUS.with_label_values(&["200", SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG]).inc(); 48 | Ok((StatusCode::OK, Json(res).into_response())) 49 | } 50 | 51 | Err(err) => { 52 | error!(%err, %block_hash, "CRITICAL: no payload received from relays. Check previous logs or use the Relay Data API"); 53 | state.publish_event(BuilderEvent::MissedPayload { block_hash }); 54 | 55 | let err = PbsClientError::NoPayload; 56 | BEACON_NODE_STATUS 57 | .with_label_values(&[err.status_code().as_str(), SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG]) 58 | .inc(); 59 | Err(err) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/pbs/src/service.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use cb_common::{ 4 | constants::{COMMIT_BOOST_COMMIT, COMMIT_BOOST_VERSION}, 5 | pbs::{BUILDER_API_PATH, GET_STATUS_PATH}, 6 | types::Chain, 7 | }; 8 | use cb_metrics::provider::MetricsProvider; 9 | use eyre::{bail, Context, Result}; 10 | use parking_lot::RwLock; 11 | use prometheus::core::Collector; 12 | use tokio::net::TcpListener; 13 | use tracing::info; 14 | use url::Url; 15 | 16 | use crate::{ 17 | api::BuilderApi, 18 | metrics::PBS_METRICS_REGISTRY, 19 | routes::create_app_router, 20 | state::{BuilderApiState, PbsState}, 21 | }; 22 | 23 | pub struct PbsService; 24 | 25 | impl PbsService { 26 | pub async fn run>(state: PbsState) -> Result<()> { 27 | let addr = state.config.endpoint; 28 | let events_subs = 29 | state.config.event_publisher.as_ref().map(|e| e.n_subscribers()).unwrap_or_default(); 30 | info!(version = COMMIT_BOOST_VERSION, commit_hash = COMMIT_BOOST_COMMIT, ?addr, events_subs, chain =? state.config.chain, "starting PBS service"); 31 | 32 | let app = create_app_router::(RwLock::new(state).into()); 33 | let listener = TcpListener::bind(addr).await?; 34 | 35 | let task = 36 | tokio::spawn( 37 | async move { axum::serve(listener, app).await.wrap_err("PBS server exited") }, 38 | ); 39 | 40 | // wait for the server to start 41 | tokio::time::sleep(Duration::from_millis(250)).await; 42 | let local_url = 43 | Url::parse(&format!("http://{}{}{}", addr, BUILDER_API_PATH, GET_STATUS_PATH))?; 44 | 45 | let status = reqwest::get(local_url).await?; 46 | if !status.status().is_success() { 47 | bail!("PBS server failed to start. Are the relays properly configured?"); 48 | } 49 | 50 | task.await? 51 | } 52 | 53 | pub fn register_metric(c: Box) { 54 | PBS_METRICS_REGISTRY.register(c).expect("failed to register metric"); 55 | } 56 | 57 | pub fn init_metrics(network: Chain) -> Result<()> { 58 | MetricsProvider::load_and_run(network, PBS_METRICS_REGISTRY.clone()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/pbs/src/state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use alloy::rpc::types::beacon::BlsPublicKey; 4 | use cb_common::{ 5 | config::{PbsConfig, PbsModuleConfig}, 6 | pbs::{BuilderEvent, RelayClient}, 7 | }; 8 | use parking_lot::RwLock; 9 | 10 | pub trait BuilderApiState: Clone + Sync + Send + 'static {} 11 | impl BuilderApiState for () {} 12 | 13 | pub type PbsStateGuard = Arc>>; 14 | 15 | /// Config for the Pbs module. It can be extended by adding extra data to the 16 | /// state for modules that need it 17 | // TODO: consider remove state from the PBS module altogether 18 | #[derive(Clone)] 19 | pub struct PbsState { 20 | /// Config data for the Pbs service 21 | pub config: PbsModuleConfig, 22 | /// Opaque extra data for library use 23 | pub data: S, 24 | } 25 | 26 | impl PbsState<()> { 27 | pub fn new(config: PbsModuleConfig) -> Self { 28 | Self { config, data: () } 29 | } 30 | 31 | pub fn with_data(self, data: S) -> PbsState { 32 | PbsState { data, config: self.config } 33 | } 34 | } 35 | 36 | impl PbsState 37 | where 38 | S: BuilderApiState, 39 | { 40 | pub fn publish_event(&self, e: BuilderEvent) { 41 | if let Some(publisher) = self.config.event_publisher.as_ref() { 42 | publisher.publish(e); 43 | } 44 | } 45 | 46 | // Getters 47 | pub fn pbs_config(&self) -> &PbsConfig { 48 | &self.config.pbs_config 49 | } 50 | 51 | /// Returns all the relays (including those in muxes) 52 | /// DO NOT use this through the PBS module, use 53 | /// [`PbsState::mux_config_and_relays`] instead 54 | pub fn all_relays(&self) -> &[RelayClient] { 55 | &self.config.all_relays 56 | } 57 | 58 | /// Returns the PBS config and relay clients for the given validator pubkey. 59 | /// If the pubkey is not found in any mux, the default configs are 60 | /// returned 61 | pub fn mux_config_and_relays( 62 | &self, 63 | pubkey: &BlsPublicKey, 64 | ) -> (&PbsConfig, &[RelayClient], Option<&str>) { 65 | match self.config.muxes.as_ref().and_then(|muxes| muxes.get(pubkey)) { 66 | Some(mux) => (&mux.config, mux.relays.as_slice(), Some(&mux.id)), 67 | // return only the default relays if there's no match 68 | None => (self.pbs_config(), &self.config.relays, None), 69 | } 70 | } 71 | 72 | pub fn extra_validation_enabled(&self) -> bool { 73 | self.config.pbs_config.extra_validation_enabled 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/pbs/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cb_common::pbs::error::PbsError; 2 | use futures::StreamExt; 3 | use reqwest::Response; 4 | 5 | pub async fn read_chunked_body_with_max( 6 | res: Response, 7 | max_size: usize, 8 | ) -> Result, PbsError> { 9 | let mut stream = res.bytes_stream(); 10 | let mut response_bytes = Vec::new(); 11 | 12 | while let Some(chunk) = stream.next().await { 13 | let chunk = chunk?; 14 | if response_bytes.len() + chunk.len() > max_size { 15 | // avoid spamming logs if the message is too large 16 | response_bytes.truncate(1024); 17 | return Err(PbsError::PayloadTooLarge { 18 | max: max_size, 19 | raw: String::from_utf8_lossy(&response_bytes).into_owned(), 20 | }); 21 | } 22 | 23 | response_bytes.extend_from_slice(&chunk); 24 | } 25 | 26 | Ok(response_bytes) 27 | } 28 | 29 | const GAS_LIMIT_ADJUSTMENT_FACTOR: u64 = 1024; 30 | const GAS_LIMIT_MINIMUM: u64 = 5_000; 31 | 32 | /// Validates the gas limit against the parent gas limit, according to the 33 | /// execution spec https://github.com/ethereum/execution-specs/blob/98d6ddaaa709a2b7d0cd642f4cfcdadc8c0808e1/src/ethereum/cancun/fork.py#L1118-L1154 34 | pub fn check_gas_limit(gas_limit: u64, parent_gas_limit: u64) -> bool { 35 | let max_adjustment_delta = parent_gas_limit / GAS_LIMIT_ADJUSTMENT_FACTOR; 36 | if gas_limit >= parent_gas_limit + max_adjustment_delta { 37 | return false; 38 | } 39 | 40 | if gas_limit <= parent_gas_limit - max_adjustment_delta { 41 | return false; 42 | } 43 | 44 | if gas_limit < GAS_LIMIT_MINIMUM { 45 | return false; 46 | } 47 | 48 | true 49 | } 50 | -------------------------------------------------------------------------------- /crates/signer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-signer" 4 | publish = false 5 | rust-version.workspace = true 6 | version.workspace = true 7 | 8 | [dependencies] 9 | alloy.workspace = true 10 | axum.workspace = true 11 | axum-extra.workspace = true 12 | bimap.workspace = true 13 | blsful.workspace = true 14 | cb-common.workspace = true 15 | cb-metrics.workspace = true 16 | eyre.workspace = true 17 | futures.workspace = true 18 | headers.workspace = true 19 | lazy_static.workspace = true 20 | prometheus.workspace = true 21 | prost.workspace = true 22 | rand.workspace = true 23 | thiserror.workspace = true 24 | tokio.workspace = true 25 | tonic.workspace = true 26 | tracing.workspace = true 27 | tree_hash.workspace = true 28 | uuid.workspace = true 29 | jsonwebtoken.workspace = true 30 | 31 | [build-dependencies] 32 | tonic-build.workspace = true 33 | -------------------------------------------------------------------------------- /crates/signer/README.md: -------------------------------------------------------------------------------- 1 | # Signer Module 2 | 3 | ## Compile proto files 4 | 5 | ### Requirements 6 | 7 | - `protoc` compiler 8 | - Submodules initialized: `git submodule update --init` 9 | 10 | To compile the `.proto` files and generate the Rust bindings, simply run `cargo build`. This will only run if there are changes to the `.proto` files. If you want to force a rebuild, run `cargo clean` first. 11 | 12 | Note that this process is not required to run Commit-Boost as the generated files are already included in the repository. 13 | -------------------------------------------------------------------------------- /crates/signer/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | std::env::set_var("OUT_DIR", "src/proto"); 3 | tonic_build::configure().build_server(false).compile_protos( 4 | &[ 5 | "proto/pb/v1/lister.proto", 6 | "proto/pb/v1/accountmanager.proto", 7 | "proto/pb/v1/signer.proto", 8 | ], 9 | &["proto/pb/v1", "proto/third-party"], 10 | )?; 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /crates/signer/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const GET_PUBKEYS_ENDPOINT_TAG: &str = "get_pubkeys"; 2 | pub const GENERATE_PROXY_KEY_ENDPOINT_TAG: &str = "generate_proxy_key"; 3 | pub const REQUEST_SIGNATURE_ENDPOINT_TAG: &str = "request_signature"; 4 | -------------------------------------------------------------------------------- /crates/signer/src/error.rs: -------------------------------------------------------------------------------- 1 | use alloy::hex; 2 | use axum::{ 3 | http::StatusCode, 4 | response::{IntoResponse, Response}, 5 | }; 6 | use thiserror::Error; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum SignerModuleError { 10 | #[error("unauthorized")] 11 | Unauthorized, 12 | 13 | #[error("signer error: {0}")] 14 | SignerError(#[from] alloy::signers::Error), 15 | 16 | #[error("unknown consensus signer: 0x{}", hex::encode(.0))] 17 | UnknownConsensusSigner(Vec), 18 | 19 | #[error("unknown proxy signer: 0x{}", hex::encode(.0))] 20 | UnknownProxySigner(Vec), 21 | 22 | #[error("Dirk communication error: {0}")] 23 | DirkCommunicationError(String), 24 | 25 | #[error("Dirk signer does not support this operation")] 26 | DirkNotSupported, 27 | 28 | #[error("internal error: {0}")] 29 | Internal(String), 30 | } 31 | 32 | impl IntoResponse for SignerModuleError { 33 | fn into_response(self) -> Response { 34 | match self { 35 | SignerModuleError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()), 36 | SignerModuleError::UnknownConsensusSigner(_) => { 37 | (StatusCode::NOT_FOUND, self.to_string()) 38 | } 39 | SignerModuleError::UnknownProxySigner(_) => (StatusCode::NOT_FOUND, self.to_string()), 40 | SignerModuleError::DirkCommunicationError(_) => { 41 | (StatusCode::BAD_GATEWAY, "Dirk communication error".to_string()) 42 | } 43 | SignerModuleError::DirkNotSupported => (StatusCode::BAD_REQUEST, self.to_string()), 44 | SignerModuleError::Internal(_) => { 45 | (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string()) 46 | } 47 | SignerModuleError::SignerError(err) => (StatusCode::BAD_REQUEST, err.to_string()), 48 | } 49 | .into_response() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/signer/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod constants; 2 | pub mod error; 3 | pub mod manager; 4 | mod metrics; 5 | mod proto; 6 | pub mod service; 7 | -------------------------------------------------------------------------------- /crates/signer/src/manager/mod.rs: -------------------------------------------------------------------------------- 1 | use cb_common::{commit::request::ConsensusProxyMap, types::ModuleId}; 2 | use dirk::DirkManager; 3 | use local::LocalSigningManager; 4 | 5 | use crate::error::SignerModuleError; 6 | 7 | pub mod dirk; 8 | pub mod local; 9 | 10 | #[derive(Clone)] 11 | pub enum SigningManager { 12 | Local(LocalSigningManager), 13 | Dirk(DirkManager), 14 | } 15 | 16 | impl SigningManager { 17 | /// Amount of consensus signers available 18 | pub fn available_consensus_signers(&self) -> usize { 19 | match self { 20 | SigningManager::Local(local_manager) => local_manager.consensus_pubkeys().len(), 21 | SigningManager::Dirk(dirk_manager) => dirk_manager.available_consensus_signers(), 22 | } 23 | } 24 | 25 | /// Amount of proxy signers available 26 | pub fn available_proxy_signers(&self) -> usize { 27 | match self { 28 | SigningManager::Local(local_manager) => local_manager.available_proxy_signers(), 29 | SigningManager::Dirk(dirk_manager) => dirk_manager.available_proxy_signers(), 30 | } 31 | } 32 | 33 | pub fn get_consensus_proxy_maps( 34 | &self, 35 | module_id: &ModuleId, 36 | ) -> Result, SignerModuleError> { 37 | match self { 38 | SigningManager::Local(local_manager) => { 39 | local_manager.get_consensus_proxy_maps(module_id) 40 | } 41 | SigningManager::Dirk(dirk_manager) => { 42 | Ok(dirk_manager.get_consensus_proxy_maps(module_id)) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /crates/signer/src/metrics.rs: -------------------------------------------------------------------------------- 1 | //! Metrics for Signer module 2 | 3 | use axum::http::Uri; 4 | use cb_common::commit::constants::{ 5 | GENERATE_PROXY_KEY_PATH, GET_PUBKEYS_PATH, REQUEST_SIGNATURE_PATH, 6 | }; 7 | use lazy_static::lazy_static; 8 | use prometheus::{register_int_counter_vec_with_registry, IntCounterVec, Registry}; 9 | 10 | use crate::constants::{ 11 | GENERATE_PROXY_KEY_ENDPOINT_TAG, GET_PUBKEYS_ENDPOINT_TAG, REQUEST_SIGNATURE_ENDPOINT_TAG, 12 | }; 13 | 14 | lazy_static! { 15 | pub static ref SIGNER_METRICS_REGISTRY: Registry = 16 | Registry::new_custom(Some("cb_signer".to_string()), None).unwrap(); 17 | 18 | /// Status code returned by endpoint 19 | pub static ref SIGNER_STATUS: IntCounterVec = register_int_counter_vec_with_registry!( 20 | "signer_status_code_total", 21 | "HTTP status code returned by signer", 22 | &["http_status_code", "endpoint"], 23 | SIGNER_METRICS_REGISTRY 24 | ).unwrap(); 25 | } 26 | 27 | pub fn uri_to_tag(uri: &Uri) -> &str { 28 | match uri.path() { 29 | GET_PUBKEYS_PATH => GET_PUBKEYS_ENDPOINT_TAG, 30 | GENERATE_PROXY_KEY_PATH => GENERATE_PROXY_KEY_ENDPOINT_TAG, 31 | REQUEST_SIGNATURE_PATH => REQUEST_SIGNATURE_ENDPOINT_TAG, 32 | _ => "unknown endpoint", 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/signer/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | #[rustfmt::skip] 2 | pub mod v1; 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/architecture/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/docs/architecture/img/architecture.png -------------------------------------------------------------------------------- /docs/docs/architecture/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Overview of the architecture of Commit-Boost 3 | --- 4 | 5 | # Overview 6 | 7 | Below is schematic overview of Commit-Boost. 8 | 9 | Commit-Boost runs as a single sidecar composed of multiple modules: 10 | - Pbs Module with the [BuilderAPI](https://ethereum.github.io/builder-specs/) for [MEV Boost](https://docs.flashbots.net/flashbots-mev-boost/architecture-overview/specifications) 11 | - A Signer Module implementing the SignerAPI 12 | - Commit Modules that implement some custom commit protocol logic 13 | - Telemetry modules like Prometheus and Grafana 14 | 15 | ![architecture](./img/architecture.png) 16 | -------------------------------------------------------------------------------- /docs/docs/developing/custom-modules.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | # Custom Modules 5 | Commit-Boost aims to provide an open platform for developers to create and distribute commitment protocols sidecars. 6 | 7 | There are three ways to leverage Commit-Boost modularity: 8 | 9 | 1. Commit Modules, which request signatures from the proposer, e.g. for preconfirmations ([example](https://github.com/Commit-Boost/commit-boost-client/tree/78bdc47bf89082f4d1ea302f9a3f86f609966b28/examples/da_commit)). 10 | 2. PBS Modules, which tweak the default PBS Module with additional logic, e.g. verifying additional constraints in `get_header` ([example](https://github.com/Commit-Boost/commit-boost-client/tree/78bdc47bf89082f4d1ea302f9a3f86f609966b28/examples/status_api)). 11 | 3. PBS Events, which trigger based on the different events of the PBS lifecycle and can be used e.g. for monitoring and reporting ([example](https://github.com/Commit-Boost/commit-boost-client/tree/78bdc47bf89082f4d1ea302f9a3f86f609966b28/examples/builder_log)). 12 | -------------------------------------------------------------------------------- /docs/docs/developing/environment-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | # Environment setup 5 | 6 | ## Dirk signer 7 | 8 | In order to test Commit-Boost with a Dirk signer, you need to have a running Dirk instance. You can find a complete step-by-step guide on how to setup one in the Dirk's docs [here](https://github.com/attestantio/dirk/blob/master/docs/distributed_key_generation.md). 9 | 10 | If you are using a custom certificate authority, don't forget to add the CA certificate to the TOML config under `signer.dirk.ca_cert_path`. 11 | -------------------------------------------------------------------------------- /docs/docs/get_started/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Initial setup 3 | --- 4 | 5 | # Overview 6 | 7 | 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. 8 | 9 | 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. 10 | Commit-Boost ships with two core modules: 11 | - 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). 12 | - A signer module, which implements the [Signer API](/api) and provides the interface for modules to request proposer commitments. 13 | 14 | ## Setup 15 | 16 | The Commit-Boost CLI creates a dynamic `docker-compose` file, with services and ports already set up. 17 | 18 | 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. 19 | 20 | ## Binaries and images 21 | Find the latest releases at https://github.com/Commit-Boost/commit-boost-client/releases. 22 | 23 | The modules are also published at [each release](https://github.com/orgs/Commit-Boost/packages?repo_name=commit-boost-client). 24 | 25 | ### From source 26 | Requirements: 27 | - Rust 1.83 28 | 29 | :::note 30 | Run `rustup update` to update Rust and Cargo to the latest version 31 | ::: 32 | 33 | ```bash 34 | # Pull the repo 35 | git clone https://github.com/Commit-Boost/commit-boost-client 36 | 37 | # Stable branch has the latest released version 38 | git checkout stable 39 | ``` 40 | 41 | :::note 42 | 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` 43 | ::: 44 | 45 | ### Docker 46 | You will need to build the CLI to create the `docker-compose` file: 47 | 48 | ```bash 49 | # Build the CLI 50 | cargo build --release --bin commit-boost-cli 51 | 52 | # Check that it works 53 | ./target/release/commit-boost-cli --version 54 | ``` 55 | 56 | and the modules as Docker images: 57 | ```bash 58 | docker build -t commitboost_pbs_default . -f ./provisioning/pbs.Dockerfile 59 | docker build -t commitboost_signer . -f ./provisioning/signer.Dockerfile 60 | ``` 61 | 62 | 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. 63 | 64 | ### Binaries 65 | 66 | 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: 67 | 68 | ```bash 69 | # Build the PBS module 70 | cargo build --release --bin commit-boost-pbs 71 | 72 | # Build the Signer module 73 | cargo build --release --bin commit-boost-signer 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /docs/docs/get_started/running/binary.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Run Commit-Boost modules natively 3 | --- 4 | 5 | # Binary 6 | 7 | :::warning 8 | Running the modules natively means you opt out of the security guarantees made by Docker and it's up to you how to setup and ensure the modules run safely. 9 | ::: 10 | 11 | 12 | ## Setup 13 | Get the binary of the module either by compiling from source or by downloading a [published release](https://github.com/Commit-Boost/commit-boost-client/releases). 14 | 15 | Modules need some environment variables to work correctly. 16 | 17 | ### Common 18 | - `CB_CONFIG`: required, path to the `.toml` config file. 19 | - `CHAIN_SPEC_ENV`: optional, path to a chain spec file. This will override the `[chain]` field in the `.toml` config. 20 | - `CB_METRICS_PORT`: optional, port where to expose the `/metrics` endpoint for Prometheus. 21 | - `CB_LOGS_DIR`: optional, directory to store logs. This will override the directory in the `.toml` config. 22 | 23 | ### PBS Module 24 | - `CB_BUILDER_URLS`: optional, comma-separated list of urls to `events` modules where to post builder events. 25 | - `CB_PBS_ENDPOINT`: optional, override the endpoint where the PBS module will open the port for the beacon node. 26 | - `CB_MUX_PATH_{ID}`: optional, override where to load mux validator keys for mux with `id=\{ID\}`. 27 | 28 | ### Signer Module 29 | - `CB_SIGNER_PORT`: required, port to open the signer server on. 30 | - For loading keys we currently support: 31 | - `CB_SIGNER_LOADER_FILE`: path to a `.json` with plaintext keys (for testing purposes only). 32 | - `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). 33 | - For storing proxy keys we currently support: 34 | - `CB_PROXY_STORE_DIR`: directory where proxy keys and delegations will be saved in plaintext (for testing purposes only). 35 | - `CB_PROXY_KEYS_DIR` and `CB_PROXY_SECRETS_DIR`: paths to the `keys` and `secrets` directories or files (ERC-2335 style keystores, see [Proxy keys store](../configuration/#proxy-keys-store) for more info). 36 | - For Dirk remote signer the following envs are available (see [Dirk config](../configuration/#dirk) for more info): 37 | - `CB_SIGNER_DIRK_CERT_FILE`: required, path to the client certificate file. 38 | - `CB_SIGNER_DIRK_KEY_FILE`: required, path to the client key file. 39 | - `CB_SIGNER_DIRK_SECRETS_DIR`: required, path to the secrets directory. 40 | - `CB_SIGNER_DIRK_CA_CERT_FILE`: optional, path to the CA certificate file. 41 | 42 | ### Modules 43 | - `CB_MODULE_ID`: required, unique id of the module. 44 | 45 | #### Commit modules 46 | - `CB_SIGNER_URL`: required, url to the signer module server. 47 | - `CB_SIGNER_JWT`: required, jwt to use for signature requests. 48 | 49 | #### Events modules 50 | - `CB_BUILDER_PORT`: required, port to open to receive builder events from the PBS module. 51 | 52 | Modules might also have additional envs required, which should be detailed by the maintainers. 53 | 54 | ## Start 55 | 56 | After creating the `cb-config.toml` file, setup the required envs and run the binary. For example: 57 | 58 | ```bash 59 | CB_CONFIG=./cb-config.toml commit-boost-pbs 60 | ``` 61 | 62 | ## Security 63 | Running the modules natively means you opt out of the security guarantees made by Docker and it's up to you how to setup and ensure the modules run safely. 64 | -------------------------------------------------------------------------------- /docs/docs/get_started/running/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Run Commit-Boost with Docker 3 | --- 4 | 5 | # Docker 6 | 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. 7 | 8 | ## Init 9 | 10 | First run: 11 | ```bash 12 | commit-boost-cli init --config cb-config.toml 13 | ``` 14 | This will create up to three files: 15 | - `cb.docker-compose.yml` which contains the full setup of the Commit-Boost services. 16 | - `.cb.env` with local env variables, including JWTs for modules, only created if the signer module is enabled. 17 | - `target.json` which enables dynamic discovery of services for metrics scraping via Prometheus, only created if metrics are enabled. 18 | 19 | ## Start 20 | 21 | To start Commit-Boost run: 22 | ```bash 23 | docker compose --env-file ".cb.env" -f ".cb.docker-compose.yml" up -d 24 | ``` 25 | 26 | This will run all the configured services, including PBS, signer and modules (if any). 27 | 28 | The MEV-Boost server will be exposed at `pbs.port` from the config, `18550` in our example. You'll need to point your CL/Validator client to this port to be able to source blocks from the builder market. 29 | 30 | ## Logs 31 | To check the logs, run: 32 | ```bash 33 | docker compose --env-file ".cb.env" -f ".cb.docker-compose.yml" logs -f 34 | ``` 35 | This will currently show all logs from the different services via the Docker logs interface. Logs are also optionally saved to file, depending on your `[logs]` configuration. 36 | 37 | ## Stop 38 | 39 | To stop all the services and cleanup, simply run: 40 | ```bash 41 | docker compose --env-file ".cb.env" -f ".cb.docker-compose.yml" down 42 | ``` 43 | This will wind down all services and clear internal networks and file mounts. -------------------------------------------------------------------------------- /docs/docs/get_started/running/metrics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Setup metrics collection 3 | --- 4 | 5 | # Metrics 6 | 7 | Commit-Boost can be configured to collect metrics from the different services and expose them to be scraped from Prometheus. 8 | 9 | Make sure to add the `[metrics]` section to your config file: 10 | 11 | ```toml 12 | [metrics] 13 | enabled = true 14 | ``` 15 | 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). 16 | 17 | ## Example setup 18 | 19 | :::note 20 | The following examples assume you're running Prometheus/Grafana on the same machine as Commit-Boost. In general you should avoid this setup, and instead run them on a separate machine. cAdvisor should run in the same machine as the containers you want to monitor. 21 | ::: 22 | 23 | 24 | ### cAdvisor 25 | [cAdvisor](https://github.com/google/cadvisor) is a tool for collecting and reporting resource usage and performance characteristics of running containers. 26 | 27 | ```yml title="cb.docker-compose.yml" 28 | cb_cadvisor: 29 | image: gcr.io/cadvisor/cadvisor 30 | container_name: cb_cadvisor 31 | ports: 32 | - 127.0.0.1:8080:8080 33 | volumes: 34 | - /var/run/docker.sock:/var/run/docker.sock:ro 35 | - /sys:/sys:ro 36 | - /var/lib/docker/:/var/lib/docker:ro 37 | ``` 38 | 39 | ### Prometheus 40 | 41 | For more information on how to setup Prometheus, see the [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/getting_started/). 42 | 43 | ```yml title="cb.docker-compose.yml" 44 | cb_prometheus: 45 | image: prom/prometheus:v3.0.0 46 | container_name: cb_prometheus 47 | ports: 48 | - 127.0.0.1:9090:9090 49 | volumes: 50 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 51 | - prometheus-data:/prometheus 52 | ``` 53 | 54 | ```yml title="prometheus.yml" 55 | global: 56 | scrape_interval: 15s 57 | 58 | scrape_configs: 59 | - job_name: "commit-boost" 60 | static_configs: 61 | - targets: ["cb_da_commit:10000", "cb_pbs:10001", "cb_signer:10002", "cb_cadvisor:8080"] 62 | ``` 63 | 64 | ### Grafana 65 | For more information on how to setup Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/getting-started/). 66 | 67 | ```yml title="cb.docker-compose.yml" 68 | cb_grafana: 69 | image: grafana/grafana:11.3.1 70 | container_name: cb_grafana 71 | ports: 72 | - 127.0.0.1:3000:3000 73 | volumes: 74 | - ./grafana/datasources:/etc/grafana/provisioning/datasources 75 | - grafana-data:/var/lib/grafana 76 | ``` 77 | 78 | ```yml title="datasources.yml" 79 | apiVersion: 1 80 | 81 | datasources: 82 | - name: prometheus 83 | type: prometheus 84 | uid: prometheus 85 | access: proxy 86 | orgId: 1 87 | url: http://cb_prometheus:9090 88 | isDefault: true 89 | editable: true 90 | ``` 91 | 92 | Once Grafana is running, you can [import](https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/) the Commit-Boost dashboards from [here](https://github.com/Commit-Boost/commit-boost-client/tree/main/provisioning/grafana), making sure to select the correct `Prometheus` datasource. 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/docs/get_started/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Common issues 3 | --- 4 | 5 | # Troubleshooting 6 | 7 | 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). 8 | 9 | 10 | If you started the modules correctly you should see the following logs. 11 | 12 | ## PBS 13 | After the module started correctly you should see: 14 | ```bash 15 | 2024-09-16T19:27:16.004643Z INFO Starting PBS service address=0.0.0.0:18550 events_subs=0 16 | ``` 17 | 18 | To check that the setup is correct and you are connected to relays, you can trigger manually the `/status` endpoint, by running: 19 | 20 | ```bash 21 | curl http://0.0.0.0:18550/eth/v1/builder/status -vvv 22 | 23 | * Trying 0.0.0.0:18550... 24 | * Connected to 0.0.0.0 (127.0.0.1) port 18550 (#0) 25 | > GET /eth/v1/builder/status HTTP/1.1 26 | > Host: 0.0.0.0:18550 27 | > User-Agent: curl/7.81.0 28 | > Accept: */* 29 | > 30 | * Mark bundle as not supporting multiuse 31 | < HTTP/1.1 200 OK 32 | < content-length: 0 33 | < date: Mon, 16 Sep 2024 19:32:07 GMT 34 | < 35 | * Connection #0 to host 0.0.0.0 left intact 36 | ``` 37 | 38 | if now you check the logs, you should see: 39 | 40 | ```bash 41 | 2024-09-16T19:32:07.634966Z INFO status{req_id=62f1c0db-f277-49fa-91e7-a9a1c2b2a6d3}: ua="curl/7.81.0" relay_check=true 42 | 2024-09-16T19:32:07.642992Z INFO status{req_id=62f1c0db-f277-49fa-91e7-a9a1c2b2a6d3}: relay check successful 43 | ``` 44 | 45 | If the sidecar is setup correctly, it will receive and process calls from the CL: 46 | #### Register validator 47 | This should happen periodically, depending on your validator setup. 48 | 49 | ```bash 50 | 2024-09-16T19:28:37.976534Z INFO register_validators{req_id=296f662f-0e7a-4f15-be75-55b8ca19ffc0}: ua="Lighthouse/v5.2.1-9e12c21" num_registrations=500 51 | 2024-09-16T19:28:38.819591Z INFO register_validators{req_id=296f662f-0e7a-4f15-be75-55b8ca19ffc0}: register validator successful 52 | ``` 53 | 54 | #### Get header 55 | This will only happen if some of your validators have a proposal slot coming up. 56 | 57 | ```bash 58 | 2024-09-16T19:30:24.135376Z INFO get_header{req_id=74126c5f-69e6-4961-86a6-6c2597bf15f5 slot=2551052}: ua="Lighthouse/v5.2.1-9e12c21" parent_hash=0x641c99d6e4f14bf6d268eb2a8c0dc51c7030ab24e384c0e679f2a6b438d298ea validator_pubkey=0x84fc20b09496341f24abfcb6f407e916ecc317497c5b1bba4970e50e96cf5e731b88e51753064c30cb221453bd71aebf ms_into_slot=135 59 | 2024-09-16T19:30:25.089477Z INFO get_header{req_id=74126c5f-69e6-4961-86a6-6c2597bf15f5 slot=2551052}: received header block_hash=0x0139686e8d251f010153875270256fce6f298d7b3f3f9129179fb86297dffad3 value_eth="0.001399518501462470" 60 | ``` 61 | 62 | #### Submit block 63 | This will only happen if you received a header in the previous call, and if the header is higher than the locally built block. 64 | 65 | ```bash 66 | 2024-09-16T14:38:01.409075Z INFO submit_blinded_block{req_id=6eb9a04d-6f79-4295-823f-c054582b3599 slot=2549590}: ua="Lighthouse/v5.2.1-9e12c21" slot_uuid=16186e06-0cd0-47bc-9758-daa1b66eff5c ms_into_slot=1409 block_hash=0xfa135ae6f2bfb32b0a47368f93d69e0a2b3f8b855d917ec61d78e78779edaae6 67 | 2024-09-16T14:38:02.910974Z INFO submit_blinded_block{req_id=6eb9a04d-6f79-4295-823f-c054582b3599 slot=2549590}: received unblinded block 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: introduction 3 | sidebar_position: 1 4 | slug: / 5 | --- 6 | 7 | # Introduction 8 | 9 | import CommitBoostLogo from '/img/logo.png'; 10 | import Overview from '/img/overview.png'; 11 | 12 | Commit Boost Logo 13 | 14 |
15 |
16 | 17 | Commit-Boost is a new Ethereum validator sidecar focused on standardizing the communication between validators and third-party protocols. This open-source public good is fully compatible with [MEV-Boost](https://github.com/flashbots/mev-boost) and acts as a light-weight platform to allow validators to safely make commitments. 18 | 19 | Commit-Boost runs as a single sidecar composed of multiple modules: 20 | 21 | Commit Boost Overview 22 | 23 |
24 |
25 | 26 | Commit-Boost is being developed in Rust from scratch, and has been designed with safety and modularity at its core, with the goal of not limiting the market downstream including stakeholders, flows, proposer commitments, enforcement mechanisms, etc. 27 | 28 | Please see Commit-Boost, Inc's [terms of service](https://github.com/Commit-Boost/commit-boost-client/blob/main/TERMS-OF-USE.md). 29 | -------------------------------------------------------------------------------- /docs/docs/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Overview 6 | 7 | ## Background 8 | - Proposer commitments have been an important part of Ethereum’s history. Today, we already see the power of commitments where over 90% of validators give up their autonomy and make a wholesale commitment that outsources block building to a sophisticated actor called a block builder. 9 | - However, most are starting to agree on a common denominator: in the future, beacon proposers will face a broader set of options of what they may “commit" to–be it inclusions lists or preconfs or other types of commitments such as long-dated blockspace futures–compared to just an external or local payload they see today. 10 | - A recent post from Barnabe captures this well; during block construction, the validator “…creates the specs, or the template, by which the resulting block must be created, and the builders engaged by the proposer are tasked with delivering the block according to its specifications”. 11 | - While this all seems great, the challenge is that many teams building commitments are creating new sidecars driving fragmentation and risks for Ethereum. 12 | - For Ethereum, there are going to be significant challenges and increased risks during upgrades if there are a handful of sidecars validators are running. 13 | - For validators, these risks potentially take us to a world where proposers will need to make decisions on which teams to “bet on” and which sidecars they will need to run to participate in what those teams are offering. 14 | - For homestakers, this is difficult and they likely will be unable to participate in more than one of these commitments. 15 | - For sophisticated actors, this increases the attack vector and operational complexity as more and more sidecars are required to be run. 16 | - Another side effect of this is validators are somewhat locked into using a specific sidecar due to limited operational capacity and the switching costs of running a different sidecar (i.e., vendor lock-in). The higher the switching costs, the more embedded network effects could become if these sidecars only support certain downstream actors / proposer commitment protocols. 17 | - This also could create a dynamic where core out-of-protocol infrastructure supporting Ethereum which should be a public good, starts being used for monetization, distribution, or other purposes. 18 | - Commit-Boost aims to standardize how proposer commitment protocols communicate with the proposer, by providing a unified interface implemented in a single validator sidecar with the goal of reducing fragmentation. 19 | 20 | ## Goals 21 | - Unify behind a software / standard to reduce fragmentation risks for Ethereum and its validators, while ensuring open innovation downstream from the proposer can flourish. 22 | - Create a neutral, open-source, public good for the safe development and distribution of proposer commitments protocols. 23 | - Provide a well-tested, reliable validator sidecar with support for advanced observability and telemetry. 24 | 25 | ## Why Commit-Boost? 26 | 27 | ### For validators 28 | - Run a single sidecar with support for MEV-Boost and other proposer commitments protocols, such as preconfirmations and inclusion lists. 29 | - Out-of-the-box support for metrics reporting and dashboards to have clear insight into what is happening in your validator. 30 | - Plug-in system to add custom modules, e.g. receive a notification on Telegram if a relay fails to deliver a block. 31 | 32 | ### For developers 33 | - A modular platform to develop and distribute proposer commitments protocols. 34 | - A single API to interact with validators. 35 | - Support for hard-forks and new protocol requirements. 36 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // `@type` JSDoc annotations allow editor autocompletion and type checking 3 | // (when paired with `@ts-check`). 4 | // There are various equivalent ways to declare your Docusaurus config. 5 | // See: https://docusaurus.io/docs/api/docusaurus-config 6 | 7 | import { themes as prismThemes } from 'prism-react-renderer'; 8 | 9 | /** @type {import('@docusaurus/types').Config} */ 10 | const config = { 11 | title: 'Commit Boost', 12 | tagline: 'Commit-Boost allows Ethereum validators to safely run MEV-Boost and community-built commitment protocols', 13 | favicon: 'img/favicon.ico', 14 | 15 | // Set the production url of your site here 16 | url: 'https://commit-boost.github.io', 17 | // Set the // pathname under which your site is served 18 | // For GitHub pages deployment, it is often '//' 19 | baseUrl: '/commit-boost-client/', 20 | 21 | // GitHub pages deployment config. 22 | // If you aren't using GitHub pages, you don't need these. 23 | organizationName: 'Commit-Boost', // Usually your GitHub org/user name. 24 | projectName: 'commit-boost-client', // Usually your repo name. 25 | 26 | onBrokenLinks: 'throw', 27 | onBrokenMarkdownLinks: 'warn', 28 | 29 | // Even if you don't use internationalization, you can use this field to set 30 | // useful metadata like html lang. For example, if your site is Chinese, you 31 | // may want to replace "en" with "zh-Hans". 32 | i18n: { 33 | defaultLocale: 'en', 34 | locales: ['en'], 35 | }, 36 | 37 | presets: [ 38 | [ 39 | 'classic', 40 | /** @type {import('@docusaurus/preset-classic').Options} */ 41 | ({ 42 | docs: { 43 | routeBasePath: '/', 44 | sidebarPath: './sidebars.js', 45 | // Please change this to your repo. 46 | // Remove this to remove the "edit this page" links. 47 | editUrl: 48 | 'https://github.com/Commit-Boost/commit-boost-client/tree/main/docs/', 49 | }, 50 | blog: false, 51 | theme: { 52 | customCss: './src/css/custom.css', 53 | }, 54 | }), 55 | ], 56 | ], 57 | 58 | themeConfig: 59 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 60 | ({ 61 | // Replace with your project's social card 62 | image: 'img/icon.png', 63 | navbar: { 64 | title: 'Commit Boost', 65 | logo: { 66 | alt: 'Commit Boost Icon', 67 | src: 'img/icon.png', 68 | }, 69 | items: [ 70 | { to: '/', label: 'Docs', position: 'left' }, 71 | { to: '/api', label: 'API', position: 'left' }, 72 | { 73 | href: 'https://github.com/Commit-Boost/commit-boost-client', 74 | label: 'GitHub', 75 | position: 'right', 76 | }, 77 | ], 78 | }, 79 | footer: { 80 | style: 'dark', 81 | links: [ 82 | // { 83 | // title: 'Docs', 84 | // items: [ 85 | // { 86 | // label: 'Tutorial', 87 | // to: '/docs/intro', 88 | // }, 89 | // ], 90 | // }, 91 | { 92 | title: 'Links', 93 | items: [ 94 | { 95 | label: 'Github', 96 | href: 'https://github.com/Commit-Boost/commit-boost-client', 97 | }, 98 | { 99 | label: 'X (Twitter)', 100 | href: 'https://x.com/Commit_Boost', 101 | }, 102 | { 103 | label: 'Telegram', 104 | href: 'https://t.me/+Pcs9bykxK3BiMzk5', 105 | }, 106 | ], 107 | }, 108 | ], 109 | // copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`, 110 | }, 111 | prism: { 112 | theme: prismThemes.github, 113 | darkTheme: prismThemes.dracula, 114 | additionalLanguages: ['bash','toml'], 115 | }, 116 | }), 117 | }; 118 | 119 | export default config; 120 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "3.4.0", 18 | "@docusaurus/preset-classic": "3.4.0", 19 | "@mdx-js/react": "^3.0.0", 20 | "clsx": "^2.0.0", 21 | "prism-react-renderer": "^2.3.0", 22 | "react": "^18.0.0", 23 | "react-dom": "^18.0.0", 24 | "swagger-ui-react": "^5.17.14" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "3.4.0", 28 | "@docusaurus/types": "3.4.0" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 3 chrome version", 38 | "last 3 firefox version", 39 | "last 5 safari version" 40 | ] 41 | }, 42 | "engines": { 43 | "node": ">=18.0" 44 | }, 45 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 46 | } 47 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | // tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], 18 | 19 | 20 | tutorialSidebar: [ 21 | 'introduction', 22 | 'overview', 23 | { 24 | type: 'category', 25 | label: 'Get started', 26 | link: { 27 | type: 'generated-index', 28 | }, 29 | collapsed: false, 30 | items: [ 31 | 'get_started/overview', 32 | 'get_started/configuration', 33 | { 34 | type: 'category', 35 | label: 'Running', 36 | link: { 37 | type: 'generated-index', 38 | }, 39 | items: [ 40 | 'get_started/running/docker', 41 | 'get_started/running/binary', 42 | 'get_started/running/metrics', 43 | 44 | ], 45 | }, 46 | 'get_started/troubleshooting', 47 | ], 48 | }, 49 | { 50 | type: 'category', 51 | label: 'Developing', 52 | link: { 53 | type: 'generated-index', 54 | }, 55 | items: [ 56 | 'developing/custom-modules', 57 | 'developing/commit-module', 58 | 'developing/environment-setup', 59 | ], 60 | }, 61 | { 62 | type: 'category', 63 | label: 'Architecture', 64 | link: { 65 | type: 'generated-index', 66 | }, 67 | items: [ 68 | 'architecture/overview', 69 | ], 70 | }, 71 | { 72 | type: 'html', 73 | value: '
', // This adds a horizontal line separator 74 | }, 75 | { 76 | type: 'link', 77 | label: 'Github', 78 | href: 'https://github.com/Commit-Boost/commit-boost-client', 79 | }, 80 | { 81 | type: 'link', 82 | label: 'X (Twitter)', 83 | href: 'https://x.com/Commit_Boost', 84 | }, 85 | { 86 | type: 'link', 87 | label: 'Telegram', 88 | href: 'https://t.me/+Pcs9bykxK3BiMzk5', 89 | }, 90 | ], 91 | 92 | // But you can create a sidebar manually 93 | /* 94 | tutorialSidebar: [ 95 | 'intro', 96 | 'hello', 97 | { 98 | type: 'category', 99 | label: 'Tutorial', 100 | items: ['tutorial-basics/create-a-document'], 101 | }, 102 | ], 103 | */ 104 | }; 105 | 106 | export default sidebars; 107 | -------------------------------------------------------------------------------- /docs/src/components/SwaggerUI.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SwaggerUI from 'swagger-ui-react'; 3 | import 'swagger-ui-react/swagger-ui.css'; 4 | 5 | const SwaggerUIComponent = () => { 6 | return ; 7 | }; 8 | 9 | export default SwaggerUIComponent; 10 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/api.js: -------------------------------------------------------------------------------- 1 | // src/pages/swagger.js 2 | import React from 'react'; 3 | import Layout from '@theme/Layout'; 4 | import SwaggerUIComponent from '../components/SwaggerUI'; 5 | 6 | const SwaggerPage = () => { 7 | return ( 8 | 9 |
10 | 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default SwaggerPage; 17 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/static/img/icon.png -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/static/img/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/docs/static/img/overview.png -------------------------------------------------------------------------------- /examples/builder_log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "builder_log" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | async-trait.workspace = true 9 | commit-boost = { path = "../../bin" } 10 | eyre.workspace = true 11 | tokio.workspace = true 12 | tracing.workspace = true 13 | -------------------------------------------------------------------------------- /examples/builder_log/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef 2 | WORKDIR /app 3 | 4 | FROM chef AS planner 5 | COPY . . 6 | RUN cargo chef prepare --recipe-path recipe.json 7 | 8 | FROM chef AS builder 9 | COPY --from=planner /app/recipe.json recipe.json 10 | 11 | RUN cargo chef cook --release --recipe-path recipe.json 12 | 13 | RUN apt-get update && apt-get install -y protobuf-compiler 14 | 15 | COPY . . 16 | RUN cargo build --release --bin builder_log 17 | 18 | 19 | FROM ubuntu AS runtime 20 | WORKDIR /app 21 | 22 | RUN apt-get update 23 | RUN apt-get install -y openssl ca-certificates libssl3 libssl-dev 24 | 25 | COPY --from=builder /app/target/release/builder_log /usr/local/bin 26 | ENTRYPOINT ["/usr/local/bin/builder_log"] 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/builder_log/src/main.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use commit_boost::prelude::*; 3 | use tracing::{error, info}; 4 | 5 | #[derive(Debug, Clone)] 6 | struct LogProcessor; 7 | 8 | #[async_trait] 9 | impl OnBuilderApiEvent for LogProcessor { 10 | async fn on_builder_api_event(&self, event: BuilderEvent) { 11 | info!(?event, "Received builder event"); 12 | } 13 | } 14 | 15 | #[tokio::main] 16 | async fn main() -> eyre::Result<()> { 17 | match load_builder_module_config::<()>() { 18 | Ok(config) => { 19 | let _guard = initialize_tracing_log(&config.id, LogsSettings::from_env_config()?); 20 | 21 | info!(module_id = %config.id, "Starting module"); 22 | 23 | let client = BuilderEventClient::new(config.server_port, LogProcessor); 24 | 25 | if let Err(err) = client.run().await { 26 | error!(%err, "Service failed"); 27 | } 28 | } 29 | Err(err) => { 30 | eprintln!("Failed to load module config: {err:?}"); 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/configs/custom_chain.toml: -------------------------------------------------------------------------------- 1 | # PBS config with a custom chain spec file 2 | 3 | # genesis time in seconds needs to be specified 4 | chain = { genesis_time_secs = 100, path = "tests/data/holesky_spec.json" } 5 | 6 | [pbs] 7 | port = 18550 8 | 9 | [[relays]] 10 | id = "example-relay" 11 | url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" 12 | -------------------------------------------------------------------------------- /examples/configs/dirk_signer.toml: -------------------------------------------------------------------------------- 1 | chain = "Holesky" 2 | 3 | [pbs] 4 | port = 18550 5 | 6 | [[relays]] 7 | url = "http://0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09fe73ccd21f88eab31d6de16194d17782e@def.xyz" 8 | 9 | [signer] 10 | docker_image = "commitboost_signer" 11 | 12 | [signer.dirk] 13 | cert_path = "/path/to/client.crt" 14 | key_path = "/path/to/client.key" 15 | secrets_path = "./dirk_secrets" 16 | ca_cert_path = "/path/to/ca.crt" 17 | 18 | # Example of a single Dirk host 19 | [[signer.dirk.hosts]] 20 | url = "https://gateway.dirk.url" 21 | accounts = ["wallet1/accountX", "wallet2/accountY"] 22 | 23 | [signer.dirk.store] 24 | proxy_dir = "/path/to/proxies" 25 | 26 | [[modules]] 27 | id = "DA_COMMIT" 28 | type = "commit" 29 | docker_image = "test_da_commit" 30 | sleep_secs = 10 31 | use_ecdsa_keys = false 32 | -------------------------------------------------------------------------------- /examples/configs/minimal.toml: -------------------------------------------------------------------------------- 1 | # Minimal PBS config, with no metrics and only stdout logs 2 | 3 | chain = "Holesky" 4 | 5 | [pbs] 6 | port = 18550 7 | 8 | [[relays]] 9 | id = "example-relay" 10 | url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" 11 | -------------------------------------------------------------------------------- /examples/configs/pbs_metrics.toml: -------------------------------------------------------------------------------- 1 | # PBS + metrics + logs to file 2 | 3 | chain = "Holesky" 4 | 5 | [pbs] 6 | port = 18550 7 | 8 | [[relays]] 9 | id = "example-relay" 10 | url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" 11 | 12 | [metrics] 13 | enabled = true 14 | 15 | [logs] 16 | log_dir_path = "./logs" 17 | -------------------------------------------------------------------------------- /examples/configs/pbs_mux.toml: -------------------------------------------------------------------------------- 1 | # PBS config with a mux for a single validator 2 | 3 | chain = "Holesky" 4 | 5 | [pbs] 6 | port = 18550 7 | timeout_get_header_ms = 950 8 | late_in_slot_time_ms = 2000 9 | rpc_url = "https://ethereum-holesky-rpc.publicnode.com" 10 | 11 | # Used for all validators except the ones in the mux 12 | [[relays]] 13 | id = "relay-1" 14 | url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" 15 | 16 | 17 | [[mux]] 18 | id = "timing-mux" 19 | validator_pubkeys = [ 20 | "0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745", 21 | ] 22 | loader = "./tests/data/mux_keys.example.json" 23 | timeout_get_header_ms = 900 24 | late_in_slot_time_ms = 1500 25 | 26 | 27 | [[mux.relays]] 28 | id = "relay-2" 29 | url = "http://0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09fe73ccd21f88eab31d6de16194d17782e@def.xyz" 30 | enable_timing_games = true 31 | target_first_request_ms = 200 32 | 33 | 34 | [[mux]] 35 | id = "lido-mux" 36 | loader = { registry = "lido", node_operator_id = 8 } 37 | 38 | [[mux.relays]] 39 | id = "relay-3" 40 | url = "http://0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745@fgh.xyz" 41 | 42 | [[mux]] 43 | id = "ssv-mux" 44 | loader = { registry = "ssv", node_operator_id = 200 } 45 | 46 | [[mux.relays]] 47 | id = "relay-4" 48 | url = "http://0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745@fgh.xyz" 49 | -------------------------------------------------------------------------------- /examples/da_commit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "da_commit" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | alloy.workspace = true 9 | color-eyre.workspace = true 10 | commit-boost = { path = "../../bin" } 11 | eyre.workspace = true 12 | lazy_static.workspace = true 13 | prometheus.workspace = true 14 | serde.workspace = true 15 | serde_json.workspace = true 16 | tokio.workspace = true 17 | tracing.workspace = true 18 | -------------------------------------------------------------------------------- /examples/da_commit/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef 2 | WORKDIR /app 3 | 4 | FROM chef AS planner 5 | COPY . . 6 | RUN cargo chef prepare --recipe-path recipe.json 7 | 8 | FROM chef AS builder 9 | COPY --from=planner /app/recipe.json recipe.json 10 | 11 | RUN cargo chef cook --release --recipe-path recipe.json 12 | 13 | RUN apt-get update && apt-get install -y protobuf-compiler 14 | 15 | COPY . . 16 | RUN cargo build --release --bin da_commit 17 | 18 | 19 | FROM ubuntu AS runtime 20 | WORKDIR /app 21 | 22 | RUN apt-get update 23 | RUN apt-get install -y openssl ca-certificates libssl3 libssl-dev 24 | 25 | COPY --from=builder /app/target/release/da_commit /usr/local/bin 26 | ENTRYPOINT ["/usr/local/bin/da_commit"] 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/status_api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "status_api" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | async-trait.workspace = true 9 | axum.workspace = true 10 | color-eyre.workspace = true 11 | commit-boost = { path = "../../bin" } 12 | eyre.workspace = true 13 | lazy_static.workspace = true 14 | prometheus.workspace = true 15 | reqwest.workspace = true 16 | serde.workspace = true 17 | tokio.workspace = true 18 | tracing.workspace = true 19 | -------------------------------------------------------------------------------- /examples/status_api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef 2 | WORKDIR /app 3 | 4 | FROM chef AS planner 5 | COPY . . 6 | RUN cargo chef prepare --recipe-path recipe.json 7 | 8 | FROM chef AS builder 9 | COPY --from=planner /app/recipe.json recipe.json 10 | 11 | RUN cargo chef cook --release --recipe-path recipe.json 12 | 13 | RUN apt-get update && apt-get install -y protobuf-compiler 14 | 15 | COPY . . 16 | RUN cargo build --release --bin status_api 17 | 18 | 19 | FROM ubuntu AS runtime 20 | WORKDIR /app 21 | 22 | RUN apt-get update 23 | RUN apt-get install -y openssl ca-certificates libssl3 libssl-dev 24 | 25 | COPY --from=builder /app/target/release/status_api /usr/local/bin 26 | ENTRYPOINT ["/usr/local/bin/status_api"] 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/status_api/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicU64, Ordering}, 3 | Arc, 4 | }; 5 | 6 | use async_trait::async_trait; 7 | use axum::{ 8 | extract::State, 9 | response::{IntoResponse, Response}, 10 | routing::get, 11 | Router, 12 | }; 13 | use commit_boost::prelude::*; 14 | use eyre::Result; 15 | use lazy_static::lazy_static; 16 | use prometheus::IntCounter; 17 | use reqwest::{header::HeaderMap, StatusCode}; 18 | use serde::Deserialize; 19 | use tracing::info; 20 | 21 | lazy_static! { 22 | pub static ref CHECK_RECEIVED_COUNTER: IntCounter = 23 | IntCounter::new("checks", "successful /check requests received").unwrap(); 24 | } 25 | 26 | /// Extra config loaded from the config file 27 | /// You should add an `inc_amount` field to the config file in the `pbs` 28 | /// section. Be sure also to change the `pbs.docker_image` field, 29 | /// `test_status_api` in this case (from scripts/build_local_modules.sh). 30 | #[derive(Debug, Deserialize)] 31 | struct ExtraConfig { 32 | inc_amount: u64, 33 | } 34 | 35 | // Extra state available at runtime 36 | #[derive(Clone)] 37 | struct MyBuilderState { 38 | inc_amount: u64, 39 | counter: Arc, 40 | } 41 | 42 | impl BuilderApiState for MyBuilderState {} 43 | 44 | impl MyBuilderState { 45 | fn from_config(extra: ExtraConfig) -> Self { 46 | Self { inc_amount: extra.inc_amount, counter: Arc::new(AtomicU64::new(0)) } 47 | } 48 | 49 | fn inc(&self) { 50 | self.counter.fetch_add(self.inc_amount, Ordering::Relaxed); 51 | } 52 | fn get(&self) -> u64 { 53 | self.counter.load(Ordering::Relaxed) 54 | } 55 | } 56 | 57 | struct MyBuilderApi; 58 | 59 | #[async_trait] 60 | impl BuilderApi for MyBuilderApi { 61 | async fn get_status(req_headers: HeaderMap, state: PbsState) -> Result<()> { 62 | state.data.inc(); 63 | info!("THIS IS A CUSTOM LOG"); 64 | CHECK_RECEIVED_COUNTER.inc(); 65 | get_status(req_headers, state).await 66 | } 67 | 68 | async fn reload(state: PbsState) -> Result> { 69 | let (pbs_config, extra_config) = load_pbs_custom_config::().await?; 70 | let mut data = state.data.clone(); 71 | data.inc_amount = extra_config.inc_amount; 72 | 73 | Ok(PbsState::new(pbs_config).with_data(data)) 74 | } 75 | 76 | fn extra_routes() -> Option>> { 77 | let mut router = Router::new(); 78 | router = router.route("/check", get(handle_check)); 79 | Some(router) 80 | } 81 | } 82 | 83 | async fn handle_check(State(state): State>) -> Response { 84 | (StatusCode::OK, format!("Received {count} status requests!", count = state.read().data.get())) 85 | .into_response() 86 | } 87 | 88 | #[tokio::main] 89 | async fn main() -> Result<()> { 90 | color_eyre::install()?; 91 | 92 | let (pbs_config, extra) = load_pbs_custom_config::().await?; 93 | let chain = pbs_config.chain; 94 | let _guard = initialize_tracing_log(PBS_MODULE_NAME, LogsSettings::from_env_config()?)?; 95 | 96 | let custom_state = MyBuilderState::from_config(extra); 97 | let state = PbsState::new(pbs_config).with_data(custom_state); 98 | 99 | PbsService::register_metric(Box::new(CHECK_RECEIVED_COUNTER.clone())); 100 | PbsService::init_metrics(chain)?; 101 | 102 | PbsService::run::(state).await 103 | } 104 | -------------------------------------------------------------------------------- /provisioning/k8s/README.md: -------------------------------------------------------------------------------- 1 | # Commit-Boost on k8s 2 | 3 | Currently, only the PBS module is supported and it can be used as a 4 | drop-in replacement to mev-boost. To quickly install it, from the 5 | chart directory, edit the `values.yaml` configuration file to your 6 | liking, then: 7 | 8 | ``` 9 | helm install commit-boost . -f values.yaml 10 | ``` 11 | 12 | By default the PBS service should be available on port `18550`, you 13 | can then point your beacons to it. 14 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: commit-boost 3 | description: A Helm chart for Kubernetes 4 | type: application 5 | version: 0.0.5 6 | appVersion: "v0.4.0" 7 | maintainers: 8 | - name: mxs 9 | email: mxs@kiln.fi 10 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/README.md: -------------------------------------------------------------------------------- 1 | # commit-boost 2 | 3 | ![Version: 0.0.5](https://img.shields.io/badge/Version-0.0.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.4.0](https://img.shields.io/badge/AppVersion-v0.4.0-informational?style=flat-square) 4 | 5 | A Helm chart for Kubernetes 6 | 7 | ## Maintainers 8 | 9 | | Name | Email | Url | 10 | | ---- | ------ | --- | 11 | | mxs | | | 12 | 13 | ## Values 14 | 15 | | Key | Type | Default | Description | 16 | |-----|------|---------|-------------| 17 | | affinity | object | `{}` | | 18 | | autoscaling.enabled | bool | `false` | | 19 | | autoscaling.maxReplicas | int | `100` | | 20 | | autoscaling.minReplicas | int | `1` | | 21 | | autoscaling.targetCPUUtilizationPercentage | int | `80` | | 22 | | commitBoost.pbs.config.chain | string | `"Holesky"` | | 23 | | commitBoost.pbs.config.metrics.prometheus_config | string | `"/dev/null"` | | 24 | | commitBoost.pbs.config.metrics.server_port | int | `10000` | | 25 | | commitBoost.pbs.config.mux | list | `[]` | | 26 | | commitBoost.pbs.config.pbs.port | int | `18550` | | 27 | | commitBoost.pbs.config.relays | list | `[]` | | 28 | | commitBoost.pbs.enable | bool | `true` | | 29 | | commitBoost.pbs.image.pullPolicy | string | `"IfNotPresent"` | | 30 | | commitBoost.pbs.image.repository | string | `"ghcr.io/commit-boost/pbs"` | | 31 | | commitBoost.pbs.image.tag | string | `"v0.4.0"` | | 32 | | fullnameOverride | string | `""` | | 33 | | imagePullSecrets | list | `[]` | | 34 | | ingress.annotations | object | `{}` | | 35 | | ingress.className | string | `""` | | 36 | | ingress.enabled | bool | `false` | | 37 | | ingress.hosts[0].host | string | `"chart-example.local"` | | 38 | | ingress.hosts[0].paths[0].path | string | `"/"` | | 39 | | ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | 40 | | ingress.tls | list | `[]` | | 41 | | nameOverride | string | `""` | | 42 | | nodeSelector | object | `{}` | | 43 | | podAnnotations | object | `{}` | | 44 | | podLabels | object | `{}` | | 45 | | podSecurityContext | object | `{}` | | 46 | | replicaCount | int | `1` | | 47 | | resources | object | `{}` | | 48 | | securityContext | object | `{}` | | 49 | | service.pbs_metrics_port | int | `10000` | | 50 | | service.pbs_port | int | `18550` | | 51 | | service.type | string | `"ClusterIP"` | | 52 | | serviceAccount.annotations | object | `{}` | | 53 | | serviceAccount.automount | bool | `true` | | 54 | | serviceAccount.create | bool | `true` | | 55 | | serviceAccount.name | string | `""` | | 56 | | serviceMonitor.additionalLabels | object | `{}` | | 57 | | serviceMonitor.enabled | bool | `true` | | 58 | | serviceMonitor.metricRelabelings | list | `[]` | | 59 | | serviceMonitor.namespace | string | `""` | | 60 | | serviceMonitor.namespaceSelector | object | `{}` | | 61 | | serviceMonitor.path | string | `"/metrics"` | | 62 | | serviceMonitor.scrapeInterval | string | `"30s"` | | 63 | | serviceMonitor.targetLabels | list | `[]` | | 64 | | tolerations | list | `[]` | | 65 | | volumeMounts | list | `[]` | | 66 | | volumes | list | `[]` | | 67 | 68 | ---------------------------------------------- 69 | Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) 70 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "commit-boost.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "commit-boost.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "commit-boost.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "commit-boost.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "commit-boost.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "commit-boost.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "commit-boost.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "commit-boost.labels" -}} 37 | helm.sh/chart: {{ include "commit-boost.chart" . }} 38 | {{ include "commit-boost.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "commit-boost.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "commit-boost.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "commit-boost.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "commit-boost.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "commit-boost.fullname" . }} 5 | labels: 6 | {{- include "commit-boost.labels" . | nindent 4 }} 7 | data: 8 | {{- if .Values.commitBoost.pbs.enable }} 9 | # Currently toToml doesn't handle properly integer conversions, it 10 | # translates them to floats with a trailing ".0" leading to commit-boost 11 | # crashing at start. We have three possible approaches to fix this: 12 | # 13 | # 1. Do not use toToml and manually convert all config fields from yaml to 14 | # toml, this means the chart won't be in sync with evolutions of 15 | # commit-boost, 16 | # 17 | # 2. Have commit-boost handle floats as integers in the config parsing, this 18 | # would likely be confusing for everyone else using commit-boost, 19 | # 20 | # 3. Introduce a temporary hack to remove trailing .0 from the toml output. This 21 | # is the current temporary approach taken. It's dangerous because it might 22 | # remove ".0" from other places where it's actually needed. 23 | # 24 | # We use 3) for now considering https://github.com/helm/helm/pull/13534 is likely 25 | # to be part of a new Helm version. 26 | config.pbs.toml: |- 27 | {{ .Values.commitBoost.pbs.config | toToml | replace ".0" "" | nindent 4 }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "commit-boost.fullname" . }} 5 | labels: 6 | {{- include "commit-boost.labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "commit-boost.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "commit-boost.labels" . | nindent 8 }} 22 | {{- with .Values.podLabels }} 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | spec: 26 | {{- with .Values.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- toYaml . | nindent 8 }} 29 | {{- end }} 30 | serviceAccountName: {{ include "commit-boost.serviceAccountName" . }} 31 | securityContext: 32 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 33 | containers: 34 | {{- if .Values.commitBoost.pbs.enable }} 35 | - name: {{ .Chart.Name }}-pbs 36 | securityContext: 37 | {{- toYaml .Values.securityContext | nindent 12 }} 38 | image: "{{ .Values.commitBoost.pbs.image.repository }}:{{ .Values.commitBoost.pbs.image.tag }}" 39 | imagePullPolicy: {{ .Values.commitBoost.pbs.image.pullPolicy }} 40 | ports: 41 | - name: http-pbs 42 | containerPort: {{ .Values.commitBoost.pbs.config.pbs.port }} 43 | protocol: TCP 44 | - name: http-metrics 45 | containerPort: {{ .Values.commitBoost.pbs.config.metrics.server_port }} 46 | protocol: TCP 47 | livenessProbe: 48 | httpGet: 49 | path: /status 50 | port: http-metrics 51 | readinessProbe: 52 | httpGet: 53 | path: /status 54 | port: http-metrics 55 | resources: 56 | {{- toYaml .Values.resources | nindent 12 }} 57 | env: 58 | - name: CB_CONFIG 59 | value: /config/config.pbs.toml 60 | - name: CB_METRICS_PORT 61 | value: "{{ .Values.commitBoost.pbs.config.metrics.server_port }}" 62 | - name: ROLLING_DURATION 63 | value: never 64 | - name: RUST_LOG 65 | value: info 66 | - name: CB_PBS_ENDPOINT 67 | value: "0.0.0.0:{{ .Values.commitBoost.pbs.config.pbs.port }}" 68 | volumeMounts: 69 | - name: commit-boost-configs 70 | mountPath: /config 71 | {{- with .Values.volumeMounts }} 72 | {{- toYaml . | nindent 12 }} 73 | {{- end }} 74 | {{- end }} 75 | volumes: 76 | - name: commit-boost-configs 77 | configMap: 78 | defaultMode: 0755 79 | name: {{ include "commit-boost.fullname" . }} 80 | items: 81 | {{- if .Values.commitBoost.pbs.enable }} 82 | - key: config.pbs.toml 83 | path: config.pbs.toml 84 | {{- end }} 85 | {{- with .Values.volumes }} 86 | {{- toYaml . | nindent 8 }} 87 | {{- end }} 88 | {{- with .Values.nodeSelector }} 89 | nodeSelector: 90 | {{- toYaml . | nindent 8 }} 91 | {{- end }} 92 | {{- with .Values.affinity }} 93 | affinity: 94 | {{- toYaml . | nindent 8 }} 95 | {{- end }} 96 | {{- with .Values.tolerations }} 97 | tolerations: 98 | {{- toYaml . | nindent 8 }} 99 | {{- end }} 100 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "commit-boost.fullname" . }} 6 | labels: 7 | {{- include "commit-boost.labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "commit-boost.fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | target: 21 | type: Utilization 22 | averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 23 | {{- end }} 24 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 25 | - type: Resource 26 | resource: 27 | name: memory 28 | target: 29 | type: Utilization 30 | averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 31 | {{- end }} 32 | {{- end }} 33 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "commit-boost.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "commit-boost.labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.commitBoost.pbs.enable }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "commit-boost.fullname" . }} 6 | labels: 7 | {{- include "commit-boost.labels" . | nindent 4 }} 8 | spec: 9 | type: ClusterIP 10 | ports: 11 | - port: {{ .Values.commitBoost.pbs.config.pbs.port}} 12 | name: http-pbs 13 | targetPort: http-pbs 14 | protocol: TCP 15 | - port: {{ .Values.commitBoost.pbs.config.metrics.server_port}} 16 | protocol: TCP 17 | name: http-metrics 18 | targetPort: http-metrics 19 | selector: 20 | {{- include "commit-boost.selectorLabels" . | nindent 4 }} 21 | {{- end }} -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "commit-boost.serviceAccountName" . }} 6 | labels: 7 | {{- include "commit-boost.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | automountServiceAccountToken: {{ .Values.serviceAccount.automount }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceMonitor.enabled -}} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "commit-boost.fullname" . }} 6 | {{- if .Values.serviceMonitor.namespace }} 7 | namespace: {{ .Values.serviceMonitor.namespace | quote }} 8 | {{- end }} 9 | labels: 10 | {{- include "commit-boost.labels" . | nindent 4 }} 11 | {{- if .Values.serviceMonitor.additionalLabels }} 12 | {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | endpoints: 16 | - port: http-metrics 17 | interval: {{ .Values.serviceMonitor.scrapeInterval }} 18 | {{- if .Values.serviceMonitor.honorLabels }} 19 | honorLabels: true 20 | {{- end }} 21 | {{- if .Values.serviceMonitor.metricRelabelings }} 22 | metricRelabelings: {{ toYaml .Values.serviceMonitor.metricRelabelings | nindent 8 }} 23 | {{- end }} 24 | path: {{ .Values.serviceMonitor.path }} 25 | {{- if .Values.serviceMonitor.namespaceSelector }} 26 | namespaceSelector: {{ toYaml .Values.serviceMonitor.namespaceSelector | nindent 4 }} 27 | {{ else }} 28 | namespaceSelector: 29 | matchNames: 30 | - {{ .Release.Namespace }} 31 | {{- end }} 32 | {{- if .Values.serviceMonitor.targetLabels }} 33 | targetLabels: 34 | {{- range .Values.serviceMonitor.targetLabels }} 35 | - {{ . }} 36 | {{- end }} 37 | {{- end }} 38 | selector: 39 | matchLabels: 40 | {{- include "commit-boost.selectorLabels" . | nindent 6 }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "commit-boost.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "commit-boost.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "commit-boost.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /provisioning/k8s/commit-boost/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for commit-boost. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | imagePullSecrets: [] 8 | nameOverride: "" 9 | fullnameOverride: "" 10 | 11 | serviceAccount: 12 | # Specifies whether a service account should be created 13 | create: true 14 | # Automatically mount a ServiceAccount's API credentials? 15 | automount: true 16 | # Annotations to add to the service account 17 | annotations: {} 18 | # The name of the service account to use. 19 | # If not set and create is true, a name is generated using the fullname template 20 | name: "" 21 | 22 | podAnnotations: {} 23 | podLabels: {} 24 | 25 | podSecurityContext: 26 | {} 27 | # fsGroup: 2000 28 | 29 | securityContext: 30 | {} 31 | # capabilities: 32 | # drop: 33 | # - ALL 34 | # readOnlyRootFilesystem: true 35 | # runAsNonRoot: true 36 | # runAsUser: 1000 37 | 38 | service: 39 | type: ClusterIP 40 | pbs_port: 18550 41 | pbs_metrics_port: 10000 42 | 43 | ingress: 44 | enabled: false 45 | className: "" 46 | annotations: 47 | {} 48 | # kubernetes.io/ingress.class: nginx 49 | # kubernetes.io/tls-acme: "true" 50 | hosts: 51 | - host: chart-example.local 52 | paths: 53 | - path: / 54 | pathType: ImplementationSpecific 55 | tls: [] 56 | # - secretName: chart-example-tls 57 | # hosts: 58 | # - chart-example.local 59 | 60 | resources: 61 | {} 62 | # We usually recommend not to specify default resources and to leave this as a conscious 63 | # choice for the user. This also increases chances charts run on environments with little 64 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 65 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 66 | # limits: 67 | # cpu: 100m 68 | # memory: 128Mi 69 | # requests: 70 | # cpu: 100m 71 | # memory: 128Mi 72 | 73 | autoscaling: 74 | enabled: false 75 | minReplicas: 1 76 | maxReplicas: 100 77 | targetCPUUtilizationPercentage: 80 78 | # targetMemoryUtilizationPercentage: 80 79 | 80 | # Additional volumes on the output Deployment definition. 81 | volumes: [] 82 | # - name: foo 83 | # secret: 84 | # secretName: mysecret 85 | # optional: false 86 | 87 | # Additional volumeMounts on the output Deployment definition. 88 | volumeMounts: [] 89 | # - name: foo 90 | # mountPath: "/etc/foo" 91 | # readOnly: true 92 | 93 | nodeSelector: {} 94 | 95 | tolerations: [] 96 | 97 | affinity: {} 98 | 99 | serviceMonitor: 100 | enabled: true 101 | additionalLabels: {} 102 | namespace: "" 103 | namespaceSelector: {} 104 | scrapeInterval: 30s 105 | targetLabels: [] 106 | metricRelabelings: [] 107 | path: /metrics 108 | 109 | commitBoost: 110 | pbs: 111 | enable: true 112 | image: 113 | repository: ghcr.io/commit-boost/pbs 114 | pullPolicy: IfNotPresent 115 | tag: "v0.4.0" 116 | config: 117 | chain: "Holesky" 118 | metrics: 119 | server_port: 10000 120 | prometheus_config: "/dev/null" 121 | pbs: 122 | port: 18550 123 | relays: [] 124 | mux: [] 125 | -------------------------------------------------------------------------------- /provisioning/pbs.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | ARG BINARIES_PATH TARGETOS TARGETARCH 3 | COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost-pbs /usr/local/bin/commit-boost-pbs 4 | RUN apt-get update && apt-get install -y \ 5 | openssl \ 6 | ca-certificates \ 7 | libssl3 \ 8 | libssl-dev \ 9 | curl && \ 10 | # Cleanup 11 | apt-get clean autoclean && \ 12 | rm -rf /var/lib/apt/lists/* 13 | 14 | # Create a non-root user to run the application 15 | RUN groupadd -g 10001 commitboost && \ 16 | useradd -u 10001 -g commitboost -s /sbin/nologin commitboost 17 | USER commitboost 18 | 19 | ENTRYPOINT ["/usr/local/bin/commit-boost-pbs"] -------------------------------------------------------------------------------- /provisioning/pectra-config.yml: -------------------------------------------------------------------------------- 1 | participants: 2 | - el_type: geth 3 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 4 | el_extra_params: ["--miner.extradata=pawanRocks"] 5 | cl_type: lighthouse 6 | cl_image: ethpandaops/lighthouse:unstable-95cec45 7 | - el_type: geth 8 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 9 | el_extra_params: ["--miner.extradata=TekuFromLocal"] 10 | cl_type: teku 11 | cl_image: consensys/teku:develop 12 | - el_type: geth 13 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 14 | el_extra_params: ["--miner.extradata=lodestarFromLocal"] 15 | cl_type: lodestar 16 | cl_image: ethpandaops/lodestar:unstable-7982031 17 | - el_type: geth 18 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 19 | el_extra_params: ["--miner.extradata=prysmFromLocal"] 20 | cl_type: prysm 21 | cl_image: ethpandaops/prysm-beacon-chain:develop-910609a 22 | vc_image: ethpandaops/prysm-validator:develop-910609a 23 | - el_type: geth 24 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 25 | el_extra_params: ["--miner.extradata=nimbusFromLocal"] 26 | cl_type: nimbus 27 | cl_image: ethpandaops/nimbus-eth2:unstable-dec1cd3 28 | - el_type: geth 29 | el_image: ethpandaops/geth:prague-devnet-6-c070db6 30 | el_extra_params: ["--miner.extradata=grandineFromLocal"] 31 | cl_type: grandine 32 | cl_image: ethpandaops/grandine:devnet5-7ba51a4 33 | 34 | additional_services: 35 | - dora 36 | - tx_spammer 37 | - spamoor_blob 38 | mev_type: commit-boost 39 | 40 | mev_params: 41 | mev_relay_image: jtraglia/mev-boost-relay:electra 42 | mev_boost_image: commitboost_pbs_default # build this locally with scripts/build_local_images.sh 43 | mev_builder_cl_image: ethpandaops/lighthouse:unstable-a1b7d61 44 | mev_builder_image: ethpandaops/reth-rbuilder:devnet6-fdeb4d6 45 | 46 | network_params: 47 | electra_fork_epoch: 1 48 | min_validator_withdrawability_delay: 1 49 | shard_committee_period: 1 50 | churn_limit_quotient: 16 51 | genesis_delay: 120 52 | 53 | spamoor_blob_params: 54 | throughput: 10 55 | max_blobs: 2 56 | max_pending: 40 -------------------------------------------------------------------------------- /provisioning/protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script installs the latest version of protoc (Protocol Buffers Compiler) from the official GitHub repository. 4 | 5 | # Print a failure message to stderr and exit 6 | fail() { 7 | MESSAGE=$1 8 | RED='\033[0;31m' 9 | RESET='\033[;0m' 10 | >&2 echo -e "\n${RED}**ERROR**\n$MESSAGE${RESET}\n" 11 | exit 1 12 | } 13 | 14 | # Get the OS 15 | case "$(uname)" in 16 | Darwin*) 17 | PROTOC_OS="osx" ; 18 | TARGET_DIR="/opt/homebrew" ; # Emulating a homebrew install so we don't need elevated permissions 19 | # Darwin comes with unzip and curl already 20 | brew install jq ;; 21 | Linux*) 22 | PROTOC_OS="linux" ; 23 | TARGET_DIR="/usr" ; # Assumes the script is run as root or the user can do it manually 24 | if [ $(id -u) != "0" ]; then 25 | CMD_PREFIX="sudo " ; 26 | fi 27 | ${CMD_PREFIX}apt update && ${CMD_PREFIX}apt install -y unzip curl ca-certificates jq ;; 28 | *) 29 | echo "Unsupported OS: $(uname)" ; 30 | exit 1 ;; 31 | esac 32 | 33 | # Get the architecture 34 | case "$(uname -m)" in 35 | x86_64) PROTOC_ARCH="x86_64" ;; 36 | aarch64) PROTOC_ARCH="aarch_64" ;; 37 | arm64) PROTOC_ARCH="aarch_64" ;; 38 | *) echo "Unsupported architecture: [$(uname -m)]"; exit 1 ;; 39 | esac 40 | 41 | # Get the latest version 42 | PROTOC_RAW_VERSION=$(curl --retry 10 --retry-delay 2 --retry-all-errors -fsL "https://api.github.com/repos/protocolbuffers/protobuf/releases/latest" | jq -r .tag_name) || fail "Failed to get the latest version of protoc" 43 | if [ "$PROTOC_RAW_VERSION" = "null" ]; then 44 | fail "Failed to get the latest version of protoc" 45 | fi 46 | echo "Latest version of protoc: [$PROTOC_RAW_VERSION]" 47 | PROTOC_VERSION=$(echo $PROTOC_RAW_VERSION | sed 's/^v//') || fail "Failed to parse the latest version of protoc" 48 | if [ -z "$PROTOC_VERSION" ]; then 49 | fail "Latest version of protoc was empty" 50 | fi 51 | 52 | echo "Installing protoc: $PROTOC_VERSION-$PROTOC_OS-$PROTOC_ARCH" 53 | 54 | # Download and install protoc 55 | curl --retry 10 --retry-delay 2 --retry-all-errors -fsLo protoc.zip https://github.com/protocolbuffers/protobuf/releases/latest/download/protoc-$PROTOC_VERSION-$PROTOC_OS-$PROTOC_ARCH.zip || fail "Failed to download protoc" 56 | ${CMD_PREFIX}unzip -qo protoc.zip bin/protoc -d $TARGET_DIR || fail "Failed to unzip protoc" 57 | ${CMD_PREFIX}unzip -qo protoc.zip "include/google/*" -d $TARGET_DIR || fail "Failed to unzip protoc includes" 58 | ${CMD_PREFIX}chmod a+x $TARGET_DIR/bin/protoc || fail "Failed to set executable permissions for protoc" 59 | rm -rf protoc.zip || fail "Failed to remove protoc zip file" 60 | echo "protoc ${PROTOC_VERSION} installed successfully for ${PROTOC_OS} ${PROTOC_ARCH}" -------------------------------------------------------------------------------- /provisioning/signer.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | ARG BINARIES_PATH TARGETOS TARGETARCH 3 | COPY ${BINARIES_PATH}/${TARGETOS}_${TARGETARCH}/commit-boost-signer /usr/local/bin/commit-boost-signer 4 | RUN apt-get update && apt-get install -y \ 5 | openssl \ 6 | ca-certificates \ 7 | libssl3 \ 8 | libssl-dev \ 9 | curl && \ 10 | # Cleanup 11 | apt-get clean autoclean && \ 12 | rm -rf /var/lib/apt/lists/* 13 | 14 | # Create a non-root user to run the application 15 | RUN groupadd -g 10001 commitboost && \ 16 | useradd -u 10001 -g commitboost -s /sbin/nologin commitboost 17 | USER commitboost 18 | 19 | ENTRYPOINT ["/usr/local/bin/commit-boost-signer"] -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.83.0" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | group_imports = "StdExternalCrate" 3 | imports_granularity = "Crate" 4 | use_small_heuristics = "Max" 5 | comment_width = 80 6 | wrap_comments = true 7 | max_width = 100 8 | binop_separator = "Back" 9 | trailing_comma = "Vertical" 10 | trailing_semicolon = false 11 | use_field_init_shorthand = true 12 | overflow_delimited_expr = true 13 | format_code_in_doc_comments = true 14 | -------------------------------------------------------------------------------- /taplo.toml: -------------------------------------------------------------------------------- 1 | [formatting] 2 | column_width = 120 3 | compact_entries = false 4 | reorder_arrays = true 5 | reorder_inline_tables = true 6 | reorder_keys = true 7 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition.workspace = true 3 | name = "cb-tests" 4 | rust-version.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | alloy.workspace = true 9 | axum.workspace = true 10 | cb-common.workspace = true 11 | cb-pbs.workspace = true 12 | eyre.workspace = true 13 | reqwest.workspace = true 14 | serde_json.workspace = true 15 | tokio.workspace = true 16 | tracing.workspace = true 17 | tracing-subscriber.workspace = true 18 | tree_hash.workspace = true 19 | url.workspace = true 20 | -------------------------------------------------------------------------------- /tests/data/configs/pbs.happy.toml: -------------------------------------------------------------------------------- 1 | chain = "Holesky" 2 | 3 | [pbs] 4 | docker_image = "ghcr.io/commit-boost/pbs:latest" 5 | with_signer = false 6 | host = "127.0.0.1" 7 | port = 18550 8 | relay_check = true 9 | wait_all_registrations = true 10 | timeout_get_header_ms = 950 11 | timeout_get_payload_ms = 4000 12 | timeout_register_validator_ms = 3000 13 | skip_sigverify = false 14 | min_bid_eth = 0.5 15 | late_in_slot_time_ms = 2000 16 | extra_validation_enabled = false 17 | rpc_url = "https://ethereum-holesky-rpc.publicnode.com" 18 | 19 | [[relays]] 20 | id = "example-relay" 21 | url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz" 22 | headers = { X-MyCustomHeader = "MyCustomHeader" } 23 | enable_timing_games = false 24 | target_first_request_ms = 200 25 | frequency_get_header_ms = 300 26 | -------------------------------------------------------------------------------- /tests/data/keys.example.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0088e364a5396a81b50febbdc8784663fb9089b5e67cbdc173991a00c587673f", 3 | "0x16f3bec1b7f4f1b87c5e1930f944a6dda76ad211a89bc98e8c8e88b5069f8a04" 4 | ] -------------------------------------------------------------------------------- /tests/data/keystores/keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4/voting-keystore.json: -------------------------------------------------------------------------------- 1 | {"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"0ded1a0ed9d0d5aa9c41ac1a6be6d9943835f9ccbe1081869af74925611a4687"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1de458543b0532666e8f24e679f93ed6f168fd09de1da7c3f4f79b7fa2f2412"},"cipher":{"function":"aes-128-ctr","params":{"iv":"3ca34eb318e53a4c7e545571d8d0c7af"},"message":"acc6c222eea80974107b5a9bf824c8156edaad944f0d444a1aab4cc2118cecc5"}},"description":"0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","pubkey":"883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","path":"","uuid":"61c06c9c-b0bc-4022-9bf8-a2f250d4e751","version":4} -------------------------------------------------------------------------------- /tests/data/keystores/keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9/voting-keystore.json: -------------------------------------------------------------------------------- 1 | {"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"2154bba4d5999c6069442db5b499b2b27b6c2f54f36490e51163934dd4fb412e"},"message":""},"checksum":{"function":"sha256","params":{},"message":"1db4975098c97905f1dd9a9207cab0a9af7e16bebdab700ee08efb51e068017f"},"cipher":{"function":"aes-128-ctr","params":{"iv":"2265a3b57110b46c08295e53379165b5"},"message":"3bd312cc34cebfdd890c9704752191ed93ecd562bb62d2d8ceb4ff945b58b790"}},"description":"0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","pubkey":"b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","path":"","uuid":"a8457299-739d-42fb-a0f6-961020f22b8e","version":4} -------------------------------------------------------------------------------- /tests/data/keystores/lodestar-secrets/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4: -------------------------------------------------------------------------------- 1 | 2MtI__9JSKFcN2Syqpdy5MmM8RXZbM26Pel7G1HCuIg= -------------------------------------------------------------------------------- /tests/data/keystores/lodestar-secrets/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9: -------------------------------------------------------------------------------- 1 | BWBoV1UZpkO4cUA-t8T9aViJ0sBfilR7qJFHgU4tBSc= -------------------------------------------------------------------------------- /tests/data/keystores/prysm/direct/accounts/all-accounts.keystore.json: -------------------------------------------------------------------------------- 1 | { 2 | "crypto": { 3 | "kdf": { 4 | "function": "pbkdf2", 5 | "params": { 6 | "dklen": 32, 7 | "c": 262144, 8 | "prf": "hmac-sha256", 9 | "salt": "0e538586adf998caa12c7a42772cb559ccb49e69c71159d924f0ade3e4a86240" 10 | }, 11 | "message": "" 12 | }, 13 | "checksum": { 14 | "function": "sha256", 15 | "params": {}, 16 | "message": "da07b64a482f95c322b6c506dea20f53007391bc7c60255e480fef5994d6d826" 17 | }, 18 | "cipher": { 19 | "function": "aes-128-ctr", 20 | "params": { 21 | "iv": "7180c42635fb41584db7b9f14264b504" 22 | }, 23 | "message": "11d4016d0893228d09e14d9d354a6d8a5c280eefbb8277c36b281a95dfe9a5c506ae8538f6a25799d1c16c32319bb126ceff4c09a3de5ec355ed8e1c5662e1942e2b32a28977c59ed9a7e3d8756e69b3862dd03f38391ae110f48b0b3520c715633afb7ed62fc6ec9b41b4318e629da6b44ed216b4de02b05b2b0224c083f5ec932980a8d13672562a73bead88b61760753bff91a484dfdc50442686ee054894a61b072c52c934d0763c9502f9988b10f1a50176a2d2a9ba2186d620faa9f97be4762be86da03fa2209c9c7c1974158539a7835b8426225ff6ff173790c55a304282b9a8991ddc5cb9c6e7e7e1cd7ec75e02deeb9b82e0dcfed874fe58fb7bf8a027f9bc127e1d9472afc27ac34575dcb67cc71522ca0c915ba023224a" 24 | } 25 | }, 26 | "path": "", 27 | "uuid": "7d7e3a49-c4ca-4d0a-a0e6-cb199dd72a85", 28 | "version": 4 29 | } -------------------------------------------------------------------------------- /tests/data/keystores/prysm/empty_pass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Commit-Boost/commit-boost-client/ea48da6e53e06b1e61593c2d3316f973b7f98da6/tests/data/keystores/prysm/empty_pass -------------------------------------------------------------------------------- /tests/data/keystores/prysm/keymanageropts.json: -------------------------------------------------------------------------------- 1 | {"direct_eip_version": "EIP-2335"} -------------------------------------------------------------------------------- /tests/data/keystores/pubkeys.json: -------------------------------------------------------------------------------- 1 | ["0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4"] -------------------------------------------------------------------------------- /tests/data/keystores/secrets/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4: -------------------------------------------------------------------------------- 1 | 2MtI__9JSKFcN2Syqpdy5MmM8RXZbM26Pel7G1HCuIg= -------------------------------------------------------------------------------- /tests/data/keystores/secrets/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9: -------------------------------------------------------------------------------- 1 | BWBoV1UZpkO4cUA-t8T9aViJ0sBfilR7qJFHgU4tBSc= -------------------------------------------------------------------------------- /tests/data/keystores/teku-keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4.json: -------------------------------------------------------------------------------- 1 | {"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"0ded1a0ed9d0d5aa9c41ac1a6be6d9943835f9ccbe1081869af74925611a4687"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1de458543b0532666e8f24e679f93ed6f168fd09de1da7c3f4f79b7fa2f2412"},"cipher":{"function":"aes-128-ctr","params":{"iv":"3ca34eb318e53a4c7e545571d8d0c7af"},"message":"acc6c222eea80974107b5a9bf824c8156edaad944f0d444a1aab4cc2118cecc5"}},"description":"0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","pubkey":"883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","path":"","uuid":"61c06c9c-b0bc-4022-9bf8-a2f250d4e751","version":4} -------------------------------------------------------------------------------- /tests/data/keystores/teku-keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9.json: -------------------------------------------------------------------------------- 1 | {"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"2154bba4d5999c6069442db5b499b2b27b6c2f54f36490e51163934dd4fb412e"},"message":""},"checksum":{"function":"sha256","params":{},"message":"1db4975098c97905f1dd9a9207cab0a9af7e16bebdab700ee08efb51e068017f"},"cipher":{"function":"aes-128-ctr","params":{"iv":"2265a3b57110b46c08295e53379165b5"},"message":"3bd312cc34cebfdd890c9704752191ed93ecd562bb62d2d8ceb4ff945b58b790"}},"description":"0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","pubkey":"b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","path":"","uuid":"a8457299-739d-42fb-a0f6-961020f22b8e","version":4} -------------------------------------------------------------------------------- /tests/data/keystores/teku-secrets/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4.txt: -------------------------------------------------------------------------------- 1 | 2MtI__9JSKFcN2Syqpdy5MmM8RXZbM26Pel7G1HCuIg= -------------------------------------------------------------------------------- /tests/data/keystores/teku-secrets/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9.txt: -------------------------------------------------------------------------------- 1 | BWBoV1UZpkO4cUA-t8T9aViJ0sBfilR7qJFHgU4tBSc= -------------------------------------------------------------------------------- /tests/data/mux_keys.example.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0x8160998addda06f2956e5d1945461f33dbc140486e972b96f341ebf2bdb553a0e3feb127451f5332dd9e33469d37ca67", 3 | "0x87b5dc7f78b68a7b5e7f2e8b9c2115f968332cbf6fc2caaaaa2c9dc219a58206b72c924805f2278c58b55790a2c3bf17", 4 | "0x89e2f50fe5cd07ed2ff0a01340b2f717aa65cced6d89a79fdecc1e924be5f4bbe75c11598bb9a53d307bb39b8223bc52" 5 | ] -------------------------------------------------------------------------------- /tests/data/proxy/keys/0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118/TEST_MODULE/bls/0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba.json: -------------------------------------------------------------------------------- 1 | {"crypto":{"kdf":{"function":"scrypt","params":{"dklen":32,"n":262144,"r":8,"p":1,"salt":"c84961e82805391c0f761cf342c1e6293dab474d388179f4fdea8386310d3920"},"message":""},"checksum":{"function":"sha256","params":{},"message":"4a6ed334d558abeb81ea04893eeed79214eaec476d6225bacccbc7ffbde95843"},"cipher":{"function":"aes-128-ctr","params":{"iv":"bff99639dd8ad6e3339177bad87dcac4"},"message":"e9bca9829d688baa09e65ddecadedd1cb6b49c024a9fff98630817cf835aa9bb"}},"uuid":"38fcc27a-da59-4604-8858-cf3d58d06acc","path":null,"pubkey":"a77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba","version":4,"description":"0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba","name":null} -------------------------------------------------------------------------------- /tests/data/proxy/keys/0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118/TEST_MODULE/bls/0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba.sig: -------------------------------------------------------------------------------- 1 | 0xb2e44e777cc68b50b9d19cbded2b2b6a0a5c428e3c341b5ade22f90e67679116511855b94e26ae930d1350628933994713f4fd48d1d70715a99d875a564c88e229aa9bb2d89e9f60b725c97300659bd0fc7bc1e2e599f12625b81ef63890f857 -------------------------------------------------------------------------------- /tests/data/proxy/secrets/0xac5e059177afc33263e95d0be0690138b9a1d79a6e19018086a0362e0c30a50bf9e05a08cb44785724d0b2718c5c7118/TEST_MODULE/bls/0xa77084280678d9f1efe4ef47a3d62af27872ce82db19a35ee012c4fd5478e6b1123b8869032ba18b2383e8873294f0ba: -------------------------------------------------------------------------------- 1 | 4ecdc703bdc0b4957876643fbba74f20f5cf7e4435b852fcd9b2d0c2b977a854 -------------------------------------------------------------------------------- /tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod mock_relay; 2 | pub mod mock_validator; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /tests/src/mock_validator.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | primitives::B256, 3 | rpc::types::beacon::{relay::ValidatorRegistration, BlsPublicKey}, 4 | }; 5 | use cb_common::pbs::{RelayClient, SignedBlindedBeaconBlock}; 6 | use reqwest::Response; 7 | 8 | use crate::utils::generate_mock_relay; 9 | 10 | pub struct MockValidator { 11 | pub comm_boost: RelayClient, 12 | } 13 | 14 | impl MockValidator { 15 | pub fn new(port: u16) -> eyre::Result { 16 | Ok(Self { comm_boost: generate_mock_relay(port, BlsPublicKey::default())? }) 17 | } 18 | 19 | pub async fn do_get_header(&self, pubkey: Option) -> eyre::Result { 20 | let url = self.comm_boost.get_header_url(0, B256::ZERO, pubkey.unwrap_or_default())?; 21 | Ok(self.comm_boost.client.get(url).send().await?) 22 | } 23 | 24 | pub async fn do_get_status(&self) -> eyre::Result { 25 | let url = self.comm_boost.get_status_url()?; 26 | Ok(self.comm_boost.client.get(url).send().await?) 27 | } 28 | 29 | pub async fn do_register_validator(&self) -> eyre::Result { 30 | self.do_register_custom_validators(vec![]).await 31 | } 32 | 33 | pub async fn do_register_custom_validators( 34 | &self, 35 | registrations: Vec, 36 | ) -> eyre::Result { 37 | let url = self.comm_boost.register_validator_url().unwrap(); 38 | 39 | Ok(self.comm_boost.client.post(url).json(®istrations).send().await?) 40 | } 41 | 42 | pub async fn do_submit_block( 43 | &self, 44 | signed_blinded_block: Option, 45 | ) -> eyre::Result { 46 | let url = self.comm_boost.submit_block_url().unwrap(); 47 | 48 | Ok(self 49 | .comm_boost 50 | .client 51 | .post(url) 52 | .json(&signed_blinded_block.unwrap_or_default()) 53 | .send() 54 | .await?) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{Ipv4Addr, SocketAddr}, 3 | sync::{Arc, Once}, 4 | }; 5 | 6 | use alloy::{primitives::U256, rpc::types::beacon::BlsPublicKey}; 7 | use cb_common::{ 8 | config::{PbsConfig, PbsModuleConfig, RelayConfig}, 9 | pbs::{RelayClient, RelayEntry}, 10 | types::Chain, 11 | }; 12 | use eyre::Result; 13 | 14 | pub fn get_local_address(port: u16) -> String { 15 | format!("http://0.0.0.0:{port}") 16 | } 17 | 18 | static SYNC_SETUP: Once = Once::new(); 19 | pub fn setup_test_env() { 20 | SYNC_SETUP.call_once(|| { 21 | tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init(); 22 | }); 23 | } 24 | 25 | pub fn generate_mock_relay(port: u16, pubkey: BlsPublicKey) -> Result { 26 | let entry = 27 | RelayEntry { id: format!("mock_{port}"), pubkey, url: get_local_address(port).parse()? }; 28 | let config = RelayConfig { 29 | entry, 30 | id: None, 31 | headers: None, 32 | get_params: None, 33 | enable_timing_games: false, 34 | target_first_request_ms: None, 35 | frequency_get_header_ms: None, 36 | validator_registration_batch_size: None, 37 | }; 38 | RelayClient::new(config) 39 | } 40 | 41 | pub fn generate_mock_relay_with_batch_size( 42 | port: u16, 43 | pubkey: BlsPublicKey, 44 | batch_size: usize, 45 | ) -> Result { 46 | let entry = 47 | RelayEntry { id: format!("mock_{port}"), pubkey, url: get_local_address(port).parse()? }; 48 | let config = RelayConfig { 49 | entry, 50 | id: None, 51 | headers: None, 52 | get_params: None, 53 | enable_timing_games: false, 54 | target_first_request_ms: None, 55 | frequency_get_header_ms: None, 56 | validator_registration_batch_size: Some(batch_size), 57 | }; 58 | RelayClient::new(config) 59 | } 60 | 61 | pub fn get_pbs_static_config(port: u16) -> PbsConfig { 62 | PbsConfig { 63 | host: Ipv4Addr::UNSPECIFIED, 64 | port, 65 | wait_all_registrations: true, 66 | relay_check: true, 67 | timeout_get_header_ms: u64::MAX, 68 | timeout_get_payload_ms: u64::MAX, 69 | timeout_register_validator_ms: u64::MAX, 70 | skip_sigverify: false, 71 | min_bid_wei: U256::ZERO, 72 | late_in_slot_time_ms: u64::MAX, 73 | extra_validation_enabled: false, 74 | rpc_url: None, 75 | } 76 | } 77 | 78 | pub fn to_pbs_config( 79 | chain: Chain, 80 | pbs_config: PbsConfig, 81 | relays: Vec, 82 | ) -> PbsModuleConfig { 83 | PbsModuleConfig { 84 | chain, 85 | endpoint: SocketAddr::new(pbs_config.host.into(), pbs_config.port), 86 | pbs_config: Arc::new(pbs_config), 87 | signer_client: None, 88 | event_publisher: None, 89 | all_relays: relays.clone(), 90 | relays, 91 | muxes: None, 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/tests/pbs_get_status.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use cb_common::{ 4 | signer::{random_secret, BlsPublicKey}, 5 | types::Chain, 6 | utils::blst_pubkey_to_alloy, 7 | }; 8 | use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; 9 | use cb_tests::{ 10 | mock_relay::{start_mock_relay_service, MockRelayState}, 11 | mock_validator::MockValidator, 12 | utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, 13 | }; 14 | use eyre::Result; 15 | use reqwest::StatusCode; 16 | use tracing::info; 17 | 18 | #[tokio::test] 19 | async fn test_get_status() -> Result<()> { 20 | setup_test_env(); 21 | let signer = random_secret(); 22 | let pubkey: BlsPublicKey = blst_pubkey_to_alloy(&signer.sk_to_pk()).into(); 23 | 24 | let chain = Chain::Holesky; 25 | let pbs_port = 3500; 26 | let relay_0_port = pbs_port + 1; 27 | let relay_1_port = pbs_port + 2; 28 | 29 | let relays = vec![ 30 | generate_mock_relay(relay_0_port, pubkey)?, 31 | generate_mock_relay(relay_1_port, pubkey)?, 32 | ]; 33 | let mock_state = Arc::new(MockRelayState::new(chain, signer)); 34 | tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_0_port)); 35 | tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_1_port)); 36 | 37 | let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays.clone()); 38 | let state = PbsState::new(config); 39 | tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); 40 | 41 | // leave some time to start servers 42 | tokio::time::sleep(Duration::from_millis(100)).await; 43 | 44 | let mock_validator = MockValidator::new(pbs_port)?; 45 | info!("Sending get status"); 46 | let res = mock_validator.do_get_status().await.expect("failed to get status"); 47 | assert_eq!(res.status(), StatusCode::OK); 48 | 49 | // Expect two statuses since two relays in config 50 | assert_eq!(mock_state.received_get_status(), 2); 51 | Ok(()) 52 | } 53 | 54 | #[tokio::test] 55 | async fn test_get_status_returns_502_if_relay_down() -> Result<()> { 56 | setup_test_env(); 57 | let signer = random_secret(); 58 | let pubkey: BlsPublicKey = blst_pubkey_to_alloy(&signer.sk_to_pk()).into(); 59 | 60 | let chain = Chain::Holesky; 61 | let pbs_port = 3600; 62 | let relay_port = pbs_port + 1; 63 | 64 | let relays = vec![generate_mock_relay(relay_port, pubkey)?]; 65 | let mock_state = Arc::new(MockRelayState::new(chain, signer)); 66 | 67 | // Don't start the relay 68 | // tokio::spawn(start_mock_relay_service(mock_state.clone(), relay_port)); 69 | 70 | let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays.clone()); 71 | let state = PbsState::new(config); 72 | tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); 73 | 74 | // leave some time to start servers 75 | tokio::time::sleep(Duration::from_millis(100)).await; 76 | 77 | let mock_validator = MockValidator::new(pbs_port)?; 78 | info!("Sending get status"); 79 | let res = mock_validator.do_get_status().await.expect("failed to get status"); 80 | assert_eq!(res.status(), StatusCode::BAD_GATEWAY); // 502 error 81 | 82 | // Expect no statuses since relay is down 83 | assert_eq!(mock_state.received_get_status(), 0); 84 | Ok(()) 85 | } 86 | -------------------------------------------------------------------------------- /tests/tests/pbs_mux.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc, time::Duration}; 2 | 3 | use cb_common::{ 4 | config::RuntimeMuxConfig, 5 | signer::{random_secret, BlsPublicKey}, 6 | types::Chain, 7 | utils::blst_pubkey_to_alloy, 8 | }; 9 | use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; 10 | use cb_tests::{ 11 | mock_relay::{start_mock_relay_service, MockRelayState}, 12 | mock_validator::MockValidator, 13 | utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, 14 | }; 15 | use eyre::Result; 16 | use reqwest::StatusCode; 17 | use tracing::info; 18 | 19 | #[tokio::test] 20 | async fn test_mux() -> Result<()> { 21 | setup_test_env(); 22 | let signer = random_secret(); 23 | let pubkey: BlsPublicKey = blst_pubkey_to_alloy(&signer.sk_to_pk()).into(); 24 | 25 | let chain = Chain::Holesky; 26 | let pbs_port = 3700; 27 | 28 | let mux_relay_1 = generate_mock_relay(pbs_port + 1, pubkey)?; 29 | let mux_relay_2 = generate_mock_relay(pbs_port + 2, pubkey)?; 30 | let default_relay = generate_mock_relay(pbs_port + 3, pubkey)?; 31 | 32 | // Run 3 mock relays 33 | let mock_state = Arc::new(MockRelayState::new(chain, signer)); 34 | tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); 35 | tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 2)); 36 | tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 3)); 37 | 38 | // Register all relays in PBS config 39 | let relays = vec![default_relay.clone()]; 40 | let mut config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); 41 | config.all_relays = vec![mux_relay_1.clone(), mux_relay_2.clone(), default_relay.clone()]; 42 | 43 | // Configure mux for two relays 44 | let mux = RuntimeMuxConfig { 45 | id: String::from("test"), 46 | config: config.pbs_config.clone(), 47 | relays: vec![mux_relay_1, mux_relay_2], 48 | }; 49 | 50 | // Bind mux to a specific validator key 51 | let validator_pubkey = blst_pubkey_to_alloy(&random_secret().sk_to_pk()); 52 | config.muxes = Some(HashMap::from([(validator_pubkey, mux)])); 53 | 54 | // Run PBS service 55 | let state = PbsState::new(config); 56 | tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); 57 | 58 | // leave some time to start servers 59 | tokio::time::sleep(Duration::from_millis(100)).await; 60 | 61 | // Send default request without specifying a validator key 62 | let mock_validator = MockValidator::new(pbs_port)?; 63 | info!("Sending get header with default"); 64 | assert_eq!(mock_validator.do_get_header(None).await?.status(), StatusCode::OK); 65 | assert_eq!(mock_state.received_get_header(), 1); // only default relay was used 66 | 67 | // Send request specifying a validator key to use mux 68 | info!("Sending get header with mux"); 69 | assert_eq!( 70 | mock_validator.do_get_header(Some(validator_pubkey)).await?.status(), 71 | StatusCode::OK 72 | ); 73 | assert_eq!(mock_state.received_get_header(), 3); // two mux relays were used 74 | 75 | // Status requests should go to all relays 76 | info!("Sending get status"); 77 | assert_eq!(mock_validator.do_get_status().await?.status(), StatusCode::OK); 78 | assert_eq!(mock_state.received_get_status(), 3); // default + 2 mux relays were used 79 | 80 | // Register requests should go to all relays 81 | info!("Sending register validator"); 82 | assert_eq!(mock_validator.do_register_validator().await?.status(), StatusCode::OK); 83 | assert_eq!(mock_state.received_register_validator(), 3); // default + 2 mux relays were used 84 | 85 | // Submit block requests should go to all relays 86 | info!("Sending submit block"); 87 | assert_eq!(mock_validator.do_submit_block(None).await?.status(), StatusCode::OK); 88 | assert_eq!(mock_state.received_submit_block(), 3); // default + 2 mux relays were used 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /tests/tests/pbs_post_blinded_blocks.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use cb_common::{ 4 | pbs::{SignedBlindedBeaconBlock, SubmitBlindedBlockResponse}, 5 | signer::{random_secret, BlsPublicKey}, 6 | types::Chain, 7 | utils::blst_pubkey_to_alloy, 8 | }; 9 | use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; 10 | use cb_tests::{ 11 | mock_relay::{start_mock_relay_service, MockRelayState}, 12 | mock_validator::MockValidator, 13 | utils::{generate_mock_relay, get_pbs_static_config, setup_test_env, to_pbs_config}, 14 | }; 15 | use eyre::Result; 16 | use reqwest::StatusCode; 17 | use tracing::info; 18 | 19 | #[tokio::test] 20 | async fn test_submit_block() -> Result<()> { 21 | setup_test_env(); 22 | let signer = random_secret(); 23 | let pubkey: BlsPublicKey = blst_pubkey_to_alloy(&signer.sk_to_pk()).into(); 24 | 25 | let chain = Chain::Holesky; 26 | let pbs_port = 3800; 27 | 28 | // Run a mock relay 29 | let relays = vec![generate_mock_relay(pbs_port + 1, pubkey)?]; 30 | let mock_state = Arc::new(MockRelayState::new(chain, signer)); 31 | tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); 32 | 33 | // Run the PBS service 34 | let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); 35 | let state = PbsState::new(config); 36 | tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); 37 | 38 | // leave some time to start servers 39 | tokio::time::sleep(Duration::from_millis(100)).await; 40 | 41 | let mock_validator = MockValidator::new(pbs_port)?; 42 | info!("Sending submit block"); 43 | let res = mock_validator.do_submit_block(Some(SignedBlindedBeaconBlock::default())).await?; 44 | 45 | assert_eq!(res.status(), StatusCode::OK); 46 | assert_eq!(mock_state.received_submit_block(), 1); 47 | 48 | let response_body = serde_json::from_slice::(&res.bytes().await?)?; 49 | assert_eq!(response_body.block_hash(), SubmitBlindedBlockResponse::default().block_hash()); 50 | Ok(()) 51 | } 52 | 53 | #[tokio::test] 54 | async fn test_submit_block_too_large() -> Result<()> { 55 | setup_test_env(); 56 | let signer = random_secret(); 57 | let pubkey: BlsPublicKey = blst_pubkey_to_alloy(&signer.sk_to_pk()).into(); 58 | 59 | let chain = Chain::Holesky; 60 | let pbs_port = 3900; 61 | 62 | let relays = vec![generate_mock_relay(pbs_port + 1, pubkey)?]; 63 | let mock_state = Arc::new(MockRelayState::new(chain, signer).with_large_body()); 64 | tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1)); 65 | 66 | let config = to_pbs_config(chain, get_pbs_static_config(pbs_port), relays); 67 | let state = PbsState::new(config); 68 | tokio::spawn(PbsService::run::<(), DefaultBuilderApi>(state)); 69 | 70 | // leave some time to start servers 71 | tokio::time::sleep(Duration::from_millis(100)).await; 72 | 73 | let mock_validator = MockValidator::new(pbs_port)?; 74 | info!("Sending submit block"); 75 | let res = mock_validator.do_submit_block(None).await; 76 | 77 | // response size exceeds max size: max: 20971520 78 | assert_eq!(res.unwrap().status(), StatusCode::BAD_GATEWAY); 79 | assert_eq!(mock_state.received_submit_block(), 1); 80 | Ok(()) 81 | } 82 | --------------------------------------------------------------------------------