Skip to main content

linera_chain/certificate/
confirmed.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{borrow::Cow, ops::Deref};
6
7use allocative::Allocative;
8use linera_base::{
9    crypto::{CryptoHash, ValidatorPublicKey, ValidatorSignature},
10    data_types::{Epoch, Round},
11    identifiers::ChainId,
12};
13use linera_execution::committee::Committee;
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15
16use super::{generic::GenericCertificate, Certificate, Certified, LiteCertificate};
17use crate::{
18    block::{Block, ConfirmedBlock, ConversionError},
19    data_types::MessageBundle,
20    justification::JustificationChain,
21    ChainError,
22};
23
24/// The serialized representation of a [`ConfirmedBlockCertificate`]. Deriving the
25/// (de)serialization on this single type keeps both directions in sync and free of manual field
26/// bookkeeping; the manual impls only add the strictly-ordered-signatures invariant.
27#[derive(serde::Serialize, serde::Deserialize)]
28#[serde(rename = "ConfirmedBlockCertificate")]
29struct Repr<'a> {
30    value: Cow<'a, ConfirmedBlock>,
31    round: Round,
32    first_round: bool,
33    justification_commitment: Option<CryptoHash>,
34    signatures: Cow<'a, [(ValidatorPublicKey, ValidatorSignature)]>,
35    justification: Cow<'a, JustificationChain>,
36}
37
38/// Certificate for a [`ConfirmedBlock`] instance, certified in some round by a quorum of
39/// `ConfirmedBlock` votes (which carry no unlocking round).
40///
41/// A confirmed block certificate means that the block is finalized: it is the agreed block at
42/// that height on that chain. It wraps the signed quorum and carries the full chain of validated
43/// quorums for the block, making it self-contained evidence for fault attribution.
44#[derive(Clone, Debug, Allocative)]
45#[cfg_attr(with_testing, derive(Eq, PartialEq))]
46pub struct ConfirmedBlockCertificate {
47    /// The signed quorum of `ConfirmedBlock` votes. Its unlocking round is always `None`.
48    quorum: GenericCertificate<ConfirmedBlock>,
49    /// The full chain of validated quorums for the block, rising from the grounding round to its
50    /// top link in the round the block was confirmed. Empty iff the block was confirmed in the
51    /// chain's first round.
52    justification: JustificationChain,
53}
54
55impl ConfirmedBlockCertificate {
56    /// Creates a confirmed block certificate with an empty justification chain (first round).
57    pub fn new(
58        value: ConfirmedBlock,
59        round: Round,
60        signatures: Vec<(ValidatorPublicKey, ValidatorSignature)>,
61    ) -> Self {
62        Self {
63            quorum: GenericCertificate::new(value, round, signatures),
64            justification: JustificationChain::default(),
65        }
66    }
67
68    /// Creates a confirmed block certificate from a signed quorum and its justification chain.
69    pub fn from_parts(
70        quorum: GenericCertificate<ConfirmedBlock>,
71        justification: JustificationChain,
72    ) -> Self {
73        Self {
74            quorum,
75            justification,
76        }
77    }
78
79    /// Returns the signed quorum of `ConfirmedBlock` votes.
80    pub fn quorum(&self) -> &GenericCertificate<ConfirmedBlock> {
81        &self.quorum
82    }
83
84    /// Returns the full chain of validated quorums for the block.
85    pub fn justification(&self) -> &JustificationChain {
86        &self.justification
87    }
88
89    /// Consumes this certificate, returning the signed quorum and the justification chain.
90    pub fn into_parts(self) -> (GenericCertificate<ConfirmedBlock>, JustificationChain) {
91        (self.quorum, self.justification)
92    }
93
94    /// Returns the round in which the value was certified.
95    pub fn round(&self) -> Round {
96        self.quorum.round()
97    }
98
99    /// Consumes this certificate, returning the confirmed block it contains.
100    pub fn into_value(self) -> ConfirmedBlock {
101        self.quorum.into_value()
102    }
103
104    /// Consumes this certificate, returning the confirmed block it contains.
105    pub fn into_inner(self) -> ConfirmedBlock {
106        self.quorum.into_inner()
107    }
108
109    /// Verifies the certificate's signatures and justification chain: the quorum of
110    /// `ConfirmedBlock` votes, that the justification chain is itself a valid chain of quorums,
111    /// and that — if present — it heads at the confirmation round. Delegates to
112    /// [`LiteCertificate::check`], the single source of truth for the quorum-to-chain binding.
113    ///
114    /// An *absent* chain is accepted only when the quorum carries the first-round attestation
115    /// (a fast-round confirmation always does, since the fast round is a chain's first round):
116    /// the attestation asserts that no lower round exists, so any conflicting confirmation is
117    /// attributable without this block's chain — via the other block's chain if it is higher, or
118    /// via the attestation itself if it is lower (see `EquivocationProof::FirstRoundViolation`).
119    /// The attestation is also sanity-checked against the round — it can only be set in a round
120    /// that could be a chain's first one. Whether it is the *actual* first round depends on the
121    /// chain's ownership at that height, which a committee-only check cannot know; that, and the
122    /// obligation of a later-round block to carry its chain, rest on honest block construction
123    /// (see `finalize_block`), full-execution verification, and the per-signature justifications
124    /// retained by the commitment scheme.
125    pub fn check(&self, committee: &Committee) -> Result<(), ChainError> {
126        self.lite_certificate().check(committee)?;
127        Ok(())
128    }
129
130    /// Returns the [`LiteCertificate`] corresponding to this certificate, borrowing the chain.
131    pub fn lite_certificate(&self) -> LiteCertificate<'_> {
132        let mut lite = self.quorum.lite_certificate_without_justification();
133        lite.justification = Cow::Borrowed(&self.justification);
134        lite
135    }
136}
137
138impl Deref for ConfirmedBlockCertificate {
139    type Target = GenericCertificate<ConfirmedBlock>;
140
141    fn deref(&self) -> &Self::Target {
142        &self.quorum
143    }
144}
145
146impl Certified for ConfirmedBlockCertificate {
147    type Value = ConfirmedBlock;
148
149    fn value(&self) -> &ConfirmedBlock {
150        self.quorum.value()
151    }
152
153    fn round(&self) -> Round {
154        ConfirmedBlockCertificate::round(self)
155    }
156
157    fn unlocking_round(&self) -> Option<Round> {
158        self.quorum.unlocking_round()
159    }
160
161    fn signatures(&self) -> &Vec<(ValidatorPublicKey, ValidatorSignature)> {
162        self.quorum.signatures()
163    }
164
165    fn lite_certificate(&self) -> LiteCertificate<'_> {
166        ConfirmedBlockCertificate::lite_certificate(self)
167    }
168
169    fn check(&self, committee: &Committee) -> Result<(), ChainError> {
170        ConfirmedBlockCertificate::check(self, committee)
171    }
172}
173
174impl GenericCertificate<ConfirmedBlock> {
175    /// Returns reference to the `Block` contained in this certificate.
176    pub fn block(&self) -> &Block {
177        self.inner().block()
178    }
179
180    /// Returns the bundles of messages sent to the specified recipient.
181    /// Messages originating from different transactions of the original block
182    /// are kept in separate bundles.
183    pub fn message_bundles_for(
184        &self,
185        recipient: ChainId,
186    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
187        let certificate_hash = self.hash();
188        self.block()
189            .message_bundles_for(recipient, certificate_hash)
190    }
191
192    /// Returns the total number of outgoing messages in the certified block.
193    #[cfg(with_testing)]
194    pub fn outgoing_message_count(&self) -> usize {
195        self.block().messages().iter().map(Vec::len).sum()
196    }
197}
198
199impl TryFrom<Certificate> for ConfirmedBlockCertificate {
200    type Error = ConversionError;
201
202    fn try_from(cert: Certificate) -> Result<Self, Self::Error> {
203        match cert {
204            Certificate::Confirmed(confirmed) => Ok(confirmed),
205            _ => Err(ConversionError::ConfirmedBlock),
206        }
207    }
208}
209
210impl From<ConfirmedBlockCertificate> for Certificate {
211    fn from(cert: ConfirmedBlockCertificate) -> Certificate {
212        Certificate::Confirmed(cert)
213    }
214}
215
216impl From<&ConfirmedBlockCertificate> for Certificate {
217    fn from(cert: &ConfirmedBlockCertificate) -> Certificate {
218        Certificate::Confirmed(cert.clone())
219    }
220}
221
222impl Serialize for ConfirmedBlockCertificate {
223    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224    where
225        S: Serializer,
226    {
227        Repr {
228            value: Cow::Borrowed(self.quorum.inner()),
229            round: self.quorum.round(),
230            first_round: self.quorum.first_round(),
231            justification_commitment: self.quorum.justification_commitment(),
232            signatures: Cow::Borrowed(self.quorum.signatures().as_slice()),
233            justification: Cow::Borrowed(&self.justification),
234        }
235        .serialize(serializer)
236    }
237}
238
239impl<'de> Deserialize<'de> for ConfirmedBlockCertificate {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: Deserializer<'de>,
243    {
244        let helper = Repr::deserialize(deserializer)?;
245        let signatures = helper.signatures.into_owned();
246        if !crate::data_types::is_strictly_ordered(&signatures) {
247            Err(serde::de::Error::custom("Vector is not strictly sorted"))
248        } else {
249            Ok(Self::from_parts(
250                GenericCertificate::new_with_payload(
251                    helper.value.into_owned(),
252                    helper.round,
253                    None,
254                    helper.first_round,
255                    helper.justification_commitment,
256                    signatures,
257                ),
258                helper.justification.into_owned(),
259            ))
260        }
261    }
262}