diff --git a/linera-chain/src/block.rs b/linera-chain/src/block.rs index cd068e6aa48b..f2cf31067180 100644 --- a/linera-chain/src/block.rs +++ b/linera-chain/src/block.rs @@ -79,3 +79,23 @@ impl ConfirmedBlock { self.executed_block } } + +use linera_base::{data_types::BlockHeight, identifiers::ChainId}; +use linera_execution::committee::Epoch; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)] +pub struct Timeout { + pub chain_id: ChainId, + pub height: BlockHeight, + pub epoch: Epoch, +} + +impl Timeout { + pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self { + Self { + chain_id, + height, + epoch, + } + } +} diff --git a/linera-chain/src/certificate.rs b/linera-chain/src/certificate.rs index 69b031b16195..e477b46b2283 100644 --- a/linera-chain/src/certificate.rs +++ b/linera-chain/src/certificate.rs @@ -13,10 +13,13 @@ use linera_base::{ identifiers::BlobId, }; use linera_execution::committee::{Committee, ValidatorName}; -use serde::{ser::Serializer, Deserialize, Deserializer, Serialize}; +use serde::{ + ser::{Serialize, SerializeStruct, Serializer}, + Deserialize, Deserializer, +}; use crate::{ - block::{ConfirmedBlock, ValidatedBlock}, + block::{ConfirmedBlock, Timeout, ValidatedBlock}, data_types::{ Certificate, CertificateValue, ExecutedBlock, HashedCertificateValue, LiteCertificate, LiteValue, @@ -30,6 +33,9 @@ pub type ValidatedBlockCertificate = GenericCertificate; /// Certificate for a [`ConfirmedBlock`] instance. pub type ConfirmedBlockCertificate = GenericCertificate; +/// Certificate for a Timeout instance. +pub type TimeoutCertificate = GenericCertificate; + /// Generic type representing a certificate for `value` of type `T`. pub struct GenericCertificate { value: Hashed, @@ -69,13 +75,25 @@ impl Debug for GenericCertificate { } } -// NOTE: For backwards compatiblity reasons we serialize the new `Certificate` type as the old -// one. Otherwise we would be breaking the RPC API schemas. We can't implement generic serialization -// for `Certificate` since only specific `T`s have corresponding `CertificateValue` variants. impl Serialize for ValidatedBlockCertificate { fn serialize(&self, serializer: S) -> Result { - let cert = Certificate::from(self.clone()); - cert.serialize(serializer) + let mut state = serializer.serialize_struct("ValidatedBlockCertificate", 4)?; + state.serialize_field("hash", &self.value.hash())?; + state.serialize_field("value", &self.inner())?; + state.serialize_field("round", &self.round)?; + state.serialize_field("signatures", &self.signatures)?; + state.end() + } +} + +impl Serialize for TimeoutCertificate { + fn serialize(&self, serializer: S) -> Result { + let mut state = serializer.serialize_struct("TimeoutCertificate", 4)?; + state.serialize_field("hash", &self.value.hash())?; + state.serialize_field("value", &self.inner())?; + state.serialize_field("round", &self.round)?; + state.serialize_field("signatures", &self.signatures)?; + state.end() } } @@ -84,7 +102,42 @@ impl<'de> Deserialize<'de> for ValidatedBlockCertificate { where D: Deserializer<'de>, { - Certificate::deserialize(deserializer).map(ValidatedBlockCertificate::from) + #[derive(Deserialize)] + #[serde(rename = "ValidatedBlockCertificate")] + struct Inner { + hash: CryptoHash, + value: ValidatedBlock, + round: Round, + signatures: Vec<(ValidatorName, Signature)>, + } + let value = Inner::deserialize(deserializer)?; + Ok(Self { + value: Hashed::unchecked_new(value.value, value.hash), + round: value.round, + signatures: value.signatures, + }) + } +} + +impl<'de> Deserialize<'de> for TimeoutCertificate { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(rename = "TimeoutCertificate")] + struct Inner { + hash: CryptoHash, + value: Timeout, + round: Round, + signatures: Vec<(ValidatorName, Signature)>, + } + let value = Inner::deserialize(deserializer)?; + Ok(Self { + value: Hashed::unchecked_new(value.value, value.hash), + round: value.round, + signatures: value.signatures, + }) } } @@ -129,7 +182,23 @@ impl From for Certificate { } } -// TODO(#2842): In practice, it should be HashedCertificateValue = Hashed +impl From for Certificate { + fn from(cert: TimeoutCertificate) -> Certificate { + let TimeoutCertificate { + value, + round, + signatures, + } = cert; + let timeout = value.into_inner(); + Certificate::new( + HashedCertificateValue::new_timeout(timeout.chain_id, timeout.height, timeout.epoch), + round, + signatures, + ) + } +} + +// In practice, it should be HashedCertificateValue = Hashed // but [`HashedCertificateValue`] is used in too many places to change it now. /// Wrapper type around hashed instance of `T` type. pub struct Hashed { @@ -319,3 +388,22 @@ impl TryFrom for ConfirmedBlockCertificate { } } } + +impl From for TimeoutCertificate { + fn from(cert: Certificate) -> Self { + let signatures = cert.signatures().clone(); + let hash = cert.value.hash(); + match cert.value.into_inner() { + CertificateValue::Timeout { + chain_id, + epoch, + height, + } => Self { + value: Hashed::unchecked_new(Timeout::new(chain_id, height, epoch), hash), + round: cert.round, + signatures, + }, + _ => panic!("Expected a timeout certificate"), + } + } +} diff --git a/linera-chain/src/manager.rs b/linera-chain/src/manager.rs index 43f0b195a4c7..4017b4919416 100644 --- a/linera-chain/src/manager.rs +++ b/linera-chain/src/manager.rs @@ -81,14 +81,13 @@ use linera_execution::committee::Epoch; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use rand_distr::{Distribution, WeightedAliasIndex}; use serde::{Deserialize, Serialize}; -use tracing::error; use crate::{ data_types::{ - Block, BlockExecutionOutcome, BlockProposal, Certificate, CertificateValue, - HashedCertificateValue, LiteVote, ProposalContent, Vote, + Block, BlockExecutionOutcome, BlockProposal, CertificateValue, HashedCertificateValue, + LiteVote, ProposalContent, Vote, }, - types::{ConfirmedBlockCertificate, ValidatedBlockCertificate}, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainError, }; @@ -117,7 +116,7 @@ pub struct ChainManager { /// validator). pub locked: Option, /// Latest leader timeout certificate we have received. - pub timeout: Option, + pub timeout: Option, /// Latest vote we have cast, to validate or confirm. pub pending: Option, /// Latest timeout vote we cast. @@ -470,12 +469,11 @@ impl ChainManager { /// Updates the round number and timer if the timeout certificate is from a higher round than /// any known certificate. - pub fn handle_timeout_certificate(&mut self, certificate: Certificate, local_time: Timestamp) { - if !certificate.value().is_timeout() { - // Unreachable: This is only called with timeout certificates. - error!("Unexpected certificate; expected leader timeout"); - return; - } + pub fn handle_timeout_certificate( + &mut self, + certificate: TimeoutCertificate, + local_time: Timestamp, + ) { let round = certificate.round; if let Some(known_certificate) = &self.timeout { if known_certificate.round >= round { @@ -565,7 +563,7 @@ pub struct ChainManagerInfo { /// validator). pub requested_locked: Option>, /// Latest timeout certificate we have seen. - pub timeout: Option>, + pub timeout: Option>, /// Latest vote we cast (either to validate or to confirm a block). pub pending: Option, /// Latest timeout vote we cast. diff --git a/linera-core/src/chain_worker/actor.rs b/linera-core/src/chain_worker/actor.rs index 896a6ffc83ff..6c343a5cda66 100644 --- a/linera-core/src/chain_worker/actor.rs +++ b/linera-core/src/chain_worker/actor.rs @@ -16,10 +16,9 @@ use linera_base::{ }; use linera_chain::{ data_types::{ - Block, BlockProposal, Certificate, ExecutedBlock, HashedCertificateValue, MessageBundle, - Origin, Target, + Block, BlockProposal, ExecutedBlock, HashedCertificateValue, MessageBundle, Origin, Target, }, - types::{ConfirmedBlockCertificate, ValidatedBlockCertificate}, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainStateView, }; use linera_execution::{ @@ -45,7 +44,8 @@ where #[cfg(with_testing)] ReadCertificate { height: BlockHeight, - callback: oneshot::Sender, WorkerError>>, + callback: + oneshot::Sender, WorkerError>>, }, /// Search for a bundle in one of the chain's inboxes. @@ -84,7 +84,7 @@ where /// Process a leader timeout issued for this multi-owner chain. ProcessTimeout { - certificate: Certificate, + certificate: TimeoutCertificate, callback: oneshot::Sender>, }, diff --git a/linera-core/src/chain_worker/state/attempted_changes.rs b/linera-core/src/chain_worker/state/attempted_changes.rs index 7485641df382..0604113416a8 100644 --- a/linera-core/src/chain_worker/state/attempted_changes.rs +++ b/linera-core/src/chain_worker/state/attempted_changes.rs @@ -12,11 +12,10 @@ use linera_base::{ }; use linera_chain::{ data_types::{ - BlockExecutionOutcome, BlockProposal, Certificate, CertificateValue, MessageBundle, Origin, - Target, + BlockExecutionOutcome, BlockProposal, Certificate, MessageBundle, Origin, Target, }, manager, - types::{ConfirmedBlockCertificate, ValidatedBlockCertificate}, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainStateView, }; use linera_execution::{ @@ -68,17 +67,8 @@ where /// Processes a leader timeout issued for this multi-owner chain. pub(super) async fn process_timeout( &mut self, - certificate: Certificate, + certificate: TimeoutCertificate, ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> { - let (chain_id, height, epoch) = match certificate.value() { - CertificateValue::Timeout { - chain_id, - height, - epoch, - .. - } => (*chain_id, *height, *epoch), - _ => panic!("Expecting a leader timeout certificate"), - }; // Check that the chain is active and ready for this timeout. // Verify the certificate. Returns a catch-all error to make client code more robust. self.state.ensure_is_active()?; @@ -90,11 +80,11 @@ where .current_committee() .expect("chain is active"); ensure!( - epoch == chain_epoch, + certificate.inner().epoch == chain_epoch, WorkerError::InvalidEpoch { - chain_id, + chain_id: certificate.inner().chain_id, chain_epoch, - epoch + epoch: certificate.inner().epoch } ); certificate.check(committee)?; @@ -104,7 +94,7 @@ where .chain .tip_state .get() - .already_validated_block(height)? + .already_validated_block(certificate.inner().height)? { return Ok(( ChainInfoResponse::new(&self.state.chain, self.state.config.key_pair()), @@ -112,19 +102,21 @@ where )); } let old_round = self.state.chain.manager.get().current_round; + let timeout_chainid = certificate.inner().chain_id; + let timeout_height = certificate.inner().height; self.state .chain .manager .get_mut() - .handle_timeout_certificate( - certificate.clone(), - self.state.storage.clock().current_time(), - ); + .handle_timeout_certificate(certificate, self.state.storage.clock().current_time()); let round = self.state.chain.manager.get().current_round; if round > old_round { actions.notifications.push(Notification { - chain_id, - reason: Reason::NewRound { height, round }, + chain_id: timeout_chainid, + reason: Reason::NewRound { + height: timeout_height, + round, + }, }) } let info = ChainInfoResponse::new(&self.state.chain, self.state.config.key_pair()); diff --git a/linera-core/src/chain_worker/state/mod.rs b/linera-core/src/chain_worker/state/mod.rs index 5b8f1d109983..80654435be1e 100644 --- a/linera-core/src/chain_worker/state/mod.rs +++ b/linera-core/src/chain_worker/state/mod.rs @@ -19,10 +19,10 @@ use linera_base::{ }; use linera_chain::{ data_types::{ - Block, BlockProposal, Certificate, ExecutedBlock, HashedCertificateValue, Medium, - MessageBundle, Origin, Target, + Block, BlockProposal, ExecutedBlock, HashedCertificateValue, Medium, MessageBundle, Origin, + Target, }, - types::{ConfirmedBlockCertificate, ValidatedBlockCertificate}, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainError, ChainStateView, }; use linera_execution::{ @@ -130,7 +130,7 @@ where pub(super) async fn read_certificate( &mut self, height: BlockHeight, - ) -> Result, WorkerError> { + ) -> Result, WorkerError> { ChainWorkerStateWithTemporaryChanges::new(self) .await .read_certificate(height) @@ -188,7 +188,7 @@ where /// Processes a leader timeout issued for this multi-owner chain. pub(super) async fn process_timeout( &mut self, - certificate: Certificate, + certificate: TimeoutCertificate, ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> { ChainWorkerStateWithAttemptedChanges::new(self) .await diff --git a/linera-core/src/updater.rs b/linera-core/src/updater.rs index 668bf864e1ea..e59d2c678b57 100644 --- a/linera-core/src/updater.rs +++ b/linera-core/src/updater.rs @@ -352,8 +352,8 @@ where } } if let Some(cert) = manager.timeout { - if cert.value().is_timeout() && cert.value().chain_id() == chain_id { - self.send_certificate(cert, CrossChainMessageDelivery::NonBlocking) + if cert.inner().chain_id == chain_id { + self.send_certificate(cert.into(), CrossChainMessageDelivery::NonBlocking) .await?; } } diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index 23f3cb80e0ae..41c74abc0a22 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -25,7 +25,7 @@ use linera_chain::{ Block, BlockExecutionOutcome, BlockProposal, Certificate, CertificateValue, ExecutedBlock, HashedCertificateValue, LiteCertificate, MessageBundle, Origin, Target, }, - types::{ConfirmedBlockCertificate, ValidatedBlockCertificate}, + types::{ConfirmedBlockCertificate, TimeoutCertificate, ValidatedBlockCertificate}, ChainStateView, }; use linera_execution::{committee::Epoch, Query, Response}; @@ -514,12 +514,10 @@ where #[instrument(level = "trace", skip(self, certificate))] async fn process_timeout( &self, - certificate: Certificate, + certificate: TimeoutCertificate, ) -> Result<(ChainInfoResponse, NetworkActions), WorkerError> { - let CertificateValue::Timeout { chain_id, .. } = certificate.value() else { - panic!("Expecting a leader timeout certificate"); - }; - self.query_chain_worker(*chain_id, move |callback| { + let chain_id = certificate.inner().chain_id; + self.query_chain_worker(chain_id, move |callback| { ChainWorkerRequest::ProcessTimeout { certificate, callback, @@ -792,7 +790,10 @@ where } CertificateValue::Timeout { .. } => { // Handle the leader timeout. - self.process_timeout(certificate).await? + // Note: This conversion panics if `certificate` is not a timeout certificate + // but we just checked that it is. + let timeout_certificate = certificate.into(); + self.process_timeout(timeout_certificate).await? } }; diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 032ee89e0e7b..f0d51941c49c 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -266,10 +266,10 @@ ChainManagerInfo: TYPENAME: BlockProposal - requested_locked: OPTION: - TYPENAME: Certificate + TYPENAME: ValidatedBlockCertificate - timeout: OPTION: - TYPENAME: Certificate + TYPENAME: TimeoutCertificate - pending: OPTION: TYPENAME: LiteVote @@ -1029,6 +1029,27 @@ SystemOperation: TYPENAME: AdminOperation TimeDelta: NEWTYPESTRUCT: U64 +Timeout: + STRUCT: + - chain_id: + TYPENAME: ChainId + - height: + TYPENAME: BlockHeight + - epoch: + TYPENAME: Epoch +TimeoutCertificate: + STRUCT: + - hash: + TYPENAME: CryptoHash + - value: + TYPENAME: Timeout + - round: + TYPENAME: Round + - signatures: + SEQ: + TUPLE: + - TYPENAME: ValidatorName + - TYPENAME: Signature TimeoutConfig: STRUCT: - fast_round_duration: @@ -1052,6 +1073,23 @@ UserApplicationDescription: - required_application_ids: SEQ: TYPENAME: ApplicationId +ValidatedBlock: + STRUCT: + - executed_block: + TYPENAME: ExecutedBlock +ValidatedBlockCertificate: + STRUCT: + - hash: + TYPENAME: CryptoHash + - value: + TYPENAME: ValidatedBlock + - round: + TYPENAME: Round + - signatures: + SEQ: + TUPLE: + - TYPENAME: ValidatorName + - TYPENAME: Signature ValidatorName: NEWTYPESTRUCT: TYPENAME: PublicKey