mod available_block_range;
mod block_body;
mod block_global;
mod block_hash;
mod block_hash_and_height;
mod block_header;
mod block_identifier;
mod block_signatures;
mod block_sync_status;
mod block_v1;
mod block_v2;
mod chain_name_digest;
mod era_end;
mod finality_signature;
mod finality_signature_id;
mod json_compatibility;
mod rewarded_signatures;
mod rewards;
mod signed_block;
mod signed_block_header;
#[cfg(any(all(feature = "std", feature = "testing"), test))]
mod test_block_builder;
use alloc::{boxed::Box, vec::Vec};
use core::fmt::{self, Display, Formatter};
use itertools::Either;
#[cfg(feature = "json-schema")]
use once_cell::sync::Lazy;
#[cfg(feature = "std")]
use std::error::Error as StdError;
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(feature = "std")]
use num_rational::Ratio;
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
#[cfg(feature = "std")]
use crate::TransactionConfig;
use crate::{
bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
transaction::TransactionHash,
Digest, EraId, ProtocolVersion, PublicKey, Timestamp,
};
pub use available_block_range::AvailableBlockRange;
pub use block_body::{BlockBody, BlockBodyV1, BlockBodyV2};
pub use block_global::{BlockGlobalAddr, BlockGlobalAddrTag};
pub use block_hash::BlockHash;
pub use block_hash_and_height::BlockHashAndHeight;
pub use block_header::{BlockHeader, BlockHeaderV1, BlockHeaderV2};
pub use block_identifier::BlockIdentifier;
pub use block_signatures::{
BlockSignatures, BlockSignaturesMergeError, BlockSignaturesV1, BlockSignaturesV2,
};
pub use block_sync_status::{BlockSyncStatus, BlockSynchronizerStatus};
pub use block_v1::BlockV1;
pub use block_v2::BlockV2;
pub use chain_name_digest::ChainNameDigest;
pub use era_end::{EraEnd, EraEndV1, EraEndV2, EraReport};
pub use finality_signature::{FinalitySignature, FinalitySignatureV1, FinalitySignatureV2};
pub use finality_signature_id::FinalitySignatureId;
#[cfg(all(feature = "std", feature = "json-schema"))]
pub use json_compatibility::JsonBlockWithSignatures;
pub use rewarded_signatures::{RewardedSignatures, SingleBlockRewardedSignatures};
pub use rewards::Rewards;
pub use signed_block::SignedBlock;
pub use signed_block_header::{SignedBlockHeader, SignedBlockHeaderValidationError};
#[cfg(any(all(feature = "std", feature = "testing"), test))]
pub use test_block_builder::{TestBlockBuilder, TestBlockV1Builder};
#[cfg(feature = "json-schema")]
static BLOCK: Lazy<Block> = Lazy::new(|| BlockV2::example().into());
#[derive(Clone, Eq, PartialEq, Debug)]
#[cfg_attr(any(feature = "std", test), derive(serde::Serialize))]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[non_exhaustive]
pub enum BlockValidationError {
Bytesrepr(bytesrepr::Error),
UnexpectedBlockHash {
block: Box<Block>,
actual_block_hash: BlockHash,
},
UnexpectedBodyHash {
block: Box<Block>,
actual_block_body_hash: Digest,
},
IncompatibleVersions,
}
impl Display for BlockValidationError {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match self {
BlockValidationError::Bytesrepr(error) => {
write!(formatter, "error validating block: {}", error)
}
BlockValidationError::UnexpectedBlockHash {
block,
actual_block_hash,
} => {
write!(
formatter,
"block has incorrect block hash - actual block hash: {:?}, block: {:?}",
actual_block_hash, block
)
}
BlockValidationError::UnexpectedBodyHash {
block,
actual_block_body_hash,
} => {
write!(
formatter,
"block header has incorrect body hash - actual body hash: {:?}, block: {:?}",
actual_block_body_hash, block
)
}
BlockValidationError::IncompatibleVersions => {
write!(formatter, "block body and header versions do not match")
}
}
}
}
impl From<bytesrepr::Error> for BlockValidationError {
fn from(error: bytesrepr::Error) -> Self {
BlockValidationError::Bytesrepr(error)
}
}
#[cfg(feature = "std")]
impl StdError for BlockValidationError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
BlockValidationError::Bytesrepr(error) => Some(error),
BlockValidationError::UnexpectedBlockHash { .. }
| BlockValidationError::UnexpectedBodyHash { .. }
| BlockValidationError::IncompatibleVersions => None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BlockConversionError {
DifferentVersion { expected_version: u8 },
}
#[cfg(feature = "std")]
impl Display for BlockConversionError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BlockConversionError::DifferentVersion { expected_version } => {
write!(
f,
"Could not convert a block to the expected version {}",
expected_version
)
}
}
}
}
const TAG_LENGTH: usize = U8_SERIALIZED_LENGTH;
const BLOCK_V1_TAG: u8 = 0;
const BLOCK_V2_TAG: u8 = 1;
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
any(feature = "std", feature = "json-schema", test),
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub enum Block {
#[cfg_attr(
any(feature = "std", feature = "json-schema", test),
serde(rename = "Version1")
)]
V1(BlockV1),
#[cfg_attr(
any(feature = "std", feature = "json-schema", test),
serde(rename = "Version2")
)]
V2(BlockV2),
}
impl Block {
#[doc(hidden)]
pub fn new_from_header_and_body(
block_header: BlockHeader,
block_body: BlockBody,
) -> Result<Self, Box<BlockValidationError>> {
let hash = block_header.block_hash();
let block = match (block_body, block_header) {
(BlockBody::V1(body), BlockHeader::V1(header)) => {
Ok(Block::V1(BlockV1 { hash, header, body }))
}
(BlockBody::V2(body), BlockHeader::V2(header)) => {
Ok(Block::V2(BlockV2 { hash, header, body }))
}
_ => Err(BlockValidationError::IncompatibleVersions),
}?;
block.verify()?;
Ok(block)
}
pub fn clone_header(&self) -> BlockHeader {
match self {
Block::V1(v1) => BlockHeader::V1(v1.header().clone()),
Block::V2(v2) => BlockHeader::V2(v2.header().clone()),
}
}
pub fn take_header(self) -> BlockHeader {
match self {
Block::V1(v1) => BlockHeader::V1(v1.take_header()),
Block::V2(v2) => BlockHeader::V2(v2.take_header()),
}
}
pub fn timestamp(&self) -> Timestamp {
match self {
Block::V1(v1) => v1.header.timestamp(),
Block::V2(v2) => v2.header.timestamp(),
}
}
pub fn protocol_version(&self) -> ProtocolVersion {
match self {
Block::V1(v1) => v1.header.protocol_version(),
Block::V2(v2) => v2.header.protocol_version(),
}
}
pub fn hash(&self) -> &BlockHash {
match self {
Block::V1(v1) => v1.hash(),
Block::V2(v2) => v2.hash(),
}
}
pub fn body_hash(&self) -> &Digest {
match self {
Block::V1(v1) => v1.header().body_hash(),
Block::V2(v2) => v2.header().body_hash(),
}
}
pub fn random_bit(&self) -> bool {
match self {
Block::V1(v1) => v1.header().random_bit(),
Block::V2(v2) => v2.header().random_bit(),
}
}
pub fn accumulated_seed(&self) -> &Digest {
match self {
Block::V1(v1) => v1.accumulated_seed(),
Block::V2(v2) => v2.accumulated_seed(),
}
}
pub fn parent_hash(&self) -> &BlockHash {
match self {
Block::V1(v1) => v1.parent_hash(),
Block::V2(v2) => v2.parent_hash(),
}
}
pub fn proposer(&self) -> &PublicKey {
match self {
Block::V1(v1) => v1.proposer(),
Block::V2(v2) => v2.proposer(),
}
}
pub fn clone_body(&self) -> BlockBody {
match self {
Block::V1(v1) => BlockBody::V1(v1.body().clone()),
Block::V2(v2) => BlockBody::V2(v2.body().clone()),
}
}
pub fn take_body(self) -> BlockBody {
match self {
Block::V1(v1) => BlockBody::V1(v1.take_body()),
Block::V2(v2) => BlockBody::V2(v2.take_body()),
}
}
pub fn verify(&self) -> Result<(), BlockValidationError> {
match self {
Block::V1(v1) => v1.verify(),
Block::V2(v2) => v2.verify(),
}
}
pub fn height(&self) -> u64 {
match self {
Block::V1(v1) => v1.header.height(),
Block::V2(v2) => v2.header.height(),
}
}
pub fn era_id(&self) -> EraId {
match self {
Block::V1(v1) => v1.era_id(),
Block::V2(v2) => v2.era_id(),
}
}
pub fn clone_era_end(&self) -> Option<EraEnd> {
match self {
Block::V1(v1) => v1.header().era_end().cloned().map(EraEnd::V1),
Block::V2(v2) => v2.header().era_end().cloned().map(EraEnd::V2),
}
}
pub fn is_switch_block(&self) -> bool {
match self {
Block::V1(v1) => v1.header.is_switch_block(),
Block::V2(v2) => v2.header.is_switch_block(),
}
}
pub fn is_genesis(&self) -> bool {
match self {
Block::V1(v1) => v1.header.is_genesis(),
Block::V2(v2) => v2.header.is_genesis(),
}
}
pub fn state_root_hash(&self) -> &Digest {
match self {
Block::V1(v1) => v1.header.state_root_hash(),
Block::V2(v2) => v2.header.state_root_hash(),
}
}
pub fn rewarded_signatures(&self) -> &RewardedSignatures {
match self {
Block::V1(_v1) => &rewarded_signatures::EMPTY,
Block::V2(v2) => v2.body.rewarded_signatures(),
}
}
pub fn maybe_current_gas_price(&self) -> Option<u8> {
match self {
Block::V1(_) => None,
Block::V2(v2) => Some(v2.header().current_gas_price()),
}
}
pub fn transaction_count(&self) -> u64 {
match self {
Block::V1(block) => {
(block.body.deploy_hashes().len() + block.body.transfer_hashes().len()) as u64
}
Block::V2(block_v2) => block_v2.all_transactions().count() as u64,
}
}
pub fn all_transaction_hashes(&self) -> impl Iterator<Item = TransactionHash> + '_ {
match self {
Block::V1(block) => Either::Left(
block
.body
.deploy_and_transfer_hashes()
.map(TransactionHash::from),
),
Block::V2(block_v2) => Either::Right(block_v2.all_transactions().copied()),
}
}
#[cfg(feature = "std")]
pub fn block_utilization(&self, transaction_config: TransactionConfig) -> u64 {
match self {
Block::V1(_) => {
0
}
Block::V2(block_v2) => {
let has_hit_slot_limt = self.has_hit_slot_capacity(transaction_config.clone());
let per_block_capacity = transaction_config
.transaction_v1_config
.get_max_block_count();
if has_hit_slot_limt {
100u64
} else {
let num = block_v2.all_transactions().count() as u64;
Ratio::new(num * 100, per_block_capacity).to_integer()
}
}
}
}
#[cfg(feature = "std")]
pub fn has_hit_slot_capacity(&self, transaction_config: TransactionConfig) -> bool {
match self {
Block::V1(_) => false,
Block::V2(block_v2) => {
let mint_count = block_v2.mint().count();
if mint_count as u64
>= transaction_config
.transaction_v1_config
.native_mint_lane
.max_transaction_count()
{
return true;
}
let auction_count = block_v2.auction().count();
if auction_count as u64
>= transaction_config
.transaction_v1_config
.native_auction_lane
.max_transaction_count()
{
return true;
}
let install_upgrade_count = block_v2.install_upgrade().count();
if install_upgrade_count as u64
>= transaction_config
.transaction_v1_config
.install_upgrade_lane
.max_transaction_count()
{
return true;
}
for (lane_id, transactions) in block_v2.body.transactions() {
let transaction_count = transactions.len();
if *lane_id < 2 {
continue;
};
let max_transaction_count = transaction_config
.transaction_v1_config
.get_max_transaction_count(*lane_id);
if transaction_count as u64 >= max_transaction_count {
return true;
}
}
false
}
}
}
#[doc(hidden)]
#[cfg(feature = "json-schema")]
pub fn example() -> &'static Self {
&BLOCK
}
}
impl Display for Block {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"executed block #{}, {}, timestamp {}, {}, parent {}, post-state hash {}, body hash \
{}, random bit {}, protocol version: {}",
self.height(),
self.hash(),
self.timestamp(),
self.era_id(),
self.parent_hash().inner(),
self.state_root_hash(),
self.body_hash(),
self.random_bit(),
self.protocol_version()
)?;
if let Some(era_end) = self.clone_era_end() {
write!(formatter, ", era_end: {}", era_end)?;
}
Ok(())
}
}
impl ToBytes for Block {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
match self {
Block::V1(v1) => {
buffer.insert(0, BLOCK_V1_TAG);
buffer.extend(v1.to_bytes()?);
}
Block::V2(v2) => {
buffer.insert(0, BLOCK_V2_TAG);
buffer.extend(v2.to_bytes()?);
}
}
Ok(buffer)
}
fn serialized_length(&self) -> usize {
TAG_LENGTH
+ match self {
Block::V1(v1) => v1.serialized_length(),
Block::V2(v2) => v2.serialized_length(),
}
}
}
impl FromBytes for Block {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (tag, remainder) = u8::from_bytes(bytes)?;
match tag {
BLOCK_V1_TAG => {
let (body, remainder): (BlockV1, _) = FromBytes::from_bytes(remainder)?;
Ok((Self::V1(body), remainder))
}
BLOCK_V2_TAG => {
let (body, remainder): (BlockV2, _) = FromBytes::from_bytes(remainder)?;
Ok((Self::V2(body), remainder))
}
_ => Err(bytesrepr::Error::Formatting),
}
}
}
impl From<&BlockV2> for Block {
fn from(block: &BlockV2) -> Self {
Block::V2(block.clone())
}
}
impl From<BlockV2> for Block {
fn from(block: BlockV2) -> Self {
Block::V2(block)
}
}
impl From<&BlockV1> for Block {
fn from(block: &BlockV1) -> Self {
Block::V1(block.clone())
}
}
impl From<BlockV1> for Block {
fn from(block: BlockV1) -> Self {
Block::V1(block)
}
}
#[cfg(all(feature = "std", feature = "json-schema"))]
impl From<JsonBlockWithSignatures> for Block {
fn from(block_with_signatures: JsonBlockWithSignatures) -> Self {
block_with_signatures.block
}
}
#[cfg(test)]
mod tests {
use crate::{bytesrepr, testing::TestRng};
use super::*;
#[test]
fn bytesrepr_roundtrip() {
let rng = &mut TestRng::new();
let block_v1 = TestBlockV1Builder::new().build(rng);
let block = Block::V1(block_v1);
bytesrepr::test_serialization_roundtrip(&block);
let block_v2 = TestBlockBuilder::new().build(rng);
let block = Block::V2(block_v2);
bytesrepr::test_serialization_roundtrip(&block);
}
}