diff --git a/crates/builder/src/lib.rs b/crates/builder/src/lib.rs index 8c33e5f3a88..b8351d7b525 100755 --- a/crates/builder/src/lib.rs +++ b/crates/builder/src/lib.rs @@ -120,6 +120,7 @@ pub mod testing { let config: HotShotConfig = HotShotConfig { num_nodes_with_stake: NonZeroUsize::new(num_nodes_with_stake).unwrap(), known_da_nodes: known_nodes_with_stake.clone(), + da_committees: Default::default(), known_nodes_with_stake: known_nodes_with_stake.clone(), next_view_timeout: Duration::from_secs(5).as_millis() as u64, num_bootstrap: 1usize, diff --git a/crates/builder/src/non_permissioned.rs b/crates/builder/src/non_permissioned.rs index 489e1509ab4..517e2f67579 100644 --- a/crates/builder/src/non_permissioned.rs +++ b/crates/builder/src/non_permissioned.rs @@ -66,7 +66,7 @@ pub fn build_instance_state( let coordinator = EpochMembershipCoordinator::new( Arc::new(RwLock::new(EpochCommittees::new_stake( vec![], - vec![], + Default::default(), None, fetcher, genesis.epoch_height.unwrap_or_default(), diff --git a/crates/hotshot/task-impls/src/upgrade.rs b/crates/hotshot/task-impls/src/upgrade.rs index 679368c1206..f0d1fcbb8f7 100644 --- a/crates/hotshot/task-impls/src/upgrade.rs +++ b/crates/hotshot/task-impls/src/upgrade.rs @@ -20,6 +20,7 @@ use hotshot_types::{ simple_vote::{UpgradeProposalData, UpgradeVote}, traits::{ block_contents::BlockHeader, + election::Membership, node_implementation::{ConsensusTime, NodeType, Versions}, signature_key::SignatureKey, }, @@ -450,3 +451,44 @@ impl TaskState for UpgradeTaskState { fn cancel_subtasks(&mut self) {} } + +// Test function. This doesn't belong here. Should be called once we accept an upgrade. +async fn slice_membership_da_committees_for_upgrade( + cur_epoch: Epoch, + version: Version, + da_committees: &BTreeMap>>>, + membership: &mut dyn Membership, // Needs to go through EpochMembershipCoordinator +) { + if version < RotatingDaCommitteeVersion::VERSION { + return; // We aren't ready to start adding DA committees yet + } + + let Some(committees) = da_committees.get(&version) else { + return; // No new da committees for this version + }; + + //let mut res = committees.range(cur_epoch + 1..).collect(); + let mut res = BTreeMap::new(); + + for (epoch, committee) in committees.iter().reverse() { + if *epoch <= cur_epoch { + // We only want future epochs. + let should_add = if let Some((&k, _)) = res.first_key_value() { + k > cur_epoch + 1 // We should add an extra epoch if there's space between (cur_epoch + 1) and the next da_committee epoch + } else { + true + }; + + if should_add { + // This allows for the case where we transitioned into a new version partway into + // the defined epochs for that version. + res.insert(cur_epoch + 1, committee.clone()); + } + + break; + } + res.insert(*epoch, committee.clone()); + } + + membership.write().await.update_da_committees(res); +} diff --git a/crates/hotshot/testing/src/test_builder.rs b/crates/hotshot/testing/src/test_builder.rs index 669882d30d4..bc158d8acd0 100644 --- a/crates/hotshot/testing/src/test_builder.rs +++ b/crates/hotshot/testing/src/test_builder.rs @@ -70,6 +70,7 @@ pub fn default_hotshot_config( start_threshold: (1, 1), num_nodes_with_stake: NonZeroUsize::new(known_nodes_with_stake.len()).unwrap(), known_da_nodes: known_da_nodes.clone(), + da_committees: Default::default(), num_bootstrap: num_bootstrap_nodes, known_nodes_with_stake: known_nodes_with_stake.clone(), da_staked_committee_size: known_da_nodes.len(), diff --git a/crates/hotshot/types/src/hotshot_config_file.rs b/crates/hotshot/types/src/hotshot_config_file.rs index 31394ce48ab..45e739d4f97 100644 --- a/crates/hotshot/types/src/hotshot_config_file.rs +++ b/crates/hotshot/types/src/hotshot_config_file.rs @@ -4,10 +4,11 @@ // You should have received a copy of the MIT License // along with the HotShot repository. If not, see . -use std::{num::NonZeroUsize, time::Duration}; +use std::{collections::BTreeMap, num::NonZeroUsize, time::Duration}; use alloy::primitives::U256; use url::Url; +use vbs::version::Version; use vec1::Vec1; use crate::{ @@ -45,6 +46,9 @@ pub struct HotShotConfigFile { #[serde(skip)] /// The known DA nodes' public key and stake values pub known_da_nodes: Vec>, + #[serde(skip)] + /// The known DA nodes' public keys and stake values, by start epoch + pub da_committees: BTreeMap>>>, /// Number of staking DA nodes pub staked_da_nodes: usize, /// Number of fixed leaders for GPU VID @@ -89,6 +93,7 @@ impl From> for HotShotConfig { start_threshold: val.start_threshold, num_nodes_with_stake: val.num_nodes_with_stake, known_da_nodes: val.known_da_nodes, + da_committees: val.da_committees, known_nodes_with_stake: val.known_nodes_with_stake, da_staked_committee_size: val.staked_da_nodes, fixed_leader_for_gpuvid: val.fixed_leader_for_gpuvid, @@ -154,6 +159,7 @@ impl HotShotConfigFile { known_nodes_with_stake: gen_known_nodes_with_stake, staked_da_nodes, known_da_nodes, + da_committees: Default::default(), fixed_leader_for_gpuvid: 1, next_view_timeout: 10000, view_sync_timeout: Duration::from_millis(1000), diff --git a/crates/hotshot/types/src/lib.rs b/crates/hotshot/types/src/lib.rs index 30762b036d1..bab804dfda5 100644 --- a/crates/hotshot/types/src/lib.rs +++ b/crates/hotshot/types/src/lib.rs @@ -5,7 +5,9 @@ // along with the HotShot repository. If not, see . //! Types and Traits for the `HotShot` consensus module -use std::{fmt::Debug, future::Future, num::NonZeroUsize, pin::Pin, time::Duration}; +use std::{ + collections::BTreeMap, fmt::Debug, future::Future, num::NonZeroUsize, pin::Pin, time::Duration, +}; use alloy::primitives::U256; use bincode::Options; @@ -17,6 +19,7 @@ use traits::{ signature_key::{SignatureKey, StateSignatureKey}, }; use url::Url; +use vbs::version::Version; use vec1::Vec1; use crate::utils::bincode_opts; @@ -195,6 +198,8 @@ pub struct HotShotConfig { pub known_nodes_with_stake: Vec>, /// All public keys known to be DA nodes pub known_da_nodes: Vec>, + /// All public keys known to be DA nodes, by start epoch + pub da_committees: BTreeMap>>>, /// List of DA committee (staking)nodes for static DA committee pub da_staked_committee_size: usize, /// Number of fixed leaders for GPU VID, normally it will be 0, it's only used when running GPU VID @@ -266,4 +271,11 @@ impl HotShotConfig { pub fn hotshot_stake_table(&self) -> HSStakeTable { self.known_nodes_with_stake.clone().into() } + + pub fn build_da_committees(&self) -> Vec> { + // TODO: THIS IS A TEMPORARY FIX WITH THE WRONG RETURN TYPE. + // It's done so that we can start using this function and have the existing behavior + // (use known_da_nodes) while we transition to using da_committees. + self.known_da_nodes.clone() + } } diff --git a/crates/hotshot/types/src/traits/election.rs b/crates/hotshot/types/src/traits/election.rs index bcbcf7cc707..40983e8ae61 100644 --- a/crates/hotshot/types/src/traits/election.rs +++ b/crates/hotshot/types/src/traits/election.rs @@ -5,12 +5,17 @@ // along with the HotShot repository. If not, see . //! The election trait, used to decide which node is the leader and determine if a vote is valid. -use std::{collections::BTreeSet, fmt::Debug, sync::Arc}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, + sync::Arc, +}; use alloy::primitives::U256; use async_lock::RwLock; use committable::{Commitment, Committable}; use hotshot_utils::anytrace::Result; +use vbs::version::Version; use super::node_implementation::NodeType; use crate::{ @@ -199,6 +204,13 @@ pub trait Membership: Debug + Send + Sync { fn stake_table_hash(&self, _epoch: TYPES::Epoch) -> Option> { None } + + fn update_da_committees( + &mut self, + da_committees: BTreeMap>>, + ) { + // TODO: Make this not defaulted + } } pub fn membership_spawn_add_epoch_root( diff --git a/hotshot-query-service/examples/simple-server.rs b/hotshot-query-service/examples/simple-server.rs index 8d45406e0a6..3bdb1137ed8 100644 --- a/hotshot-query-service/examples/simple-server.rs +++ b/hotshot-query-service/examples/simple-server.rs @@ -201,6 +201,7 @@ async fn init_consensus( next_view_timeout: 10000, num_bootstrap: 0, known_da_nodes: known_nodes_with_stake.clone(), + da_committees: Default::default(), da_staked_committee_size: pub_keys.len(), data_request_delay: Duration::from_millis(200), view_sync_timeout: Duration::from_millis(250), diff --git a/hotshot-query-service/src/testing/consensus.rs b/hotshot-query-service/src/testing/consensus.rs index d1e71879072..eddd3664bc8 100644 --- a/hotshot-query-service/src/testing/consensus.rs +++ b/hotshot-query-service/src/testing/consensus.rs @@ -141,6 +141,7 @@ impl MockNetwork { num_bootstrap: 0, da_staked_committee_size: pub_keys.len(), known_da_nodes: known_nodes_with_stake.clone(), + da_committees: Default::default(), data_request_delay: Duration::from_millis(200), view_sync_timeout: Duration::from_millis(250), start_threshold: ( diff --git a/sequencer/src/genesis.rs b/sequencer/src/genesis.rs index a19948c9155..2f8b31b89ce 100644 --- a/sequencer/src/genesis.rs +++ b/sequencer/src/genesis.rs @@ -6,9 +6,10 @@ use std::{ use alloy::primitives::Address; use anyhow::{Context, Ok}; use espresso_types::{ - v0_3::ChainConfig, FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, Timestamp, - Upgrade, + v0_3::ChainConfig, FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, SeqTypes, + Timestamp, Upgrade, }; +use hotshot_types::traits::node_implementation::NodeType; use serde::{Deserialize, Serialize}; use vbs::version::Version; @@ -44,6 +45,48 @@ pub enum L1Finalized { Timestamp { timestamp: Timestamp }, } +/// Helper type to deal with TOML keys that are u64 but represented as strings +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TomlKeyU64(u64); + +impl<'de> Deserialize<'de> for TomlKeyU64 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + tracing::warn!("Using TomlKeyU64::deserialize"); + let s = String::deserialize(deserializer)?; + + let n = s + .parse::() + .map_err(|_| serde::de::Error::custom("invalid epoch"))?; + + std::result::Result::Ok(TomlKeyU64(n)) + } +} + +impl Serialize for TomlKeyU64 { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl From<&TomlKeyU64> for u64 { + fn from(val: &TomlKeyU64) -> Self { + val.0 + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerConfigData { + pub stake_table_key: ::SignatureKey, + pub state_ver_key: ::StateSignatureKey, + pub stake: u64, +} + /// Genesis of an Espresso chain. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Genesis { @@ -67,6 +110,8 @@ pub struct Genesis { #[serde(rename = "upgrade", with = "upgrade_ser")] #[serde(default)] pub upgrades: BTreeMap, + #[serde(default)] + pub da_committees: Option>>>, } impl Genesis { diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index 48f11eb3396..6a44c3f800b 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -57,6 +57,7 @@ use hotshot_types::{ epoch_membership::EpochMembershipCoordinator, light_client::{StateKeyPair, StateSignKey}, signature_key::{BLSPrivKey, BLSPubKey}, + stake_table::StakeTableEntry, traits::{ metrics::{Metrics, NoMetrics}, network::ConnectedNetwork, @@ -390,6 +391,34 @@ where network_config.config.epoch_start_block = epoch_start_block; network_config.config.stake_table_capacity = stake_table_capacity; + if let Some(da_committees) = &genesis.da_committees { + tracing::warn!("setting da_committees from genesis"); + network_config.config.da_committees = da_committees + .iter() + .map(|(k, v)| { + ( + *k, + v.iter() + .map(|(k, v)| { + ( + k.into(), + v.iter() + .map(|pcd| hotshot_types::PeerConfig { + stake_table_entry: StakeTableEntry { + stake_key: pcd.stake_table_key, + stake_amount: U256::from(pcd.stake), + }, + state_ver_key: pcd.state_ver_key.clone(), + }) + .collect(), + ) + }) + .collect(), + ) + }) + .collect(); + } + // If the `Libp2p` bootstrap nodes were supplied via the command line, override those // present in the config file. if let Some(bootstrap_nodes) = network_params.libp2p_bootstrap_nodes { @@ -529,7 +558,7 @@ where // Create the HotShot membership let mut membership = EpochCommittees::new_stake( network_config.config.known_nodes_with_stake.clone(), - network_config.config.known_da_nodes.clone(), + network_config.config.build_da_committees(), block_reward, fetcher, epoch_height, @@ -1020,6 +1049,7 @@ pub mod testing { fixed_leader_for_gpuvid: 0, num_nodes_with_stake: num_nodes.try_into().unwrap(), known_da_nodes: known_nodes_with_stake.clone(), + da_committees: Default::default(), known_nodes_with_stake: known_nodes_with_stake.clone(), next_view_timeout: Duration::from_secs(5).as_millis() as u64, num_bootstrap: 1usize, @@ -1269,7 +1299,7 @@ pub mod testing { let block_reward = fetcher.fetch_fixed_block_reward().await.ok(); let mut membership = EpochCommittees::new_stake( config.known_nodes_with_stake.clone(), - config.known_da_nodes.clone(), + config.build_da_committees(), block_reward, fetcher, config.epoch_height, diff --git a/sequencer/src/restart_tests.rs b/sequencer/src/restart_tests.rs index 9427f884422..8a32a76a067 100755 --- a/sequencer/src/restart_tests.rs +++ b/sequencer/src/restart_tests.rs @@ -668,6 +668,7 @@ impl TestNetwork { .into_iter() .collect(), genesis_version: Version { major: 0, minor: 1 }, + da_committees: None, }; let node_params = (0..da_nodes + regular_nodes) diff --git a/sequencer/src/run.rs b/sequencer/src/run.rs index e4b51d34107..59ce4de280a 100644 --- a/sequencer/src/run.rs +++ b/sequencer/src/run.rs @@ -336,6 +336,7 @@ mod test { epoch_start_block: None, stake_table_capacity: None, genesis_version: Version { major: 0, minor: 1 }, + da_committees: None, }; genesis.to_file(&genesis_file).unwrap(); diff --git a/types/src/v0/config.rs b/types/src/v0/config.rs index ef718cbb82d..bab24e400f2 100644 --- a/types/src/v0/config.rs +++ b/types/src/v0/config.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroUsize, time::Duration}; +use std::{collections::BTreeMap, num::NonZeroUsize, time::Duration}; use hotshot_types::{ network::{ @@ -8,6 +8,7 @@ use hotshot_types::{ }; use serde::{Deserialize, Serialize}; use tide_disco::Url; +use vbs::version::Version; use vec1::Vec1; use crate::{PubKey, SeqTypes}; @@ -55,6 +56,8 @@ pub struct PublicHotShotConfig { num_nodes_with_stake: NonZeroUsize, known_nodes_with_stake: Vec>, known_da_nodes: Vec>, + #[serde(default)] + da_committees: BTreeMap>>>, da_staked_committee_size: usize, fixed_leader_for_gpuvid: usize, next_view_timeout: u64, @@ -105,6 +108,7 @@ impl From> for PublicHotShotConfig { num_nodes_with_stake, known_nodes_with_stake, known_da_nodes, + da_committees, da_staked_committee_size, fixed_leader_for_gpuvid, next_view_timeout, @@ -133,6 +137,7 @@ impl From> for PublicHotShotConfig { num_nodes_with_stake, known_nodes_with_stake, known_da_nodes, + da_committees, da_staked_committee_size, fixed_leader_for_gpuvid, next_view_timeout, @@ -165,6 +170,7 @@ impl PublicHotShotConfig { num_nodes_with_stake: self.num_nodes_with_stake, known_nodes_with_stake: self.known_nodes_with_stake, known_da_nodes: self.known_da_nodes, + da_committees: self.da_committees, da_staked_committee_size: self.da_staked_committee_size, fixed_leader_for_gpuvid: self.fixed_leader_for_gpuvid, next_view_timeout: self.next_view_timeout, @@ -196,9 +202,11 @@ impl PublicHotShotConfig { pub fn known_da_nodes(&self) -> Vec> { self.known_da_nodes.clone() } + pub fn blocks_per_epoch(&self) -> u64 { self.epoch_height } + pub fn epoch_start_block(&self) -> u64 { self.epoch_start_block } diff --git a/types/src/v0/impls/instance_state.rs b/types/src/v0/impls/instance_state.rs index b4ab6e45ac3..e6991fbfba0 100644 --- a/types/src/v0/impls/instance_state.rs +++ b/types/src/v0/impls/instance_state.rs @@ -170,7 +170,7 @@ impl NodeState { let membership = Arc::new(RwLock::new(EpochCommittees::new_stake( vec![], - vec![], + Default::default(), None, Fetcher::mock(), 0, @@ -202,7 +202,7 @@ impl NodeState { let membership = Arc::new(RwLock::new(EpochCommittees::new_stake( vec![], - vec![], + Default::default(), None, Fetcher::mock(), 0, @@ -232,7 +232,7 @@ impl NodeState { let membership = Arc::new(RwLock::new(EpochCommittees::new_stake( vec![], - vec![], + Default::default(), None, Fetcher::mock(), 0, @@ -327,7 +327,7 @@ impl Default for NodeState { let membership = Arc::new(RwLock::new(EpochCommittees::new_stake( vec![], - vec![], + Default::default(), None, Fetcher::mock(), 0, diff --git a/types/src/v0/mod.rs b/types/src/v0/mod.rs index 809b2fe5281..f25f2ea16c2 100644 --- a/types/src/v0/mod.rs +++ b/types/src/v0/mod.rs @@ -182,6 +182,7 @@ pub type V0_1 = StaticVersion<0, 1>; pub type FeeVersion = StaticVersion<0, 2>; pub type EpochVersion = StaticVersion<0, 3>; pub type DrbAndHeaderUpgradeVersion = StaticVersion<0, 4>; +pub type RotatingDaCommitteeVersion = StaticVersion<0, 4>; // TODO: 0.4 or 0.5? pub type Leaf = hotshot_types::data::Leaf; pub type Leaf2 = hotshot_types::data::Leaf2; diff --git a/types/src/v0/v0_3/mod.rs b/types/src/v0/v0_3/mod.rs index 79a59569cf3..6061138f31f 100644 --- a/types/src/v0/v0_3/mod.rs +++ b/types/src/v0/v0_3/mod.rs @@ -29,4 +29,4 @@ pub use header::*; pub use nsproof::*; pub use stake_table::*; pub use state::*; -pub use txproof::*; +pub use txproof::*; \ No newline at end of file