Skip to content

Commit

Permalink
Generalize Vote and SignatureAggregator over type of value they hold. (
Browse files Browse the repository at this point in the history
…#2887)

* SignatureAggregator over GenericCertificate

* Vote is generic over value it contains

* Use actual Timeout type in ChainManager instead

* Replace From with TryFrom

* Replace TODO with a comment.

* Replace hand-written Deserialize for Vote with derive

* Simplify call to extract ChainId
  • Loading branch information
deuszx authored Nov 15, 2024
1 parent c552c15 commit 92115c7
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 64 deletions.
24 changes: 23 additions & 1 deletion linera-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use linera_base::{crypto::BcsHashable, data_types::BlockHeight, identifiers::Cha
use linera_execution::committee::Epoch;
use serde::{Deserialize, Serialize};

use crate::{data_types::ExecutedBlock, types::HashedCertificateValue};
use crate::{
data_types::ExecutedBlock,
types::{CertificateValue, Has, Hashed, HashedCertificateValue},
};

/// Wrapper around an `ExecutedBlock` that has been validated.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
Expand Down Expand Up @@ -109,3 +112,22 @@ impl From<Timeout> for HashedCertificateValue {
HashedCertificateValue::new_timeout(value.chain_id, value.height, value.epoch)
}
}

impl TryFrom<HashedCertificateValue> for Hashed<Timeout> {
type Error = &'static str;
fn try_from(value: HashedCertificateValue) -> Result<Hashed<Timeout>, Self::Error> {
let hash = value.hash();
match value.into_inner() {
CertificateValue::Timeout(timeout) => Ok(Hashed::unchecked_new(timeout, hash)),
_ => Err("Expected a Timeout value"),
}
}
}

impl BcsHashable for Timeout {}

impl Has<ChainId> for Timeout {
fn get(&self) -> &ChainId {
&self.chain_id
}
}
41 changes: 40 additions & 1 deletion linera-chain/src/certificate/hashed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
// SPDX-License-Identifier: Apache-2.0

use custom_debug_derive::Debug;
use linera_base::crypto::{BcsHashable, CryptoHash};
use linera_base::{
crypto::{BcsHashable, CryptoHash},
identifiers::ChainId,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::data_types::LiteValue;

/// Wrapper type around hashed instance of `T` type.
#[derive(Debug)]
Expand Down Expand Up @@ -46,6 +52,34 @@ impl<T> Hashed<T> {
pub fn into_inner(self) -> T {
self.value
}

pub fn lite(&self) -> LiteValue
where
T: Has<ChainId>,
{
LiteValue {
value_hash: self.hash,
chain_id: *self.value.get(),
}
}
}

impl<T: Serialize> Serialize for Hashed<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.value.serialize(serializer)
}
}

impl<'de, T: DeserializeOwned + BcsHashable> Deserialize<'de> for Hashed<T> {
fn deserialize<D>(deserializer: D) -> Result<Hashed<T>, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Hashed::new(T::deserialize(deserializer)?))
}
}

impl<T: Clone> Clone for Hashed<T> {
Expand All @@ -66,3 +100,8 @@ impl<T> PartialEq for Hashed<T> {

#[cfg(with_testing)]
impl<T> Eq for Hashed<T> {}

/// A constraint summing a value of type `T`.
pub trait Has<T> {
fn get(&self) -> &T;
}
2 changes: 1 addition & 1 deletion linera-chain/src/certificate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod value;
use std::borrow::Cow;

pub use generic::GenericCertificate;
pub use hashed::Hashed;
pub use hashed::{Has, Hashed};
use linera_base::{
crypto::Signature,
data_types::Round,
Expand Down
38 changes: 10 additions & 28 deletions linera-chain/src/certificate/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use linera_base::{crypto::CryptoHash, data_types::BlockHeight, ensure, identifiers::ChainId};
use linera_base::{
crypto::{BcsHashable, CryptoHash},
data_types::BlockHeight,
ensure,
identifiers::ChainId,
};
use linera_execution::committee::Epoch;
use serde::{Deserialize, Deserializer, Serialize};
use serde::{Deserialize, Serialize};

use super::Hashed;
#[cfg(with_testing)]
use crate::data_types::OutgoingMessage;
use crate::{
block::{ConfirmedBlock, Timeout, ValidatedBlock},
data_types::{Block, ExecutedBlock, LiteValue},
data_types::{Block, ExecutedBlock},
ChainError,
};

Expand Down Expand Up @@ -127,6 +132,8 @@ impl CertificateValue {
}
}

impl BcsHashable for CertificateValue {}

/// A statement to be certified by the validators, with its hash.
pub type HashedCertificateValue = Hashed<CertificateValue>;

Expand All @@ -150,13 +157,6 @@ impl HashedCertificateValue {
CertificateValue::Timeout(Timeout::new(chain_id, height, epoch)).into()
}

pub fn lite(&self) -> LiteValue {
LiteValue {
value_hash: self.hash(),
chain_id: self.inner().chain_id(),
}
}

/// Returns the corresponding `ConfirmedBlock`, if this is a `ValidatedBlock`.
pub fn validated_to_confirmed(&self) -> Option<HashedCertificateValue> {
match self.inner() {
Expand All @@ -168,24 +168,6 @@ impl HashedCertificateValue {
}
}

impl Serialize for HashedCertificateValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.inner().serialize(serializer)
}
}

impl<'a> Deserialize<'a> for HashedCertificateValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
Ok(CertificateValue::deserialize(deserializer)?.into())
}
}

impl From<CertificateValue> for HashedCertificateValue {
fn from(value: CertificateValue) -> HashedCertificateValue {
value.with_hash()
Expand Down
53 changes: 35 additions & 18 deletions linera-chain/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ use linera_execution::{
system::OpenChainConfig,
Message, MessageKind, Operation, SystemMessage, SystemOperation,
};
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::{
block::Timeout,
types::{
Certificate, CertificateValue, HashedCertificateValue, LiteCertificate,
CertificateValue, GenericCertificate, Has, Hashed, LiteCertificate,
ValidatedBlockCertificate,
},
ChainError,
Expand Down Expand Up @@ -424,16 +425,27 @@ struct ValueHashAndRound(CryptoHash, Round);

/// A vote on a statement from a validator.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Vote {
pub value: HashedCertificateValue,
#[serde(bound(deserialize = "T: DeserializeOwned + BcsHashable"))]
pub struct Vote<T> {
pub value: Hashed<T>,
pub round: Round,
pub validator: ValidatorName,
pub signature: Signature,
}

impl Vote {
impl Has<ChainId> for CertificateValue {
fn get(&self) -> &ChainId {
match self {
CertificateValue::ConfirmedBlock(confirmed) => &confirmed.inner().block.chain_id,
CertificateValue::ValidatedBlock(validated) => &validated.inner().block.chain_id,
CertificateValue::Timeout(Timeout { chain_id, .. }) => chain_id,
}
}
}

impl<T> Vote<T> {
/// Use signing key to create a signed object.
pub fn new(value: HashedCertificateValue, round: Round, key_pair: &KeyPair) -> Self {
pub fn new(value: Hashed<T>, round: Round, key_pair: &KeyPair) -> Self {
let hash_and_round = ValueHashAndRound(value.hash(), round);
let signature = Signature::new(&hash_and_round, key_pair);
Self {
Expand All @@ -445,7 +457,10 @@ impl Vote {
}

/// Returns the vote, with a `LiteValue` instead of the full value.
pub fn lite(&self) -> LiteVote {
pub fn lite(&self) -> LiteVote
where
T: Has<ChainId>,
{
LiteVote {
value: self.value.lite(),
round: self.round,
Expand All @@ -455,7 +470,7 @@ impl Vote {
}

/// Returns the value this vote is for.
pub fn value(&self) -> &CertificateValue {
pub fn value(&self) -> &T {
self.value.inner()
}
}
Expand All @@ -472,8 +487,9 @@ pub struct LiteVote {

impl LiteVote {
/// Returns the full vote, with the value, if it matches.
pub fn with_value(self, value: HashedCertificateValue) -> Option<Vote> {
if self.value != value.lite() {
#[cfg(with_testing)]
pub fn with_value<T>(self, value: Hashed<T>) -> Option<Vote<T>> {
if self.value.value_hash != value.hash() {
return None;
}
Some(Vote {
Expand Down Expand Up @@ -784,21 +800,21 @@ impl LiteVote {
}
}

pub struct SignatureAggregator<'a> {
pub struct SignatureAggregator<'a, T> {
committee: &'a Committee,
weight: u64,
used_validators: HashSet<ValidatorName>,
partial: Certificate,
partial: GenericCertificate<T>,
}

impl<'a> SignatureAggregator<'a> {
impl<'a, T> SignatureAggregator<'a, T> {
/// Starts aggregating signatures for the given value into a certificate.
pub fn new(value: HashedCertificateValue, round: Round, committee: &'a Committee) -> Self {
pub fn new(value: Hashed<T>, round: Round, committee: &'a Committee) -> Self {
Self {
committee,
weight: 0,
used_validators: HashSet::new(),
partial: Certificate::new(value, round, Vec::new()),
partial: GenericCertificate::new(value, round, Vec::new()),
}
}

Expand All @@ -809,7 +825,10 @@ impl<'a> SignatureAggregator<'a> {
&mut self,
validator: ValidatorName,
signature: Signature,
) -> Result<Option<Certificate>, ChainError> {
) -> Result<Option<GenericCertificate<T>>, ChainError>
where
T: Clone,
{
let hash_and_round = ValueHashAndRound(self.partial.hash(), self.partial.round);
signature.check(&hash_and_round, validator.0)?;
// Check that each validator only appears once.
Expand Down Expand Up @@ -876,8 +895,6 @@ impl BcsSignable for ProposalContent {}

impl BcsSignable for ValueHashAndRound {}

impl BcsHashable for CertificateValue {}

doc_scalar!(
MessageAction,
"Whether an incoming message is accepted or rejected."
Expand Down
21 changes: 15 additions & 6 deletions linera-chain/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ use rand_distr::{Distribution, WeightedAliasIndex};
use serde::{Deserialize, Serialize};

use crate::{
block::Timeout,
data_types::{Block, BlockExecutionOutcome, BlockProposal, LiteVote, ProposalContent, Vote},
types::{
CertificateValue, ConfirmedBlockCertificate, HashedCertificateValue, TimeoutCertificate,
Expand Down Expand Up @@ -125,13 +126,13 @@ pub struct ChainManager {
pub timeout: Option<TimeoutCertificate>,
/// Latest vote we have cast, to validate or confirm.
#[debug(skip_if = Option::is_none)]
pub pending: Option<Vote>,
pub pending: Option<Vote<CertificateValue>>,
/// Latest timeout vote we cast.
#[debug(skip_if = Option::is_none)]
pub timeout_vote: Option<Vote>,
pub timeout_vote: Option<Vote<Timeout>>,
/// Fallback vote we cast.
#[debug(skip_if = Option::is_none)]
pub fallback_vote: Option<Vote>,
pub fallback_vote: Option<Vote<Timeout>>,
/// The time after which we are ready to sign a timeout certificate for the current round.
#[debug(skip_if = Option::is_none)]
pub round_timeout: Option<Timestamp>,
Expand Down Expand Up @@ -221,7 +222,7 @@ impl ChainManager {
}

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

Expand Down Expand Up @@ -318,7 +319,11 @@ impl ChainManager {
}
}
let value = HashedCertificateValue::new_timeout(chain_id, height, epoch);
self.timeout_vote = Some(Vote::new(value, current_round, key_pair));
self.timeout_vote = Some(Vote::new(
value.try_into().expect("Timeout certificate"),
current_round,
key_pair,
));
true
}

Expand All @@ -341,7 +346,11 @@ impl ChainManager {
}
let value = HashedCertificateValue::new_timeout(chain_id, height, epoch);
let last_regular_round = Round::SingleLeader(u32::MAX);
self.fallback_vote = Some(Vote::new(value, last_regular_round, key_pair));
self.fallback_vote = Some(Vote::new(
value.try_into().expect("Timeout certificate"),
last_regular_round,
key_pair,
));
true
}

Expand Down
10 changes: 5 additions & 5 deletions linera-chain/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use linera_execution::{

use crate::{
data_types::{Block, BlockProposal, IncomingBundle, PostedMessage, SignatureAggregator, Vote},
types::{Certificate, HashedCertificateValue},
types::{GenericCertificate, HashedCertificateValue},
};

/// Creates a new child of the given block, with the same timestamp.
Expand Down Expand Up @@ -124,13 +124,13 @@ impl BlockTestExt for Block {
}
}

pub trait VoteTestExt: Sized {
pub trait VoteTestExt<T>: Sized {
/// Returns a certificate for a committee consisting only of this validator.
fn into_certificate(self) -> Certificate;
fn into_certificate(self) -> GenericCertificate<T>;
}

impl VoteTestExt for Vote {
fn into_certificate(self) -> Certificate {
impl<T: Clone> VoteTestExt<T> for Vote<T> {
fn into_certificate(self) -> GenericCertificate<T> {
let state = ValidatorState {
network_address: "".to_string(),
votes: 100,
Expand Down
Loading

0 comments on commit 92115c7

Please sign in to comment.