Skip to content
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

feat: add support for Gnosis Chain #455

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 67 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ use futures::executor::block_on;
use helios_core::client::Client;
use helios_core::consensus::Consensus;
use helios_core::network_spec::NetworkSpec;
use helios_ethereum::builder::GnosisClientBuilder;
use helios_ethereum::config::{cli::CliConfig, Config as EthereumConfig};
use helios_ethereum::database::FileDB;
use helios_ethereum::{EthereumClient, EthereumClientBuilder};
use helios_ethereum::{EthereumClient, EthereumClientBuilder, GnosisClient};
use helios_opstack::{config::Config as OpStackConfig, OpStackClient, OpStackClientBuilder};
use tracing::{error, info};
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
Expand All @@ -42,6 +43,11 @@ async fn main() -> Result<()> {
start_client(&mut client).await;
register_shutdown_handler(client);
}
Command::Gnosis(gnosis) => {
let mut client = gnosis.make_client();
start_client(&mut client).await;
register_shutdown_handler(client);
}
}

std::future::pending().await
Expand Down Expand Up @@ -117,6 +123,8 @@ enum Command {
Ethereum(EthereumArgs),
#[clap(name = "opstack")]
OpStack(OpStackArgs),
#[clap(name = "gnosis")]
Gnosis(GnosisArgs),
}

#[derive(Args)]
Expand Down Expand Up @@ -258,6 +266,64 @@ impl OpStackArgs {
}
}

#[derive(Args)]
struct GnosisArgs {
#[clap(short, long, default_value = "gnosis")]
network: String,
#[clap(short = 'b', long, env)]
rpc_bind_ip: Option<IpAddr>,
#[clap(short = 'p', long, env)]
rpc_port: Option<u16>,
#[clap(short = 'w', long, env)]
checkpoint: Option<B256>,
#[clap(short, long, env)]
execution_rpc: Option<String>,
#[clap(short, long, env)]
consensus_rpc: Option<String>,
#[clap(short, long, env)]
data_dir: Option<String>,
#[clap(short = 'f', long, env)]
fallback: Option<String>,
#[clap(short = 'l', long, env)]
load_external_fallback: bool,
#[clap(short = 's', long, env)]
strict_checkpoint_age: bool,
}

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

match GnosisClientBuilder::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,
execution_rpc: self.execution_rpc.clone(),
consensus_rpc: self.consensus_rpc.clone(),
data_dir: self
.data_dir
.as_ref()
.map(|s| PathBuf::from_str(s).expect("cannot find data dir")),
rpc_bind_ip: self.rpc_bind_ip,
rpc_port: self.rpc_port,
fallback: self.fallback.clone(),
load_external_fallback: true_or_none(self.load_external_fallback),
strict_checkpoint_age: true_or_none(self.strict_checkpoint_age),
}
}
}

fn true_or_none(b: bool) -> Option<bool> {
if b {
Some(b)
Expand Down
4 changes: 2 additions & 2 deletions ethereum/consensus-core/src/consensus_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,15 +371,15 @@ pub fn force_update<S: ConsensusSpec>(store: &mut LightClientStore<S>, current_s
}
}

pub fn expected_current_slot(now: SystemTime, genesis_time: u64) -> u64 {
pub fn expected_current_slot(now: SystemTime, genesis_time: u64, block_time: u64) -> u64 {
let now = now
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| panic!("unreachable"))
.as_secs();

let since_genesis = now - genesis_time;

since_genesis / 12
since_genesis / block_time
}

pub fn calc_sync_period<S: ConsensusSpec>(slot: u64) -> u64 {
Expand Down
18 changes: 18 additions & 0 deletions ethereum/consensus-core/src/consensus_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ impl ConsensusSpec for MainnetConsensusSpec {
type SyncCommitteeSize = typenum::U512;
}

#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
pub struct GnosisConsensusSpec;
// Here is the modified version for Gnosis Chain
impl ConsensusSpec for GnosisConsensusSpec {
type MaxProposerSlashings = typenum::U16;
type MaxAttesterSlashings = typenum::U2;
type MaxAttestations = typenum::U128;
type MaxDeposits = typenum::U16;
type MaxVoluntaryExits = typenum::U16;
type MaxBlsToExecutionChanged = typenum::U16;
type MaxBlobKzgCommitments = typenum::U4096;
type MaxWithdrawals = typenum::U8;
type MaxValidatorsPerCommitee = typenum::U2048;
type SlotsPerEpoch = typenum::U16;
type EpochsPerSyncCommiteePeriod = typenum::U512;
type SyncCommitteeSize = typenum::U512;
}

#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
pub struct MinimalConsensusSpec;

Expand Down
218 changes: 217 additions & 1 deletion ethereum/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::Arc;
use alloy::primitives::B256;
use eyre::{eyre, Result};

use helios_consensus_core::consensus_spec::GnosisConsensusSpec;
use helios_consensus_core::consensus_spec::MainnetConsensusSpec;
use helios_core::client::Client;

Expand All @@ -16,7 +17,7 @@ use crate::consensus::ConsensusClient;
use crate::database::Database;
use crate::rpc::http_rpc::HttpRpc;
use crate::spec::Ethereum;
use crate::EthereumClient;
use crate::{EthereumClient, GnosisClient};

#[derive(Default)]
pub struct EthereumClientBuilder {
Expand Down Expand Up @@ -231,3 +232,218 @@ impl EthereumClientBuilder {
)
}
}

#[derive(Default)]
pub struct GnosisClientBuilder {
network: Option<Network>,
consensus_rpc: Option<String>,
execution_rpc: Option<String>,
checkpoint: Option<B256>,
#[cfg(not(target_arch = "wasm32"))]
rpc_bind_ip: Option<IpAddr>,
#[cfg(not(target_arch = "wasm32"))]
rpc_port: Option<u16>,
#[cfg(not(target_arch = "wasm32"))]
data_dir: Option<PathBuf>,
config: Option<Config>,
fallback: Option<String>,
load_external_fallback: bool,
strict_checkpoint_age: bool,
}

impl GnosisClientBuilder {
pub fn new() -> Self {
Self::default()
}

pub fn network(mut self, network: Network) -> Self {
self.network = Some(network);
self
}

pub fn consensus_rpc(mut self, consensus_rpc: &str) -> Self {
self.consensus_rpc = Some(consensus_rpc.to_string());
self
}

pub fn execution_rpc(mut self, execution_rpc: &str) -> Self {
self.execution_rpc = Some(execution_rpc.to_string());
self
}

pub fn checkpoint(mut self, checkpoint: B256) -> Self {
self.checkpoint = Some(checkpoint);
self
}

#[cfg(not(target_arch = "wasm32"))]
pub fn rpc_bind_ip(mut self, ip: IpAddr) -> Self {
self.rpc_bind_ip = Some(ip);
self
}

#[cfg(not(target_arch = "wasm32"))]
pub fn rpc_port(mut self, port: u16) -> Self {
self.rpc_port = Some(port);
self
}

#[cfg(not(target_arch = "wasm32"))]
pub fn data_dir(mut self, data_dir: PathBuf) -> Self {
self.data_dir = Some(data_dir);
self
}

pub fn config(mut self, config: Config) -> Self {
self.config = Some(config);
self
}

pub fn fallback(mut self, fallback: &str) -> Self {
self.fallback = Some(fallback.to_string());
self
}

pub fn load_external_fallback(mut self) -> Self {
self.load_external_fallback = true;
self
}

pub fn strict_checkpoint_age(mut self) -> Self {
self.strict_checkpoint_age = true;
self
}

pub fn build<DB: Database>(self) -> Result<GnosisClient<DB>> {
let base_config = if let Some(network) = self.network {
network.to_base_config()
} else {
let config = self
.config
.as_ref()
.ok_or(eyre!("missing network config"))?;
config.to_base_config()
};

let consensus_rpc = self.consensus_rpc.unwrap_or_else(|| {
self.config
.as_ref()
.expect("missing consensus rpc")
.consensus_rpc
.clone()
});

let execution_rpc = self.execution_rpc.unwrap_or_else(|| {
self.config
.as_ref()
.expect("missing execution rpc")
.execution_rpc
.clone()
});

let checkpoint = if let Some(checkpoint) = self.checkpoint {
Some(checkpoint)
} else if let Some(config) = &self.config {
config.checkpoint
} else {
None
};

let default_checkpoint = if let Some(config) = &self.config {
config.default_checkpoint
} else {
println!("Default checkpoint {:?}", base_config.default_checkpoint);
base_config.default_checkpoint
};

#[cfg(not(target_arch = "wasm32"))]
let rpc_bind_ip = if self.rpc_bind_ip.is_some() {
self.rpc_bind_ip
} else if let Some(config) = &self.config {
config.rpc_bind_ip
} else {
Some(base_config.rpc_bind_ip)
};

#[cfg(not(target_arch = "wasm32"))]
let rpc_port = if self.rpc_port.is_some() {
self.rpc_port
} else if let Some(config) = &self.config {
config.rpc_port
} else {
None
};

#[cfg(not(target_arch = "wasm32"))]
let data_dir = if self.data_dir.is_some() {
self.data_dir
} else if let Some(config) = &self.config {
config.data_dir.clone()
} else {
None
};

let fallback = if self.fallback.is_some() {
self.fallback
} else if let Some(config) = &self.config {
config.fallback.clone()
} else {
None
};

let load_external_fallback = if let Some(config) = &self.config {
self.load_external_fallback || config.load_external_fallback
} else {
self.load_external_fallback
};

let strict_checkpoint_age = if let Some(config) = &self.config {
self.strict_checkpoint_age || config.strict_checkpoint_age
} else {
self.strict_checkpoint_age
};

let config = Config {
consensus_rpc,
execution_rpc,
checkpoint,
default_checkpoint,
#[cfg(not(target_arch = "wasm32"))]
rpc_bind_ip,
#[cfg(target_arch = "wasm32")]
rpc_bind_ip: None,
#[cfg(not(target_arch = "wasm32"))]
rpc_port,
#[cfg(target_arch = "wasm32")]
rpc_port: None,
#[cfg(not(target_arch = "wasm32"))]
data_dir,
#[cfg(target_arch = "wasm32")]
data_dir: None,
chain: base_config.chain,
forks: base_config.forks,
max_checkpoint_age: base_config.max_checkpoint_age,
fallback,
load_external_fallback,
strict_checkpoint_age,
database_type: None,
};

#[cfg(not(target_arch = "wasm32"))]
let socket = if rpc_bind_ip.is_some() && rpc_port.is_some() {
Some(SocketAddr::new(rpc_bind_ip.unwrap(), rpc_port.unwrap()))
} else {
None
};

let config = Arc::new(config);
let consensus = ConsensusClient::new(&config.consensus_rpc, config.clone())?;

Client::<Ethereum, ConsensusClient<GnosisConsensusSpec, HttpRpc, DB>>::new(
&config.execution_rpc.clone(),
consensus,
#[cfg(not(target_arch = "wasm32"))]
socket,
)
}
}
Loading