consensus_core/
block.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    fmt,
7    hash::{Hash, Hasher},
8    ops::Deref,
9    sync::Arc,
10};
11
12use bytes::Bytes;
13use consensus_config::{
14    AuthorityIndex, DIGEST_LENGTH, DefaultHashFunction, Epoch, ProtocolKeyPair,
15    ProtocolKeySignature, ProtocolPublicKey,
16};
17use enum_dispatch::enum_dispatch;
18use fastcrypto::hash::{Digest, HashFunction};
19use serde::{Deserialize, Serialize};
20use shared_crypto::intent::{Intent, IntentMessage, IntentScope};
21
22use crate::{
23    commit::CommitVote,
24    context::Context,
25    ensure,
26    error::{ConsensusError, ConsensusResult},
27};
28
29/// Round number of a block.
30pub type Round = u32;
31
32pub(crate) const GENESIS_ROUND: Round = 0;
33
34/// Block proposal as epoch UNIX timestamp in milliseconds.
35pub type BlockTimestampMs = u64;
36
37/// IOTA transaction in serialised bytes
38#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Default, Debug)]
39pub struct Transaction {
40    data: Bytes,
41}
42
43impl Transaction {
44    pub fn new(data: Vec<u8>) -> Self {
45        Self { data: data.into() }
46    }
47
48    pub fn data(&self) -> &[u8] {
49        &self.data
50    }
51
52    pub fn into_data(self) -> Bytes {
53        self.data
54    }
55}
56
57/// A block includes references to previous round blocks and transactions that
58/// the authority considers valid.
59/// Well behaved authorities produce at most one block per round, but malicious
60/// authorities can equivocate.
61#[derive(Clone, Deserialize, Serialize)]
62#[enum_dispatch(BlockAPI)]
63pub enum Block {
64    V1(BlockV1),
65}
66
67#[enum_dispatch]
68pub trait BlockAPI {
69    fn epoch(&self) -> Epoch;
70    fn round(&self) -> Round;
71    fn author(&self) -> AuthorityIndex;
72    fn slot(&self) -> Slot;
73    fn timestamp_ms(&self) -> BlockTimestampMs;
74    fn ancestors(&self) -> &[BlockRef];
75    fn transactions(&self) -> &[Transaction];
76    fn commit_votes(&self) -> &[CommitVote];
77    fn misbehavior_reports(&self) -> &[MisbehaviorReport];
78}
79
80#[derive(Clone, Default, Deserialize, Serialize)]
81pub struct BlockV1 {
82    epoch: Epoch,
83    round: Round,
84    author: AuthorityIndex,
85    // TODO: during verification ensure that timestamp_ms >= ancestors.timestamp
86    timestamp_ms: BlockTimestampMs,
87    ancestors: Vec<BlockRef>,
88    transactions: Vec<Transaction>,
89    commit_votes: Vec<CommitVote>,
90    misbehavior_reports: Vec<MisbehaviorReport>,
91}
92
93impl BlockV1 {
94    pub(crate) fn new(
95        epoch: Epoch,
96        round: Round,
97        author: AuthorityIndex,
98        timestamp_ms: BlockTimestampMs,
99        ancestors: Vec<BlockRef>,
100        transactions: Vec<Transaction>,
101        commit_votes: Vec<CommitVote>,
102        misbehavior_reports: Vec<MisbehaviorReport>,
103    ) -> BlockV1 {
104        Self {
105            epoch,
106            round,
107            author,
108            timestamp_ms,
109            ancestors,
110            transactions,
111            commit_votes,
112            misbehavior_reports,
113        }
114    }
115
116    fn genesis_block(epoch: Epoch, author: AuthorityIndex) -> Self {
117        Self {
118            epoch,
119            round: GENESIS_ROUND,
120            author,
121            timestamp_ms: 0,
122            ancestors: vec![],
123            transactions: vec![],
124            commit_votes: vec![],
125            misbehavior_reports: vec![],
126        }
127    }
128}
129
130impl BlockAPI for BlockV1 {
131    fn epoch(&self) -> Epoch {
132        self.epoch
133    }
134
135    fn round(&self) -> Round {
136        self.round
137    }
138
139    fn author(&self) -> AuthorityIndex {
140        self.author
141    }
142
143    fn slot(&self) -> Slot {
144        Slot::new(self.round, self.author)
145    }
146
147    fn timestamp_ms(&self) -> BlockTimestampMs {
148        self.timestamp_ms
149    }
150
151    fn ancestors(&self) -> &[BlockRef] {
152        &self.ancestors
153    }
154
155    fn transactions(&self) -> &[Transaction] {
156        &self.transactions
157    }
158
159    fn commit_votes(&self) -> &[CommitVote] {
160        &self.commit_votes
161    }
162
163    fn misbehavior_reports(&self) -> &[MisbehaviorReport] {
164        &self.misbehavior_reports
165    }
166}
167
168/// `BlockRef` uniquely identifies a `VerifiedBlock` via `digest`. It also
169/// contains the slot info (round and author) so it can be used in logic such as
170/// aggregating stakes for a round.
171#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
172pub struct BlockRef {
173    pub round: Round,
174    pub author: AuthorityIndex,
175    pub digest: BlockDigest,
176}
177
178impl BlockRef {
179    pub const MIN: Self = Self {
180        round: 0,
181        author: AuthorityIndex::MIN,
182        digest: BlockDigest::MIN,
183    };
184
185    pub const MAX: Self = Self {
186        round: u32::MAX,
187        author: AuthorityIndex::MAX,
188        digest: BlockDigest::MAX,
189    };
190
191    pub fn new(round: Round, author: AuthorityIndex, digest: BlockDigest) -> Self {
192        Self {
193            round,
194            author,
195            digest,
196        }
197    }
198}
199
200// TODO: re-evaluate formats for production debugging.
201impl fmt::Display for BlockRef {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
203        write!(f, "B{}({},{})", self.round, self.author, self.digest)
204    }
205}
206
207impl fmt::Debug for BlockRef {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
209        write!(f, "B{}({},{:?})", self.round, self.author, self.digest)
210    }
211}
212
213impl Hash for BlockRef {
214    fn hash<H: Hasher>(&self, state: &mut H) {
215        state.write(&self.digest.0[..8]);
216    }
217}
218
219/// Digest of a `VerifiedBlock` or verified `SignedBlock`, which covers the
220/// `Block` and its signature.
221///
222/// Note: the signature algorithm is assumed to be non-malleable, so it is
223/// impossible for another party to create an altered but valid signature,
224/// producing an equivocating `BlockDigest`.
225#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
226pub struct BlockDigest([u8; consensus_config::DIGEST_LENGTH]);
227
228impl BlockDigest {
229    /// Lexicographic min & max digest.
230    pub const MIN: Self = Self([u8::MIN; consensus_config::DIGEST_LENGTH]);
231    pub const MAX: Self = Self([u8::MAX; consensus_config::DIGEST_LENGTH]);
232}
233
234impl Hash for BlockDigest {
235    fn hash<H: Hasher>(&self, state: &mut H) {
236        state.write(&self.0[..8]);
237    }
238}
239
240impl From<BlockDigest> for Digest<{ DIGEST_LENGTH }> {
241    fn from(hd: BlockDigest) -> Self {
242        Digest::new(hd.0)
243    }
244}
245
246impl fmt::Display for BlockDigest {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
248        write!(
249            f,
250            "{}",
251            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, self.0)
252                .get(0..4)
253                .ok_or(fmt::Error)?
254        )
255    }
256}
257
258impl fmt::Debug for BlockDigest {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
260        write!(
261            f,
262            "{}",
263            base64::Engine::encode(&base64::engine::general_purpose::STANDARD, self.0)
264        )
265    }
266}
267
268impl AsRef<[u8]> for BlockDigest {
269    fn as_ref(&self) -> &[u8] {
270        &self.0
271    }
272}
273
274/// Slot is the position of blocks in the DAG. It can contain 0, 1 or multiple
275/// blocks from the same authority at the same round.
276#[derive(Clone, Copy, PartialEq, PartialOrd, Default, Hash)]
277pub struct Slot {
278    pub round: Round,
279    pub authority: AuthorityIndex,
280}
281
282impl Slot {
283    pub fn new(round: Round, authority: AuthorityIndex) -> Self {
284        Self { round, authority }
285    }
286
287    #[cfg(test)]
288    pub fn new_for_test(round: Round, authority: u32) -> Self {
289        Self {
290            round,
291            authority: AuthorityIndex::new_for_test(authority),
292        }
293    }
294}
295
296impl From<BlockRef> for Slot {
297    fn from(value: BlockRef) -> Self {
298        Slot::new(value.round, value.author)
299    }
300}
301
302// TODO: re-evaluate formats for production debugging.
303impl fmt::Display for Slot {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        write!(f, "{}{}", self.authority, self.round,)
306    }
307}
308
309impl fmt::Debug for Slot {
310    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311        write!(f, "{}", self)
312    }
313}
314
315/// A Block with its signature, before they are verified.
316///
317/// Note: `BlockDigest` is computed over this struct, so any added field
318/// (without `#[serde(skip)]`) will affect the values of `BlockDigest` and
319/// `BlockRef`.
320#[derive(Deserialize, Serialize)]
321pub(crate) struct SignedBlock {
322    inner: Block,
323    signature: Bytes,
324}
325
326impl SignedBlock {
327    /// Should only be used when constructing the genesis blocks
328    pub(crate) fn new_genesis(block: Block) -> Self {
329        Self {
330            inner: block,
331            signature: Bytes::default(),
332        }
333    }
334
335    pub(crate) fn new(block: Block, protocol_keypair: &ProtocolKeyPair) -> ConsensusResult<Self> {
336        let signature = compute_block_signature(&block, protocol_keypair)?;
337        Ok(Self {
338            inner: block,
339            signature: Bytes::copy_from_slice(signature.to_bytes()),
340        })
341    }
342
343    pub(crate) fn signature(&self) -> &Bytes {
344        &self.signature
345    }
346
347    /// This method only verifies this block's signature. Verification of the
348    /// full block should be done via BlockVerifier.
349    pub(crate) fn verify_signature(&self, context: &Context) -> ConsensusResult<()> {
350        let block = &self.inner;
351        let committee = &context.committee;
352        ensure!(
353            committee.is_valid_index(block.author()),
354            ConsensusError::InvalidAuthorityIndex {
355                index: block.author(),
356                max: committee.size() - 1
357            }
358        );
359        let authority = committee.authority(block.author());
360        verify_block_signature(block, self.signature(), &authority.protocol_key)
361    }
362
363    /// Serialises the block using the bcs serializer
364    pub(crate) fn serialize(&self) -> Result<Bytes, bcs::Error> {
365        let bytes = bcs::to_bytes(self)?;
366        Ok(bytes.into())
367    }
368
369    /// Clears signature for testing.
370    #[cfg(test)]
371    pub(crate) fn clear_signature(&mut self) {
372        self.signature = Bytes::default();
373    }
374}
375
376/// Digest of a block, covering all `Block` fields without its signature.
377/// This is used during Block signing and signature verification.
378/// This should never be used outside of this file, to avoid confusion with
379/// `BlockDigest`.
380#[derive(Serialize, Deserialize)]
381struct InnerBlockDigest([u8; consensus_config::DIGEST_LENGTH]);
382
383/// Computes the digest of a Block, only for signing and verifications.
384fn compute_inner_block_digest(block: &Block) -> ConsensusResult<InnerBlockDigest> {
385    let mut hasher = DefaultHashFunction::new();
386    hasher.update(bcs::to_bytes(block).map_err(ConsensusError::SerializationFailure)?);
387    Ok(InnerBlockDigest(hasher.finalize().into()))
388}
389
390/// Wrap a InnerBlockDigest in the intent message.
391fn to_consensus_block_intent(digest: InnerBlockDigest) -> IntentMessage<InnerBlockDigest> {
392    IntentMessage::new(Intent::consensus_app(IntentScope::ConsensusBlock), digest)
393}
394
395/// Process for signing & verying a block signature:
396/// 1. Compute the digest of `Block`.
397/// 2. Wrap the digest in `IntentMessage`.
398/// 3. Sign the serialized `IntentMessage`, or verify signature against it.
399fn compute_block_signature(
400    block: &Block,
401    protocol_keypair: &ProtocolKeyPair,
402) -> ConsensusResult<ProtocolKeySignature> {
403    let digest = compute_inner_block_digest(block)?;
404    let message = bcs::to_bytes(&to_consensus_block_intent(digest))
405        .map_err(ConsensusError::SerializationFailure)?;
406    Ok(protocol_keypair.sign(&message))
407}
408fn verify_block_signature(
409    block: &Block,
410    signature: &[u8],
411    protocol_pubkey: &ProtocolPublicKey,
412) -> ConsensusResult<()> {
413    let digest = compute_inner_block_digest(block)?;
414    let message = bcs::to_bytes(&to_consensus_block_intent(digest))
415        .map_err(ConsensusError::SerializationFailure)?;
416    let sig =
417        ProtocolKeySignature::from_bytes(signature).map_err(ConsensusError::MalformedSignature)?;
418    protocol_pubkey
419        .verify(&message, &sig)
420        .map_err(ConsensusError::SignatureVerificationFailure)
421}
422
423/// Allow quick access on the underlying Block without having to always refer to
424/// the inner block ref.
425impl Deref for SignedBlock {
426    type Target = Block;
427
428    fn deref(&self) -> &Self::Target {
429        &self.inner
430    }
431}
432
433/// VerifiedBlock allows full access to its content.
434/// Note: clone() is relatively cheap with most underlying data refcounted.
435#[derive(Clone)]
436pub struct VerifiedBlock {
437    block: Arc<SignedBlock>,
438
439    // Cached Block digest and serialized SignedBlock, to avoid re-computing these values.
440    digest: BlockDigest,
441    serialized: Bytes,
442}
443
444impl VerifiedBlock {
445    /// Creates VerifiedBlock from a verified SignedBlock and its serialized
446    /// bytes.
447    pub(crate) fn new_verified(signed_block: SignedBlock, serialized: Bytes) -> Self {
448        let digest = Self::compute_digest(&serialized);
449        VerifiedBlock {
450            block: Arc::new(signed_block),
451            digest,
452            serialized,
453        }
454    }
455
456    /// This method is public for testing in other crates.
457    pub fn new_for_test(block: Block) -> Self {
458        let signed_block = SignedBlock {
459            inner: block,
460            signature: Default::default(),
461        };
462        let serialized: Bytes = bcs::to_bytes(&signed_block)
463            .expect("Serialization should not fail")
464            .into();
465        let digest = Self::compute_digest(&serialized);
466        VerifiedBlock {
467            block: Arc::new(signed_block),
468            digest,
469            serialized,
470        }
471    }
472
473    /// Returns reference to the block.
474    pub fn reference(&self) -> BlockRef {
475        BlockRef {
476            round: self.round(),
477            author: self.author(),
478            digest: self.digest(),
479        }
480    }
481
482    pub(crate) fn digest(&self) -> BlockDigest {
483        self.digest
484    }
485
486    /// Returns the serialized block with signature.
487    pub(crate) fn serialized(&self) -> &Bytes {
488        &self.serialized
489    }
490
491    /// Computes digest from the serialized block with signature.
492    pub(crate) fn compute_digest(serialized: &[u8]) -> BlockDigest {
493        let mut hasher = DefaultHashFunction::new();
494        hasher.update(serialized);
495        BlockDigest(hasher.finalize().into())
496    }
497}
498
499/// Allow quick access on the underlying Block without having to always refer to
500/// the inner block ref.
501impl Deref for VerifiedBlock {
502    type Target = Block;
503
504    fn deref(&self) -> &Self::Target {
505        &self.block.inner
506    }
507}
508
509impl PartialEq for VerifiedBlock {
510    fn eq(&self, other: &Self) -> bool {
511        self.digest() == other.digest()
512    }
513}
514
515impl fmt::Display for VerifiedBlock {
516    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
517        write!(f, "{}", self.reference())
518    }
519}
520
521// TODO: re-evaluate formats for production debugging.
522impl fmt::Debug for VerifiedBlock {
523    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
524        write!(
525            f,
526            "{:?}({}ms;{:?};{}t;{}c)",
527            self.reference(),
528            self.timestamp_ms(),
529            self.ancestors(),
530            self.transactions().len(),
531            self.commit_votes().len(),
532        )
533    }
534}
535
536/// Block with extended additional information, such as
537/// local blocks that are excluded from the block's ancestors.
538/// The extended information do not need to be certified or forwarded to other
539/// authorities.
540#[derive(Clone, Debug)]
541pub(crate) struct ExtendedBlock {
542    pub block: VerifiedBlock,
543    pub excluded_ancestors: Vec<BlockRef>,
544}
545
546/// Generates the genesis blocks for the current Committee.
547/// The blocks are returned in authority index order.
548pub(crate) fn genesis_blocks(context: Arc<Context>) -> Vec<VerifiedBlock> {
549    context
550        .committee
551        .authorities()
552        .map(|(authority_index, _)| {
553            let signed_block = SignedBlock::new_genesis(Block::V1(BlockV1::genesis_block(
554                context.committee.epoch(),
555                authority_index,
556            )));
557            let serialized = signed_block
558                .serialize()
559                .expect("Genesis block serialization failed.");
560            // Unnecessary to verify genesis blocks.
561            VerifiedBlock::new_verified(signed_block, serialized)
562        })
563        .collect::<Vec<VerifiedBlock>>()
564}
565
566/// This struct is public for testing in other crates.
567#[derive(Clone)]
568pub struct TestBlock {
569    block: BlockV1,
570}
571
572impl TestBlock {
573    pub fn new(round: Round, author: u32) -> Self {
574        Self {
575            block: BlockV1 {
576                round,
577                author: AuthorityIndex::new_for_test(author),
578                ..Default::default()
579            },
580        }
581    }
582
583    pub fn set_epoch(mut self, epoch: Epoch) -> Self {
584        self.block.epoch = epoch;
585        self
586    }
587
588    pub fn set_round(mut self, round: Round) -> Self {
589        self.block.round = round;
590        self
591    }
592
593    pub fn set_author(mut self, author: AuthorityIndex) -> Self {
594        self.block.author = author;
595        self
596    }
597
598    pub fn set_timestamp_ms(mut self, timestamp_ms: BlockTimestampMs) -> Self {
599        self.block.timestamp_ms = timestamp_ms;
600        self
601    }
602
603    pub fn set_ancestors(mut self, ancestors: Vec<BlockRef>) -> Self {
604        self.block.ancestors = ancestors;
605        self
606    }
607
608    pub fn set_transactions(mut self, transactions: Vec<Transaction>) -> Self {
609        self.block.transactions = transactions;
610        self
611    }
612
613    pub fn set_commit_votes(mut self, commit_votes: Vec<CommitVote>) -> Self {
614        self.block.commit_votes = commit_votes;
615        self
616    }
617
618    pub fn build(self) -> Block {
619        Block::V1(self.block)
620    }
621}
622
623/// A block can attach reports of misbehavior by other authorities.
624#[derive(Clone, Serialize, Deserialize, Debug)]
625pub struct MisbehaviorReport {
626    pub target: AuthorityIndex,
627    pub proof: MisbehaviorProof,
628}
629
630/// Proof of misbehavior are usually signed block(s) from the misbehaving
631/// authority.
632#[derive(Clone, Serialize, Deserialize, Debug)]
633pub enum MisbehaviorProof {
634    InvalidBlock(BlockRef),
635}
636
637// TODO: add basic verification for BlockRef and BlockDigest.
638// TODO: add tests for SignedBlock and VerifiedBlock conversion.
639
640#[cfg(test)]
641mod tests {
642    use std::sync::Arc;
643
644    use fastcrypto::error::FastCryptoError;
645
646    use crate::{
647        block::{SignedBlock, TestBlock},
648        context::Context,
649        error::ConsensusError,
650    };
651
652    #[tokio::test]
653    async fn test_sign_and_verify() {
654        let (context, key_pairs) = Context::new_for_test(4);
655        let context = Arc::new(context);
656
657        // Create a block that authority 2 has created
658        let block = TestBlock::new(10, 2).build();
659
660        // Create a signed block with authority's 2 private key
661        let author_two_key = &key_pairs[2].1;
662        let signed_block = SignedBlock::new(block, author_two_key).expect("Shouldn't fail signing");
663
664        // Now verify the block's signature
665        let result = signed_block.verify_signature(&context);
666        assert!(result.is_ok());
667
668        // Try to sign authority's 2 block with authority's 1 key
669        let block = TestBlock::new(10, 2).build();
670        let author_one_key = &key_pairs[1].1;
671        let signed_block = SignedBlock::new(block, author_one_key).expect("Shouldn't fail signing");
672
673        // Now verify the block, it should fail
674        let result = signed_block.verify_signature(&context);
675        match result.err().unwrap() {
676            ConsensusError::SignatureVerificationFailure(err) => {
677                assert_eq!(err, FastCryptoError::InvalidSignature);
678            }
679            err => panic!("Unexpected error: {err:?}"),
680        }
681    }
682}