use crate::ChainError;
use async_graphql::{Object, SimpleObject};
use linera_base::{
crypto::{BcsHashable, BcsSignable, CryptoHash, KeyPair, Signature},
data_types::{BlockHeight, RoundNumber, Timestamp},
doc_scalar, ensure,
identifiers::{ChainId, ChannelName, Destination, MessageId, Owner},
};
use linera_execution::{
committee::{Committee, Epoch, ValidatorName},
ApplicationId, BytecodeLocation, Message, Operation,
};
use serde::{de::Deserializer, Deserialize, Serialize};
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
};
#[cfg(test)]
#[path = "unit_tests/data_types_tests.rs"]
mod data_types_tests;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct Block {
pub chain_id: ChainId,
pub epoch: Epoch,
pub incoming_messages: Vec<IncomingMessage>,
pub operations: Vec<Operation>,
pub height: BlockHeight,
pub timestamp: Timestamp,
pub authenticated_signer: Option<Owner>,
pub previous_block_hash: Option<CryptoHash>,
}
impl Block {
pub fn bytecode_locations(&self) -> HashMap<BytecodeLocation, ChainId> {
let mut locations = HashMap::new();
for message in &self.incoming_messages {
if let Message::System(sys_message) = &message.event.message {
locations.extend(
sys_message
.bytecode_locations(message.event.certificate_hash)
.map(|location| (location, message.origin.sender)),
);
}
}
locations
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, SimpleObject)]
pub struct ChainAndHeight {
pub chain_id: ChainId,
pub height: BlockHeight,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct BlockAndRound {
pub block: Block,
pub round: RoundNumber,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct IncomingMessage {
pub origin: Origin,
pub event: Event,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct Event {
pub certificate_hash: CryptoHash,
pub height: BlockHeight,
pub index: u32,
pub authenticated_signer: Option<Owner>,
pub timestamp: Timestamp,
pub message: Message,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
pub struct Origin {
pub sender: ChainId,
pub medium: Medium,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
pub struct Target {
pub recipient: ChainId,
pub medium: Medium,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ChannelFullName {
pub application_id: ApplicationId,
pub name: ChannelName,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
pub enum Medium {
Direct,
Channel(ChannelFullName),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct BlockProposal {
pub content: BlockAndRound,
pub owner: Owner,
pub signature: Signature,
pub blobs: Vec<HashedValue>,
pub validated: Option<Certificate>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct OutgoingMessage {
pub destination: Destination,
pub authenticated_signer: Option<Owner>,
pub message: Message,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct ExecutedBlock {
pub block: Block,
pub messages: Vec<OutgoingMessage>,
pub state_hash: CryptoHash,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub enum CertificateValue {
ValidatedBlock { executed_block: ExecutedBlock },
ConfirmedBlock { executed_block: ExecutedBlock },
}
#[Object]
impl CertificateValue {
#[graphql(derived(name = "executed_block"))]
async fn _executed_block(&self) -> ExecutedBlock {
self.executed_block().clone()
}
async fn status(&self) -> String {
match self {
CertificateValue::ValidatedBlock { .. } => "validated".to_string(),
CertificateValue::ConfirmedBlock { .. } => "confirmed".to_string(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct HashedValue {
value: CertificateValue,
hash: CryptoHash,
}
#[Object]
impl HashedValue {
#[graphql(derived(name = "hash"))]
async fn _hash(&self) -> CryptoHash {
self.hash
}
#[graphql(derived(name = "value"))]
async fn _value(&self) -> CertificateValue {
self.value.clone()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct LiteValue {
pub value_hash: CryptoHash,
pub chain_id: ChainId,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
struct ValueHashAndRound(CryptoHash, RoundNumber);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Vote {
pub value: HashedValue,
pub round: RoundNumber,
pub validator: ValidatorName,
pub signature: Signature,
}
impl Vote {
pub fn new(value: HashedValue, round: RoundNumber, key_pair: &KeyPair) -> Self {
let hash_and_round = ValueHashAndRound(value.hash, round);
let signature = Signature::new(&hash_and_round, key_pair);
Self {
value,
round,
validator: ValidatorName(key_pair.public()),
signature,
}
}
pub fn lite(&self) -> LiteVote {
LiteVote {
value: self.value.lite(),
round: self.round,
validator: self.validator,
signature: self.signature,
}
}
pub fn value(&self) -> &CertificateValue {
self.value.inner()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct LiteVote {
pub value: LiteValue,
pub round: RoundNumber,
pub validator: ValidatorName,
pub signature: Signature,
}
impl LiteVote {
pub fn with_value(self, value: HashedValue) -> Option<Vote> {
if self.value != value.lite() {
return None;
}
Some(Vote {
value,
round: self.round,
validator: self.validator,
signature: self.signature,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct LiteCertificate<'a> {
pub value: LiteValue,
pub round: RoundNumber,
pub signatures: Cow<'a, [(ValidatorName, Signature)]>,
}
impl<'a> LiteCertificate<'a> {
pub fn new(
value: LiteValue,
round: RoundNumber,
signatures: Vec<(ValidatorName, Signature)>,
) -> Self {
let signatures = Cow::Owned(signatures);
Self {
value,
round,
signatures,
}
}
pub fn check(self, committee: &Committee) -> Result<LiteValue, ChainError> {
check_signatures(&self.value, self.round, &self.signatures, committee)?;
Ok(self.value)
}
pub fn with_value(self, value: HashedValue) -> Option<Certificate> {
if self.value.chain_id != value.inner().chain_id() || self.value.value_hash != value.hash()
{
return None;
}
Some(Certificate {
value,
round: self.round,
signatures: self.signatures.into_owned(),
})
}
pub fn cloned(&self) -> LiteCertificate<'static> {
LiteCertificate {
value: self.value.clone(),
round: self.round,
signatures: Cow::Owned(self.signatures.clone().into_owned()),
}
}
}
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(any(test, feature = "test"), derive(Eq, PartialEq))]
pub struct Certificate {
pub value: HashedValue,
pub round: RoundNumber,
signatures: Vec<(ValidatorName, Signature)>,
}
impl Origin {
pub fn chain(sender: ChainId) -> Self {
Self {
sender,
medium: Medium::Direct,
}
}
pub fn channel(sender: ChainId, name: ChannelFullName) -> Self {
Self {
sender,
medium: Medium::Channel(name),
}
}
}
impl Target {
pub fn chain(recipient: ChainId) -> Self {
Self {
recipient,
medium: Medium::Direct,
}
}
pub fn channel(recipient: ChainId, name: ChannelFullName) -> Self {
Self {
recipient,
medium: Medium::Channel(name),
}
}
}
impl Serialize for HashedValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.value.serialize(serializer)
}
}
impl<'a> Deserialize<'a> for HashedValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
Ok(CertificateValue::deserialize(deserializer)?.into())
}
}
impl From<CertificateValue> for HashedValue {
fn from(value: CertificateValue) -> HashedValue {
let hash = CryptoHash::new(&value);
HashedValue { value, hash }
}
}
impl From<HashedValue> for CertificateValue {
fn from(hv: HashedValue) -> CertificateValue {
hv.value
}
}
impl CertificateValue {
pub fn chain_id(&self) -> ChainId {
self.executed_block().block.chain_id
}
pub fn height(&self) -> BlockHeight {
self.executed_block().block.height
}
pub fn epoch(&self) -> Epoch {
self.executed_block().block.epoch
}
pub fn with_hash_unchecked(self, hash: CryptoHash) -> HashedValue {
HashedValue { value: self, hash }
}
pub fn has_message(&self, message_id: &MessageId) -> bool {
self.height() == message_id.height
&& self.chain_id() == message_id.chain_id
&& self.executed_block().messages.len()
> usize::try_from(message_id.index).unwrap_or(usize::MAX)
}
pub fn nth_last_message_id(&self, n: u32) -> Option<MessageId> {
if n == 0 {
return None;
}
let message_count = self.executed_block().messages.len();
Some(MessageId {
chain_id: self.chain_id(),
height: self.height(),
index: u32::try_from(message_count).ok()?.checked_sub(n)?,
})
}
pub fn is_confirmed(&self) -> bool {
matches!(self, CertificateValue::ConfirmedBlock { .. })
}
pub fn is_validated(&self) -> bool {
matches!(self, CertificateValue::ValidatedBlock { .. })
}
#[cfg(any(test, feature = "test"))]
pub fn messages(&self) -> &Vec<OutgoingMessage> {
&self.executed_block().messages
}
pub fn executed_block(&self) -> &ExecutedBlock {
match self {
CertificateValue::ConfirmedBlock { executed_block, .. }
| CertificateValue::ValidatedBlock { executed_block, .. } => executed_block,
}
}
}
impl HashedValue {
#[cfg(any(test, feature = "test"))]
pub fn new_confirmed(executed_block: ExecutedBlock) -> HashedValue {
CertificateValue::ConfirmedBlock { executed_block }.into()
}
pub fn new_validated(executed_block: ExecutedBlock) -> HashedValue {
CertificateValue::ValidatedBlock { executed_block }.into()
}
pub fn hash(&self) -> CryptoHash {
self.hash
}
pub fn lite(&self) -> LiteValue {
LiteValue {
value_hash: self.hash(),
chain_id: self.value.chain_id(),
}
}
pub fn into_confirmed(self) -> HashedValue {
match self.value {
value @ CertificateValue::ConfirmedBlock { .. } => HashedValue {
hash: self.hash,
value,
},
CertificateValue::ValidatedBlock { executed_block } => {
CertificateValue::ConfirmedBlock { executed_block }.into()
}
}
}
pub fn inner(&self) -> &CertificateValue {
&self.value
}
pub fn into_inner(self) -> CertificateValue {
self.value
}
pub fn nth_last_message_id(&self, n: u32) -> Option<MessageId> {
self.value.nth_last_message_id(n)
}
}
impl BlockProposal {
pub fn new(
content: BlockAndRound,
secret: &KeyPair,
blobs: Vec<HashedValue>,
validated: Option<Certificate>,
) -> Self {
let signature = Signature::new(&content, secret);
Self {
content,
owner: secret.public().into(),
signature,
blobs,
validated,
}
}
}
impl LiteVote {
pub fn new(value: LiteValue, round: RoundNumber, key_pair: &KeyPair) -> Self {
let hash_and_round = ValueHashAndRound(value.value_hash, round);
let signature = Signature::new(&hash_and_round, key_pair);
Self {
value,
round,
validator: ValidatorName(key_pair.public()),
signature,
}
}
pub fn check(&self) -> Result<(), ChainError> {
let hash_and_round = ValueHashAndRound(self.value.value_hash, self.round);
Ok(self.signature.check(&hash_and_round, self.validator.0)?)
}
}
pub struct SignatureAggregator<'a> {
committee: &'a Committee,
weight: u64,
used_validators: HashSet<ValidatorName>,
partial: Certificate,
}
impl<'a> SignatureAggregator<'a> {
pub fn new(value: HashedValue, round: RoundNumber, committee: &'a Committee) -> Self {
Self {
committee,
weight: 0,
used_validators: HashSet::new(),
partial: Certificate {
value,
round,
signatures: Vec::new(),
},
}
}
pub fn append(
&mut self,
validator: ValidatorName,
signature: Signature,
) -> Result<Option<Certificate>, ChainError> {
let hash_and_round = ValueHashAndRound(self.partial.hash(), self.partial.round);
signature.check(&hash_and_round, validator.0)?;
ensure!(
!self.used_validators.contains(&validator),
ChainError::CertificateValidatorReuse
);
self.used_validators.insert(validator);
let voting_rights = self.committee.weight(&validator);
ensure!(voting_rights > 0, ChainError::InvalidSigner);
self.weight += voting_rights;
self.partial.add_signature((validator, signature));
if self.weight >= self.committee.quorum_threshold() {
self.weight = 0; Ok(Some(self.partial.clone()))
} else {
Ok(None)
}
}
}
fn is_strictly_ordered(values: &[(ValidatorName, Signature)]) -> bool {
values.windows(2).all(|pair| pair[0].0 < pair[1].0)
}
impl<'de> Deserialize<'de> for Certificate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
#[serde(rename = "Certificate")]
struct CertificateHelper {
value: HashedValue,
round: RoundNumber,
signatures: Vec<(ValidatorName, Signature)>,
}
let helper: CertificateHelper = Deserialize::deserialize(deserializer)?;
if !is_strictly_ordered(&helper.signatures) {
Err(serde::de::Error::custom("Vector is not strictly sorted"))
} else {
Ok(Self {
value: helper.value,
round: helper.round,
signatures: helper.signatures,
})
}
}
}
impl Certificate {
pub fn new(
value: HashedValue,
round: RoundNumber,
mut signatures: Vec<(ValidatorName, Signature)>,
) -> Self {
if !is_strictly_ordered(&signatures) {
signatures.sort_by_key(|&(validator_name, _)| validator_name)
}
Self {
value,
round,
signatures,
}
}
pub fn signatures(&self) -> &Vec<(ValidatorName, Signature)> {
&self.signatures
}
pub fn add_signature(
&mut self,
signature: (ValidatorName, Signature),
) -> &Vec<(ValidatorName, Signature)> {
let index = self
.signatures
.binary_search_by(|(name, _)| name.cmp(&signature.0))
.unwrap_or_else(std::convert::identity);
self.signatures.insert(index, signature);
&self.signatures
}
pub fn check<'a>(&'a self, committee: &Committee) -> Result<&'a HashedValue, ChainError> {
check_signatures(&self.lite_value(), self.round, &self.signatures, committee)?;
Ok(&self.value)
}
pub fn lite_certificate(&self) -> LiteCertificate {
LiteCertificate {
value: self.lite_value(),
round: self.round,
signatures: Cow::Borrowed(&self.signatures),
}
}
pub fn lite_value(&self) -> LiteValue {
LiteValue {
value_hash: self.hash(),
chain_id: self.value().chain_id(),
}
}
pub fn value(&self) -> &CertificateValue {
&self.value.value
}
pub fn hash(&self) -> CryptoHash {
self.value.hash
}
pub fn is_signed_by(&self, validator_name: &ValidatorName) -> bool {
self.signatures
.binary_search_by(|(name, _)| name.cmp(validator_name))
.is_ok()
}
}
fn check_signatures(
value: &LiteValue,
round: RoundNumber,
signatures: &[(ValidatorName, Signature)],
committee: &Committee,
) -> Result<(), ChainError> {
let mut weight = 0;
let mut used_validators = HashSet::new();
for (validator, _) in signatures {
ensure!(
!used_validators.contains(validator),
ChainError::CertificateValidatorReuse
);
used_validators.insert(*validator);
let voting_rights = committee.weight(validator);
ensure!(voting_rights > 0, ChainError::InvalidSigner);
weight += voting_rights;
}
ensure!(
weight >= committee.quorum_threshold(),
ChainError::CertificateRequiresQuorum
);
let hash_and_round = ValueHashAndRound(value.value_hash, round);
Signature::verify_batch(&hash_and_round, signatures.iter().map(|(v, s)| (&v.0, s)))?;
Ok(())
}
impl BcsSignable for BlockAndRound {}
impl BcsSignable for ValueHashAndRound {}
impl BcsHashable for CertificateValue {}
doc_scalar!(
ChannelFullName,
"A channel name together with its application id"
);
doc_scalar!(
Event,
"A message together with non replayable information to ensure uniqueness in a particular inbox"
);
doc_scalar!(
Medium,
"The origin of a message coming from a particular chain. Used to identify each inbox."
);
doc_scalar!(
Origin,
"The origin of a message, relative to a particular application. Used to identify each inbox."
);
doc_scalar!(
Target,
"The target of a message, relative to a particular application. Used to identify each outbox."
);