Skip to content

Commit

Permalink
Merge pull request #36 from worldcoin/0xkitsune/state-bridge-service
Browse files Browse the repository at this point in the history
State Bridge Service Tx Logic
  • Loading branch information
0xKitsune authored Nov 19, 2023
2 parents e84babd + cf59804 commit a2de03a
Show file tree
Hide file tree
Showing 11 changed files with 491 additions and 235 deletions.
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ axum = "0.6.20"
axum-middleware = { path = "crates/axum-middleware" }
clap = { version = "4.4.7", features = ["derive"] }
common = { path = "crates/common" }
ethers = { version = "2.0.10", features = ["abigen", "ws", "ipc", "rustls", "openssl"] }
ethers = { version = "2.0.10", features = [
"abigen",
"ws",
"ipc",
"rustls",
"openssl",
] }
ethers-throttle = { path = "crates/ethers-throttle" }
eyre = "0.6.8"
futures = "0.3.28"
Expand Down
33 changes: 33 additions & 0 deletions bin/configs/state_bridge.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Address of the WorldIDIdentityManager contract on L1
l1_world_id = "0x78eC127A3716D447F4575E9c834d452E397EE9E1"
l1_rpc_endpoint = ""
# Request per second limit for rpc endpoint
throttle = 5
# Number of block confirmations before considering a `propagateRoot()` tx finalized
block_confirmations = 3
# Optional max gas price to use when sending `propagateRoot()` txs
# max_gas_price = 3000000000


# Optimism
[[state_bridge]]
# Address of the state bridge contract on L1
l1_state_bridge = "0x39CcB3b670651a14da8b3835f42924f49C2C5986"
# Address of the WorldID contract on L2
l2_world_id = "0x715161DB36C260e4bE7094773785A111D11bC5F9"
# RPC endpoint for L2
l2_rpc_endpoint = ""
# Request per second limit for rpc endpoint
throttle = 5
# Minimum time between `propagateRoot()` transactions
relaying_period_seconds = 1800



# Polygon
[[state_bridge]]
l1_state_bridge = "0x9e4984bdE7F17aB422Cc4254C08074b3c42160d1"
l2_world_id = "0xCDfDF72065493bDDb2131478c89C1D5482BD1dF6"
l2_rpc_endpoint = ""
throttle = 5
relaying_period_seconds = 1800
262 changes: 142 additions & 120 deletions bin/state_bridge_service.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use clap::Parser;
use common::tracing::{init_datadog_subscriber, init_subscriber};
use ethers::abi::Address;
use ethers::middleware::gas_escalator::{
Frequency, GasEscalatorMiddleware, GeometricGasPrice,
};
use ethers::prelude::{
Http, LocalWallet, NonceManagerMiddleware, Provider, Signer,
SignerMiddleware, H160,
Http, LocalWallet, NonceManagerMiddleware, Provider, Signer, H160,
};
use ethers::providers::Middleware;
use ethers::types::U256;
use ethers_throttle::ThrottledProvider;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use governor::Jitter;
use opentelemetry::global::shutdown_tracer_provider;
use serde::{Deserialize, Serialize};
use serde::{de, Deserialize, Deserializer, Serialize};
use tracing::Level;
use world_tree::abi::{IBridgedWorldID, IStateBridge};
use url::Url;
use world_tree::state_bridge::service::StateBridgeService;
use world_tree::state_bridge::StateBridge;

#[derive(Parser, Debug)]
#[clap(
name = "State Bridge Service",
about = "The state bridge service listens to root changes from the WorldIdIdentityManager and propagates them to each of the corresponding Layer 2s specified in the configuration file."
about = "The state bridge service listens to root changes from the `WorldIdIdentityManager` and propagates them to each of the corresponding Layer 2s specified in the configuration file."
)]
struct Opts {
#[clap(
Expand All @@ -32,52 +37,38 @@ struct Opts {
help = "Path to the TOML state bridge service config file"
)]
config: PathBuf,

#[clap(long, help = "Enable datadog backend for instrumentation")]
#[clap(
short,
long,
help = "Private key for account used to send `propagateRoot()` txs"
)]
private_key: String,
#[clap(short, long, help = "Enable datadog backend for instrumentation")]
datadog: bool,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
struct BridgeConfig {
name: String, //TODO: do we need this where do we use it?
state_bridge_address: Address,
bridged_world_id_address: Address,
bridged_rpc_url: String,
#[derive(Deserialize, Serialize, Debug)]
struct Config {
l1_rpc_endpoint: String,
#[serde(deserialize_with = "deserialize_h160")]
l1_world_id: H160,
block_confirmations: usize,
state_bridge: Vec<StateBridgeConfig>,
throttle: u32,
#[serde(deserialize_with = "deserialize_opt_u256", default)]
max_gas_price: Option<U256>,
}

//TODO: lets update this to be a yaml file and then we can do something like the following:
// rpc_url: ""
// private_key: ""
// world_id_address: ""
// block_confirmations: ""
// state_bridges:
// optimism:
// state_bridge_address: ""
// bridged_world_id_address: ""
// bridged_rpc_url: ""
// relaying_period_seconds: 5
//
// arbitrum:
// state_bridge_address: ""
// bridged_world_id_address: ""
// bridged_rpc_url: ""
// relaying_period_seconds: 5

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Config {
// RPC URL for the HTTP provider (World ID IdentityManager)
rpc_url: String,
// Private key to use for the middleware signer
private_key: String,
// `WorldIDIdentityManager` contract address
world_id_address: H160,
// List of `StateBridge` and `BridgedWorldID` pair addresses
bridge_configs: Vec<BridgeConfig>,
// `propagateRoot()` call period time in seconds
struct StateBridgeConfig {
#[serde(deserialize_with = "deserialize_h160")]
l1_state_bridge: H160,
#[serde(deserialize_with = "deserialize_h160")]
l2_world_id: H160,
l2_rpc_endpoint: String,
#[serde(deserialize_with = "deserialize_duration_from_seconds")]
relaying_period_seconds: Duration,
// Number of block confirmations required for the `propagateRoot` call on the `StateBridge`
// contract
block_confirmations: Option<usize>,
throttle: u32,
}

const SERVICE_NAME: &str = "state-bridge-service";
Expand All @@ -94,73 +85,37 @@ async fn main() -> eyre::Result<()> {
init_subscriber(Level::INFO);
}

spawn_state_bridge_service(
config.rpc_url,
config.private_key,
config.world_id_address,
config.bridge_configs,
config.relaying_period_seconds,
config.block_confirmations.unwrap_or(0),
let mut wallet = opts.private_key.parse::<LocalWallet>()?;
let l1_middleware = initialize_l1_middleware(
&config.l1_rpc_endpoint,
config.throttle,
wallet.address(),
config.max_gas_price,
)
.await?;

shutdown_tracer_provider();

Ok(())
}

async fn spawn_state_bridge_service(
rpc_url: String,
private_key: String,
world_id_address: H160,
bridge_configs: Vec<BridgeConfig>,
relaying_period: Duration,
block_confirmations: usize,
) -> eyre::Result<()> {
let provider = Provider::<Http>::try_from(rpc_url)
.expect("failed to initialize Http provider");

let chain_id = provider.get_chainid().await?.as_u64();

let wallet = private_key.parse::<LocalWallet>()?.with_chain_id(chain_id);
let wallet_address = wallet.address();

let signer_middleware = SignerMiddleware::new(provider, wallet);
let nonce_manager_middleware =
NonceManagerMiddleware::new(signer_middleware, wallet_address);
let middleware = Arc::new(nonce_manager_middleware);
let chain_id = l1_middleware.get_chainid().await?.as_u64();
wallet = wallet.with_chain_id(chain_id);

let mut state_bridge_service =
StateBridgeService::new(world_id_address, middleware).await?;

let wallet = private_key
.parse::<LocalWallet>()
.expect("couldn't instantiate wallet from private key");

for bridge_config in bridge_configs {
let BridgeConfig {
state_bridge_address,
bridged_world_id_address,
bridged_rpc_url,
..
} = bridge_config;

let l2_middleware =
initialize_l2_middleware(&bridged_rpc_url, wallet.clone()).await?;

let state_bridge_interface =
IStateBridge::new(state_bridge_address, l2_middleware.clone());

let bridged_world_id_interface = IBridgedWorldID::new(
bridged_world_id_address,
l2_middleware.clone(),
);

let state_bridge = StateBridge::new(
state_bridge_interface,
bridged_world_id_interface,
relaying_period,
block_confirmations,
StateBridgeService::new(config.l1_world_id, l1_middleware.clone())
.await?;

for bridge_config in config.state_bridge {
let l2_middleware = initialize_l2_middleware(
&bridge_config.l2_rpc_endpoint,
bridge_config.throttle,
)
.await?;

let state_bridge = StateBridge::new_from_parts(
bridge_config.l1_state_bridge,
wallet.clone(),
l1_middleware.clone(),
bridge_config.l2_world_id,
l2_middleware,
bridge_config.relaying_period_seconds,
config.block_confirmations,
)?;

state_bridge_service.add_state_bridge(state_bridge);
Expand All @@ -176,27 +131,94 @@ async fn spawn_state_bridge_service(
result??;
}

shutdown_tracer_provider();

Ok(())
}

pub async fn initialize_l2_middleware(
l2_rpc_endpoint: &str,
wallet: LocalWallet,
pub async fn initialize_l1_middleware(
rpc_endpoint: &str,
throttle: u32,
wallet_address: H160,
max_gas_price: Option<U256>,
) -> eyre::Result<
Arc<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>,
Arc<
GasEscalatorMiddleware<
NonceManagerMiddleware<Provider<ThrottledProvider<Http>>>,
>,
>,
> {
let l2_provider = Provider::<Http>::try_from(l2_rpc_endpoint)?;
let chain_id = l2_provider.get_chainid().await?.as_u64();
let provider = initialize_throttled_provider(rpc_endpoint, throttle)?;
let nonce_manager_middleware =
NonceManagerMiddleware::new(provider, wallet_address);

let wallet = wallet.with_chain_id(chain_id);
let wallet_address = wallet.address();
let geometric_escalator =
GeometricGasPrice::new(1.125, 60_u64, max_gas_price);
let gas_escalator_middleware = GasEscalatorMiddleware::new(
nonce_manager_middleware,
geometric_escalator,
Frequency::PerBlock,
);

let signer_middleware = SignerMiddleware::new(l2_provider, wallet);
Ok(Arc::new(gas_escalator_middleware))
}

let nonce_manager_middleware =
NonceManagerMiddleware::new(signer_middleware, wallet_address);
pub async fn initialize_l2_middleware(
l2_rpc_endpoint: &str,
throttle: u32,
) -> eyre::Result<Arc<Provider<ThrottledProvider<Http>>>> {
Ok(Arc::new(initialize_throttled_provider(
l2_rpc_endpoint,
throttle,
)?))
}

let l2_middleware = Arc::new(nonce_manager_middleware);
pub fn initialize_throttled_provider(
rpc_endpoint: &str,
throttle: u32,
) -> eyre::Result<Provider<ThrottledProvider<Http>>> {
let http_provider = Http::new(Url::parse(rpc_endpoint)?);
let throttled_http_provider = ThrottledProvider::new(
http_provider,
throttle,
Some(Jitter::new(
Duration::from_millis(10),
Duration::from_millis(100),
)),
);

Ok(Provider::new(throttled_http_provider))
}

Ok(l2_middleware)
fn deserialize_h160<'de, D>(deserializer: D) -> Result<H160, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
H160::from_str(&s).map_err(de::Error::custom)
}

fn deserialize_duration_from_seconds<'de, D>(
deserializer: D,
) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let secs = u64::deserialize(deserializer)?;
Ok(Duration::from_secs(secs))
}

fn deserialize_opt_u256<'de, D>(
deserializer: D,
) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?;
match s {
Some(value) => U256::from_dec_str(&value)
.map(Some)
.map_err(serde::de::Error::custom),
None => Ok(None),
}
}
3 changes: 2 additions & 1 deletion crates/common/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ pub fn init_statsd_exporter(host: &str, port: u16) {
.build(None)
.expect("Could not create StatsdRecorder");

metrics::set_boxed_recorder(Box::new(recorder)).expect("TODO:");
metrics::set_boxed_recorder(Box::new(recorder))
.expect("TODO: Handle this error");
}
Loading

0 comments on commit a2de03a

Please sign in to comment.