diff --git a/data/genesis/demo.toml b/data/genesis/demo.toml index 5c52dc3861..ec85c2bf65 100644 --- a/data/genesis/demo.toml +++ b/data/genesis/demo.toml @@ -13,9 +13,8 @@ timestamp = "1970-01-01T00:00:00Z" [[upgrade]] version = "0.2" -view = 5 -propose_window = 10 - +start_proposing_view = 5 +stop_proposing_view = 15 [upgrade.chain_config] chain_id = 999999999 diff --git a/sequencer/api/migrations/V36__alter_merkle_root_column_expressions.sql b/sequencer/api/migrations/V36__alter_merkle_root_column_expressions.sql new file mode 100644 index 0000000000..ff5bcf8eea --- /dev/null +++ b/sequencer/api/migrations/V36__alter_merkle_root_column_expressions.sql @@ -0,0 +1,18 @@ +-- The generated columns for header merkle roots were originally created by extracting fields +-- `block_merkle_tree_root` and `fee_merkle_tree_root` from the header JSON. Post 0.1, though, the +-- header serialization changed so that these fields are now nested one level deeper: +-- `fields.block_merkle_tree_root` and `fields.fee_merkle_tree_root`. This migration alters the +-- generated column expression to use NULL coalescing to extract the value from either of these +-- paths depending on which version of the header we have. +-- +-- Pre 17.x (we target Postgres >= 16.x), there is not explicit instruction for changing the +-- expression of a generated column, so the best we can do is drop and re-add the column with a +-- different expression. + +ALTER TABLE header + DROP column block_merkle_tree_root, + ADD column block_merkle_tree_root text + GENERATED ALWAYS AS (coalesce(data->'fields'->>'block_merkle_tree_root', data->>'block_merkle_tree_root')) STORED NOT NULL, + DROP column fee_merkle_tree_root, + ADD column fee_merkle_tree_root text + GENERATED ALWAYS AS (coalesce(data->'fields'->>'fee_merkle_tree_root', data->>'fee_merkle_tree_root')) STORED NOT NULL; diff --git a/sequencer/src/api.rs b/sequencer/src/api.rs index 3674abf4fd..221418f5cb 100644 --- a/sequencer/src/api.rs +++ b/sequencer/src/api.rs @@ -351,7 +351,7 @@ impl, Ver: StaticVersionType + 'static, P: Sequencer #[cfg(any(test, feature = "testing"))] pub mod test_helpers { - use std::{collections::BTreeMap, time::Duration}; + use std::time::Duration; use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use async_std::task::sleep; @@ -360,7 +360,7 @@ pub mod test_helpers { use espresso_types::{ mock::MockStateCatchup, v0::traits::{PersistenceOptions, StateCatchup}, - NamespaceId, Upgrade, ValidatedState, + NamespaceId, ValidatedState, }; use ethers::{prelude::Address, utils::Anvil}; use futures::{ @@ -378,7 +378,6 @@ pub mod test_helpers { use portpicker::pick_unused_port; use surf_disco::Client; use tide_disco::error::ServerError; - use vbs::version::Version; use super::*; use crate::{ @@ -503,15 +502,6 @@ pub mod test_helpers { } } - #[derive(Clone, Debug)] - pub struct TestNetworkUpgrades { - pub upgrades: BTreeMap, - pub start_proposing_view: u64, - pub stop_proposing_view: u64, - pub start_voting_view: u64, - pub stop_voting_view: u64, - } - impl TestNetwork { pub async fn new( cfg: TestNetworkConfig<{ NUM_NODES }, P, C>, @@ -529,7 +519,7 @@ pub mod test_helpers { .map(|(i, (state, persistence, catchup))| { let opt = cfg.api_config.clone(); let cfg = &cfg.network_config; - let upgrades_map = cfg.upgrades().map(|e| e.upgrades).unwrap_or_default(); + let upgrades_map = cfg.upgrades(); async move { if i == 0 { opt.serve( @@ -1049,7 +1039,9 @@ mod test { use committable::{Commitment, Committable}; use es_version::{SequencerVersion, SEQUENCER_VERSION}; use espresso_types::{ - mock::MockStateCatchup, FeeAccount, FeeAmount, Header, Upgrade, UpgradeType, ValidatedState, + mock::MockStateCatchup, + v0_1::{UpgradeMode, ViewBasedUpgrade}, + FeeAccount, FeeAmount, Header, Upgrade, UpgradeType, ValidatedState, }; use ethers::utils::Anvil; use futures::{ @@ -1063,14 +1055,17 @@ mod test { }; use hotshot_types::{ event::LeafInfo, - traits::{metrics::NoMetrics, node_implementation::ConsensusTime}, + traits::{ + metrics::NoMetrics, + node_implementation::{ConsensusTime, NodeType}, + }, }; use jf_merkle_tree::prelude::{MerkleProof, Sha3Node}; use portpicker::pick_unused_port; use surf_disco::Client; use test_helpers::{ catchup_test_helper, state_signature_test_helper, status_test_helper, submit_test_helper, - TestNetwork, TestNetworkConfigBuilder, TestNetworkUpgrades, + TestNetwork, TestNetworkConfigBuilder, }; use tide_disco::{app::AppHealth, error::ServerError, healthcheck::HealthStatus}; use vbs::version::Version; @@ -1446,28 +1441,24 @@ mod test { base_fee: 1.into(), ..Default::default() }; - let mut map = std::collections::BTreeMap::new(); - let start_proposing_view = 5; - let propose_window = 10; - map.insert( - Version { major: 0, minor: 2 }, + let mut upgrades = std::collections::BTreeMap::new(); + + upgrades.insert( + ::Upgrade::VERSION, Upgrade { - start_proposing_view, - propose_window, + mode: UpgradeMode::View(ViewBasedUpgrade { + start_voting_view: None, + stop_voting_view: None, + start_proposing_view: 1, + stop_proposing_view: 10, + }), upgrade_type: UpgradeType::ChainConfig { chain_config: chain_config_upgrade, }, }, ); - let stop_voting_view = 100; - let upgrades = TestNetworkUpgrades { - upgrades: map, - start_proposing_view, - stop_proposing_view: start_proposing_view + propose_window, - start_voting_view: 1, - stop_voting_view, - }; + let stop_voting_view = u64::MAX; const NUM_NODES: usize = 5; let config = TestNetworkConfigBuilder::::with_num_nodes() diff --git a/sequencer/src/genesis.rs b/sequencer/src/genesis.rs index 8ad111a66a..59941ae595 100644 --- a/sequencer/src/genesis.rs +++ b/sequencer/src/genesis.rs @@ -51,11 +51,14 @@ mod upgrade_serialization { use std::{collections::BTreeMap, fmt}; - use espresso_types::{Upgrade, UpgradeType}; + use espresso_types::{ + v0_1::{TimeBasedUpgrade, UpgradeMode, ViewBasedUpgrade}, + Upgrade, UpgradeType, + }; use serde::{ - de::{SeqAccess, Visitor}, + de::{self, SeqAccess, Visitor}, ser::SerializeSeq, - Deserialize, Deserializer, Serializer, + Deserialize, Deserializer, Serialize, Serializer, }; use vbs::version::Version; @@ -63,14 +66,22 @@ mod upgrade_serialization { where S: Serializer, { + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct Fields { + pub version: String, + #[serde(flatten)] + pub mode: UpgradeMode, + #[serde(flatten)] + pub upgrade_type: UpgradeType, + } + let mut seq = serializer.serialize_seq(Some(map.len()))?; for (version, upgrade) in map { - seq.serialize_element(&( - version.to_string(), - upgrade.start_proposing_view, - upgrade.propose_window, - upgrade.upgrade_type.clone(), - ))?; + seq.serialize_element(&Fields { + version: version.to_string(), + mode: upgrade.mode.clone(), + upgrade_type: upgrade.upgrade_type.clone(), + })? } seq.end() } @@ -81,6 +92,20 @@ mod upgrade_serialization { { struct VecToHashMap; + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct Fields { + pub version: String, + // If both `time_based` and `view_based` fields are provided + // and we use an enum for deserialization, then one of the variant fields will be ignored. + // We want to raise an error in such a case to avoid ambiguity + #[serde(flatten)] + pub time_based: Option, + #[serde(flatten)] + pub view_based: Option, + #[serde(flatten)] + pub upgrade_type: UpgradeType, + } + impl<'de> Visitor<'de> for VecToHashMap { type Value = BTreeMap; @@ -94,16 +119,7 @@ mod upgrade_serialization { { let mut map = BTreeMap::new(); - #[derive(Deserialize)] - struct UpgradeFields { - version: String, - view: u64, - propose_window: u64, - #[serde(flatten)] - upgrade_type: UpgradeType, - } - - while let Some(fields) = seq.next_element::()? { + while let Some(fields) = seq.next_element::()? { // add try_from in Version let version: Vec<_> = fields.version.split('.').collect(); @@ -112,14 +128,50 @@ mod upgrade_serialization { minor: version[1].parse().expect("invalid version"), }; - map.insert( - version, - Upgrade { - start_proposing_view: fields.view, - propose_window: fields.propose_window, - upgrade_type: fields.upgrade_type, - }, - ); + match (fields.time_based, fields.view_based) { + (Some(_), Some(_)) => { + return Err(de::Error::custom( + "both view and time mode parameters are set", + )) + } + (None, None) => { + return Err(de::Error::custom( + "no view or time mode parameters provided", + )) + } + (None, Some(v)) => { + if v.start_proposing_view > v.stop_proposing_view { + return Err(de::Error::custom( + "stop_proposing_view is less than start_proposing_view", + )); + } + + map.insert( + version, + Upgrade { + mode: UpgradeMode::View(v), + upgrade_type: fields.upgrade_type, + }, + ); + } + (Some(t), None) => { + if t.start_proposing_time.unix_timestamp() + > t.stop_proposing_time.unix_timestamp() + { + return Err(de::Error::custom( + "stop_proposing_time is less than start_proposing_time", + )); + } + + map.insert( + version, + Upgrade { + mode: UpgradeMode::Time(t), + upgrade_type: fields.upgrade_type.clone(), + }, + ); + } + } } Ok(map) @@ -148,7 +200,9 @@ impl Genesis { #[cfg(test)] mod test { - use espresso_types::{L1BlockInfo, Timestamp}; + use espresso_types::{ + L1BlockInfo, TimeBasedUpgrade, Timestamp, UpgradeMode, UpgradeType, ViewBasedUpgrade, + }; use ethers::prelude::{Address, H160, H256}; use sequencer_utils::ser::FromStringOrInteger; use toml::toml; @@ -179,18 +233,6 @@ mod test { number = 64 timestamp = "0x123def" hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5" - - [[upgrade]] - version = "1.0" - view = 1 - propose_window = 10 - - [upgrade.chain_config] - chain_id = 12345 - max_block_size = 30000 - base_fee = 1 - fee_recipient = "0x0000000000000000000000000000000000000000" - fee_contract = "0x0000000000000000000000000000000000000000" } .to_string(); @@ -335,4 +377,176 @@ mod test { } ) } + + #[test] + fn test_genesis_toml_upgrade_view_mode() { + // without optional fields + // with view settings + 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" + + [header] + timestamp = 123456 + + [accounts] + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000 + "0x0000000000000000000000000000000000000000" = 42 + + [l1_finalized] + number = 64 + timestamp = "0x123def" + hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5" + + [[upgrade]] + version = "0.2" + start_proposing_view = 1 + stop_proposing_view = 15 + + [upgrade.chain_config] + chain_id = 12345 + max_block_size = 30000 + base_fee = 1 + fee_recipient = "0x0000000000000000000000000000000000000000" + fee_contract = "0x0000000000000000000000000000000000000000" + } + .to_string(); + + let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}")); + + let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap(); + + assert_eq!(*version, Version { major: 0, minor: 2 }); + + let upgrade = Upgrade { + mode: UpgradeMode::View(ViewBasedUpgrade { + start_voting_view: None, + stop_voting_view: None, + start_proposing_view: 1, + stop_proposing_view: 15, + }), + upgrade_type: UpgradeType::ChainConfig { + chain_config: genesis.chain_config, + }, + }; + + assert_eq!(*genesis_upgrade, upgrade); + } + + #[test] + fn test_genesis_toml_upgrade_time_mode() { + // without optional fields + // with time settings + 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" + + [header] + timestamp = 123456 + + [accounts] + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000 + "0x0000000000000000000000000000000000000000" = 42 + + [l1_finalized] + number = 64 + timestamp = "0x123def" + hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5" + + [[upgrade]] + version = "0.2" + start_proposing_time = "2024-01-01T00:00:00Z" + stop_proposing_time = "2024-01-02T00:00:00Z" + + [upgrade.chain_config] + chain_id = 12345 + max_block_size = 30000 + base_fee = 1 + fee_recipient = "0x0000000000000000000000000000000000000000" + fee_contract = "0x0000000000000000000000000000000000000000" + } + .to_string(); + + let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}")); + + let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap(); + + assert_eq!(*version, Version { major: 0, minor: 2 }); + + let upgrade = Upgrade { + mode: UpgradeMode::Time(TimeBasedUpgrade { + start_voting_time: None, + stop_voting_time: None, + start_proposing_time: Timestamp::from_string("2024-01-01T00:00:00Z".to_string()) + .unwrap(), + stop_proposing_time: Timestamp::from_string("2024-01-02T00:00:00Z".to_string()) + .unwrap(), + }), + upgrade_type: UpgradeType::ChainConfig { + chain_config: genesis.chain_config, + }, + }; + + assert_eq!(*genesis_upgrade, upgrade); + } + + #[test] + fn test_genesis_toml_upgrade_view_and_time_mode() { + // set both time and view parameters + // this should err + 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" + + [header] + timestamp = 123456 + + [accounts] + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000 + "0x0000000000000000000000000000000000000000" = 42 + + [l1_finalized] + number = 64 + timestamp = "0x123def" + hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5" + + [[upgrade]] + version = "0.2" + start_proposing_view = 1 + stop_proposing_view = 10 + start_proposing_time = 1 + stop_proposing_time = 10 + + [upgrade.chain_config] + chain_id = 12345 + max_block_size = 30000 + base_fee = 1 + fee_recipient = "0x0000000000000000000000000000000000000000" + fee_contract = "0x0000000000000000000000000000000000000000" + } + .to_string(); + + toml::from_str::(&toml).unwrap_err(); + } } diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index e9206cdd76..7cfefdb5ae 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -204,11 +204,7 @@ pub async fn init_node( .upgrades .get(&::Upgrade::VERSION) { - let view = upgrade.start_proposing_view; - config.config.start_proposing_view = view; - config.config.stop_proposing_view = view + upgrade.propose_window; - config.config.start_voting_view = 1; - config.config.stop_voting_view = u64::MAX; + upgrade.set_hotshot_config_parameters(&mut config.config); } // If the `Libp2p` bootstrap nodes were supplied via the command line, override those @@ -356,7 +352,6 @@ pub fn empty_builder_commitment() -> BuilderCommitment { pub mod testing { use std::{collections::HashMap, time::Duration}; - use api::test_helpers::TestNetworkUpgrades; use committable::Committable; use espresso_types::{ eth_signature_key::EthKeyPair, @@ -423,7 +418,7 @@ pub mod testing { l1_url: Url, state_relay_url: Option, builder_port: Option, - upgrades: Option, + upgrades: BTreeMap, } impl TestConfigBuilder { @@ -442,21 +437,14 @@ pub mod testing { self } - pub fn upgrades(mut self, upgrades: TestNetworkUpgrades) -> Self { - self.upgrades = Some(upgrades); + pub fn upgrades(mut self, upgrades: BTreeMap) -> Self { + self.upgrades = upgrades; self } pub fn build(mut self) -> TestConfig { - if let Some(upgrades) = &self.upgrades { - self.config.start_proposing_view = upgrades.start_proposing_view; - self.config.stop_proposing_view = upgrades.stop_proposing_view; - self.config.start_voting_view = upgrades.start_voting_view; - self.config.stop_voting_view = upgrades.stop_voting_view; - self.config.start_proposing_time = 0; - self.config.stop_proposing_time = u64::MAX; - self.config.start_voting_time = 0; - self.config.stop_voting_time = u64::MAX; + if let Some(upgrade) = self.upgrades.get(&::Upgrade::VERSION) { + upgrade.set_hotshot_config_parameters(&mut self.config) } TestConfig { @@ -541,7 +529,7 @@ pub mod testing { l1_url: "http://localhost:8545".parse().unwrap(), state_relay_url: None, builder_port: None, - upgrades: None, + upgrades: Default::default(), } } } @@ -555,7 +543,7 @@ pub mod testing { l1_url: Url, state_relay_url: Option, builder_port: Option, - upgrades: Option, + upgrades: BTreeMap, } impl TestConfig { @@ -579,7 +567,7 @@ pub mod testing { self.l1_url.clone() } - pub fn upgrades(&self) -> Option { + pub fn upgrades(&self) -> BTreeMap { self.upgrades.clone() } diff --git a/types/src/v0/impls/block/full_payload/ns_proof/test.rs b/types/src/v0/impls/block/full_payload/ns_proof/test.rs index d18831a91f..39b0b434c5 100644 --- a/types/src/v0/impls/block/full_payload/ns_proof/test.rs +++ b/types/src/v0/impls/block/full_payload/ns_proof/test.rs @@ -1,4 +1,3 @@ -use crate::{v0::impls::block::test::ValidTest, NsProof, Payload}; use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use futures::future; use hotshot::traits::BlockPayload; @@ -8,6 +7,8 @@ use hotshot_types::{ }; use jf_vid::{VidDisperse, VidScheme}; +use crate::{v0::impls::block::test::ValidTest, NsProof, Payload}; + #[async_std::test] async fn ns_proof() { let test_cases = vec![ diff --git a/types/src/v0/impls/instance_state.rs b/types/src/v0/impls/instance_state.rs index c47abcda14..adf2c15e70 100644 --- a/types/src/v0/impls/instance_state.rs +++ b/types/src/v0/impls/instance_state.rs @@ -1,10 +1,14 @@ use std::{collections::BTreeMap, sync::Arc}; -use hotshot_types::traits::{node_implementation::NodeType, states::InstanceState}; +use hotshot_types::{ + traits::{node_implementation::NodeType, states::InstanceState}, + HotShotConfig, +}; use vbs::version::{StaticVersionType, Version}; use crate::{ - v0::traits::StateCatchup, ChainConfig, L1Client, NodeState, SeqTypes, Upgrade, ValidatedState, + v0::traits::StateCatchup, ChainConfig, L1Client, NodeState, PubKey, SeqTypes, Timestamp, + Upgrade, UpgradeMode, ValidatedState, }; impl NodeState { @@ -77,6 +81,38 @@ impl Default for NodeState { impl InstanceState for NodeState {} +impl Upgrade { + pub fn set_hotshot_config_parameters(&self, config: &mut HotShotConfig) { + match &self.mode { + UpgradeMode::View(v) => { + config.start_proposing_view = v.start_proposing_view; + config.stop_proposing_view = v.stop_proposing_view; + config.start_voting_view = v.start_voting_view.unwrap_or(0); + config.stop_voting_view = v.stop_voting_view.unwrap_or(u64::MAX); + config.start_proposing_time = 0; + config.stop_proposing_time = u64::MAX; + config.start_voting_time = 0; + config.stop_voting_time = u64::MAX; + } + UpgradeMode::Time(t) => { + config.start_proposing_time = t.start_proposing_time.unix_timestamp(); + config.stop_proposing_time = t.stop_proposing_time.unix_timestamp(); + config.start_voting_time = t.start_voting_time.unwrap_or_default().unix_timestamp(); + // this should not panic because Timestamp::max() constructs the maximum possible Unix timestamp + // using i64::MAX + config.stop_voting_time = t + .stop_voting_time + .unwrap_or(Timestamp::max().expect("overflow")) + .unix_timestamp(); + config.start_proposing_view = 0; + config.stop_proposing_view = u64::MAX; + config.start_voting_view = 0; + config.stop_voting_view = u64::MAX; + } + } + } +} + #[cfg(any(test, feature = "testing"))] pub mod mock { use std::collections::HashMap; diff --git a/types/src/v0/mod.rs b/types/src/v0/mod.rs index bd9bb77473..e683ac7145 100644 --- a/types/src/v0/mod.rs +++ b/types/src/v0/mod.rs @@ -114,6 +114,9 @@ reexport_unchanged_types!( TxTableEntriesRange, Upgrade, UpgradeType, + UpgradeMode, + TimeBasedUpgrade, + ViewBasedUpgrade, ValidatedState, BlockSize, ); diff --git a/types/src/v0/utils.rs b/types/src/v0/utils.rs index 530f6230c2..69a56b2080 100644 --- a/types/src/v0/utils.rs +++ b/types/src/v0/utils.rs @@ -39,6 +39,12 @@ impl Timestamp { pub fn unix_timestamp(&self) -> u64 { self.0.unix_timestamp() as u64 } + + pub fn max() -> anyhow::Result { + Ok(Self( + OffsetDateTime::from_unix_timestamp(i64::MAX).context("overflow")?, + )) + } } impl FromStringOrInteger for Timestamp { diff --git a/types/src/v0/v0_1/instance_state.rs b/types/src/v0/v0_1/instance_state.rs index 82f75ba4a7..6007bed21e 100644 --- a/types/src/v0/v0_1/instance_state.rs +++ b/types/src/v0/v0_1/instance_state.rs @@ -5,13 +5,15 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; use std::fmt::Debug; -use crate::{v0::traits::StateCatchup, ChainConfig, GenesisHeader, L1BlockInfo, ValidatedState}; +use crate::{ + v0::traits::StateCatchup, ChainConfig, GenesisHeader, L1BlockInfo, Timestamp, ValidatedState, +}; use vbs::version::Version; use super::l1::L1Client; /// Represents the specific type of upgrade. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(untagged)] #[serde(rename_all = "snake_case")] pub enum UpgradeType { @@ -20,27 +22,48 @@ pub enum UpgradeType { ChainConfig { chain_config: ChainConfig }, } -/// Represents the upgrade config including the type of upgrade and upgrade parameters for hotshot config. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Upgrade { - /// The view at which the upgrade is proposed. - /// - /// Note: Voting for the proposal begins before the upgrade is formally proposed. - /// In our implementation, `start_proposing_view` is set to `1`` for all upgrades, - /// so if an upgrade is planned then the voting starts as soon as node is started. - #[serde(rename = "view")] +/// Represents an upgrade based on time (unix timestamp). +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct TimeBasedUpgrade { + /// the earliest unix timestamp in which the node can propose an upgrade + pub start_proposing_time: Timestamp, + /// timestamp after which the node stops proposing an upgrade + pub stop_proposing_time: Timestamp, + /// The timestamp at which voting for the upgrade proposal starts + pub start_voting_time: Option, + /// The timestamp at which voting for the upgrade proposal stops + pub stop_voting_time: Option, +} + +/// Represents an upgrade based on view. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct ViewBasedUpgrade { + /// the earliest view in which the node can propose an upgrade pub start_proposing_view: u64, + /// view after which the node stops proposing an upgrade + pub stop_proposing_view: u64, + /// The view at which voting for the upgrade proposal starts + pub start_voting_view: Option, + /// The view at which voting for the upgrade proposal stops + pub stop_voting_view: Option, +} - /// The time window during which the upgrade can be proposed. - /// - /// This parameter is used for setting the `stop_propose_window_view`. - /// `stop_proposing_view` is calculated as `start_proposing_view + propose_window`. - pub propose_window: u64, +/// Represents the specific type of upgrade. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum UpgradeMode { + /// Upgrade based on unix timestamp. + Time(TimeBasedUpgrade), + /// Upgrade based on view. + View(ViewBasedUpgrade), +} - /// The specific type of upgrade configuration. - /// - /// Currently, we only support chain configuration upgrades (`upgrade.chain_config` in genesis toml file). - #[serde(flatten)] +/// Represents a general upgrade with mode and type. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Upgrade { + /// The mode of the upgrade (time-based or view-based). + pub mode: UpgradeMode, + /// The type of the upgrade. pub upgrade_type: UpgradeType, } @@ -71,6 +94,6 @@ pub struct NodeState { /// This version is checked to determine if an upgrade is planned, /// and which version variant for versioned types /// to use in functions such as genesis. - /// (example: genesis returns V2 Header if version is 0.2) + /// (example: genesis returns V2 Header if version is 0.2) pub current_version: Version, } diff --git a/types/src/v0/v0_2/mod.rs b/types/src/v0/v0_2/mod.rs index 9e745f21ff..f71f78fb9d 100644 --- a/types/src/v0/v0_2/mod.rs +++ b/types/src/v0/v0_2/mod.rs @@ -8,10 +8,11 @@ pub use super::v0_1::{ L1Snapshot, NamespaceId, NodeState, NsIndex, NsIter, NsPayload, NsPayloadBuilder, NsPayloadByteLen, NsPayloadOwned, NsPayloadRange, NsProof, NsTable, NsTableBuilder, NsTableValidationError, NumNss, NumTxs, NumTxsRange, NumTxsUnchecked, Payload, PayloadByteLen, - ResolvableChainConfig, Transaction, TxIndex, TxIter, TxPayload, TxPayloadRange, TxProof, - TxTableEntries, TxTableEntriesRange, Upgrade, UpgradeType, ValidatedState, - BLOCK_MERKLE_TREE_HEIGHT, FEE_MERKLE_TREE_HEIGHT, NS_ID_BYTE_LEN, NS_OFFSET_BYTE_LEN, - NUM_NSS_BYTE_LEN, NUM_TXS_BYTE_LEN, TX_OFFSET_BYTE_LEN, + ResolvableChainConfig, TimeBasedUpgrade, Transaction, TxIndex, TxIter, TxPayload, + TxPayloadRange, TxProof, TxTableEntries, TxTableEntriesRange, Upgrade, UpgradeMode, + UpgradeType, ValidatedState, ViewBasedUpgrade, BLOCK_MERKLE_TREE_HEIGHT, + FEE_MERKLE_TREE_HEIGHT, NS_ID_BYTE_LEN, NS_OFFSET_BYTE_LEN, NUM_NSS_BYTE_LEN, NUM_TXS_BYTE_LEN, + TX_OFFSET_BYTE_LEN, }; pub const VERSION: Version = Version { major: 0, minor: 2 }; diff --git a/types/src/v0/v0_3/mod.rs b/types/src/v0/v0_3/mod.rs index 1bf37d80ba..a3f38283c8 100644 --- a/types/src/v0/v0_3/mod.rs +++ b/types/src/v0/v0_3/mod.rs @@ -8,10 +8,11 @@ pub use super::v0_1::{ L1Snapshot, NamespaceId, NodeState, NsIndex, NsIter, NsPayload, NsPayloadBuilder, NsPayloadByteLen, NsPayloadOwned, NsPayloadRange, NsProof, NsTable, NsTableBuilder, NsTableValidationError, NumNss, NumTxs, NumTxsRange, NumTxsUnchecked, Payload, PayloadByteLen, - ResolvableChainConfig, Transaction, TxIndex, TxIter, TxPayload, TxPayloadRange, TxProof, - TxTableEntries, TxTableEntriesRange, Upgrade, UpgradeType, ValidatedState, - BLOCK_MERKLE_TREE_HEIGHT, FEE_MERKLE_TREE_HEIGHT, NS_ID_BYTE_LEN, NS_OFFSET_BYTE_LEN, - NUM_NSS_BYTE_LEN, NUM_TXS_BYTE_LEN, TX_OFFSET_BYTE_LEN, + ResolvableChainConfig, TimeBasedUpgrade, Transaction, TxIndex, TxIter, TxPayload, + TxPayloadRange, TxProof, TxTableEntries, TxTableEntriesRange, Upgrade, UpgradeMode, + UpgradeType, ValidatedState, ViewBasedUpgrade, BLOCK_MERKLE_TREE_HEIGHT, + FEE_MERKLE_TREE_HEIGHT, NS_ID_BYTE_LEN, NS_OFFSET_BYTE_LEN, NUM_NSS_BYTE_LEN, NUM_TXS_BYTE_LEN, + TX_OFFSET_BYTE_LEN, }; pub const VERSION: Version = Version { major: 0, minor: 3 };