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