Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate recent hashed values cache into separate types #2902

Merged
merged 9 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions linera-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,32 @@ impl ValidatedBlock {

impl BcsHashable for ValidatedBlock {}

impl Has<ChainId> for ValidatedBlock {
fn get(&self) -> &ChainId {
&self.executed_block.block.chain_id
}
}

impl From<ValidatedBlock> for HashedCertificateValue {
fn from(value: ValidatedBlock) -> Self {
HashedCertificateValue::new_validated(value.executed_block)
}
}

impl TryFrom<HashedCertificateValue> for Hashed<ValidatedBlock> {
type Error = &'static str;

fn try_from(value: HashedCertificateValue) -> Result<Self, Self::Error> {
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 {
Expand All @@ -57,6 +77,20 @@ impl From<ConfirmedBlock> for HashedCertificateValue {
}
}

impl TryFrom<HashedCertificateValue> for Hashed<ConfirmedBlock> {
type Error = &'static str;

fn try_from(value: HashedCertificateValue) -> Result<Self, Self::Error> {
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 {
Expand Down Expand Up @@ -90,6 +124,12 @@ impl ConfirmedBlock {
}
}

impl Has<ChainId> 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,
Expand Down
6 changes: 6 additions & 0 deletions linera-chain/src/certificate/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ impl<T> GenericCertificate<T> {
}
}

/// Returns a reference to the `Hashed` value contained in this certificate.
pub fn value(&self) -> &Hashed<T> {
&self.value
}

/// Consumes this certificate, returning the value it contains.
pub fn into_value(self) -> Hashed<T> {
self.value
}

/// Returns reference to the value contained in this certificate.
pub fn inner(&self) -> &T {
self.value.inner()
Expand Down
11 changes: 5 additions & 6 deletions linera-chain/src/certificate/lite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -74,12 +74,11 @@ impl<'a> LiteCertificate<'a> {
}

/// Returns the `Certificate` with the specified value, if it matches.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Returns the `Certificate` with the specified value, if it matches.
/// Returns the [`GenericCertificate`] with the specified value, if it matches.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in Fix comments

pub fn with_value(self, value: HashedCertificateValue) -> Option<Certificate> {
if self.value.chain_id != value.inner().chain_id() || self.value.value_hash != value.hash()
{
pub fn with_value<T: Has<ChainId>>(self, value: Hashed<T>) -> Option<GenericCertificate<T>> {
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(),
Expand Down
115 changes: 76 additions & 39 deletions linera-chain/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -124,9 +124,12 @@ pub struct ChainManager {
/// Latest leader timeout certificate we have received.
#[debug(skip_if = Option::is_none)]
pub timeout: Option<TimeoutCertificate>,
/// 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<Vote<CertificateValue>>,
pub confirmed_vote: Option<Vote<ConfirmedBlock>>,
/// Latest vote we cast to validate a block.
#[debug(skip_if = Option::is_none)]
pub validated_vote: Option<Vote<ValidatedBlock>>,
/// Latest timeout vote we cast.
#[debug(skip_if = Option::is_none)]
pub timeout_vote: Option<Vote<Timeout>>,
Expand Down Expand Up @@ -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,
Expand All @@ -221,12 +225,26 @@ impl ChainManager {
})
}

/// Returns the most recent vote we cast.
pub fn pending(&self) -> Option<&Vote<CertificateValue>> {
self.pending.as_ref()
/// Returns the most recent confirmed vote we cast.
pub fn confirmed_vote(&self) -> Option<&Vote<ConfirmedBlock>> {
self.confirmed_vote.as_ref()
}

/// Returns the most recent validated vote we cast.
pub fn validated_vote(&self) -> Option<&Vote<ValidatedBlock>> {
self.validated_vote.as_ref()
}

/// Returns the most recent timeout vote we cast.
pub fn timeout_vote(&self) -> Option<&Vote<Timeout>> {
self.timeout_vote.as_ref()
}

/// Returns the most recent fallback vote we cast.
pub fn fallback_vote(&self) -> Option<&Vote<Timeout>> {
self.fallback_vote.as_ref()
}

/// Verifies the safety of a proposed block with respect to voting rules.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean why I removed the comment? probably by mistake.

Copy link
Contributor Author

@deuszx deuszx Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2cfad93

pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
let new_round = proposal.content.round;
let new_block = &proposal.content.block;
Expand Down Expand Up @@ -361,24 +379,16 @@ impl ChainManager {
) -> Result<Outcome, ChainError> {
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm wondering if we're missing a check in this function that, regardless of locked block or votes, the certificate is from at least the current round.

I created #2914 for it.

}
}

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.)
Expand All @@ -401,7 +411,7 @@ impl ChainManager {
outcome: BlockExecutionOutcome,
key_pair: Option<&KeyPair>,
local_time: Timestamp,
) {
) -> (&Option<Vote<ValidatedBlock>>, &Option<Vote<ConfirmedBlock>>) {
// 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);
Expand All @@ -428,13 +438,29 @@ 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"),
Comment on lines +444 to +446
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have a more direct constructor for that now.

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));
}

// TODO: Is it guaranteed that if key_pair = None, then the vote is not signed ever?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to bring reviewers' attention to this - i checked but want to confirm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these should only ever contain votes we signed with key_pair, and only if that's Some. (That invariant should, in fact, be guaranteed just by this module alone. Edit: It isn't, strictly speaking, since the fields are public.)

(&self.validated_vote, &self.confirmed_vote)
}

/// Signs a vote to confirm the validated block.
Expand All @@ -455,11 +481,9 @@ 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);
}
}

Expand Down Expand Up @@ -599,7 +623,10 @@ pub struct ChainManagerInfo {
pub fallback_vote: Option<LiteVote>,
/// The value we voted for, if requested.
#[debug(skip_if = Option::is_none)]
pub requested_pending_value: Option<Box<HashedCertificateValue>>,
pub requested_confirmed: Option<Box<Hashed<ConfirmedBlock>>>,
/// The value we voted for, if requested.
#[debug(skip_if = Option::is_none)]
pub requested_validated: Option<Box<Hashed<ValidatedBlock>>>,
/// 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.
Expand All @@ -617,15 +644,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()));
Comment on lines +648 to +652
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this - it shouldn't happen that both confirmed_vote and validated_vote are both set to Some(_) at the same time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you made sure of that everywhere.

Alternatively, we could avoid explicitly setting them to None and always prefer the highest-round vote, with the confirmed one taking precedence.

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,
Expand All @@ -639,8 +672,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();
Expand Down
8 changes: 5 additions & 3 deletions linera-core/src/chain_worker/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -163,7 +163,8 @@ where
pub async fn load(
config: ChainWorkerConfig,
storage: StorageClient,
certificate_value_cache: Arc<ValueCache<CryptoHash, HashedCertificateValue>>,
confirmed_value_cache: Arc<ValueCache<CryptoHash, Hashed<ConfirmedBlock>>>,
validated_value_cache: Arc<ValueCache<CryptoHash, Hashed<ValidatedBlock>>>,
afck marked this conversation as resolved.
Show resolved Hide resolved
tracked_chains: Option<Arc<RwLock<HashSet<ChainId>>>>,
delivery_notifier: DeliveryNotifier,
chain_id: ChainId,
Expand All @@ -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,
Expand Down
Loading
Loading