Skip to content

feat: dockerize Rust operator #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: add-devnet-config
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
157 changes: 157 additions & 0 deletions devnet.yaml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 3 additions & 2 deletions operator/rust/crates/operator/src/spam_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> =
Lazy::new(|| env::var("RPC_URL").expect("failed to retrieve RPC URL"));

#[allow(unused)]
static KEY: Lazy<String> =
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 8 additions & 7 deletions operator/rust/crates/operator/src/start_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> =
Lazy::new(|| env::var("RPC_URL").expect("failed to retrieve RPC URL"));

static KEY: Lazy<String> =
Lazy::new(|| env::var("PRIVATE_KEY").expect("failed to retrieve private key"));
Expand All @@ -38,15 +39,15 @@ 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);
let m_hash = eip191_hash_message(keccak256(message.abi_encode_packed()));
let operators: Vec<DynSolValue> = vec![DynSolValue::Address(signer.address())];
let signature: Vec<DynSolValue> =
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()),
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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")?;
Expand All @@ -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,
Expand All @@ -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(),
);

Expand Down
Loading