casper_types/
block.rs

1mod available_block_range;
2mod block_body;
3mod block_global;
4mod block_hash;
5mod block_hash_and_height;
6mod block_header;
7mod block_header_with_signatures;
8mod block_identifier;
9mod block_signatures;
10mod block_sync_status;
11mod block_v1;
12mod block_v2;
13mod block_with_signatures;
14mod chain_name_digest;
15mod era_end;
16mod finality_signature;
17mod finality_signature_id;
18mod json_compatibility;
19mod rewarded_signatures;
20mod rewards;
21#[cfg(any(all(feature = "std", feature = "testing"), test))]
22mod test_block_builder;
23
24use alloc::{boxed::Box, vec::Vec};
25use core::fmt::{self, Display, Formatter};
26use itertools::Either;
27#[cfg(feature = "json-schema")]
28use once_cell::sync::Lazy;
29#[cfg(feature = "std")]
30use std::error::Error as StdError;
31
32#[cfg(feature = "datasize")]
33use datasize::DataSize;
34#[cfg(feature = "std")]
35use num_rational::Ratio;
36
37#[cfg(feature = "json-schema")]
38use schemars::JsonSchema;
39
40#[cfg(feature = "std")]
41use crate::TransactionConfig;
42
43use crate::{
44    bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
45    transaction::TransactionHash,
46    Digest, EraId, ProtocolVersion, PublicKey, Timestamp,
47};
48pub use available_block_range::AvailableBlockRange;
49pub use block_body::{BlockBody, BlockBodyV1, BlockBodyV2};
50pub use block_global::{BlockGlobalAddr, BlockGlobalAddrTag};
51pub use block_hash::BlockHash;
52pub use block_hash_and_height::BlockHashAndHeight;
53pub use block_header::{BlockHeader, BlockHeaderV1, BlockHeaderV2};
54pub use block_header_with_signatures::{
55    BlockHeaderWithSignatures, BlockHeaderWithSignaturesValidationError,
56};
57pub use block_identifier::BlockIdentifier;
58pub use block_signatures::{
59    BlockSignatures, BlockSignaturesMergeError, BlockSignaturesV1, BlockSignaturesV2,
60};
61pub use block_sync_status::{BlockSyncStatus, BlockSynchronizerStatus};
62pub use block_v1::BlockV1;
63pub use block_v2::BlockV2;
64pub use block_with_signatures::BlockWithSignatures;
65pub use chain_name_digest::ChainNameDigest;
66pub use era_end::{EraEnd, EraEndV1, EraEndV2, EraReport};
67pub use finality_signature::{FinalitySignature, FinalitySignatureV1, FinalitySignatureV2};
68pub use finality_signature_id::FinalitySignatureId;
69#[cfg(all(feature = "std", feature = "json-schema"))]
70pub use json_compatibility::JsonBlockWithSignatures;
71pub use rewarded_signatures::{RewardedSignatures, SingleBlockRewardedSignatures};
72pub use rewards::Rewards;
73#[cfg(any(all(feature = "std", feature = "testing"), test))]
74pub use test_block_builder::{TestBlockBuilder, TestBlockV1Builder};
75
76#[cfg(feature = "json-schema")]
77static BLOCK: Lazy<Block> = Lazy::new(|| BlockV2::example().into());
78
79/// An error that can arise when validating a block's cryptographic integrity using its hashes.
80#[derive(Clone, Eq, PartialEq, Debug)]
81#[cfg_attr(any(feature = "std", test), derive(serde::Serialize))]
82#[cfg_attr(feature = "datasize", derive(DataSize))]
83#[non_exhaustive]
84pub enum BlockValidationError {
85    /// Problem serializing some of a block's data into bytes.
86    Bytesrepr(bytesrepr::Error),
87    /// The provided block's hash is not the same as the actual hash of the block.
88    UnexpectedBlockHash {
89        /// The block with the incorrect block hash.
90        block: Box<Block>,
91        /// The actual hash of the block.
92        actual_block_hash: BlockHash,
93    },
94    /// The body hash in the header is not the same as the actual hash of the body of the block.
95    UnexpectedBodyHash {
96        /// The block with the header containing the incorrect block body hash.
97        block: Box<Block>,
98        /// The actual hash of the block's body.
99        actual_block_body_hash: Digest,
100    },
101    /// The header version does not match the body version.
102    IncompatibleVersions,
103}
104
105impl Display for BlockValidationError {
106    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
107        match self {
108            BlockValidationError::Bytesrepr(error) => {
109                write!(formatter, "error validating block: {}", error)
110            }
111            BlockValidationError::UnexpectedBlockHash {
112                block,
113                actual_block_hash,
114            } => {
115                write!(
116                    formatter,
117                    "block has incorrect block hash - actual block hash: {:?}, block: {:?}",
118                    actual_block_hash, block
119                )
120            }
121            BlockValidationError::UnexpectedBodyHash {
122                block,
123                actual_block_body_hash,
124            } => {
125                write!(
126                    formatter,
127                    "block header has incorrect body hash - actual body hash: {:?}, block: {:?}",
128                    actual_block_body_hash, block
129                )
130            }
131            BlockValidationError::IncompatibleVersions => {
132                write!(formatter, "block body and header versions do not match")
133            }
134        }
135    }
136}
137
138impl From<bytesrepr::Error> for BlockValidationError {
139    fn from(error: bytesrepr::Error) -> Self {
140        BlockValidationError::Bytesrepr(error)
141    }
142}
143
144#[cfg(feature = "std")]
145impl StdError for BlockValidationError {
146    fn source(&self) -> Option<&(dyn StdError + 'static)> {
147        match self {
148            BlockValidationError::Bytesrepr(error) => Some(error),
149            BlockValidationError::UnexpectedBlockHash { .. }
150            | BlockValidationError::UnexpectedBodyHash { .. }
151            | BlockValidationError::IncompatibleVersions => None,
152        }
153    }
154}
155
156#[derive(Clone, Copy, PartialEq, Eq, Debug)]
157pub enum BlockConversionError {
158    DifferentVersion { expected_version: u8 },
159}
160
161#[cfg(feature = "std")]
162impl Display for BlockConversionError {
163    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164        match self {
165            BlockConversionError::DifferentVersion { expected_version } => {
166                write!(
167                    f,
168                    "Could not convert a block to the expected version {}",
169                    expected_version
170                )
171            }
172        }
173    }
174}
175
176const TAG_LENGTH: usize = U8_SERIALIZED_LENGTH;
177
178/// Tag for block body v1.
179const BLOCK_V1_TAG: u8 = 0;
180/// Tag for block body v2.
181const BLOCK_V2_TAG: u8 = 1;
182
183/// A block after execution.
184#[cfg_attr(feature = "datasize", derive(DataSize))]
185#[cfg_attr(
186    any(feature = "std", feature = "json-schema", test),
187    derive(serde::Serialize, serde::Deserialize)
188)]
189#[derive(Clone, Debug, PartialEq, Eq)]
190#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
191pub enum Block {
192    /// The legacy, initial version of the block.
193    #[cfg_attr(
194        any(feature = "std", feature = "json-schema", test),
195        serde(rename = "Version1")
196    )]
197    V1(BlockV1),
198    /// The version 2 of the block.
199    #[cfg_attr(
200        any(feature = "std", feature = "json-schema", test),
201        serde(rename = "Version2")
202    )]
203    V2(BlockV2),
204}
205
206impl Block {
207    // This method is not intended to be used by third party crates.
208    #[doc(hidden)]
209    pub fn new_from_header_and_body(
210        block_header: BlockHeader,
211        block_body: BlockBody,
212    ) -> Result<Self, Box<BlockValidationError>> {
213        let hash = block_header.block_hash();
214        let block = match (block_body, block_header) {
215            (BlockBody::V1(body), BlockHeader::V1(header)) => {
216                Ok(Block::V1(BlockV1 { hash, header, body }))
217            }
218            (BlockBody::V2(body), BlockHeader::V2(header)) => {
219                Ok(Block::V2(BlockV2 { hash, header, body }))
220            }
221            _ => Err(BlockValidationError::IncompatibleVersions),
222        }?;
223
224        block.verify()?;
225        Ok(block)
226    }
227
228    /// Clones the header, put it in the versioning enum, and returns it.
229    pub fn clone_header(&self) -> BlockHeader {
230        match self {
231            Block::V1(v1) => BlockHeader::V1(v1.header().clone()),
232            Block::V2(v2) => BlockHeader::V2(v2.header().clone()),
233        }
234    }
235
236    /// Returns the block's header, consuming `self`.
237    pub fn take_header(self) -> BlockHeader {
238        match self {
239            Block::V1(v1) => BlockHeader::V1(v1.take_header()),
240            Block::V2(v2) => BlockHeader::V2(v2.take_header()),
241        }
242    }
243
244    /// Returns the timestamp from when the block was proposed.
245    pub fn timestamp(&self) -> Timestamp {
246        match self {
247            Block::V1(v1) => v1.header.timestamp(),
248            Block::V2(v2) => v2.header.timestamp(),
249        }
250    }
251
252    /// Returns the protocol version of the network from when this block was created.
253    pub fn protocol_version(&self) -> ProtocolVersion {
254        match self {
255            Block::V1(v1) => v1.header.protocol_version(),
256            Block::V2(v2) => v2.header.protocol_version(),
257        }
258    }
259
260    /// The hash of this block's header.
261    pub fn hash(&self) -> &BlockHash {
262        match self {
263            Block::V1(v1) => v1.hash(),
264            Block::V2(v2) => v2.hash(),
265        }
266    }
267
268    /// Returns the hash of the block's body.
269    pub fn body_hash(&self) -> &Digest {
270        match self {
271            Block::V1(v1) => v1.header().body_hash(),
272            Block::V2(v2) => v2.header().body_hash(),
273        }
274    }
275
276    /// Returns a random bit needed for initializing a future era.
277    pub fn random_bit(&self) -> bool {
278        match self {
279            Block::V1(v1) => v1.header().random_bit(),
280            Block::V2(v2) => v2.header().random_bit(),
281        }
282    }
283
284    /// Returns a seed needed for initializing a future era.
285    pub fn accumulated_seed(&self) -> &Digest {
286        match self {
287            Block::V1(v1) => v1.accumulated_seed(),
288            Block::V2(v2) => v2.accumulated_seed(),
289        }
290    }
291
292    /// Returns the parent block's hash.
293    pub fn parent_hash(&self) -> &BlockHash {
294        match self {
295            Block::V1(v1) => v1.parent_hash(),
296            Block::V2(v2) => v2.parent_hash(),
297        }
298    }
299
300    /// Returns the public key of the validator which proposed the block.
301    pub fn proposer(&self) -> &PublicKey {
302        match self {
303            Block::V1(v1) => v1.proposer(),
304            Block::V2(v2) => v2.proposer(),
305        }
306    }
307
308    /// Clone the body and wrap is up in the versioned `Body`.
309    pub fn clone_body(&self) -> BlockBody {
310        match self {
311            Block::V1(v1) => BlockBody::V1(v1.body().clone()),
312            Block::V2(v2) => BlockBody::V2(v2.body().clone()),
313        }
314    }
315
316    /// Returns the block's body, consuming `self`.
317    pub fn take_body(self) -> BlockBody {
318        match self {
319            Block::V1(v1) => BlockBody::V1(v1.take_body()),
320            Block::V2(v2) => BlockBody::V2(v2.take_body()),
321        }
322    }
323
324    /// Check the integrity of a block by hashing its body and header
325    pub fn verify(&self) -> Result<(), BlockValidationError> {
326        match self {
327            Block::V1(v1) => v1.verify(),
328            Block::V2(v2) => v2.verify(),
329        }
330    }
331
332    /// Returns the height of this block, i.e. the number of ancestors.
333    pub fn height(&self) -> u64 {
334        match self {
335            Block::V1(v1) => v1.header.height(),
336            Block::V2(v2) => v2.header.height(),
337        }
338    }
339
340    /// Returns the era ID in which this block was created.
341    pub fn era_id(&self) -> EraId {
342        match self {
343            Block::V1(v1) => v1.era_id(),
344            Block::V2(v2) => v2.era_id(),
345        }
346    }
347
348    /// Clones the era end, put it in the versioning enum, and returns it.
349    pub fn clone_era_end(&self) -> Option<EraEnd> {
350        match self {
351            Block::V1(v1) => v1.header().era_end().cloned().map(EraEnd::V1),
352            Block::V2(v2) => v2.header().era_end().cloned().map(EraEnd::V2),
353        }
354    }
355
356    /// Returns `true` if this block is the last one in the current era.
357    pub fn is_switch_block(&self) -> bool {
358        match self {
359            Block::V1(v1) => v1.header.is_switch_block(),
360            Block::V2(v2) => v2.header.is_switch_block(),
361        }
362    }
363
364    /// Returns `true` if this block is the first block of the chain, the genesis block.
365    pub fn is_genesis(&self) -> bool {
366        match self {
367            Block::V1(v1) => v1.header.is_genesis(),
368            Block::V2(v2) => v2.header.is_genesis(),
369        }
370    }
371
372    /// Returns the root hash of global state after the deploys in this block have been executed.
373    pub fn state_root_hash(&self) -> &Digest {
374        match self {
375            Block::V1(v1) => v1.header.state_root_hash(),
376            Block::V2(v2) => v2.header.state_root_hash(),
377        }
378    }
379
380    /// List of identifiers for finality signatures for a particular past block.
381    pub fn rewarded_signatures(&self) -> &RewardedSignatures {
382        match self {
383            Block::V1(_v1) => &rewarded_signatures::EMPTY,
384            Block::V2(v2) => v2.body.rewarded_signatures(),
385        }
386    }
387
388    /// Return the gas price for V2 block header.
389    pub fn maybe_current_gas_price(&self) -> Option<u8> {
390        match self {
391            Block::V1(_) => None,
392            Block::V2(v2) => Some(v2.header().current_gas_price()),
393        }
394    }
395
396    /// Returns the count of transactions within a block.
397    pub fn transaction_count(&self) -> u64 {
398        match self {
399            Block::V1(block) => {
400                (block.body.deploy_hashes().len() + block.body.transfer_hashes().len()) as u64
401            }
402            Block::V2(block_v2) => block_v2.all_transactions().count() as u64,
403        }
404    }
405
406    /// Returns a list of all transaction hashes in a block.
407    pub fn all_transaction_hashes(&self) -> impl Iterator<Item = TransactionHash> + '_ {
408        match self {
409            Block::V1(block) => Either::Left(
410                block
411                    .body
412                    .deploy_and_transfer_hashes()
413                    .map(TransactionHash::from),
414            ),
415            Block::V2(block_v2) => Either::Right(block_v2.all_transactions().copied()),
416        }
417    }
418
419    /// Returns the utilization of the block against a given chainspec.
420    #[cfg(feature = "std")]
421    pub fn block_utilization(&self, transaction_config: TransactionConfig) -> u64 {
422        match self {
423            Block::V1(_) => {
424                // We shouldnt be tracking this for legacy blocks
425                0
426            }
427            Block::V2(block_v2) => {
428                let has_hit_slot_limt = self.has_hit_slot_capacity(transaction_config.clone());
429                let per_block_capacity = transaction_config
430                    .transaction_v1_config
431                    .get_max_block_count();
432
433                if has_hit_slot_limt {
434                    100u64
435                } else {
436                    let num = block_v2.all_transactions().count() as u64;
437                    Ratio::new(num * 100, per_block_capacity).to_integer()
438                }
439            }
440        }
441    }
442
443    /// Returns true if the block has reached capacity in any of its transaction limit.
444    #[cfg(feature = "std")]
445    pub fn has_hit_slot_capacity(&self, transaction_config: TransactionConfig) -> bool {
446        match self {
447            Block::V1(_) => false,
448            Block::V2(block_v2) => {
449                let mint_count = block_v2.mint().count();
450                if mint_count as u64
451                    >= transaction_config
452                        .transaction_v1_config
453                        .native_mint_lane
454                        .max_transaction_count()
455                {
456                    return true;
457                }
458
459                let auction_count = block_v2.auction().count();
460                if auction_count as u64
461                    >= transaction_config
462                        .transaction_v1_config
463                        .native_auction_lane
464                        .max_transaction_count()
465                {
466                    return true;
467                }
468
469                let install_upgrade_count = block_v2.install_upgrade().count();
470                if install_upgrade_count as u64
471                    >= transaction_config
472                        .transaction_v1_config
473                        .install_upgrade_lane
474                        .max_transaction_count()
475                {
476                    return true;
477                }
478
479                for (lane_id, transactions) in block_v2.body.transactions() {
480                    let transaction_count = transactions.len();
481                    if *lane_id < 2 {
482                        continue;
483                    };
484                    let max_transaction_count = transaction_config
485                        .transaction_v1_config
486                        .get_max_transaction_count(*lane_id);
487
488                    if transaction_count as u64 >= max_transaction_count {
489                        return true;
490                    }
491                }
492                false
493            }
494        }
495    }
496
497    // This method is not intended to be used by third party crates.
498    #[doc(hidden)]
499    #[cfg(feature = "json-schema")]
500    pub fn example() -> &'static Self {
501        &BLOCK
502    }
503}
504
505impl Display for Block {
506    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
507        write!(
508            formatter,
509            "executed block #{}, {}, timestamp {}, {}, parent {}, post-state hash {}, body hash \
510            {}, random bit {}, protocol version: {}",
511            self.height(),
512            self.hash(),
513            self.timestamp(),
514            self.era_id(),
515            self.parent_hash().inner(),
516            self.state_root_hash(),
517            self.body_hash(),
518            self.random_bit(),
519            self.protocol_version()
520        )?;
521        if let Some(era_end) = self.clone_era_end() {
522            write!(formatter, ", era_end: {}", era_end)?;
523        }
524        Ok(())
525    }
526}
527
528impl ToBytes for Block {
529    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
530        let mut buffer = bytesrepr::allocate_buffer(self)?;
531        match self {
532            Block::V1(v1) => {
533                buffer.insert(0, BLOCK_V1_TAG);
534                buffer.extend(v1.to_bytes()?);
535            }
536            Block::V2(v2) => {
537                buffer.insert(0, BLOCK_V2_TAG);
538                buffer.extend(v2.to_bytes()?);
539            }
540        }
541        Ok(buffer)
542    }
543
544    fn serialized_length(&self) -> usize {
545        TAG_LENGTH
546            + match self {
547                Block::V1(v1) => v1.serialized_length(),
548                Block::V2(v2) => v2.serialized_length(),
549            }
550    }
551}
552
553impl FromBytes for Block {
554    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
555        let (tag, remainder) = u8::from_bytes(bytes)?;
556        match tag {
557            BLOCK_V1_TAG => {
558                let (body, remainder): (BlockV1, _) = FromBytes::from_bytes(remainder)?;
559                Ok((Self::V1(body), remainder))
560            }
561            BLOCK_V2_TAG => {
562                let (body, remainder): (BlockV2, _) = FromBytes::from_bytes(remainder)?;
563                Ok((Self::V2(body), remainder))
564            }
565            _ => Err(bytesrepr::Error::Formatting),
566        }
567    }
568}
569
570impl From<&BlockV2> for Block {
571    fn from(block: &BlockV2) -> Self {
572        Block::V2(block.clone())
573    }
574}
575
576impl From<BlockV2> for Block {
577    fn from(block: BlockV2) -> Self {
578        Block::V2(block)
579    }
580}
581
582impl From<&BlockV1> for Block {
583    fn from(block: &BlockV1) -> Self {
584        Block::V1(block.clone())
585    }
586}
587
588impl From<BlockV1> for Block {
589    fn from(block: BlockV1) -> Self {
590        Block::V1(block)
591    }
592}
593
594#[cfg(all(feature = "std", feature = "json-schema"))]
595impl From<JsonBlockWithSignatures> for Block {
596    fn from(block_with_signatures: JsonBlockWithSignatures) -> Self {
597        block_with_signatures.block
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use crate::{bytesrepr, testing::TestRng};
604
605    use super::*;
606
607    #[test]
608    fn bytesrepr_roundtrip() {
609        let rng = &mut TestRng::new();
610        let block_v1 = TestBlockV1Builder::new().build(rng);
611        let block = Block::V1(block_v1);
612        bytesrepr::test_serialization_roundtrip(&block);
613
614        let block_v2 = TestBlockBuilder::new().build(rng);
615        let block = Block::V2(block_v2);
616        bytesrepr::test_serialization_roundtrip(&block);
617    }
618}