diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..63ed8b9e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI fmt clippy and test + +on: + pull_request: + branches: ["**"] + +jobs: + rustfmt: + name: Format + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install nightly toolchain with rustfmt available + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rustfmt + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + continue-on-error: true # WARNING: only for this example, remove it! + with: + command: fmt + args: --all -- --check + + clippy: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain with clippy available + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all -- -D warnings + tests: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Run tests + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --verbose \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..63136a26 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,67 @@ +name: Deploy Images to GHCR + +on: + workflow_run: + workflows: [ "CI fmt clippy and test" ] + types: + - completed + push: + branches: + - main # Trigger only on push to main branch + tags: # Trigger on new tags + - 'v*' + +jobs: + push-store-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@main + + - name: 'Set up Docker Buildx' + uses: docker/setup-buildx-action@v1 + + - name: 'Login to GitHub Container Registry' + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 'Extract short hash' + id: vars + run: echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV # This sets the SHORT_SHA environment variable + + - name: 'Set Docker tag' + id: docker_tag + run: | + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + TAG_NAME=${{ github.ref }} + TAG_NAME=${TAG_NAME##*/} + echo "DOCKER_TAG=${TAG_NAME}" >> $GITHUB_ENV + else + echo "DOCKER_TAG=${{ env.SHORT_SHA }}" >> $GITHUB_ENV + fi + + - name: Build and push pbs + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ghcr.io/commit-boost/commit-boost-client/pbs:${{ env.DOCKER_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max + file: docker/pbs.Dockerfile + + - name: Build and push signer + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ghcr.io/commit-boost/commit-boost-client/signer:${{ env.DOCKER_TAG }} + cache-from: type=gha + cache-to: type=gha,mode=max + file: docker/signer.Dockerfile \ No newline at end of file diff --git a/.gitignore b/.gitignore index b2d30b90..1dfbe15c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ Cargo.lock *.env *.docker-compose.yml targets.json - +.idea/ diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index bc3ffdf4..20004134 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -87,8 +87,9 @@ impl Args { // Command::Start2 { config: config_path } => { // let config = CommitBoostConfig::from_file(&config_path); -// let signer_config = config.signer.expect("missing signer config with modules"); -// let metrics_config = config.metrics.clone().expect("missing metrics config"); +// let signer_config = config.signer.expect("missing signer config with +// modules"); let metrics_config = config.metrics.clone().expect("missing +// metrics config"); // // TODO: Actually generate this token // let pbs_jwt = "MY_PBS_TOKEN"; @@ -102,8 +103,8 @@ impl Args { // let jwts: HashMap = // iter::once((DEFAULT_PBS_JWT_KEY.into(), pbs_jwt.into())) // .chain(modules.iter().map(|module| -// // TODO: Generate token instead of hard-coding it. Think about persisting it -// across the project. ( +// // TODO: Generate token instead of hard-coding it. Think +// about persisting it across the project. ( // module.id.clone(), // MODULE_JWT.into() // // format!("JWT_{}", module.id) @@ -123,14 +124,14 @@ impl Args { // image: Some(module.docker_image.clone()), // host_config: Some(bollard::secret::HostConfig { // binds: { -// let full_config_path = std::fs::canonicalize(&config_path) -// .unwrap() +// let full_config_path = +// std::fs::canonicalize(&config_path) .unwrap() // .to_string_lossy() // .to_string(); -// Some(vec![format!("{}:{}", full_config_path, "/config.toml")]) -// }, -// network_mode: Some(String::from("host")), // Use the host network -// ..Default::default() +// Some(vec![format!("{}:{}", full_config_path, +// "/config.toml")]) }, +// network_mode: Some(String::from("host")), // Use the host +// network ..Default::default() // }), // env: { // let metrics_server_url = metrics_config.address; @@ -138,9 +139,9 @@ impl Args { // Some(vec![ // format!("{}={}", MODULE_ID_ENV, module.id), // format!("{}={}", CB_CONFIG_ENV, "/config.toml"), -// format!("{}={}", MODULE_JWT_ENV, jwts.get(&module.id).unwrap()), -// format!("{}={}", METRICS_SERVER_ENV, metrics_server_url), -// ]) +// format!("{}={}", MODULE_JWT_ENV, +// jwts.get(&module.id).unwrap()), format!("{}={}", +// METRICS_SERVER_ENV, metrics_server_url), ]) // }, // ..Default::default() // }; @@ -160,9 +161,9 @@ impl Args { // DockerMetricsCollector::new( // vec![cid], // metrics_config.address.clone(), -// // FIXME: The entire DockerMetricsCollector currently works with a -// // single JWT; need to migrate to per-module JWT. -// MODULE_JWT.to_string(), +// // FIXME: The entire DockerMetricsCollector currently +// works with a // single JWT; need to migrate to per-module +// JWT. MODULE_JWT.to_string(), // ) // .await // }); diff --git a/crates/common/src/commit/request.rs b/crates/common/src/commit/request.rs index 3cd98191..d29eef6c 100644 --- a/crates/common/src/commit/request.rs +++ b/crates/common/src/commit/request.rs @@ -7,7 +7,8 @@ use tree_hash_derive::TreeHash; use crate::{signature::verify_signed_builder_message, types::Chain}; -// TODO: might need to adapt the SignedProxyDelegation so that it goes through web3 signer +// TODO: might need to adapt the SignedProxyDelegation so that it goes through +// web3 signer #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, TreeHash)] pub struct ProxyDelegation { pub delegator: BlsPublicKey, diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs index dd8aad42..9ebde10b 100644 --- a/crates/common/src/config.rs +++ b/crates/common/src/config.rs @@ -44,20 +44,20 @@ pub struct CommitBoostConfig { } fn load_from_file(path: &str) -> T { - let config_file = - std::fs::read_to_string(path).expect(&format!("Unable to find config file: '{}'", path)); + let config_file = std::fs::read_to_string(path) + .unwrap_or_else(|_| panic!("Unable to find config file: '{}'", path)); toml::from_str(&config_file).unwrap() } fn load_file_from_env(env: &str) -> T { - let path = std::env::var(env).expect(&format!("{env} is not set")); + let path = std::env::var(env).unwrap_or_else(|_| panic!("{env} is not set")); load_from_file(&path) } /// Loads a map of module id -> jwt token from a json env fn load_jwts() -> HashMap { - let jwts = std::env::var(JWTS_ENV).expect(&format!("{JWTS_ENV} is not set")); - serde_json::from_str(&jwts).expect(&format!("Failed to parse jwts: {jwts}")) + let jwts = std::env::var(JWTS_ENV).unwrap_or_else(|_| panic!("{JWTS_ENV} is not set")); + serde_json::from_str(&jwts).unwrap_or_else(|_| panic!("Failed to parse jwts: {jwts}")) } impl CommitBoostConfig { @@ -299,5 +299,5 @@ pub fn load_module_config() -> eyre::Result String { - std::env::var(env).expect(&format!("{env} is not set")) + std::env::var(env).unwrap_or_else(|_| panic!("{env} is not set")) } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index b13075c1..b67562ef 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -10,5 +10,4 @@ pub mod signer; pub mod types; pub mod utils; - -pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); \ No newline at end of file +pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); diff --git a/crates/common/src/loader.rs b/crates/common/src/loader.rs index 1dda78c2..3f326325 100644 --- a/crates/common/src/loader.rs +++ b/crates/common/src/loader.rs @@ -35,16 +35,16 @@ impl SignerLoader { match self { SignerLoader::File { .. } => { let path = load_env_var_infallible(SIGNER_KEYS_ENV); - let file = - std::fs::read_to_string(path).expect(&format!("Unable to find keys file")); + let file = std::fs::read_to_string(path) + .unwrap_or_else(|_| panic!("Unable to find keys file")); let keys: Vec = serde_json::from_str(&file).unwrap(); keys.into_iter().map(|k| Signer::new_from_bytes(&k.secret_key)).collect() } SignerLoader::ValidatorsDir { .. } => { - // TODO: hacky way to load for now, we should support reading the definitions.yml - // file + // TODO: hacky way to load for now, we should support reading the + // definitions.yml file let keys_path = load_env_var_infallible(SIGNER_DIR_KEYS_ENV); let secrets_path = load_env_var_infallible(SIGNER_DIR_SECRETS_ENV); load_secrets_and_keys(keys_path, secrets_path).expect("failed to load signers") diff --git a/crates/pbs/src/mev_boost/get_header.rs b/crates/pbs/src/mev_boost/get_header.rs index 8e711f85..b6bd23f5 100644 --- a/crates/pbs/src/mev_boost/get_header.rs +++ b/crates/pbs/src/mev_boost/get_header.rs @@ -121,14 +121,16 @@ async fn send_timed_get_header( loop { handles.push(tokio::spawn( send_one_get_header( - url.clone(), params, relay.clone(), chain, - headers.clone(), - timeout_left_ms, pbs_config.skip_sigverify, pbs_config.min_bid_wei, + RequestConfig { + timeout_ms: timeout_left_ms, + url: url.clone(), + headers: headers.clone(), + }, ) .in_current_span(), )); @@ -179,41 +181,43 @@ async fn send_timed_get_header( // if no timing games or no repeated send, just send one request send_one_get_header( - url, params, relay, chain, - headers, - timeout_left_ms, pbs_config.skip_sigverify, pbs_config.min_bid_wei, + RequestConfig { timeout_ms: timeout_left_ms, url, headers }, ) .await .map(|(_, maybe_header)| maybe_header) } -async fn send_one_get_header( +struct RequestConfig { url: String, + timeout_ms: u64, + headers: HeaderMap, +} + +async fn send_one_get_header( params: GetHeaderParams, relay: RelayClient, chain: Chain, - mut headers: HeaderMap, - timeout_ms: u64, skip_sigverify: bool, min_bid_wei: U256, + mut req_config: RequestConfig, ) -> Result<(u64, Option), PbsError> { // the timestamp in the header is the consensus block time which is fixed, // use the beginning of the request as proxy to make sure we use only the // last one received let start_request_time = utcnow_ms(); - headers.insert(HEADER_START_TIME_UNIX_MS, HeaderValue::from(start_request_time)); + req_config.headers.insert(HEADER_START_TIME_UNIX_MS, HeaderValue::from(start_request_time)); let start_request = Instant::now(); let res = match relay .client - .get(url) - .timeout(Duration::from_millis(timeout_ms)) - .headers(headers) + .get(req_config.url) + .timeout(Duration::from_millis(req_config.timeout_ms)) + .headers(req_config.headers) .send() .await { diff --git a/crates/pbs/src/types/beacon_block.rs b/crates/pbs/src/types/beacon_block.rs index c6bd1d4f..8f352107 100644 --- a/crates/pbs/src/types/beacon_block.rs +++ b/crates/pbs/src/types/beacon_block.rs @@ -1,5 +1,4 @@ -use alloy::primitives::B256; -use alloy::rpc::types::beacon::BlsSignature; +use alloy::{primitives::B256, rpc::types::beacon::BlsSignature}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -52,8 +51,8 @@ mod tests { use super::{SignedBlindedBeaconBlock, SubmitBlindedBlockResponse}; #[test] - // this is from the builder api spec, but with sync_committee_bits fixed to deserialize - // correctly + // this is from the builder api spec, but with sync_committee_bits fixed to + // deserialize correctly fn test_signed_blinded_block() { let data = r#"{ "message": { @@ -567,7 +566,8 @@ mod tests { } #[test] - // this is from the builder api spec, but with blobs fixed to deserialize correctly + // this is from the builder api spec, but with blobs fixed to deserialize + // correctly fn test_submit_blinded_block_response() { let blob = alloy::primitives::hex::encode([1; 131072]); let data = format!( diff --git a/crates/pbs/src/types/blinded_block_body.rs b/crates/pbs/src/types/blinded_block_body.rs index aaa5ec14..353ed548 100644 --- a/crates/pbs/src/types/blinded_block_body.rs +++ b/crates/pbs/src/types/blinded_block_body.rs @@ -1,7 +1,7 @@ -use std::usize; - -use alloy::primitives::{Address, B256}; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::{ + primitives::{Address, B256}, + rpc::types::beacon::{BlsPublicKey, BlsSignature}, +}; use cb_common::utils::as_str; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; diff --git a/crates/pbs/src/types/execution_payload.rs b/crates/pbs/src/types/execution_payload.rs index 4734bbab..92626ece 100644 --- a/crates/pbs/src/types/execution_payload.rs +++ b/crates/pbs/src/types/execution_payload.rs @@ -1,5 +1,3 @@ -use std::usize; - use alloy::primitives::{Address, B256, U256}; use cb_common::utils::as_str; use ethereum_types::{Address as EAddress, U256 as EU256}; diff --git a/crates/pbs/src/types/get_header.rs b/crates/pbs/src/types/get_header.rs index 9b4b49ae..d6942c21 100644 --- a/crates/pbs/src/types/get_header.rs +++ b/crates/pbs/src/types/get_header.rs @@ -1,5 +1,7 @@ -use alloy::primitives::{B256, U256}; -use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; +use alloy::{ + primitives::{B256, U256}, + rpc::types::beacon::{BlsPublicKey, BlsSignature}, +}; use ethereum_types::U256 as EU256; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; diff --git a/crates/pbs/src/types/kzg.rs b/crates/pbs/src/types/kzg.rs index ba5e66d6..8bbd1aa8 100644 --- a/crates/pbs/src/types/kzg.rs +++ b/crates/pbs/src/types/kzg.rs @@ -1,7 +1,6 @@ use std::{ fmt::{self, Debug, Display, Formatter}, str::FromStr, - usize, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; diff --git a/crates/signer/src/manager.rs b/crates/signer/src/manager.rs index 9121d60f..49910bbd 100644 --- a/crates/signer/src/manager.rs +++ b/crates/signer/src/manager.rs @@ -10,12 +10,12 @@ use tree_hash::TreeHash; use crate::error::SignerModuleError; -// For extra safety and to avoid risking signing malicious messages, use a proxy setup: -// proposer creates a new ephemeral keypair which will be used to sign commit messages, -// it also signs a ProxyDelegation associating the new keypair with its consensus pubkey -// When a new commit module starts, pass the ProxyDelegation msg and then sign all future -// commit messages with the proxy key -// for slashing the faulty message + proxy delegation can be used +// For extra safety and to avoid risking signing malicious messages, use a proxy +// setup: proposer creates a new ephemeral keypair which will be used to sign +// commit messages, it also signs a ProxyDelegation associating the new keypair +// with its consensus pubkey When a new commit module starts, pass the +// ProxyDelegation msg and then sign all future commit messages with the proxy +// key for slashing the faulty message + proxy delegation can be used // Signed using builder domain pub struct ProxySigner { @@ -58,7 +58,8 @@ impl SigningManager { Ok(signed_delegation) } - // TODO: double check what we can actually sign here with different providers eg web3 signer + // TODO: double check what we can actually sign here with different providers eg + // web3 signer pub async fn sign_consensus( &self, pubkey: &BlsPublicKey, diff --git a/crates/signer/src/service.rs b/crates/signer/src/service.rs index c582786e..9a4a3689 100644 --- a/crates/signer/src/service.rs +++ b/crates/signer/src/service.rs @@ -30,7 +30,8 @@ pub struct SigningService; struct SigningState { /// Mananger handling different signing methods manager: Arc, - /// Map of module ids to JWTs. This also acts as registry of all modules running + /// Map of module ids to JWTs. This also acts as registry of all modules + /// running jwts: HashMap, } diff --git a/tests/src/main.rs b/tests/src/main.rs index d09c2758..47e28fa6 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -16,7 +16,8 @@ async fn main() { // cb_cli::Command::Start2 { config } => { // let config = CommitBoostConfig::from_file(&config); - // let mock_validator = MockValidator::new(config.pbs.pbs_config.address); + // let mock_validator = + // MockValidator::new(config.pbs.pbs_config.address); // loop { // if let Err(err) = mock_validator.do_get_status().await { diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index 1d904332..9ed39e4f 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -3,8 +3,7 @@ use std::sync::{ Arc, }; -use alloy::primitives::U256; -use alloy::rpc::types::beacon::relay::ValidatorRegistration; +use alloy::{primitives::U256, rpc::types::beacon::relay::ValidatorRegistration}; use axum::{ extract::{Path, State}, http::StatusCode, diff --git a/tests/tests/commits.rs b/tests/tests/commits.rs index ffeb7dfe..957f8628 100644 --- a/tests/tests/commits.rs +++ b/tests/tests/commits.rs @@ -2,8 +2,8 @@ // use alloy::rpc::types::beacon::{BlsPublicKey, BlsSignature}; // use cb_cli::runner::{Runner, SignRequestSender}; // use cb_common::{config::BuilderConfig, types::Chain}; -// use cb_crypto::{signature::verify_signed_builder_message, types::SignRequest}; -// use cb_pbs::{BuilderState, DefaultBuilderApi}; +// use cb_crypto::{signature::verify_signed_builder_message, +// types::SignRequest}; use cb_pbs::{BuilderState, DefaultBuilderApi}; // use cb_tests::utils::setup_test_env; // use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; // use tree_hash_derive::TreeHash; @@ -24,11 +24,13 @@ // ) -> eyre::Result<()> { // let validator_pubkey = pubkeys[0]; -// let (request, sign_rx) = SignRequest::new(COMMIT_ID, validator_pubkey, MSG); +// let (request, sign_rx) = SignRequest::new(COMMIT_ID, validator_pubkey, +// MSG); // tx.send(request).expect("failed sending request"); -// let signature = sign_rx.await.expect("failed signing").expect("sign manager is down"); +// let signature = sign_rx.await.expect("failed signing").expect("sign +// manager is down"); // test_tx.send((validator_pubkey, signature)).unwrap(); @@ -65,5 +67,5 @@ // let (pubkey, signature) = test_rx.recv().await.unwrap(); -// assert!(verify_signed_builder_message(chain, &pubkey, &MSG, &signature).is_ok()) -// } +// assert!(verify_signed_builder_message(chain, &pubkey, &MSG, +// &signature).is_ok()) }