diff --git a/linera-chain/src/block.rs b/linera-chain/src/block.rs index 37da584e6c1..1e56b340b90 100644 --- a/linera-chain/src/block.rs +++ b/linera-chain/src/block.rs @@ -38,12 +38,32 @@ impl ValidatedBlock { impl BcsHashable for ValidatedBlock {} +impl Has for ValidatedBlock { + fn get(&self) -> &ChainId { + &self.executed_block.block.chain_id + } +} + impl From for HashedCertificateValue { fn from(value: ValidatedBlock) -> Self { HashedCertificateValue::new_validated(value.executed_block) } } +impl TryFrom for Hashed { + type Error = &'static str; + + fn try_from(value: HashedCertificateValue) -> Result { + let hash = value.hash(); + match value.into_inner() { + CertificateValue::ValidatedBlock(validated) => { + Ok(Hashed::unchecked_new(validated, hash)) + } + _ => Err("Expected a ValidatedBlock value"), + } + } +} + /// Wrapper around an `ExecutedBlock` that has been confirmed. #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] pub struct ConfirmedBlock { @@ -57,6 +77,20 @@ impl From for HashedCertificateValue { } } +impl TryFrom for Hashed { + type Error = &'static str; + + fn try_from(value: HashedCertificateValue) -> Result { + let hash = value.hash(); + match value.into_inner() { + CertificateValue::ConfirmedBlock(confirmed) => { + Ok(Hashed::unchecked_new(confirmed, hash)) + } + _ => Err("Expected a ConfirmedBlock value"), + } + } +} + impl BcsHashable for ConfirmedBlock {} impl ConfirmedBlock { @@ -90,6 +124,12 @@ impl ConfirmedBlock { } } +impl Has for ConfirmedBlock { + fn get(&self) -> &ChainId { + &self.executed_block.block.chain_id + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] pub struct Timeout { pub chain_id: ChainId, diff --git a/linera-chain/src/certificate/generic.rs b/linera-chain/src/certificate/generic.rs index 7d5257d9200..20ee2ecf670 100644 --- a/linera-chain/src/certificate/generic.rs +++ b/linera-chain/src/certificate/generic.rs @@ -35,10 +35,16 @@ impl GenericCertificate { } } + /// Returns a reference to the `Hashed` value contained in this certificate. pub fn value(&self) -> &Hashed { &self.value } + /// Consumes this certificate, returning the value it contains. + pub fn into_value(self) -> Hashed { + self.value + } + /// Returns reference to the value contained in this certificate. pub fn inner(&self) -> &T { self.value.inner() diff --git a/linera-chain/src/certificate/lite.rs b/linera-chain/src/certificate/lite.rs index 85949019186..dcad55ed685 100644 --- a/linera-chain/src/certificate/lite.rs +++ b/linera-chain/src/certificate/lite.rs @@ -4,11 +4,11 @@ use std::borrow::Cow; -use linera_base::{crypto::Signature, data_types::Round}; +use linera_base::{crypto::Signature, data_types::Round, identifiers::ChainId}; use linera_execution::committee::{Committee, ValidatorName}; use serde::{Deserialize, Serialize}; -use super::{Certificate, HashedCertificateValue}; +use super::{GenericCertificate, Has, Hashed}; use crate::{ data_types::{check_signatures, LiteValue, LiteVote}, ChainError, @@ -42,7 +42,7 @@ impl<'a> LiteCertificate<'a> { } } - /// Creates a `LiteCertificate` from a list of votes, without cryptographically checking the + /// Creates a [`LiteCertificate`] from a list of votes, without cryptographically checking the /// signatures. Returns `None` if the votes are empty or don't have matching values and rounds. pub fn try_from_votes(votes: impl IntoIterator) -> Option { let mut votes = votes.into_iter(); @@ -73,20 +73,19 @@ impl<'a> LiteCertificate<'a> { Ok(&self.value) } - /// Returns the `Certificate` with the specified value, if it matches. - pub fn with_value(self, value: HashedCertificateValue) -> Option { - if self.value.chain_id != value.inner().chain_id() || self.value.value_hash != value.hash() - { + /// Returns the [`GenericCertificate`] with the specified value, if it matches. + pub fn with_value>(self, value: Hashed) -> Option> { + if &self.value.chain_id != value.inner().get() || self.value.value_hash != value.hash() { return None; } - Some(Certificate::new( + Some(GenericCertificate::new( value, self.round, self.signatures.into_owned(), )) } - /// Returns a `LiteCertificate` that owns the list of signatures. + /// Returns a [`LiteCertificate`] that owns the list of signatures. pub fn cloned(&self) -> LiteCertificate<'static> { LiteCertificate { value: self.value.clone(), diff --git a/linera-chain/src/manager.rs b/linera-chain/src/manager.rs index 54f5598d7ff..491b120ce5a 100644 --- a/linera-chain/src/manager.rs +++ b/linera-chain/src/manager.rs @@ -84,10 +84,10 @@ use rand_distr::{Distribution, WeightedAliasIndex}; use serde::{Deserialize, Serialize}; use crate::{ - block::Timeout, + block::{ConfirmedBlock, Timeout, ValidatedBlock}, data_types::{Block, BlockExecutionOutcome, BlockProposal, LiteVote, ProposalContent, Vote}, types::{ - CertificateValue, ConfirmedBlockCertificate, HashedCertificateValue, TimeoutCertificate, + ConfirmedBlockCertificate, Hashed, HashedCertificateValue, TimeoutCertificate, ValidatedBlockCertificate, }, ChainError, @@ -124,9 +124,12 @@ pub struct ChainManager { /// Latest leader timeout certificate we have received. #[debug(skip_if = Option::is_none)] pub timeout: Option, - /// Latest vote we have cast, to validate or confirm. + /// Latest vote we cast to confirm a block. #[debug(skip_if = Option::is_none)] - pub pending: Option>, + pub confirmed_vote: Option>, + /// Latest vote we cast to validate a block. + #[debug(skip_if = Option::is_none)] + pub validated_vote: Option>, /// Latest timeout vote we cast. #[debug(skip_if = Option::is_none)] pub timeout_vote: Option>, @@ -211,7 +214,8 @@ impl ChainManager { proposed: None, locked: None, timeout: None, - pending: None, + confirmed_vote: None, + validated_vote: None, timeout_vote: None, fallback_vote: None, round_timeout, @@ -221,9 +225,24 @@ impl ChainManager { }) } - /// Returns the most recent vote we cast. - pub fn pending(&self) -> Option<&Vote> { - self.pending.as_ref() + /// Returns the most recent confirmed vote we cast. + pub fn confirmed_vote(&self) -> Option<&Vote> { + self.confirmed_vote.as_ref() + } + + /// Returns the most recent validated vote we cast. + pub fn validated_vote(&self) -> Option<&Vote> { + self.validated_vote.as_ref() + } + + /// Returns the most recent timeout vote we cast. + pub fn timeout_vote(&self) -> Option<&Vote> { + self.timeout_vote.as_ref() + } + + /// Returns the most recent fallback vote we cast. + pub fn fallback_vote(&self) -> Option<&Vote> { + self.fallback_vote.as_ref() } /// Verifies the safety of a proposed block with respect to voting rules. @@ -361,24 +380,16 @@ impl ChainManager { ) -> Result { let new_block = &certificate.executed_block().block; let new_round = certificate.round; - if let Some(Vote { value, round, .. }) = &self.pending { - match value.inner() { - CertificateValue::ConfirmedBlock(confirmed) => { - if &confirmed.inner().block == new_block && *round == new_round { - return Ok(Outcome::Skip); // We already voted to confirm this block. - } - } - CertificateValue::ValidatedBlock(_) => { - ensure!(new_round >= *round, ChainError::InsufficientRound(*round)) - } - CertificateValue::Timeout(_) => { - // Unreachable: We only put validated or confirmed blocks in pending. - return Err(ChainError::InternalError( - "pending can only be validated or confirmed block".to_string(), - )); - } + if let Some(Vote { value, round, .. }) = &self.confirmed_vote { + if value.inner().inner().block == *new_block && *round == new_round { + return Ok(Outcome::Skip); // We already voted to confirm this block. } } + + if let Some(Vote { round, .. }) = &self.validated_vote { + ensure!(new_round >= *round, ChainError::InsufficientRound(*round)) + } + // We don't compare to `current_round` here: Non-validators must update their locked block // even if it is older than the current round. Validators will only sign in the current // round, though. (See `create_final_vote` below.) @@ -401,7 +412,7 @@ impl ChainManager { outcome: BlockExecutionOutcome, key_pair: Option<&KeyPair>, local_time: Timestamp, - ) { + ) -> (&Option>, &Option>) { // Record the proposed block, so it can be supplied to clients that request it. self.proposed = Some(proposal.clone()); self.update_current_round(local_time); @@ -428,13 +439,28 @@ impl ChainManager { if let Some(key_pair) = key_pair { // If this is a fast block, vote to confirm. Otherwise vote to validate. - let value = if round.is_fast() { - HashedCertificateValue::new_confirmed(executed_block) + if round.is_fast() { + self.confirmed_vote = Some(Vote::new( + HashedCertificateValue::new_confirmed(executed_block) + .try_into() + .expect("ConfirmedBlock"), + round, + key_pair, + )); + self.validated_vote = None } else { - HashedCertificateValue::new_validated(executed_block) + self.validated_vote = Some(Vote::new( + HashedCertificateValue::new_validated(executed_block) + .try_into() + .expect("ValidatedBlock"), + round, + key_pair, + )); + self.confirmed_vote = None; }; - self.pending = Some(Vote::new(value, round, key_pair)); } + + (&self.validated_vote, &self.confirmed_vote) } /// Signs a vote to confirm the validated block. @@ -455,11 +481,10 @@ impl ChainManager { self.update_current_round(local_time); if let Some(key_pair) = key_pair { // Vote to confirm. - // NOTE: For backwards compatibility, we need to turn `ValidatedBlockCertificate` - // back into `Certificate` type so that the vote is cast over hash of the old type. - let vote = Vote::new(confirmed.into_inner().into(), round, key_pair); + let vote = Vote::new(confirmed.value().clone(), round, key_pair); // Ok to overwrite validation votes with confirmation votes at equal or higher round. - self.pending = Some(vote); + self.confirmed_vote = Some(vote); + self.validated_vote = None; } } @@ -599,7 +624,10 @@ pub struct ChainManagerInfo { pub fallback_vote: Option, /// The value we voted for, if requested. #[debug(skip_if = Option::is_none)] - pub requested_pending_value: Option>, + pub requested_confirmed: Option>>, + /// The value we voted for, if requested. + #[debug(skip_if = Option::is_none)] + pub requested_validated: Option>>, /// The current round, i.e. the lowest round where we can still vote to validate a block. pub current_round: Round, /// The current leader, who is allowed to propose the next block. @@ -617,15 +645,21 @@ pub struct ChainManagerInfo { impl From<&ChainManager> for ChainManagerInfo { fn from(manager: &ChainManager) -> Self { let current_round = manager.current_round; + let pending = manager + .confirmed_vote + .as_ref() + .map(|vote| vote.lite()) + .or_else(move || manager.validated_vote.as_ref().map(|vote| vote.lite())); ChainManagerInfo { ownership: manager.ownership.clone(), requested_proposed: None, requested_locked: None, timeout: manager.timeout.clone().map(Box::new), - pending: manager.pending.as_ref().map(|vote| vote.lite()), + pending, timeout_vote: manager.timeout_vote.as_ref().map(Vote::lite), fallback_vote: manager.fallback_vote.as_ref().map(Vote::lite), - requested_pending_value: None, + requested_confirmed: None, + requested_validated: None, current_round, leader: manager.round_leader(current_round).cloned(), round_timeout: manager.round_timeout, @@ -639,8 +673,12 @@ impl ChainManagerInfo { pub fn add_values(&mut self, manager: &ChainManager) { self.requested_proposed = manager.proposed.clone().map(Box::new); self.requested_locked = manager.locked.clone().map(Box::new); - self.requested_pending_value = manager - .pending + self.requested_confirmed = manager + .confirmed_vote + .as_ref() + .map(|vote| Box::new(vote.value.clone())); + self.requested_validated = manager + .validated_vote .as_ref() .map(|vote| Box::new(vote.value.clone())); self.pending_blobs = manager.pending_blobs.clone(); diff --git a/linera-chain/src/test.rs b/linera-chain/src/test.rs index 37455b0f396..f40523088c9 100644 --- a/linera-chain/src/test.rs +++ b/linera-chain/src/test.rs @@ -15,21 +15,22 @@ use linera_execution::{ }; use crate::{ + block::ConfirmedBlock, data_types::{Block, BlockProposal, IncomingBundle, PostedMessage, SignatureAggregator, Vote}, - types::{GenericCertificate, HashedCertificateValue}, + types::{GenericCertificate, Hashed}, }; /// Creates a new child of the given block, with the same timestamp. -pub fn make_child_block(parent: &HashedCertificateValue) -> Block { +pub fn make_child_block(parent: &Hashed) -> Block { let parent_value = parent.inner(); - let parent_block = parent_value.block().unwrap(); + let parent_block = &parent_value.inner().block; Block { - epoch: parent_value.epoch(), - chain_id: parent_value.chain_id(), + epoch: parent_block.epoch, + chain_id: parent_block.chain_id, incoming_bundles: vec![], operations: vec![], previous_block_hash: Some(parent.hash()), - height: parent_value.height().try_add_one().unwrap(), + height: parent_block.height.try_add_one().unwrap(), authenticated_signer: parent_block.authenticated_signer, timestamp: parent_block.timestamp, } diff --git a/linera-chain/src/unit_tests/chain_tests.rs b/linera-chain/src/unit_tests/chain_tests.rs index 9ccdc031dc6..11610985b86 100644 --- a/linera-chain/src/unit_tests/chain_tests.rs +++ b/linera-chain/src/unit_tests/chain_tests.rs @@ -250,7 +250,7 @@ async fn test_application_permissions() { let value = HashedCertificateValue::new_confirmed(outcome.with(valid_block)); // In the second block, other operations are still not allowed. - let invalid_block = make_child_block(&value) + let invalid_block = make_child_block(&value.clone().try_into().unwrap()) .with_simple_transfer(chain_id, Amount::ONE) .with_operation(app_operation.clone()); let result = chain.execute_block(&invalid_block, time, None).await; @@ -259,7 +259,7 @@ async fn test_application_permissions() { ); // Also, blocks without an application operation or incoming message are forbidden. - let invalid_block = make_child_block(&value); + let invalid_block = make_child_block(&value.clone().try_into().unwrap()); let result = chain.execute_block(&invalid_block, time, None).await; assert_matches!(result, Err(ChainError::MissingMandatoryApplications(app_ids)) if app_ids == vec![application_id] @@ -268,6 +268,6 @@ async fn test_application_permissions() { // But app operations continue to work. application.expect_call(ExpectedCall::execute_operation(|_, _, _| Ok(vec![]))); application.expect_call(ExpectedCall::default_finalize()); - let valid_block = make_child_block(&value).with_operation(app_operation); + let valid_block = make_child_block(&value.try_into().unwrap()).with_operation(app_operation); chain.execute_block(&valid_block, time, None).await.unwrap(); } diff --git a/linera-core/src/chain_worker/actor.rs b/linera-core/src/chain_worker/actor.rs index 56bcb9316c7..da9a80f21c2 100644 --- a/linera-core/src/chain_worker/actor.rs +++ b/linera-core/src/chain_worker/actor.rs @@ -18,7 +18,7 @@ use linera_base::{ use linera_chain::{ data_types::{Block, BlockProposal, ExecutedBlock, MessageBundle, Origin, Target}, types::{ - ConfirmedBlockCertificate, HashedCertificateValue, TimeoutCertificate, + ConfirmedBlock, ConfirmedBlockCertificate, Hashed, TimeoutCertificate, ValidatedBlock, ValidatedBlockCertificate, }, ChainStateView, @@ -163,7 +163,8 @@ where pub async fn load( config: ChainWorkerConfig, storage: StorageClient, - certificate_value_cache: Arc>, + confirmed_value_cache: Arc>>, + validated_value_cache: Arc>>, tracked_chains: Option>>>, delivery_notifier: DeliveryNotifier, chain_id: ChainId, @@ -180,7 +181,8 @@ where let worker = ChainWorkerState::load( config, storage, - certificate_value_cache, + confirmed_value_cache, + validated_value_cache, tracked_chains, delivery_notifier, chain_id, diff --git a/linera-core/src/chain_worker/state/attempted_changes.rs b/linera-core/src/chain_worker/state/attempted_changes.rs index 14bd0debcef..091833aab0b 100644 --- a/linera-core/src/chain_worker/state/attempted_changes.rs +++ b/linera-core/src/chain_worker/state/attempted_changes.rs @@ -13,9 +13,7 @@ use linera_base::{ use linera_chain::{ data_types::{BlockExecutionOutcome, BlockProposal, MessageBundle, Origin, Target}, manager, - types::{ - Certificate, ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate, - }, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainStateView, }; use linera_execution::{ @@ -133,13 +131,22 @@ where ) -> Result<(), WorkerError> { // Create the vote and store it in the chain state. let manager = self.state.chain.manager.get_mut(); - manager.create_vote(proposal, outcome, self.state.config.key_pair(), local_time); - // Cache the value we voted on, so the client doesn't have to send it again. - if let Some(vote) = manager.pending() { - self.state - .recent_hashed_certificate_values - .insert(Cow::Borrowed(&vote.value)) - .await; + match manager.create_vote(proposal, outcome, self.state.config.key_pair(), local_time) { + // Cache the value we voted on, so the client doesn't have to send it again. + (Some(vote), None) => { + self.state + .recent_hashed_validated_values + .insert(Cow::Borrowed(&vote.value)) + .await; + } + (None, Some(vote)) => { + self.state + .recent_hashed_confirmed_values + .insert(Cow::Borrowed(&vote.value)) + .await; + } + (Some(_), Some(_)) => panic!("vote is either validated or confirmed"), + (None, None) => (), } self.save().await?; Ok(()) @@ -191,12 +198,9 @@ where )); } - // NOTE: Turn back to `Certificate` type to extract `HashedCertificateValue` - // as the `recent_hashed_cerificate_values` cache works on old types still. - let cert = Certificate::from(certificate.clone()); self.state - .recent_hashed_certificate_values - .insert(Cow::Borrowed(cert.value())) + .recent_hashed_validated_values + .insert(Cow::Borrowed(certificate.value())) .await; let required_blob_ids = executed_block.required_blob_ids(); // Verify that no unrelated blobs were provided. @@ -369,8 +373,8 @@ where self.save().await?; self.state - .recent_hashed_certificate_values - .insert(Cow::Owned(certificate.into_inner().into())) + .recent_hashed_confirmed_values + .insert(Cow::Owned(certificate.into_value())) .await; self.register_delivery_notifier(block_height, &actions, notify_when_messages_are_delivered) diff --git a/linera-core/src/chain_worker/state/mod.rs b/linera-core/src/chain_worker/state/mod.rs index b627f7d1b17..b2def5bb968 100644 --- a/linera-core/src/chain_worker/state/mod.rs +++ b/linera-core/src/chain_worker/state/mod.rs @@ -20,7 +20,7 @@ use linera_base::{ use linera_chain::{ data_types::{Block, BlockProposal, ExecutedBlock, Medium, MessageBundle, Origin, Target}, types::{ - ConfirmedBlockCertificate, HashedCertificateValue, TimeoutCertificate, + ConfirmedBlock, ConfirmedBlockCertificate, Hashed, TimeoutCertificate, ValidatedBlock, ValidatedBlockCertificate, }, ChainError, ChainStateView, @@ -55,7 +55,8 @@ where chain: ChainStateView, shared_chain_view: Option>>>, service_runtime_endpoint: Option, - recent_hashed_certificate_values: Arc>, + recent_hashed_confirmed_values: Arc>>, + recent_hashed_validated_values: Arc>>, tracked_chains: Option>>>, delivery_notifier: DeliveryNotifier, knows_chain_is_active: bool, @@ -70,7 +71,8 @@ where pub async fn load( config: ChainWorkerConfig, storage: StorageClient, - certificate_value_cache: Arc>, + confirmed_value_cache: Arc>>, + validated_value_cache: Arc>>, tracked_chains: Option>>>, delivery_notifier: DeliveryNotifier, chain_id: ChainId, @@ -84,7 +86,8 @@ where chain, shared_chain_view: None, service_runtime_endpoint, - recent_hashed_certificate_values: certificate_value_cache, + recent_hashed_confirmed_values: confirmed_value_cache, + recent_hashed_validated_values: validated_value_cache, tracked_chains, delivery_notifier, knows_chain_is_active: false, diff --git a/linera-core/src/unit_tests/value_cache_tests.rs b/linera-core/src/unit_tests/value_cache_tests.rs index 536d4e2c5a3..98aa8a123b0 100644 --- a/linera-core/src/unit_tests/value_cache_tests.rs +++ b/linera-core/src/unit_tests/value_cache_tests.rs @@ -5,13 +5,10 @@ use std::{borrow::Cow, collections::BTreeSet}; use linera_base::{ crypto::CryptoHash, - data_types::{Blob, BlockHeight, Timestamp}, + data_types::{Blob, BlockHeight}, identifiers::{BlobId, ChainId}, }; -use linera_chain::{ - data_types::{Block, BlockExecutionOutcome, ExecutedBlock}, - types::{CertificateValue, HashedCertificateValue, Timeout, ValidatedBlock}, -}; +use linera_chain::types::{CertificateValue, HashedCertificateValue, Timeout}; use linera_execution::committee::Epoch; use super::{ValueCache, DEFAULT_VALUE_CACHE_SIZE}; @@ -210,67 +207,6 @@ async fn test_eviction_of_second_entry() { ); } -/// Test that insertion of a validated block certificate also inserts its respective confirmed -/// block certificate. -#[tokio::test] -async fn test_insertion_of_validated_also_inserts_confirmed() { - let cache = ValueCache::::default(); - - let validated_value = create_dummy_validated_block_value(); - let validated_hash = validated_value.hash(); - - let confirmed_value = validated_value - .validated_to_confirmed() - .expect("a validated value should be convertible to a confirmed value"); - let confirmed_hash = confirmed_value.hash(); - - assert!(cache.insert(Cow::Borrowed(&validated_value)).await); - - assert!(cache.contains(&validated_hash).await); - assert!(cache.contains(&confirmed_hash).await); - assert_eq!(cache.get(&validated_hash).await, Some(validated_value)); - assert_eq!(cache.get(&confirmed_hash).await, Some(confirmed_value)); - assert_eq!( - cache.keys::>().await, - BTreeSet::from([validated_hash, confirmed_hash]) - ); -} - -/// Test that an inserted validated block certificate value gets evicted before its respective -/// confirmed block certificate value that was inserted with it. -#[tokio::test] -async fn test_eviction_of_validated_before_respective_confirmed() { - let cache = ValueCache::::default(); - let values = create_dummy_certificate_values(0..(DEFAULT_VALUE_CACHE_SIZE as u64 - 1)) - .collect::>(); - - let validated_value = create_dummy_validated_block_value(); - let validated_hash = validated_value.hash(); - - let confirmed_value = validated_value - .validated_to_confirmed() - .expect("a validated value should be convertible to a confirmed value"); - let confirmed_hash = confirmed_value.hash(); - - assert!(cache.insert(Cow::Borrowed(&validated_value)).await); - cache.insert_all(values.iter().map(Cow::Borrowed)).await; - - assert!(!cache.contains(&validated_hash).await); - assert!(cache.get(&validated_hash).await.is_none()); - - assert!(cache.contains(&confirmed_hash).await); - assert_eq!(cache.get(&confirmed_hash).await, Some(confirmed_value)); - assert_eq!( - cache.keys::>().await, - BTreeSet::from_iter( - values - .iter() - .map(HashedCertificateValue::hash) - .chain([confirmed_hash]) - ) - ); -} - /// Tests if reinsertion of the first entry promotes it so that it's not evicted so soon. #[tokio::test] async fn test_promotion_of_reinsertion() { @@ -319,153 +255,6 @@ async fn test_promotion_of_reinsertion() { ); } -/// Tests if reinsertion of a validated block certificate value promotes it and its respective -/// confirmed block certificate value so that it's not evicted so soon. -#[tokio::test] -async fn test_promotion_of_reinsertion_of_validated_block() { - let cache = ValueCache::::default(); - let dummy_values = - create_dummy_certificate_values(0..(DEFAULT_VALUE_CACHE_SIZE as u64)).collect::>(); - let validated_value = create_dummy_validated_block_value(); - let confirmed_value = validated_value - .validated_to_confirmed() - .expect("Dummy validated value should be able to create a confirmed value"); - - assert!(cache.insert(Cow::Borrowed(&validated_value)).await); - cache - .insert_all( - dummy_values - .iter() - .take(DEFAULT_VALUE_CACHE_SIZE - 2) - .map(Cow::Borrowed), - ) - .await; - assert!(!cache.insert(Cow::Borrowed(&validated_value)).await); - cache - .insert_all( - dummy_values - .iter() - .skip(DEFAULT_VALUE_CACHE_SIZE - 2) - .map(Cow::Borrowed), - ) - .await; - - for value in dummy_values.iter().take(2) { - assert!(!cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), None); - } - - let expected_values_in_cache = dummy_values - .iter() - .skip(2) - .chain([&validated_value, &confirmed_value]); - - for value in expected_values_in_cache.clone() { - assert!(cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), Some(value)); - } - - assert_eq!( - cache.keys::>().await, - BTreeSet::from_iter(expected_values_in_cache.map(HashedCertificateValue::hash)) - ); -} - -/// Tests if reinsertion of a confirmed block certificate value promotes it but not its respective -/// validated block certificate value. -#[tokio::test] -async fn test_promotion_of_reinsertion_of_confirmed_block() { - let cache = ValueCache::::default(); - let dummy_values = - create_dummy_certificate_values(0..(DEFAULT_VALUE_CACHE_SIZE as u64)).collect::>(); - let validated_value = create_dummy_validated_block_value(); - let confirmed_value = validated_value - .validated_to_confirmed() - .expect("Dummy validated value should be able to create a confirmed value"); - - assert!(cache.insert(Cow::Borrowed(&validated_value)).await); - cache - .insert_all( - dummy_values - .iter() - .take(DEFAULT_VALUE_CACHE_SIZE - 2) - .map(Cow::Borrowed), - ) - .await; - assert!(!cache.insert(Cow::Borrowed(&confirmed_value)).await); - cache - .insert_all( - dummy_values - .iter() - .skip(DEFAULT_VALUE_CACHE_SIZE - 2) - .map(Cow::Borrowed), - ) - .await; - - for value in dummy_values.iter().take(1).chain([&validated_value]) { - assert!(!cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), None); - } - - let expected_values_in_cache = dummy_values.iter().skip(1).chain([&confirmed_value]); - - for value in expected_values_in_cache.clone() { - assert!(cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), Some(value)); - } - - assert_eq!( - cache.keys::>().await, - BTreeSet::from_iter(expected_values_in_cache.map(HashedCertificateValue::hash)) - ); -} - -/// Test that a re-inserted validated block certificate value gets evicted before its respective -/// confirmed block certificate value that was inserted with it. -#[tokio::test] -async fn test_eviction_of_reinserted_validated_before_respective_confirmed() { - let cache = ValueCache::::default(); - - let initial_values = create_dummy_certificate_values(0..(DEFAULT_VALUE_CACHE_SIZE as u64 - 2)) - .collect::>(); - - let final_values = create_dummy_certificate_values( - (0..(DEFAULT_VALUE_CACHE_SIZE - 1)).map(|index| (index + DEFAULT_VALUE_CACHE_SIZE) as u64), - ) - .collect::>(); - - let validated_value = create_dummy_validated_block_value(); - let confirmed_value = validated_value - .validated_to_confirmed() - .expect("a validated value should be convertible to a confirmed value"); - - assert!(cache.insert(Cow::Borrowed(&validated_value)).await); - cache - .insert_all(initial_values.iter().map(Cow::Borrowed)) - .await; - assert!(!cache.insert(Cow::Borrowed(&validated_value)).await); - cache - .insert_all(final_values.iter().map(Cow::Borrowed)) - .await; - - for value in initial_values.iter().chain([&validated_value]) { - assert!(!cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), None); - } - - let expected_values_in_cache = final_values.iter().chain([&confirmed_value]); - - for value in expected_values_in_cache.clone() { - assert!(cache.contains(&value.hash()).await); - assert_eq!(cache.get(&value.hash()).await.as_ref(), Some(value)); - } - - assert_eq!( - cache.keys::>().await, - BTreeSet::from_iter(expected_values_in_cache.map(HashedCertificateValue::hash)) - ); -} - /// Test that the cache correctly filters out cached items from an iterator. #[tokio::test] async fn test_filtering_out_cached_items() { @@ -531,21 +320,3 @@ fn create_dummy_certificate_value(height: impl Into) -> HashedCerti fn create_dummy_blob(id: usize) -> Blob { Blob::new_data(format!("test{}", id).as_bytes().to_vec()) } - -/// Creates a dummy [`HashedCertificateValue::ValidatedBlock`] to use in the tests. -fn create_dummy_validated_block_value() -> HashedCertificateValue { - CertificateValue::ValidatedBlock(ValidatedBlock::new(ExecutedBlock { - block: Block { - chain_id: ChainId(CryptoHash::test_hash("Fake chain ID")), - epoch: Epoch::ZERO, - incoming_bundles: vec![], - operations: vec![], - height: BlockHeight::ZERO, - timestamp: Timestamp::from(0), - authenticated_signer: None, - previous_block_hash: None, - }, - outcome: BlockExecutionOutcome::default(), - })) - .into() -} diff --git a/linera-core/src/unit_tests/wasm_worker_tests.rs b/linera-core/src/unit_tests/wasm_worker_tests.rs index d9bd25ba0f0..2e50b1a1fcf 100644 --- a/linera-core/src/unit_tests/wasm_worker_tests.rs +++ b/linera-core/src/unit_tests/wasm_worker_tests.rs @@ -253,7 +253,7 @@ where // Execute an application operation let increment = 5_u64; let user_operation = bcs::to_bytes(&increment)?; - let run_block = make_child_block(create_certificate.value()) + let run_block = make_child_block(&create_certificate.into_value().try_into().unwrap()) .with_timestamp(3) .with_operation(Operation::User { application_id, diff --git a/linera-core/src/unit_tests/worker_tests.rs b/linera-core/src/unit_tests/worker_tests.rs index 9e3518986db..470ee5d8651 100644 --- a/linera-core/src/unit_tests/worker_tests.rs +++ b/linera-core/src/unit_tests/worker_tests.rs @@ -32,7 +32,7 @@ use linera_chain::{ PostedMessage, SignatureAggregator, }, test::{make_child_block, make_first_block, BlockTestExt, MessageTestExt, VoteTestExt}, - types::{Certificate, HashedCertificateValue}, + types::{ConfirmedBlockCertificate, GenericCertificate, Has, Hashed, HashedCertificateValue}, ChainError, ChainExecutionContext, }; use linera_execution::{ @@ -139,25 +139,27 @@ where init_worker_with_chains(storage, [(description, owner, balance)]).await } -fn make_certificate( +fn make_certificate( committee: &Committee, worker: &WorkerState, - value: HashedCertificateValue, -) -> Certificate + value: Hashed, +) -> GenericCertificate where S: Storage, + T: Clone + Has, { make_certificate_with_round(committee, worker, value, Round::Fast) } -fn make_certificate_with_round( +fn make_certificate_with_round( committee: &Committee, worker: &WorkerState, - value: HashedCertificateValue, + value: Hashed, round: Round, -) -> Certificate +) -> GenericCertificate where S: Storage, + T: Clone + Has, { let vote = LiteVote::new( value.lite(), @@ -181,8 +183,8 @@ async fn make_simple_transfer_certificate( committee: &Committee, balance: Amount, worker: &WorkerState, - previous_confirmed_block: Option<&Certificate>, -) -> Certificate + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, +) -> ConfirmedBlockCertificate where S: Storage, { @@ -216,8 +218,8 @@ async fn make_transfer_certificate( balance: Amount, balances: BTreeMap, worker: &WorkerState, - previous_confirmed_block: Option<&Certificate>, -) -> Certificate + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, +) -> ConfirmedBlockCertificate where S: Storage, { @@ -253,8 +255,8 @@ async fn make_transfer_certificate_for_epoch( balance: Amount, balances: BTreeMap, worker: &WorkerState, - previous_confirmed_block: Option<&Certificate>, -) -> Certificate + previous_confirmed_block: Option<&ConfirmedBlockCertificate>, +) -> ConfirmedBlockCertificate where S: Storage, { @@ -331,7 +333,7 @@ where } .with(block), ); - make_certificate(committee, worker, value) + make_certificate(committee, worker, value.try_into().expect("Confirmed cert")) } fn direct_outgoing_message( @@ -395,8 +397,11 @@ fn generate_key_pairs(count: usize) -> Vec { } /// Creates a `CrossChainRequest` with the messages sent by the certificate to the recipient. -fn update_recipient_direct(recipient: ChainId, certificate: &Certificate) -> CrossChainRequest { - let sender = certificate.inner().chain_id(); +fn update_recipient_direct( + recipient: ChainId, + certificate: &ConfirmedBlockCertificate, +) -> CrossChainRequest { + let sender = certificate.inner().inner().block.chain_id; let bundles = certificate.message_bundles_for(&Medium::Direct, recipient); CrossChainRequest::UpdateRecipient { sender, @@ -446,7 +451,8 @@ where ); let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_none()); + assert!(chain.manager.get().confirmed_vote().is_none()); + assert!(chain.manager.get().validated_vote().is_none()); Ok(()) } @@ -495,7 +501,8 @@ where ); let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_none()); + assert!(chain.manager.get().confirmed_vote().is_none()); + assert!(chain.manager.get().validated_vote().is_none()); Ok(()) } @@ -557,7 +564,7 @@ where .await?; { - let block_proposal = make_child_block(certificate.value()) + let block_proposal = make_child_block(&certificate.into_value().try_into().unwrap()) .with_timestamp(block_0_time.saturating_sub_micros(1)) .into_fast_proposal(&key_pair); // Timestamp older than previous one @@ -607,7 +614,8 @@ where ); let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_none()); + assert!(chain.manager.get().confirmed_vote().is_none()); + assert!(chain.manager.get().validated_vote().is_none()); Ok(()) } @@ -652,11 +660,17 @@ where assert_matches!( worker.handle_block_proposal(block_proposal1.clone()).await, - Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::UnexpectedBlockHeight {..}) + Err(WorkerError::ChainError(error)) if matches!( + *error, + ChainError::UnexpectedBlockHeight { + expected_block_height: BlockHeight(0), + found_block_height: BlockHeight(1) + }) ); let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_none()); + assert!(chain.manager.get().confirmed_vote().is_none()); + assert!(chain.manager.get().validated_vote().is_none()); drop(chain); worker @@ -664,19 +678,53 @@ where .await?; let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_some()); + assert_eq!( + &chain + .manager + .get() + .confirmed_vote() + .unwrap() + .value() + .inner() + .block, + &block_proposal0.content.block + ); // In fast round confirm immediately. + assert!(chain.manager.get().validated_vote().is_none()); drop(chain); + worker - .handle_certificate(certificate0, vec![], None) + .handle_certificate(certificate0.into(), vec![], None) .await?; - worker.handle_block_proposal(block_proposal1).await?; + let chain = worker.chain_state_view(ChainId::root(1)).await?; + drop(chain); + + worker + .handle_block_proposal(block_proposal1.clone()) + .await?; + let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_some()); + assert_eq!( + &chain + .manager + .get() + .confirmed_vote() + .unwrap() + .value() + .inner() + .block, + &block_proposal1.content.block + ); + assert!(chain.manager.get().validated_vote().is_none()); drop(chain); assert_matches!( - worker.handle_block_proposal(block_proposal0.clone()).await, - Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::UnexpectedBlockHeight {..}) + worker.handle_block_proposal(block_proposal0).await, + Err(WorkerError::ChainError(error)) if matches!( + *error, + ChainError::UnexpectedBlockHeight { + expected_block_height: BlockHeight(1), + found_block_height: BlockHeight(0), + }) ); Ok(()) } @@ -765,7 +813,7 @@ where oracle_responses: vec![Vec::new()], } .with( - make_child_block(certificate0.value()) + make_child_block(&certificate0.clone().into_value().try_into().unwrap()) .with_simple_transfer(ChainId::root(2), Amount::from_tokens(3)) .with_authenticated_signer(Some(sender_key_pair.public().into())), ), @@ -1008,7 +1056,7 @@ where .await?; // Then receive the next two messages. - let block_proposal = make_child_block(certificate.value()) + let block_proposal = make_child_block(&certificate.into_value().try_into().unwrap()) .with_simple_transfer(ChainId::root(3), Amount::from_tokens(3)) .with_incoming_bundle(IncomingBundle { origin: Origin::chain(ChainId::root(1)), @@ -1082,7 +1130,8 @@ where ); let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - assert!(chain.manager.get().pending().is_none()); + assert!(chain.manager.get().confirmed_vote().is_none()); + assert!(chain.manager.get().validated_vote().is_none()); Ok(()) } @@ -1114,10 +1163,11 @@ where chain_info_response.check(&ValidatorName(worker.public_key()))?; let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); - let pending_value = chain.manager.get().pending().unwrap().lite(); + assert!(chain.manager.get().validated_vote().is_none()); // Into was a fast round. + let pending_vote = chain.manager.get().confirmed_vote().unwrap().lite(); assert_eq!( chain_info_response.info.manager.pending.unwrap(), - pending_value + pending_vote ); Ok(()) } @@ -1197,7 +1247,7 @@ where .await; assert_matches!( worker - .fully_handle_certificate(certificate, vec![]) + .fully_handle_certificate(certificate.into(), vec![]) .await, Err(WorkerError::ChainError(error)) if matches!(*error, ChainError::InactiveChain {..}) ); @@ -1321,7 +1371,9 @@ where // This fails because `make_simple_transfer_certificate` uses `sender_key_pair.public()` to // compute the hash of the execution state. assert_matches!( - worker.fully_handle_certificate(certificate, vec![]).await, + worker + .fully_handle_certificate(certificate.into(), vec![]) + .await, Err(WorkerError::IncorrectOutcome { .. }) ); Ok(()) @@ -1367,9 +1419,11 @@ where .await; // Replays are ignored. worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) + .await?; + worker + .fully_handle_certificate(certificate.into(), vec![]) .await?; - worker.fully_handle_certificate(certificate, vec![]).await?; Ok(()) } @@ -1426,7 +1480,7 @@ where ) .await; worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await?; let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); @@ -1516,7 +1570,7 @@ where ) .await; worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await?; let new_sender_chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(new_sender_chain.is_active()); @@ -1572,7 +1626,7 @@ where ) .await; worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await?; let chain = worker.chain_state_view(ChainId::root(1)).await?; assert!(chain.is_active()); @@ -1645,7 +1699,10 @@ where ) .await; worker - .handle_cross_chain_request(update_recipient_direct(ChainId::root(2), &certificate)) + .handle_cross_chain_request(update_recipient_direct( + ChainId::root(2), + &certificate.clone(), + )) .await?; let chain = worker.chain_state_view(ChainId::root(2)).await?; assert!(chain.is_active()); @@ -1840,7 +1897,7 @@ where .await; let info = worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await? .info; assert_eq!(ChainId::root(1), info.chain_id); @@ -1883,7 +1940,7 @@ where ) .await; worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await?; assert_eq!( @@ -1966,7 +2023,7 @@ where .await; let info = worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await? .info; assert_eq!(ChainId::root(1), info.chain_id); @@ -2037,7 +2094,7 @@ where .await; worker - .fully_handle_certificate(certificate00.clone(), vec![]) + .fully_handle_certificate(certificate00.clone().into(), vec![]) .await?; let certificate01 = make_transfer_certificate( @@ -2071,7 +2128,7 @@ where .await; worker - .fully_handle_certificate(certificate01.clone(), vec![]) + .fully_handle_certificate(certificate01.clone().into(), vec![]) .await?; { @@ -2097,7 +2154,7 @@ where .await; worker - .fully_handle_certificate(certificate1.clone(), vec![]) + .fully_handle_certificate(certificate1.clone().into(), vec![]) .await?; let certificate2 = make_transfer_certificate( @@ -2116,7 +2173,7 @@ where .await; worker - .fully_handle_certificate(certificate2.clone(), vec![]) + .fully_handle_certificate(certificate2.clone().into(), vec![]) .await?; // Reject the first transfer and try to use the money of the second one. @@ -2169,7 +2226,7 @@ where .await; worker - .fully_handle_certificate(certificate.clone(), vec![]) + .fully_handle_certificate(certificate.clone().into(), vec![]) .await?; { @@ -2210,7 +2267,7 @@ where .await; worker - .fully_handle_certificate(certificate3.clone(), vec![]) + .fully_handle_certificate(certificate3.clone().into(), vec![]) .await?; { @@ -2366,7 +2423,7 @@ where oracle_responses: vec![Vec::new(); 2], } .with( - make_child_block(certificate0.value()) + make_child_block(&certificate0.clone().into_value().try_into().unwrap()) .with_operation(SystemOperation::Admin(AdminOperation::CreateCommittee { epoch: Epoch::from(1), committee: committee.clone(), @@ -2854,7 +2911,7 @@ where oracle_responses: vec![Vec::new()], } .with( - make_child_block(certificate1.value()) + make_child_block(&certificate1.into_value().try_into().unwrap()) .with_epoch(1) .with_incoming_bundle(IncomingBundle { origin: Origin::chain(user_id), @@ -3159,12 +3216,12 @@ where assert_eq!(response.info.manager.leader, Some(Owner::from(pub_key1))); // So owner 0 cannot propose a block in this round. And the next round hasn't started yet. - let proposal = - make_child_block(&value0).into_proposal_with_round(&key_pairs[0], Round::SingleLeader(0)); + let proposal = make_child_block(&value0.clone().try_into().unwrap()) + .into_proposal_with_round(&key_pairs[0], Round::SingleLeader(0)); let result = worker.handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::InvalidOwner)); - let proposal = - make_child_block(&value0).into_proposal_with_round(&key_pairs[0], Round::SingleLeader(1)); + let proposal = make_child_block(&value0.clone().try_into().unwrap()) + .into_proposal_with_round(&key_pairs[0], Round::SingleLeader(1)); let result = worker.handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::ChainError(ref error)) if matches!(**error, ChainError::WrongRound(Round::SingleLeader(0))) @@ -3197,7 +3254,7 @@ where assert_eq!(response.info.manager.leader, Some(Owner::from(pub_key0))); // Now owner 0 can propose a block, but owner 1 can't. - let block1 = make_child_block(&value0); + let block1 = make_child_block(&value0.clone().try_into().unwrap()); let (executed_block1, _) = worker.stage_block_execution(block1.clone()).await?; let proposal1_wrong_owner = block1 .clone() @@ -3236,7 +3293,8 @@ where // Create block2, also at height 1, but different from block 1. let amount = Amount::from_tokens(1); - let block2 = make_child_block(&value0).with_simple_transfer(ChainId::root(1), amount); + let block2 = make_child_block(&value0.clone().try_into().unwrap()) + .with_simple_transfer(ChainId::root(1), amount); let (executed_block2, _) = worker.stage_block_execution(block2.clone()).await?; // Since round 3 is already over, a validated block from round 3 won't update the validator's @@ -3365,11 +3423,12 @@ where assert_eq!(response.info.manager.leader, None); // So owner 1 cannot propose a block in this round. And the next round hasn't started yet. - let proposal = make_child_block(&value0).into_proposal_with_round(&key_pairs[1], Round::Fast); + let proposal = make_child_block(&value0.clone().try_into().unwrap()) + .into_proposal_with_round(&key_pairs[1], Round::Fast); let result = worker.handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::InvalidOwner)); - let proposal = - make_child_block(&value0).into_proposal_with_round(&key_pairs[1], Round::MultiLeader(0)); + let proposal = make_child_block(&value0.clone().try_into().unwrap()) + .into_proposal_with_round(&key_pairs[1], Round::MultiLeader(0)); let result = worker.handle_block_proposal(proposal).await; assert_matches!(result, Err(WorkerError::ChainError(ref error)) if matches!(**error, ChainError::WrongRound(Round::Fast)) @@ -3402,7 +3461,7 @@ where assert_eq!(response.info.manager.leader, None); // Now any owner can propose a block. And multi-leader rounds can be skipped without timeout. - let block1 = make_child_block(&value0); + let block1 = make_child_block(&value0.try_into().unwrap()); let proposal1 = block1 .clone() .with_authenticated_signer(Some(pub_key1.into())) @@ -3453,7 +3512,7 @@ where assert_eq!(response.info.manager.leader, None); // Owner 0 proposes another block. The validator votes to confirm. - let block1 = make_child_block(&value0); + let block1 = make_child_block(&value0.clone().try_into().unwrap()); let proposal1 = block1 .clone() .into_proposal_with_round(&key_pairs[0], Round::Fast); @@ -3478,7 +3537,7 @@ where assert_eq!(response.info.manager.leader, None); // Now any owner can propose a block. But block1 is locked. - let block2 = make_child_block(&value0) + let block2 = make_child_block(&value0.try_into().unwrap()) .with_simple_transfer(ChainId::root(1), Amount::ONE) .with_authenticated_signer(Some(pub_key1.into())); let proposal2 = block2 diff --git a/linera-core/src/value_cache.rs b/linera-core/src/value_cache.rs index c7b6b074b98..14aaba7725f 100644 --- a/linera-core/src/value_cache.rs +++ b/linera-core/src/value_cache.rs @@ -11,8 +11,12 @@ mod unit_tests; use std::{any::type_name, sync::LazyLock}; use std::{borrow::Cow, hash::Hash, num::NonZeroUsize}; -use linera_base::{crypto::CryptoHash, data_types::Blob, identifiers::BlobId}; -use linera_chain::types::{Certificate, HashedCertificateValue, LiteCertificate}; +use linera_base::{ + crypto::CryptoHash, + data_types::Blob, + identifiers::{BlobId, ChainId}, +}; +use linera_chain::types::{GenericCertificate, Has, Hashed, LiteCertificate}; use lru::LruCache; use tokio::sync::Mutex; #[cfg(with_metrics)] @@ -164,38 +168,23 @@ where } } -impl ValueCache { +impl ValueCache> { /// Inserts a [`HashedCertificateValue`] into the cache, if it's not already present. /// /// The `value` is wrapped in a [`Cow`] so that it is only cloned if it needs to be /// inserted in the cache. /// /// Returns [`true`] if the value was not already present in the cache. - /// - /// # Notes - /// - /// If the `value` is a [`HashedCertificateValue::ValidatedBlock`], its respective - /// [`HashedCertificateValue::ConfirmedBlock`] is also cached. - pub async fn insert<'a>(&self, value: Cow<'a, HashedCertificateValue>) -> bool { + pub async fn insert<'a>(&self, value: Cow<'a, Hashed>) -> bool { let hash = (*value).hash(); - let maybe_confirmed_value = value.validated_to_confirmed(); let mut cache = self.cache.lock().await; if cache.contains(&hash) { // Promote the re-inserted value in the cache, as if it was accessed again. cache.promote(&hash); - if let Some(confirmed_value) = maybe_confirmed_value { - // Also promote its respective confirmed certificate value - cache.promote(&confirmed_value.hash()); - } false } else { // Cache the certificate so that clients don't have to send the value again. cache.push(hash, value.into_owned()); - if let Some(confirmed_value) = maybe_confirmed_value { - // Cache the certificate for the confirmed block in advance, so that the clients don't - // have to send it. - cache.push(confirmed_value.hash(), confirmed_value); - } true } } @@ -205,10 +194,11 @@ impl ValueCache { /// /// The `values` are wrapped in [`Cow`]s so that each `value` is only cloned if it /// needs to be inserted in the cache. - pub async fn insert_all<'a>( - &self, - values: impl IntoIterator>, - ) { + #[cfg(with_testing)] + pub async fn insert_all<'a>(&self, values: impl IntoIterator>>) + where + T: 'a, + { let mut cache = self.cache.lock().await; for value in values { let hash = (*value).hash(); @@ -223,7 +213,10 @@ impl ValueCache { pub async fn full_certificate( &self, certificate: LiteCertificate<'_>, - ) -> Result { + ) -> Result, WorkerError> + where + T: Has, + { let value = self .get(&certificate.value.value_hash) .await diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index 2e3dec882e7..f8270483323 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -25,8 +25,8 @@ use linera_chain::{ Block, BlockExecutionOutcome, BlockProposal, ExecutedBlock, MessageBundle, Origin, Target, }, types::{ - Certificate, CertificateValue, ConfirmedBlockCertificate, HashedCertificateValue, - LiteCertificate, TimeoutCertificate, ValidatedBlockCertificate, + Certificate, CertificateValue, ConfirmedBlock, ConfirmedBlockCertificate, Hashed, + LiteCertificate, TimeoutCertificate, ValidatedBlock, ValidatedBlockCertificate, }, ChainError, ChainStateView, }; @@ -262,8 +262,8 @@ where storage: StorageClient, /// Configuration options for the [`ChainWorker`]s. chain_worker_config: ChainWorkerConfig, - /// Cached hashed certificate values by hash. - recent_hashed_certificate_values: Arc>, + recent_confirmed_value_cache: Arc>>, + recent_validated_value_cache: Arc>>, /// Chain IDs that should be tracked by a worker. tracked_chains: Option>>>, /// One-shot channels to notify callers when messages of a particular chain have been @@ -296,7 +296,8 @@ where nickname, storage, chain_worker_config: ChainWorkerConfig::default().with_key_pair(key_pair), - recent_hashed_certificate_values: Arc::new(ValueCache::default()), + recent_confirmed_value_cache: Arc::new(ValueCache::default()), + recent_validated_value_cache: Arc::new(ValueCache::default()), tracked_chains: None, delivery_notifiers: Arc::default(), chain_worker_tasks: Arc::default(), @@ -315,7 +316,8 @@ where nickname, storage, chain_worker_config: ChainWorkerConfig::default(), - recent_hashed_certificate_values: Arc::new(ValueCache::default()), + recent_confirmed_value_cache: Arc::new(ValueCache::default()), + recent_validated_value_cache: Arc::new(ValueCache::default()), tracked_chains: Some(tracked_chains), delivery_notifiers: Arc::default(), chain_worker_tasks: Arc::default(), @@ -395,9 +397,19 @@ where &self, certificate: LiteCertificate<'_>, ) -> Result { - self.recent_hashed_certificate_values - .full_certificate(certificate) + match self + .recent_confirmed_value_cache + .full_certificate(certificate.clone()) .await + { + Ok(certificate) => Ok(certificate.into()), + Err(WorkerError::MissingCertificateValue) => Ok(self + .recent_validated_value_cache + .full_certificate(certificate) + .await + .map(Into::into)?), + Err(other) => Err(other), + } } } @@ -646,7 +658,8 @@ where let actor = ChainWorkerActor::load( self.chain_worker_config.clone(), self.storage.clone(), - self.recent_hashed_certificate_values.clone(), + self.recent_confirmed_value_cache.clone(), + self.recent_validated_value_cache.clone(), self.tracked_chains.clone(), delivery_notifier, chain_id, diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 0dddf4081ba..b6eb967cbbd 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -272,9 +272,12 @@ ChainManagerInfo: - fallback_vote: OPTION: TYPENAME: LiteVote - - requested_pending_value: + - requested_confirmed: OPTION: - TYPENAME: CertificateValue + TYPENAME: ConfirmedBlock + - requested_validated: + OPTION: + TYPENAME: ValidatedBlock - current_round: TYPENAME: Round - leader: