Skip to content

Commit

Permalink
feat: opstack support (#382)
Browse files Browse the repository at this point in the history
* add lc server

* add client

* add builder

* add config

* add server cli

* fix rpc socket defaults

* change rpc defaults

* better block safety
  • Loading branch information
ncitron authored Oct 2, 2024
1 parent b66d1a5 commit 93e7289
Show file tree
Hide file tree
Showing 22 changed files with 3,629 additions and 138 deletions.
2,023 changes: 1,921 additions & 102 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"core",
"ethereum",
"ethereum/consensus-core",
"opstack",
#"helios-ts",
]

Expand Down Expand Up @@ -41,6 +42,7 @@ alloy = { version = "0.2.1", features = [
"network",
"ssz",
"json-rpc",
"signers",
]}
revm = { version = "12.1.0", default-features = false, features = [
"std",
Expand All @@ -58,6 +60,7 @@ tokio = { version = "1", features = ["rt", "sync", "macros"] }

# io
reqwest = { version = "0.12.4", features = ["json"] }
url = { version = "2.5.2", features = ["serde"] }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = "1.0.85"

Expand Down
3 changes: 3 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ eyre.workspace = true
tracing.workspace = true
futures.workspace = true
alloy.workspace = true
figment = { version = "0.10.7", features = ["toml", "env"] }

clap = { version = "4.5.4", features = ["derive", "env"] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
dirs = "5.0.1"
ctrlc = "3.2.3"

helios-core = { path = "../core" }
helios-ethereum = { path = "../ethereum" }
helios-opstack = { path = "../opstack" }
156 changes: 127 additions & 29 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::net::IpAddr;
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::{
path::PathBuf,
process::exit,
Expand All @@ -7,20 +8,46 @@ use std::{
};

use alloy::primitives::B256;
use clap::Parser;
use clap::{Args, Parser, Subcommand};
use dirs::home_dir;
use eyre::Result;
use figment::providers::Serialized;
use figment::value::Value;
use futures::executor::block_on;
use tracing::{error, info};
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
use tracing_subscriber::FmtSubscriber;

use helios_ethereum::config::{cli::CliConfig, Config};
use helios_core::client::Client;
use helios_core::consensus::Consensus;
use helios_core::network_spec::NetworkSpec;
use helios_ethereum::config::{cli::CliConfig, Config as EthereumConfig};
use helios_ethereum::database::FileDB;
use helios_ethereum::{EthereumClient, EthereumClientBuilder};
use helios_opstack::{config::Config as OpStackConfig, OpStackClient, OpStackClientBuilder};

#[tokio::main]
async fn main() -> Result<()> {
enable_tracer();

let cli = Cli::parse();
match cli.command {
Command::Ethereum(ethereum) => {
let mut client = ethereum.make_client();
start_client(&mut client).await;
register_shutdown_handler(client);
}
Command::OpStack(opstack) => {
let mut client = opstack.make_client();
start_client(&mut client).await;
register_shutdown_handler(client);
}
}

std::future::pending().await
}

fn enable_tracer() {
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env()
Expand All @@ -31,29 +58,20 @@ async fn main() -> Result<()> {
.finish();

tracing::subscriber::set_global_default(subscriber).expect("subscriber set failed");
}

let config = get_config();
let mut client = match EthereumClientBuilder::new()
.config(config)
.build::<FileDB>()
{
Ok(client) => client,
Err(err) => {
error!(target: "helios::runner", error = %err);
exit(1);
}
};

async fn start_client<N: NetworkSpec, C: Consensus<N::TransactionResponse>>(
client: &mut Client<N, C>,
) {
if let Err(err) = client.start().await {
error!(target: "helios::runner", error = %err);
exit(1);
}

register_shutdown_handler(client);
std::future::pending().await
}

fn register_shutdown_handler(client: EthereumClient<FileDB>) {
fn register_shutdown_handler<N: NetworkSpec, C: Consensus<N::TransactionResponse>>(
client: Client<N, C>,
) {
let client = Arc::new(client);
let shutdown_counter = Arc::new(Mutex::new(0));

Expand Down Expand Up @@ -85,18 +103,24 @@ fn register_shutdown_handler(client: EthereumClient<FileDB>) {
.expect("could not register shutdown handler");
}

fn get_config() -> Config {
let cli = Cli::parse();
let config_path = home_dir().unwrap().join(".helios/helios.toml");
let cli_config = cli.as_cli_config();

Config::from_file(&config_path, &cli.network, &cli_config)
}

#[derive(Parser)]
#[clap(version, about)]
/// Helios is a fast, secure, and portable light client for Ethereum
/// Helios is a fast, secure, and portable multichain light client
struct Cli {
#[command(subcommand)]
command: Command,
}

#[derive(Subcommand)]
enum Command {
#[clap(name = "ethereum")]
Ethereum(EthereumArgs),
#[clap(name = "opstack")]
OpStack(OpStackArgs),
}

#[derive(Args)]
struct EthereumArgs {
#[clap(short, long, default_value = "mainnet")]
network: String,
#[clap(short = 'b', long, env)]
Expand All @@ -119,7 +143,24 @@ struct Cli {
strict_checkpoint_age: bool,
}

impl Cli {
impl EthereumArgs {
fn make_client(&self) -> EthereumClient<FileDB> {
let config_path = home_dir().unwrap().join(".helios/helios.toml");
let cli_config = self.as_cli_config();
let config = EthereumConfig::from_file(&config_path, &self.network, &cli_config);

match EthereumClientBuilder::new()
.config(config)
.build::<FileDB>()
{
Ok(client) => client,
Err(err) => {
error!(target: "helios::runner", error = %err);
exit(1);
}
}
}

fn as_cli_config(&self) -> CliConfig {
CliConfig {
checkpoint: self.checkpoint,
Expand All @@ -138,6 +179,63 @@ impl Cli {
}
}

#[derive(Args, Debug)]
struct OpStackArgs {
#[clap(short, long)]
network: String,
#[clap(short = 'b', long, env, default_value = "127.0.0.1")]
rpc_bind_ip: Option<IpAddr>,
#[clap(short = 'p', long, env, default_value = "8545")]
rpc_port: Option<u16>,
#[clap(short, long, env)]
execution_rpc: Option<String>,
#[clap(short, long, env)]
consensus_rpc: Option<String>,
}

impl OpStackArgs {
fn make_client(&self) -> OpStackClient {
let config_path = home_dir().unwrap().join(".helios/helios.toml");
let cli_provider = self.as_provider();
let config = OpStackConfig::from_file(&config_path, &self.network, cli_provider);

match OpStackClientBuilder::new().config(config).build() {
Ok(client) => client,
Err(err) => {
error!(target: "helios::runner", error = %err);
exit(1);
}
}
}

fn as_provider(&self) -> Serialized<HashMap<&str, Value>> {
let mut user_dict = HashMap::new();

if let Some(rpc) = &self.execution_rpc {
user_dict.insert("execution_rpc", Value::from(rpc.clone()));
}

if let Some(rpc) = &self.consensus_rpc {
user_dict.insert("consensus_rpc", Value::from(rpc.clone()));
}

if self.rpc_bind_ip.is_some() && self.rpc_port.is_some() {
let rpc_socket = SocketAddr::new(self.rpc_bind_ip.unwrap(), self.rpc_port.unwrap());
user_dict.insert("rpc_socket", Value::from(rpc_socket.to_string()));
}

if let Some(ip) = self.rpc_bind_ip {
user_dict.insert("rpc_bind_ip", Value::from(ip.to_string()));
}

if let Some(port) = self.rpc_port {
user_dict.insert("rpc_port", Value::from(port));
}

Serialized::from(user_dict, &self.network)
}
}

fn true_or_none(b: bool) -> Option<bool> {
if b {
Some(b)
Expand Down
2 changes: 1 addition & 1 deletion core/src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl<N: NetworkSpec, R: ExecutionRpc<N>> ExecutionClient<N, R> {

let receipts_fut = tx_hashes.iter().map(|hash| async move {
let receipt = self.rpc.get_transaction_receipt(*hash).await;
receipt?.ok_or(eyre::eyre!("not reachable"))
receipt?.ok_or(eyre::eyre!("missing block receipt"))
});

let receipts = join_all(receipts_fut).await;
Expand Down
2 changes: 1 addition & 1 deletion core/src/execution/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl<N: NetworkSpec> State<N> {
inner_ref.write().await.push_finalized_block(block);
}

}
},
}
}
});
Expand Down
5 changes: 1 addition & 4 deletions ethereum/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,9 @@ impl Config {
match err.kind {
figment::error::Kind::MissingField(field) => {
let field = field.replace('_', "-");

println!("\x1b[91merror\x1b[0m: missing configuration field: {field}");

println!("\n\ttry supplying the proper command line argument: --{field}");

println!("\talternatively, you can add the field to your helios.toml file or as an environment variable");
println!("\talternatively, you can add the field to your helios.toml file");
println!("\nfor more information, check the github README");
}
_ => println!("cannot parse configuration: {err}"),
Expand Down
2 changes: 1 addition & 1 deletion ethereum/src/config/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn mainnet() -> BaseConfig {
"c7fc7b2f4b548bfc9305fa80bc1865ddc6eea4557f0a80507af5dc34db7bd9ce"
),
rpc_port: 8545,
consensus_rpc: Some("https://www.lightclientdata.org".to_string()),
consensus_rpc: Some("https://ethereum.operationsolarstorm.org".to_string()),
chain: ChainConfig {
chain_id: 1,
genesis_time: 1606824023,
Expand Down
46 changes: 46 additions & 0 deletions opstack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "helios-opstack"
version = "0.7.0"
edition = "2021"

[[bin]]
name = "server"
path = "./bin/server.rs"

[dependencies]
tokio.workspace = true
eyre.workspace = true
tracing.workspace = true
hex.workspace = true
serde.workspace = true
typenum.workspace = true
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
reqwest.workspace = true
url.workspace = true

# consensus
alloy.workspace = true
revm.workspace = true
sha2.workspace = true
ethereum_ssz_derive.workspace = true
ethereum_ssz.workspace = true
ssz_types.workspace = true
op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", tag = "v0.1.5" }
op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", tag = "v0.1.5" }
op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", tag = "v0.1.5" }

# server
axum = "0.7.6"
clap = { version = "4.5.4", features = ["derive", "env"] }

# config
figment = { version = "0.10.7", features = ["toml", "env"] }

# networking
libp2p = { version = "0.51.3", features = ["macros", "tokio", "tcp", "mplex", "noise", "gossipsub", "ping"] }
discv5 = "0.7.0"
libp2p-identity = { version = "0.1.2", features = ["secp256k1"] }
unsigned-varint = "0.7.1"
snap = "1"

helios-core = { path = "../core" }
49 changes: 49 additions & 0 deletions opstack/bin/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::net::SocketAddr;

use clap::Parser;
use eyre::Result;
use tracing_subscriber::{EnvFilter, FmtSubscriber};

use helios_opstack::{
config::{Network, NetworkConfig},
server::start_server,
};

#[tokio::main]
async fn main() -> Result<()> {
enable_tracing();
let cli = Cli::parse();
let config = NetworkConfig::from(cli.network);

let chain_id = config.chain.chain_id;
let unsafe_signer = config.chain.unsafe_signer;
let server_addr = cli.server_address;
let gossip_addr = cli.gossip_address;

start_server(server_addr, gossip_addr, chain_id, unsafe_signer).await?;

Ok(())
}

fn enable_tracing() {
let env_filter = EnvFilter::builder()
.with_default_directive("helios_opstack=info".parse().unwrap())
.from_env()
.expect("invalid env filter");

let subscriber = FmtSubscriber::builder()
.with_env_filter(env_filter)
.finish();

tracing::subscriber::set_global_default(subscriber).expect("subscriber set failed");
}

#[derive(Parser)]
struct Cli {
#[clap(short, long)]
network: Network,
#[clap(short, long, default_value = "127.0.0.1:3000")]
server_address: SocketAddr,
#[clap(short, long, default_value = "0.0.0.0:9876")]
gossip_address: SocketAddr,
}
Loading

0 comments on commit 93e7289

Please sign in to comment.