From dea029b23b8769e943b4ed70be6f4d567a76ec73 Mon Sep 17 00:00:00 2001 From: MRain Date: Fri, 28 Feb 2025 10:22:00 -0500 Subject: [PATCH 01/12] adding schnorr keys to nodetype --- .../src/testing/basic_test.rs | 2 + hotshot-example-types/src/node_types.rs | 7 ++- hotshot-query-service/src/testing/mocks.rs | 2 + .../tests/tests_3/memory_network.rs | 3 +- hotshot-types/src/light_client.rs | 8 ++- hotshot-types/src/signature_key.rs | 47 ++++++++++++++++- .../src/traits/node_implementation.rs | 5 +- hotshot-types/src/traits/signature_key.rs | 52 ++++++++++++++++++- types/src/v0/mod.rs | 3 +- 9 files changed, 121 insertions(+), 8 deletions(-) diff --git a/hotshot-builder-core/src/testing/basic_test.rs b/hotshot-builder-core/src/testing/basic_test.rs index 0825a9e887..01729c96f9 100644 --- a/hotshot-builder-core/src/testing/basic_test.rs +++ b/hotshot-builder-core/src/testing/basic_test.rs @@ -24,6 +24,7 @@ mod tests { use hotshot_example_types::auction_results_provider_types::TestAuctionResult; use hotshot_example_types::node_types::TestVersions; use hotshot_types::data::{DaProposal2, Leaf2, QuorumProposal2, QuorumProposalWrapper}; + use hotshot_types::signature_key::SchnorrPubKey; use hotshot_types::simple_vote::QuorumData2; use hotshot_types::traits::node_implementation::Versions; use hotshot_types::{ @@ -92,6 +93,7 @@ mod tests { type Membership = StaticCommittee; type BuilderSignatureKey = BuilderKey; type AuctionResult = TestAuctionResult; + type StateSignatureKey = SchnorrPubKey; } // no of test messages to send let num_test_messages = 5; diff --git a/hotshot-example-types/src/node_types.rs b/hotshot-example-types/src/node_types.rs index dd3d08f48b..851f1e01a8 100644 --- a/hotshot-example-types/src/node_types.rs +++ b/hotshot-example-types/src/node_types.rs @@ -23,7 +23,7 @@ use hotshot::traits::{ use hotshot_types::{ constants::TEST_UPGRADE_CONSTANTS, data::{EpochNumber, ViewNumber}, - signature_key::{BLSPubKey, BuilderKey}, + signature_key::{BLSPubKey, BuilderKey, SchnorrPubKey}, traits::node_implementation::{NodeType, Versions}, upgrade_config::UpgradeConstants, }; @@ -67,6 +67,7 @@ impl NodeType for TestTypes { type InstanceState = TestInstanceState; type Membership = StaticCommittee; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive( @@ -99,6 +100,7 @@ impl NodeType for TestTypesRandomizedLeader { type InstanceState = TestInstanceState; type Membership = Committee; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive( @@ -135,6 +137,7 @@ impl NodeType for TestTypesRandomizedCommitteeMember type Membership = RandomizedCommitteeMembers, CONFIG>; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive( @@ -167,6 +170,7 @@ impl NodeType for TestConsecutiveLeaderTypes { type InstanceState = TestInstanceState; type Membership = StaticCommitteeLeaderForTwoViews; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive( @@ -199,6 +203,7 @@ impl NodeType for TestTwoStakeTablesTypes { type InstanceState = TestInstanceState; type Membership = TwoStaticCommittees; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } /// The Push CDN implementation diff --git a/hotshot-query-service/src/testing/mocks.rs b/hotshot-query-service/src/testing/mocks.rs index 5c271ca1ff..e4f0d1b91c 100644 --- a/hotshot-query-service/src/testing/mocks.rs +++ b/hotshot-query-service/src/testing/mocks.rs @@ -25,6 +25,7 @@ use hotshot_example_types::{ state_types::{TestInstanceState, TestValidatedState}, storage_types::TestStorage, }; +use hotshot_types::signature_key::SchnorrPubKey; use hotshot_types::traits::node_implementation::Versions; use hotshot_types::{ data::{QuorumProposal, ViewNumber}, @@ -140,6 +141,7 @@ impl NodeType for MockTypes { type Membership = StaticCommittee; type BuilderSignatureKey = BLSPubKey; type AuctionResult = TestAuctionResult; + type StateSignatureKey = SchnorrPubKey; } #[derive(Clone, Debug, Copy)] diff --git a/hotshot-testing/tests/tests_3/memory_network.rs b/hotshot-testing/tests/tests_3/memory_network.rs index 875e5b40e9..d66924df52 100644 --- a/hotshot-testing/tests/tests_3/memory_network.rs +++ b/hotshot-testing/tests/tests_3/memory_network.rs @@ -25,7 +25,7 @@ use hotshot_example_types::{ use hotshot_types::{ data::{EpochNumber, ViewNumber}, message::{DataMessage, Message, MessageKind, UpgradeLock}, - signature_key::{BLSPubKey, BuilderKey}, + signature_key::{BLSPubKey, BuilderKey, SchnorrPubKey}, traits::{ network::{BroadcastDelay, ConnectedNetwork, TestableNetworkingImplementation, Topic}, node_implementation::{ConsensusTime, NodeType}, @@ -63,6 +63,7 @@ impl NodeType for Test { type InstanceState = TestInstanceState; type Membership = StaticCommittee; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive(Clone, Debug, Deserialize, Serialize, Hash, PartialEq, Eq)] diff --git a/hotshot-types/src/light_client.rs b/hotshot-types/src/light_client.rs index 7bafaee784..0b348ff8f9 100644 --- a/hotshot-types/src/light_client.rs +++ b/hotshot-types/src/light_client.rs @@ -22,6 +22,8 @@ use tagged_base64::tagged; pub type CircuitField = ark_ed_on_bn254::Fq; /// Concrete type for light client state pub type LightClientState = GenericLightClientState; +/// Concreate type for light client state message to sign +pub type LightClientStateMsg = GenericLightClientStateMsg; /// Concrete type for stake table state pub type StakeTableState = GenericStakeTableState; /// Signature scheme @@ -84,7 +86,9 @@ pub struct GenericLightClientState { pub block_comm_root: F, } -impl From> for [F; 3] { +pub type GenericLightClientStateMsg = [F; 3]; + +impl From> for GenericLightClientStateMsg { fn from(state: GenericLightClientState) -> Self { [ F::from(state.view_number as u64), @@ -94,7 +98,7 @@ impl From> for [F; 3] { } } -impl From<&GenericLightClientState> for [F; 3] { +impl From<&GenericLightClientState> for GenericLightClientStateMsg { fn from(state: &GenericLightClientState) -> Self { [ F::from(state.view_number as u64), diff --git a/hotshot-types/src/signature_key.rs b/hotshot-types/src/signature_key.rs index 5df67a872b..0115196500 100644 --- a/hotshot-types/src/signature_key.rs +++ b/hotshot-types/src/signature_key.rs @@ -19,11 +19,14 @@ use rand_chacha::ChaCha20Rng; use tracing::instrument; use crate::{ + light_client::LightClientStateMsg, qc::{BitVectorQc, QcParams}, stake_table::StakeTableEntry, traits::{ qc::QuorumCertificateScheme, - signature_key::{BuilderSignatureKey, PrivateSignatureKey, SignatureKey}, + signature_key::{ + BuilderSignatureKey, PrivateSignatureKey, SignatureKey, StateSignatureKey, + }, }, }; @@ -185,3 +188,45 @@ impl BuilderSignatureKey for BuilderKey { (kp.ver_key(), kp.sign_key_ref().clone()) } } + +pub type SchnorrPubKey = jf_signature::schnorr::VerKey; +pub type SchnorrPrivKey = jf_signature::schnorr::SignKey; +pub type SchnorrSignatureScheme = + jf_signature::schnorr::SchnorrSignatureScheme; + +impl PrivateSignatureKey for SchnorrPrivKey { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn from_bytes(bytes: &[u8]) -> anyhow::Result { + Ok(Self::from_bytes(bytes)) + } + + fn to_tagged_base64(&self) -> Result { + self.to_tagged_base64() + } +} + +impl StateSignatureKey for SchnorrPubKey { + type StatePrivateKey = SchnorrPrivKey; + + type StateSignature = jf_signature::schnorr::Signature; + + type SignError = SignatureError; + + fn sign_state( + sk: &Self::StatePrivateKey, + state: &LightClientStateMsg, + ) -> Result { + SchnorrSignatureScheme::sign(&(), sk, state, &mut rand::thread_rng()) + } + + fn verify_state_sig( + &self, + signature: &Self::StateSignature, + state: &LightClientStateMsg, + ) -> bool { + SchnorrSignatureScheme::verify(&(), self, state, signature).is_ok() + } +} diff --git a/hotshot-types/src/traits/node_implementation.rs b/hotshot-types/src/traits/node_implementation.rs index 515e8c3647..1857db3a85 100644 --- a/hotshot-types/src/traits/node_implementation.rs +++ b/hotshot-types/src/traits/node_implementation.rs @@ -29,7 +29,7 @@ use super::{ network::{ AsyncGenerator, ConnectedNetwork, NetworkReliability, TestableNetworkingImplementation, }, - signature_key::BuilderSignatureKey, + signature_key::{BuilderSignatureKey, StateSignatureKey}, states::TestableState, storage::Storage, ValidatedState, @@ -257,6 +257,9 @@ pub trait NodeType: /// The type builder uses to sign its messages type BuilderSignatureKey: BuilderSignatureKey; + + /// The type replica uses to sign the light client state + type StateSignatureKey: StateSignatureKey; } /// Version information for HotShot diff --git a/hotshot-types/src/traits/signature_key.rs b/hotshot-types/src/traits/signature_key.rs index eaf1b33ec5..ad251ee2c0 100644 --- a/hotshot-types/src/traits/signature_key.rs +++ b/hotshot-types/src/traits/signature_key.rs @@ -23,7 +23,10 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tagged_base64::{TaggedBase64, Tb64Error}; use super::EncodeBytes; -use crate::{bundle::Bundle, traits::node_implementation::NodeType, utils::BuilderCommitment}; +use crate::{ + bundle::Bundle, light_client::LightClientStateMsg, traits::node_implementation::NodeType, + utils::BuilderCommitment, +}; /// Type representing stake table entries in a `StakeTable` pub trait StakeTableEntryType { @@ -360,3 +363,50 @@ fn aggregate_block_info_data( block_info.extend_from_slice(payload_commitment.as_ref()); block_info } + +/// Light client state signature key with minimal requirements +pub trait StateSignatureKey: + Send + + Sync + + Clone + + Sized + + Debug + + Hash + + Serialize + + for<'a> Deserialize<'a> + + PartialEq + + Eq + + Display + + for<'a> TryFrom<&'a TaggedBase64> + + Into +{ + /// The private key type + type StatePrivateKey: PrivateSignatureKey; + + /// The type of the signature + type StateSignature: Send + + Sync + + Sized + + Clone + + Debug + + Eq + + Serialize + + for<'a> Deserialize<'a> + + Hash; + + /// Type of error that can occur when signing data + type SignError: std::error::Error + Send + Sync; + + /// Sign the light client state + fn sign_state( + private_key: &Self::StatePrivateKey, + state: &LightClientStateMsg, + ) -> Result; + + /// Verify the light client state signature + fn verify_state_sig( + &self, + signature: &Self::StateSignature, + state: &LightClientStateMsg, + ) -> bool; +} diff --git a/types/src/v0/mod.rs b/types/src/v0/mod.rs index 5b0dce4a3f..a205a16900 100644 --- a/types/src/v0/mod.rs +++ b/types/src/v0/mod.rs @@ -1,6 +1,6 @@ use hotshot_types::{ data::{EpochNumber, ViewNumber}, - signature_key::BLSPubKey, + signature_key::{BLSPubKey, SchnorrPubKey}, traits::{ node_implementation::{NodeType, Versions}, signature_key::SignatureKey, @@ -141,6 +141,7 @@ impl NodeType for SeqTypes { type Membership = EpochCommittees; type BuilderSignatureKey = FeeAccount; type AuctionResult = SolverAuctionResults; + type StateSignatureKey = SchnorrPubKey; } #[derive(Clone, Default, Debug, Copy)] From 8dcfcca3e2e3008b8abf622fbd4b190c684de684 Mon Sep 17 00:00:00 2001 From: MRain Date: Mon, 10 Mar 2025 14:52:45 -0400 Subject: [PATCH 02/12] vote handler --- Cargo.lock | 2 + contracts/rust/adapter/src/light_client.rs | 8 +- contracts/rust/adapter/src/stake_table.rs | 14 +- hotshot-events-service/src/events_source.rs | 9 +- hotshot-events-service/src/test.rs | 2 +- hotshot-example-types/src/block_types.rs | 9 + hotshot-example-types/src/storage_types.rs | 11 +- hotshot-orchestrator/src/client.rs | 33 +-- hotshot-orchestrator/src/lib.rs | 61 ++--- hotshot-task-impls/src/consensus/handlers.rs | 83 ++++++- hotshot-task-impls/src/consensus/mod.rs | 37 ++- hotshot-task-impls/src/events.rs | 53 +++-- hotshot-task-impls/src/network.rs | 9 +- hotshot-task-impls/src/quorum_proposal/mod.rs | 5 +- .../src/quorum_vote/handlers.rs | 29 ++- hotshot-task-impls/src/quorum_vote/mod.rs | 11 +- hotshot-task-impls/src/vote_collection.rs | 224 +++++++++++++++++- hotshot-testing/src/helpers.rs | 4 +- hotshot-testing/src/spinning_task.rs | 12 +- hotshot-testing/src/test_builder.rs | 27 ++- hotshot-testing/src/test_launcher.rs | 9 +- hotshot-testing/src/test_runner.rs | 17 +- hotshot-types/Cargo.toml | 2 + hotshot-types/src/consensus.rs | 32 ++- hotshot-types/src/hotshot_config_file.rs | 18 +- hotshot-types/src/lib.rs | 61 ++--- hotshot-types/src/light_client.rs | 38 ++- hotshot-types/src/message.rs | 29 ++- hotshot-types/src/network.rs | 43 ++-- hotshot-types/src/signature_key.rs | 9 + hotshot-types/src/simple_certificate.rs | 85 ++++++- hotshot-types/src/simple_vote.rs | 48 +++- hotshot-types/src/traits/block_contents.rs | 4 + hotshot-types/src/traits/consensus_api.rs | 7 +- hotshot-types/src/traits/election.rs | 12 +- hotshot-types/src/traits/signature_key.rs | 3 + hotshot-types/src/vote.rs | 80 ++++++- hotshot/src/lib.rs | 42 +++- hotshot/src/tasks/mod.rs | 7 +- hotshot/src/tasks/task_state.rs | 2 + .../traits/election/randomized_committee.rs | 82 +++---- .../election/randomized_committee_members.rs | 33 +-- .../src/traits/election/static_committee.rs | 44 ++-- .../static_committee_leader_two_views.rs | 73 +++--- .../traits/election/two_static_committees.rs | 56 ++--- .../src/traits/networking/libp2p_network.rs | 2 +- sequencer/src/state_signature.rs | 37 +-- 47 files changed, 1081 insertions(+), 437 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e70eb9ff2..a86f743ddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5587,7 +5587,9 @@ dependencies = [ "either", "futures", "hotshot-utils", + "jf-crhf 0.1.0", "jf-pcs", + "jf-rescue", "jf-signature 0.2.0", "jf-utils", "jf-vid", diff --git a/contracts/rust/adapter/src/light_client.rs b/contracts/rust/adapter/src/light_client.rs index fdf8d95a3c..ab7b1a0f8d 100644 --- a/contracts/rust/adapter/src/light_client.rs +++ b/contracts/rust/adapter/src/light_client.rs @@ -56,8 +56,8 @@ impl From for ParsedLi impl From for GenericLightClientState { fn from(v: ParsedLightClientState) -> Self { Self { - view_number: v.view_num as usize, - block_height: v.block_height as usize, + view_number: v.view_num, + block_height: v.block_height, block_comm_root: u256_to_field(v.block_comm_root), } } @@ -66,8 +66,8 @@ impl From for GenericLightClientState impl From> for ParsedLightClientState { fn from(v: GenericLightClientState) -> Self { Self { - view_num: v.view_number as u64, - block_height: v.block_height as u64, + view_num: v.view_number, + block_height: v.block_height, block_comm_root: field_to_u256(v.block_comm_root), } } diff --git a/contracts/rust/adapter/src/stake_table.rs b/contracts/rust/adapter/src/stake_table.rs index 1ea6e98077..89faa8fd81 100644 --- a/contracts/rust/adapter/src/stake_table.rs +++ b/contracts/rust/adapter/src/stake_table.rs @@ -25,7 +25,7 @@ use hotshot_types::{ network::PeerConfigKeys, signature_key::BLSPubKey, stake_table::StakeTableEntry, - traits::signature_key::SignatureKey as _, + traits::{node_implementation::NodeType, signature_key::SignatureKey as _}, PeerConfig, }; use serde::{Deserialize, Serialize}; @@ -217,7 +217,10 @@ impl From for StakeTableEntry { } } -impl From for PeerConfig { +impl From for PeerConfig +where + TYPES: NodeType, +{ fn from(value: NodeInfoJf) -> Self { Self { stake_table_entry: StakeTableEntry { @@ -289,8 +292,11 @@ impl From for NodeInfoJf { } } -impl From> for NodeInfoJf { - fn from(value: PeerConfigKeys) -> Self { +impl From> for NodeInfoJf +where + TYPES: NodeType, +{ + fn from(value: PeerConfigKeys) -> Self { let PeerConfigKeys { stake_table_key, state_ver_key, diff --git a/hotshot-events-service/src/events_source.rs b/hotshot-events-service/src/events_source.rs index 4f905bca63..cb14e10799 100644 --- a/hotshot-events-service/src/events_source.rs +++ b/hotshot-events-service/src/events_source.rs @@ -26,8 +26,9 @@ where } #[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "Types::SignatureKey: for<'a> Deserialize<'a>")] pub struct StartupInfo { - pub known_node_with_stake: Vec>, + pub known_node_with_stake: Vec>, pub non_staked_node_count: usize, } @@ -46,12 +47,12 @@ pub struct EventsStreamer { subscriber_send_channel: BroadcastSender>>, // required for sending startup info - known_nodes_with_stake: Vec>, + known_nodes_with_stake: Vec>, non_staked_node_count: usize, } impl EventsStreamer { - pub fn known_node_with_stake(&self) -> Vec> { + pub fn known_node_with_stake(&self) -> Vec> { self.known_nodes_with_stake.clone() } @@ -155,7 +156,7 @@ impl EventsSource for EventsStreamer { impl EventsStreamer { pub fn new( - known_nodes_with_stake: Vec>, + known_nodes_with_stake: Vec>, non_staked_node_count: usize, ) -> Self { let (mut subscriber_send_channel, to_subscribe_clone_recv) = diff --git a/hotshot-events-service/src/test.rs b/hotshot-events-service/src/test.rs index be5dde136b..22918dd7ff 100644 --- a/hotshot-events-service/src/test.rs +++ b/hotshot-events-service/src/test.rs @@ -96,7 +96,7 @@ mod tests { let pub_key = BLSPubKey::from_private(&private_key); let state_key_pair = StateKeyPair::generate(); - let peer_config = PeerConfig:: { + let peer_config = PeerConfig:: { stake_table_entry: pub_key.stake_table_entry(1), state_ver_key: state_key_pair.ver_key(), }; diff --git a/hotshot-example-types/src/block_types.rs b/hotshot-example-types/src/block_types.rs index 7d4ba68c4d..dd68183a1c 100644 --- a/hotshot-example-types/src/block_types.rs +++ b/hotshot-example-types/src/block_types.rs @@ -15,6 +15,7 @@ use committable::{Commitment, Committable, RawCommitmentBuilder}; use hotshot_types::{ data::VidCommitment, data::{BlockError, Leaf2}, + light_client::LightClientState, traits::{ block_contents::{BlockHeader, BuilderFee, EncodeBytes, TestableBlock, Transaction}, node_implementation::NodeType, @@ -388,6 +389,14 @@ impl< fn get_auction_results(&self) -> Option { Some(TYPES::AuctionResult { urls: vec![] }) } + + fn get_light_client_state(&self) -> anyhow::Result { + LightClientState::new( + self.block_number, + self.block_number, + self.payload_commitment.as_ref(), + ) + } } impl Committable for TestBlockHeader { diff --git a/hotshot-example-types/src/storage_types.rs b/hotshot-example-types/src/storage_types.rs index a20a8a631e..72f9ba0dcf 100644 --- a/hotshot-example-types/src/storage_types.rs +++ b/hotshot-example-types/src/storage_types.rs @@ -21,7 +21,10 @@ use hotshot_types::{ }, event::HotShotAction, message::Proposal, - simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate2, + UpgradeCertificate, + }, traits::{ node_implementation::{ConsensusTime, NodeType}, storage::Storage, @@ -56,6 +59,7 @@ pub struct TestStorageState { Option>, action: TYPES::View, epoch: Option, + state_cert: Option>, } impl Default for TestStorageState { @@ -73,6 +77,7 @@ impl Default for TestStorageState { high_qc2: None, action: TYPES::View::genesis(), epoch: None, + state_cert: None, } } } @@ -136,6 +141,10 @@ impl TestStorage { pub async fn vids_cloned(&self) -> VidShares2 { self.inner.read().await.vid2.clone() } + + pub async fn state_cert_cloned(&self) -> Option> { + self.inner.read().await.state_cert.clone() + } } #[async_trait] diff --git a/hotshot-orchestrator/src/client.rs b/hotshot-orchestrator/src/client.rs index de167ff505..601ab8c9ea 100644 --- a/hotshot-orchestrator/src/client.rs +++ b/hotshot-orchestrator/src/client.rs @@ -10,7 +10,7 @@ use clap::Parser; use futures::{Future, FutureExt}; use hotshot_types::{ network::{NetworkConfig, NetworkConfigSource}, - traits::signature_key::SignatureKey, + traits::node_implementation::NodeType, PeerConfig, ValidatorConfig, }; use libp2p_identity::PeerId; @@ -167,15 +167,19 @@ pub struct MultiValidatorArgs { /// /// # Errors /// If we are unable to get the configuration from the orchestrator -pub async fn get_complete_config( +pub async fn get_complete_config( client: &OrchestratorClient, - mut validator_config: ValidatorConfig, + mut validator_config: ValidatorConfig, libp2p_advertise_address: Option, libp2p_public_key: Option, -) -> anyhow::Result<(NetworkConfig, ValidatorConfig, NetworkConfigSource)> { +) -> anyhow::Result<( + NetworkConfig, + ValidatorConfig, + NetworkConfigSource, +)> { // get the configuration from the orchestrator - let run_config: NetworkConfig = client - .post_and_wait_all_public_keys::( + let run_config: NetworkConfig = client + .post_and_wait_all_public_keys::( &mut validator_config, libp2p_advertise_address, libp2p_public_key, @@ -250,11 +254,11 @@ impl OrchestratorClient { /// # Errors /// If we were unable to serialize the Libp2p data #[allow(clippy::type_complexity)] - pub async fn get_config_without_peer( + pub async fn get_config_without_peer( &self, libp2p_advertise_address: Option, libp2p_public_key: Option, - ) -> anyhow::Result> { + ) -> anyhow::Result> { // Serialize our (possible) libp2p-specific data let request_body = vbs::Serializer::::serialize(&( libp2p_advertise_address, @@ -281,7 +285,7 @@ impl OrchestratorClient { // get the corresponding config let f = |client: Client| { async move { - let config: Result, ClientError> = client + let config: Result, ClientError> = client .post(&format!("api/config/{node_index}")) .send() .await; @@ -322,7 +326,7 @@ impl OrchestratorClient { /// /// Does not fail, retries internally until success. #[instrument(skip_all, name = "orchestrator config")] - pub async fn get_config_after_collection(&self) -> NetworkConfig { + pub async fn get_config_after_collection(&self) -> NetworkConfig { // Define the request for post-register configurations let get_config_after_collection = |client: Client| { async move { @@ -396,13 +400,14 @@ impl OrchestratorClient { /// # Panics /// if unable to post #[instrument(skip(self), name = "orchestrator public keys")] - pub async fn post_and_wait_all_public_keys( + pub async fn post_and_wait_all_public_keys( &self, - validator_config: &mut ValidatorConfig, + validator_config: &mut ValidatorConfig, libp2p_advertise_address: Option, libp2p_public_key: Option, - ) -> NetworkConfig { - let pubkey: Vec = PeerConfig::::to_bytes(&validator_config.public_config()).clone(); + ) -> NetworkConfig { + let pubkey: Vec = + PeerConfig::::to_bytes(&validator_config.public_config()).clone(); let da_requested: bool = validator_config.is_da; // Serialize our (possible) libp2p-specific data diff --git a/hotshot-orchestrator/src/lib.rs b/hotshot-orchestrator/src/lib.rs index bafdccbe2c..391c594e8a 100644 --- a/hotshot-orchestrator/src/lib.rs +++ b/hotshot-orchestrator/src/lib.rs @@ -23,6 +23,7 @@ use csv::Writer; use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; use hotshot_types::{ network::{BuilderType, NetworkConfig, PublicKeysFile}, + traits::node_implementation::NodeType, traits::signature_key::{SignatureKey, StakeTableEntryType}, PeerConfig, }; @@ -70,13 +71,13 @@ pub fn libp2p_generate_indexed_identity(seed: [u8; 32], index: u64) -> Keypair { /// The state of the orchestrator #[derive(Default, Clone)] #[allow(clippy::struct_excessive_bools)] -struct OrchestratorState { +struct OrchestratorState { /// Tracks the latest node index we have generated a configuration for latest_index: u16, /// Tracks the latest temporary index we have generated for init validator's key pair tmp_latest_index: u16, /// The network configuration - config: NetworkConfig, + config: NetworkConfig, /// Whether the network configuration has been updated with all the peer's public keys/configs peer_pub_ready: bool, /// A map from public keys to `(node_index, is_da)`. @@ -85,7 +86,7 @@ struct OrchestratorState { /// Will be set to true once all nodes post they are ready to start start: bool, /// The total nodes that have posted they are ready to start - nodes_connected: HashSet>, + nodes_connected: HashSet>, /// The results of the benchmarks bench_results: BenchResults, /// The number of nodes that have posted their results @@ -100,9 +101,9 @@ struct OrchestratorState { fixed_stake_table: bool, } -impl OrchestratorState { +impl OrchestratorState { /// create a new [`OrchestratorState`] - pub fn new(network_config: NetworkConfig) -> Self { + pub fn new(network_config: NetworkConfig) -> Self { let mut peer_pub_ready = false; let mut fixed_stake_table = false; @@ -173,7 +174,7 @@ impl OrchestratorState { } /// An api exposed by the orchestrator -pub trait OrchestratorApi { +pub trait OrchestratorApi { /// Post an identity to the orchestrator. Takes in optional /// arguments so others can identify us on the Libp2p network. /// # Errors @@ -186,7 +187,7 @@ pub trait OrchestratorApi { /// post endpoint for each node's config /// # Errors /// if unable to serve - fn post_getconfig(&mut self, _node_index: u16) -> Result, ServerError>; + fn post_getconfig(&mut self, _node_index: u16) -> Result, ServerError>; /// get endpoint for the next available temporary node index /// # Errors /// if unable to serve @@ -208,7 +209,7 @@ pub trait OrchestratorApi { /// get endpoint for the network config after all peers public keys are collected /// # Errors /// if unable to serve - fn post_config_after_peer_collected(&mut self) -> Result, ServerError>; + fn post_config_after_peer_collected(&mut self) -> Result, ServerError>; /// get endpoint for whether or not the run has started /// # Errors /// if unable to serve @@ -220,7 +221,7 @@ pub trait OrchestratorApi { /// A node POSTs its public key to let the orchestrator know that it is ready /// # Errors /// if unable to serve - fn post_ready(&mut self, peer_config: &PeerConfig) -> Result<(), ServerError>; + fn post_ready(&mut self, peer_config: &PeerConfig) -> Result<(), ServerError>; /// post endpoint for manually starting the orchestrator /// # Errors /// if unable to serve @@ -235,9 +236,9 @@ pub trait OrchestratorApi { fn get_builders(&self) -> Result, ServerError>; } -impl OrchestratorState +impl OrchestratorState where - KEY: serde::Serialize + Clone + SignatureKey + 'static, + TYPES::SignatureKey: serde::Serialize + Clone + SignatureKey + 'static, { /// register a node with an unknown public key. /// this method should be used when we don't have a fixed stake table @@ -264,7 +265,7 @@ where let node_index = self.pub_posted.len() as u64; // Deserialize the public key - let staked_pubkey = PeerConfig::::from_bytes(pubkey).unwrap(); + let staked_pubkey = PeerConfig::::from_bytes(pubkey).unwrap(); self.config .config @@ -333,7 +334,7 @@ where } // Deserialize the public key - let staked_pubkey = PeerConfig::::from_bytes(pubkey).unwrap(); + let staked_pubkey = PeerConfig::::from_bytes(pubkey).unwrap(); // Check if the node is allowed to connect, returning its index and config entry if so. let Some((node_index, node_config)) = @@ -382,9 +383,9 @@ where } } -impl OrchestratorApi for OrchestratorState +impl OrchestratorApi for OrchestratorState where - KEY: serde::Serialize + Clone + SignatureKey + 'static, + TYPES::SignatureKey: serde::Serialize + Clone + SignatureKey + 'static, { /// Post an identity to the orchestrator. Takes in optional /// arguments so others can identify us on the Libp2p network. @@ -425,7 +426,7 @@ where // Assumes nodes will set their own index that they received from the // 'identity' endpoint - fn post_getconfig(&mut self, _node_index: u16) -> Result, ServerError> { + fn post_getconfig(&mut self, _node_index: u16) -> Result, ServerError> { Ok(self.config.clone()) } @@ -469,7 +470,7 @@ where Ok(self.peer_pub_ready) } - fn post_config_after_peer_collected(&mut self) -> Result, ServerError> { + fn post_config_after_peer_collected(&mut self) -> Result, ServerError> { if !self.peer_pub_ready { return Err(ServerError { status: tide_disco::StatusCode::BAD_REQUEST, @@ -492,7 +493,7 @@ where } // Assumes nodes do not post 'ready' twice - fn post_ready(&mut self, peer_config: &PeerConfig) -> Result<(), ServerError> { + fn post_ready(&mut self, peer_config: &PeerConfig) -> Result<(), ServerError> { // If we have not disabled registration verification. // Is this node allowed to connect? if !self @@ -657,11 +658,12 @@ where /// Sets up all API routes #[allow(clippy::too_many_lines)] -fn define_api() -> Result, ApiError> +fn define_api() -> Result, ApiError> where + TYPES: NodeType, State: 'static + Send + Sync + ReadState + WriteState, - ::State: Send + Sync + OrchestratorApi, - KEY: serde::Serialize + SignatureKey, + ::State: Send + Sync + OrchestratorApi, + TYPES::SignatureKey: serde::Serialize, VER: StaticVersionType + 'static, { let api_toml = toml::from_str::(include_str!(concat!( @@ -735,7 +737,7 @@ where let mut body_bytes = req.body_bytes(); body_bytes.drain(..12); // Decode the payload-supplied pubkey - let Some(pubkey) = PeerConfig::::from_bytes(&body_bytes) else { + let Some(pubkey) = PeerConfig::::from_bytes(&body_bytes) else { return Err(ServerError { status: tide_disco::StatusCode::BAD_REQUEST, message: "Malformed body".to_string(), @@ -817,12 +819,12 @@ where /// This errors if tide disco runs into an issue during serving /// # Panics /// This panics if unable to register the api with tide disco -pub async fn run_orchestrator( - mut network_config: NetworkConfig, +pub async fn run_orchestrator( + mut network_config: NetworkConfig, url: Url, ) -> io::Result<()> where - KEY: SignatureKey + 'static + serde::Serialize, + TYPES::SignatureKey: 'static + serde::Serialize, { let env_password = std::env::var("ORCHESTRATOR_MANUAL_START_PASSWORD"); @@ -841,8 +843,8 @@ where let config_file_as_string: String = fs::read_to_string(filepath.clone()) .unwrap_or_else(|_| panic!("Could not read config file located at {filepath}")); - let file: PublicKeysFile = - toml::from_str::>(&config_file_as_string) + let file: PublicKeysFile = + toml::from_str::>(&config_file_as_string) .expect("Unable to convert config file to TOML"); network_config.public_keys = file.public_keys; @@ -871,9 +873,10 @@ where let web_api = define_api().map_err(|_e| io::Error::new(ErrorKind::Other, "Failed to define api")); - let state: RwLock> = RwLock::new(OrchestratorState::new(network_config)); + let state: RwLock> = + RwLock::new(OrchestratorState::new(network_config)); - let mut app = App::>, ServerError>::with_state(state); + let mut app = App::>, ServerError>::with_state(state); app.register_module::("api", web_api.unwrap()) .expect("Error registering api"); tracing::error!("listening on {:?}", url); diff --git a/hotshot-task-impls/src/consensus/handlers.rs b/hotshot-task-impls/src/consensus/handlers.rs index 64b8e19960..4770a3f82b 100644 --- a/hotshot-task-impls/src/consensus/handlers.rs +++ b/hotshot-task-impls/src/consensus/handlers.rs @@ -10,7 +10,8 @@ use async_broadcast::{Receiver, Sender}; use chrono::Utc; use hotshot_types::{ event::{Event, EventType}, - simple_vote::{HasEpoch, QuorumVote2, TimeoutData2, TimeoutVote2}, + simple_certificate::ExtendedQuorumCertificate, + simple_vote::{ExtendedQuorumVote, HasEpoch, QuorumVote2, TimeoutData2, TimeoutVote2}, traits::{ election::Membership, node_implementation::{ConsensusTime, NodeImplementation, NodeType}, @@ -28,7 +29,7 @@ use crate::{ consensus::Versions, events::HotShotEvent, helpers::{broadcast_event, wait_for_next_epoch_qc}, - vote_collection::handle_vote, + vote_collection::{handle_extended_quorum_vote, handle_vote}, }; /// Handle a `QuorumVoteRecv` event. @@ -107,6 +108,76 @@ pub(crate) async fn handle_quorum_vote_recv< Ok(()) } +/// Handle a `ExtendedQuorumVoteRecv` event. +pub(crate) async fn handle_extended_quorum_vote_recv< + TYPES: NodeType, + I: NodeImplementation, + V: Versions, +>( + vote: &ExtendedQuorumVote, + event: Arc>, + sender: &Sender>>, + task_state: &mut ConsensusTaskState, +) -> Result<()> { + let in_transition = task_state + .consensus + .read() + .await + .is_high_qc_for_last_block(); + let we_are_leader = task_state + .membership + .read() + .await + .leader(vote.view_number() + 1, vote.vote.data.epoch)? + == task_state.public_key; + ensure!( + in_transition && we_are_leader, + info!( + "We should be the leader and in epoch transition for view {:?}", + vote.view_number() + 1 + ) + ); + + handle_extended_quorum_vote( + &mut task_state.extended_quorum_vote_collectors, + vote, + task_state.public_key.clone(), + &task_state.membership, + vote.vote.data.epoch, + task_state.id, + &event, + sender, + &task_state.upgrade_lock, + ) + .await?; + + if let Some(vote_epoch) = vote.epoch() { + // If the vote sender belongs to the next epoch, collect it separately to form the second QC + let has_stake = task_state + .membership + .read() + .await + .has_stake(&vote.vote.signing_key(), Some(vote_epoch + 1)); + if has_stake { + handle_vote( + &mut task_state.next_epoch_vote_collectors, + &vote.vote.clone().into(), + task_state.public_key.clone(), + &task_state.membership, + vote.vote.data.epoch, + task_state.id, + &event, + sender, + &task_state.upgrade_lock, + EpochTransitionIndicator::InTransition, + ) + .await?; + } + } + + Ok(()) +} + /// Handle a `TimeoutVoteRecv` event. pub(crate) async fn handle_timeout_vote_recv< TYPES: NodeType, @@ -169,6 +240,8 @@ pub async fn send_high_qc, V: V, >, + /// A map of collector tasks for light client state update vote + pub extended_quorum_vote_collectors: ExtendedQuorumVoteCollectorsMap, + /// A map of `TimeoutVote` collector tasks. pub timeout_vote_collectors: VoteCollectorsMap, TimeoutCertificate2, V>, @@ -119,6 +126,13 @@ impl, V: Versions> ConsensusTaskSt tracing::debug!("Failed to handle QuorumVoteRecv event; error = {e}"); } } + HotShotEvent::ExtendedQuorumVoteRecv(ref vote) => { + if let Err(e) = + handle_extended_quorum_vote_recv(vote, Arc::clone(&event), &sender, self).await + { + tracing::debug!("Failed to handle ExtendedQuorumVoteRecv event; error = {e}"); + } + } HotShotEvent::TimeoutVoteRecv(ref vote) => { if let Err(e) = handle_timeout_vote_recv(vote, Arc::clone(&event), &sender, self).await @@ -193,18 +207,18 @@ impl, V: Versions> ConsensusTaskSt ) .await; } - HotShotEvent::ExtendedQcRecv(high_qc, next_epoch_high_qc, _) => { + HotShotEvent::ExtendedQcRecv(eqc, next_epoch_high_qc, _) => { if !self .consensus .read() .await - .is_leaf_extended(high_qc.data.leaf_commit) + .is_leaf_extended(eqc.qc.data.leaf_commit) { tracing::warn!("Received extended QC but we can't verify the leaf is extended"); return Ok(()); } if let Err(e) = validate_qc_and_next_epoch_qc( - high_qc, + &eqc.qc, Some(next_epoch_high_qc), &self.consensus, &self.membership, @@ -216,8 +230,10 @@ impl, V: Versions> ConsensusTaskSt return Ok(()); } + // TODO(Chengyu): verify the light client state in epoch transition qc. + let mut consensus_writer = self.consensus.write().await; - let high_qc_updated = consensus_writer.update_high_qc(high_qc.clone()).is_ok(); + let high_qc_updated = consensus_writer.update_high_qc(eqc.qc.clone()).is_ok(); let next_high_qc_updated = consensus_writer .update_next_epoch_high_qc(next_epoch_high_qc.clone()) .is_ok(); @@ -225,18 +241,15 @@ impl, V: Versions> ConsensusTaskSt tracing::debug!( "Received Extended QC for view {:?} and epoch {:?}.", - high_qc.view_number(), - high_qc.epoch() + eqc.view_number(), + eqc.epoch() ); if high_qc_updated || next_high_qc_updated { // Send ViewChange indicating new view and new epoch. - let next_epoch = high_qc.data.epoch().map(|x| x + 1); + let next_epoch = eqc.qc.data.epoch().map(|x| x + 1); tracing::info!("Entering new epoch: {:?}", next_epoch); broadcast_event( - Arc::new(HotShotEvent::ViewChange( - high_qc.view_number() + 1, - next_epoch, - )), + Arc::new(HotShotEvent::ViewChange(eqc.view_number() + 1, next_epoch)), &sender, ) .await; diff --git a/hotshot-task-impls/src/events.rs b/hotshot-task-impls/src/events.rs index e34f856476..7f818ffeb2 100644 --- a/hotshot-task-impls/src/events.rs +++ b/hotshot-task-impls/src/events.rs @@ -10,20 +10,19 @@ use async_broadcast::Sender; use either::Either; use hotshot_task::task::TaskEvent; use hotshot_types::{ - data::VidCommitment, data::{ DaProposal2, Leaf2, PackedBundle, QuorumProposal2, QuorumProposalWrapper, UpgradeProposal, - VidDisperse, VidDisperseShare, + VidCommitment, VidDisperse, VidDisperseShare, }, message::Proposal, request_response::ProposalRequestPayload, simple_certificate::{ - DaCertificate2, NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, - TimeoutCertificate, TimeoutCertificate2, UpgradeCertificate, ViewSyncCommitCertificate2, - ViewSyncFinalizeCertificate2, ViewSyncPreCommitCertificate2, + DaCertificate2, ExtendedQuorumCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, TimeoutCertificate, TimeoutCertificate2, UpgradeCertificate, + ViewSyncCommitCertificate2, ViewSyncFinalizeCertificate2, ViewSyncPreCommitCertificate2, }, simple_vote::{ - DaVote2, QuorumVote2, TimeoutVote2, UpgradeVote, ViewSyncCommitVote2, + DaVote2, ExtendedQuorumVote, QuorumVote2, TimeoutVote2, UpgradeVote, ViewSyncCommitVote2, ViewSyncFinalizeVote2, ViewSyncPreCommitVote2, }, traits::{ @@ -78,6 +77,8 @@ pub enum HotShotEvent { ), /// A quorum vote has been received from the network; handled by the consensus task QuorumVoteRecv(QuorumVote2), + /// Quorum vote for extended QC + ExtendedQuorumVoteRecv(ExtendedQuorumVote), /// A timeout vote received from the network; handled by consensus task TimeoutVoteRecv(TimeoutVote2), /// Send a timeout vote to the network; emitted by consensus task replicas @@ -99,8 +100,8 @@ pub enum HotShotEvent { ), /// Send a quorum vote to the next leader; emitted by a replica in the consensus task after seeing a valid quorum proposal QuorumVoteSend(QuorumVote2), - /// Broadcast a quorum vote to form an eQC; emitted by a replica in the consensus task after seeing a valid quorum proposal - ExtendedQuorumVoteSend(QuorumVote2), + /// Quorum vote for extended QC + ExtendedQuorumVoteSend(ExtendedQuorumVote), /// A quorum proposal with the given parent leaf is validated. /// The full validation checks include: /// 1. The proposal is not for an old view @@ -133,6 +134,8 @@ pub enum HotShotEvent { QcFormed(Either, TimeoutCertificate>), /// The next leader has collected enough votes to form a QC; emitted by the next leader in the consensus task; an internal event only Qc2Formed(Either, TimeoutCertificate2>), + /// The next leader has collected enough votes to form a extended QC; emitted by the next leader in the consensus task; an internal event only + ExtendedQcFormed(Either, TimeoutCertificate2>), /// The next leader has collected enough votes from the next epoch nodes to form a QC; emitted by the next leader in the consensus task; an internal event only NextEpochQc2Formed(Either, TimeoutCertificate>), /// The DA leader has collected enough votes to form a DAC; emitted by the DA leader in the DA task; sent to the entire network via the networking task @@ -261,14 +264,14 @@ pub enum HotShotEvent { /// A replica sent us an extended QuorumCertificate and NextEpochQuorumCertificate ExtendedQcRecv( - QuorumCertificate2, + ExtendedQuorumCertificate, NextEpochQuorumCertificate2, TYPES::SignatureKey, ), /// Send our extended QuorumCertificate and NextEpochQuorumCertificate to all nodes in the old and new epoch ExtendedQcSend( - QuorumCertificate2, + ExtendedQuorumCertificate, NextEpochQuorumCertificate2, TYPES::SignatureKey, ), @@ -279,7 +282,6 @@ impl HotShotEvent { /// Return the view number for a hotshot event if present pub fn view_number(&self) -> Option { match self { - HotShotEvent::QuorumVoteRecv(v) => Some(v.view_number()), HotShotEvent::TimeoutVoteRecv(v) | HotShotEvent::TimeoutVoteSend(v) => { Some(v.view_number()) } @@ -291,9 +293,11 @@ impl HotShotEvent { | HotShotEvent::QuorumProposalPreliminarilyValidated(proposal) => { Some(proposal.data.view_number()) } - HotShotEvent::QuorumVoteSend(vote) | HotShotEvent::ExtendedQuorumVoteSend(vote) => { + HotShotEvent::QuorumVoteSend(vote) | HotShotEvent::QuorumVoteRecv(vote) => { Some(vote.view_number()) } + HotShotEvent::ExtendedQuorumVoteSend(vote) + | HotShotEvent::ExtendedQuorumVoteRecv(vote) => Some(vote.view_number()), HotShotEvent::DaProposalRecv(proposal, _) | HotShotEvent::DaProposalValidated(proposal, _) | HotShotEvent::DaProposalSend(proposal, _) => Some(proposal.data.view_number()), @@ -308,6 +312,10 @@ impl HotShotEvent { either::Left(qc) => Some(qc.view_number()), either::Right(tc) => Some(tc.view_number()), }, + HotShotEvent::ExtendedQcFormed(cert) => match cert { + either::Left(qc) => Some(qc.view_number()), + either::Right(tc) => Some(tc.view_number()), + }, HotShotEvent::NextEpochQc2Formed(cert) => match cert { either::Left(qc) => Some(qc.view_number()), either::Right(tc) => Some(tc.view_number()), @@ -355,10 +363,12 @@ impl HotShotEvent { | HotShotEvent::VidRequestRecv(request, _) => Some(request.view), HotShotEvent::VidResponseSend(_, _, proposal) | HotShotEvent::VidResponseRecv(_, proposal) => Some(proposal.data.view_number()), - HotShotEvent::HighQcRecv(qc, _) - | HotShotEvent::HighQcSend(qc, ..) - | HotShotEvent::ExtendedQcRecv(qc, _, _) - | HotShotEvent::ExtendedQcSend(qc, _, _) => Some(qc.view_number()), + HotShotEvent::HighQcRecv(qc, ..) | HotShotEvent::HighQcSend(qc, ..) => { + Some(qc.view_number()) + } + HotShotEvent::ExtendedQcRecv(qc, ..) | HotShotEvent::ExtendedQcSend(qc, ..) => { + Some(qc.view_number()) + } } } } @@ -383,6 +393,13 @@ impl Display for HotShotEvent { v.view_number() ) } + HotShotEvent::ExtendedQuorumVoteRecv(v) => { + write!( + f, + "ExtendedQuorumVoteRecv(view_number={:?})", + v.view_number() + ) + } HotShotEvent::TimeoutVoteRecv(v) => { write!(f, "TimeoutVoteRecv(view_number={:?})", v.view_number()) } @@ -439,6 +456,10 @@ impl Display for HotShotEvent { either::Left(qc) => write!(f, "QcFormed2(view_number={:?})", qc.view_number()), either::Right(tc) => write!(f, "QcFormed2(view_number={:?})", tc.view_number()), }, + HotShotEvent::ExtendedQcFormed(cert) => match cert { + either::Left(qc) => write!(f, "QcFormed2(view_number={:?})", qc.view_number()), + either::Right(tc) => write!(f, "QcFormed2(view_number={:?})", tc.view_number()), + }, HotShotEvent::NextEpochQc2Formed(cert) => match cert { either::Left(qc) => { write!(f, "NextEpochQc2Formed(view_number={:?})", qc.view_number()) diff --git a/hotshot-task-impls/src/network.rs b/hotshot-task-impls/src/network.rs index 03004f5077..1eea1c6931 100644 --- a/hotshot-task-impls/src/network.rs +++ b/hotshot-task-impls/src/network.rs @@ -311,6 +311,9 @@ impl NetworkMessageTaskState { GeneralConsensusMessage::ExtendedQc(qc, next_epoch_qc) => { HotShotEvent::ExtendedQcRecv(qc, next_epoch_qc, sender) } + GeneralConsensusMessage::ExtendedQuorumVote(vote) => { + HotShotEvent::ExtendedQuorumVoteRecv(vote) + } }, SequencingMessage::Da(da_message) => match da_message { DaConsensusMessage::DaProposal(proposal) => { @@ -757,15 +760,15 @@ impl< *maybe_action = Some(HotShotAction::Vote); let message = if self.upgrade_lock.epochs_enabled(vote.view_number()).await { MessageKind::::from_consensus_message(SequencingMessage::General( - GeneralConsensusMessage::Vote2(vote.clone()), + GeneralConsensusMessage::ExtendedQuorumVote(vote.clone()), )) } else { MessageKind::::from_consensus_message(SequencingMessage::General( - GeneralConsensusMessage::Vote(vote.clone().to_vote()), + GeneralConsensusMessage::Vote(vote.vote.clone().to_vote()), )) }; - Some((vote.signing_key(), message, TransmitType::Broadcast)) + Some((vote.vote.signing_key(), message, TransmitType::Broadcast)) } HotShotEvent::QuorumProposalRequestSend(req, signature) => Some(( req.key.clone(), diff --git a/hotshot-task-impls/src/quorum_proposal/mod.rs b/hotshot-task-impls/src/quorum_proposal/mod.rs index c5bcc1bc00..a1c456c3ea 100644 --- a/hotshot-task-impls/src/quorum_proposal/mod.rs +++ b/hotshot-task-impls/src/quorum_proposal/mod.rs @@ -19,7 +19,7 @@ use hotshot_types::StakeTableEntries; use hotshot_types::{ consensus::OuterConsensus, message::UpgradeLock, - simple_certificate::{QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ExtendedQuorumCertificate, QuorumCertificate2, UpgradeCertificate}, traits::{ election::Membership, node_implementation::{ConsensusTime, NodeImplementation, NodeType, Versions}, @@ -553,7 +553,8 @@ impl, V: Versions> let keep_view = TYPES::View::new(view.saturating_sub(1)); self.cancel_tasks(keep_view); } - HotShotEvent::HighQcSend(qc, ..) | HotShotEvent::ExtendedQcSend(qc, ..) => { + HotShotEvent::HighQcSend(qc, ..) + | HotShotEvent::ExtendedQcSend(ExtendedQuorumCertificate { qc, .. }, ..) => { ensure!(qc.view_number() > self.highest_qc.view_number()); let cert_epoch_number = qc.data.epoch; diff --git a/hotshot-task-impls/src/quorum_vote/handlers.rs b/hotshot-task-impls/src/quorum_vote/handlers.rs index 7f1937c253..99b40c7f41 100644 --- a/hotshot-task-impls/src/quorum_vote/handlers.rs +++ b/hotshot-task-impls/src/quorum_vote/handlers.rs @@ -16,12 +16,14 @@ use hotshot_types::{ drb::{compute_drb_result, DrbResult}, event::{Event, EventType}, message::{Proposal, UpgradeLock}, - simple_vote::{HasEpoch, QuorumData2, QuorumVote2}, + simple_vote::{ + ExtendedQuorumVote, HasEpoch, LightClientStateUpdateVote, QuorumData2, QuorumVote2, + }, traits::{ block_contents::BlockHeader, election::Membership, node_implementation::{ConsensusTime, NodeImplementation, NodeType}, - signature_key::SignatureKey, + signature_key::{SignatureKey, StateSignatureKey}, storage::Storage, ValidatedState, }, @@ -628,6 +630,7 @@ pub(crate) async fn submit_vote, V vid_share: Proposal>, extended_vote: bool, epoch_height: u64, + state_private_key: &::StatePrivateKey, ) -> Result<()> { let epoch_number = option_epoch_from_block_number::( leaf.with_epoch, @@ -677,8 +680,28 @@ pub(crate) async fn submit_vote, V if extended_vote { tracing::debug!("sending extended vote to everybody",); + // TODO(Chengyu): add light client state + let light_client_state = leaf + .block_header() + .get_light_client_state() + .wrap() + .context(error!("Failed to generate light client state"))?; + let signature = ::sign_state( + state_private_key, + &(&light_client_state).into(), + ) + .wrap() + .context(error!("Failed to sign the light client state"))?; + let state_vote = LightClientStateUpdateVote { + epoch: epoch_number.unwrap(), + light_client_state, + signature, + }; broadcast_event( - Arc::new(HotShotEvent::ExtendedQuorumVoteSend(vote)), + Arc::new(HotShotEvent::ExtendedQuorumVoteSend(ExtendedQuorumVote { + vote, + state_vote, + })), &sender, ) .await; diff --git a/hotshot-task-impls/src/quorum_vote/mod.rs b/hotshot-task-impls/src/quorum_vote/mod.rs index 32354d7c6b..6905133d9b 100644 --- a/hotshot-task-impls/src/quorum_vote/mod.rs +++ b/hotshot-task-impls/src/quorum_vote/mod.rs @@ -15,7 +15,6 @@ use hotshot_task::{ dependency_task::{DependencyTask, HandleDepOutput}, task::TaskState, }; -use hotshot_types::StakeTableEntries; use hotshot_types::{ consensus::{ConsensusMetricsValue, OuterConsensus}, data::{Leaf2, QuorumProposalWrapper}, @@ -33,6 +32,7 @@ use hotshot_types::{ utils::{epoch_from_block_number, option_epoch_from_block_number}, vote::{Certificate, HasViewNumber}, }; +use hotshot_types::{traits::signature_key::StateSignatureKey, StakeTableEntries}; use hotshot_utils::anytrace::*; use tokio::task::JoinHandle; use tracing::instrument; @@ -98,6 +98,9 @@ pub struct VoteDependencyHandle, V /// Number of blocks in an epoch, zero means there are no epochs pub epoch_height: u64, + + /// Signature key for light client state + pub state_private_key: ::StatePrivateKey, } impl + 'static, V: Versions> HandleDepOutput @@ -282,6 +285,7 @@ impl + 'static, V: Versions> Handl vid_share, false, self.epoch_height, + &self.state_private_key, ) .await { @@ -338,6 +342,9 @@ pub struct QuorumVoteTaskState, V: /// Number of blocks in an epoch, zero means there are no epochs pub epoch_height: u64, + + /// Signature key for light client state + pub state_private_key: ::StatePrivateKey, } impl, V: Versions> QuorumVoteTaskState { @@ -443,6 +450,7 @@ impl, V: Versions> QuorumVoteTaskS id: self.id, epoch_height: self.epoch_height, consensus_metrics: Arc::clone(&self.consensus_metrics), + state_private_key: self.state_private_key.clone(), }, ); self.vote_dependencies @@ -784,6 +792,7 @@ impl, V: Versions> QuorumVoteTaskS updated_vid, is_vote_leaf_extended, self.epoch_height, + &self.state_private_key, ) .await .context(|e| debug!("Failed to submit vote; error = {}", e)) diff --git a/hotshot-task-impls/src/vote_collection.rs b/hotshot-task-impls/src/vote_collection.rs index ede632a973..ded65aadc4 100644 --- a/hotshot-task-impls/src/vote_collection.rs +++ b/hotshot-task-impls/src/vote_collection.rs @@ -18,20 +18,22 @@ use either::Either::{Left, Right}; use hotshot_types::{ message::UpgradeLock, simple_certificate::{ - DaCertificate2, NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, - TimeoutCertificate2, UpgradeCertificate, ViewSyncCommitCertificate2, + DaCertificate2, ExtendedQuorumCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, TimeoutCertificate2, UpgradeCertificate, ViewSyncCommitCertificate2, ViewSyncFinalizeCertificate2, ViewSyncPreCommitCertificate2, }, simple_vote::{ - DaVote2, NextEpochQuorumVote2, QuorumVote, QuorumVote2, TimeoutVote2, UpgradeVote, - ViewSyncCommitVote2, ViewSyncFinalizeVote2, ViewSyncPreCommitVote2, + DaVote2, ExtendedQuorumVote, NextEpochQuorumVote2, QuorumVote, QuorumVote2, TimeoutVote2, + UpgradeVote, ViewSyncCommitVote2, ViewSyncFinalizeVote2, ViewSyncPreCommitVote2, }, traits::{ election::Membership, node_implementation::{NodeType, Versions}, }, utils::EpochTransitionIndicator, - vote::{Certificate, HasViewNumber, Vote, VoteAccumulator}, + vote::{ + Certificate, HasViewNumber, LightClientStateUpdateVoteAccumulator, Vote, VoteAccumulator, + }, }; use hotshot_utils::anytrace::*; @@ -700,3 +702,215 @@ impl matches!(event.as_ref(), HotShotEvent::ViewSyncFinalizeVoteRecv(_)) } } + +/// A map for extended quorum vote collectors +pub type ExtendedQuorumVoteCollectorsMap = + BTreeMap<::View, ExtendedQuorumVoteCollectionTaskState>; + +pub struct ExtendedQuorumVoteCollectionTaskState { + // pub vote_task_state: + // VoteCollectionTaskState, QuorumCertificate2, V>, + // pub light_client_task_state: LightClientStateUpdateVoteCollectionTaskState, + /// Public key for this node. + pub public_key: TYPES::SignatureKey, + + /// Membership for voting + pub membership: Arc>, + + /// accumulator for quorum votes + pub accumulator: + Option, QuorumCertificate2, V>>, + + /// accumulator for light client state update votes + pub state_vote_accumulator: Option>, + + /// The view which we are collecting votes for + pub view: TYPES::View, + + /// The epoch which we are collecting votes for + pub epoch: Option, + + /// Node id + pub id: u64, +} + +// Handlers for extended quorum vote accumulators +impl ExtendedQuorumVoteCollectionTaskState { + /// Take one vote and accumulate it. Returns the certs once formed. + async fn handle_vote_event( + &mut self, + event: Arc>, + sender: &Sender>>, + ) -> Result>> { + match event.as_ref() { + HotShotEvent::ExtendedQuorumVoteRecv(vote) => { + self.accumulate_vote(vote, self.epoch, sender).await + } + _ => Ok(None), + } + } + + /// Accumulate a vote and return the certificates if formed + async fn accumulate_vote( + &mut self, + vote: &ExtendedQuorumVote, + sender_epoch: Option, + event_stream: &Sender>>, + ) -> Result>> { + let ExtendedQuorumVote { vote, state_vote } = vote; + + ensure!( + vote.leader(&*self.membership.read().await, self.epoch)? == self.public_key, + info!("Received vote for a view in which we were not the leader.") + ); + + ensure!( + vote.view_number() == self.view, + error!( + "Vote view does not match! vote view is {} current view is {}. This vote should not have been passed to this accumulator.", + *vote.view_number(), + *self.view + ) + ); + + let accumulator = self.accumulator.as_mut().context(warn!( + "No accumulator to handle extended quorum vote with. This shouldn't happen." + ))?; + + let state_vote_accumulator = self.state_vote_accumulator.as_mut().context(warn!( + "No accumulator to handle light client state update vote with. This shouldn't happen." + ))?; + + let key = vote.signing_key(); + let epoch = state_vote.epoch; + let membership_reader = self.membership.read().await; + let stake_table_entry = + QuorumCertificate2::::stake_table_entry(&*membership_reader, &key, Some(epoch)) + .ok_or(error!("Invalid signing key: {:?}", key))?; + let stake_table = + QuorumCertificate2::::stake_table(&*membership_reader, Some(epoch)); + let threshold = QuorumCertificate2::::threshold(&*membership_reader, Some(epoch)); + drop(membership_reader); + + match ( + accumulator + .accumulate(vote, &self.membership, sender_epoch) + .await, + state_vote_accumulator.accumulate( + epoch, + state_vote, + &stake_table_entry.state_ver_key, + &stake_table, + threshold.into(), + ), + ) { + (None, None) => Ok(None), + (Some(cert), Some(state_cert)) => { + tracing::debug!("Certificate Formed! {:?}", cert); + + let eqc = ExtendedQuorumCertificate { + qc: cert, + state_cert, + }; + broadcast_event( + Arc::new(HotShotEvent::ExtendedQcFormed(Left(eqc.clone()))), + event_stream, + ) + .await; + self.accumulator = None; + + Ok(Some(eqc)) + } + _ => Err(error!( + "Only one certificate formed during epoch transition, this should not happen." + )), + } + } +} + +async fn create_extended_quorum_vote_accumulator( + info: &AccumulatorInfo, + event: Arc>, + sender: &Sender>>, + upgrade_lock: UpgradeLock, +) -> Result> { + let new_accumulator = + VoteAccumulator::, QuorumCertificate2, V> { + vote_outcomes: HashMap::new(), + signers: HashMap::new(), + phantom: PhantomData, + upgrade_lock, + }; + let state_vote_accumulator = LightClientStateUpdateVoteAccumulator { + vote_outcomes: HashMap::new(), + }; + + let mut state = ExtendedQuorumVoteCollectionTaskState:: { + membership: Arc::clone(&info.membership), + public_key: info.public_key.clone(), + accumulator: Some(new_accumulator), + state_vote_accumulator: Some(state_vote_accumulator), + view: info.view, + epoch: info.epoch, + id: info.id, + }; + + state.handle_vote_event(Arc::clone(&event), sender).await?; + + Ok(state) +} + +/// A helper function that handles extended quorum vote collection +/// +/// # Errors +/// If we fail to handle the vote +#[allow(clippy::too_many_arguments)] +pub async fn handle_extended_quorum_vote( + collectors: &mut ExtendedQuorumVoteCollectorsMap, + vote: &ExtendedQuorumVote, + public_key: TYPES::SignatureKey, + membership: &Arc>, + epoch: Option, + id: u64, + event: &Arc>, + event_stream: &Sender>>, + upgrade_lock: &UpgradeLock, +) -> Result<()> { + match collectors.entry(vote.view_number()) { + Entry::Vacant(entry) => { + tracing::debug!("Starting vote handle for view {:?}", vote.view_number()); + let info = AccumulatorInfo { + public_key, + membership: Arc::clone(membership), + view: vote.view_number(), + epoch, + id, + }; + let collector = create_extended_quorum_vote_accumulator( + &info, + Arc::clone(event), + event_stream, + upgrade_lock.clone(), + ) + .await?; + + entry.insert(collector); + + Ok(()) + } + Entry::Occupied(mut entry) => { + // handle the vote, and garbage collect if the vote collector is finished + if entry + .get_mut() + .handle_vote_event(Arc::clone(event), event_stream) + .await? + .is_some() + { + entry.remove(); + *collectors = collectors.split_off(&vote.view_number()); + } + + Ok(()) + } + } +} diff --git a/hotshot-testing/src/helpers.rs b/hotshot-testing/src/helpers.rs index 253a1e4265..177e7d1ec2 100644 --- a/hotshot-testing/src/helpers.rs +++ b/hotshot-testing/src/helpers.rs @@ -113,10 +113,11 @@ pub async fn build_system_handle_from_launcher< let is_da = node_id < hotshot_config.da_staked_committee_size as u64; // We assign node's public key and stake value rather than read from config file since it's a test - let validator_config: ValidatorConfig = + let validator_config: ValidatorConfig = ValidatorConfig::generated_from_seed_indexed([0u8; 32], node_id, 1, is_da); let private_key = validator_config.private_key.clone(); let public_key = validator_config.public_key.clone(); + let state_private_key = validator_config.state_private_key.clone(); let memberships = Arc::new(RwLock::new(TYPES::Membership::new( hotshot_config.known_nodes_with_stake.clone(), @@ -128,6 +129,7 @@ pub async fn build_system_handle_from_launcher< let (c, s, r) = SystemContext::init( public_key, private_key, + state_private_key, node_id, hotshot_config, memberships, diff --git a/hotshot-testing/src/spinning_task.rs b/hotshot-testing/src/spinning_task.rs index 8f54eaee0a..3515d9b794 100644 --- a/hotshot-testing/src/spinning_task.rs +++ b/hotshot-testing/src/spinning_task.rs @@ -28,7 +28,9 @@ use hotshot_types::{ data::Leaf2, event::Event, message::convert_proposal, - simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2}, + simple_certificate::{ + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate2, + }, traits::{ network::{AsyncGenerator, ConnectedNetwork}, node_implementation::{ConsensusTime, NodeImplementation, NodeType, Versions}, @@ -77,6 +79,8 @@ pub struct SpinningTask< pub(crate) restart_contexts: HashMap>, /// Generate network channel for restart nodes pub(crate) channel_generator: AsyncGenerator>, + /// The light client state update certificate + pub(crate) state_cert: LightClientStateUpdateCertificate, } #[async_trait] @@ -170,6 +174,7 @@ where BTreeMap::new(), BTreeMap::new(), None, + self.state_cert.clone(), ); // We assign node's public key and stake value rather than read from config file since it's a test let validator_config = @@ -253,6 +258,10 @@ where ) .await, ); + let state_cert = read_storage + .state_cert_cloned() + .await + .unwrap_or(LightClientStateUpdateCertificate::genesis()); let saved_proposals = read_storage.proposals_cloned().await; let mut vid_shares = BTreeMap::new(); for (view, hash_map) in read_storage.vids_cloned().await { @@ -274,6 +283,7 @@ where saved_proposals, vid_shares, decided_upgrade_certificate, + state_cert, ); // We assign node's public key and stake value rather than read from config file since it's a test let validator_config = ValidatorConfig::generated_from_seed_indexed( diff --git a/hotshot-testing/src/test_builder.rs b/hotshot-testing/src/test_builder.rs index 452d90473a..17223c6c70 100644 --- a/hotshot-testing/src/test_builder.rs +++ b/hotshot-testing/src/test_builder.rs @@ -57,11 +57,11 @@ pub struct TimingData { } pub fn default_hotshot_config( - known_nodes_with_stake: Vec>, - known_da_nodes: Vec>, + known_nodes_with_stake: Vec>, + known_da_nodes: Vec>, num_bootstrap_nodes: usize, epoch_height: u64, -) -> HotShotConfig { +) -> HotShotConfig { HotShotConfig { start_threshold: (1, 1), num_nodes_with_stake: NonZeroUsize::new(known_nodes_with_stake.len()).unwrap(), @@ -92,15 +92,12 @@ pub fn default_hotshot_config( pub fn gen_node_lists( num_staked_nodes: u64, num_da_nodes: u64, -) -> ( - Vec>, - Vec>, -) { +) -> (Vec>, Vec>) { let mut staked_nodes = Vec::new(); let mut da_nodes = Vec::new(); for n in 0..num_staked_nodes { - let validator_config: ValidatorConfig = + let validator_config: ValidatorConfig = ValidatorConfig::generated_from_seed_indexed([0u8; 32], n, 1, n < num_da_nodes); let peer_config = validator_config.public_config(); @@ -121,7 +118,7 @@ pub struct TestDescription, V: Ver /// /// Note: this is not the same as the `HotShotConfig` passed to test nodes for `SystemContext::init`; /// those configs are instead provided by the resource generators in the test launcher. - pub test_config: HotShotConfig, + pub test_config: HotShotConfig, /// Whether to skip initializing nodes that will start late, which will catch up later with /// `HotShotInitializer::from_reload` in the spinning task. pub skip_late: bool, @@ -231,7 +228,7 @@ pub async fn create_test_handle< node_id: u64, network: Network, memberships: Arc>, - config: HotShotConfig, + config: HotShotConfig, storage: I::Storage, marketplace_config: MarketplaceConfig, ) -> SystemContextHandle { @@ -245,12 +242,13 @@ pub async fn create_test_handle< // See whether or not we should be DA let is_da = node_id < config.da_staked_committee_size as u64; - let validator_config: ValidatorConfig = + let validator_config: ValidatorConfig = ValidatorConfig::generated_from_seed_indexed([0u8; 32], node_id, 1, is_da); // Get key pair for certificate aggregation let private_key = validator_config.private_key.clone(); let public_key = validator_config.public_key.clone(); + let state_private_key = validator_config.state_private_key.clone(); let behaviour = (metadata.behaviour)(node_id); match behaviour { @@ -260,6 +258,7 @@ pub async fn create_test_handle< .spawn_twin_handles( public_key, private_key, + state_private_key, node_id, config, memberships, @@ -279,6 +278,7 @@ pub async fn create_test_handle< .spawn_handle( public_key, private_key, + state_private_key, node_id, config, memberships, @@ -294,6 +294,7 @@ pub async fn create_test_handle< let hotshot = SystemContext::::new( public_key, private_key, + state_private_key, node_id, config, memberships, @@ -545,7 +546,7 @@ where let da_staked_committee_size = test_config.da_staked_committee_size; let validator_config = Rc::new(move |node_id| { - ValidatorConfig::::generated_from_seed_indexed( + ValidatorConfig::::generated_from_seed_indexed( [0u8; 32], node_id, 1, @@ -563,7 +564,7 @@ where view_sync_timeout, } = timing_data; // TODO this should really be using the timing config struct - let mod_hotshot_config = move |hotshot_config: &mut HotShotConfig| { + let mod_hotshot_config = move |hotshot_config: &mut HotShotConfig| { hotshot_config.next_view_timeout = next_view_timeout; hotshot_config.builder_timeout = builder_timeout; hotshot_config.data_request_delay = data_request_delay; diff --git a/hotshot-testing/src/test_launcher.rs b/hotshot-testing/src/test_launcher.rs index 35c77d64de..1290321868 100644 --- a/hotshot-testing/src/test_launcher.rs +++ b/hotshot-testing/src/test_launcher.rs @@ -35,9 +35,9 @@ pub struct ResourceGenerators>, /// configuration used to generate each hotshot node - pub hotshot_config: Generator>, + pub hotshot_config: Generator>, /// config that contains the signature keys - pub validator_config: Generator>, + pub validator_config: Generator>, /// generate a new marketplace config for each node pub marketplace_config: Generator>, } @@ -67,10 +67,7 @@ impl, V: Versions> TestLau } /// Modifies the config used when generating nodes with `f` #[must_use] - pub fn map_hotshot_config( - mut self, - f: impl Fn(&mut HotShotConfig) + 'static, - ) -> Self { + pub fn map_hotshot_config(mut self, f: impl Fn(&mut HotShotConfig) + 'static) -> Self { let mut test_config = self.metadata.test_config.clone(); f(&mut test_config); diff --git a/hotshot-testing/src/test_runner.rs b/hotshot-testing/src/test_runner.rs index 545004f4d2..4cfd664e5b 100644 --- a/hotshot-testing/src/test_runner.rs +++ b/hotshot-testing/src/test_runner.rs @@ -31,7 +31,7 @@ use hotshot_types::{ consensus::ConsensusMetricsValue, constants::EVENT_CHANNEL_SIZE, data::Leaf2, - simple_certificate::QuorumCertificate2, + simple_certificate::{LightClientStateUpdateCertificate, QuorumCertificate2}, traits::{ election::Membership, network::ConnectedNetwork, @@ -196,6 +196,7 @@ where async_delay_config: launcher.metadata.async_delay_config, restart_contexts: HashMap::new(), channel_generator: launcher.resource_generators.channel_generator, + state_cert: LightClientStateUpdateCertificate::::genesis(), }; let spinning_task = TestTask::>::new( spinning_task_state, @@ -591,18 +592,20 @@ where network: Network, memberships: TYPES::Membership, initializer: HotShotInitializer, - config: HotShotConfig, - validator_config: ValidatorConfig, + config: HotShotConfig, + validator_config: ValidatorConfig, storage: I::Storage, marketplace_config: MarketplaceConfig, ) -> Arc> { // Get key pair for certificate aggregation let private_key = validator_config.private_key.clone(); let public_key = validator_config.public_key.clone(); + let state_private_key = validator_config.state_private_key.clone(); SystemContext::new( public_key, private_key, + state_private_key, node_id, config, Arc::new(RwLock::new(memberships)), @@ -624,8 +627,8 @@ where network: Network, memberships: Arc>, initializer: HotShotInitializer, - config: HotShotConfig, - validator_config: ValidatorConfig, + config: HotShotConfig, + validator_config: ValidatorConfig, storage: I::Storage, marketplace_config: MarketplaceConfig, internal_channel: ( @@ -637,10 +640,12 @@ where // Get key pair for certificate aggregation let private_key = validator_config.private_key.clone(); let public_key = validator_config.public_key.clone(); + let state_private_key = validator_config.state_private_key.clone(); SystemContext::new_from_channels( public_key, private_key, + state_private_key, node_id, config, memberships, @@ -676,7 +681,7 @@ pub struct LateNodeContextParameters, + pub config: HotShotConfig, /// The marketplace config for this node. pub marketplace_config: MarketplaceConfig, diff --git a/hotshot-types/Cargo.toml b/hotshot-types/Cargo.toml index e236b4acc3..68dbb2d1eb 100644 --- a/hotshot-types/Cargo.toml +++ b/hotshot-types/Cargo.toml @@ -28,7 +28,9 @@ dyn-clone = "1.0.17" either = { workspace = true } futures = { workspace = true, features = ["alloc"] } hotshot-utils = { workspace = true } +jf-crhf = { workspace = true } jf-pcs = { workspace = true } +jf-rescue = { workspace = true } jf-signature = { workspace = true, features = ["bls", "schnorr"] } jf-utils = { workspace = true } jf-vid = { workspace = true } diff --git a/hotshot-types/src/consensus.rs b/hotshot-types/src/consensus.rs index c775880206..73aa345b18 100644 --- a/hotshot-types/src/consensus.rs +++ b/hotshot-types/src/consensus.rs @@ -26,7 +26,10 @@ use crate::{ error::HotShotError, event::{HotShotAction, LeafInfo}, message::{Proposal, UpgradeLock}, - simple_certificate::{DaCertificate2, NextEpochQuorumCertificate2, QuorumCertificate2}, + simple_certificate::{ + DaCertificate2, LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, + QuorumCertificate2, + }, traits::{ block_contents::BuilderFee, metrics::{Counter, Gauge, Histogram, Metrics, NoMetrics}, @@ -329,6 +332,9 @@ pub struct Consensus { /// Tables for the DRB seeds and results. pub drb_seeds_and_results: DrbSeedsAndResults, + + /// The light client state update certificate + pub state_cert: LightClientStateUpdateCertificate, } /// This struct holds a payload and its metadata @@ -430,6 +436,7 @@ impl Consensus { next_epoch_high_qc: Option>, metrics: Arc, epoch_height: u64, + state_cert: LightClientStateUpdateCertificate, ) -> Self { Consensus { validated_state_map, @@ -448,6 +455,7 @@ impl Consensus { metrics, epoch_height, drb_seeds_and_results: DrbSeedsAndResults::new(), + state_cert, } } @@ -476,6 +484,11 @@ impl Consensus { &self.high_qc } + /// Get the light client state certificate + pub fn state_cert(&self) -> &LightClientStateUpdateCertificate { + &self.state_cert + } + /// Get the next epoch high QC. pub fn next_epoch_high_qc(&self) -> Option<&NextEpochQuorumCertificate2> { self.next_epoch_high_qc.as_ref() @@ -792,6 +805,23 @@ impl Consensus { Ok(()) } + /// Update the light client state update certificate if given a newer one. + /// # Errors + /// Can return an error when the provided state_cert is not newer than the existing entry. + pub fn update_state_cert( + &mut self, + state_cert: LightClientStateUpdateCertificate, + ) -> Result<()> { + ensure!( + state_cert.epoch > self.state_cert.epoch || state_cert == self.state_cert, + debug!("Light client state update certification with an equal or higher epoch exists.") + ); + tracing::debug!("Updating light client state update certification"); + self.state_cert = state_cert; + + Ok(()) + } + /// Add a new entry to the vid_shares map. pub fn update_vid_shares( &mut self, diff --git a/hotshot-types/src/hotshot_config_file.rs b/hotshot-types/src/hotshot_config_file.rs index 53633d5d7a..932490eb0f 100644 --- a/hotshot-types/src/hotshot_config_file.rs +++ b/hotshot-types/src/hotshot_config_file.rs @@ -10,8 +10,8 @@ use url::Url; use vec1::Vec1; use crate::{ - constants::REQUEST_DATA_DELAY, traits::signature_key::SignatureKey, - upgrade_config::UpgradeConfig, HotShotConfig, PeerConfig, ValidatorConfig, + constants::REQUEST_DATA_DELAY, upgrade_config::UpgradeConfig, HotShotConfig, NodeType, + PeerConfig, ValidatorConfig, }; /// Default builder URL, used as placeholder @@ -22,7 +22,7 @@ fn default_builder_urls() -> Vec1 { /// Holds configuration for a `HotShot` #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(bound(deserialize = ""))] -pub struct HotShotConfigFile { +pub struct HotShotConfigFile { /// The proportion of nodes required before the orchestrator issues the ready signal, /// expressed as (numerator, denominator) pub start_threshold: (u64, u64), @@ -30,10 +30,10 @@ pub struct HotShotConfigFile { pub num_nodes_with_stake: NonZeroUsize, #[serde(skip)] /// The known nodes' public key and stake value - pub known_nodes_with_stake: Vec>, + pub known_nodes_with_stake: Vec>, #[serde(skip)] /// The known DA nodes' public key and stake values - pub known_da_nodes: Vec>, + pub known_da_nodes: Vec>, /// Number of staking DA nodes pub staked_da_nodes: usize, /// Number of fixed leaders for GPU VID @@ -57,8 +57,8 @@ pub struct HotShotConfigFile { pub epoch_height: u64, } -impl From> for HotShotConfig { - fn from(val: HotShotConfigFile) -> Self { +impl From> for HotShotConfig { + fn from(val: HotShotConfigFile) -> Self { HotShotConfig { start_threshold: val.start_threshold, num_nodes_with_stake: val.num_nodes_with_stake, @@ -87,7 +87,7 @@ impl From> for HotShotConfig { } } -impl HotShotConfigFile { +impl HotShotConfigFile { /// Creates a new `HotShotConfigFile` with 5 nodes and 10 DA nodes. /// /// # Panics @@ -101,7 +101,7 @@ impl HotShotConfigFile { let gen_known_nodes_with_stake = (0..10) .map(|node_id| { - let mut cur_validator_config: ValidatorConfig = + let mut cur_validator_config: ValidatorConfig = ValidatorConfig::generated_from_seed_indexed([0u8; 32], node_id, 1, false); if node_id < staked_da_nodes as u64 { diff --git a/hotshot-types/src/lib.rs b/hotshot-types/src/lib.rs index 822fc62f6e..0d47a921f3 100644 --- a/hotshot-types/src/lib.rs +++ b/hotshot-types/src/lib.rs @@ -9,9 +9,11 @@ use std::{fmt::Debug, future::Future, num::NonZeroUsize, pin::Pin, time::Duratio use bincode::Options; use displaydoc::Display; -use light_client::StateVerKey; use tracing::error; -use traits::{node_implementation::NodeType, signature_key::SignatureKey}; +use traits::{ + node_implementation::NodeType, + signature_key::{SignatureKey, StateSignatureKey}, +}; use url::Url; use vec1::Vec1; @@ -65,20 +67,22 @@ where #[derive(Clone, Debug, Display)] /// config for validator, including public key, private key, stake value -pub struct ValidatorConfig { +pub struct ValidatorConfig { /// The validator's public key and stake value - pub public_key: KEY, + pub public_key: TYPES::SignatureKey, /// The validator's private key, should be in the mempool, not public - pub private_key: KEY::PrivateKey, + pub private_key: ::PrivateKey, /// The validator's stake pub stake_value: u64, - /// the validator's key pairs for state signing/verification - pub state_key_pair: light_client::StateKeyPair, + /// the validator's key pairs for state verification + pub state_public_key: TYPES::StateSignatureKey, + /// the validator's key pairs for state verification + pub state_private_key: ::StatePrivateKey, /// Whether or not this validator is DA pub is_da: bool, } -impl ValidatorConfig { +impl ValidatorConfig { /// generate validator config from input seed, index, stake value, and whether it's DA #[must_use] pub fn generated_from_seed_indexed( @@ -87,27 +91,30 @@ impl ValidatorConfig { stake_value: u64, is_da: bool, ) -> Self { - let (public_key, private_key) = KEY::generated_from_seed_indexed(seed, index); - let state_key_pairs = light_client::StateKeyPair::generate_from_seed_indexed(seed, index); + let (public_key, private_key) = + TYPES::SignatureKey::generated_from_seed_indexed(seed, index); + let (state_public_key, state_private_key) = + TYPES::StateSignatureKey::generated_from_seed_indexed(seed, index); Self { public_key, private_key, stake_value, - state_key_pair: state_key_pairs, + state_public_key, + state_private_key, is_da, } } /// get the public config of the validator - pub fn public_config(&self) -> PeerConfig { + pub fn public_config(&self) -> PeerConfig { PeerConfig { stake_table_entry: self.public_key.stake_table_entry(self.stake_value), - state_ver_key: self.state_key_pair.0.ver_key(), + state_ver_key: self.state_public_key.clone(), } } } -impl Default for ValidatorConfig { +impl Default for ValidatorConfig { fn default() -> Self { Self::generated_from_seed_indexed([0u8; 32], 0, 1, true) } @@ -116,14 +123,14 @@ impl Default for ValidatorConfig { #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Display, PartialEq, Eq, Hash)] #[serde(bound(deserialize = ""))] /// structure of peers' config, including public key, stake value, and state key. -pub struct PeerConfig { +pub struct PeerConfig { /// The peer's public key and stake value - pub stake_table_entry: KEY::StakeTableEntry, + pub stake_table_entry: ::StakeTableEntry, /// the peer's state public key - pub state_ver_key: StateVerKey, + pub state_ver_key: TYPES::StateSignatureKey, } -impl PeerConfig { +impl PeerConfig { /// Serialize a peer's config to bytes pub fn to_bytes(config: &Self) -> Vec { let x = bincode_opts().serialize(config); @@ -140,7 +147,7 @@ impl PeerConfig { /// # Errors /// Will return `None` if deserialization fails pub fn from_bytes(bytes: &[u8]) -> Option { - let x: Result, _> = bincode_opts().deserialize(bytes); + let x: Result, _> = bincode_opts().deserialize(bytes); match x { Ok(pub_key) => Some(pub_key), Err(e) => { @@ -151,9 +158,9 @@ impl PeerConfig { } } -impl Default for PeerConfig { +impl Default for PeerConfig { fn default() -> Self { - let default_validator_config = ValidatorConfig::::default(); + let default_validator_config = ValidatorConfig::::default(); default_validator_config.public_config() } } @@ -162,8 +169,8 @@ pub struct StakeTableEntries( pub Vec<<::SignatureKey as SignatureKey>::StakeTableEntry>, ); -impl From>> for StakeTableEntries { - fn from(peers: Vec>) -> Self { +impl From>> for StakeTableEntries { + fn from(peers: Vec>) -> Self { Self( peers .into_iter() @@ -176,7 +183,7 @@ impl From>> for StakeTableE /// Holds configuration for a `HotShot` #[derive(Clone, derive_more::Debug, serde::Serialize, serde::Deserialize)] #[serde(bound(deserialize = ""))] -pub struct HotShotConfig { +pub struct HotShotConfig { /// The proportion of nodes required before the orchestrator issues the ready signal, /// expressed as (numerator, denominator) pub start_threshold: (u64, u64), @@ -184,9 +191,9 @@ pub struct HotShotConfig { // Earlier it was total_nodes pub num_nodes_with_stake: NonZeroUsize, /// List of known node's public keys and stake value for certificate aggregation, serving as public parameter - pub known_nodes_with_stake: Vec>, + pub known_nodes_with_stake: Vec>, /// All public keys known to be DA nodes - pub known_da_nodes: Vec>, + pub known_da_nodes: Vec>, /// 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 @@ -223,7 +230,7 @@ pub struct HotShotConfig { pub epoch_height: u64, } -impl HotShotConfig { +impl HotShotConfig { /// Update a hotshot config to have a view-based upgrade. pub fn set_view_upgrade(&mut self, view: u64) { self.start_proposing_view = view; diff --git a/hotshot-types/src/light_client.rs b/hotshot-types/src/light_client.rs index 0b348ff8f9..7f8190379f 100644 --- a/hotshot-types/src/light_client.rs +++ b/hotshot-types/src/light_client.rs @@ -11,6 +11,8 @@ use std::collections::HashMap; use ark_ed_on_bn254::EdwardsConfig as Config; use ark_ff::PrimeField; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use jf_crhf::CRHF; +use jf_rescue::{crhf::VariableLengthRescueCRHF, RescueError, RescueParameter}; use jf_signature::schnorr; use primitive_types::U256; use rand::SeedableRng; @@ -79,9 +81,9 @@ pub struct StateSignaturesBundle { )] pub struct GenericLightClientState { /// Current view number - pub view_number: usize, + pub view_number: u64, /// Current block height - pub block_height: usize, + pub block_height: u64, /// Root of the block commitment tree pub block_comm_root: F, } @@ -91,8 +93,8 @@ pub type GenericLightClientStateMsg = [F; 3]; impl From> for GenericLightClientStateMsg { fn from(state: GenericLightClientState) -> Self { [ - F::from(state.view_number as u64), - F::from(state.block_height as u64), + F::from(state.view_number), + F::from(state.block_height), state.block_comm_root, ] } @@ -101,13 +103,27 @@ impl From> for GenericLightClientState impl From<&GenericLightClientState> for GenericLightClientStateMsg { fn from(state: &GenericLightClientState) -> Self { [ - F::from(state.view_number as u64), - F::from(state.block_height as u64), + F::from(state.view_number), + F::from(state.block_height), state.block_comm_root, ] } } +impl GenericLightClientState { + pub fn new( + view_number: u64, + block_height: u64, + block_comm_root: &[u8], + ) -> anyhow::Result { + Ok(Self { + view_number, + block_height, + block_comm_root: hash_bytes_to_field(block_comm_root)?, + }) + } +} + /// Stake table state #[tagged("STAKE_TABLE_STATE")] #[derive( @@ -259,3 +275,13 @@ impl GenericPublicInput { self.0[6] } } + +fn hash_bytes_to_field(bytes: &[u8]) -> Result { + // make sure that `mod_order` won't happen. + let bytes_len = ((::MODULUS_BIT_SIZE + 7) / 8 - 1) as usize; + let elem = bytes + .chunks(bytes_len) + .map(F::from_le_bytes_mod_order) + .collect::>(); + Ok(VariableLengthRescueCRHF::<_, 1>::evaluate(elem)?[0]) +} diff --git a/hotshot-types/src/message.rs b/hotshot-types/src/message.rs index b2ecca7840..558bebf37b 100644 --- a/hotshot-types/src/message.rs +++ b/hotshot-types/src/message.rs @@ -32,15 +32,15 @@ use crate::{ }, request_response::ProposalRequestPayload, simple_certificate::{ - DaCertificate, DaCertificate2, NextEpochQuorumCertificate2, QuorumCertificate2, - UpgradeCertificate, ViewSyncCommitCertificate, ViewSyncCommitCertificate2, - ViewSyncFinalizeCertificate, ViewSyncFinalizeCertificate2, ViewSyncPreCommitCertificate, - ViewSyncPreCommitCertificate2, + DaCertificate, DaCertificate2, ExtendedQuorumCertificate, NextEpochQuorumCertificate2, + QuorumCertificate2, UpgradeCertificate, ViewSyncCommitCertificate, + ViewSyncCommitCertificate2, ViewSyncFinalizeCertificate, ViewSyncFinalizeCertificate2, + ViewSyncPreCommitCertificate, ViewSyncPreCommitCertificate2, }, simple_vote::{ - DaVote, DaVote2, HasEpoch, QuorumVote, QuorumVote2, TimeoutVote, TimeoutVote2, UpgradeVote, - ViewSyncCommitVote, ViewSyncCommitVote2, ViewSyncFinalizeVote, ViewSyncFinalizeVote2, - ViewSyncPreCommitVote, ViewSyncPreCommitVote2, + DaVote, DaVote2, ExtendedQuorumVote, HasEpoch, QuorumVote, QuorumVote2, TimeoutVote, + TimeoutVote2, UpgradeVote, ViewSyncCommitVote, ViewSyncCommitVote2, ViewSyncFinalizeVote, + ViewSyncFinalizeVote2, ViewSyncPreCommitVote, ViewSyncPreCommitVote2, }, traits::{ block_contents::BlockHeader, @@ -245,10 +245,13 @@ pub enum GeneralConsensusMessage { /// Message for the next leader containing our highest QC ExtendedQc( - QuorumCertificate2, + ExtendedQuorumCertificate, NextEpochQuorumCertificate2, ), + /// Message with a epoch transition quorum vote + ExtendedQuorumVote(ExtendedQuorumVote), + /// Message with a view sync pre-commit vote ViewSyncPreCommitVote2(ViewSyncPreCommitVote2), @@ -374,8 +377,9 @@ impl SequencingMessage { } GeneralConsensusMessage::UpgradeProposal(message) => message.data.view_number(), GeneralConsensusMessage::UpgradeVote(message) => message.view_number(), - GeneralConsensusMessage::HighQc(qc) - | GeneralConsensusMessage::ExtendedQc(qc, _) => qc.view_number(), + GeneralConsensusMessage::HighQc(qc) => qc.view_number(), + GeneralConsensusMessage::ExtendedQc(qc, _) => qc.view_number(), + GeneralConsensusMessage::ExtendedQuorumVote(message) => message.view_number(), } } SequencingMessage::Da(da_message) => { @@ -445,8 +449,9 @@ impl SequencingMessage { } GeneralConsensusMessage::UpgradeProposal(message) => message.data.epoch(), GeneralConsensusMessage::UpgradeVote(message) => message.epoch(), - GeneralConsensusMessage::HighQc(qc) - | GeneralConsensusMessage::ExtendedQc(qc, _) => qc.epoch(), + GeneralConsensusMessage::HighQc(qc) => qc.epoch(), + GeneralConsensusMessage::ExtendedQc(qc, _) => qc.epoch(), + GeneralConsensusMessage::ExtendedQuorumVote(message) => message.epoch(), } } SequencingMessage::Da(da_message) => { diff --git a/hotshot-types/src/network.rs b/hotshot-types/src/network.rs index f2d35b6984..66bd807dbf 100644 --- a/hotshot-types/src/network.rs +++ b/hotshot-types/src/network.rs @@ -19,9 +19,7 @@ use crate::{ ORCHESTRATOR_DEFAULT_TRANSACTION_SIZE, REQUEST_DATA_DELAY, }, hotshot_config_file::HotShotConfigFile, - light_client::StateVerKey, - traits::signature_key::SignatureKey, - HotShotConfig, ValidatorConfig, + HotShotConfig, NodeType, ValidatorConfig, }; /// Configuration describing a libp2p node @@ -74,11 +72,11 @@ pub enum BuilderType { /// Node PeerConfig keys #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[serde(bound(deserialize = ""))] -pub struct PeerConfigKeys { +pub struct PeerConfigKeys { /// The peer's public key - pub stake_table_key: KEY, + pub stake_table_key: TYPES::SignatureKey, /// the peer's state public key - pub state_ver_key: StateVerKey, + pub state_ver_key: TYPES::StateSignatureKey, /// the peer's stake pub stake: u64, /// whether the node is a DA node @@ -109,7 +107,7 @@ impl Default for RandomBuilderConfig { /// a network configuration #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[serde(bound(deserialize = ""))] -pub struct NetworkConfig { +pub struct NetworkConfig { /// number of views to run pub rounds: usize, /// whether DA membership is determined by index. @@ -142,7 +140,7 @@ pub struct NetworkConfig { /// the libp2p config pub libp2p_config: Option, /// the hotshot config - pub config: HotShotConfig, + pub config: HotShotConfig, /// The address for the Push CDN's "marshal", A.K.A. load balancer pub cdn_marshal_address: Option, /// combined network config @@ -154,7 +152,7 @@ pub struct NetworkConfig { /// random builder config pub random_builder: Option, /// The list of public keys that are allowed to connect to the orchestrator - pub public_keys: Vec>, + pub public_keys: Vec>, } /// the source of the network config @@ -165,10 +163,13 @@ pub enum NetworkConfigSource { File, } -impl NetworkConfig { +impl NetworkConfig { /// Get a temporary node index for generating a validator config #[must_use] - pub fn generate_init_validator_config(cur_node_index: u16, is_da: bool) -> ValidatorConfig { + pub fn generate_init_validator_config( + cur_node_index: u16, + is_da: bool, + ) -> ValidatorConfig { // This cur_node_index is only used for key pair generation, it's not bound with the node, // later the node with the generated key pair will get a new node_index from orchestrator. ValidatorConfig::generated_from_seed_indexed([0u8; 32], cur_node_index.into(), 1, is_da) @@ -267,7 +268,7 @@ impl NetworkConfig { } } -impl Default for NetworkConfig { +impl Default for NetworkConfig { fn default() -> Self { Self { rounds: ORCHESTRATOR_DEFAULT_NUM_ROUNDS, @@ -279,7 +280,7 @@ impl Default for NetworkConfig { manual_start_password: None, libp2p_config: None, config: HotShotConfigFile::hotshot_config_5_nodes_10_da().into(), - key_type_name: std::any::type_name::().to_string(), + key_type_name: std::any::type_name::().to_string(), cdn_marshal_address: None, combined_network_config: None, next_view_timeout: 10, @@ -299,19 +300,19 @@ impl Default for NetworkConfig { #[serde_inline_default] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(bound(deserialize = ""))] -pub struct PublicKeysFile { +pub struct PublicKeysFile { /// The list of public keys that are allowed to connect to the orchestrator /// /// If nonempty, this list becomes the stake table and is used to determine DA membership (ignoring the node's request). #[serde(default)] - pub public_keys: Vec>, + pub public_keys: Vec>, } /// a network config stored in a file #[serde_inline_default] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(bound(deserialize = ""))] -pub struct NetworkConfigFile { +pub struct NetworkConfigFile { /// number of views to run #[serde_inline_default(ORCHESTRATOR_DEFAULT_NUM_ROUNDS)] pub rounds: usize, @@ -336,7 +337,7 @@ pub struct NetworkConfigFile { pub transaction_size: usize, /// the hotshot config file #[serde(default = "HotShotConfigFile::hotshot_config_5_nodes_10_da")] - pub config: HotShotConfigFile, + pub config: HotShotConfigFile, /// The address of the Push CDN's "marshal", A.K.A. load balancer #[serde(default)] pub cdn_marshal_address: Option, @@ -353,11 +354,11 @@ pub struct NetworkConfigFile { /// /// If nonempty, this list becomes the stake table and is used to determine DA membership (ignoring the node's request). #[serde(default)] - pub public_keys: Vec>, + pub public_keys: Vec>, } -impl From> for NetworkConfig { - fn from(val: NetworkConfigFile) -> Self { +impl From> for NetworkConfig { + fn from(val: NetworkConfigFile) -> Self { NetworkConfig { rounds: val.rounds, indexed_da: val.indexed_da, @@ -378,7 +379,7 @@ impl From> for NetworkConfig { bootstrap_nodes: Vec::new(), }), config: val.config.into(), - key_type_name: std::any::type_name::().to_string(), + key_type_name: std::any::type_name::().to_string(), cdn_marshal_address: val.cdn_marshal_address, combined_network_config: val.combined_network_config, commit_sha: String::new(), diff --git a/hotshot-types/src/signature_key.rs b/hotshot-types/src/signature_key.rs index 0115196500..c00a2996c8 100644 --- a/hotshot-types/src/signature_key.rs +++ b/hotshot-types/src/signature_key.rs @@ -229,4 +229,13 @@ impl StateSignatureKey for SchnorrPubKey { ) -> bool { SchnorrSignatureScheme::verify(&(), self, state, signature).is_ok() } + + fn generated_from_seed_indexed(seed: [u8; 32], index: u64) -> (Self, Self::StatePrivateKey) { + let mut hasher = blake3::Hasher::new(); + hasher.update(&seed); + hasher.update(&index.to_le_bytes()); + let new_seed = *hasher.finalize().as_bytes(); + let kp = jf_signature::schnorr::KeyPair::generate(&mut ChaCha20Rng::from_seed(new_seed)); + (kp.ver_key(), kp.sign_key()) + } } diff --git a/hotshot-types/src/simple_certificate.rs b/hotshot-types/src/simple_certificate.rs index 82092027b5..212485843c 100644 --- a/hotshot-types/src/simple_certificate.rs +++ b/hotshot-types/src/simple_certificate.rs @@ -22,6 +22,7 @@ use serde::{Deserialize, Serialize}; use crate::{ data::serialize_signature2, + light_client::LightClientState, message::UpgradeLock, simple_vote::{ DaData, DaData2, HasEpoch, NextEpochQuorumData2, QuorumData, QuorumData2, QuorumMarker, @@ -32,7 +33,7 @@ use crate::{ traits::{ election::Membership, node_implementation::{ConsensusTime, NodeType, Versions}, - signature_key::SignatureKey, + signature_key::{SignatureKey, StateSignatureKey}, }, vote::{Certificate, HasViewNumber}, PeerConfig, StakeTableEntries, @@ -193,7 +194,7 @@ impl> Certificate membership: &MEMBERSHIP, pub_key: &TYPES::SignatureKey, epoch: Option, - ) -> Option> { + ) -> Option> { membership.da_stake(pub_key, epoch) } @@ -201,7 +202,7 @@ impl> Certificate fn stake_table>( membership: &MEMBERSHIP, epoch: Option, - ) -> Vec> { + ) -> Vec> { membership.da_stake_table(epoch) } /// Proxy's to `Membership.da_total_nodes` @@ -282,7 +283,7 @@ impl> Certificate, - ) -> Option> { + ) -> Option> { membership.da_stake(pub_key, epoch) } @@ -290,7 +291,7 @@ impl> Certificate>( membership: &MEMBERSHIP, epoch: Option, - ) -> Vec> { + ) -> Vec> { membership.da_stake_table(epoch) } /// Proxy's to `Membership.da_total_nodes` @@ -380,14 +381,14 @@ impl< membership: &MEMBERSHIP, pub_key: &TYPES::SignatureKey, epoch: Option, - ) -> Option> { + ) -> Option> { membership.stake(pub_key, epoch) } fn stake_table>( membership: &MEMBERSHIP, epoch: Option, - ) -> Vec> { + ) -> Vec> { membership.stake_table(epoch) } @@ -788,3 +789,73 @@ pub type ViewSyncFinalizeCertificate2 = /// Type alias for a `UpgradeCertificate`, which is a `SimpleCertificate` of `UpgradeProposalData` pub type UpgradeCertificate = SimpleCertificate, UpgradeThreshold>; + +/// Type for light client state update certificate +#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)] +pub struct LightClientStateUpdateCertificate { + /// The epoch of the light client state + pub epoch: TYPES::Epoch, + /// Light client state for epoch transition + pub light_client_state: LightClientState, + /// Signatures to the light client state + pub signatures: Vec<( + TYPES::StateSignatureKey, + ::StateSignature, + )>, +} + +impl HasViewNumber for LightClientStateUpdateCertificate { + fn view_number(&self) -> TYPES::View { + TYPES::View::new(self.light_client_state.view_number) + } +} + +impl HasEpoch for LightClientStateUpdateCertificate { + fn epoch(&self) -> Option { + Some(self.epoch) + } +} + +impl LightClientStateUpdateCertificate { + pub fn genesis() -> Self { + Self { + epoch: TYPES::Epoch::genesis(), + light_client_state: Default::default(), + signatures: vec![], + } + } +} + +/// Type for light client state update certificate +#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)] +#[serde(bound = "QuorumCertificate2: Serialize + for<'a> Deserialize<'a>")] +pub struct ExtendedQuorumCertificate { + /// The quorum certificate + pub qc: QuorumCertificate2, + /// The light client state update certificate + pub state_cert: LightClientStateUpdateCertificate, +} + +impl HasViewNumber for ExtendedQuorumCertificate { + fn view_number(&self) -> TYPES::View { + self.qc.view_number() + } +} + +impl HasEpoch for ExtendedQuorumCertificate { + fn epoch(&self) -> Option { + self.qc.epoch() + } +} + +impl ExtendedQuorumCertificate { + pub async fn genesis( + validated_state: &TYPES::ValidatedState, + instance_state: &TYPES::InstanceState, + ) -> Self { + Self { + qc: QuorumCertificate2::::genesis::(validated_state, instance_state).await, + state_cert: LightClientStateUpdateCertificate::::genesis(), + } + } +} diff --git a/hotshot-types/src/simple_vote.rs b/hotshot-types/src/simple_vote.rs index 547db100a6..9ff95c4476 100644 --- a/hotshot-types/src/simple_vote.rs +++ b/hotshot-types/src/simple_vote.rs @@ -21,10 +21,11 @@ use vbs::version::Version; use crate::{ data::VidCommitment, data::{Leaf, Leaf2}, + light_client::LightClientState, message::UpgradeLock, traits::{ - node_implementation::{NodeType, Versions}, - signature_key::SignatureKey, + node_implementation::{ConsensusTime, NodeType, Versions}, + signature_key::{SignatureKey, StateSignatureKey}, }, vote::{HasViewNumber, Vote}, }; @@ -920,3 +921,46 @@ impl From> for NextEpochQuorumVote2 { } } } + +/// Type for light client state update vote +#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)] +pub struct LightClientStateUpdateVote { + /// The epoch number + pub epoch: TYPES::Epoch, + /// The light client state + pub light_client_state: LightClientState, + /// The signature to the light client state + pub signature: ::StateSignature, +} + +impl HasViewNumber for LightClientStateUpdateVote { + fn view_number(&self) -> TYPES::View { + TYPES::View::new(self.light_client_state.view_number) + } +} + +impl HasEpoch for LightClientStateUpdateVote { + fn epoch(&self) -> Option { + Some(self.epoch) + } +} + +/// Type for extended quorum vote +#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)] +#[serde(bound(deserialize = "QuorumVote2: for<'a> Deserialize<'a>"))] +pub struct ExtendedQuorumVote { + pub vote: QuorumVote2, + pub state_vote: LightClientStateUpdateVote, +} + +impl HasViewNumber for ExtendedQuorumVote { + fn view_number(&self) -> TYPES::View { + self.vote.view_number() + } +} + +impl HasEpoch for ExtendedQuorumVote { + fn epoch(&self) -> Option { + self.vote.epoch() + } +} diff --git a/hotshot-types/src/traits/block_contents.rs b/hotshot-types/src/traits/block_contents.rs index 781a7b7673..0ace2ffd3a 100644 --- a/hotshot-types/src/traits/block_contents.rs +++ b/hotshot-types/src/traits/block_contents.rs @@ -25,6 +25,7 @@ use vbs::version::Version; use super::signature_key::BuilderSignatureKey; use crate::{ data::{Leaf2, VidCommitment}, + light_client::LightClientState, traits::{node_implementation::NodeType, states::InstanceState, ValidatedState}, utils::BuilderCommitment, }; @@ -214,4 +215,7 @@ pub trait BlockHeader: /// Get the results of the auction for this Header. Only used in post-marketplace versions fn get_auction_results(&self) -> Option; + + /// Get the light client state + fn get_light_client_state(&self) -> anyhow::Result; } diff --git a/hotshot-types/src/traits/consensus_api.rs b/hotshot-types/src/traits/consensus_api.rs index 4c4daff9a7..0d0fe5133f 100644 --- a/hotshot-types/src/traits/consensus_api.rs +++ b/hotshot-types/src/traits/consensus_api.rs @@ -14,7 +14,7 @@ use crate::{ event::Event, traits::{ node_implementation::{NodeImplementation, NodeType}, - signature_key::SignatureKey, + signature_key::{SignatureKey, StateSignatureKey}, }, }; @@ -34,6 +34,11 @@ pub trait ConsensusApi>: Send + Sy /// Get a reference to the private key. fn private_key(&self) -> &::PrivateKey; + /// Get a reference to the light client signing key. + fn state_private_key( + &self, + ) -> &::StatePrivateKey; + /// Notify the system of an event within `hotshot-consensus`. async fn send_event(&self, event: Event); } diff --git a/hotshot-types/src/traits/election.rs b/hotshot-types/src/traits/election.rs index 853bcfae5f..d14ca0a224 100644 --- a/hotshot-types/src/traits/election.rs +++ b/hotshot-types/src/traits/election.rs @@ -22,15 +22,15 @@ pub trait Membership: Debug + Send + Sync { fn new( // Note: eligible_leaders is currently a hack because the DA leader == the quorum leader // but they should not have voting power. - stake_committee_members: Vec>, - da_committee_members: Vec>, + stake_committee_members: Vec>, + da_committee_members: Vec>, ) -> Self; /// Get all participants in the committee (including their stake) for a specific epoch - fn stake_table(&self, epoch: Option) -> Vec>; + fn stake_table(&self, epoch: Option) -> Vec>; /// Get all participants in the committee (including their stake) for a specific epoch - fn da_stake_table(&self, epoch: Option) -> Vec>; + fn da_stake_table(&self, epoch: Option) -> Vec>; /// Get all participants in the committee for a specific view for a specific epoch fn committee_members( @@ -59,7 +59,7 @@ pub trait Membership: Debug + Send + Sync { &self, pub_key: &TYPES::SignatureKey, epoch: Option, - ) -> Option>; + ) -> Option>; /// Get the DA stake table entry for a public key, returns `None` if the /// key is not in the table for a specific epoch @@ -67,7 +67,7 @@ pub trait Membership: Debug + Send + Sync { &self, pub_key: &TYPES::SignatureKey, epoch: Option, - ) -> Option>; + ) -> Option>; /// See if a node has stake in the committee in a specific epoch fn has_stake(&self, pub_key: &TYPES::SignatureKey, epoch: Option) -> bool; diff --git a/hotshot-types/src/traits/signature_key.rs b/hotshot-types/src/traits/signature_key.rs index ad251ee2c0..4ae8dd9457 100644 --- a/hotshot-types/src/traits/signature_key.rs +++ b/hotshot-types/src/traits/signature_key.rs @@ -409,4 +409,7 @@ pub trait StateSignatureKey: signature: &Self::StateSignature, state: &LightClientStateMsg, ) -> bool; + + /// Generate a new key pair + fn generated_from_seed_indexed(seed: [u8; 32], index: u64) -> (Self, Self::StatePrivateKey); } diff --git a/hotshot-types/src/vote.rs b/hotshot-types/src/vote.rs index 789615ab80..767c463dcd 100644 --- a/hotshot-types/src/vote.rs +++ b/hotshot-types/src/vote.rs @@ -21,13 +21,14 @@ use primitive_types::U256; use tracing::error; use crate::{ + light_client::LightClientState, message::UpgradeLock, - simple_certificate::Threshold, - simple_vote::{VersionedVoteData, Voteable}, + simple_certificate::{LightClientStateUpdateCertificate, Threshold}, + simple_vote::{LightClientStateUpdateVote, VersionedVoteData, Voteable}, traits::{ election::Membership, node_implementation::{NodeType, Versions}, - signature_key::{SignatureKey, StakeTableEntryType}, + signature_key::{SignatureKey, StakeTableEntryType, StateSignatureKey}, }, PeerConfig, StakeTableEntries, }; @@ -92,7 +93,7 @@ pub trait Certificate: HasViewNumber { fn stake_table>( membership: &MEMBERSHIP, epoch: Option, - ) -> Vec>; + ) -> Vec>; /// Get Total Nodes from Membership implementation. fn total_nodes>( @@ -105,7 +106,7 @@ pub trait Certificate: HasViewNumber { membership: &MEMBERSHIP, pub_key: &TYPES::SignatureKey, epoch: Option, - ) -> Option>; + ) -> Option>; /// Get the commitment which was voted on fn data(&self) -> &Self::Voteable; @@ -253,3 +254,72 @@ impl< /// Mapping of commitments to vote tokens by key. type VoteMap2 = HashMap)>; + +/// Accumulator for light client state update vote +#[allow(clippy::type_complexity)] +pub struct LightClientStateUpdateVoteAccumulator { + pub vote_outcomes: HashMap< + LightClientState, + ( + U256, + HashMap< + TYPES::StateSignatureKey, + ::StateSignature, + >, + ), + >, +} + +impl LightClientStateUpdateVoteAccumulator { + /// Add a vote to the total accumulated votes for the given epoch. + /// Returns the accumulator or the certificate if we + /// have accumulated enough votes to exceed the threshold for creating a certificate. + pub fn accumulate( + &mut self, + epoch: TYPES::Epoch, + vote: &LightClientStateUpdateVote, + key: &TYPES::StateSignatureKey, + stake_table: &[PeerConfig], + threshold: U256, + ) -> Option> { + // Find the signer + if let Some(key_index) = stake_table + .iter() + .position(|config| &config.state_ver_key == key) + { + // Verify the vote validity + let state_msg = (&vote.light_client_state).into(); + if !key.verify_state_sig(&vote.signature, &state_msg) { + error!("Invalid light client state update vote {:?}", vote); + return None; + } + let (total_stake_casted, vote_map) = self + .vote_outcomes + .entry(vote.light_client_state.clone()) + .or_insert_with(|| (U256::from(0), HashMap::new())); + + // Check for duplicate vote + if vote_map.contains_key(key) { + tracing::warn!("Duplicate vote (key: {:?}, vote: {:?})", key, vote); + return None; + } + + *total_stake_casted += stake_table[key_index].stake_table_entry.stake(); + vote_map.insert(key.clone(), vote.signature.clone()); + + if *total_stake_casted >= threshold { + return Some(LightClientStateUpdateCertificate { + epoch, + light_client_state: vote.light_client_state.clone(), + signatures: Vec::from_iter( + vote_map.iter().map(|(k, v)| (k.clone(), v.clone())), + ), + }); + } + None + } else { + error!("Light client state update vote with invalid key {:?}", key); + None + } + } +} diff --git a/hotshot/src/lib.rs b/hotshot/src/lib.rs index 84400a2364..bd753b803a 100644 --- a/hotshot/src/lib.rs +++ b/hotshot/src/lib.rs @@ -15,7 +15,11 @@ use committable::Committable; use futures::future::{select, Either}; use hotshot_types::{ message::UpgradeLock, - traits::{block_contents::BlockHeader, network::BroadcastDelay, node_implementation::Versions}, + simple_certificate::LightClientStateUpdateCertificate, + traits::{ + block_contents::BlockHeader, network::BroadcastDelay, node_implementation::Versions, + signature_key::StateSignatureKey, + }, }; use rand::Rng; use url::Url; @@ -104,8 +108,11 @@ pub struct SystemContext, V: Versi /// The private key of this node private_key: ::PrivateKey, + /// The private key to sign the light client state + state_private_key: ::StatePrivateKey, + /// Configuration items for this hotshot instance - pub config: HotShotConfig, + pub config: HotShotConfig, /// The underlying network pub network: Arc, @@ -164,6 +171,7 @@ impl, V: Versions> Clone Self { public_key: self.public_key.clone(), private_key: self.private_key.clone(), + state_private_key: self.state_private_key.clone(), config: self.config.clone(), network: Arc::clone(&self.network), memberships: Arc::clone(&self.memberships), @@ -200,8 +208,9 @@ impl, V: Versions> SystemContext::PrivateKey, + state_private_key: ::StatePrivateKey, nonce: u64, - config: HotShotConfig, + config: HotShotConfig, memberships: Arc>, network: Arc, initializer: HotShotInitializer, @@ -229,6 +238,7 @@ impl, V: Versions> SystemContext, V: Versions> SystemContext::PrivateKey, + state_private_key: ::StatePrivateKey, nonce: u64, - config: HotShotConfig, + config: HotShotConfig, memberships: Arc>, network: Arc, initializer: HotShotInitializer, @@ -347,6 +358,7 @@ impl, V: Versions> SystemContext, V: Versions> SystemContext, V: Versions> SystemContext::PrivateKey, + state_private_key: ::StatePrivateKey, node_id: u64, - config: HotShotConfig, + config: HotShotConfig, memberships: Arc>, network: Arc, initializer: HotShotInitializer, @@ -637,6 +651,7 @@ impl, V: Versions> SystemContext::PrivateKey, + state_private_key: ::StatePrivateKey, nonce: u64, - config: HotShotConfig, + config: HotShotConfig, memberships: Arc>, network: Arc, initializer: HotShotInitializer, @@ -797,6 +813,7 @@ where let left_system_context = SystemContext::new( public_key.clone(), private_key.clone(), + state_private_key.clone(), nonce, config.clone(), Arc::clone(&memberships), @@ -810,6 +827,7 @@ where let right_system_context = SystemContext::new( public_key, private_key, + state_private_key, nonce, config, memberships, @@ -984,6 +1002,12 @@ impl, V: Versions> ConsensusApi &::PrivateKey { &self.hotshot.private_key } + + fn state_private_key( + &self, + ) -> &::StatePrivateKey { + &self.hotshot.state_private_key + } } #[derive(Clone)] @@ -1037,6 +1061,9 @@ pub struct HotShotInitializer { /// Saved VID shares pub saved_vid_shares: VidShares, + + /// The last formed light client state update certificate + pub state_cert: LightClientStateUpdateCertificate, } impl HotShotInitializer { @@ -1066,6 +1093,7 @@ impl HotShotInitializer { instance_state, saved_vid_shares: BTreeMap::new(), epoch_height, + state_cert: LightClientStateUpdateCertificate::::genesis(), }) } @@ -1126,6 +1154,7 @@ impl HotShotInitializer { saved_proposals: BTreeMap>>, saved_vid_shares: VidShares, decided_upgrade_certificate: Option>, + state_cert: LightClientStateUpdateCertificate, ) -> Self { let anchor_state = Arc::new(TYPES::ValidatedState::from_header( anchor_leaf.block_header(), @@ -1148,6 +1177,7 @@ impl HotShotInitializer { decided_upgrade_certificate, undecided_leaves: BTreeMap::new(), undecided_state: BTreeMap::new(), + state_cert, }; initializer.update_undecided() diff --git a/hotshot/src/tasks/mod.rs b/hotshot/src/tasks/mod.rs index e69ed4aaa2..04e3f5f49e 100644 --- a/hotshot/src/tasks/mod.rs +++ b/hotshot/src/tasks/mod.rs @@ -46,7 +46,8 @@ use vbs::version::StaticVersionType; use crate::{ genesis_epoch_from_version, tasks::task_state::CreateTaskState, types::SystemContextHandle, ConsensusApi, ConsensusMetricsValue, ConsensusTaskRegistry, HotShotConfig, HotShotInitializer, - MarketplaceConfig, NetworkTaskRegistry, SignatureKey, SystemContext, Versions, + MarketplaceConfig, NetworkTaskRegistry, SignatureKey, StateSignatureKey, SystemContext, + Versions, }; /// event for global event stream @@ -320,8 +321,9 @@ where &'static mut self, public_key: TYPES::SignatureKey, private_key: ::PrivateKey, + state_private_key: ::StatePrivateKey, nonce: u64, - config: HotShotConfig, + config: HotShotConfig, memberships: Arc>, network: Arc, initializer: HotShotInitializer, @@ -333,6 +335,7 @@ where let hotshot = SystemContext::new( public_key, private_key, + state_private_key, nonce, config, memberships, diff --git a/hotshot/src/tasks/task_state.rs b/hotshot/src/tasks/task_state.rs index b0c1b9a11d..4e53a7f6ce 100644 --- a/hotshot/src/tasks/task_state.rs +++ b/hotshot/src/tasks/task_state.rs @@ -232,6 +232,7 @@ impl, V: Versions> CreateTaskState Self { public_key: handle.public_key().clone(), private_key: handle.private_key().clone(), + state_private_key: handle.state_private_key().clone(), consensus: OuterConsensus::new(consensus), instance_state: handle.hotshot.instance_state(), latest_voted_view: handle.cur_view().await, @@ -315,6 +316,7 @@ impl, V: Versions> CreateTaskState network: Arc::clone(&handle.hotshot.network), membership: Arc::clone(&handle.hotshot.memberships), vote_collectors: BTreeMap::default(), + extended_quorum_vote_collectors: BTreeMap::default(), next_epoch_vote_collectors: BTreeMap::default(), timeout_vote_collectors: BTreeMap::default(), cur_view: handle.cur_view().await, diff --git a/hotshot/src/traits/election/randomized_committee.rs b/hotshot/src/traits/election/randomized_committee.rs index dca2593d33..c88e7abc2b 100644 --- a/hotshot/src/traits/election/randomized_committee.rs +++ b/hotshot/src/traits/election/randomized_committee.rs @@ -32,76 +32,70 @@ pub struct Committee { /// The nodes eligible for leadership. /// NOTE: This is currently a hack because the DA leader needs to be the quorum /// leader but without voting rights. - eligible_leaders: Vec>, + eligible_leaders: Vec>, /// The nodes on the committee and their stake - stake_table: Vec>, + stake_table: Vec>, /// The nodes on the committee and their stake - da_stake_table: Vec>, + da_stake_table: Vec>, /// Stake tables randomized with the DRB, used (only) for leader election randomized_committee: RandomizedCommittee<::StakeTableEntry>, /// The nodes on the committee and their stake, indexed by public key - indexed_stake_table: BTreeMap>, + indexed_stake_table: BTreeMap>, /// The nodes on the committee and their stake, indexed by public key - indexed_da_stake_table: BTreeMap>, + indexed_da_stake_table: BTreeMap>, } impl Membership for Committee { type Error = hotshot_utils::anytrace::Error; /// Create a new election - fn new( - committee_members: Vec::SignatureKey>>, - da_members: Vec::SignatureKey>>, - ) -> Self { + fn new(committee_members: Vec>, da_members: Vec>) -> Self { // For each eligible leader, get the stake table entry - let eligible_leaders: Vec::SignatureKey>> = - committee_members - .iter() - .filter(|&member| member.stake_table_entry.stake() > U256::zero()) - .cloned() - .collect(); + let eligible_leaders: Vec> = committee_members + .iter() + .filter(|&member| member.stake_table_entry.stake() > U256::zero()) + .cloned() + .collect(); // For each member, get the stake table entry - let members: Vec::SignatureKey>> = committee_members + let members: Vec> = committee_members .iter() .filter(|&member| member.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // For each member, get the stake table entry - let da_members: Vec::SignatureKey>> = da_members + let da_members: Vec> = da_members .iter() .filter(|&member| member.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // Index the stake table by public key - let indexed_stake_table: BTreeMap> = - members - .iter() - .map(|config| { - ( - TYPES::SignatureKey::public_key(&config.stake_table_entry), - config.clone(), - ) - }) - .collect(); + let indexed_stake_table: BTreeMap> = members + .iter() + .map(|config| { + ( + TYPES::SignatureKey::public_key(&config.stake_table_entry), + config.clone(), + ) + }) + .collect(); // Index the stake table by public key - let indexed_da_stake_table: BTreeMap> = - da_members - .iter() - .map(|config| { - ( - TYPES::SignatureKey::public_key(&config.stake_table_entry), - config.clone(), - ) - }) - .collect(); + let indexed_da_stake_table: BTreeMap> = da_members + .iter() + .map(|config| { + ( + TYPES::SignatureKey::public_key(&config.stake_table_entry), + config.clone(), + ) + }) + .collect(); // We use a constant value of `[0u8; 32]` for the drb, since this is just meant to be used in tests let randomized_committee = generate_stake_cdf( @@ -124,18 +118,12 @@ impl Membership for Committee { } /// Get the stake table for the current view - fn stake_table( - &self, - _epoch: Option<::Epoch>, - ) -> Vec> { + fn stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.stake_table.clone() } /// Get the stake table for the current view - fn da_stake_table( - &self, - _epoch: Option<::Epoch>, - ) -> Vec> { + fn da_stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.da_stake_table.clone() } @@ -180,7 +168,7 @@ impl Membership for Committee { &self, pub_key: &::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_stake_table.get(pub_key).cloned() } @@ -190,7 +178,7 @@ impl Membership for Committee { &self, pub_key: &::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_da_stake_table.get(pub_key).cloned() } diff --git a/hotshot/src/traits/election/randomized_committee_members.rs b/hotshot/src/traits/election/randomized_committee_members.rs index 78d5881005..72099a725d 100644 --- a/hotshot/src/traits/election/randomized_committee_members.rs +++ b/hotshot/src/traits/election/randomized_committee_members.rs @@ -32,19 +32,19 @@ pub struct RandomizedCommitteeMembers { /// The nodes eligible for leadership. /// NOTE: This is currently a hack because the DA leader needs to be the quorum /// leader but without voting rights. - eligible_leaders: Vec>, + eligible_leaders: Vec>, /// The nodes on the committee and their stake - stake_table: Vec>, + stake_table: Vec>, /// The nodes on the da committee and their stake - da_stake_table: Vec>, + da_stake_table: Vec>, /// The nodes on the committee and their stake, indexed by public key - indexed_stake_table: BTreeMap>, + indexed_stake_table: BTreeMap>, /// The nodes on the da committee and their stake, indexed by public key - indexed_da_stake_table: BTreeMap>, + indexed_da_stake_table: BTreeMap>, /// Phantom _pd: PhantomData, @@ -97,10 +97,7 @@ impl Membership { type Error = hotshot_utils::anytrace::Error; /// Create a new election - fn new( - committee_members: Vec::SignatureKey>>, - da_members: Vec::SignatureKey>>, - ) -> Self { + fn new(committee_members: Vec>, da_members: Vec>) -> Self { // For each eligible leader, get the stake table entry let eligible_leaders = committee_members .iter() @@ -109,14 +106,14 @@ impl Membership .collect(); // For each member, get the stake table entry - let members: Vec::SignatureKey>> = committee_members + let members: Vec> = committee_members .iter() .filter(|&entry| entry.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // For each da member, get the stake table entry - let da_members: Vec::SignatureKey>> = da_members + let da_members: Vec> = da_members .iter() .filter(|&entry| entry.stake_table_entry.stake() > U256::zero()) .cloned() @@ -159,10 +156,7 @@ impl Membership } /// Get the stake table for the current view - fn stake_table( - &self, - epoch: Option<::Epoch>, - ) -> Vec> { + fn stake_table(&self, epoch: Option<::Epoch>) -> Vec> { if let Some(epoch) = epoch { let filter = self.make_quorum_filter(epoch); //self.stake_table.clone()s @@ -178,10 +172,7 @@ impl Membership } /// Get the da stake table for the current view - fn da_stake_table( - &self, - epoch: Option<::Epoch>, - ) -> Vec> { + fn da_stake_table(&self, epoch: Option<::Epoch>) -> Vec> { if let Some(epoch) = epoch { let filter = self.make_da_quorum_filter(epoch); //self.stake_table.clone()s @@ -254,7 +245,7 @@ impl Membership &self, pub_key: &::SignatureKey, epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { if let Some(epoch) = epoch { let filter = self.make_quorum_filter(epoch); let actual_members: BTreeSet<_> = self @@ -282,7 +273,7 @@ impl Membership &self, pub_key: &::SignatureKey, epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { if let Some(epoch) = epoch { let filter = self.make_da_quorum_filter(epoch); let actual_members: BTreeSet<_> = self diff --git a/hotshot/src/traits/election/static_committee.rs b/hotshot/src/traits/election/static_committee.rs index 6b6232a5a6..0a5c79ca60 100644 --- a/hotshot/src/traits/election/static_committee.rs +++ b/hotshot/src/traits/election/static_committee.rs @@ -28,44 +28,40 @@ pub struct StaticCommittee { /// The nodes eligible for leadership. /// NOTE: This is currently a hack because the DA leader needs to be the quorum /// leader but without voting rights. - eligible_leaders: Vec>, + eligible_leaders: Vec>, /// The nodes on the committee and their stake - stake_table: Vec>, + stake_table: Vec>, /// The nodes on the committee and their stake - da_stake_table: Vec>, + da_stake_table: Vec>, /// The nodes on the committee and their stake, indexed by public key - indexed_stake_table: BTreeMap>, + indexed_stake_table: BTreeMap>, /// The nodes on the committee and their stake, indexed by public key - indexed_da_stake_table: BTreeMap>, + indexed_da_stake_table: BTreeMap>, } impl Membership for StaticCommittee { type Error = hotshot_utils::anytrace::Error; /// Create a new election - fn new( - committee_members: Vec::SignatureKey>>, - da_members: Vec::SignatureKey>>, - ) -> Self { + fn new(committee_members: Vec>, da_members: Vec>) -> Self { // For each eligible leader, get the stake table entry - let eligible_leaders: Vec::SignatureKey>> = - committee_members - .clone() - .into_iter() - .filter(|member| member.stake_table_entry.stake() > U256::zero()) - .collect(); + let eligible_leaders: Vec> = committee_members + .clone() + .into_iter() + .filter(|member| member.stake_table_entry.stake() > U256::zero()) + .collect(); // For each member, get the stake table entry - let members: Vec::SignatureKey>> = committee_members + let members: Vec> = committee_members .into_iter() .filter(|member| member.stake_table_entry.stake() > U256::zero()) .collect(); // For each member, get the stake table entry - let da_members: Vec::SignatureKey>> = da_members + let da_members: Vec> = da_members .into_iter() .filter(|member| member.stake_table_entry.stake() > U256::zero()) .collect(); @@ -102,18 +98,12 @@ impl Membership for StaticCommittee { } /// Get the stake table for the current view - fn stake_table( - &self, - _epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + fn stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.stake_table.clone() } /// Get the stake table for the current view - fn da_stake_table( - &self, - _epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + fn da_stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.da_stake_table.clone() } @@ -158,7 +148,7 @@ impl Membership for StaticCommittee { &self, pub_key: &::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_stake_table.get(pub_key).cloned() } @@ -168,7 +158,7 @@ impl Membership for StaticCommittee { &self, pub_key: &::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_da_stake_table.get(pub_key).cloned() } diff --git a/hotshot/src/traits/election/static_committee_leader_two_views.rs b/hotshot/src/traits/election/static_committee_leader_two_views.rs index 37ce00dcd5..c1a30f4fd1 100644 --- a/hotshot/src/traits/election/static_committee_leader_two_views.rs +++ b/hotshot/src/traits/election/static_committee_leader_two_views.rs @@ -27,72 +27,67 @@ pub struct StaticCommitteeLeaderForTwoViews { /// The nodes eligible for leadership. /// NOTE: This is currently a hack because the DA leader needs to be the quorum /// leader but without voting rights. - eligible_leaders: Vec>, + eligible_leaders: Vec>, /// The nodes on the committee and their stake - stake_table: Vec>, + stake_table: Vec>, /// The nodes on the committee and their stake - da_stake_table: Vec>, + da_stake_table: Vec>, /// The nodes on the committee and their stake, indexed by public key - indexed_stake_table: BTreeMap>, + indexed_stake_table: BTreeMap>, /// The nodes on the committee and their stake, indexed by public key - indexed_da_stake_table: BTreeMap>, + indexed_da_stake_table: BTreeMap>, } impl Membership for StaticCommitteeLeaderForTwoViews { type Error = hotshot_utils::anytrace::Error; /// Create a new election - fn new( - committee_members: Vec::SignatureKey>>, - da_members: Vec::SignatureKey>>, - ) -> Self { + fn new(committee_members: Vec>, da_members: Vec>) -> Self { // For each eligible leader, get the stake table entry - let eligible_leaders: Vec> = committee_members + let eligible_leaders: Vec> = committee_members .iter() .filter(|&member| member.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // For each member, get the stake table entry - let members: Vec> = committee_members + let members: Vec> = committee_members .iter() .filter(|&member| member.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // For each member, get the stake table entry - let da_members: Vec> = da_members + let da_members: Vec> = da_members .iter() .filter(|&member| member.stake_table_entry.stake() > U256::zero()) .cloned() .collect(); // Index the stake table by public key - let indexed_stake_table: BTreeMap> = - members - .iter() - .map(|member| { - ( - TYPES::SignatureKey::public_key(&member.stake_table_entry), - member.clone(), - ) - }) - .collect(); + let indexed_stake_table: BTreeMap> = members + .iter() + .map(|member| { + ( + TYPES::SignatureKey::public_key(&member.stake_table_entry), + member.clone(), + ) + }) + .collect(); // Index the stake table by public key - let indexed_da_stake_table: BTreeMap> = - da_members - .iter() - .map(|member| { - ( - TYPES::SignatureKey::public_key(&member.stake_table_entry), - member.clone(), - ) - }) - .collect(); + let indexed_da_stake_table: BTreeMap> = da_members + .iter() + .map(|member| { + ( + TYPES::SignatureKey::public_key(&member.stake_table_entry), + member.clone(), + ) + }) + .collect(); Self { eligible_leaders, @@ -104,18 +99,12 @@ impl Membership for StaticCommitteeLeaderForTwoViews::Epoch>, - ) -> Vec::SignatureKey>> { + fn stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.stake_table.clone() } /// Get the stake table for the current view - fn da_stake_table( - &self, - _epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + fn da_stake_table(&self, _epoch: Option<::Epoch>) -> Vec> { self.da_stake_table.clone() } @@ -160,7 +149,7 @@ impl Membership for StaticCommitteeLeaderForTwoViews::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_stake_table.get(pub_key).cloned() } @@ -170,7 +159,7 @@ impl Membership for StaticCommitteeLeaderForTwoViews::SignatureKey, _epoch: Option<::Epoch>, - ) -> Option> { + ) -> Option> { // Only return the stake if it is above zero self.indexed_da_stake_table.get(pub_key).cloned() } diff --git a/hotshot/src/traits/election/two_static_committees.rs b/hotshot/src/traits/election/two_static_committees.rs index 205a127029..b0566c2c70 100644 --- a/hotshot/src/traits/election/two_static_committees.rs +++ b/hotshot/src/traits/election/two_static_committees.rs @@ -22,21 +22,15 @@ use std::{ }; /// Tuple type for eligible leaders -type EligibleLeaders = ( - Vec::SignatureKey>>, - Vec::SignatureKey>>, -); +type EligibleLeaders = (Vec>, Vec>); /// Tuple type for stake tables -type StakeTables = ( - Vec::SignatureKey>>, - Vec::SignatureKey>>, -); +type StakeTables = (Vec>, Vec>); /// Tuple type for indexed stake tables type IndexedStakeTables = ( - BTreeMap<::SignatureKey, PeerConfig<::SignatureKey>>, - BTreeMap<::SignatureKey, PeerConfig<::SignatureKey>>, + BTreeMap<::SignatureKey, PeerConfig>, + BTreeMap<::SignatureKey, PeerConfig>, ); #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -63,17 +57,13 @@ pub struct TwoStaticCommittees { impl Membership for TwoStaticCommittees { type Error = hotshot_utils::anytrace::Error; /// Create a new election - fn new( - committee_members: Vec::SignatureKey>>, - da_members: Vec::SignatureKey>>, - ) -> Self { + fn new(committee_members: Vec>, da_members: Vec>) -> Self { // For each eligible leader, get the stake table entry - let eligible_leaders: Vec::SignatureKey>> = - committee_members - .clone() - .into_iter() - .filter(|member| member.stake_table_entry.stake() > U256::zero()) - .collect(); + let eligible_leaders: Vec> = committee_members + .clone() + .into_iter() + .filter(|member| member.stake_table_entry.stake() > U256::zero()) + .collect(); let eligible_leaders1 = eligible_leaders .iter() @@ -89,19 +79,19 @@ impl Membership for TwoStaticCommittees { .collect(); // For each member, get the stake table entry - let members: Vec::SignatureKey>> = committee_members + let members: Vec> = committee_members .clone() .into_iter() .filter(|member| member.stake_table_entry.stake() > U256::zero()) .collect(); - let members1: Vec::SignatureKey>> = members + let members1: Vec> = members .iter() .enumerate() .filter(|(idx, _)| idx % 2 == 0) .map(|(_, leader)| leader.clone()) .collect(); - let members2: Vec::SignatureKey>> = members + let members2: Vec> = members .iter() .enumerate() .filter(|(idx, _)| idx % 2 == 1) @@ -109,19 +99,19 @@ impl Membership for TwoStaticCommittees { .collect(); // For each member, get the stake table entry - let da_members: Vec::SignatureKey>> = da_members + let da_members: Vec> = da_members .clone() .into_iter() .filter(|member| member.stake_table_entry.stake() > U256::zero()) .collect(); - let da_members1: Vec::SignatureKey>> = da_members + let da_members1: Vec> = da_members .iter() .enumerate() .filter(|(idx, _)| idx % 2 == 0) .map(|(_, leader)| leader.clone()) .collect(); - let da_members2: Vec::SignatureKey>> = da_members + let da_members2: Vec> = da_members .iter() .enumerate() .filter(|(idx, _)| idx % 2 == 1) @@ -180,10 +170,7 @@ impl Membership for TwoStaticCommittees { } /// Get the stake table for the current view - fn stake_table( - &self, - epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + fn stake_table(&self, epoch: Option<::Epoch>) -> Vec> { let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees"); if *epoch != 0 && *epoch % 2 == 0 { self.stake_table.0.clone() @@ -193,10 +180,7 @@ impl Membership for TwoStaticCommittees { } /// Get the stake table for the current view - fn da_stake_table( - &self, - epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + fn da_stake_table(&self, epoch: Option<::Epoch>) -> Vec> { let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees"); if *epoch != 0 && *epoch % 2 == 0 { self.da_stake_table.0.clone() @@ -276,7 +260,7 @@ impl Membership for TwoStaticCommittees { &self, pub_key: &::SignatureKey, epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { // Only return the stake if it is above zero let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees"); if *epoch != 0 && *epoch % 2 == 0 { @@ -291,7 +275,7 @@ impl Membership for TwoStaticCommittees { &self, pub_key: &::SignatureKey, epoch: Option<::Epoch>, - ) -> Option::SignatureKey>> { + ) -> Option> { // Only return the stake if it is above zero let epoch = epoch.expect("epochs cannot be disabled with TwoStaticCommittees"); if *epoch != 0 && *epoch % 2 == 0 { diff --git a/hotshot/src/traits/networking/libp2p_network.rs b/hotshot/src/traits/networking/libp2p_network.rs index 24ae0d9138..7bc78e79c0 100644 --- a/hotshot/src/traits/networking/libp2p_network.rs +++ b/hotshot/src/traits/networking/libp2p_network.rs @@ -393,7 +393,7 @@ impl Libp2pNetwork { /// If we are unable to calculate the replication factor #[allow(clippy::too_many_arguments)] pub async fn from_config( - mut config: NetworkConfig, + mut config: NetworkConfig, dht_persistent_storage: D, quorum_membership: Arc>, gossip_config: GossipConfig, diff --git a/sequencer/src/state_signature.rs b/sequencer/src/state_signature.rs index 87ff5b1761..9112136d2c 100644 --- a/sequencer/src/state_signature.rs +++ b/sequencer/src/state_signature.rs @@ -76,7 +76,14 @@ impl StateSigner { let Some(LeafInfo { leaf, .. }) = leaf_chain.first() else { return; }; - match form_light_client_state(leaf) { + let view_number = leaf.view_number().u64(); + let block_height = leaf.height(); + let mut block_comm_root_bytes = vec![]; + if let Err(e) = leaf.block_comm_root().serialize(&mut block_comm_root_bytes) { + tracing::error!("Error serializing block commitment root: {:?}", e); + return; + } + match LightClientState::new(view_number, block_height, &block_comm_root_bytes) { Ok(state) => { let signature = self.sign_new_state(&state).await; tracing::debug!("New leaves decided. Latest block height: {}", leaf.height(),); @@ -137,34 +144,6 @@ impl StateSigner { } } -fn hash_bytes_to_field(bytes: &[u8]) -> Result { - // make sure that `mod_order` won't happen. - let bytes_len = ((::MODULUS_BIT_SIZE + 7) / 8 - 1) as usize; - let elem = bytes - .chunks(bytes_len) - .map(CircuitField::from_le_bytes_mod_order) - .collect::>(); - Ok(VariableLengthRescueCRHF::<_, 1>::evaluate(elem)?[0]) -} - -fn form_light_client_state(leaf: &Leaf2) -> anyhow::Result { - let header = leaf.block_header(); - let mut block_comm_root_bytes = vec![]; - header - .block_merkle_tree_root() - .serialize_compressed(&mut block_comm_root_bytes)?; - - let mut fee_ledger_comm_bytes = vec![]; - header - .fee_merkle_tree_root() - .serialize_compressed(&mut fee_ledger_comm_bytes)?; - Ok(LightClientState { - view_number: leaf.view_number().u64() as usize, - block_height: leaf.height() as usize, - block_comm_root: hash_bytes_to_field(&block_comm_root_bytes)?, - }) -} - /// A rolling in-memory storage for the most recent light client state signatures. #[derive(Debug, Default)] pub struct StateSignatureMemStorage { From 1493354a33e76fe1fc659ae2f554e79c2a1f90bb Mon Sep 17 00:00:00 2001 From: MRain Date: Mon, 10 Mar 2025 15:45:36 -0400 Subject: [PATCH 03/12] leftovers of merge --- hotshot/src/lib.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/hotshot/src/lib.rs b/hotshot/src/lib.rs index 76c344e1fe..ae4135723f 100644 --- a/hotshot/src/lib.rs +++ b/hotshot/src/lib.rs @@ -19,11 +19,8 @@ use hotshot_types::{ message::UpgradeLock, simple_certificate::LightClientStateUpdateCertificate, traits::{ - block_contents::BlockHeader, election::Membership, network::BroadcastDelay, - node_implementation::Versions, - , - signature_key::StateSignatureKey, + node_implementation::Versions, signature_key::StateSignatureKey, }, }; use rand::Rng; @@ -1025,14 +1022,6 @@ pub struct InitializerEpochInfo { pub block_header: Option, } -#[derive(Clone, Debug, PartialEq)] -pub struct InitializerEpochInfo { - pub epoch: TYPES::Epoch, - pub drb_result: DrbResult, - // pub stake_table: Option, // TODO: Figure out how to connect this up - pub block_header: Option, -} - #[derive(Clone)] /// initializer struct for creating starting block pub struct HotShotInitializer { From a433d8d93b1fa6b8a2c9798563ddda7b019cf43e Mon Sep 17 00:00:00 2001 From: MRain Date: Tue, 11 Mar 2025 18:39:59 -0400 Subject: [PATCH 04/12] jesus it compiles, again --- Cargo.lock | 1 + builder/src/lib.rs | 27 +++---- hotshot-example-types/src/node_types.rs | 1 + hotshot-example-types/src/storage_types.rs | 19 +++++ hotshot-examples/infra/mod.rs | 75 +++++++++---------- .../push-cdn/whitelist-adapter.rs | 8 +- .../examples/simple-server.rs | 7 +- .../src/testing/consensus.rs | 7 +- hotshot-state-prover/src/circuit.rs | 4 +- hotshot-state-prover/src/service.rs | 3 +- hotshot-task-impls/src/consensus/mod.rs | 37 ++------- hotshot-task-impls/src/events.rs | 6 -- .../src/quorum_proposal/handlers.rs | 56 ++++++++------ hotshot-task-impls/src/quorum_proposal/mod.rs | 67 ++++++++++++++++- hotshot-task-impls/src/vote_collection.rs | 5 +- hotshot-types/src/consensus.rs | 12 +++ hotshot-types/src/light_client.rs | 2 +- hotshot-types/src/traits/storage.rs | 18 ++++- hotshot/src/types.rs | 2 +- marketplace-solver/src/events.rs | 4 +- marketplace-solver/src/state.rs | 4 +- node-metrics/src/api/node_validator/v0/mod.rs | 2 +- sequencer/src/api.rs | 16 ++-- sequencer/src/api/data_source.rs | 6 +- sequencer/src/bin/cdn-whitelist.rs | 8 +- sequencer/src/bin/deploy.rs | 3 +- sequencer/src/bin/orchestrator.rs | 4 +- .../bin/update-permissioned-stake-table.rs | 4 +- sequencer/src/catchup.rs | 8 +- sequencer/src/context.rs | 29 +++---- sequencer/src/lib.rs | 16 ++-- sequencer/src/persistence/fs.rs | 61 ++++++++++++++- sequencer/src/persistence/no_storage.rs | 16 +++- sequencer/src/persistence/sql.rs | 36 ++++++++- sequencer/src/restart_tests.rs | 2 +- sequencer/src/state_signature.rs | 53 ++++++------- sequencer/src/state_signature/relay_server.rs | 4 +- types/src/v0/config.rs | 37 ++++----- types/src/v0/impls/header.rs | 15 ++++ types/src/v0/impls/instance_state.rs | 4 +- types/src/v0/impls/stake_table.rs | 30 ++++---- types/src/v0/mod.rs | 2 +- types/src/v0/traits.rs | 23 +++++- types/src/v0/v0_3/stake_table.rs | 8 +- utils/Cargo.toml | 1 + utils/src/stake_table.rs | 58 +++++++++----- 46 files changed, 528 insertions(+), 283 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f0b90fec1..037f629c61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10275,6 +10275,7 @@ dependencies = [ "futures", "hotshot", "hotshot-contract-adapter", + "hotshot-example-types", "hotshot-types", "log-panics", "portpicker", diff --git a/builder/src/lib.rs b/builder/src/lib.rs index 627fcfccba..3081f4bf1c 100755 --- a/builder/src/lib.rs +++ b/builder/src/lib.rs @@ -80,7 +80,6 @@ pub mod testing { }, HotShotConfig, PeerConfig, ValidatorConfig, }; - use jf_signature::bls_over_bn254::VerKey; use sequencer::{context::Consensus, network, SequencerApiVersion}; use surf_disco::Client; use vbs::version::StaticVersion; @@ -90,7 +89,7 @@ pub mod testing { #[derive(Clone)] pub struct HotShotTestConfig { - pub config: HotShotConfig, + pub config: HotShotConfig, priv_keys_staking_nodes: Vec, priv_keys_non_staking_nodes: Vec, staking_nodes_state_key_pairs: Vec, @@ -115,7 +114,7 @@ pub mod testing { let builder_url = hotshot_builder_url(); - let config: HotShotConfig = HotShotConfig { + let config: HotShotConfig = HotShotConfig { num_nodes_with_stake: NonZeroUsize::new(num_nodes_with_stake).unwrap(), known_da_nodes: known_nodes_with_stake.clone(), known_nodes_with_stake: known_nodes_with_stake.clone(), @@ -157,7 +156,11 @@ pub mod testing { pub fn generate_stake_table_entries( num_nodes: u64, stake_value: u64, - ) -> (Vec, Vec, Vec>) { + ) -> ( + Vec, + Vec, + Vec>, + ) { // Generate keys for the nodes. let priv_keys = (0..num_nodes) .map(|_| PrivKey::generate(&mut rand::thread_rng())) @@ -173,7 +176,7 @@ pub mod testing { let nodes_with_stake = pub_keys .iter() .zip(&state_key_pairs) - .map(|(pub_key, state_key_pair)| PeerConfig:: { + .map(|(pub_key, state_key_pair)| PeerConfig:: { stake_table_entry: pub_key.stake_table_entry(stake_value), state_ver_key: state_key_pair.ver_key(), }) @@ -201,11 +204,7 @@ pub mod testing { pub fn get_anvil(&self) -> Arc { self.anvil.clone() } - pub fn get_validator_config( - &self, - i: usize, - is_staked: bool, - ) -> ValidatorConfig { + pub fn get_validator_config(&self, i: usize, is_staked: bool) -> ValidatorConfig { if is_staked { ValidatorConfig { public_key: self.config.known_nodes_with_stake[i] @@ -216,7 +215,8 @@ pub mod testing { .stake_table_entry .stake_amount .as_u64(), - state_key_pair: self.staking_nodes_state_key_pairs[i].clone(), + state_public_key: self.staking_nodes_state_key_pairs[i].ver_key(), + state_private_key: self.staking_nodes_state_key_pairs[i].sign_key(), is_da: true, } } else { @@ -226,7 +226,8 @@ pub mod testing { .stake_key, private_key: self.priv_keys_non_staking_nodes[i].clone(), stake_value: 0, - state_key_pair: self.non_staking_nodes_state_key_pairs[i].clone(), + state_public_key: self.non_staking_nodes_state_key_pairs[i].ver_key(), + state_private_key: self.non_staking_nodes_state_key_pairs[i].sign_key(), is_da: true, } } @@ -267,7 +268,7 @@ pub mod testing { // enable hotshot event streaming pub fn enable_hotshot_node_event_streaming( hotshot_events_api_url: Url, - known_nodes_with_stake: Vec>, + known_nodes_with_stake: Vec>, num_non_staking_nodes: usize, hotshot_context_handle: Arc>, ) { diff --git a/hotshot-example-types/src/node_types.rs b/hotshot-example-types/src/node_types.rs index 1ca046503d..07526c6369 100644 --- a/hotshot-example-types/src/node_types.rs +++ b/hotshot-example-types/src/node_types.rs @@ -131,6 +131,7 @@ impl NodeType for TestTypesEpochCatchupTypes { type InstanceState = TestInstanceState; type Membership = DummyCatchupCommittee; type BuilderSignatureKey = BuilderKey; + type StateSignatureKey = SchnorrPubKey; } #[derive( diff --git a/hotshot-example-types/src/storage_types.rs b/hotshot-example-types/src/storage_types.rs index 407cd7a231..18cf48c92c 100644 --- a/hotshot-example-types/src/storage_types.rs +++ b/hotshot-example-types/src/storage_types.rs @@ -322,6 +322,25 @@ impl Storage for TestStorage { Ok(()) } + async fn update_state_cert( + &self, + state_cert: hotshot_types::simple_certificate::LightClientStateUpdateCertificate, + ) -> Result<()> { + if self.should_return_err { + bail!("Failed to update state_cert to storage"); + } + Self::run_delay_settings_from_config(&self.delay_config).await; + let mut inner = self.inner.write().await; + if let Some(ref current_state_cert) = inner.state_cert { + if state_cert.epoch > current_state_cert.epoch { + inner.state_cert = Some(state_cert); + } + } else { + inner.state_cert = Some(state_cert); + } + Ok(()) + } + async fn update_next_epoch_high_qc2( &self, new_next_epoch_high_qc: hotshot_types::simple_certificate::NextEpochQuorumCertificate2< diff --git a/hotshot-examples/infra/mod.rs b/hotshot-examples/infra/mod.rs index 01e9d6223f..79a45d59ed 100755 --- a/hotshot-examples/infra/mod.rs +++ b/hotshot-examples/infra/mod.rs @@ -77,7 +77,7 @@ pub struct OrchestratorArgs { /// The url the orchestrator runs on; this should be in the form of `http://localhost:5555` or `http://0.0.0.0:5555` pub url: Url, /// The configuration file to be used for this run - pub config: NetworkConfig, + pub config: NetworkConfig, } #[derive(Parser, Debug, Clone)] @@ -103,8 +103,7 @@ impl Default for ConfigArgs { /// # Panics /// If unable to read the config file from the command line #[allow(clippy::too_many_lines)] -pub fn read_orchestrator_init_config() -> (NetworkConfig, Url) -{ +pub fn read_orchestrator_init_config() -> (NetworkConfig, Url) { // assign default setting let mut orchestrator_url = Url::parse("http://localhost:4444").unwrap(); let mut args = ConfigArgs::default(); @@ -208,8 +207,7 @@ pub fn read_orchestrator_init_config() -> (NetworkConfig = - load_config_from_file::(&args.config_file); + let mut config: NetworkConfig = load_config_from_file::(&args.config_file); if let Some(total_nodes_string) = matches.get_one::("total_nodes") { config.config.num_nodes_with_stake = total_nodes_string.parse::().unwrap(); @@ -263,16 +261,14 @@ pub fn read_orchestrator_init_config() -> (NetworkConfig( - config_file: &str, -) -> NetworkConfig { +pub fn load_config_from_file(config_file: &str) -> NetworkConfig { let config_file_as_string: String = fs::read_to_string(config_file) .unwrap_or_else(|_| panic!("Could not read config file located at {config_file}")); - let config_toml: NetworkConfigFile = - toml::from_str::>(&config_file_as_string) + let config_toml: NetworkConfigFile = + toml::from_str::>(&config_file_as_string) .expect("Unable to convert config file to TOML"); - let mut config: NetworkConfig = config_toml.into(); + let mut config: NetworkConfig = config_toml.into(); // initialize it with size for better assignment of peers' config config.config.known_nodes_with_stake = @@ -286,7 +282,7 @@ pub async fn run_orchestrator( OrchestratorArgs { url, config }: OrchestratorArgs, ) { println!("Starting orchestrator",); - let _ = hotshot_orchestrator::run_orchestrator::(config, url).await; + let _ = hotshot_orchestrator::run_orchestrator::(config, url).await; } /// Helper function to calculate the number of transactions to send per node per round @@ -352,8 +348,8 @@ pub trait RunDa< { /// Initializes networking, returns self async fn initialize_networking( - config: NetworkConfig, - validator_config: ValidatorConfig, + config: NetworkConfig, + validator_config: ValidatorConfig, libp2p_advertise_address: Option, membership: &Arc::Membership>>, ) -> Self; @@ -381,6 +377,7 @@ pub trait RunDa< // Get KeyPair for certificate Aggregation let pk = validator_config.public_key.clone(); let sk = validator_config.private_key.clone(); + let state_sk = validator_config.state_private_key.clone(); let network = self.network(); @@ -394,6 +391,7 @@ pub trait RunDa< SystemContext::init( pk, sk, + state_sk, config.node_index, config.config, EpochMembershipCoordinator::new(membership, epoch_height), @@ -584,10 +582,10 @@ pub trait RunDa< fn network(&self) -> NETWORK; /// Returns the config for this run - fn config(&self) -> NetworkConfig; + fn config(&self) -> NetworkConfig; /// Returns the validator config with private signature keys for this run. - fn validator_config(&self) -> ValidatorConfig; + fn validator_config(&self) -> ValidatorConfig; } // Push CDN @@ -595,9 +593,9 @@ pub trait RunDa< /// Represents a Push CDN-based run pub struct PushCdnDaRun { /// The underlying configuration - config: NetworkConfig, + config: NetworkConfig, /// The private validator config - validator_config: ValidatorConfig, + validator_config: ValidatorConfig, /// The underlying network network: PushCdnNetwork, } @@ -625,8 +623,8 @@ where Self: Sync, { async fn initialize_networking( - config: NetworkConfig, - validator_config: ValidatorConfig, + config: NetworkConfig, + validator_config: ValidatorConfig, _libp2p_advertise_address: Option, _membership: &Arc::Membership>>, ) -> PushCdnDaRun { @@ -668,11 +666,11 @@ where self.network.clone() } - fn config(&self) -> NetworkConfig { + fn config(&self) -> NetworkConfig { self.config.clone() } - fn validator_config(&self) -> ValidatorConfig { + fn validator_config(&self) -> ValidatorConfig { self.validator_config.clone() } } @@ -682,9 +680,9 @@ where /// Represents a libp2p-based run pub struct Libp2pDaRun { /// The underlying network configuration - config: NetworkConfig, + config: NetworkConfig, /// The private validator config - validator_config: ValidatorConfig, + validator_config: ValidatorConfig, /// The underlying network network: Libp2pNetwork, } @@ -712,8 +710,8 @@ where Self: Sync, { async fn initialize_networking( - config: NetworkConfig, - validator_config: ValidatorConfig, + config: NetworkConfig, + validator_config: ValidatorConfig, libp2p_advertise_address: Option, membership: &Arc::Membership>>, ) -> Libp2pDaRun { @@ -777,11 +775,11 @@ where self.network.clone() } - fn config(&self) -> NetworkConfig { + fn config(&self) -> NetworkConfig { self.config.clone() } - fn validator_config(&self) -> ValidatorConfig { + fn validator_config(&self) -> ValidatorConfig { self.validator_config.clone() } } @@ -791,9 +789,9 @@ where /// Represents a combined-network-based run pub struct CombinedDaRun { /// The underlying network configuration - config: NetworkConfig, + config: NetworkConfig, /// The private validator config - validator_config: ValidatorConfig, + validator_config: ValidatorConfig, /// The underlying network network: CombinedNetworks, } @@ -821,8 +819,8 @@ where Self: Sync, { async fn initialize_networking( - config: NetworkConfig, - validator_config: ValidatorConfig, + config: NetworkConfig, + validator_config: ValidatorConfig, libp2p_advertise_address: Option, membership: &Arc::Membership>>, ) -> CombinedDaRun { @@ -876,11 +874,11 @@ where self.network.clone() } - fn config(&self) -> NetworkConfig { + fn config(&self) -> NetworkConfig { self.config.clone() } - fn validator_config(&self) -> ValidatorConfig { + fn validator_config(&self) -> ValidatorConfig { self.validator_config.clone() } } @@ -919,7 +917,7 @@ pub async fn main_entry_point< let orchestrator_client: OrchestratorClient = OrchestratorClient::new(args.url.clone()); // We assume one node will not call this twice to generate two validator_config-s with same identity. - let validator_config = NetworkConfig::::generate_init_validator_config( + let validator_config = NetworkConfig::::generate_init_validator_config( orchestrator_client .get_node_index_for_init_validator_config() .await, @@ -933,8 +931,7 @@ pub async fn main_entry_point< .expect("failed to derive Libp2p keypair"); // We need this to be able to register our node - let peer_config = - PeerConfig::::to_bytes(&validator_config.public_config()).clone(); + let peer_config = PeerConfig::::to_bytes(&validator_config.public_config()).clone(); // Derive the advertise multiaddress from the supplied string let advertise_multiaddress = args.advertise_address.clone().map(|advertise_address| { @@ -1061,8 +1058,8 @@ async fn initialize_builder< InstanceState = TestInstanceState, >, >( - run_config: &mut NetworkConfig<::SignatureKey>, - validator_config: &ValidatorConfig<::SignatureKey>, + run_config: &mut NetworkConfig, + validator_config: &ValidatorConfig, args: &ValidatorArgs, orchestrator_client: &OrchestratorClient, ) -> Option>> diff --git a/hotshot-examples/push-cdn/whitelist-adapter.rs b/hotshot-examples/push-cdn/whitelist-adapter.rs index e855a41aba..9445295610 100644 --- a/hotshot-examples/push-cdn/whitelist-adapter.rs +++ b/hotshot-examples/push-cdn/whitelist-adapter.rs @@ -15,10 +15,7 @@ use cdn_broker::reexports::discovery::{DiscoveryClient, Embedded, Redis}; use clap::Parser; use hotshot_example_types::node_types::TestTypes; use hotshot_orchestrator::client::OrchestratorClient; -use hotshot_types::{ - network::NetworkConfig, - traits::{node_implementation::NodeType, signature_key::SignatureKey}, -}; +use hotshot_types::{network::NetworkConfig, traits::signature_key::SignatureKey}; use surf_disco::Url; #[derive(Parser, Debug)] @@ -55,8 +52,7 @@ async fn main() -> Result<()> { // Attempt to get the config from the orchestrator. // Loops internally until the config is received. - let config: NetworkConfig<::SignatureKey> = - orchestrator_client.get_config_after_collection().await; + let config: NetworkConfig = orchestrator_client.get_config_after_collection().await; tracing::info!("Received config from orchestrator"); diff --git a/hotshot-query-service/examples/simple-server.rs b/hotshot-query-service/examples/simple-server.rs index 916e8a5c7d..f4515ac8fc 100644 --- a/hotshot-query-service/examples/simple-server.rs +++ b/hotshot-query-service/examples/simple-server.rs @@ -163,7 +163,7 @@ async fn init_consensus( let known_nodes_with_stake = pub_keys .iter() .zip(&state_key_pairs) - .map(|(pub_key, state_key_pair)| PeerConfig:: { + .map(|(pub_key, state_key_pair)| PeerConfig:: { stake_table_entry: pub_key.stake_table_entry(1u64), state_ver_key: state_key_pair.ver_key(), }) @@ -227,6 +227,10 @@ async fn init_consensus( let pub_keys = pub_keys.clone(); let config = config.clone(); let master_map = master_map.clone(); + let state_private_keys = state_key_pairs + .iter() + .map(|kp| kp.sign_key()) + .collect::>(); let membership = membership.clone(); async move { @@ -246,6 +250,7 @@ async fn init_consensus( SystemContext::init( pub_keys[node_id], priv_key, + state_private_keys[node_id].clone(), node_id as u64, config, coordinator, diff --git a/hotshot-query-service/src/testing/consensus.rs b/hotshot-query-service/src/testing/consensus.rs index ce65187d9c..7a594f09ff 100644 --- a/hotshot-query-service/src/testing/consensus.rs +++ b/hotshot-query-service/src/testing/consensus.rs @@ -83,7 +83,7 @@ impl MockNetwork { } pub async fn init_with_config( - update_config: impl FnOnce(&mut HotShotConfig), + update_config: impl FnOnce(&mut HotShotConfig), leaf_only: bool, ) -> Self { let (pub_keys, priv_keys): (Vec<_>, Vec<_>) = (0..NUM_NODES) @@ -163,6 +163,10 @@ impl MockNetwork { let pub_keys = pub_keys.clone(); let master_map = master_map.clone(); + let state_priv_keys = state_key_pairs + .iter() + .map(|kp| kp.sign_key()) + .collect::>(); let span = info_span!("initialize node", node_id); async move { @@ -187,6 +191,7 @@ impl MockNetwork { let hotshot = SystemContext::init( pub_keys[node_id], priv_key, + state_priv_keys[node_id].clone(), node_id as u64, config, memberships, diff --git a/hotshot-state-prover/src/circuit.rs b/hotshot-state-prover/src/circuit.rs index c45f517c9a..3c16fe30f7 100644 --- a/hotshot-state-prover/src/circuit.rs +++ b/hotshot-state-prover/src/circuit.rs @@ -79,8 +79,8 @@ impl LightClientStateVar { state: &GenericLightClientState, ) -> Result { Ok(Self { - view_num: circuit.create_public_variable(F::from(state.view_number as u64))?, - block_height: circuit.create_public_variable(F::from(state.block_height as u64))?, + view_num: circuit.create_public_variable(F::from(state.view_number))?, + block_height: circuit.create_public_variable(F::from(state.block_height))?, block_comm_root: circuit.create_public_variable(state.block_comm_root)?, }) } diff --git a/hotshot-state-prover/src/service.rs b/hotshot-state-prover/src/service.rs index d03dfd51d1..6f7ae73cf4 100644 --- a/hotshot-state-prover/src/service.rs +++ b/hotshot-state-prover/src/service.rs @@ -9,6 +9,7 @@ use std::{ use anyhow::{anyhow, Context, Result}; use contract_bindings_ethers::light_client::{LightClient, LightClientErrors}; use displaydoc::Display; +use espresso_types::SeqTypes; use ethers::{ core::k256::ecdsa::SigningKey, middleware::{ @@ -123,7 +124,7 @@ pub fn init_stake_table( #[derive(Debug, Deserialize)] struct PublicHotShotConfig { - known_nodes_with_stake: Vec>, + known_nodes_with_stake: Vec>, } #[derive(Debug, Deserialize)] diff --git a/hotshot-task-impls/src/consensus/mod.rs b/hotshot-task-impls/src/consensus/mod.rs index 66302869c3..c37e804893 100644 --- a/hotshot-task-impls/src/consensus/mod.rs +++ b/hotshot-task-impls/src/consensus/mod.rs @@ -21,7 +21,6 @@ use hotshot_types::{ node_implementation::{NodeImplementation, NodeType, Versions}, signature_key::SignatureKey, }, - utils::option_epoch_from_block_number, vote::HasViewNumber, }; use hotshot_utils::anytrace::*; @@ -33,7 +32,7 @@ use self::handlers::{ }; use crate::{ events::HotShotEvent, - helpers::{broadcast_event, validate_qc_and_next_epoch_qc, wait_for_next_epoch_qc}, + helpers::{broadcast_event, validate_qc_and_next_epoch_qc}, vote_collection::{ExtendedQuorumVoteCollectorsMap, VoteCollectorsMap}, }; @@ -126,6 +125,13 @@ impl, V: Versions> ConsensusTaskSt tracing::debug!("Failed to handle QuorumVoteRecv event; error = {e}"); } }, + HotShotEvent::ExtendedQuorumVoteRecv(ref vote) => { + if let Err(e) = + handle_extended_quorum_vote_recv(vote, Arc::clone(&event), &sender, self).await + { + tracing::debug!("Failed to handle ExtendedQuorumVoteRecv event; error = {e}"); + } + }, HotShotEvent::TimeoutVoteRecv(ref vote) => { if let Err(e) = handle_timeout_vote_recv(vote, Arc::clone(&event), &sender, self).await @@ -147,33 +153,6 @@ impl, V: Versions> ConsensusTaskSt tracing::debug!("Failed to handle Timeout event; error = {e}"); } }, - HotShotEvent::ExtendedQc2Formed(eqc) => { - let cert_view = eqc.view_number(); - let cert_block_number = self - .consensus - .read() - .await - .saved_leaves() - .get(&eqc.data.leaf_commit) - .context(error!( - "Could not find the leaf for the eQC. It shouldn't happen." - ))? - .height(); - - let cert_epoch = option_epoch_from_block_number::( - true, - cert_block_number, - self.epoch_height, - ); - // Transition to the new epoch by sending ViewChange - let next_epoch = cert_epoch.map(|x| x + 1); - tracing::info!("Entering new epoch: {:?}", next_epoch); - broadcast_event( - Arc::new(HotShotEvent::ViewChange(cert_view + 1, next_epoch)), - &sender, - ) - .await; - }, HotShotEvent::ExtendedQcRecv(eqc, next_epoch_high_qc, _) => { if !self .consensus diff --git a/hotshot-task-impls/src/events.rs b/hotshot-task-impls/src/events.rs index e347a6892a..0e7e4ca13e 100644 --- a/hotshot-task-impls/src/events.rs +++ b/hotshot-task-impls/src/events.rs @@ -138,8 +138,6 @@ pub enum HotShotEvent { ExtendedQcFormed(Either, TimeoutCertificate2>), /// The next leader has collected enough votes from the next epoch nodes to form a QC; emitted by the next leader in the consensus task; an internal event only NextEpochQc2Formed(Either, TimeoutCertificate>), - /// A validator formed both a current epoch eQC and a next epoch eQC - ExtendedQc2Formed(QuorumCertificate2), /// The DA leader has collected enough votes to form a DAC; emitted by the DA leader in the DA task; sent to the entire network via the networking task DacSend(DaCertificate2, TYPES::SignatureKey), /// The current view has changed; emitted by the replica in the consensus task or replica in the view sync task; received by almost all other tasks @@ -322,7 +320,6 @@ impl HotShotEvent { either::Left(qc) => Some(qc.view_number()), either::Right(tc) => Some(tc.view_number()), }, - HotShotEvent::ExtendedQc2Formed(cert) => Some(cert.view_number()), HotShotEvent::ViewSyncCommitVoteSend(vote) | HotShotEvent::ViewSyncCommitVoteRecv(vote) => Some(vote.view_number()), HotShotEvent::ViewSyncPreCommitVoteRecv(vote) @@ -471,9 +468,6 @@ impl Display for HotShotEvent { write!(f, "NextEpochQc2Formed(view_number={:?})", tc.view_number()) }, }, - HotShotEvent::ExtendedQc2Formed(cert) => { - write!(f, "ExtendedQc2Formed(view_number={:?})", cert.view_number()) - }, HotShotEvent::DacSend(cert, _) => { write!(f, "DacSend(view_number={:?})", cert.view_number()) }, diff --git a/hotshot-task-impls/src/quorum_proposal/handlers.rs b/hotshot-task-impls/src/quorum_proposal/handlers.rs index 16f50edec8..196165938f 100644 --- a/hotshot-task-impls/src/quorum_proposal/handlers.rs +++ b/hotshot-task-impls/src/quorum_proposal/handlers.rs @@ -13,7 +13,6 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::{ensure, Context, Result}; use async_broadcast::{Receiver, Sender}; use async_lock::RwLock; use committable::{Commitment, Committable}; @@ -510,41 +509,54 @@ pub(super) async fn handle_eqc_formed< leaf_commit: Commitment>, task_state: &QuorumProposalTaskState, event_sender: &Sender>>, -) { - if !task_state.upgrade_lock.epochs_enabled(cert_view).await { - tracing::debug!("QC2 formed but epochs not enabled. Do nothing"); - return; - } - if !task_state - .consensus - .read() - .await - .is_leaf_extended(leaf_commit) - { - tracing::debug!("We formed QC but not eQC. Do nothing"); - return; - } + epoch_height: u64, +) -> Result<()> { + ensure!( + task_state.upgrade_lock.epochs_enabled(cert_view).await, + debug!("QC2 formed but epochs not enabled. Do nothing") + ); + ensure!( + task_state + .consensus + .read() + .await + .is_leaf_extended(leaf_commit), + debug!("We formed QC but not eQC. Do nothing") + ); let consensus_reader = task_state.consensus.read().await; let current_epoch_qc = consensus_reader.high_qc(); + let cert_view = current_epoch_qc.view_number(); + let cert_block_number = consensus_reader + .saved_leaves() + .get(¤t_epoch_qc.data.leaf_commit) + .context(error!( + "Could not find the leaf for the eQC. It shouldn't happen." + ))? + .height(); + let Some(next_epoch_qc) = consensus_reader.next_epoch_high_qc() else { - tracing::debug!("We formed the eQC but we don't have the next epoch eQC at all."); - return; + return Err(debug!( + "We formed the eQC but we don't have the next epoch eQC at all." + )); }; if current_epoch_qc.view_number() != next_epoch_qc.view_number() || current_epoch_qc.data != *next_epoch_qc.data { - tracing::debug!( + return Err(debug!( "We formed the eQC but the current and next epoch QCs do not correspond to each other." - ); - return; + )); } - let current_epoch_qc_clone = current_epoch_qc.clone(); drop(consensus_reader); + let cert_epoch = option_epoch_from_block_number::(true, cert_block_number, epoch_height); + // Transition to the new epoch by sending ViewChange + let next_epoch = cert_epoch.map(|x| x + 1); + tracing::info!("Entering new epoch: {:?}", next_epoch); broadcast_event( - Arc::new(HotShotEvent::ExtendedQc2Formed(current_epoch_qc_clone)), + Arc::new(HotShotEvent::ViewChange(cert_view + 1, next_epoch)), event_sender, ) .await; + Ok(()) } diff --git a/hotshot-task-impls/src/quorum_proposal/mod.rs b/hotshot-task-impls/src/quorum_proposal/mod.rs index f3a0f2e089..9637db4f46 100644 --- a/hotshot-task-impls/src/quorum_proposal/mod.rs +++ b/hotshot-task-impls/src/quorum_proposal/mod.rs @@ -448,9 +448,6 @@ impl, V: Versions> .wrap() .context(error!("Failed to update high QC in storage!"))?; - handle_eqc_formed(qc.view_number(), qc.data.leaf_commit, self, &event_sender) - .await; - let view_number = qc.view_number() + 1; self.create_dependency_task_if_new( view_number, @@ -463,6 +460,67 @@ impl, V: Versions> .await?; }, }, + HotShotEvent::ExtendedQcFormed(cert) => match cert { + either::Right(timeout_cert) => { + let view_number = timeout_cert.view_number + 1; + self.create_dependency_task_if_new( + view_number, + epoch_number, + event_receiver, + event_sender, + Arc::clone(&event), + epoch_transition_indicator, + ) + .await?; + }, + either::Left(eqc) => { + // Only update if the qc is from a newer view + if eqc.qc.view_number() <= self.consensus.read().await.high_qc().view_number { + tracing::trace!( + "Received a QC for a view that was not > than our current high QC" + ); + } + self.consensus + .write() + .await + .update_high_qc_and_state_cert(eqc.qc.clone(), eqc.state_cert.clone()) + .wrap() + .context(error!( + "Failed to update high QC or state certificate in internal consensus state!" + ))?; + + // Then update the high QC and the state certificate in storage + self.storage + .write() + .await + .update_high_qc2_and_state_cert(eqc.qc.clone(), eqc.state_cert.clone()) + .await + .wrap() + .context(error!( + "Failed to update high QC or state certificate in storage!" + ))?; + + handle_eqc_formed( + eqc.qc.view_number(), + eqc.qc.data.leaf_commit, + self, + &event_sender, + self.epoch_height, + ) + .await?; + + let view_number = eqc.qc.view_number() + 1; + self.create_dependency_task_if_new( + view_number, + epoch_number, + event_receiver, + event_sender, + Arc::clone(&event), + epoch_transition_indicator, + ) + .await?; + }, + }, HotShotEvent::SendPayloadCommitmentAndMetadata( _payload_commitment, _builder_commitment, @@ -622,8 +680,9 @@ impl, V: Versions> next_epoch_qc.data.leaf_commit, self, &event_sender, + self.epoch_height, ) - .await; + .await?; }, _ => {}, } diff --git a/hotshot-task-impls/src/vote_collection.rs b/hotshot-task-impls/src/vote_collection.rs index ddf0feaaec..0eabd6888f 100644 --- a/hotshot-task-impls/src/vote_collection.rs +++ b/hotshot-task-impls/src/vote_collection.rs @@ -692,9 +692,7 @@ impl ExtendedQuorumVoteCollectionTaskState>>, ) -> Result>> { match event.as_ref() { - HotShotEvent::ExtendedQuorumVoteRecv(vote) => { - self.accumulate_vote(vote, self.epoch, sender).await - }, + HotShotEvent::ExtendedQuorumVoteRecv(vote) => self.accumulate_vote(vote, sender).await, _ => Ok(None), } } @@ -703,7 +701,6 @@ impl ExtendedQuorumVoteCollectionTaskState, - sender_epoch: Option, event_stream: &Sender>>, ) -> Result>> { let ExtendedQuorumVote { vote, state_vote } = vote; diff --git a/hotshot-types/src/consensus.rs b/hotshot-types/src/consensus.rs index 5a039f817e..925d9b649b 100644 --- a/hotshot-types/src/consensus.rs +++ b/hotshot-types/src/consensus.rs @@ -823,6 +823,18 @@ impl Consensus { Ok(()) } + /// Update the light client state update certificate if given a newer one. + /// # Errors + /// Can return an error when the provided state_cert is not newer than the existing entry. + pub fn update_high_qc_and_state_cert( + &mut self, + high_qc: QuorumCertificate2, + state_cert: LightClientStateUpdateCertificate, + ) -> Result<()> { + self.update_high_qc(high_qc)?; + self.update_state_cert(state_cert) + } + /// Add a new entry to the vid_shares map. pub fn update_vid_shares( &mut self, diff --git a/hotshot-types/src/light_client.rs b/hotshot-types/src/light_client.rs index 7f8190379f..460df94f64 100644 --- a/hotshot-types/src/light_client.rs +++ b/hotshot-types/src/light_client.rs @@ -276,7 +276,7 @@ impl GenericPublicInput { } } -fn hash_bytes_to_field(bytes: &[u8]) -> Result { +pub fn hash_bytes_to_field(bytes: &[u8]) -> Result { // make sure that `mod_order` won't happen. let bytes_len = ((::MODULUS_BIT_SIZE + 7) / 8 - 1) as usize; let elem = bytes diff --git a/hotshot-types/src/traits/storage.rs b/hotshot-types/src/traits/storage.rs index 7ad83f58ed..08ad4404df 100644 --- a/hotshot-types/src/traits/storage.rs +++ b/hotshot-types/src/traits/storage.rs @@ -27,7 +27,8 @@ use crate::{ event::HotShotAction, message::{convert_proposal, Proposal}, simple_certificate::{ - NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, UpgradeCertificate, }, }; @@ -114,6 +115,21 @@ pub trait Storage: Send + Sync + Clone { async fn update_high_qc2(&self, high_qc: QuorumCertificate2) -> Result<()> { self.update_high_qc(high_qc.to_qc()).await } + /// Udpate the light client state update certificate in storage. + async fn update_state_cert( + &self, + state_cert: LightClientStateUpdateCertificate, + ) -> Result<()>; + + async fn update_high_qc2_and_state_cert( + &self, + high_qc: QuorumCertificate2, + state_cert: LightClientStateUpdateCertificate, + ) -> Result<()> { + self.update_high_qc2(high_qc).await?; + self.update_state_cert(state_cert).await + } + /// Update the current high QC in storage. async fn update_next_epoch_high_qc2( &self, diff --git a/hotshot/src/types.rs b/hotshot/src/types.rs index 5013e44298..27181910b1 100644 --- a/hotshot/src/types.rs +++ b/hotshot/src/types.rs @@ -11,6 +11,6 @@ pub use event::{Event, EventType}; pub use handle::SystemContextHandle; pub use hotshot_types::{ message::Message, - signature_key::{BLSPrivKey, BLSPubKey}, + signature_key::{BLSPrivKey, BLSPubKey, SchnorrPrivKey, SchnorrPubKey}, traits::signature_key::SignatureKey, }; diff --git a/marketplace-solver/src/events.rs b/marketplace-solver/src/events.rs index 85a49ca1dc..5f22675b67 100644 --- a/marketplace-solver/src/events.rs +++ b/marketplace-solver/src/events.rs @@ -94,7 +94,7 @@ pub mod mock { const STAKED_NODES: usize = 10; pub type StaticVer01 = StaticVersion<0, 1>; - pub fn generate_stake_table() -> Vec> { + pub fn generate_stake_table() -> Vec> { (0..STAKED_NODES) .map(|_| { let private_key = @@ -102,7 +102,7 @@ pub mod mock { let pub_key = BLSPubKey::from_private(&private_key); let state_key_pair = StateKeyPair::generate(); - PeerConfig:: { + PeerConfig:: { stake_table_entry: pub_key.stake_table_entry(NODE_STAKE), state_ver_key: state_key_pair.ver_key(), } diff --git a/marketplace-solver/src/state.rs b/marketplace-solver/src/state.rs index 697a273539..8a798d7473 100644 --- a/marketplace-solver/src/state.rs +++ b/marketplace-solver/src/state.rs @@ -7,7 +7,7 @@ use espresso_types::{ BidTx, RollupRegistration, RollupRegistrationBody, RollupUpdate, RollupUpdatebody, SolverAuctionResults, }, - PubKey, SeqTypes, + SeqTypes, Update::Set, }; use hotshot::types::SignatureKey; @@ -48,7 +48,7 @@ pub struct SolverState { } pub struct StakeTable { - pub known_nodes_with_stake: Vec>, + pub known_nodes_with_stake: Vec>, } #[async_trait] diff --git a/node-metrics/src/api/node_validator/v0/mod.rs b/node-metrics/src/api/node_validator/v0/mod.rs index c152a8e81b..47b52b6a29 100644 --- a/node-metrics/src/api/node_validator/v0/mod.rs +++ b/node-metrics/src/api/node_validator/v0/mod.rs @@ -295,7 +295,7 @@ where #[derive(Debug, Deserialize)] pub struct PublishHotShotConfig { - pub known_nodes_with_stake: Vec>, + pub known_nodes_with_stake: Vec>, } #[derive(Debug, Deserialize)] diff --git a/sequencer/src/api.rs b/sequencer/src/api.rs index 0433e9f03e..f437cefbaf 100644 --- a/sequencer/src/api.rs +++ b/sequencer/src/api.rs @@ -62,7 +62,7 @@ struct ConsensusState, P: SequencerPersistence, V: V state_signer: Arc>, event_streamer: Arc>>, node_state: NodeState, - network_config: NetworkConfig, + network_config: NetworkConfig, #[derivative(Debug = "ignore")] handle: Arc>>, @@ -112,7 +112,7 @@ impl, P: SequencerPersistence, V: Versions> ApiState Arc::clone(&self.consensus.as_ref().get().await.get_ref().handle) } - async fn network_config(&self) -> NetworkConfig { + async fn network_config(&self) -> NetworkConfig { self.consensus .as_ref() .get() @@ -167,14 +167,12 @@ impl, D: Sync, V: Versions, P: SequencerPersistence> async fn get_stake_table( &self, epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + ) -> Vec> { self.as_ref().get_stake_table(epoch).await } /// Get the stake table for the current epoch if not provided - async fn get_stake_table_current( - &self, - ) -> Vec::SignatureKey>> { + async fn get_stake_table_current(&self) -> Vec> { self.as_ref().get_stake_table_current().await } } @@ -185,7 +183,7 @@ impl, V: Versions, P: SequencerPersistence> async fn get_stake_table( &self, epoch: Option<::Epoch>, - ) -> Vec::SignatureKey>> { + ) -> Vec> { let Ok(mem) = self .consensus() .await @@ -201,9 +199,7 @@ impl, V: Versions, P: SequencerPersistence> } /// Get the stake table for the current epoch if not provided - async fn get_stake_table_current( - &self, - ) -> Vec::SignatureKey>> { + async fn get_stake_table_current(&self) -> Vec> { let epoch = self.consensus().await.read().await.cur_epoch().await; self.get_stake_table(epoch).await diff --git a/sequencer/src/api/data_source.rs b/sequencer/src/api/data_source.rs index a88ad528be..5f27a9bb40 100644 --- a/sequencer/src/api/data_source.rs +++ b/sequencer/src/api/data_source.rs @@ -115,12 +115,10 @@ pub(crate) trait StakeTableDataSource { fn get_stake_table( &self, epoch: Option<::Epoch>, - ) -> impl Send + Future>>; + ) -> impl Send + Future>>; /// Get the stake table for the current epoch if not provided - fn get_stake_table_current( - &self, - ) -> impl Send + Future>>; + fn get_stake_table_current(&self) -> impl Send + Future>>; } pub(crate) trait CatchupDataSource: Sync { diff --git a/sequencer/src/bin/cdn-whitelist.rs b/sequencer/src/bin/cdn-whitelist.rs index 2e47b3d049..af8c2486a2 100644 --- a/sequencer/src/bin/cdn-whitelist.rs +++ b/sequencer/src/bin/cdn-whitelist.rs @@ -9,10 +9,7 @@ use cdn_broker::reexports::discovery::{DiscoveryClient, Embedded, Redis}; use clap::Parser; use espresso_types::SeqTypes; use hotshot_orchestrator::client::OrchestratorClient; -use hotshot_types::{ - network::NetworkConfig, - traits::{node_implementation::NodeType, signature_key::SignatureKey}, -}; +use hotshot_types::{network::NetworkConfig, traits::signature_key::SignatureKey}; use surf_disco::Url; #[derive(Parser, Debug)] @@ -54,8 +51,7 @@ async fn main() -> Result<()> { // Attempt to get the config from the orchestrator. // Loops internally until the config is received. - let config: NetworkConfig<::SignatureKey> = - orchestrator_client.get_config_after_collection().await; + let config: NetworkConfig = orchestrator_client.get_config_after_collection().await; tracing::info!("Received config from orchestrator"); diff --git a/sequencer/src/bin/deploy.rs b/sequencer/src/bin/deploy.rs index 8a5916bd43..fc43aa1ea9 100644 --- a/sequencer/src/bin/deploy.rs +++ b/sequencer/src/bin/deploy.rs @@ -2,6 +2,7 @@ use std::{fs::File, io::stdout, path::PathBuf, time::Duration}; use clap::Parser; use espresso_types::parse_duration; +use espresso_types::SeqTypes; use ethers::types::Address; use futures::FutureExt; use hotshot_stake_table::config::STAKE_TABLE_CAPACITY; @@ -155,7 +156,7 @@ async fn main() -> anyhow::Result<()> { let initial_stake_table = if let Some(path) = opt.initial_stake_table_path { tracing::info!("Loading initial stake table from {:?}", path); - Some(PermissionedStakeTableConfig::from_toml_file(&path)?.into()) + Some(PermissionedStakeTableConfig::::from_toml_file(&path)?.into()) } else { None }; diff --git a/sequencer/src/bin/orchestrator.rs b/sequencer/src/bin/orchestrator.rs index e37069fc6f..b7eca0e441 100644 --- a/sequencer/src/bin/orchestrator.rs +++ b/sequencer/src/bin/orchestrator.rs @@ -2,7 +2,7 @@ use std::{num::NonZeroUsize, time::Duration}; use clap::Parser; use derive_more::From; -use espresso_types::{parse_duration, PubKey, Ratio}; +use espresso_types::{parse_duration, Ratio, SeqTypes}; use ethers::utils::hex::{self, FromHexError}; use hotshot_orchestrator::run_orchestrator; use hotshot_types::network::{Libp2pConfig, NetworkConfig}; @@ -117,7 +117,7 @@ async fn main() { let args = Args::parse(); args.logging.init(); - let mut config = NetworkConfig:: { + let mut config = NetworkConfig:: { manual_start_password: args.manual_start_password, indexed_da: false, ..Default::default() diff --git a/sequencer/src/bin/update-permissioned-stake-table.rs b/sequencer/src/bin/update-permissioned-stake-table.rs index 37534bc66c..50ec059e31 100644 --- a/sequencer/src/bin/update-permissioned-stake-table.rs +++ b/sequencer/src/bin/update-permissioned-stake-table.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, time::Duration}; use anyhow::Result; use clap::Parser; -use espresso_types::parse_duration; +use espresso_types::{parse_duration, SeqTypes}; use ethers::types::Address; use sequencer_utils::{ logging, @@ -88,7 +88,7 @@ struct Options { async fn main() -> Result<()> { let opts = Options::parse(); opts.logging.init(); - let update = PermissionedStakeTableUpdate::from_toml_file(&opts.update_toml_path)?; + let update = PermissionedStakeTableUpdate::::from_toml_file(&opts.update_toml_path)?; update_stake_table( opts.rpc_url, diff --git a/sequencer/src/catchup.rs b/sequencer/src/catchup.rs index 59068e3f84..de4b88fc1f 100644 --- a/sequencer/src/catchup.rs +++ b/sequencer/src/catchup.rs @@ -7,7 +7,7 @@ use committable::{Commitment, Committable}; use espresso_types::{ config::PublicNetworkConfig, traits::SequencerPersistence, v0::traits::StateCatchup, v0_99::ChainConfig, BackoffParams, BlockMerkleTree, FeeAccount, FeeAccountProof, - FeeMerkleCommitment, FeeMerkleTree, Leaf2, NodeState, + FeeMerkleCommitment, FeeMerkleTree, Leaf2, NodeState, SeqTypes, }; use futures::future::{Future, FutureExt, TryFuture, TryFutureExt}; use hotshot_types::{ @@ -29,7 +29,7 @@ use tokio::time::timeout; use url::Url; use vbs::version::StaticVersionType; -use crate::{api::BlocksFrontier, PubKey}; +use crate::api::BlocksFrontier; // This newtype is probably not worth having. It's only used to be able to log // URLs before doing requests. @@ -221,8 +221,8 @@ impl StatePeers { #[tracing::instrument(skip(self, my_own_validator_config))] pub async fn fetch_config( &self, - my_own_validator_config: ValidatorConfig, - ) -> anyhow::Result> { + my_own_validator_config: ValidatorConfig, + ) -> anyhow::Result> { self.backoff() .retry(self, move |provider, retry| { let my_own_validator_config = my_own_validator_config.clone(); diff --git a/sequencer/src/context.rs b/sequencer/src/context.rs index 1ad2d1a2da..e31307dc5d 100644 --- a/sequencer/src/context.rs +++ b/sequencer/src/context.rs @@ -26,11 +26,7 @@ use hotshot_types::{ data::{Leaf2, ViewNumber}, epoch_membership::EpochMembershipCoordinator, network::NetworkConfig, - traits::{ - metrics::Metrics, - network::ConnectedNetwork, - node_implementation::{NodeType, Versions}, - }, + traits::{metrics::Metrics, network::ConnectedNetwork, node_implementation::Versions}, PeerConfig, ValidatorConfig, }; use parking_lot::Mutex; @@ -94,18 +90,18 @@ pub struct SequencerContext, P: SequencerPersistence node_state: NodeState, - network_config: NetworkConfig, + network_config: NetworkConfig, #[derivative(Debug = "ignore")] - validator_config: ValidatorConfig<::SignatureKey>, + validator_config: ValidatorConfig, } impl, P: SequencerPersistence, V: Versions> SequencerContext { #[tracing::instrument(skip_all, fields(node_id = instance_state.node_id))] #[allow(clippy::too_many_arguments)] pub async fn init( - network_config: NetworkConfig, - validator_config: ValidatorConfig<::SignatureKey>, + network_config: NetworkConfig, + validator_config: ValidatorConfig, membership: EpochCommittees, instance_state: NodeState, persistence: P, @@ -141,7 +137,7 @@ impl, P: SequencerPersistence, V: Versions> Sequence .try_into() .context("stake table capacity out of range")?, ); - let state_key_pair = validator_config.state_key_pair.clone(); + // let state_key_pair = validator_config.state_key_pair.clone(); let event_streamer = Arc::new(RwLock::new(EventsStreamer::::new( config.known_nodes_with_stake.clone(), @@ -156,6 +152,7 @@ impl, P: SequencerPersistence, V: Versions> Sequence let handle = SystemContext::init( validator_config.public_key, validator_config.private_key.clone(), + validator_config.state_private_key.clone(), instance_state.node_id, config.clone(), coordinator, @@ -168,7 +165,11 @@ impl, P: SequencerPersistence, V: Versions> Sequence .await? .0; - let mut state_signer = StateSigner::new(state_key_pair, stake_table_commit); + let mut state_signer = StateSigner::new( + validator_config.state_private_key.clone(), + validator_config.state_public_key.clone(), + stake_table_commit, + ); if let Some(url) = state_relay_server { state_signer = state_signer.with_relay_server(url); } @@ -247,8 +248,8 @@ impl, P: SequencerPersistence, V: Versions> Sequence >, event_streamer: Arc>>, node_state: NodeState, - network_config: NetworkConfig, - validator_config: ValidatorConfig<::SignatureKey>, + network_config: NetworkConfig, + validator_config: ValidatorConfig, event_consumer: impl PersistenceEventConsumer + 'static, anchor_view: Option, proposal_fetcher_cfg: ProposalFetcherConfig, @@ -422,7 +423,7 @@ impl, P: SequencerPersistence, V: Versions> Sequence } /// Get the network config - pub fn network_config(&self) -> NetworkConfig { + pub fn network_config(&self) -> NetworkConfig { self.network_config.clone() } } diff --git a/sequencer/src/lib.rs b/sequencer/src/lib.rs index 1ebd47d0a7..9db5eacba2 100644 --- a/sequencer/src/lib.rs +++ b/sequencer/src/lib.rs @@ -289,7 +289,8 @@ pub async fn init_node( public_key: pub_key, private_key: network_params.private_staking_key, stake_value: 1, - state_key_pair, + state_public_key: state_key_pair.ver_key(), + state_private_key: state_key_pair.sign_key(), is_da, }; @@ -705,7 +706,7 @@ pub mod testing { } pub struct TestConfigBuilder { - config: HotShotConfig, + config: HotShotConfig, priv_keys: Vec, state_key_pairs: Vec, master_map: Arc>, @@ -779,7 +780,7 @@ pub mod testing { let known_nodes_with_stake = pub_keys .iter() .zip(&state_key_pairs) - .map(|(pub_key, state_key_pair)| PeerConfig:: { + .map(|(pub_key, state_key_pair)| PeerConfig:: { stake_table_entry: pub_key.stake_table_entry(1), state_ver_key: state_key_pair.ver_key(), }) @@ -787,7 +788,7 @@ pub mod testing { let master_map = MasterMap::new(); - let config: HotShotConfig = HotShotConfig { + let config: HotShotConfig = HotShotConfig { fixed_leader_for_gpuvid: 0, num_nodes_with_stake: num_nodes.try_into().unwrap(), known_da_nodes: known_nodes_with_stake.clone(), @@ -835,7 +836,7 @@ pub mod testing { #[derive(Clone)] pub struct TestConfig { - config: HotShotConfig, + config: HotShotConfig, priv_keys: Vec, state_key_pairs: Vec, master_map: Arc>, @@ -851,7 +852,7 @@ pub mod testing { self.priv_keys.len() } - pub fn hotshot_config(&self) -> &HotShotConfig { + pub fn hotshot_config(&self) -> &HotShotConfig { &self.config } @@ -944,7 +945,8 @@ pub mod testing { public_key: my_peer_config.stake_table_entry.stake_key, private_key: self.priv_keys[i].clone(), stake_value: my_peer_config.stake_table_entry.stake_amount.as_u64(), - state_key_pair: self.state_key_pairs[i].clone(), + state_public_key: self.state_key_pairs[i].ver_key(), + state_private_key: self.state_key_pairs[i].sign_key(), is_da, }; diff --git a/sequencer/src/persistence/fs.rs b/sequencer/src/persistence/fs.rs index 21c25850a9..d47dcb35cb 100644 --- a/sequencer/src/persistence/fs.rs +++ b/sequencer/src/persistence/fs.rs @@ -28,7 +28,8 @@ use hotshot_types::{ event::{Event, EventType, HotShotAction, LeafInfo}, message::{convert_proposal, Proposal}, simple_certificate::{ - NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, UpgradeCertificate, }, traits::{ block_contents::{BlockHeader, BlockPayload}, @@ -219,6 +220,10 @@ impl Inner { self.path.join("epoch_root_block_header") } + fn light_client_state_update_certificate_dir_path(&self) -> PathBuf { + self.path.join("state_cert") + } + fn update_migration(&mut self) -> anyhow::Result<()> { let path = self.migration(); let bytes = bincode::serialize(&self.migrated)?; @@ -1320,6 +1325,28 @@ impl SequencerPersistence for Persistence { Ok(()) } + async fn add_state_cert( + &self, + state_cert: LightClientStateUpdateCertificate, + ) -> anyhow::Result<()> { + let inner = self.inner.write().await; + let epoch = state_cert.epoch; + let dir_path = inner.light_client_state_update_certificate_dir_path(); + + fs::create_dir_all(dir_path.clone()) + .context("failed to create light client state update certificate dir")?; + + let bytes = bincode::serialize(&state_cert) + .context("serialize light client state update certificate")?; + + let file_path = dir_path.join(epoch.to_string()).with_extension("txt"); + fs::write(file_path, bytes).context(format!( + "writing light client state update certificate file for epoch {epoch:?}" + ))?; + + Ok(()) + } + async fn load_start_epoch_info(&self) -> anyhow::Result>> { let inner = self.inner.read().await; let drb_dir_path = inner.epoch_drb_result_dir_path(); @@ -1363,6 +1390,38 @@ impl SequencerPersistence for Persistence { Ok(result) } + + async fn load_state_cert(&self) -> anyhow::Result> { + let inner = self.inner.read().await; + let dir_path = inner.light_client_state_update_certificate_dir_path(); + // let block_header_dir_path = inner.epoch_root_block_header_dir_path(); + + let mut result = LightClientStateUpdateCertificate::genesis(); + + if !dir_path.is_dir() { + return Ok(result); + } + for (epoch, path) in epoch_files(dir_path)? { + if epoch <= result.epoch { + continue; + } + let bytes = fs::read(&path).context(format!( + "reading light client state update certificate {}", + path.display() + ))?; + let cert = bincode::deserialize::>(&bytes) + .context(format!( + "parsing light client state update certificate {}", + path.display() + ))?; + + if cert.epoch > result.epoch { + result = cert; + } + } + + Ok(result) + } } /// Update a `NetworkConfig` that may have originally been persisted with an old version. diff --git a/sequencer/src/persistence/no_storage.rs b/sequencer/src/persistence/no_storage.rs index 49f00bb46e..a4c644a713 100644 --- a/sequencer/src/persistence/no_storage.rs +++ b/sequencer/src/persistence/no_storage.rs @@ -20,7 +20,10 @@ use hotshot_types::{ drb::DrbResult, event::{Event, EventType, HotShotAction, LeafInfo}, message::Proposal, - simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate2, + UpgradeCertificate, + }, utils::View, }; @@ -243,4 +246,15 @@ impl SequencerPersistence for NoStorage { async fn load_start_epoch_info(&self) -> anyhow::Result>> { Ok(Vec::new()) } + + async fn add_state_cert( + &self, + _state_cert: LightClientStateUpdateCertificate, + ) -> anyhow::Result<()> { + Ok(()) + } + + async fn load_state_cert(&self) -> anyhow::Result> { + Ok(LightClientStateUpdateCertificate::::genesis()) + } } diff --git a/sequencer/src/persistence/sql.rs b/sequencer/src/persistence/sql.rs index a49c744313..34f6879ac6 100644 --- a/sequencer/src/persistence/sql.rs +++ b/sequencer/src/persistence/sql.rs @@ -43,7 +43,8 @@ use hotshot_types::{ event::{Event, EventType, HotShotAction, LeafInfo}, message::{convert_proposal, Proposal}, simple_certificate::{ - NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, UpgradeCertificate, }, traits::{ block_contents::{BlockHeader, BlockPayload}, @@ -1912,6 +1913,39 @@ impl SequencerPersistence for Persistence { tx.commit().await } + async fn add_state_cert( + &self, + state_cert: LightClientStateUpdateCertificate, + ) -> anyhow::Result<()> { + let state_cert_bytes = bincode::serialize(&state_cert) + .context("serializing light client state update certificate")?; + + let mut tx = self.db.write().await?; + tx.upsert( + "state_cert", + ["epoch", "state_cert"], + ["epoch"], + [(state_cert.epoch.u64() as i64, state_cert_bytes)], + ) + .await?; + tx.commit().await + } + + async fn load_state_cert(&self) -> anyhow::Result> { + let row = self + .db + .read() + .await? + .fetch_one("SELECT state_cert, MAX(epoch) from state_cert GROUP BY epoch") + .await?; + if let Some(data) = row.get::>, _>("state_cert") { + bincode::deserialize(&data) + .context("deserializing light client state update certificate") + } else { + Ok(LightClientStateUpdateCertificate::genesis()) + } + } + async fn load_start_epoch_info(&self) -> anyhow::Result>> { let rows = self .db diff --git a/sequencer/src/restart_tests.rs b/sequencer/src/restart_tests.rs index b7a365cd49..b0143f99db 100755 --- a/sequencer/src/restart_tests.rs +++ b/sequencer/src/restart_tests.rs @@ -828,7 +828,7 @@ fn start_orchestrator(port: u16, nodes: &[NodeParams], builder_port: u16) -> Joi }) .collect(); - let mut config = NetworkConfig:: { + let mut config = NetworkConfig:: { indexed_da: false, libp2p_config: Some(Libp2pConfig { bootstrap_nodes }), ..Default::default() diff --git a/sequencer/src/state_signature.rs b/sequencer/src/state_signature.rs index d226823146..e034d24461 100644 --- a/sequencer/src/state_signature.rs +++ b/sequencer/src/state_signature.rs @@ -2,34 +2,29 @@ use std::collections::{HashMap, VecDeque}; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; use async_lock::RwLock; -use espresso_types::Leaf2; use hotshot::types::{Event, EventType}; use hotshot_stake_table::vec_based::StakeTable; use hotshot_types::{ event::LeafInfo, light_client::{ - CircuitField, LightClientState, StateSignature, StateSignatureRequestBody, + CircuitField, LightClientState, StateSignKey, StateSignature, StateSignatureRequestBody, StateSignatureScheme, StateVerKey, }, signature_key::BLSPubKey, traits::{ - node_implementation::ConsensusTime, + block_contents::BlockHeader, signature_key::StakeTableEntryType, stake_table::{SnapshotVersion, StakeTableScheme as _}, }, PeerConfig, }; -use jf_crhf::CRHF; -use jf_rescue::{crhf::VariableLengthRescueCRHF, RescueError}; use jf_signature::SignatureScheme; use surf_disco::{Client, Url}; use tide_disco::error::ServerError; use vbs::version::StaticVersionType; -use crate::{SeqTypes, StateKeyPair}; +use crate::SeqTypes; /// A relay server that's collecting and serving the light client state signatures pub mod relay_server; @@ -39,8 +34,11 @@ const SIGNATURE_STORAGE_CAPACITY: usize = 100; #[derive(Debug)] pub struct StateSigner { - /// Key pair for signing a new light client state - key_pair: StateKeyPair, + /// Key for signing a new light client state + sign_key: StateSignKey, + + /// Key for verifying a light client state + ver_key: StateVerKey, /// The most recent light client state signatures signatures: RwLock, @@ -54,9 +52,14 @@ pub struct StateSigner { } impl StateSigner { - pub fn new(key_pair: StateKeyPair, stake_table_comm: StakeTableCommitmentType) -> Self { + pub fn new( + sign_key: StateSignKey, + ver_key: StateVerKey, + stake_table_comm: StakeTableCommitmentType, + ) -> Self { Self { - key_pair, + sign_key, + ver_key, stake_table_comm, signatures: Default::default(), relay_server_client: Default::default(), @@ -76,21 +79,14 @@ impl StateSigner { let Some(LeafInfo { leaf, .. }) = leaf_chain.first() else { return; }; - let view_number = leaf.view_number().u64(); - let block_height = leaf.height(); - let mut block_comm_root_bytes = vec![]; - if let Err(e) = leaf.block_comm_root().serialize(&mut block_comm_root_bytes) { - tracing::error!("Error serializing block commitment root: {:?}", e); - return; - } - match LightClientState::new(view_number, block_height, &block_comm_root_bytes) { + match leaf.block_header().get_light_client_state() { Ok(state) => { let signature = self.sign_new_state(&state).await; tracing::debug!("New leaves decided. Latest block height: {}", leaf.height(),); if let Some(client) = &self.relay_server_client { let request_body = StateSignatureRequestBody { - key: self.key_pair.ver_key(), + key: self.ver_key.clone(), state, signature, }; @@ -120,18 +116,13 @@ impl StateSigner { /// Sign the light client state at given height and store it. async fn sign_new_state(&self, state: &LightClientState) -> StateSignature { let msg: [CircuitField; 3] = state.into(); - let signature = StateSignatureScheme::sign( - &(), - self.key_pair.sign_key_ref(), - msg, - &mut rand::thread_rng(), - ) - .unwrap(); + let signature = + StateSignatureScheme::sign(&(), &self.sign_key, msg, &mut rand::thread_rng()).unwrap(); let mut pool_guard = self.signatures.write().await; pool_guard.push( - state.block_height as u64, + state.block_height, StateSignatureRequestBody { - key: self.key_pair.ver_key(), + key: self.ver_key.clone(), state: state.clone(), signature: signature.clone(), }, @@ -170,7 +161,7 @@ pub type StakeTableCommitmentType = (CircuitField, CircuitField, CircuitField); /// Helper function for stake table commitment pub fn static_stake_table_commitment( - known_nodes_with_stakes: &[PeerConfig], + known_nodes_with_stakes: &[PeerConfig], capacity: usize, ) -> (CircuitField, CircuitField, CircuitField) { let mut st = StakeTable::::new(capacity); diff --git a/sequencer/src/state_signature/relay_server.rs b/sequencer/src/state_signature/relay_server.rs index c718d8a185..94c8495ead 100644 --- a/sequencer/src/state_signature/relay_server.rs +++ b/sequencer/src/state_signature/relay_server.rs @@ -105,7 +105,7 @@ impl StateRelayServerDataSource for StateRelayServerState { state: LightClientState, signature: StateSignature, ) -> Result<(), Error> { - if (state.block_height as u64) <= self.latest_block_height.unwrap_or(0) { + if state.block_height <= self.latest_block_height.unwrap_or(0) { // This signature is no longer needed return Ok(()); } @@ -124,7 +124,7 @@ impl StateRelayServerDataSource for StateRelayServerState { "The posted signature is not valid.".to_owned(), )); } - let block_height = state.block_height as u64; + let block_height = state.block_height; // TODO(Chengyu): this serialization should be removed once `LightClientState` implements `Eq`. let bundles_at_height = self.bundles.entry(block_height).or_insert_with(|| { self.queue.insert(block_height); diff --git a/types/src/v0/config.rs b/types/src/v0/config.rs index 860871f0ca..a37bc82562 100644 --- a/types/src/v0/config.rs +++ b/types/src/v0/config.rs @@ -13,6 +13,8 @@ use vec1::Vec1; use crate::PubKey; +use super::SeqTypes; + /// This struct defines the public Hotshot validator configuration. /// Private key and state key pairs are excluded for security reasons. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -25,18 +27,17 @@ pub struct PublicValidatorConfig { state_key_pair: String, } -impl From> for PublicValidatorConfig { - fn from(v: ValidatorConfig) -> Self { - let ValidatorConfig:: { +impl From> for PublicValidatorConfig { + fn from(v: ValidatorConfig) -> Self { + let ValidatorConfig:: { public_key, private_key: _, stake_value, - state_key_pair, + state_public_key, + state_private_key: _, is_da, } = v; - let state_public_key = state_key_pair.ver_key(); - Self { public_key, stake_value, @@ -55,8 +56,8 @@ impl From> for PublicValidatorConfig { pub struct PublicHotShotConfig { start_threshold: (u64, u64), num_nodes_with_stake: NonZeroUsize, - known_nodes_with_stake: Vec>, - known_da_nodes: Vec>, + known_nodes_with_stake: Vec>, + known_da_nodes: Vec>, da_staked_committee_size: usize, fixed_leader_for_gpuvid: usize, next_view_timeout: u64, @@ -77,12 +78,12 @@ pub struct PublicHotShotConfig { epoch_start_block: u64, } -impl From> for PublicHotShotConfig { - fn from(v: HotShotConfig) -> Self { +impl From> for PublicHotShotConfig { + fn from(v: HotShotConfig) -> Self { // Destructure all fields from HotShotConfig to return an error // if new fields are added to HotShotConfig. This makes sure that we handle // all fields appropriately and do not miss any updates. - let HotShotConfig:: { + let HotShotConfig:: { start_threshold, num_nodes_with_stake, known_nodes_with_stake, @@ -135,7 +136,7 @@ impl From> for PublicHotShotConfig { } impl PublicHotShotConfig { - pub fn into_hotshot_config(self) -> HotShotConfig { + pub fn into_hotshot_config(self) -> HotShotConfig { HotShotConfig { start_threshold: self.start_threshold, num_nodes_with_stake: self.num_nodes_with_stake, @@ -162,11 +163,11 @@ impl PublicHotShotConfig { } } - pub fn known_nodes_with_stake(&self) -> Vec> { + pub fn known_nodes_with_stake(&self) -> Vec> { self.known_nodes_with_stake.clone() } - pub fn known_da_nodes(&self) -> Vec> { + pub fn known_da_nodes(&self) -> Vec> { self.known_da_nodes.clone() } } @@ -195,8 +196,8 @@ pub struct PublicNetworkConfig { random_builder: Option, } -impl From> for PublicNetworkConfig { - fn from(cfg: NetworkConfig) -> Self { +impl From> for PublicNetworkConfig { + fn from(cfg: NetworkConfig) -> Self { Self { rounds: cfg.rounds, indexed_da: cfg.indexed_da, @@ -225,8 +226,8 @@ impl From> for PublicNetworkConfig { impl PublicNetworkConfig { pub fn into_network_config( self, - my_own_validator_config: ValidatorConfig, - ) -> anyhow::Result> { + my_own_validator_config: ValidatorConfig, + ) -> anyhow::Result> { let node_index = self .config .known_nodes_with_stake diff --git a/types/src/v0/impls/header.rs b/types/src/v0/impls/header.rs index 3f765bca55..4bf7cc6b67 100644 --- a/types/src/v0/impls/header.rs +++ b/types/src/v0/impls/header.rs @@ -7,6 +7,7 @@ use ethers_conv::ToAlloy; use hotshot_query_service::{availability::QueryableHeader, explorer::ExplorerHeader}; use hotshot_types::{ data::VidCommitment, + light_client::LightClientState, traits::{ block_contents::{BlockHeader, BuilderFee}, node_implementation::NodeType, @@ -1123,6 +1124,20 @@ impl BlockHeader for Header { fn builder_commitment(&self) -> BuilderCommitment { self.builder_commitment().clone() } + + fn get_light_client_state(&self) -> anyhow::Result { + let mut block_comm_root_bytes = vec![]; + self.block_merkle_tree_root() + .serialize_compressed(&mut block_comm_root_bytes)?; + + Ok(LightClientState { + view_number: self.height(), // TODO(Chengyu): fix view number + block_height: self.height(), + block_comm_root: hotshot_types::light_client::hash_bytes_to_field( + &block_comm_root_bytes, + )?, + }) + } } impl QueryableHeader for Header { diff --git a/types/src/v0/impls/instance_state.rs b/types/src/v0/impls/instance_state.rs index cf5c2dbeaf..1c8c8a5add 100644 --- a/types/src/v0/impls/instance_state.rs +++ b/types/src/v0/impls/instance_state.rs @@ -7,7 +7,7 @@ use vbs::version::{StaticVersion, StaticVersionType}; use super::state::ValidatedState; use crate::v0::{ - traits::StateCatchup, v0_99::ChainConfig, GenesisHeader, L1BlockInfo, L1Client, PubKey, + traits::StateCatchup, v0_99::ChainConfig, GenesisHeader, L1BlockInfo, L1Client, SeqTypes, Timestamp, Upgrade, UpgradeMode, }; @@ -163,7 +163,7 @@ impl Default for NodeState { impl InstanceState for NodeState {} impl Upgrade { - pub fn set_hotshot_config_parameters(&self, config: &mut HotShotConfig) { + pub fn set_hotshot_config_parameters(&self, config: &mut HotShotConfig) { match &self.mode { UpgradeMode::View(v) => { config.start_proposing_view = v.start_proposing_view; diff --git a/types/src/v0/impls/stake_table.rs b/types/src/v0/impls/stake_table.rs index d65d6f8335..f56accfe4a 100644 --- a/types/src/v0/impls/stake_table.rs +++ b/types/src/v0/impls/stake_table.rs @@ -81,8 +81,8 @@ impl StakeTables { StakeTableChange::Remove(_) => None, }); - let mut consensus_stake_table: Vec> = vec![]; - let mut da_members: Vec> = vec![]; + let mut consensus_stake_table: Vec> = vec![]; + let mut da_members: Vec> = vec![]; for node in currently_staking { consensus_stake_table.push(node.clone().into()); if node.da { @@ -143,19 +143,19 @@ struct Committee { /// The nodes eligible for leadership. /// NOTE: This is currently a hack because the DA leader needs to be the quorum /// leader but without voting rights. - eligible_leaders: Vec>, + eligible_leaders: Vec>, /// Keys for nodes participating in the network - stake_table: Vec>, + stake_table: Vec>, /// Keys for DA members - da_members: Vec>, + da_members: Vec>, /// Stake entries indexed by public key, for efficient lookup. - indexed_stake_table: HashMap>, + indexed_stake_table: HashMap>, /// DA entries indexed by public key, for efficient lookup. - indexed_da_members: HashMap>, + indexed_da_members: HashMap>, } impl EpochCommittees { @@ -219,8 +219,8 @@ impl EpochCommittees { pub fn new_stake( // TODO remove `new` from trait and rename this to `new`. // https://github.com/EspressoSystems/HotShot/commit/fcb7d54a4443e29d643b3bbc53761856aef4de8b - committee_members: Vec>, - da_members: Vec>, + committee_members: Vec>, + da_members: Vec>, instance_state: &NodeState, epoch_size: u64, ) -> Self { @@ -312,14 +312,14 @@ impl Membership for EpochCommittees { fn new( // TODO remove `new` from trait and remove this fn as well. // https://github.com/EspressoSystems/HotShot/commit/fcb7d54a4443e29d643b3bbc53761856aef4de8b - _committee_members: Vec>, - _da_members: Vec>, + _committee_members: Vec>, + _da_members: Vec>, ) -> Self { panic!("This function has been replaced with new_stake()"); } /// Get the stake table for the current view - fn stake_table(&self, epoch: Option) -> Vec> { + fn stake_table(&self, epoch: Option) -> Vec> { if let Some(st) = self.state(&epoch) { st.stake_table.clone() } else { @@ -327,7 +327,7 @@ impl Membership for EpochCommittees { } } /// Get the stake table for the current view - fn da_stake_table(&self, epoch: Option) -> Vec> { + fn da_stake_table(&self, epoch: Option) -> Vec> { if let Some(sc) = self.state(&epoch) { sc.da_members.clone() } else { @@ -376,14 +376,14 @@ impl Membership for EpochCommittees { } /// Get the stake table entry for a public key - fn stake(&self, pub_key: &PubKey, epoch: Option) -> Option> { + fn stake(&self, pub_key: &PubKey, epoch: Option) -> Option> { // Only return the stake if it is above zero self.state(&epoch) .and_then(|h| h.indexed_stake_table.get(pub_key).cloned()) } /// Get the DA stake table entry for a public key - fn da_stake(&self, pub_key: &PubKey, epoch: Option) -> Option> { + fn da_stake(&self, pub_key: &PubKey, epoch: Option) -> Option> { // Only return the stake if it is above zero self.state(&epoch) .and_then(|h| h.indexed_da_members.get(pub_key).cloned()) diff --git a/types/src/v0/mod.rs b/types/src/v0/mod.rs index 9b95e4d2fb..595b2e0dc8 100644 --- a/types/src/v0/mod.rs +++ b/types/src/v0/mod.rs @@ -188,7 +188,7 @@ pub type Event = hotshot::types::Event; pub type PubKey = BLSPubKey; pub type PrivKey = ::PrivateKey; -pub type NetworkConfig = hotshot_types::network::NetworkConfig; +pub type NetworkConfig = hotshot_types::network::NetworkConfig; pub use self::impls::{NodeState, SolverAuctionResultsProvider, ValidatedState}; pub use crate::v0_1::{ diff --git a/types/src/v0/traits.rs b/types/src/v0/traits.rs index e4f1389077..91942cae5f 100644 --- a/types/src/v0/traits.rs +++ b/types/src/v0/traits.rs @@ -18,7 +18,8 @@ use hotshot_types::{ event::{HotShotAction, LeafInfo}, message::{convert_proposal, Proposal, UpgradeLock}, simple_certificate::{ - NextEpochQuorumCertificate2, QuorumCertificate, QuorumCertificate2, UpgradeCertificate, + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate, + QuorumCertificate2, UpgradeCertificate, }, traits::{ node_implementation::{ConsensusTime, NodeType, Versions}, @@ -539,6 +540,7 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { &self, ) -> anyhow::Result>>; async fn load_start_epoch_info(&self) -> anyhow::Result>>; + async fn load_state_cert(&self) -> anyhow::Result>; /// Load the latest known consensus state. /// @@ -647,6 +649,11 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { .await .context("loading start epoch info")?; + let state_cert = self + .load_state_cert() + .await + .context("loading light client state update certificate")?; + tracing::info!( ?leaf, ?view, @@ -657,6 +664,7 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { ?undecided_state, ?saved_proposals, ?upgrade_certificate, + ?state_cert, "loaded consensus state" ); @@ -682,6 +690,7 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { undecided_state, saved_vid_shares: Default::default(), // TODO: implement saved_vid_shares start_epoch_info, + state_cert, }, anchor_view, )) @@ -846,6 +855,11 @@ pub trait SequencerPersistence: Sized + Send + Sync + Clone + 'static { epoch: ::Epoch, block_header: ::BlockHeader, ) -> anyhow::Result<()>; + + async fn add_state_cert( + &self, + state_cert: LightClientStateUpdateCertificate, + ) -> anyhow::Result<()>; } #[async_trait] @@ -990,6 +1004,13 @@ impl Storage for Arc

{ ) -> anyhow::Result<()> { (**self).add_epoch_root(epoch, block_header).await } + + async fn update_state_cert( + &self, + state_cert: LightClientStateUpdateCertificate, + ) -> anyhow::Result<()> { + (**self).add_state_cert(state_cert).await + } } /// Data that can be deserialized from a subslice of namespace payload bytes. diff --git a/types/src/v0/v0_3/stake_table.rs b/types/src/v0/v0_3/stake_table.rs index a8027869ce..9b9a1a3086 100644 --- a/types/src/v0/v0_3/stake_table.rs +++ b/types/src/v0/v0_3/stake_table.rs @@ -1,4 +1,4 @@ -use crate::PubKey; +use crate::SeqTypes; use derive_more::derive::{From, Into}; use hotshot_contract_adapter::stake_table::NodeInfoJf; use hotshot_types::{network::PeerConfigKeys, PeerConfig}; @@ -9,15 +9,15 @@ pub struct PermissionedStakeTableEntry(NodeInfoJf); /// Stake table holding all staking information (DA and non-DA stakers) #[derive(Debug, Clone, Serialize, Deserialize, From)] -pub struct CombinedStakeTable(Vec>); +pub struct CombinedStakeTable(Vec>); #[derive(Clone, Debug, From, Into)] /// NewType to disambiguate DA Membership -pub struct DAMembers(pub Vec>); +pub struct DAMembers(pub Vec>); #[derive(Clone, Debug, From, Into)] /// NewType to disambiguate StakeTable -pub struct StakeTable(pub Vec>); +pub struct StakeTable(pub Vec>); #[derive(Clone, Debug)] pub struct StakeTables { diff --git a/utils/Cargo.toml b/utils/Cargo.toml index e0ced8190d..b6305ce70b 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -23,6 +23,7 @@ ethers-conv = { workspace = true } futures = { workspace = true } hotshot = { workspace = true } hotshot-contract-adapter = { workspace = true } +hotshot-example-types = { workspace = true } hotshot-types = { workspace = true } log-panics = { workspace = true } portpicker = { workspace = true } diff --git a/utils/src/stake_table.rs b/utils/src/stake_table.rs index f82d6ac66c..d34ceb45de 100644 --- a/utils/src/stake_table.rs +++ b/utils/src/stake_table.rs @@ -14,22 +14,25 @@ use ethers::{ signers::{coins_bip39::English, MnemonicBuilder, Signer as _}, types::Address, }; -use hotshot::types::BLSPubKey; +use hotshot::types::{BLSPubKey, SchnorrPubKey}; use hotshot_contract_adapter::stake_table::{bls_jf_to_sol, NodeInfoJf}; -use hotshot_types::network::PeerConfigKeys; +use hotshot_types::{network::PeerConfigKeys, traits::node_implementation::NodeType}; use url::Url; /// A stake table config stored in a file #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(bound(deserialize = ""))] -pub struct PermissionedStakeTableConfig { +pub struct PermissionedStakeTableConfig { /// The list of public keys that are initially inserted into the /// permissioned stake table contract. #[serde(default)] - pub public_keys: Vec>, + pub public_keys: Vec>, } -impl PermissionedStakeTableConfig { +impl PermissionedStakeTableConfig +where + TYPES: NodeType, +{ pub fn from_toml_file(path: &Path) -> anyhow::Result { let config_file_as_string: String = fs::read_to_string(path) .unwrap_or_else(|_| panic!("Could not read config file located at {}", path.display())); @@ -45,8 +48,11 @@ impl PermissionedStakeTableConfig { } } -impl From for Vec { - fn from(value: PermissionedStakeTableConfig) -> Self { +impl From> for Vec +where + TYPES: NodeType, +{ + fn from(value: PermissionedStakeTableConfig) -> Self { value .public_keys .into_iter() @@ -72,14 +78,17 @@ impl From for BLSPubKey { /// Information to add and remove stakers in the permissioned stake table contract. #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(bound(deserialize = ""))] -pub struct PermissionedStakeTableUpdate { +pub struct PermissionedStakeTableUpdate { #[serde(default)] stakers_to_remove: Vec, #[serde(default)] - new_stakers: Vec>, + new_stakers: Vec>, } -impl PermissionedStakeTableUpdate { +impl PermissionedStakeTableUpdate +where + TYPES: NodeType, +{ pub fn from_toml_file(path: &Path) -> anyhow::Result { let config_file_as_string: String = fs::read_to_string(path) .unwrap_or_else(|_| panic!("Could not read config file located at {}", path.display())); @@ -112,14 +121,17 @@ impl PermissionedStakeTableUpdate { } } -pub async fn update_stake_table( +pub async fn update_stake_table( l1url: Url, l1_interval: Duration, mnemonic: String, account_index: u32, contract_address: Address, - update: PermissionedStakeTableUpdate, -) -> anyhow::Result<()> { + update: PermissionedStakeTableUpdate, +) -> anyhow::Result<()> +where + TYPES: NodeType, +{ let provider = Provider::::try_from(l1url.to_string())?.interval(l1_interval); let chain_id = provider.get_chainid().await?.as_u64(); let wallet = MnemonicBuilder::::default() @@ -145,7 +157,11 @@ pub async fn update_stake_table( #[cfg(test)] mod test { use hotshot::types::{BLSPubKey, SignatureKey}; - use hotshot_types::{light_client::StateKeyPair, network::PeerConfigKeys}; + use hotshot_example_types::node_types::TestTypes; + use hotshot_types::{ + light_client::StateKeyPair, network::PeerConfigKeys, signature_key::SchnorrPubKey, + traits::node_implementation::NodeType, + }; use toml::toml; use crate::{ @@ -153,20 +169,24 @@ mod test { test_utils::setup_test, }; - fn assert_peer_config_eq(p1: &PeerConfigKeys, p2: &PeerConfigKeys) { + fn assert_peer_config_eq( + p1: &PeerConfigKeys, + p2: &PeerConfigKeys, + ) { assert_eq!(p1.stake_table_key, p2.stake_table_key); assert_eq!(p1.state_ver_key, p2.state_ver_key); assert_eq!(p1.stake, p2.stake); assert_eq!(p1.da, p2.da); } - fn mk_keys() -> Vec> { + fn mk_keys>( + ) -> Vec> { let mut keys = Vec::new(); for i in 0..3 { let (pubkey, _) = BLSPubKey::generated_from_seed_indexed([0; 32], i); let state_kp = StateKeyPair::generate_from_seed_indexed([0; 32], i).0; let ver_key = state_kp.ver_key(); - keys.push(PeerConfigKeys { + keys.push(PeerConfigKeys:: { stake_table_key: pubkey, state_ver_key: ver_key, stake: i + 1, @@ -180,7 +200,7 @@ mod test { fn test_permissioned_stake_table_from_toml() { setup_test(); - let keys = mk_keys(); + let keys = mk_keys::(); let st_key_1 = keys[0].stake_table_key.to_string(); let verkey_1 = keys[0].state_ver_key.to_string(); @@ -233,7 +253,7 @@ mod test { fn test_permissioned_stake_table_update_from_toml() { setup_test(); - let keys = mk_keys(); + let keys = mk_keys::(); let st_key_1 = keys[0].stake_table_key.to_string(); From 741fc9b02f22695009d8a5862222af90ef7593bf Mon Sep 17 00:00:00 2001 From: MRain Date: Tue, 11 Mar 2025 19:12:46 -0400 Subject: [PATCH 05/12] complete logic for state_cert --- Cargo.lock | 1 + hotshot-task-impls/Cargo.toml | 1 + hotshot-task-impls/src/consensus/mod.rs | 22 +++++++-- hotshot-task-impls/src/helpers.rs | 48 ++++++++++++++++++- .../src/quorum_vote/handlers.rs | 1 - hotshot-types/src/traits/storage.rs | 1 - sequencer-sqlite/Cargo.lock | 3 ++ 7 files changed, 70 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 037f629c61..ad3a38c493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5526,6 +5526,7 @@ dependencies = [ "hotshot-utils", "jf-vid", "lru 0.12.5", + "primitive-types", "rand 0.8.5", "serde", "sha2 0.10.8", diff --git a/hotshot-task-impls/Cargo.toml b/hotshot-task-impls/Cargo.toml index 672f36a1d8..579d3ff3ec 100644 --- a/hotshot-task-impls/Cargo.toml +++ b/hotshot-task-impls/Cargo.toml @@ -26,6 +26,7 @@ hotshot-types = { workspace = true } hotshot-utils = { workspace = true } jf-vid = { workspace = true } lru = { workspace = true } +primitive-types = { workspace = true } rand = { workspace = true } serde = { workspace = true } sha2 = { workspace = true } diff --git a/hotshot-task-impls/src/consensus/mod.rs b/hotshot-task-impls/src/consensus/mod.rs index c37e804893..9f52b4f5a1 100644 --- a/hotshot-task-impls/src/consensus/mod.rs +++ b/hotshot-task-impls/src/consensus/mod.rs @@ -32,7 +32,10 @@ use self::handlers::{ }; use crate::{ events::HotShotEvent, - helpers::{broadcast_event, validate_qc_and_next_epoch_qc}, + helpers::{ + broadcast_event, validate_light_client_state_update_certificate, + validate_qc_and_next_epoch_qc, + }, vote_collection::{ExtendedQuorumVoteCollectorsMap, VoteCollectorsMap}, }; @@ -176,10 +179,23 @@ impl, V: Versions> ConsensusTaskSt return Ok(()); } - // TODO(Chengyu): verify the light client state in epoch transition qc. + if let Err(e) = validate_light_client_state_update_certificate( + &eqc.state_cert, + &self.membership_coordinator, + ) + .await + { + tracing::error!( + "Received invalid light client state update certificate: {}", + e + ); + return Ok(()); + } let mut consensus_writer = self.consensus.write().await; - let high_qc_updated = consensus_writer.update_high_qc(eqc.qc.clone()).is_ok(); + let high_qc_updated = consensus_writer + .update_high_qc_and_state_cert(eqc.qc.clone(), eqc.state_cert.clone()) + .is_ok(); let next_high_qc_updated = consensus_writer .update_next_epoch_high_qc(next_epoch_high_qc.clone()) .is_ok(); diff --git a/hotshot-task-impls/src/helpers.rs b/hotshot-task-impls/src/helpers.rs index 6884bc0169..54168a35d6 100644 --- a/hotshot-task-impls/src/helpers.rs +++ b/hotshot-task-impls/src/helpers.rs @@ -22,13 +22,16 @@ use hotshot_types::{ event::{Event, EventType, LeafInfo}, message::{Proposal, UpgradeLock}, request_response::ProposalRequestPayload, - simple_certificate::{NextEpochQuorumCertificate2, QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ + LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate2, + UpgradeCertificate, + }, simple_vote::HasEpoch, traits::{ block_contents::BlockHeader, election::Membership, node_implementation::{ConsensusTime, NodeImplementation, NodeType, Versions}, - signature_key::SignatureKey, + signature_key::{SignatureKey, StakeTableEntryType, StateSignatureKey}, storage::Storage, BlockPayload, ValidatedState, }, @@ -40,6 +43,7 @@ use hotshot_types::{ StakeTableEntries, }; use hotshot_utils::anytrace::*; +use primitive_types::U256; use tokio::time::timeout; use tracing::instrument; @@ -996,3 +1000,43 @@ pub async fn validate_qc_and_next_epoch_qc( } Ok(()) } + +/// Validates qc's signatures and, if provided, validates next_epoch_qc's signatures and whether it +/// corresponds to the provided high_qc. +pub async fn validate_light_client_state_update_certificate( + state_cert: &LightClientStateUpdateCertificate, + membership_coordinator: &EpochMembershipCoordinator, +) -> Result<()> { + let epoch_membership = membership_coordinator + .membership_for_epoch(state_cert.epoch()) + .await?; + + let membership_stake_table = epoch_membership.stake_table().await; + let membership_success_threshold = epoch_membership.success_threshold().await; + + let mut state_key_map = HashMap::new(); + membership_stake_table.into_iter().for_each(|config| { + state_key_map.insert( + config.state_ver_key.clone(), + config.stake_table_entry.stake(), + ); + }); + + let mut accumulated_stake = U256::zero(); + let state_msg = (&state_cert.light_client_state).into(); + for (key, sig) in state_cert.signatures.iter() { + if let Some(stake) = state_key_map.get(key) { + accumulated_stake += *stake; + if !key.verify_state_sig(sig, &state_msg) { + bail!("Invalid light client state update certificate signature"); + } + } else { + bail!("Invalid light client state update certificate signature"); + } + } + if accumulated_stake < U256::from(membership_success_threshold.get()) { + bail!("Light client state update certificate does not meet the success threshold"); + } + + Ok(()) +} diff --git a/hotshot-task-impls/src/quorum_vote/handlers.rs b/hotshot-task-impls/src/quorum_vote/handlers.rs index 507d6c759a..8cb3fb7c26 100644 --- a/hotshot-task-impls/src/quorum_vote/handlers.rs +++ b/hotshot-task-impls/src/quorum_vote/handlers.rs @@ -742,7 +742,6 @@ pub(crate) async fn submit_vote, V if extended_vote { tracing::debug!("sending extended vote to everybody",); - // TODO(Chengyu): add light client state let light_client_state = leaf .block_header() .get_light_client_state() diff --git a/hotshot-types/src/traits/storage.rs b/hotshot-types/src/traits/storage.rs index 08ad4404df..48235b5beb 100644 --- a/hotshot-types/src/traits/storage.rs +++ b/hotshot-types/src/traits/storage.rs @@ -38,7 +38,6 @@ pub trait Storage: Send + Sync + Clone { /// Add a proposal to the stored VID proposals. async fn append_vid(&self, proposal: &Proposal>) -> Result<()>; /// Add a proposal to the stored VID proposals. - /// TODO(Chengyu): fix this async fn append_vid2(&self, proposal: &Proposal>) -> Result<()>; diff --git a/sequencer-sqlite/Cargo.lock b/sequencer-sqlite/Cargo.lock index 96be4a2167..76d10f64f0 100644 --- a/sequencer-sqlite/Cargo.lock +++ b/sequencer-sqlite/Cargo.lock @@ -5182,7 +5182,9 @@ dependencies = [ "either", "futures", "hotshot-utils", + "jf-crhf 0.1.0", "jf-pcs", + "jf-rescue", "jf-signature 0.2.0", "jf-utils", "jf-vid", @@ -9568,6 +9570,7 @@ dependencies = [ "futures", "hotshot", "hotshot-contract-adapter", + "hotshot-example-types", "hotshot-types", "log-panics", "portpicker", From 291633ac9da6e5d8078f5ff2b62836288503d730 Mon Sep 17 00:00:00 2001 From: MRain Date: Tue, 11 Mar 2025 19:21:51 -0400 Subject: [PATCH 06/12] typos --- hotshot-types/src/traits/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hotshot-types/src/traits/storage.rs b/hotshot-types/src/traits/storage.rs index 48235b5beb..487abddcec 100644 --- a/hotshot-types/src/traits/storage.rs +++ b/hotshot-types/src/traits/storage.rs @@ -114,7 +114,7 @@ pub trait Storage: Send + Sync + Clone { async fn update_high_qc2(&self, high_qc: QuorumCertificate2) -> Result<()> { self.update_high_qc(high_qc.to_qc()).await } - /// Udpate the light client state update certificate in storage. + /// Update the light client state update certificate in storage. async fn update_state_cert( &self, state_cert: LightClientStateUpdateCertificate, From 9dacab7eb4bf599ff540ae103f61e80ce2e74eb0 Mon Sep 17 00:00:00 2001 From: MRain Date: Tue, 11 Mar 2025 19:22:19 -0400 Subject: [PATCH 07/12] fmt --- hotshot-builder-core/src/testing/basic_test.rs | 1 - hotshot-orchestrator/src/lib.rs | 6 ++++-- hotshot-query-service/src/testing/mocks.rs | 3 +-- sequencer/src/bin/deploy.rs | 3 +-- types/src/v0/config.rs | 3 +-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/hotshot-builder-core/src/testing/basic_test.rs b/hotshot-builder-core/src/testing/basic_test.rs index 48174eca69..d4ae0be390 100644 --- a/hotshot-builder-core/src/testing/basic_test.rs +++ b/hotshot-builder-core/src/testing/basic_test.rs @@ -22,7 +22,6 @@ mod tests { use committable::{Commitment, CommitmentBoundsArkless, Committable}; use hotshot::types::SignatureKey; use hotshot_builder_api::v0_2::data_source::BuilderDataSource; - use hotshot_example_types::{ auction_results_provider_types::TestAuctionResult, block_types::{TestBlockHeader, TestBlockPayload, TestMetadata, TestTransaction}, diff --git a/hotshot-orchestrator/src/lib.rs b/hotshot-orchestrator/src/lib.rs index 391c594e8a..596ef2295e 100644 --- a/hotshot-orchestrator/src/lib.rs +++ b/hotshot-orchestrator/src/lib.rs @@ -23,8 +23,10 @@ use csv::Writer; use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; use hotshot_types::{ network::{BuilderType, NetworkConfig, PublicKeysFile}, - traits::node_implementation::NodeType, - traits::signature_key::{SignatureKey, StakeTableEntryType}, + traits::{ + node_implementation::NodeType, + signature_key::{SignatureKey, StakeTableEntryType}, + }, PeerConfig, }; use libp2p_identity::{ diff --git a/hotshot-query-service/src/testing/mocks.rs b/hotshot-query-service/src/testing/mocks.rs index 7915e73f82..9fcde8a734 100644 --- a/hotshot-query-service/src/testing/mocks.rs +++ b/hotshot-query-service/src/testing/mocks.rs @@ -21,10 +21,9 @@ use hotshot_example_types::{ state_types::{TestInstanceState, TestValidatedState}, storage_types::TestStorage, }; -use hotshot_types::signature_key::SchnorrPubKey; use hotshot_types::{ data::{QuorumProposal, ViewNumber}, - signature_key::BLSPubKey, + signature_key::{BLSPubKey, SchnorrPubKey}, traits::node_implementation::{NodeType, Versions}, }; use jf_merkle_tree::{ diff --git a/sequencer/src/bin/deploy.rs b/sequencer/src/bin/deploy.rs index fc43aa1ea9..e0214bdf25 100644 --- a/sequencer/src/bin/deploy.rs +++ b/sequencer/src/bin/deploy.rs @@ -1,8 +1,7 @@ use std::{fs::File, io::stdout, path::PathBuf, time::Duration}; use clap::Parser; -use espresso_types::parse_duration; -use espresso_types::SeqTypes; +use espresso_types::{parse_duration, SeqTypes}; use ethers::types::Address; use futures::FutureExt; use hotshot_stake_table::config::STAKE_TABLE_CAPACITY; diff --git a/types/src/v0/config.rs b/types/src/v0/config.rs index a37bc82562..274552b1db 100644 --- a/types/src/v0/config.rs +++ b/types/src/v0/config.rs @@ -11,9 +11,8 @@ use serde::{Deserialize, Serialize}; use tide_disco::Url; use vec1::Vec1; -use crate::PubKey; - use super::SeqTypes; +use crate::PubKey; /// This struct defines the public Hotshot validator configuration. /// Private key and state key pairs are excluded for security reasons. From ab49047b4970d03fd8e76cc8e52c3368c0815977 Mon Sep 17 00:00:00 2001 From: MRain Date: Tue, 11 Mar 2025 22:30:06 -0400 Subject: [PATCH 08/12] view number in light client state --- hotshot-example-types/src/block_types.rs | 6 +++--- hotshot-task-impls/src/quorum_vote/handlers.rs | 2 +- hotshot-types/src/traits/block_contents.rs | 2 +- sequencer/src/state_signature.rs | 5 ++++- types/src/v0/impls/header.rs | 9 ++++++--- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/hotshot-example-types/src/block_types.rs b/hotshot-example-types/src/block_types.rs index e46b442a46..785560ca2c 100644 --- a/hotshot-example-types/src/block_types.rs +++ b/hotshot-example-types/src/block_types.rs @@ -17,7 +17,7 @@ use hotshot_types::{ light_client::LightClientState, traits::{ block_contents::{BlockHeader, BuilderFee, EncodeBytes, TestableBlock, Transaction}, - node_implementation::NodeType, + node_implementation::{ConsensusTime, NodeType}, BlockPayload, ValidatedState, }, utils::BuilderCommitment, @@ -405,9 +405,9 @@ impl< Some(TYPES::AuctionResult { urls: vec![] }) } - fn get_light_client_state(&self) -> anyhow::Result { + fn get_light_client_state(&self, view: TYPES::View) -> anyhow::Result { LightClientState::new( - self.block_number, + view.u64(), self.block_number, self.payload_commitment.as_ref(), ) diff --git a/hotshot-task-impls/src/quorum_vote/handlers.rs b/hotshot-task-impls/src/quorum_vote/handlers.rs index 8cb3fb7c26..152bc5da3a 100644 --- a/hotshot-task-impls/src/quorum_vote/handlers.rs +++ b/hotshot-task-impls/src/quorum_vote/handlers.rs @@ -744,7 +744,7 @@ pub(crate) async fn submit_vote, V tracing::debug!("sending extended vote to everybody",); let light_client_state = leaf .block_header() - .get_light_client_state() + .get_light_client_state(view_number) .wrap() .context(error!("Failed to generate light client state"))?; let signature = ::sign_state( diff --git a/hotshot-types/src/traits/block_contents.rs b/hotshot-types/src/traits/block_contents.rs index 0ace2ffd3a..2b72aa0e17 100644 --- a/hotshot-types/src/traits/block_contents.rs +++ b/hotshot-types/src/traits/block_contents.rs @@ -217,5 +217,5 @@ pub trait BlockHeader: fn get_auction_results(&self) -> Option; /// Get the light client state - fn get_light_client_state(&self) -> anyhow::Result; + fn get_light_client_state(&self, view: TYPES::View) -> anyhow::Result; } diff --git a/sequencer/src/state_signature.rs b/sequencer/src/state_signature.rs index e034d24461..43329323d2 100644 --- a/sequencer/src/state_signature.rs +++ b/sequencer/src/state_signature.rs @@ -79,7 +79,10 @@ impl StateSigner { let Some(LeafInfo { leaf, .. }) = leaf_chain.first() else { return; }; - match leaf.block_header().get_light_client_state() { + match leaf + .block_header() + .get_light_client_state(leaf.view_number()) + { Ok(state) => { let signature = self.sign_new_state(&state).await; tracing::debug!("New leaves decided. Latest block height: {}", leaf.height(),); diff --git a/types/src/v0/impls/header.rs b/types/src/v0/impls/header.rs index 4bf7cc6b67..91e75cb680 100644 --- a/types/src/v0/impls/header.rs +++ b/types/src/v0/impls/header.rs @@ -10,7 +10,7 @@ use hotshot_types::{ light_client::LightClientState, traits::{ block_contents::{BlockHeader, BuilderFee}, - node_implementation::NodeType, + node_implementation::{ConsensusTime, NodeType}, signature_key::BuilderSignatureKey, BlockPayload, ValidatedState as _, }, @@ -1125,13 +1125,16 @@ impl BlockHeader for Header { self.builder_commitment().clone() } - fn get_light_client_state(&self) -> anyhow::Result { + fn get_light_client_state( + &self, + view: ::View, + ) -> anyhow::Result { let mut block_comm_root_bytes = vec![]; self.block_merkle_tree_root() .serialize_compressed(&mut block_comm_root_bytes)?; Ok(LightClientState { - view_number: self.height(), // TODO(Chengyu): fix view number + view_number: view.u64(), block_height: self.height(), block_comm_root: hotshot_types::light_client::hash_bytes_to_field( &block_comm_root_bytes, From c3b3bc7e18127308f04e0679ebcc8476da85a47b Mon Sep 17 00:00:00 2001 From: MRain Date: Wed, 12 Mar 2025 14:55:27 -0400 Subject: [PATCH 09/12] better tracing & bug fix --- hotshot-task-impls/src/quorum_vote/handlers.rs | 2 +- hotshot-task-impls/src/vote_collection.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/hotshot-task-impls/src/quorum_vote/handlers.rs b/hotshot-task-impls/src/quorum_vote/handlers.rs index 152bc5da3a..1da99b5f84 100644 --- a/hotshot-task-impls/src/quorum_vote/handlers.rs +++ b/hotshot-task-impls/src/quorum_vote/handlers.rs @@ -754,7 +754,7 @@ pub(crate) async fn submit_vote, V .wrap() .context(error!("Failed to sign the light client state"))?; let state_vote = LightClientStateUpdateVote { - epoch: TYPES::Epoch::new(epoch_height), + epoch: TYPES::Epoch::new(epoch_from_block_number(leaf.height(), epoch_height)), light_client_state, signature, }; diff --git a/hotshot-task-impls/src/vote_collection.rs b/hotshot-task-impls/src/vote_collection.rs index 0eabd6888f..c05100a667 100644 --- a/hotshot-task-impls/src/vote_collection.rs +++ b/hotshot-task-impls/src/vote_collection.rs @@ -748,12 +748,13 @@ impl ExtendedQuorumVoteCollectionTaskState Ok(None), (Some(cert), Some(state_cert)) => { - tracing::debug!("Certificate Formed! {:?}", cert); - let eqc = ExtendedQuorumCertificate { qc: cert, state_cert, }; + + tracing::debug!("Certificate Formed! {:?}", eqc); + broadcast_event( Arc::new(HotShotEvent::ExtendedQcFormed(Left(eqc.clone()))), event_stream, @@ -819,7 +820,10 @@ pub async fn handle_extended_quorum_vote( ) -> Result<()> { match collectors.entry(vote.view_number()) { Entry::Vacant(entry) => { - tracing::debug!("Starting vote handle for view {:?}", vote.view_number()); + tracing::debug!( + "Starting extended quorum vote handle for view {:?}", + vote.view_number() + ); let info = AccumulatorInfo { public_key, membership: membership.clone(), From 77b614f2665f151aa259709d9a7f115cd26a900c Mon Sep 17 00:00:00 2001 From: MRain Date: Wed, 12 Mar 2025 16:00:51 -0400 Subject: [PATCH 10/12] next_epoch_quorum_vote should collect ExtendedQuorumVote --- hotshot-task-impls/src/vote_collection.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hotshot-task-impls/src/vote_collection.rs b/hotshot-task-impls/src/vote_collection.rs index c05100a667..7145f33780 100644 --- a/hotshot-task-impls/src/vote_collection.rs +++ b/hotshot-task-impls/src/vote_collection.rs @@ -520,11 +520,18 @@ impl // #3967 REVIEW NOTE: Should we error if self.epoch is None? self.accumulate_vote(&vote.clone().into(), sender).await }, + HotShotEvent::ExtendedQuorumVoteRecv(vote) => { + self.accumulate_vote(&vote.vote.clone().into(), sender) + .await + }, _ => Ok(None), } } fn filter(event: Arc>) -> bool { - matches!(event.as_ref(), HotShotEvent::QuorumVoteRecv(_)) + matches!( + event.as_ref(), + HotShotEvent::QuorumVoteRecv(_) | HotShotEvent::ExtendedQuorumVoteRecv(_) + ) } } From 56dcde607160db327f27178c47904cba7f5a2932 Mon Sep 17 00:00:00 2001 From: MRain Date: Wed, 12 Mar 2025 21:02:46 -0400 Subject: [PATCH 11/12] resolves missing dep --- hotshot-task-impls/src/quorum_proposal/handlers.rs | 8 ++++++++ hotshot-task-impls/src/quorum_proposal/mod.rs | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/hotshot-task-impls/src/quorum_proposal/handlers.rs b/hotshot-task-impls/src/quorum_proposal/handlers.rs index 196165938f..8f8ff4f0f4 100644 --- a/hotshot-task-impls/src/quorum_proposal/handlers.rs +++ b/hotshot-task-impls/src/quorum_proposal/handlers.rs @@ -440,6 +440,14 @@ impl HandleDepOutput for ProposalDependencyHandle< parent_qc = Some(qc.clone()); }, }, + HotShotEvent::ExtendedQcFormed(cert) => match cert { + either::Right(timeout) => { + timeout_certificate = Some(timeout.clone()); + }, + either::Left(eqc) => { + parent_qc = Some(eqc.qc.clone()); + }, + }, HotShotEvent::ViewSyncFinalizeCertificateRecv(cert) => { view_sync_finalize_cert = Some(cert.clone()); }, diff --git a/hotshot-task-impls/src/quorum_proposal/mod.rs b/hotshot-task-impls/src/quorum_proposal/mod.rs index 9637db4f46..968d4d1eb4 100644 --- a/hotshot-task-impls/src/quorum_proposal/mod.rs +++ b/hotshot-task-impls/src/quorum_proposal/mod.rs @@ -111,6 +111,8 @@ impl, V: Versions> ProposalDependency::Qc => { if let HotShotEvent::Qc2Formed(either::Left(qc)) = event { qc.view_number() + 1 + } else if let HotShotEvent::ExtendedQcFormed(either::Left(eqc)) = event { + eqc.qc.view_number() + 1 } else { return false; } @@ -118,6 +120,9 @@ impl, V: Versions> ProposalDependency::TimeoutCert => { if let HotShotEvent::Qc2Formed(either::Right(timeout)) = event { timeout.view_number() + 1 + } else if let HotShotEvent::ExtendedQcFormed(either::Right(timeout)) = event + { + timeout.view_number() + 1 } else { return false; } @@ -230,6 +235,10 @@ impl, V: Versions> qc_dependency.mark_as_completed(event); }, }, + HotShotEvent::ExtendedQcFormed(eqc) => match eqc { + Either::Right(_) => timeout_dependency.mark_as_completed(event), + Either::Left(_) => qc_dependency.mark_as_completed(event), + }, HotShotEvent::ViewSyncFinalizeCertificateRecv(_) => { view_sync_dependency.mark_as_completed(event); }, From 7921fb889098ddd8970f69d91e523a2e6b829a38 Mon Sep 17 00:00:00 2001 From: MRain Date: Wed, 12 Mar 2025 22:47:45 -0400 Subject: [PATCH 12/12] fix proposal dependency --- .../src/quorum_proposal/handlers.rs | 44 +++++++++++-------- sequencer-sqlite/Cargo.lock | 1 + 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/hotshot-task-impls/src/quorum_proposal/handlers.rs b/hotshot-task-impls/src/quorum_proposal/handlers.rs index 8f8ff4f0f4..180e0cf60d 100644 --- a/hotshot-task-impls/src/quorum_proposal/handlers.rs +++ b/hotshot-task-impls/src/quorum_proposal/handlers.rs @@ -22,7 +22,7 @@ use hotshot_types::{ data::{Leaf2, QuorumProposal2, QuorumProposalWrapper, VidDisperse, ViewChangeEvidence2}, epoch_membership::EpochMembership, message::Proposal, - simple_certificate::{QuorumCertificate2, UpgradeCertificate}, + simple_certificate::{ExtendedQuorumCertificate, QuorumCertificate2, UpgradeCertificate}, traits::{ block_contents::BlockHeader, node_implementation::{NodeImplementation, NodeType}, @@ -127,23 +127,31 @@ impl ProposalDependencyHandle { rx: &mut Receiver>>, ) -> Option> { while let Ok(event) = rx.recv_direct().await { - if let HotShotEvent::HighQcRecv(qc, _sender) = event.as_ref() { - let prev_epoch = qc.data.epoch; - let epoch_membership = self.membership.get_new_epoch(prev_epoch).await.ok()?; - let membership_stake_table = epoch_membership.stake_table().await; - let membership_success_threshold = epoch_membership.success_threshold().await; - - if qc - .is_valid_cert( - StakeTableEntries::::from(membership_stake_table).0, - membership_success_threshold, - &self.upgrade_lock, - ) - .await - .is_ok() - { - return Some(qc.clone()); - } + match event.as_ref() { + HotShotEvent::HighQcRecv(qc, _) + | HotShotEvent::ExtendedQcRecv( + ExtendedQuorumCertificate { qc, state_cert: _ }, + _, + _, + ) => { + let prev_epoch = qc.data.epoch; + let epoch_membership = self.membership.get_new_epoch(prev_epoch).await.ok()?; + let membership_stake_table = epoch_membership.stake_table().await; + let membership_success_threshold = epoch_membership.success_threshold().await; + + if qc + .is_valid_cert( + StakeTableEntries::::from(membership_stake_table).0, + membership_success_threshold, + &self.upgrade_lock, + ) + .await + .is_ok() + { + return Some(qc.clone()); + } + }, + _ => {}, } } None diff --git a/sequencer-sqlite/Cargo.lock b/sequencer-sqlite/Cargo.lock index 76d10f64f0..655c2aa357 100644 --- a/sequencer-sqlite/Cargo.lock +++ b/sequencer-sqlite/Cargo.lock @@ -5099,6 +5099,7 @@ dependencies = [ "hotshot-utils", "jf-vid", "lru 0.12.5", + "primitive-types", "rand 0.8.5", "serde", "sha2 0.10.8",