diff --git a/.env b/.env index ea1ab030d5..a463143d4c 100644 --- a/.env +++ b/.env @@ -30,10 +30,9 @@ ESPRESSO_SEQUENCER1_API_PORT=24001 ESPRESSO_SEQUENCER2_API_PORT=24002 ESPRESSO_SEQUENCER3_API_PORT=24003 ESPRESSO_SEQUENCER4_API_PORT=24004 -ESPRESSO_SEQUENCER_MAX_BLOCK_SIZE=1mb -ESPRESSO_SEQUENCER_BASE_FEE=1 ESPRESSO_SEQUENCER_URL=http://sequencer0:${ESPRESSO_SEQUENCER_API_PORT} ESPRESSO_SEQUENCER_STORAGE_PATH=/store/sequencer +ESPRESSO_SEQUENCER_GENESIS_FILE=/genesis ESPRESSO_SEQUENCER_L1_PORT=8545 ESPRESSO_SEQUENCER_L1_WS_PORT=8546 ESPRESSO_SEQUENCER_L1_PROVIDER=http://demo-l1-network:${ESPRESSO_SEQUENCER_L1_PORT} @@ -55,7 +54,6 @@ ESPRESSO_DEPLOYER_ACCOUNT_INDEX=9 # Contracts ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS=0xb19b36b1456e65e3a6d514d3f715f204bd59f431 -ESPRESSO_SEQUENCER_FEE_CONTRACT_PROXY_ADDRESS=0xa15bb66138824a1c7167f5e85b957d04dd34e468 ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS=0xe1da8919f262ee86f9be05059c9280142cf23f48 ESPRESSO_SEQUENCER_LIGHTCLIENT_ADDRESS=$ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS @@ -104,6 +102,7 @@ ESPRESSO_BUILDER_INIT_NODE_COUNT=$ESPRESSO_ORCHESTRATOR_NUM_NODES ESPRESSO_BUILDER_BOOTSTRAPPED_VIEW=0 ESPRESSO_BUILDER_WEBSERVER_RESPONSE_TIMEOUT_DURATION=1500ms ESPRESSO_BUILDER_BUFFER_VIEW_NUM_COUNT=50 +ESPRESSO_BUILDER_GENESIS_FILE=$ESPRESSO_SEQUENCER_GENESIS_FILE # Load generator ESPRESSO_SUBMIT_TRANSACTIONS_DELAY=2s diff --git a/builder/src/bin/permissioned-builder.rs b/builder/src/bin/permissioned-builder.rs index 12840b23b3..c35a24e1bc 100644 --- a/builder/src/bin/permissioned-builder.rs +++ b/builder/src/bin/permissioned-builder.rs @@ -10,9 +10,9 @@ use hotshot_types::light_client::StateSignKey; use hotshot_types::signature_key::BLSPrivKey; use hotshot_types::traits::metrics::NoMetrics; use hotshot_types::traits::node_implementation::ConsensusTime; -use sequencer::eth_signature_key::EthKeyPair; use sequencer::persistence::no_storage::NoStorage; -use sequencer::{options::parse_size, BuilderParams, L1Params, NetworkParams}; +use sequencer::{eth_signature_key::EthKeyPair, Genesis}; +use sequencer::{L1Params, NetworkParams}; use snafu::Snafu; use std::net::ToSocketAddrs; use std::num::NonZeroUsize; @@ -21,10 +21,6 @@ use url::Url; #[derive(Parser, Clone, Debug)] pub struct PermissionedBuilderOptions { - /// Unique identifier for this instance of the sequencer network. - #[clap(long, env = "ESPRESSO_SEQUENCER_CHAIN_ID", default_value = "0")] - pub chain_id: u16, - /// URL of the HotShot orchestrator. #[clap( short, @@ -80,6 +76,10 @@ pub struct PermissionedBuilderOptions { )] pub webserver_poll_interval: Duration, + /// Path to TOML file containing genesis state. + #[clap(long, name = "GENESIS_FILE", env = "ESPRESSO_BUILDER_GENESIS_FILE")] + pub genesis_file: PathBuf, + /// Path to file containing private keys. /// /// The file should follow the .env format, with two keys: @@ -129,10 +129,6 @@ pub struct PermissionedBuilderOptions { #[clap(long, env = "ESPRESSO_SEQUENCER_STATE_PEERS", value_delimiter = ',')] pub state_peers: Vec, - /// Maximum size in bytes of a block - #[clap(long, env = "ESPRESSO_SEQUENCER_MAX_BLOCK_SIZE", value_parser = parse_size)] - pub max_block_size: u64, - /// Port to run the builder server on. #[clap(short, long, env = "ESPRESSO_BUILDER_SERVER_PORT")] pub port: u16, @@ -174,10 +170,6 @@ pub struct PermissionedBuilderOptions { /// Whether or not we are a DA node. #[clap(long, env = "ESPRESSO_SEQUENCER_IS_DA", action)] pub is_da: bool, - - /// Base Fee for a block - #[clap(long, env = "ESPRESSO_BUILDER_BLOCK_BASE_FEE", default_value = "0")] - base_fee: u64, } #[derive(Clone, Debug, Snafu)] @@ -226,16 +218,11 @@ async fn main() -> anyhow::Result<()> { let l1_params = L1Params { url: opt.l1_provider_url, - finalized_block: None, events_max_block_range: 10000, }; let builder_key_pair = EthKeyPair::from_mnemonic(&opt.eth_mnemonic, opt.eth_account_index)?; - let builder_params = BuilderParams { - prefunded_accounts: vec![], - }; - // Parse supplied Libp2p addresses to their socket form // We expect all nodes to be reachable via IPv4, so we filter out any IPv6 addresses. // Downstream in HotShot we pin the IP address to v4, but this can be fixed in the future. @@ -277,9 +264,9 @@ async fn main() -> anyhow::Result<()> { // it will internally spawn the builder web server let ctx = init_node( + Genesis::from_file(&opt.genesis_file)?, network_params, &NoMetrics, - builder_params, l1_params, builder_server_url.clone(), builder_key_pair, @@ -291,8 +278,6 @@ async fn main() -> anyhow::Result<()> { buffer_view_num_count, opt.is_da, txn_timeout_duration, - opt.base_fee, - opt.max_block_size, ) .await?; diff --git a/builder/src/bin/permissionless-builder.rs b/builder/src/bin/permissionless-builder.rs index 19c4a1e30b..137b5e4e31 100644 --- a/builder/src/bin/permissionless-builder.rs +++ b/builder/src/bin/permissionless-builder.rs @@ -3,13 +3,12 @@ use builder::non_permissioned::{build_instance_state, BuilderConfig}; use clap::Parser; use cld::ClDuration; use es_version::SEQUENCER_VERSION; -use ethers::types::U256; use hotshot_types::data::ViewNumber; use hotshot_types::traits::node_implementation::ConsensusTime; -use sequencer::{eth_signature_key::EthKeyPair, options::parse_size, ChainConfig, L1Params}; +use sequencer::{eth_signature_key::EthKeyPair, Genesis, L1Params}; use snafu::Snafu; use std::num::NonZeroUsize; -use std::{str::FromStr, time::Duration}; +use std::{path::PathBuf, str::FromStr, time::Duration}; use url::Url; #[derive(Parser, Clone, Debug)] @@ -42,18 +41,6 @@ struct NonPermissionedBuilderOptions { #[clap(long, env = "ESPRESSO_SEQUENCER_STATE_PEERS", value_delimiter = ',')] state_peers: Vec, - /// Unique identifier for this instance of the sequencer network. - #[clap(long, env = "ESPRESSO_SEQUENCER_CHAIN_ID", default_value = "0")] - chain_id: u64, - - /// Maximum size in bytes of a block - #[clap(long, env = "ESPRESSO_SEQUENCER_MAX_BLOCK_SIZE", value_parser = parse_size)] - max_block_size: u64, - - /// Minimum fee in WEI per byte of payload - #[clap(long, env = "ESPRESSO_SEQUENCER_BASE_FEE")] - base_fee: U256, - /// Port to run the builder server on. #[clap(short, long, env = "ESPRESSO_BUILDER_SERVER_PORT")] port: u16, @@ -87,6 +74,10 @@ struct NonPermissionedBuilderOptions { default_value = "15" )] buffer_view_num_count: usize, + + /// Path to TOML file containing genesis state. + #[clap(long, name = "GENESIS_FILE", env = "ESPRESSO_BUILDER_GENESIS_FILE")] + genesis_file: PathBuf, } #[derive(Clone, Debug, Snafu)] @@ -108,12 +99,12 @@ async fn main() -> anyhow::Result<()> { setup_backtrace(); let opt = NonPermissionedBuilderOptions::parse(); + let genesis = Genesis::from_file(&opt.genesis_file)?; let sequencer_version = SEQUENCER_VERSION; let l1_params = L1Params { url: opt.l1_provider_url, - finalized_block: None, events_max_block_range: 10000, }; @@ -122,14 +113,13 @@ async fn main() -> anyhow::Result<()> { let builder_server_url: Url = format!("http://0.0.0.0:{}", opt.port).parse().unwrap(); - let chain_config = ChainConfig { - chain_id: opt.chain_id.into(), - max_block_size: opt.max_block_size, - base_fee: opt.base_fee.into(), - ..Default::default() - }; - let instance_state = - build_instance_state(l1_params, opt.state_peers, chain_config, sequencer_version).unwrap(); + let instance_state = build_instance_state( + genesis.chain_config, + l1_params, + opt.state_peers, + sequencer_version, + ) + .unwrap(); let api_response_timeout_duration = opt.max_api_timeout_duration; diff --git a/builder/src/lib.rs b/builder/src/lib.rs index 980653d93f..48ac4bd71e 100644 --- a/builder/src/lib.rs +++ b/builder/src/lib.rs @@ -54,7 +54,7 @@ use sequencer::{ state::FeeAccount, state::ValidatedState, state_signature::{static_stake_table_commitment, StateSigner}, - BuilderParams, L1Params, NetworkParams, Node, NodeState, PrivKey, PubKey, SeqTypes, + L1Params, NetworkParams, Node, NodeState, PrivKey, PubKey, SeqTypes, }; use std::{alloc::System, any, fmt::Debug, mem}; use std::{marker::PhantomData, net::IpAddr}; diff --git a/builder/src/non_permissioned.rs b/builder/src/non_permissioned.rs index 3918405117..8b3a411c0f 100644 --- a/builder/src/non_permissioned.rs +++ b/builder/src/non_permissioned.rs @@ -34,8 +34,8 @@ use hotshot_types::{ utils::BuilderCommitment, }; use sequencer::{ - catchup::StatePeers, eth_signature_key::EthKeyPair, l1_client::L1Client, BuilderParams, - ChainConfig, L1Params, NetworkParams, NodeState, Payload, PrivKey, PubKey, SeqTypes, + catchup::StatePeers, eth_signature_key::EthKeyPair, l1_client::L1Client, ChainConfig, L1Params, + NetworkParams, NodeState, Payload, PrivKey, PubKey, SeqTypes, }; use hotshot_events_service::{ @@ -58,9 +58,9 @@ pub struct BuilderConfig { } pub fn build_instance_state( + chain_config: ChainConfig, l1_params: L1Params, state_peers: Vec, - chain_config: ChainConfig, _: Ver, ) -> anyhow::Result { let l1_client = L1Client::new(l1_params.url, l1_params.events_max_block_range); @@ -157,8 +157,8 @@ impl BuilderConfig { buffered_view_num_count as u64, maximize_txns_count_timeout_duration, instance_state - .chain_config() - .base_fee + .chain_config + .base_fee() .as_u64() .context("the base fee exceeds the maximum amount that a builder can pay (defined by u64::MAX)")?, Arc::new(instance_state), diff --git a/builder/src/permissioned.rs b/builder/src/permissioned.rs index c36793e37e..3aeb2cadc0 100644 --- a/builder/src/permissioned.rs +++ b/builder/src/permissioned.rs @@ -76,7 +76,7 @@ use sequencer::{ state::FeeAccount, state::ValidatedState, state_signature::{static_stake_table_commitment, StateSigner}, - BuilderParams, L1Params, NetworkParams, Node, NodeState, Payload, PrivKey, PubKey, SeqTypes, + Genesis, L1Params, NetworkParams, Node, NodeState, Payload, PrivKey, PubKey, SeqTypes, }; use std::{alloc::System, any, fmt::Debug, mem}; use std::{marker::PhantomData, net::IpAddr}; @@ -127,9 +127,9 @@ pub struct BuilderContext< #[allow(clippy::too_many_arguments)] pub async fn init_node( + genesis: Genesis, network_params: NetworkParams, metrics: &dyn Metrics, - builder_params: BuilderParams, l1_params: L1Params, hotshot_builder_api_url: Url, eth_key_pair: EthKeyPair, @@ -141,8 +141,6 @@ pub async fn init_node anyhow::Result> { // Orchestrator client let validator_args = ValidatorArgs { @@ -242,22 +240,21 @@ pub async fn init_node::from_urls(network_params.state_peers)), - ); + genesis_state, + l1_genesis: genesis.l1_finalized, + peers: Arc::new(StatePeers::::from_urls(network_params.state_peers)), + node_id: node_index, + }; let stake_table_commit = static_stake_table_commitment(&config.config.known_nodes_with_stake, STAKE_TABLE_CAPACITY); @@ -445,8 +442,8 @@ impl { pub server: SequencerContext, diff --git a/sequencer/src/chain_config.rs b/sequencer/src/chain_config.rs index ae89ae8418..09c03749ab 100644 --- a/sequencer/src/chain_config.rs +++ b/sequencer/src/chain_config.rs @@ -2,7 +2,7 @@ use crate::{ options::parse_size, state::{FeeAccount, FeeAmount}, }; -use clap::Args; +use anyhow::{bail, Context}; use committable::{Commitment, Committable}; use derive_more::{From, Into}; use ethers::types::{Address, U256}; @@ -22,43 +22,48 @@ impl From for ChainId { } } -impl FromStr for ChainId { - type Err = ::Err; - - fn from_str(s: &str) -> Result { - Ok(u64::from_str(s)?.into()) +impl ChainId { + pub fn from_toml(toml: &toml::Value) -> anyhow::Result { + if let Some(s) = toml.as_str() { + if s.starts_with("0x") { + Ok(Self(U256::from_str(s)?)) + } else { + Ok(Self(U256::from_dec_str(s)?)) + } + } else if let Some(n) = toml.as_integer() { + Ok(u64::try_from(n) + .context("must be an unsigned integer")? + .into()) + } else { + bail!("must be an integer or an integral string"); + } } } /// Global variables for an Espresso blockchain. -#[derive(Args, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ChainConfig { /// Espresso chain ID - #[clap(long, env = "ESPRESSO_SEQUENCER_CHAIN_ID", default_value = "0")] pub chain_id: ChainId, + /// Maximum size in bytes of a block - #[clap(long, env = "ESPRESSO_SEQUENCER_MAX_BLOCK_SIZE", default_value = "30mb", value_parser = parse_size)] pub max_block_size: u64, + /// Minimum fee in WEI per byte of payload - #[clap(long, env = "ESPRESSO_SEQUENCER_BASE_FEE", default_value = "0")] pub base_fee: FeeAmount, + /// Fee contract address on L1. /// /// This is optional so that fees can easily be toggled on/off, with no need to deploy a /// contract when they are off. In a future release, after fees are switched on and thoroughly /// tested, this may be made mandatory. - #[clap(long, env = "ESPRESSO_SEQUENCER_FEE_CONTRACT_PROXY_ADDRESS")] pub fee_contract: Option
, + /// Account that receives sequencing fees. /// /// This account in the Espresso fee ledger will always receive every fee paid in Espresso, /// regardless of whether or not their is a `fee_contract` deployed. Once deployed, the fee /// contract can decide what to do with tokens locked in this account in Espresso. - #[clap( - long, - env = "ESPRESSO_SEQUENCER_FEE_RECIPIENT", - default_value = "0x0000000000000000000000000000000000000000" - )] pub fee_recipient: FeeAccount, } @@ -74,6 +79,55 @@ impl Default for ChainConfig { } } +impl ChainConfig { + pub fn from_toml(toml: &toml::Value) -> anyhow::Result { + let cfg = toml.as_table().context("must be table")?; + let chain_id = ChainId::from_toml(cfg.get("chain_id").context("missing chain_id")?) + .context("invalid chain ID")?; + let max_block_size = match cfg + .get("max_block_size") + .context("missing max_block_size")? + { + toml::Value::String(s) => parse_size(s).context("invalid max block size")?, + toml::Value::Integer(n) => (*n) + .try_into() + .context("max_block_size must be an unsigned integer")?, + _ => bail!("max_block_size must be an integer or an integral string"), + }; + let base_fee = FeeAmount::from_toml(cfg.get("base_fee").context("missing base_fee")?) + .context("invalid base fee")?; + let fee_contract = match cfg.get("fee_contract") { + Some(toml::Value::String(s)) => { + Some(s.parse().context("invalid fee_contract address")?) + } + Some(_) => bail!("fee_contract must be an address string"), + None => None, + }; + let fee_recipient = cfg + .get("fee_recipient") + .context("missing fee_recipient")? + .as_str() + .context("fee_recipient must be an address string")? + .parse() + .context("invalid fee_recipient")?; + Ok(Self { + chain_id, + max_block_size, + base_fee, + fee_contract, + fee_recipient, + }) + } + + pub fn max_block_size(&self) -> u64 { + self.max_block_size + } + + pub fn base_fee(&self) -> FeeAmount { + self.base_fee + } +} + impl Committable for ChainConfig { fn tag() -> String { "CHAIN_CONFIG".to_string() diff --git a/sequencer/src/context.rs b/sequencer/src/context.rs index 75bbbe5409..55dfd63426 100644 --- a/sequencer/src/context.rs +++ b/sequencer/src/context.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use async_std::{ sync::{Arc, RwLock}, task::{spawn, JoinHandle}, @@ -72,7 +73,7 @@ impl>, state_relay_server: Option, metrics: &dyn Metrics, - stake_table_capacity: usize, + stake_table_capacity: u64, _: Ver, ) -> anyhow::Result { let pub_key = config.my_own_validator_config.public_key; @@ -111,8 +112,12 @@ impl::new( diff --git a/sequencer/src/genesis.rs b/sequencer/src/genesis.rs new file mode 100644 index 0000000000..69b5d29a04 --- /dev/null +++ b/sequencer/src/genesis.rs @@ -0,0 +1,199 @@ +use crate::{ + l1_client::L1BlockInfo, + state::{FeeAccount, FeeAmount}, + ChainConfig, +}; +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, path::Path, str::FromStr}; + +/// Initial configuration of an Espresso stake table. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct StakeTableConfig { + pub capacity: u64, +} + +/// Genesis of an Espresso chain. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Genesis { + pub chain_config: ChainConfig, + pub accounts: HashMap, + pub l1_finalized: Option, + pub stake_table: StakeTableConfig, +} + +impl Genesis { + pub fn from_file(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + let bytes = std::fs::read(path).context(format!("genesis file {}", path.display()))?; + let text = std::str::from_utf8(&bytes).context("genesis file must be UTF-8")?; + let toml: toml::Value = toml::from_str(text).context("malformed genesis file")?; + Self::from_toml(&toml).context("malformed genesis file") + } + + pub fn from_toml(toml: &toml::Value) -> anyhow::Result { + let genesis = toml.as_table().context("must be a TOML table")?; + let chain_config = ChainConfig::from_toml( + genesis + .get("chain_config") + .context("missing chain_config section")?, + ) + .context("invalid chain config section")?; + let accounts = match toml.get("accounts") { + Some(accounts) => { + let accounts = accounts + .as_table() + .context("accounts section must be a table")?; + accounts + .iter() + .map(|(account, value)| { + Ok(( + FeeAccount::from_str(account) + .context(format!("invalid account {account}"))?, + FeeAmount::from_toml(value) + .context(format!("invalid value for account {account}"))?, + )) + }) + .collect::>()? + } + None => Default::default(), + }; + let l1_finalized = toml + .get("l1_finalized") + .map(|toml| L1BlockInfo::from_toml(toml).context("ivnalid L1 finalized block")) + .transpose()?; + let stake_table = toml::from_str(&toml::to_string( + toml.get("stake_table").context("missing stake_table")?, + )?) + .context("invalid stake table")?; + + Ok(Self { + chain_config, + accounts, + l1_finalized, + stake_table, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use ethers::prelude::{Address, H160, H256}; + use toml::toml; + + #[test] + fn test_genesis_from_toml_with_optional_fields() { + let toml = toml! { + [stake_table] + capacity = 10 + + [chain_config] + chain_id = 12345 + max_block_size = 30000 + base_fee = 1 + fee_recipient = "0x0000000000000000000000000000000000000000" + fee_contract = "0x0000000000000000000000000000000000000000" + + [accounts] + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000 + "0x0000000000000000000000000000000000000000" = 42 + + [l1_finalized] + number = 64 + timestamp = "0x123def" + hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5" + } + .into(); + + let genesis = Genesis::from_toml(&toml).unwrap(); + assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 }); + assert_eq!( + genesis.chain_config, + ChainConfig { + chain_id: 12345.into(), + max_block_size: 30000, + base_fee: 1.into(), + fee_recipient: FeeAccount::default(), + fee_contract: Some(Address::default()) + } + ); + assert_eq!( + genesis.accounts, + [ + ( + FeeAccount::from(H160([ + 0x23, 0x61, 0x8e, 0x81, 0xe3, 0xf5, 0xcd, 0xf7, 0xf5, 0x4c, 0x3d, 0x65, + 0xf7, 0xfb, 0xc0, 0xab, 0xf5, 0xb2, 0x1e, 0x8f + ])), + 100000.into() + ), + (FeeAccount::default(), 42.into()) + ] + .into_iter() + .collect::>() + ); + assert_eq!( + genesis.l1_finalized, + Some(L1BlockInfo { + number: 64, + timestamp: 0x123def.into(), + hash: H256([ + 0x80, 0xf5, 0xdd, 0x11, 0xf2, 0xbd, 0xda, 0x28, 0x14, 0xcb, 0x1a, 0xd9, 0x4e, + 0xf3, 0x0a, 0x47, 0xde, 0x02, 0xcf, 0x28, 0xad, 0x68, 0xc8, 0x9e, 0x10, 0x4c, + 0x00, 0xc4, 0xe5, 0x1b, 0xb7, 0xa5 + ]) + }) + ); + } + + #[test] + fn test_genesis_from_toml_without_optional_fields() { + let toml = toml! { + [stake_table] + capacity = 10 + + [chain_config] + chain_id = 12345 + max_block_size = 30000 + base_fee = 1 + fee_recipient = "0x0000000000000000000000000000000000000000" + } + .into(); + + let genesis = Genesis::from_toml(&toml).unwrap(); + assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 }); + assert_eq!( + genesis.chain_config, + ChainConfig { + chain_id: 12345.into(), + max_block_size: 30000, + base_fee: 1.into(), + fee_recipient: FeeAccount::default(), + fee_contract: None, + } + ); + assert_eq!(genesis.accounts, HashMap::default()); + assert_eq!(genesis.l1_finalized, None); + } + + #[test] + fn test_genesis_from_toml_units() { + let toml = toml! { + [stake_table] + capacity = 10 + + [chain_config] + chain_id = 12345 + max_block_size = "30mb" + base_fee = "1 gwei" + fee_recipient = "0x0000000000000000000000000000000000000000" + } + .into(); + + let genesis = Genesis::from_toml(&toml).unwrap(); + assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 }); + assert_eq!(genesis.chain_config.max_block_size, 30000000); + assert_eq!(genesis.chain_config.base_fee, 1_000_000_000.into()); + } +} diff --git a/sequencer/src/header.rs b/sequencer/src/header.rs index 76676e8e81..7114efb817 100644 --- a/sequencer/src/header.rs +++ b/sequencer/src/header.rs @@ -293,7 +293,7 @@ impl BlockHeader for Header { let mut validated_state = parent_state.clone(); // Fetch the latest L1 snapshot. - let l1_snapshot = instance_state.l1_client().snapshot().await; + let l1_snapshot = instance_state.l1_client.snapshot().await; // Fetch the new L1 deposits between parent and current finalized L1 block. let l1_deposits = if let (Some(addr), Some(block_info)) = (chain_config.fee_contract, l1_snapshot.finalized) diff --git a/sequencer/src/l1_client.rs b/sequencer/src/l1_client.rs index ed127dbe97..cdca5546a0 100644 --- a/sequencer/src/l1_client.rs +++ b/sequencer/src/l1_client.rs @@ -41,6 +41,12 @@ pub struct L1BlockInfo { pub hash: H256, } +impl L1BlockInfo { + pub fn from_toml(toml: &toml::Value) -> anyhow::Result { + Ok(toml::from_str(&toml::to_string(toml)?)?) + } +} + impl PartialOrd for L1BlockInfo { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -279,7 +285,7 @@ mod test { // Test that nothing funky is happening to the provider when // passed along in state. let state = NodeState::mock().with_l1(L1Client::new(anvil.endpoint().parse().unwrap(), 1)); - let version = state.l1_client().provider.client_version().await.unwrap(); + let version = state.l1_client.provider.client_version().await.unwrap(); assert_eq!("anvil/v0.2.0", version); // compare response of underlying provider w/ `get_block_number` diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index 23151d3737..cd69f93ca9 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -4,6 +4,7 @@ pub mod catchup; mod chain_config; pub mod context; pub mod eth_signature_key; +mod genesis; mod header; pub mod hotshot_commitment; pub mod options; @@ -15,7 +16,7 @@ use async_trait::async_trait; use block::entry::TxTableEntryWord; use catchup::{StateCatchup, StatePeers}; use context::SequencerContext; -use ethers::types::{Address, U256}; +use ethers::types::U256; // Should move `STAKE_TABLE_CAPACITY` in the sequencer repo when we have variate stake table support @@ -77,6 +78,7 @@ use hotshot::traits::implementations::{CombinedNetworks, Libp2pNetwork}; pub use block::payload::Payload; pub use chain_config::ChainConfig; +pub use genesis::Genesis; pub use header::Header; pub use l1_client::L1BlockInfo; pub use options::Options; @@ -157,12 +159,12 @@ impl Storage for Arc> { #[derive(Debug, Clone)] pub struct NodeState { - node_id: u64, - chain_config: ChainConfig, - l1_client: L1Client, - peers: Arc, - genesis_state: ValidatedState, - l1_genesis: Option, + pub node_id: u64, + pub chain_config: ChainConfig, + pub l1_client: L1Client, + pub peers: Arc, + pub genesis_state: ValidatedState, + pub l1_genesis: Option, } impl NodeState { @@ -192,10 +194,6 @@ impl NodeState { ) } - pub fn chain_config(&self) -> &ChainConfig { - &self.chain_config - } - pub fn with_l1(mut self, l1_client: L1Client) -> Self { self.l1_client = l1_client; self @@ -210,10 +208,6 @@ impl NodeState { self.chain_config = cfg; self } - - fn l1_client(&self) -> &L1Client { - &self.l1_client - } } // This allows us to turn on `Default` on InstanceState trait @@ -284,27 +278,18 @@ pub struct NetworkParams { pub libp2p_bind_address: SocketAddr, } -#[derive(Clone, Debug)] -pub struct BuilderParams { - pub prefunded_accounts: Vec
, -} - pub struct L1Params { pub url: Url, - pub finalized_block: Option, pub events_max_block_range: u64, } -#[allow(clippy::too_many_arguments)] pub async fn init_node( + genesis: Genesis, network_params: NetworkParams, metrics: &dyn Metrics, persistence_opt: P, - builder_params: BuilderParams, l1_params: L1Params, - stake_table_capacity: usize, bind_version: Ver, - chain_config: ChainConfig, is_da: bool, ) -> anyhow::Result> { // Expose git information via status API. @@ -446,22 +431,17 @@ pub async fn init_node( let _ = NetworkingMetricsValue::new(metrics); let mut genesis_state = ValidatedState::default(); - for address in builder_params.prefunded_accounts { - tracing::info!("Prefunding account {:?} for demo", address); - genesis_state.prefund_account(address.into(), U256::max_value().into()); + for (address, amount) in genesis.accounts { + tracing::info!(%address, %amount, "Prefunding account for demo"); + genesis_state.prefund_account(address, amount); } let l1_client = L1Client::new(l1_params.url, l1_params.events_max_block_range); - let l1_genesis = match l1_params.finalized_block { - Some(block) => Some(l1_client.get_block(block).await?), - None => None, - }; - let instance_state = NodeState { - chain_config, + chain_config: genesis.chain_config, l1_client, genesis_state, - l1_genesis, + l1_genesis: genesis.l1_finalized, peers: catchup::local_and_remote( persistence_opt, StatePeers::::from_urls(network_params.state_peers), @@ -477,7 +457,7 @@ pub async fn init_node( networks, Some(network_params.state_relay_server_url), metrics, - stake_table_capacity, + genesis.stake_table.capacity, bind_version, ) .await?; @@ -524,7 +504,7 @@ pub mod testing { use portpicker::pick_unused_port; use std::time::Duration; - const STAKE_TABLE_CAPACITY_FOR_TEST: usize = 10; + const STAKE_TABLE_CAPACITY_FOR_TEST: u64 = 10; pub async fn run_test_builder() -> (Option>>, Url) { >::start( @@ -648,7 +628,7 @@ pub mod testing { persistence_opt: P, catchup: impl StateCatchup + 'static, metrics: &dyn Metrics, - stake_table_capacity: usize, + stake_table_capacity: u64, bind_version: Ver, ) -> SequencerContext { let mut config = self.config.clone(); diff --git a/sequencer/src/main.rs b/sequencer/src/main.rs index 2cc3d8e463..51d19cb2a7 100644 --- a/sequencer/src/main.rs +++ b/sequencer/src/main.rs @@ -9,7 +9,7 @@ use sequencer::{ api::{self, data_source::DataSourceOptions}, init_node, options::{Modules, Options}, - persistence, BuilderParams, L1Params, NetworkParams, + persistence, Genesis, L1Params, NetworkParams, }; use vbs::version::StaticVersionType; @@ -48,16 +48,14 @@ async fn init_with_storage( where S: DataSourceOptions, { + let genesis = Genesis::from_file(&opt.genesis_file)?; + tracing::info!(?genesis, "genesis"); + let (private_staking_key, private_state_key) = opt.private_keys()?; - let stake_table_capacity = opt.stake_table_capacity; let l1_params = L1Params { url: opt.l1_provider_url, - finalized_block: opt.l1_genesis, events_max_block_range: opt.l1_events_max_block_range, }; - let builder_params = BuilderParams { - prefunded_accounts: opt.prefunded_builder_accounts, - }; // Parse supplied Libp2p addresses to their socket form // We expect all nodes to be reachable via IPv4, so we filter out any IPv6 addresses. @@ -120,14 +118,12 @@ where move |metrics| { async move { init_node( + genesis, network_params, &*metrics, storage_opt, - builder_params, l1_params, - stake_table_capacity, bind_version, - opt.chain_config, opt.is_da, ) .await @@ -141,14 +137,12 @@ where } None => { init_node( + genesis, network_params, &NoMetrics, storage_opt, - builder_params, l1_params, - stake_table_capacity, bind_version, - opt.chain_config, opt.is_da, ) .await? diff --git a/sequencer/src/options.rs b/sequencer/src/options.rs index 488314e06d..7a993f87e5 100644 --- a/sequencer/src/options.rs +++ b/sequencer/src/options.rs @@ -1,4 +1,4 @@ -use crate::{api, persistence, ChainConfig}; +use crate::{api, persistence}; use anyhow::{bail, Context}; use bytesize::ByteSize; use clap::{error::ErrorKind, Args, FromArgMatches, Parser}; @@ -6,8 +6,6 @@ use cld::ClDuration; use core::fmt::Display; use derivative::Derivative; use derive_more::From; -use ethers::types::Address; -use hotshot_stake_table::config::STAKE_TABLE_CAPACITY; use hotshot_types::light_client::StateSignKey; use hotshot_types::signature_key::BLSPrivKey; use snafu::Snafu; @@ -89,6 +87,10 @@ pub struct Options { #[derivative(Debug(format_with = "Display::fmt"))] pub state_relay_server_url: Url, + /// Path to TOML file containing genesis state. + #[clap(long, name = "GENESIS_FILE", env = "ESPRESSO_SEQUENCER_GENESIS_FILE")] + pub genesis_file: PathBuf, + /// Path to file containing private keys. /// /// The file should follow the .env format, with two keys: @@ -137,25 +139,11 @@ pub struct Options { #[clap(raw = true)] modules: Vec, - /// Prefunded the builder accounts. Use for demo purposes only. - /// - /// Comma-separated list of Ethereum addresses. - #[clap( - long, - env = "ESPRESSO_SEQUENCER_PREFUNDED_BUILDER_ACCOUNTS", - value_delimiter = ',' - )] - pub prefunded_builder_accounts: Vec
, - /// Url we will use for RPC communication with L1. #[clap(long, env = "ESPRESSO_SEQUENCER_L1_PROVIDER")] #[derivative(Debug(format_with = "Display::fmt"))] pub l1_provider_url: Url, - /// L1 block number from which to start the Espresso chain. - #[clap(long, env = "ESPRESSO_SEQUENCER_L1_GENESIS")] - pub l1_genesis: Option, - /// Maximum number of L1 blocks that can be scanned for events in a single query. #[clap( long, @@ -172,13 +160,6 @@ pub struct Options { #[clap(long, env = "ESPRESSO_SEQUENCER_STATE_PEERS", value_delimiter = ',')] #[derivative(Debug(format_with = "fmt_urls"))] pub state_peers: Vec, - - /// Stake table capacity for the prover circuit - #[clap(long, env = "ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY", default_value_t = STAKE_TABLE_CAPACITY)] - pub stake_table_capacity: usize, - - #[clap(flatten)] - pub chain_config: ChainConfig, } impl Options { diff --git a/sequencer/src/state.rs b/sequencer/src/state.rs index fac937ae10..b4c13394f6 100644 --- a/sequencer/src/state.rs +++ b/sequencer/src/state.rs @@ -568,7 +568,7 @@ impl ValidatedState { let missing_accounts = self.forgotten_accounts( [ proposed_header.fee_info.account, - instance.chain_config().fee_recipient, + instance.chain_config.fee_recipient, ] .into_iter() .chain(l1_deposits.iter().map(|fee_info| fee_info.account)), @@ -633,7 +633,7 @@ impl ValidatedState { &mut validated_state, &mut delta, proposed_header.fee_info, - instance.chain_config().fee_recipient, + instance.chain_config.fee_recipient, )?; Ok((validated_state, delta)) @@ -972,6 +972,43 @@ impl FeeAmount { None } } + + pub fn from_toml(toml: &toml::Value) -> anyhow::Result { + match toml { + toml::Value::String(s) => { + // Interpret the integer as hex if the string starts with 0x. + let (s, hex) = match s.strip_prefix("0x") { + Some(s) => (s, true), + None => (s.as_str(), false), + }; + // Strip an optional non-numeric suffix, which will be interpreted as a unit. + let (s, multiplier) = match s.split_once(char::is_whitespace) { + Some((s, unit)) => { + let multiplier = match unit.to_lowercase().as_str() { + "wei" => 1u64, + "gwei" => 1_000_000_000, + "eth" | "ether" => 1_000_000_000_000_000_000, + unit => bail!("unrecognized unit {unit}"), + }; + (s, multiplier) + } + None => (s, 1), + }; + // Parse the base amount as an integer. + let base = if hex { + s.parse()? + } else { + U256::from_dec_str(s)? + }; + + Ok(Self(base * multiplier)) + } + toml::Value::Integer(n) => Ok(u64::try_from(*n) + .context("must be an unsigned integer")? + .into()), + _ => bail!("must be an integer or an integral string"), + } + } } // New Type for `Address` in order to implement `CanonicalSerialize` and