diff --git a/sequencer/src/genesis.rs b/sequencer/src/genesis.rs
index c71ee7deeb..03f0c1b46e 100644
--- a/sequencer/src/genesis.rs
+++ b/sequencer/src/genesis.rs
@@ -4,13 +4,13 @@ use std::{
};
use alloy::primitives::Address;
-use anyhow::{Context, Ok};
+use anyhow::{ensure, Context, Ok};
use espresso_types::{
- v0_3::ChainConfig, FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, Timestamp,
- Upgrade,
+ v0_3::ChainConfig, EpochVersion, FeeAccount, FeeAmount, FeeVersion, GenesisHeader, L1BlockInfo,
+ L1Client, Timestamp, Upgrade,
};
use serde::{Deserialize, Serialize};
-use vbs::version::Version;
+use vbs::version::{StaticVersionType, Version};
/// Initial configuration of an Espresso stake table.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
@@ -131,6 +131,87 @@ impl Genesis {
// TODO: it's optional for the fee contract to be included in a proxy in v1 so no need to panic but revisit this after v1 https://github.com/EspressoSystems/espresso-sequencer/pull/2000#discussion_r1765174702
Ok(())
}
+
+ pub async fn validate(&self, l1: &L1Client) -> anyhow::Result<()> {
+ self.validate_fee_contract(l1).await?;
+ self.validate_base_version_config()?;
+ self.validate_upgrades_config()?;
+ Ok(())
+ }
+
+ fn validate_base_version_config(&self) -> anyhow::Result<()> {
+ if self.base_version >= FeeVersion::version() {
+ Self::validate_address(
+ self.chain_config.fee_contract,
+ "fee_contract",
+ "base_version",
+ &self.base_version,
+ )?;
+ }
+
+ if self.base_version >= EpochVersion::version() {
+ ensure!(
+ self.epoch_height.is_some(),
+ "epoch_height must be provided for base_version {} and above",
+ self.base_version
+ );
+
+ ensure!(
+ self.epoch_start_block.is_some(),
+ "epoch_start_block must be provided for base_version {} and above",
+ self.base_version
+ );
+
+ Self::validate_address(
+ self.chain_config.stake_table_contract,
+ "stake_table_contract",
+ "base_version",
+ &self.base_version,
+ )?;
+ }
+
+ Ok(())
+ }
+
+ fn validate_upgrades_config(&self) -> anyhow::Result<()> {
+ for (version, upgrade) in &self.upgrades {
+ let chain_config = upgrade.upgrade_type.chain_config();
+
+ if *version >= FeeVersion::version() {
+ Self::validate_address(
+ chain_config.and_then(|c| c.fee_contract),
+ "fee_contract",
+ "upgrade version",
+ version,
+ )?;
+ }
+
+ if *version >= EpochVersion::version() {
+ Self::validate_address(
+ chain_config.and_then(|c| c.stake_table_contract),
+ "stake_table_contract",
+ "upgrade version",
+ version,
+ )?;
+ }
+ }
+ Ok(())
+ }
+
+ fn validate_address(
+ opt: Option
,
+ field: &str,
+ kind: &str,
+ version: &Version,
+ ) -> anyhow::Result<()> {
+ match opt {
+ Some(addr) if addr == Address::default() => {
+ anyhow::bail!("{field} cannot be the zero address in {kind} {version} and above")
+ },
+ Some(_) => Ok(()),
+ None => anyhow::bail!("{field} must be provided in {kind} {version} and above"),
+ }
+ }
}
mod version_ser {
@@ -1130,4 +1211,123 @@ mod test {
toml::from_str::(&toml).unwrap();
}
+
+ fn mock_genesis() -> Genesis {
+ Genesis {
+ base_version: Version { major: 0, minor: 0 },
+ upgrade_version: Version { major: 0, minor: 0 },
+ genesis_version: Version { major: 0, minor: 0 },
+ epoch_height: None,
+ drb_difficulty: None,
+ drb_upgrade_difficulty: None,
+ epoch_start_block: None,
+ stake_table_capacity: None,
+ chain_config: Default::default(),
+ stake_table: StakeTableConfig { capacity: 10 },
+ accounts: HashMap::new(),
+ l1_finalized: L1Finalized::Number { number: 0 },
+ header: GenesisHeader::default(),
+ upgrades: BTreeMap::new(),
+ }
+ }
+
+ #[test]
+ fn test_base_version_fee_contract_missing_should_fail() {
+ let mut genesis = mock_genesis();
+ genesis.base_version = FeeVersion::version();
+ genesis.chain_config.fee_contract = None;
+
+ assert!(genesis.validate_base_version_config().is_err())
+ }
+
+ #[test]
+ fn test_base_version_fee_contract_zero_should_fail() {
+ let mut genesis = mock_genesis();
+ genesis.base_version = FeeVersion::version();
+ genesis.chain_config.fee_contract = Some(Address::default());
+
+ assert!(genesis.validate_base_version_config().is_err());
+ }
+
+ #[test]
+ fn test_base_version_fee_contract_ok() {
+ let mut genesis = mock_genesis();
+ genesis.base_version = FeeVersion::version();
+ genesis.chain_config.fee_contract = Some(Address::random());
+
+ assert!(genesis.validate_base_version_config().is_ok());
+ }
+
+ #[test]
+ fn test_base_version_epoch_fields_missing_should_fail() {
+ let mut genesis = mock_genesis();
+ genesis.base_version = EpochVersion::version();
+ genesis.chain_config.stake_table_contract = Some(Address::random());
+
+ assert!(genesis.validate_base_version_config().is_err());
+ }
+
+ #[test]
+ fn test_base_version_epoch_fields_ok() {
+ let mut genesis = mock_genesis();
+ genesis.base_version = EpochVersion::version();
+ genesis.epoch_height = Some(10);
+ genesis.epoch_start_block = Some(5);
+ genesis.chain_config.stake_table_contract = Some(Address::random());
+ genesis.chain_config.fee_contract = Some(Address::random());
+
+ assert!(genesis.validate_base_version_config().is_ok());
+ }
+
+ #[test]
+ fn test_upgrade_with_zero_stake_table_should_fail() {
+ let mut genesis = mock_genesis();
+ genesis.upgrades.insert(
+ EpochVersion::version(),
+ Upgrade {
+ mode: UpgradeMode::View(ViewBasedUpgrade {
+ start_proposing_view: 0,
+ stop_proposing_view: 0,
+ start_voting_view: None,
+ stop_voting_view: None,
+ }),
+ upgrade_type: UpgradeType::Epoch {
+ chain_config: ChainConfig {
+ base_fee: 1.into(),
+ fee_contract: Some(Address::random()),
+ stake_table_contract: Some(Address::default()),
+ ..Default::default()
+ },
+ },
+ },
+ );
+
+ assert!(genesis.validate_upgrades_config().is_err());
+ }
+
+ #[test]
+ fn test_upgrade_with_valid_stake_table_should_pass() {
+ let mut genesis = mock_genesis();
+ genesis.upgrades.insert(
+ EpochVersion::version(),
+ Upgrade {
+ mode: UpgradeMode::View(ViewBasedUpgrade {
+ start_proposing_view: 0,
+ stop_proposing_view: 0,
+ start_voting_view: None,
+ stop_voting_view: None,
+ }),
+ upgrade_type: UpgradeType::Epoch {
+ chain_config: ChainConfig {
+ base_fee: 1.into(),
+ fee_contract: Some(Address::random()),
+ stake_table_contract: Some(Address::random()),
+ ..Default::default()
+ },
+ },
+ },
+ );
+
+ assert!(genesis.validate_upgrades_config().is_ok());
+ }
}
diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs
index 3121de2db5..7fdeecfac4 100644
--- a/sequencer/src/lib.rs
+++ b/sequencer/src/lib.rs
@@ -467,7 +467,7 @@ where
.with_metrics(metrics)
.connect(l1_params.urls)
.with_context(|| "failed to create L1 client")?;
- genesis.validate_fee_contract(&l1_client).await?;
+ genesis.validate(&l1_client).await?;
l1_client.spawn_tasks().await;
let l1_genesis = match genesis.l1_finalized {
diff --git a/sequencer/src/restart_tests.rs b/sequencer/src/restart_tests.rs
index 6f88d31af2..173bae8cd6 100755
--- a/sequencer/src/restart_tests.rs
+++ b/sequencer/src/restart_tests.rs
@@ -647,7 +647,7 @@ impl TestNetwork {
let genesis_file_path = tmp.path().join("genesis.toml");
let mut genesis = Genesis {
- chain_config: Default::default(),
+ chain_config: ChainConfig::default(),
// TODO we apparently have two `capacity` configurations
stake_table: StakeTableConfig {
capacity: STAKE_TABLE_CAPACITY_FOR_TEST,
@@ -742,12 +742,13 @@ impl TestNetwork {
};
// Deploy stake contracts and delegate.
- let stake_table_address = network.deploy(&genesis).await.unwrap();
+ let (fee_proxy, stake_table_address) = network.deploy(&genesis).await.unwrap();
// Add contract address to `ChainConfig`.
let chain_config = ChainConfig {
base_fee: 1.into(),
stake_table_contract: Some(stake_table_address),
+ fee_contract: Some(fee_proxy),
..Default::default()
};
genesis.chain_config = chain_config;
@@ -783,7 +784,7 @@ impl TestNetwork {
}
/// Deploy stake contracts and delegate.
- async fn deploy(&self, genesis: &Genesis) -> anyhow::Result {
+ async fn deploy(&self, genesis: &Genesis) -> anyhow::Result<(Address, Address)> {
let stake_table_version = StakeTableContractVersion::V2;
let delegation_config = DelegationConfig::EqualAmounts;
@@ -857,6 +858,9 @@ impl TestNetwork {
}
.context("failed to deploy contracts")?;
+ let fee = contracts
+ .address(Contract::FeeContractProxy)
+ .expect("FeeContractProxy address not found ");
let stake_table_address = contracts
.address(Contract::StakeTableProxy)
.expect("StakeTableProxy address not found");
@@ -881,7 +885,7 @@ impl TestNetwork {
.await
.expect("interval mining");
- Ok(stake_table_address)
+ Ok((fee, stake_table_address))
}
async fn wait_for_epoch(&self) {
diff --git a/types/src/v0/impls/instance_state.rs b/types/src/v0/impls/instance_state.rs
index 8acd22a20f..db24c5e95b 100644
--- a/types/src/v0/impls/instance_state.rs
+++ b/types/src/v0/impls/instance_state.rs
@@ -208,7 +208,7 @@ impl NodeState {
Arc::new(mock::MockStateCatchup::default()),
StaticVersion::<0, 2>::version(),
coordinator,
- Version { major: 0, minor: 1 },
+ Version { major: 0, minor: 2 },
)
}
@@ -238,7 +238,7 @@ impl NodeState {
mock::MockStateCatchup::default(),
StaticVersion::<0, 3>::version(),
coordinator,
- Version { major: 0, minor: 1 },
+ Version { major: 0, minor: 3 },
)
}