diff --git a/crates/examples/infra/mod.rs b/crates/examples/infra/mod.rs index 77c3ba3fb6..20b0ed8a56 100755 --- a/crates/examples/infra/mod.rs +++ b/crates/examples/infra/mod.rs @@ -368,7 +368,7 @@ pub trait RunDa< /// Note: sequencing leaf does not have state, so does not return state async fn initialize_state_and_hotshot(&self) -> SystemContextHandle { let initializer = - hotshot::HotShotInitializer::::from_genesis(TestInstanceState::default()) + hotshot::HotShotInitializer::::from_genesis::(TestInstanceState::default()) .await .expect("Couldn't generate genesis block"); diff --git a/crates/hotshot/src/lib.rs b/crates/hotshot/src/lib.rs index e67e0ebd8f..3a1a2cd086 100644 --- a/crates/hotshot/src/lib.rs +++ b/crates/hotshot/src/lib.rs @@ -37,7 +37,6 @@ use async_broadcast::{broadcast, InactiveReceiver, Receiver, Sender}; use async_compatibility_layer::art::async_spawn; use async_lock::RwLock; use async_trait::async_trait; -use committable::Committable; use futures::join; use hotshot_task::task::{ConsensusTaskRegistry, NetworkTaskRegistry}; use hotshot_task_impls::{events::HotShotEvent, helpers::broadcast_event}; @@ -194,7 +193,7 @@ impl, V: Versions> SystemContext::PrivateKey, nonce: u64, @@ -223,6 +222,7 @@ impl, V: Versions> SystemContext`] with the given configuration options. @@ -233,7 +233,7 @@ impl, V: Versions> SystemContext::PrivateKey, nonce: u64, @@ -279,7 +279,7 @@ impl, V: Versions> SystemContext, V: Versions> SystemContext, V: Versions> SystemContext(&validated_state, self.instance_state.as_ref()) .await, ); @@ -596,7 +599,8 @@ impl, V: Versions> SystemContext HotShotInitializer { /// initialize from genesis /// # Errors /// If we are unable to apply the genesis block to the default state - pub async fn from_genesis( + pub async fn from_genesis( instance_state: TYPES::InstanceState, ) -> Result> { let (validated_state, state_delta) = TYPES::ValidatedState::genesis(&instance_state); - let high_qc = QuorumCertificate::genesis(&validated_state, &instance_state).await; + let high_qc = QuorumCertificate::genesis::(&validated_state, &instance_state).await; Ok(Self { inner: Leaf::genesis(&validated_state, &instance_state).await, diff --git a/crates/hotshot/src/tasks/mod.rs b/crates/hotshot/src/tasks/mod.rs index 5f7964c24b..9bab74694a 100644 --- a/crates/hotshot/src/tasks/mod.rs +++ b/crates/hotshot/src/tasks/mod.rs @@ -318,7 +318,8 @@ where metrics, storage, marketplace_config, - ); + ) + .await; let consensus_registry = ConsensusTaskRegistry::new(); let network_registry = NetworkTaskRegistry::new(); diff --git a/crates/task-impls/src/consensus/handlers.rs b/crates/task-impls/src/consensus/handlers.rs index 0668b87360..36f6831e5a 100644 --- a/crates/task-impls/src/consensus/handlers.rs +++ b/crates/task-impls/src/consensus/handlers.rs @@ -14,7 +14,6 @@ use async_lock::RwLock; #[cfg(async_executor_impl = "async-std")] use async_std::task::JoinHandle; use chrono::Utc; -use committable::Committable; use futures::FutureExt; use hotshot_types::{ consensus::{CommitmentAndMetadata, OuterConsensus, View}, @@ -37,7 +36,7 @@ use hotshot_types::{ #[cfg(async_executor_impl = "tokio")] use tokio::task::JoinHandle; use tracing::{debug, error, info, instrument, warn}; -use vbs::version::{StaticVersionType, Version}; +use vbs::version::StaticVersionType; use super::ConsensusTaskState; use crate::{ @@ -67,7 +66,7 @@ pub async fn create_and_send_proposal( proposal_cert: Option>, round_start_delay: u64, instance_state: Arc, - version: Version, + upgrade_lock: UpgradeLock, id: u64, ) -> Result<()> { let consensus_read = consensus.read().await; @@ -81,6 +80,11 @@ pub async fn create_and_send_proposal( .context("Failed to get vid share")?; drop(consensus_read); + let version = upgrade_lock + .version(view) + .await + .context("Failed to get version number")?; + let block_header = if version < V::Marketplace::VERSION { TYPES::BlockHeader::new_legacy( state.as_ref(), @@ -122,9 +126,12 @@ pub async fn create_and_send_proposal( let proposed_leaf = Leaf::from_quorum_proposal(&proposal); - ensure!(proposed_leaf.parent_commitment() == parent_leaf.commit()); + ensure!(proposed_leaf.parent_commitment() == parent_leaf.commit(&upgrade_lock).await); - let signature = TYPES::SignatureKey::sign(&private_key, proposed_leaf.commit().as_ref())?; + let signature = TYPES::SignatureKey::sign( + &private_key, + proposed_leaf.commit(&upgrade_lock).await.as_ref(), + )?; let message = Proposal { data: proposal, @@ -221,8 +228,6 @@ pub async fn publish_proposal_from_commitment_and_metadata( public_key, @@ -237,7 +242,7 @@ pub async fn publish_proposal_from_commitment_and_metadata::create_signed_vote( QuorumData { - leaf_commit: proposed_leaf.commit(), + leaf_commit: proposed_leaf.commit(upgrade_lock).await, }, view, &public_key, @@ -861,7 +868,7 @@ pub async fn update_state_and_vote_if_able< cur_view, View { view_inner: ViewInner::Leaf { - leaf: proposed_leaf.commit(), + leaf: proposed_leaf.commit(upgrade_lock).await, state: Arc::clone(&state), delta: Some(Arc::clone(&delta)), }, @@ -869,7 +876,9 @@ pub async fn update_state_and_vote_if_able< ) { tracing::trace!("{e:?}"); } - consensus.update_saved_leaves(proposed_leaf.clone()); + consensus + .update_saved_leaves(proposed_leaf.clone(), upgrade_lock) + .await; let new_leaves = consensus.saved_leaves().clone(); let new_state = consensus.validated_state_map().clone(); drop(consensus); diff --git a/crates/task-impls/src/helpers.rs b/crates/task-impls/src/helpers.rs index 441948887d..67f78ac670 100644 --- a/crates/task-impls/src/helpers.rs +++ b/crates/task-impls/src/helpers.rs @@ -108,7 +108,7 @@ pub(crate) async fn fetch_proposal( hs_event.as_ref() { // Make sure that the quorum_proposal is valid - if quorum_proposal.validate_signature(&mem).is_ok() { + if quorum_proposal.validate_signature(&mem, upgrade_lock).await.is_ok() { proposal = Some(quorum_proposal.clone()); } @@ -140,7 +140,7 @@ pub(crate) async fn fetch_proposal( let view = View { view_inner: ViewInner::Leaf { - leaf: leaf.commit(), + leaf: leaf.commit(upgrade_lock).await, state, delta: None, }, @@ -149,7 +149,9 @@ pub(crate) async fn fetch_proposal( tracing::trace!("{e:?}"); } - consensus_write.update_saved_leaves(leaf.clone()); + consensus_write + .update_saved_leaves(leaf.clone(), upgrade_lock) + .await; broadcast_event( HotShotEvent::ValidatedStateUpdated(view_number, view).into(), &event_sender, @@ -410,7 +412,7 @@ pub(crate) async fn parent_leaf_and_state( let reached_decided = leaf.view_number() == consensus_reader.last_decided_view(); let parent_leaf = leaf.clone(); - let original_parent_hash = parent_leaf.commit(); + let original_parent_hash = parent_leaf.commit(upgrade_lock).await; let mut next_parent_hash = original_parent_hash; // Walk back until we find a decide @@ -460,7 +462,7 @@ pub async fn validate_proposal_safety_and_liveness< let proposed_leaf = Leaf::from_quorum_proposal(&proposal.data); ensure!( - proposed_leaf.parent_commitment() == parent_leaf.commit(), + proposed_leaf.parent_commitment() == parent_leaf.commit(&upgrade_lock).await, "Proposed leaf does not extend the parent leaf." ); @@ -469,7 +471,7 @@ pub async fn validate_proposal_safety_and_liveness< ); let view = View { view_inner: ViewInner::Leaf { - leaf: proposed_leaf.commit(), + leaf: proposed_leaf.commit(&upgrade_lock).await, state, delta: None, // May be updated to `Some` in the vote task. }, @@ -480,7 +482,9 @@ pub async fn validate_proposal_safety_and_liveness< if let Err(e) = consensus_write.update_validated_state_map(view_number, view.clone()) { tracing::trace!("{e:?}"); } - consensus_write.update_saved_leaves(proposed_leaf.clone()); + consensus_write + .update_saved_leaves(proposed_leaf.clone(), &upgrade_lock) + .await; // Update our internal storage of the proposal. The proposal is valid, so // we swallow this error and just log if it occurs. @@ -602,7 +606,9 @@ pub async fn validate_proposal_view_and_certs( ); // Validate the proposal's signature. This should also catch if the leaf_commitment does not equal our calculated parent commitment - proposal.validate_signature(quorum_membership)?; + proposal + .validate_signature(quorum_membership, upgrade_lock) + .await?; // Verify a timeout certificate OR a view sync certificate exists and is valid. if proposal.data.justify_qc.view_number() != view - 1 { diff --git a/crates/task-impls/src/quorum_proposal/handlers.rs b/crates/task-impls/src/quorum_proposal/handlers.rs index 5a73bec2d1..867b6298a9 100644 --- a/crates/task-impls/src/quorum_proposal/handlers.rs +++ b/crates/task-impls/src/quorum_proposal/handlers.rs @@ -13,7 +13,6 @@ use anyhow::{ensure, Context, Result}; use async_broadcast::{Receiver, Sender}; use async_compatibility_layer::art::{async_sleep, async_spawn}; use async_lock::RwLock; -use committable::Committable; use hotshot_task::{ dependency::{Dependency, EventDependency}, dependency_task::HandleDepOutput, @@ -211,13 +210,15 @@ impl ProposalDependencyHandle { let proposed_leaf = Leaf::from_quorum_proposal(&proposal); ensure!( - proposed_leaf.parent_commitment() == parent_leaf.commit(), + proposed_leaf.parent_commitment() == parent_leaf.commit(&self.upgrade_lock).await, "Proposed leaf parent does not equal high qc" ); - let signature = - TYPES::SignatureKey::sign(&self.private_key, proposed_leaf.commit().as_ref()) - .context("Failed to compute proposed_leaf.commit()")?; + let signature = TYPES::SignatureKey::sign( + &self.private_key, + proposed_leaf.commit(&self.upgrade_lock).await.as_ref(), + ) + .context("Failed to compute proposed_leaf.commit()")?; let message = Proposal { data: proposal, diff --git a/crates/task-impls/src/quorum_proposal_recv/handlers.rs b/crates/task-impls/src/quorum_proposal_recv/handlers.rs index 1658910986..7145c3bde8 100644 --- a/crates/task-impls/src/quorum_proposal_recv/handlers.rs +++ b/crates/task-impls/src/quorum_proposal_recv/handlers.rs @@ -55,7 +55,7 @@ async fn validate_proposal_liveness + 'static, V: Versions> let view = View { view_inner: ViewInner::Leaf { - leaf: proposed_leaf.commit(), + leaf: proposed_leaf.commit(&self.upgrade_lock).await, state: Arc::clone(&state), delta: Some(Arc::clone(&delta)), }, @@ -167,7 +166,9 @@ impl + 'static, V: Versions> { tracing::trace!("{e:?}"); } - consensus_writer.update_saved_leaves(proposed_leaf.clone()); + consensus_writer + .update_saved_leaves(proposed_leaf.clone(), &self.upgrade_lock) + .await; // Kick back our updated structures for downstream usage. let new_leaves = consensus_writer.saved_leaves().clone(); @@ -209,7 +210,7 @@ impl + 'static, V: Versions> // Create and send the vote. let vote = QuorumVote::::create_signed_vote( QuorumData { - leaf_commit: leaf.commit(), + leaf_commit: leaf.commit(&self.upgrade_lock).await, }, self.view_number, &self.public_key, @@ -285,7 +286,7 @@ impl + 'static, V: Versions> Handl } else { payload_commitment = Some(proposal_payload_comm); } - let parent_commitment = parent_leaf.commit(); + let parent_commitment = parent_leaf.commit(&self.upgrade_lock).await; let proposed_leaf = Leaf::from_quorum_proposal(proposal); if proposed_leaf.parent_commitment() != parent_commitment { warn!("Proposed leaf parent commitment does not match parent leaf payload commitment. Aborting vote."); diff --git a/crates/testing/src/consistency_task.rs b/crates/testing/src/consistency_task.rs index 2d05c7c4df..9f9c2ad1e8 100644 --- a/crates/testing/src/consistency_task.rs +++ b/crates/testing/src/consistency_task.rs @@ -5,15 +5,15 @@ // along with the HotShot repository. If not, see . #![allow(clippy::unwrap_or_default)] -use std::collections::BTreeMap; +use std::{collections::BTreeMap, marker::PhantomData}; use anyhow::{bail, ensure, Context, Result}; use async_trait::async_trait; -use committable::Committable; use hotshot_types::{ data::Leaf, event::{Event, EventType}, - traits::node_implementation::NodeType, + message::UpgradeLock, + traits::node_implementation::{ConsensusTime, NodeType, Versions}, }; use crate::{ @@ -55,7 +55,36 @@ fn sanitize_node_map( } /// For a NodeMapSanitized, we validate that each leaf extends the preceding leaf. -fn validate_node_map(node_map: &NodeMapSanitized) -> Result<()> { +async fn validate_node_map( + node_map: &NodeMapSanitized, +) -> Result<()> { + // We first scan 3-chains to find an upgrade certificate that has reached a decide. + let leaf_triples = node_map + .values() + .zip(node_map.values().skip(1)) + .zip(node_map.values().skip(2)) + .map(|((a, b), c)| (a, b, c)); + + let mut decided_upgrade_certificate = None; + let mut view_decided = TYPES::Time::new(0); + + for (grandparent, _parent, child) in leaf_triples { + if let Some(cert) = grandparent.upgrade_certificate() { + if cert.data.decide_by <= child.view_number() { + decided_upgrade_certificate = Some(cert); + view_decided = child.view_number(); + + break; + } + } + } + + // To mimic consensus to use e.g. the `extends_upgrade` method, + // we cannot immediately put the upgrade certificate in the lock. + // + // Instead, we initialize an empty lock and add the certificate in the appropriate view. + let upgrade_lock = UpgradeLock::::new(); + let leaf_pairs = node_map.values().zip(node_map.values().skip(1)); // Check that the child leaf follows the parent, possibly with a gap. @@ -63,13 +92,28 @@ fn validate_node_map(node_map: &NodeMapSanitized) -> Res ensure!( child.justify_qc().view_number >= parent.view_number(), "The node has provided leaf:\n\n{child:?}\n\nbut its quorum certificate points to a view before the most recent leaf:\n\n{parent:?}" - ); + ); + + child + .extends_upgrade(parent, &upgrade_lock.decided_upgrade_certificate) + .await + .context("Leaf {child} does not extend its parent {parent}")?; + // We want to make sure the commitment matches, + // but allow for the possibility that we may have skipped views in between. if child.justify_qc().view_number == parent.view_number() - && child.justify_qc().data.leaf_commit != parent.commit() + && child.justify_qc().data.leaf_commit != parent.commit(&upgrade_lock).await { bail!("The node has provided leaf:\n\n{child:?}\n\nwhich points to:\n\n{parent:?}\n\nbut the commits do not match."); } + + if child.view_number() == view_decided { + upgrade_lock + .decided_upgrade_certificate + .write() + .await + .clone_from(&decided_upgrade_certificate); + } } Ok(()) @@ -105,12 +149,13 @@ pub type ViewMap = BTreeMap<::Time, BTreeMap( +async fn invert_network_map( network_map: &NetworkMapSanitized, ) -> Result> { let mut inverted_map = BTreeMap::new(); for (node_id, node_map) in network_map.iter() { - validate_node_map(node_map) + validate_node_map::(node_map) + .await .context(format!("Node {node_id} has an invalid leaf history"))?; // validate each node's leaf map @@ -153,20 +198,22 @@ fn sanitize_view_map( } /// Data availability task state -pub struct ConsistencyTask { +pub struct ConsistencyTask { /// A map from node ids to (leaves keyed on view number) pub consensus_leaves: NetworkMap, /// safety task requirements pub safety_properties: OverallSafetyPropertiesDescription, /// whether we should have seen an upgrade certificate or not pub ensure_upgrade: bool, + /// phantom marker + pub _pd: PhantomData, } -impl ConsistencyTask { - pub fn validate(&self) -> Result<()> { +impl ConsistencyTask { + pub async fn validate(&self) -> Result<()> { let sanitized_network_map = sanitize_network_map(&self.consensus_leaves)?; - let inverted_map = invert_network_map(&sanitized_network_map)?; + let inverted_map = invert_network_map::(&sanitized_network_map).await?; let sanitized_view_map = sanitize_view_map(&inverted_map)?; @@ -184,7 +231,7 @@ impl ConsistencyTask { } #[async_trait] -impl TestTaskState for ConsistencyTask { +impl TestTaskState for ConsistencyTask { type Event = Event; /// Handles an event from one of multiple receivers. @@ -206,8 +253,8 @@ impl TestTaskState for ConsistencyTask { Ok(()) } - fn check(&self) -> TestResult { - if let Err(e) = self.validate() { + async fn check(&self) -> TestResult { + if let Err(e) = self.validate().await { return TestResult::Fail(Box::new(e)); } diff --git a/crates/testing/src/helpers.rs b/crates/testing/src/helpers.rs index 8ffe9fafe7..54d6039f33 100644 --- a/crates/testing/src/helpers.rs +++ b/crates/testing/src/helpers.rs @@ -73,7 +73,7 @@ pub async fn build_system_handle< let marketplace_config = (launcher.resource_generator.marketplace_config)(node_id); let config = launcher.resource_generator.config.clone(); - let initializer = HotShotInitializer::::from_genesis(TestInstanceState::new( + let initializer = HotShotInitializer::::from_genesis::(TestInstanceState::new( launcher.metadata.async_delay_config, )) .await @@ -380,7 +380,7 @@ pub async fn build_vote, V: Versio let leaf: Leaf<_> = Leaf::from_quorum_proposal(&proposal); let vote = QuorumVote::::create_signed_vote( QuorumData { - leaf_commit: leaf.commit(), + leaf_commit: leaf.commit(&handle.hotshot.upgrade_lock).await, }, view, &handle.public_key(), @@ -410,18 +410,22 @@ where } /// This function will create a fake [`View`] from a provided [`Leaf`]. -pub fn build_fake_view_with_leaf(leaf: Leaf) -> View { - build_fake_view_with_leaf_and_state(leaf, TestValidatedState::default()) +pub async fn build_fake_view_with_leaf( + leaf: Leaf, + upgrade_lock: &UpgradeLock, +) -> View { + build_fake_view_with_leaf_and_state(leaf, TestValidatedState::default(), upgrade_lock).await } /// This function will create a fake [`View`] from a provided [`Leaf`] and `state`. -pub fn build_fake_view_with_leaf_and_state( +pub async fn build_fake_view_with_leaf_and_state( leaf: Leaf, state: TestValidatedState, + upgrade_lock: &UpgradeLock, ) -> View { View { view_inner: ViewInner::Leaf { - leaf: leaf.commit(), + leaf: leaf.commit(upgrade_lock).await, state: state.into(), delta: None, }, diff --git a/crates/testing/src/overall_safety_task.rs b/crates/testing/src/overall_safety_task.rs index 97ea7fcdf1..a4680d5000 100644 --- a/crates/testing/src/overall_safety_task.rs +++ b/crates/testing/src/overall_safety_task.rs @@ -234,7 +234,7 @@ impl, V: Versions> TestTas Ok(()) } - fn check(&self) -> TestResult { + async fn check(&self) -> TestResult { if let Some(e) = &self.error { return TestResult::Fail(e.clone()); } diff --git a/crates/testing/src/spinning_task.rs b/crates/testing/src/spinning_task.rs index 8d5d8420ae..ec9bda7660 100644 --- a/crates/testing/src/spinning_task.rs +++ b/crates/testing/src/spinning_task.rs @@ -216,7 +216,7 @@ where read_storage.last_actioned_view().await, read_storage.proposals_cloned().await, read_storage.high_qc_cloned().await.unwrap_or( - QuorumCertificate::genesis( + QuorumCertificate::genesis::( &TestValidatedState::default(), &TestInstanceState::default(), ) @@ -302,7 +302,7 @@ where Ok(()) } - fn check(&self) -> TestResult { + async fn check(&self) -> TestResult { TestResult::Pass } } diff --git a/crates/testing/src/test_builder.rs b/crates/testing/src/test_builder.rs index 2cb521b08b..098d12512d 100644 --- a/crates/testing/src/test_builder.rs +++ b/crates/testing/src/test_builder.rs @@ -119,7 +119,7 @@ pub async fn create_test_handle< storage: I::Storage, marketplace_config: MarketplaceConfig, ) -> SystemContextHandle { - let initializer = HotShotInitializer::::from_genesis(TestInstanceState::new( + let initializer = HotShotInitializer::::from_genesis::(TestInstanceState::new( metadata.async_delay_config, )) .await @@ -185,7 +185,8 @@ pub async fn create_test_handle< ConsensusMetricsValue::default(), storage, marketplace_config, - ); + ) + .await; hotshot.run_tasks().await } diff --git a/crates/testing/src/test_runner.rs b/crates/testing/src/test_runner.rs index 6167d23698..e616e270c8 100644 --- a/crates/testing/src/test_runner.rs +++ b/crates/testing/src/test_runner.rs @@ -184,7 +184,7 @@ where &TestInstanceState::default(), ) .await, - high_qc: QuorumCertificate::genesis( + high_qc: QuorumCertificate::genesis::( &TestValidatedState::default(), &TestInstanceState::default(), ) @@ -209,9 +209,10 @@ where consensus_leaves: BTreeMap::new(), safety_properties: self.launcher.metadata.overall_safety_properties, ensure_upgrade: self.launcher.metadata.upgrade_view.is_some(), + _pd: PhantomData, }; - let consistency_task = TestTask::>::new( + let consistency_task = TestTask::>::new( consistency_task_state, event_rxs.clone(), test_receiver.clone(), @@ -481,7 +482,7 @@ where }, ); } else { - let initializer = HotShotInitializer::::from_genesis( + let initializer = HotShotInitializer::::from_genesis::( TestInstanceState::new(self.launcher.metadata.async_delay_config.clone()), ) .await @@ -615,6 +616,7 @@ where storage, marketplace_config, ) + .await } /// add a specific node with a config @@ -654,6 +656,7 @@ where internal_channel, external_channel, ) + .await } } diff --git a/crates/testing/src/test_task.rs b/crates/testing/src/test_task.rs index 0dcc56c068..3559c4cee9 100644 --- a/crates/testing/src/test_task.rs +++ b/crates/testing/src/test_task.rs @@ -44,7 +44,7 @@ pub trait TestTaskState: Send { async fn handle_event(&mut self, (event, id): (Self::Event, usize)) -> Result<()>; /// Check the result of the test. - fn check(&self) -> TestResult; + async fn check(&self) -> TestResult; } /// A basic task which loops waiting for events to come from `event_receiver` @@ -88,7 +88,7 @@ impl TestTask { spawn(async move { loop { if let Ok(TestEvent::Shutdown) = self.test_receiver.try_recv() { - break self.state.check(); + break self.state.check().await; } let mut messages = Vec::new(); diff --git a/crates/testing/src/view_generator.rs b/crates/testing/src/view_generator.rs index 7c6c8af2ee..21d5eb7c66 100644 --- a/crates/testing/src/view_generator.rs +++ b/crates/testing/src/view_generator.rs @@ -12,7 +12,6 @@ use std::{ task::{Context, Poll}, }; -use committable::Committable; use futures::{FutureExt, Stream}; use hotshot::types::{BLSPubKey, SignatureKey, SystemContextHandle}; use hotshot_example_types::{ @@ -128,7 +127,7 @@ impl TestView { let quorum_proposal_inner = QuorumProposal:: { block_header: block_header.clone(), view_number: genesis_view, - justify_qc: QuorumCertificate::genesis( + justify_qc: QuorumCertificate::genesis::( &TestValidatedState::default(), &TestInstanceState::default(), ) @@ -160,8 +159,11 @@ impl TestView { transactions: transactions.clone(), }); - let signature = ::sign(&private_key, leaf.commit().as_ref()) - .expect("Failed to sign leaf commitment!"); + let signature = ::sign( + &private_key, + leaf.commit(&upgrade_lock).await.as_ref(), + ) + .expect("Failed to sign leaf commitment!"); let quorum_proposal = Proposal { data: quorum_proposal_inner, @@ -208,7 +210,7 @@ impl TestView { let transactions = &self.transactions; let quorum_data = QuorumData { - leaf_commit: old.leaf.commit(), + leaf_commit: old.leaf.commit(&self.upgrade_lock).await, }; let (old_private_key, old_public_key) = key_pair_for_id::(*old_view); @@ -359,8 +361,11 @@ impl TestView { transactions: transactions.clone(), }); - let signature = ::sign(&private_key, leaf.commit().as_ref()) - .expect("Failed to sign leaf commitment."); + let signature = ::sign( + &private_key, + leaf.commit(&self.upgrade_lock).await.as_ref(), + ) + .expect("Failed to sign leaf commitment."); let quorum_proposal = Proposal { data: proposal, @@ -422,7 +427,7 @@ impl TestView { ) -> QuorumVote { QuorumVote::::create_signed_vote( QuorumData { - leaf_commit: self.leaf.commit(), + leaf_commit: self.leaf.commit(&handle.hotshot.upgrade_lock).await, }, self.view_number, &handle.public_key(), diff --git a/crates/testing/src/view_sync_task.rs b/crates/testing/src/view_sync_task.rs index f788c29af5..b107fc68f9 100644 --- a/crates/testing/src/view_sync_task.rs +++ b/crates/testing/src/view_sync_task.rs @@ -63,7 +63,7 @@ impl> TestTaskState Ok(()) } - fn check(&self) -> TestResult { + async fn check(&self) -> TestResult { match self.description.clone() { ViewSyncTaskDescription::Threshold(min, max) => { let num_hits = self.hit_view_sync.len(); diff --git a/crates/testing/tests/tests_1/quorum_proposal_recv_task.rs b/crates/testing/tests/tests_1/quorum_proposal_recv_task.rs index f7f905b54a..940e30f62e 100644 --- a/crates/testing/tests/tests_1/quorum_proposal_recv_task.rs +++ b/crates/testing/tests/tests_1/quorum_proposal_recv_task.rs @@ -79,11 +79,15 @@ async fn test_quorum_proposal_recv_task() { // These are both updated when we vote. Since we don't have access // to that, we'll just put them in here. consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number, - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); } @@ -104,7 +108,9 @@ async fn test_quorum_proposal_recv_task() { >::from_header( &proposals[1].data.block_header, ), - ), + &handle.hotshot.upgrade_lock, + ) + .await, )), exact(QuorumProposalValidated( proposals[1].data.clone(), @@ -177,7 +183,7 @@ async fn test_quorum_proposal_recv_task_liveness_check() { consensus_writer .update_validated_state_map( inserted_view_number, - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); @@ -226,7 +232,9 @@ async fn test_quorum_proposal_recv_task_liveness_check() { >::from_header( &proposals[2].data.block_header, ), - ), + &handle.hotshot.upgrade_lock + ) + .await, )), exact(QuorumProposalRequestSend(req, signature)), exact(HighQcUpdated(proposals[2].data.justify_qc.clone())), diff --git a/crates/testing/tests/tests_1/quorum_proposal_task.rs b/crates/testing/tests/tests_1/quorum_proposal_task.rs index b984c60889..324d3241b2 100644 --- a/crates/testing/tests/tests_1/quorum_proposal_task.rs +++ b/crates/testing/tests/tests_1/quorum_proposal_task.rs @@ -80,7 +80,11 @@ async fn test_quorum_proposal_task_quorum_proposal_view_1() { // We don't have a `QuorumProposalRecv` task handler, so we'll just manually insert the proposals // to make sure they show up during tests. consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; } // We must send the genesis cert here to initialize hotshot successfully. @@ -110,7 +114,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_1() { ), ValidatedStateUpdated( proposals[0].data.view_number(), - build_fake_view_with_leaf(leaves[0].clone()), + build_fake_view_with_leaf(leaves[0].clone(), &handle.hotshot.upgrade_lock).await, ), ], ]; @@ -170,12 +174,16 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { // We don't have a `QuorumProposalRecv` task handler, so we'll just manually insert the proposals // to make sure they show up during tests. consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); } @@ -212,7 +220,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { VidDisperseSend(vid_dispersals[0].clone(), handle.public_key()), ValidatedStateUpdated( genesis_cert.view_number(), - build_fake_view_with_leaf(genesis_leaf.clone()), + build_fake_view_with_leaf(genesis_leaf.clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -229,7 +237,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { VidDisperseSend(vid_dispersals[1].clone(), handle.public_key()), ValidatedStateUpdated( proposals[0].data.view_number(), - build_fake_view_with_leaf(leaves[0].clone()), + build_fake_view_with_leaf(leaves[0].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -246,7 +254,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { VidDisperseSend(vid_dispersals[2].clone(), handle.public_key()), ValidatedStateUpdated( proposals[1].data.view_number(), - build_fake_view_with_leaf(leaves[1].clone()), + build_fake_view_with_leaf(leaves[1].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -263,7 +271,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { VidDisperseSend(vid_dispersals[3].clone(), handle.public_key()), ValidatedStateUpdated( proposals[2].data.view_number(), - build_fake_view_with_leaf(leaves[2].clone()), + build_fake_view_with_leaf(leaves[2].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -280,7 +288,7 @@ async fn test_quorum_proposal_task_quorum_proposal_view_gt_1() { VidDisperseSend(vid_dispersals[4].clone(), handle.public_key()), ValidatedStateUpdated( proposals[3].data.view_number(), - build_fake_view_with_leaf(leaves[3].clone()), + build_fake_view_with_leaf(leaves[3].clone(), &handle.hotshot.upgrade_lock).await, ), ], ]; @@ -390,7 +398,7 @@ async fn test_quorum_proposal_task_qc_timeout() { VidDisperseSend(vid_dispersals[2].clone(), handle.public_key()), ValidatedStateUpdated( proposals[1].data.view_number(), - build_fake_view_with_leaf(leaves[1].clone()), + build_fake_view_with_leaf(leaves[1].clone(), &handle.hotshot.upgrade_lock).await, ), ]]; @@ -480,7 +488,7 @@ async fn test_quorum_proposal_task_view_sync() { VidDisperseSend(vid_dispersals[1].clone(), handle.public_key()), ValidatedStateUpdated( proposals[0].data.view_number(), - build_fake_view_with_leaf(leaves[0].clone()), + build_fake_view_with_leaf(leaves[0].clone(), &handle.hotshot.upgrade_lock).await, ), ]]; @@ -532,11 +540,15 @@ async fn test_quorum_proposal_task_liveness_check() { // We don't have a `QuorumProposalRecv` task handler, so we'll just manually insert the proposals // to make sure they show up during tests. consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); } @@ -572,7 +584,7 @@ async fn test_quorum_proposal_task_liveness_check() { VidDisperseSend(vid_dispersals[0].clone(), handle.public_key()), ValidatedStateUpdated( genesis_cert.view_number(), - build_fake_view_with_leaf(genesis_leaf.clone()), + build_fake_view_with_leaf(genesis_leaf.clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -589,7 +601,7 @@ async fn test_quorum_proposal_task_liveness_check() { VidDisperseSend(vid_dispersals[1].clone(), handle.public_key()), ValidatedStateUpdated( proposals[0].data.view_number(), - build_fake_view_with_leaf(leaves[0].clone()), + build_fake_view_with_leaf(leaves[0].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -606,7 +618,7 @@ async fn test_quorum_proposal_task_liveness_check() { VidDisperseSend(vid_dispersals[2].clone(), handle.public_key()), ValidatedStateUpdated( proposals[1].data.view_number(), - build_fake_view_with_leaf(leaves[1].clone()), + build_fake_view_with_leaf(leaves[1].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -623,7 +635,7 @@ async fn test_quorum_proposal_task_liveness_check() { VidDisperseSend(vid_dispersals[3].clone(), handle.public_key()), ValidatedStateUpdated( proposals[2].data.view_number(), - build_fake_view_with_leaf(leaves[2].clone()), + build_fake_view_with_leaf(leaves[2].clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -640,7 +652,7 @@ async fn test_quorum_proposal_task_liveness_check() { VidDisperseSend(vid_dispersals[4].clone(), handle.public_key()), ValidatedStateUpdated( proposals[3].data.view_number(), - build_fake_view_with_leaf(leaves[3].clone()), + build_fake_view_with_leaf(leaves[3].clone(), &handle.hotshot.upgrade_lock).await, ), ], ]; diff --git a/crates/testing/tests/tests_1/quorum_vote_task.rs b/crates/testing/tests/tests_1/quorum_vote_task.rs index 265910168d..e11ac16e5a 100644 --- a/crates/testing/tests/tests_1/quorum_vote_task.rs +++ b/crates/testing/tests/tests_1/quorum_vote_task.rs @@ -62,10 +62,12 @@ async fn test_quorum_vote_task_success() { consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); - consensus_writer.update_saved_leaves(view.leaf.clone()); + consensus_writer + .update_saved_leaves(view.leaf.clone(), &handle.hotshot.upgrade_lock) + .await; } drop(consensus_writer); @@ -135,10 +137,12 @@ async fn test_quorum_vote_task_miss_dependency() { consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); - consensus_writer.update_saved_leaves(view.leaf.clone()); + consensus_writer + .update_saved_leaves(view.leaf.clone(), &handle.hotshot.upgrade_lock) + .await; } drop(consensus_writer); diff --git a/crates/testing/tests/tests_1/test_with_failures_2.rs b/crates/testing/tests/tests_1/test_with_failures_2.rs index a4153b407b..c757b5d1b7 100644 --- a/crates/testing/tests/tests_1/test_with_failures_2.rs +++ b/crates/testing/tests/tests_1/test_with_failures_2.rs @@ -19,9 +19,9 @@ use hotshot_testing::{ test_builder::TestDescription, view_sync_task::ViewSyncTaskDescription, }; -use hotshot_types::message::{GeneralConsensusMessage, MessageKind, SequencingMessage}; use hotshot_types::{ data::ViewNumber, + message::{GeneralConsensusMessage, MessageKind, SequencingMessage}, traits::{ election::Membership, network::TransmitType, diff --git a/crates/testing/tests/tests_1/upgrade_task_with_proposal.rs b/crates/testing/tests/tests_1/upgrade_task_with_proposal.rs index fcb25aaf23..a465e94b37 100644 --- a/crates/testing/tests/tests_1/upgrade_task_with_proposal.rs +++ b/crates/testing/tests/tests_1/upgrade_task_with_proposal.rs @@ -102,11 +102,15 @@ async fn test_upgrade_task_with_proposal() { leaders.push(view.leader_public_key); views.push(view.clone()); consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); } @@ -122,11 +126,15 @@ async fn test_upgrade_task_with_proposal() { leaves.push(view.leaf.clone()); views.push(view.clone()); consensus_writer - .update_saved_leaves(Leaf::from_quorum_proposal(&view.quorum_proposal.data)); + .update_saved_leaves( + Leaf::from_quorum_proposal(&view.quorum_proposal.data), + &handle.hotshot.upgrade_lock, + ) + .await; consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); } @@ -175,7 +183,7 @@ async fn test_upgrade_task_with_proposal() { VidDisperseSend(vid_dispersals[0].clone(), handle.public_key()), ValidatedStateUpdated( genesis_cert.view_number(), - build_fake_view_with_leaf(genesis_leaf.clone()), + build_fake_view_with_leaf(genesis_leaf.clone(), &handle.hotshot.upgrade_lock).await, ), ], random![ @@ -192,7 +200,7 @@ async fn test_upgrade_task_with_proposal() { VidDisperseSend(vid_dispersals[1].clone(), handle.public_key()), ValidatedStateUpdated( proposals[0].data.view_number(), - build_fake_view_with_leaf(leaves[0].clone()), + build_fake_view_with_leaf(leaves[0].clone(), &handle.hotshot.upgrade_lock).await, ), ], InputOrder::Random(upgrade_vote_recvs), @@ -210,7 +218,7 @@ async fn test_upgrade_task_with_proposal() { VidDisperseSend(vid_dispersals[2].clone(), handle.public_key()), ValidatedStateUpdated( proposals[1].data.view_number(), - build_fake_view_with_leaf(leaves[1].clone()), + build_fake_view_with_leaf(leaves[1].clone(), &handle.hotshot.upgrade_lock).await, ), ], ]; diff --git a/crates/testing/tests/tests_1/upgrade_task_with_vote.rs b/crates/testing/tests/tests_1/upgrade_task_with_vote.rs index 37e3134bf1..71b2719c27 100644 --- a/crates/testing/tests/tests_1/upgrade_task_with_vote.rs +++ b/crates/testing/tests/tests_1/upgrade_task_with_vote.rs @@ -89,10 +89,12 @@ async fn test_upgrade_task_with_vote() { consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); - consensus_writer.update_saved_leaves(view.leaf.clone()); + consensus_writer + .update_saved_leaves(view.leaf.clone(), &handle.hotshot.upgrade_lock) + .await; } drop(consensus_writer); diff --git a/crates/testing/tests/tests_1/vote_dependency_handle.rs b/crates/testing/tests/tests_1/vote_dependency_handle.rs index 13d5e86297..62d1d8040b 100644 --- a/crates/testing/tests/tests_1/vote_dependency_handle.rs +++ b/crates/testing/tests/tests_1/vote_dependency_handle.rs @@ -58,10 +58,12 @@ async fn test_vote_dependency_handle() { consensus_writer .update_validated_state_map( view.quorum_proposal.data.view_number(), - build_fake_view_with_leaf(view.leaf.clone()), + build_fake_view_with_leaf(view.leaf.clone(), &handle.hotshot.upgrade_lock).await, ) .unwrap(); - consensus_writer.update_saved_leaves(view.leaf.clone()); + consensus_writer + .update_saved_leaves(view.leaf.clone(), &handle.hotshot.upgrade_lock) + .await; } drop(consensus_writer); diff --git a/crates/testing/tests/tests_2/byzantine_tests.rs b/crates/testing/tests/tests_2/byzantine_tests.rs index 6120e9bd92..902999c55c 100644 --- a/crates/testing/tests/tests_2/byzantine_tests.rs +++ b/crates/testing/tests/tests_2/byzantine_tests.rs @@ -1,5 +1,7 @@ use std::{ collections::{HashMap, HashSet}, + rc::Rc, + sync::Arc, time::Duration, }; @@ -27,8 +29,6 @@ use hotshot_types::{ }, vote::HasViewNumber, }; -use std::rc::Rc; -use std::sync::Arc; cross_tests!( TestName: double_propose_vote, Impls: [MemoryImpl], diff --git a/crates/types/src/consensus.rs b/crates/types/src/consensus.rs index 0c85127f8b..3fc23951e8 100644 --- a/crates/types/src/consensus.rs +++ b/crates/types/src/consensus.rs @@ -15,7 +15,7 @@ use std::{ use anyhow::{bail, ensure, Result}; use async_lock::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard}; -use committable::{Commitment, Committable}; +use committable::Commitment; use tracing::{debug, error, instrument, trace}; use vec1::Vec1; @@ -24,12 +24,12 @@ use crate::{ data::{Leaf, QuorumProposal, VidDisperse, VidDisperseShare}, error::HotShotError, event::HotShotAction, - message::Proposal, + message::{Proposal, UpgradeLock}, simple_certificate::{DaCertificate, QuorumCertificate}, traits::{ block_contents::BuilderFee, metrics::{Counter, Gauge, Histogram, Metrics, NoMetrics}, - node_implementation::{ConsensusTime, NodeType}, + node_implementation::{ConsensusTime, NodeType, Versions}, signature_key::SignatureKey, BlockPayload, ValidatedState, }, @@ -585,8 +585,13 @@ impl Consensus { } /// Update the saved leaves with a new leaf. - pub fn update_saved_leaves(&mut self, leaf: Leaf) { - self.saved_leaves.insert(leaf.commit(), leaf); + pub async fn update_saved_leaves( + &mut self, + leaf: Leaf, + upgrade_lock: &UpgradeLock, + ) { + self.saved_leaves + .insert(leaf.commit(upgrade_lock).await, leaf); } /// Update the saved payloads with a new encoded transaction. diff --git a/crates/types/src/data.rs b/crates/types/src/data.rs index 168ca0669b..03d30ca817 100644 --- a/crates/types/src/data.rs +++ b/crates/types/src/data.rs @@ -32,21 +32,22 @@ use snafu::Snafu; #[cfg(async_executor_impl = "tokio")] use tokio::task::spawn_blocking; use tracing::error; +use vbs::version::StaticVersionType; use vec1::Vec1; use crate::{ - message::Proposal, + message::{Proposal, UpgradeLock}, simple_certificate::{ QuorumCertificate, TimeoutCertificate, UpgradeCertificate, ViewSyncFinalizeCertificate2, }, - simple_vote::{QuorumData, UpgradeProposalData}, + simple_vote::{QuorumData, UpgradeProposalData, VersionedVoteData}, traits::{ block_contents::{ vid_commitment, BlockHeader, BuilderFee, EncodeBytes, TestableBlock, GENESIS_VID_NUM_STORAGE_NODES, }, election::Membership, - node_implementation::{ConsensusTime, NodeType}, + node_implementation::{ConsensusTime, NodeType, Versions}, signature_key::SignatureKey, states::TestableState, BlockPayload, @@ -444,6 +445,29 @@ pub struct Leaf { block_payload: Option, } +impl Leaf { + /// Calculate the leaf commitment, + /// which is gated on the version to include the block header. + pub async fn commit( + &self, + upgrade_lock: &UpgradeLock, + ) -> Commitment { + let version = upgrade_lock.version_infallible(self.view_number).await; + + if version < V::Marketplace::VERSION { + ::commit(self) + } else { + RawCommitmentBuilder::new("leaf commitment") + .u64_field("view number", *self.view_number) + .field("parent leaf commitment", self.parent_commitment) + .field("block header", self.block_header.commit()) + .field("justify qc", self.justify_qc.commit()) + .optional("upgrade certificate", &self.upgrade_certificate) + .finalize() + } + } +} + impl PartialEq for Leaf { fn eq(&self, other: &Self) -> bool { self.view_number == other.view_number @@ -477,20 +501,32 @@ impl Display for Leaf { impl QuorumCertificate { #[must_use] /// Creat the Genesis certificate - pub async fn genesis( + pub async fn genesis( validated_state: &TYPES::ValidatedState, instance_state: &TYPES::InstanceState, ) -> Self { + // since this is genesis, we should never have a decided upgrade certificate. + let upgrade_lock = UpgradeLock::::new(); + + let genesis_view = ::genesis(); + let data = QuorumData { leaf_commit: Leaf::genesis(validated_state, instance_state) .await - .commit(), + .commit(&upgrade_lock) + .await, }; - let commit = data.commit(); + + let versioned_data = + VersionedVoteData::<_, _, V>::new_infallible(data.clone(), genesis_view, &upgrade_lock) + .await; + + let bytes: [u8; 32] = versioned_data.commit().into(); + Self::new( data, - commit, - ::genesis(), + Commitment::from_raw(bytes), + genesis_view, None, PhantomData, ) diff --git a/crates/types/src/message.rs b/crates/types/src/message.rs index 05c25fab12..303e1d7073 100644 --- a/crates/types/src/message.rs +++ b/crates/types/src/message.rs @@ -14,7 +14,6 @@ use std::{fmt, fmt::Debug, marker::PhantomData, sync::Arc}; use anyhow::{bail, ensure, Context, Result}; use async_lock::RwLock; use cdn_proto::util::mnemonic; -use committable::Committable; use derivative::Derivative; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use vbs::{ @@ -358,13 +357,20 @@ where /// Checks that the signature of the quorum proposal is valid. /// # Errors /// Returns an error when the proposal signature is invalid. - pub fn validate_signature(&self, quorum_membership: &TYPES::Membership) -> Result<()> { + pub async fn validate_signature( + &self, + quorum_membership: &TYPES::Membership, + upgrade_lock: &UpgradeLock, + ) -> Result<()> { let view_number = self.data.view_number(); let view_leader_key = quorum_membership.leader(view_number); let proposed_leaf = Leaf::from_quorum_proposal(&self.data); ensure!( - view_leader_key.validate(&self.signature, proposed_leaf.commit().as_ref()), + view_leader_key.validate( + &self.signature, + proposed_leaf.commit(upgrade_lock).await.as_ref() + ), "Proposal signature is invalid." ); @@ -417,6 +423,24 @@ impl UpgradeLock { Ok(version) } + /// Calculate the version applied in a view, based on the provided upgrade lock. + /// + /// This function does not fail, since it does not check that the version is supported. + pub async fn version_infallible(&self, view: TYPES::Time) -> Version { + let upgrade_certificate = self.decided_upgrade_certificate.read().await; + + match *upgrade_certificate { + Some(ref cert) => { + if view >= cert.data.new_version_first_view { + cert.data.new_version + } else { + cert.data.old_version + } + } + None => V::Base::VERSION, + } + } + /// Serialize a message with a version number, using `message.view_number()` and an optional decided upgrade certificate to determine the message's version. /// /// # Errors diff --git a/crates/types/src/simple_vote.rs b/crates/types/src/simple_vote.rs index fd1500c04c..a86452b9a9 100644 --- a/crates/types/src/simple_vote.rs +++ b/crates/types/src/simple_vote.rs @@ -216,6 +216,24 @@ impl VersionedVoteData, + ) -> Self { + let version = upgrade_lock.version_infallible(view).await; + + Self { + data, + view, + version, + _pd: PhantomData, + } + } } impl Committable