diff --git a/Dockerfile b/Dockerfile index 63a34d62..60096af8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,23 @@ -FROM node:22 +# Rust +FROM rust:1.81 AS rs-builder +WORKDIR /usr/src/hello-world +COPY ./Cargo.toml ./Cargo.lock rust-toolchain.toml ./ +COPY ./operator/rust ./operator/rust +RUN cargo build --release --bin start_operator --bin spam_tasks + +FROM ubuntu:24.04 AS operator-rs +WORKDIR /app +COPY --from=rs-builder /usr/src/hello-world/target/release/start_operator /usr/local/bin/start_operator +CMD ["start_operator"] + +FROM ubuntu:24.04 AS traffic-generator-rs +WORKDIR /app +COPY --from=rs-builder /usr/src/hello-world/target/release/spam_tasks /usr/local/bin/spam_tasks +CMD ["spam_tasks"] + + +# TypeScript +FROM node:22 AS operator-ts WORKDIR /app diff --git a/devnet.yaml b/devnet.yaml new file mode 100644 index 00000000..0bb8898e --- /dev/null +++ b/devnet.yaml @@ -0,0 +1,157 @@ +# yaml-language-server: $schema=https://github.com/Layr-Labs/avs-devnet/raw/refs/heads/main/schema.json + +# Local devnet config file for the hello-world-avs example +# This file can be used inside the hello-world-avs repo to start +# a devnet with any local changes + +# To run this example: +# 0. Ensure you have the latest version of the devnet (see https://github.com/Layr-Labs/avs-devnet for how to install) +# 1. While inside the Hello World repo, run `avs-devnet start` + +deployments: + - name: EigenLayer + repo: "." + contracts_path: "contracts" + script: script/DeployEigenLayerCore.s.sol + env: + # NOTE: this is used inside the deployer script + PRIVATE_KEY: "{{.deployer_private_key}}" + # NOTE: these are needed because of repo's `foundry.toml` + HOLESKY_PRIVATE_KEY: "" + HOLESKY_RPC_URL: "" + ETHERSCAN_API_KEY: "" + input: + config/core/: core_deployment_config + output: + eigenlayer_addresses: "deployments/core/31337.json" + + - name: hello-world-avs + repo: "." + contracts_path: "contracts" + script: script/HelloWorldDeployer.s.sol + env: + # NOTE: this is used inside the deployer script + PRIVATE_KEY: "{{.deployer_private_key}}" + # NOTE: these are needed because of repo's `foundry.toml` + HOLESKY_PRIVATE_KEY: "" + HOLESKY_RPC_URL: "" + ETHERSCAN_API_KEY: "" + input: + config/hello-world/: avs_deployment_config + deployments/core/: eigenlayer_addresses + output: + avs_addresses: "deployments/hello-world/31337.json" + +services: + - name: operator0 + image: hello_world_ts + build_cmd: "docker build . -t hello_world_ts --target operator-ts" + input: + /app/contracts/deployments/core/: eigenlayer_addresses + /app/contracts/deployments/hello-world/: avs_addresses + env: + # This expands to the RPC node's URL + RPC_URL: "{{.http_rpc_url}}" + PRIVATE_KEY: "{{.keys.operator0_ecdsa_keys.private_key}}" + cmd: ["npm", "run", "start:operator"] + + - name: operator1 + image: operator_rs + build_cmd: "docker build . -t operator_rs --target operator-rs" + input: + /app/contracts/deployments/core/: eigenlayer_addresses + /app/contracts/deployments/hello-world/: avs_addresses + env: + # This expands to the RPC node's URL + RPC_URL: "{{.http_rpc_url}}" + PRIVATE_KEY: "{{.keys.operator1_ecdsa_keys.private_key}}" + + - name: traffic-generator0 + image: hello_world_ts + build_cmd: "docker build . -t hello_world_ts --target operator-ts" + input: + /app/contracts/deployments/core/: eigenlayer_addresses + /app/contracts/deployments/hello-world/: avs_addresses + env: + # This expands to the RPC node's URL + RPC_URL: "{{.http_rpc_url}}" + PRIVATE_KEY: "{{.keys.traffic_generator0_ecdsa_keys.private_key}}" + cmd: ["npm", "run", "start:traffic"] + + - name: traffic-generator1 + image: traffic_generator_rs + build_cmd: "docker build . -t traffic_generator_rs --target traffic-generator-rs" + input: + /app/contracts/deployments/core/: eigenlayer_addresses + /app/contracts/deployments/hello-world/: avs_addresses + env: + # This expands to the RPC node's URL + RPC_URL: "{{.http_rpc_url}}" + PRIVATE_KEY: "{{.keys.traffic_generator1_ecdsa_keys.private_key}}" + +keys: + - name: operator0_ecdsa_keys + type: ecdsa + - name: operator1_ecdsa_keys + type: ecdsa + - name: traffic_generator0_ecdsa_keys + type: ecdsa + - name: traffic_generator1_ecdsa_keys + type: ecdsa + +artifacts: + core_deployment_config: + files: + 31337.json: + template: | + { + "strategyManager": { + "init_paused_status": 0, + "init_withdrawal_delay_blocks": 50400 + }, + "delegation": { + "init_paused_status": 0, + "init_withdrawal_delay_blocks": 50400 + }, + "slasher": { + "init_paused_status": 0 + }, + "eigenPodManager": { + "init_paused_status": 0 + }, + "rewardsCoordinator": { + "init_paused_status": 0, + "MAX_REWARDS_DURATION": 864000, + "MAX_RETROACTIVE_LENGTH": 86400, + "MAX_FUTURE_LENGTH": 86400, + "GENESIS_REWARDS_TIMESTAMP": 1672531200, + "rewards_updater_address": "{{.deployer_address}}", + "rewards_updater_key": "{{.deployer_private_key}}", + "activation_delay": 0, + "calculation_interval_seconds": 86400, + "global_operator_commission_bips": 1000 + } + } + + avs_deployment_config: + files: + 31337.json: + template: | + { + "addresses": { + "rewardsOwner": "{{.deployer_address}}", + "rewardsInitiator": "{{.deployer_address}}" + }, + "keys": { + "rewardsOwner": "{{.deployer_private_key}}", + "rewardsInitiator": "{{.deployer_private_key}}" + } + } + +ethereum_package: + additional_services: + - blockscout + network_params: + # We use the chain ID hardcoded in the hello-world-avs example + network_id: "31337" + seconds_per_slot: 3 diff --git a/operator/rust/crates/operator/src/spam_tasks.rs b/operator/rust/crates/operator/src/spam_tasks.rs index da3b0834..6e64605b 100644 --- a/operator/rust/crates/operator/src/spam_tasks.rs +++ b/operator/rust/crates/operator/src/spam_tasks.rs @@ -10,7 +10,8 @@ use rand::Rng; use std::{env, str::FromStr}; use tokio::time::{self, Duration}; -pub const ANVIL_RPC_URL: &str = "http://localhost:8545"; +static RPC_URL: Lazy = + Lazy::new(|| env::var("RPC_URL").expect("failed to retrieve RPC URL")); #[allow(unused)] static KEY: Lazy = @@ -38,7 +39,7 @@ async fn create_new_task(task_name: &str) -> Result<()> { let parsed: HelloWorldData = serde_json::from_str(&data)?; let hello_world_contract_address: Address = parsed.addresses.hello_world_service_manager.parse()?; - let pr = get_signer(&KEY.clone(), ANVIL_RPC_URL); + let pr = get_signer(&KEY.clone(), &RPC_URL.clone()); let signer = PrivateKeySigner::from_str(&KEY.clone())?; let hello_world_contract = HelloWorldServiceManager::new(hello_world_contract_address, pr); diff --git a/operator/rust/crates/operator/src/start_operator.rs b/operator/rust/crates/operator/src/start_operator.rs index 5c0de67b..567a8c64 100644 --- a/operator/rust/crates/operator/src/start_operator.rs +++ b/operator/rust/crates/operator/src/start_operator.rs @@ -28,7 +28,8 @@ use once_cell::sync::Lazy; use rand::TryRngCore; use std::{env, str::FromStr}; -pub const ANVIL_RPC_URL: &str = "http://localhost:8545"; +static RPC_URL: Lazy = + Lazy::new(|| env::var("RPC_URL").expect("failed to retrieve RPC URL")); static KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").expect("failed to retrieve private key")); @@ -38,7 +39,7 @@ async fn sign_and_response_to_task( task_created_block: u32, name: String, ) -> Result<()> { - let pr = get_signer(&KEY.clone(), ANVIL_RPC_URL); + let pr = get_signer(&KEY.clone(), &RPC_URL.clone()); let signer = PrivateKeySigner::from_str(&KEY.clone())?; let message = format!("Hello, {}", name); @@ -46,7 +47,7 @@ async fn sign_and_response_to_task( let operators: Vec = vec![DynSolValue::Address(signer.address())]; let signature: Vec = vec![DynSolValue::Bytes(signer.sign_hash_sync(&m_hash)?.into())]; - let current_block = U256::from(get_provider(ANVIL_RPC_URL).get_block_number().await?); + let current_block = U256::from(get_provider(&RPC_URL.clone()).get_block_number().await?); let signature_data = DynSolValue::Tuple(vec![ DynSolValue::Array(operators.clone()), DynSolValue::Array(signature.clone()), @@ -86,7 +87,7 @@ async fn sign_and_response_to_task( /// Monitor new tasks async fn monitor_new_tasks() -> Result<()> { - let pr = get_signer(&KEY.clone(), ANVIL_RPC_URL); + let pr = get_signer(&KEY.clone(), &RPC_URL.clone()); let hello_world_contract_address: Address = parse_hello_world_service_manager("contracts/deployments/hello-world/31337.json")?; let mut latest_processed_block = pr.get_block_number().await?; @@ -121,7 +122,7 @@ async fn monitor_new_tasks() -> Result<()> { } async fn register_operator() -> Result<()> { - let pr = get_signer(&KEY.clone(), ANVIL_RPC_URL); + let pr = get_signer(&KEY.clone(), &RPC_URL.clone()); let signer = PrivateKeySigner::from_str(&KEY.clone())?; let data = std::fs::read_to_string("contracts/deployments/core/31337.json")?; @@ -136,7 +137,7 @@ async fn register_operator() -> Result<()> { Address::ZERO, avs_directory_address, None, - ANVIL_RPC_URL.to_string(), + RPC_URL.to_string(), ); let elcontracts_writer_instance = ELChainWriter::new( Address::ZERO, @@ -145,7 +146,7 @@ async fn register_operator() -> Result<()> { None, Address::ZERO, elcontracts_reader_instance.clone(), - ANVIL_RPC_URL.to_string(), + RPC_URL.to_string(), KEY.clone(), );