use std::{
borrow::Cow,
collections::{HashMap, HashSet},
};
use async_graphql::{Object, SimpleObject};
use linera_base::{
crypto::{BcsHashable, BcsSignable, CryptoHash, KeyPair, Signature},
data_types::{Amount, BlockHeight, Round, Timestamp},
doc_scalar, ensure,
identifiers::{
Account, ChainId, ChannelName, Destination, GenericApplicationId, MessageId, Owner,
},
};
use linera_execution::{
committee::{Committee, Epoch, ValidatorName},
BytecodeLocation, Message, MessageKind, Operation,
};
use serde::{de::Deserializer, Deserialize, Serialize};
use crate::ChainError;
#[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
}
pub fn has_only_rejected_messages(&self) -> bool {
self.operations.is_empty()
&& self
.incoming_messages
.iter()
.all(|message| message.action == MessageAction::Reject)
}
}
#[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: Round,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct IncomingMessage {
pub origin: Origin,
pub event: Event,
pub action: MessageAction,
}
#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum MessageAction {
Accept,
Reject,
}
impl IncomingMessage {
pub fn id(&self) -> MessageId {
MessageId {
chain_id: self.origin.sender,
height: self.event.height,
index: self.event.index,
}
}
}
#[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 grant: Amount,
pub refund_grant_to: Option<Account>,
pub kind: MessageKind,
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(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub struct MessageBundle {
pub height: BlockHeight,
pub epoch: Epoch,
pub timestamp: Timestamp,
pub hash: CryptoHash,
pub messages: Vec<(u32, OutgoingMessage)>,
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ChannelFullName {
pub application_id: GenericApplicationId,
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(with_testing, 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 grant: Amount,
pub refund_grant_to: Option<Account>,
pub kind: MessageKind,
pub message: Message,
}
impl OutgoingMessage {
pub fn has_destination(&self, medium: &Medium, recipient: ChainId) -> bool {
match (&self.destination, medium) {
(Destination::Recipient(_), Medium::Channel(_))
| (Destination::Subscribers(_), Medium::Direct) => false,
(Destination::Recipient(id), Medium::Direct) => *id == recipient,
(
Destination::Subscribers(dest_name),
Medium::Channel(ChannelFullName {
application_id,
name,
}),
) => *application_id == self.message.application_id() && name == dest_name,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct ExecutedBlock {
pub block: Block,
pub messages: Vec<OutgoingMessage>,
pub message_counts: Vec<u32>,
pub state_hash: CryptoHash,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct BlockExecutionOutcome {
pub messages: Vec<OutgoingMessage>,
pub message_counts: Vec<u32>,
pub state_hash: CryptoHash,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub enum CertificateValue {
ValidatedBlock {
executed_block: ExecutedBlock,
},
ConfirmedBlock {
executed_block: ExecutedBlock,
},
LeaderTimeout {
chain_id: ChainId,
height: BlockHeight,
epoch: Epoch,
},
}
#[Object]
impl CertificateValue {
#[graphql(derived(name = "executed_block"))]
async fn _executed_block(&self) -> Option<ExecutedBlock> {
self.executed_block().cloned()
}
async fn status(&self) -> String {
match self {
CertificateValue::ValidatedBlock { .. } => "validated".to_string(),
CertificateValue::ConfirmedBlock { .. } => "confirmed".to_string(),
CertificateValue::LeaderTimeout { .. } => "timeout".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, Round);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Vote {
pub value: HashedValue,
pub round: Round,
pub validator: ValidatorName,
pub signature: Signature,
}
impl Vote {
pub fn new(value: HashedValue, round: Round, 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(with_testing, derive(Eq, PartialEq))]
pub struct LiteVote {
pub value: LiteValue,
pub round: Round,
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(with_testing, derive(Eq, PartialEq))]
pub struct LiteCertificate<'a> {
pub value: LiteValue,
pub round: Round,
pub signatures: Cow<'a, [(ValidatorName, Signature)]>,
}
impl<'a> LiteCertificate<'a> {
pub fn new(
value: LiteValue,
round: Round,
mut signatures: Vec<(ValidatorName, Signature)>,
) -> Self {
if !is_strictly_ordered(&signatures) {
signatures.sort_by_key(|&(validator_name, _)| validator_name)
}
let signatures = Cow::Owned(signatures);
Self {
value,
round,
signatures,
}
}
pub fn try_from_votes(votes: impl IntoIterator<Item = LiteVote>) -> Option<Self> {
let mut votes = votes.into_iter();
let LiteVote {
value,
round,
validator,
signature,
} = votes.next()?;
let mut signatures = vec![(validator, signature)];
for vote in votes {
if vote.value.value_hash != value.value_hash || vote.round != round {
return None;
}
signatures.push((vote.validator, vote.signature));
}
Some(LiteCertificate::new(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(with_testing, derive(Eq, PartialEq))]
pub struct Certificate {
pub value: HashedValue,
pub round: Round,
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 {
match self {
CertificateValue::ConfirmedBlock { executed_block, .. }
| CertificateValue::ValidatedBlock { executed_block, .. } => {
executed_block.block.chain_id
}
CertificateValue::LeaderTimeout { chain_id, .. } => *chain_id,
}
}
pub fn height(&self) -> BlockHeight {
match self {
CertificateValue::ConfirmedBlock { executed_block, .. }
| CertificateValue::ValidatedBlock { executed_block, .. } => {
executed_block.block.height
}
CertificateValue::LeaderTimeout { height, .. } => *height,
}
}
pub fn epoch(&self) -> Epoch {
match self {
CertificateValue::ConfirmedBlock { executed_block, .. }
| CertificateValue::ValidatedBlock { executed_block, .. } => executed_block.block.epoch,
CertificateValue::LeaderTimeout { epoch, .. } => *epoch,
}
}
pub fn with_hash_unchecked(self, hash: CryptoHash) -> HashedValue {
HashedValue { value: self, hash }
}
pub fn has_message(&self, message_id: &MessageId) -> bool {
let Some(executed_block) = self.executed_block() else {
return false;
};
let Ok(index) = usize::try_from(message_id.index) else {
return false;
};
self.height() == message_id.height
&& self.chain_id() == message_id.chain_id
&& executed_block.messages.len() > index
}
pub fn is_confirmed(&self) -> bool {
matches!(self, CertificateValue::ConfirmedBlock { .. })
}
pub fn is_validated(&self) -> bool {
matches!(self, CertificateValue::ValidatedBlock { .. })
}
pub fn is_timeout(&self) -> bool {
matches!(self, CertificateValue::LeaderTimeout { .. })
}
#[cfg(with_testing)]
pub fn messages(&self) -> Option<&Vec<OutgoingMessage>> {
Some(&self.executed_block()?.messages)
}
pub fn executed_block(&self) -> Option<&ExecutedBlock> {
match self {
CertificateValue::ConfirmedBlock { executed_block, .. }
| CertificateValue::ValidatedBlock { executed_block, .. } => Some(executed_block),
CertificateValue::LeaderTimeout { .. } => None,
}
}
pub fn block(&self) -> Option<&Block> {
self.executed_block()
.map(|executed_block| &executed_block.block)
}
pub fn to_log_str(&self) -> &'static str {
match self {
CertificateValue::ConfirmedBlock { .. } => "confirmed_block",
CertificateValue::ValidatedBlock { .. } => "validated_block",
CertificateValue::LeaderTimeout { .. } => "leader_timeout",
}
}
}
impl Event {
pub fn is_skippable(&self) -> bool {
use MessageKind::*;
match self.kind {
Protected | Tracked => false,
Simple | Bouncing => self.grant == Amount::ZERO,
}
}
pub fn is_protected(&self) -> bool {
matches!(self.kind, MessageKind::Protected)
}
pub fn is_tracked(&self) -> bool {
matches!(self.kind, MessageKind::Tracked)
}
pub fn is_bouncing(&self) -> bool {
matches!(self.kind, MessageKind::Bouncing)
}
}
impl ExecutedBlock {
pub fn message_id_for_operation(
&self,
operation_index: usize,
message_index: u32,
) -> Option<MessageId> {
let block = &self.block;
let transaction_index = block.incoming_messages.len().checked_add(operation_index)?;
let first_message_index = match transaction_index.checked_sub(1) {
None => 0,
Some(index) => *self.message_counts.get(index)?,
};
let index = first_message_index.checked_add(message_index)?;
let next_transaction_index = *self.message_counts.get(transaction_index)?;
if index < next_transaction_index {
Some(self.message_id(index))
} else {
None
}
}
pub fn message_by_id(&self, message_id: &MessageId) -> Option<&OutgoingMessage> {
let MessageId {
chain_id,
height,
index,
} = message_id;
if self.block.chain_id != *chain_id || self.block.height != *height {
return None;
}
self.messages.get(usize::try_from(*index).ok()?)
}
fn message_id(&self, index: u32) -> MessageId {
MessageId {
chain_id: self.block.chain_id,
height: self.block.height,
index,
}
}
}
impl BlockExecutionOutcome {
pub fn with(self, block: Block) -> ExecutedBlock {
let BlockExecutionOutcome {
messages,
message_counts,
state_hash,
} = self;
ExecutedBlock {
block,
messages,
message_counts,
state_hash,
}
}
}
impl HashedValue {
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 new_leader_timeout(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> HashedValue {
CertificateValue::LeaderTimeout {
chain_id,
height,
epoch,
}
.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 validated_to_confirmed(&self) -> Option<HashedValue> {
match &self.value {
CertificateValue::ValidatedBlock { executed_block } => Some(
CertificateValue::ConfirmedBlock {
executed_block: executed_block.clone(),
}
.into(),
),
CertificateValue::ConfirmedBlock { .. } | CertificateValue::LeaderTimeout { .. } => {
None
}
}
}
pub fn inner(&self) -> &CertificateValue {
&self.value
}
pub fn into_inner(self) -> CertificateValue {
self.value
}
}
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: Round, 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: Round, 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: Round,
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: Round,
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()
}
pub fn message_bundle_for(&self, medium: &Medium, recipient: ChainId) -> Option<MessageBundle> {
let executed_block = self.value().executed_block()?;
let messages = (0u32..)
.zip(&executed_block.messages)
.filter(|(_, message)| message.has_destination(medium, recipient))
.map(|(idx, message)| (idx, message.clone()))
.collect();
Some(MessageBundle {
height: executed_block.block.height,
epoch: executed_block.block.epoch,
timestamp: executed_block.block.timestamp,
hash: self.hash(),
messages,
})
}
}
fn check_signatures(
value: &LiteValue,
round: Round,
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!(
MessageAction,
"Whether an incoming message is accepted or rejected"
);
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."
);