|
| 1 | +use crate::signer::{LocalOrAws, SignerError}; |
| 2 | +use alloy::network::{Ethereum, EthereumWallet}; |
| 3 | +use alloy::providers::fillers::BlobGasFiller; |
| 4 | +use alloy::providers::{ |
| 5 | + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, |
| 6 | + Identity, ProviderBuilder, RootProvider, |
| 7 | +}; |
| 8 | +use alloy::transports::BoxTransport; |
| 9 | +use alloy_primitives::Address; |
| 10 | +use std::{borrow::Cow, env, num, str::FromStr}; |
| 11 | +use zenith_types::Zenith; |
| 12 | + |
| 13 | +// Keys for .env variables that need to be set to configure the builder. |
| 14 | +const HOST_CHAIN_ID: &str = "HOST_CHAIN_ID"; |
| 15 | +const RU_CHAIN_ID: &str = "RU_CHAIN_ID"; |
| 16 | +const HOST_RPC_URL: &str = "HOST_RPC_URL"; |
| 17 | +const ZENITH_ADDRESS: &str = "ZENITH_ADDRESS"; |
| 18 | +const QUINCEY_URL: &str = "QUINCEY_URL"; |
| 19 | +const BUILDER_PORT: &str = "BUILDER_PORT"; |
| 20 | +const SEQUENCER_KEY: &str = "SEQUENCER_KEY"; // empty (to use Quincey) OR AWS key ID (to use AWS signer) OR raw private key (to use local signer) |
| 21 | +const BUILDER_KEY: &str = "BUILDER_KEY"; // AWS key ID (to use AWS signer) OR raw private key (to use local signer) |
| 22 | +const INCOMING_TRANSACTIONS_BUFFER: &str = "INCOMING_TRANSACTIONS_BUFFER"; |
| 23 | +const BLOCK_CONFIRMATION_BUFFER: &str = "BLOCK_CONFIRMATION_BUFFER"; |
| 24 | +const BUILDER_REWARDS_ADDRESS: &str = "BUILDER_REWARDS_ADDRESS"; |
| 25 | +const ROLLUP_BLOCK_GAS_LIMIT: &str = "ROLLUP_BLOCK_GAS_LIMIT"; |
| 26 | +const TX_POOL_URL: &str = "TX_POOL_URL"; |
| 27 | +const TX_POOL_POLL_INTERVAL: &str = "TX_POOL_POLL_INTERVAL"; |
| 28 | +const TX_POOL_CACHE_DURATION: &str = "TX_POOL_CACHE_DURATION"; |
| 29 | +const OAUTH_CLIENT_ID: &str = "OAUTH_CLIENT_ID"; |
| 30 | +const OAUTH_CLIENT_SECRET: &str = "OAUTH_CLIENT_SECRET"; |
| 31 | +const OAUTH_AUTHENTICATE_URL: &str = "OAUTH_AUTHENTICATE_URL"; |
| 32 | +const OAUTH_TOKEN_URL: &str = "OAUTH_TOKEN_URL"; |
| 33 | +const OAUTH_AUDIENCE: &str = "OAUTH_AUDIENCE"; |
| 34 | + |
| 35 | +/// Configuration for a builder running a specific rollup on a specific host |
| 36 | +/// chain. |
| 37 | +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] |
| 38 | +pub struct BuilderConfig { |
| 39 | + /// The chain ID of the host chain |
| 40 | + pub host_chain_id: u64, |
| 41 | + /// The chain ID of the host chain |
| 42 | + pub ru_chain_id: u64, |
| 43 | + /// URL for Host RPC node. |
| 44 | + pub host_rpc_url: Cow<'static, str>, |
| 45 | + /// address of the Zenith contract on Host. |
| 46 | + pub zenith_address: Address, |
| 47 | + /// URL for remote Quincey Sequencer server to sign blocks. |
| 48 | + /// Disregarded if a sequencer_signer is configured. |
| 49 | + pub quincey_url: Cow<'static, str>, |
| 50 | + /// Port for the Builder server. |
| 51 | + pub builder_port: u16, |
| 52 | + /// Key to access Sequencer Wallet - AWS Key ID _OR_ local private key. |
| 53 | + /// Set IFF using local Sequencer signing instead of remote Quincey signing. |
| 54 | + pub sequencer_key: Option<String>, |
| 55 | + /// Key to access Builder transaction submission wallet - AWS Key ID _OR_ local private key. |
| 56 | + pub builder_key: String, |
| 57 | + /// Buffer in seconds that Builder will wait & accept incoming transactions before bundling them and submitting as a block. |
| 58 | + pub incoming_transactions_buffer: u64, |
| 59 | + /// Buffer in seconds in which the `submitBlock` transaction must confirm on the Host chain. |
| 60 | + pub block_confirmation_buffer: u64, |
| 61 | + /// Address on Rollup to which Builder will receive user transaction fees. |
| 62 | + pub builder_rewards_address: Address, |
| 63 | + /// Gas limit for RU block. |
| 64 | + /// NOTE: a "smart" builder would determine this programmatically by simulating the block. |
| 65 | + pub rollup_block_gas_limit: u64, |
| 66 | + /// URL of the tx pool to poll for incoming transactions. |
| 67 | + pub tx_pool_url: Cow<'static, str>, |
| 68 | + //// Interval in seconds to poll the tx-pool for new transactions. |
| 69 | + pub tx_pool_poll_interval: u64, |
| 70 | + /// Duration in seconds transactions can live in the tx-pool cache. |
| 71 | + pub tx_pool_cache_duration: u64, |
| 72 | + /// OAuth client ID for the builder. |
| 73 | + pub oauth_client_id: String, |
| 74 | + /// OAuth client secret for the builder. |
| 75 | + pub oauth_client_secret: String, |
| 76 | + /// OAuth authenticate URL for the builder for performing OAuth logins. |
| 77 | + pub oauth_authenticate_url: String, |
| 78 | + /// OAuth token URL for the builder to get an OAuth2 access token |
| 79 | + pub oauth_token_url: String, |
| 80 | + /// OAuth audience for the builder. |
| 81 | + pub oauth_audience: String, |
| 82 | +} |
| 83 | + |
| 84 | +#[derive(Debug, thiserror::Error)] |
| 85 | +pub enum ConfigError { |
| 86 | + /// Error loading from environment variable |
| 87 | + #[error("missing or non-unicode environment variable: {0}")] |
| 88 | + Var(String), |
| 89 | + /// Error parsing environment variable |
| 90 | + #[error("failed to parse environment variable: {0}")] |
| 91 | + Parse(#[from] num::ParseIntError), |
| 92 | + /// Error parsing boolean environment variable |
| 93 | + #[error("failed to parse boolean environment variable")] |
| 94 | + ParseBool, |
| 95 | + /// Error parsing hex from environment variable |
| 96 | + #[error("failed to parse hex: {0}")] |
| 97 | + Hex(#[from] hex::FromHexError), |
| 98 | + /// Error connecting to the provider |
| 99 | + #[error("failed to connect to provider: {0}")] |
| 100 | + Provider(#[from] alloy::transports::TransportError), |
| 101 | + /// Error connecting to the signer |
| 102 | + #[error("failed to connect to signer: {0}")] |
| 103 | + Signer(#[from] SignerError), |
| 104 | +} |
| 105 | + |
| 106 | +impl ConfigError { |
| 107 | + /// Missing or non-unicode env var. |
| 108 | + pub fn missing(s: &str) -> Self { |
| 109 | + ConfigError::Var(s.to_string()) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +/// Provider type used by this transaction |
| 114 | +pub type Provider = FillProvider< |
| 115 | + JoinFill< |
| 116 | + JoinFill< |
| 117 | + Identity, |
| 118 | + JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>, |
| 119 | + >, |
| 120 | + WalletFiller<EthereumWallet>, |
| 121 | + >, |
| 122 | + RootProvider<BoxTransport>, |
| 123 | + BoxTransport, |
| 124 | + Ethereum, |
| 125 | +>; |
| 126 | + |
| 127 | +pub type ZenithInstance = Zenith::ZenithInstance<BoxTransport, Provider>; |
| 128 | + |
| 129 | +impl BuilderConfig { |
| 130 | + /// Load the builder configuration from environment variables. |
| 131 | + pub fn load_from_env() -> Result<BuilderConfig, ConfigError> { |
| 132 | + Ok(BuilderConfig { |
| 133 | + host_chain_id: load_u64(HOST_CHAIN_ID)?, |
| 134 | + ru_chain_id: load_u64(RU_CHAIN_ID)?, |
| 135 | + host_rpc_url: load_url(HOST_RPC_URL)?, |
| 136 | + zenith_address: load_address(ZENITH_ADDRESS)?, |
| 137 | + quincey_url: load_url(QUINCEY_URL)?, |
| 138 | + builder_port: load_u16(BUILDER_PORT)?, |
| 139 | + sequencer_key: load_string_option(SEQUENCER_KEY), |
| 140 | + builder_key: load_string(BUILDER_KEY)?, |
| 141 | + incoming_transactions_buffer: load_u64(INCOMING_TRANSACTIONS_BUFFER)?, |
| 142 | + block_confirmation_buffer: load_u64(BLOCK_CONFIRMATION_BUFFER)?, |
| 143 | + builder_rewards_address: load_address(BUILDER_REWARDS_ADDRESS)?, |
| 144 | + rollup_block_gas_limit: load_u64(ROLLUP_BLOCK_GAS_LIMIT)?, |
| 145 | + tx_pool_url: load_url(TX_POOL_URL)?, |
| 146 | + tx_pool_poll_interval: load_u64(TX_POOL_POLL_INTERVAL)?, |
| 147 | + tx_pool_cache_duration: load_u64(TX_POOL_CACHE_DURATION)?, |
| 148 | + oauth_client_id: load_string(OAUTH_CLIENT_ID)?, |
| 149 | + oauth_client_secret: load_string(OAUTH_CLIENT_SECRET)?, |
| 150 | + oauth_authenticate_url: load_string(OAUTH_AUTHENTICATE_URL)?, |
| 151 | + oauth_token_url: load_string(OAUTH_TOKEN_URL)?, |
| 152 | + oauth_audience: load_string(OAUTH_AUDIENCE)?, |
| 153 | + }) |
| 154 | + } |
| 155 | + |
| 156 | + pub async fn connect_builder_signer(&self) -> Result<LocalOrAws, ConfigError> { |
| 157 | + LocalOrAws::load(&self.builder_key, Some(self.host_chain_id)).await.map_err(Into::into) |
| 158 | + } |
| 159 | + |
| 160 | + pub async fn connect_sequencer_signer(&self) -> Result<Option<LocalOrAws>, ConfigError> { |
| 161 | + match &self.sequencer_key { |
| 162 | + Some(sequencer_key) => LocalOrAws::load(sequencer_key, Some(self.host_chain_id)) |
| 163 | + .await |
| 164 | + .map_err(Into::into) |
| 165 | + .map(Some), |
| 166 | + None => Ok(None), |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + /// Connect to the provider using the configuration. |
| 171 | + pub async fn connect_provider(&self) -> Result<Provider, ConfigError> { |
| 172 | + let builder_signer = self.connect_builder_signer().await?; |
| 173 | + ProviderBuilder::new() |
| 174 | + .with_recommended_fillers() |
| 175 | + .wallet(EthereumWallet::from(builder_signer)) |
| 176 | + .on_builtin(&self.host_rpc_url) |
| 177 | + .await |
| 178 | + .map_err(Into::into) |
| 179 | + } |
| 180 | + |
| 181 | + pub fn connect_zenith(&self, provider: Provider) -> ZenithInstance { |
| 182 | + Zenith::new(self.zenith_address, provider) |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +fn load_string(key: &str) -> Result<String, ConfigError> { |
| 187 | + env::var(key).map_err(|_| ConfigError::missing(key)) |
| 188 | +} |
| 189 | + |
| 190 | +fn load_string_option(key: &str) -> Option<String> { |
| 191 | + load_string(key).ok() |
| 192 | +} |
| 193 | + |
| 194 | +fn load_u64(key: &str) -> Result<u64, ConfigError> { |
| 195 | + let val = load_string(key)?; |
| 196 | + val.parse::<u64>().map_err(Into::into) |
| 197 | +} |
| 198 | + |
| 199 | +fn load_u16(key: &str) -> Result<u16, ConfigError> { |
| 200 | + let val = load_string(key)?; |
| 201 | + val.parse::<u16>().map_err(Into::into) |
| 202 | +} |
| 203 | + |
| 204 | +fn load_url(key: &str) -> Result<Cow<'static, str>, ConfigError> { |
| 205 | + load_string(key).map_err(Into::into).map(Into::into) |
| 206 | +} |
| 207 | + |
| 208 | +fn load_address(key: &str) -> Result<Address, ConfigError> { |
| 209 | + let address = load_string(key)?; |
| 210 | + Address::from_str(&address).map_err(Into::into) |
| 211 | +} |
0 commit comments