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