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