#![allow(clippy::field_reassign_with_default)]
#[cfg(test)]
use std::iter;
use std::{
array::TryFromSliceError,
collections::BTreeMap,
error::Error as StdError,
fmt::{self, Debug, Display, Formatter},
hash::Hash,
};
use blake2::{
digest::{Update, VariableOutput},
VarBlake2b,
};
use datasize::DataSize;
use hex::FromHexError;
use hex_fmt::{HexFmt, HexList};
use once_cell::sync::Lazy;
#[cfg(test)]
use rand::Rng;
use rand::SeedableRng;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(test)]
use casper_types::auction::BLOCK_REWARD;
use casper_types::{
bytesrepr::{self, FromBytes, ToBytes},
PublicKey, SecretKey, Signature,
};
use super::{Item, Tag, Timestamp};
#[cfg(test)]
use crate::testing::TestRng;
use crate::{
components::consensus::{self, EraId},
crypto::{
self,
hash::{self, Digest},
AsymmetricKeyExt,
},
rpcs::docs::DocExample,
types::{Deploy, DeployHash, NodeRng},
utils::DisplayIter,
};
static ERA_END: Lazy<EraEnd> = Lazy::new(|| {
let secret_key_1 = SecretKey::ed25519([0; 32]);
let public_key_1 = PublicKey::from(&secret_key_1);
let equivocators = vec![public_key_1];
let secret_key_2 = SecretKey::ed25519([1; 32]);
let public_key_2 = PublicKey::from(&secret_key_2);
let mut rewards = BTreeMap::new();
rewards.insert(public_key_2, 1000);
EraEnd {
equivocators,
rewards,
}
});
static FINALIZED_BLOCK: Lazy<FinalizedBlock> = Lazy::new(|| {
let deploy_hashes = vec![*Deploy::doc_example().id()];
let random_bit = true;
let proto_block = ProtoBlock::new(deploy_hashes, vec![], random_bit);
let timestamp = *Timestamp::doc_example();
let era_end = Some(EraEnd::doc_example().clone());
let era: u64 = 1;
let secret_key = SecretKey::doc_example();
let public_key = PublicKey::from(secret_key);
FinalizedBlock::new(
proto_block,
timestamp,
era_end,
EraId(era),
era * 10,
public_key,
)
});
static BLOCK: Lazy<Block> = Lazy::new(|| {
let parent_hash = BlockHash::new(Digest::from([7u8; Digest::LENGTH]));
let state_root_hash = Digest::from([8u8; Digest::LENGTH]);
let finalized_block = FinalizedBlock::doc_example().clone();
let parent_seed = Digest::from([9u8; Digest::LENGTH]);
let mut block = Block::new(parent_hash, parent_seed, state_root_hash, finalized_block);
let secret_key = SecretKey::doc_example();
let public_key = PublicKey::from(secret_key);
let mut rng = NodeRng::seed_from_u64(0);
let signature = crypto::sign(block.hash.inner(), &secret_key, &public_key, &mut rng);
block.append_proof(public_key, signature);
block
});
#[derive(Debug, Error)]
pub enum Error {
#[error("encoding to JSON: {0}")]
EncodeToJson(#[from] serde_json::Error),
#[error("decoding from JSON: {0}")]
DecodeFromJson(Box<dyn StdError>),
}
impl From<FromHexError> for Error {
fn from(error: FromHexError) -> Self {
Error::DecodeFromJson(Box::new(error))
}
}
impl From<TryFromSliceError> for Error {
fn from(error: TryFromSliceError) -> Self {
Error::DecodeFromJson(Box::new(error))
}
}
pub trait BlockLike: Eq + Hash {
fn deploys(&self) -> Vec<&DeployHash>;
}
#[derive(
Copy,
Clone,
DataSize,
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize,
Debug,
Default,
)]
pub struct ProtoBlockHash(Digest);
impl ProtoBlockHash {
pub fn new(hash: Digest) -> Self {
ProtoBlockHash(hash)
}
pub fn inner(&self) -> &Digest {
&self.0
}
}
impl Display for ProtoBlockHash {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "proto-block-hash({})", self.0)
}
}
#[derive(Clone, DataSize, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProtoBlock {
hash: ProtoBlockHash,
wasm_deploys: Vec<DeployHash>,
transfers: Vec<DeployHash>,
random_bit: bool,
}
impl ProtoBlock {
pub(crate) fn new(
wasm_deploys: Vec<DeployHash>,
transfers: Vec<DeployHash>,
random_bit: bool,
) -> Self {
let deploys = wasm_deploys
.iter()
.chain(transfers.iter())
.collect::<Vec<_>>();
let hash = ProtoBlockHash::new(hash::hash(
&bincode::serialize(&(&deploys, random_bit)).expect("serialize ProtoBlock"),
));
ProtoBlock {
hash,
wasm_deploys,
transfers,
random_bit,
}
}
pub(crate) fn hash(&self) -> &ProtoBlockHash {
&self.hash
}
pub(crate) fn wasm_deploys(&self) -> &Vec<DeployHash> {
&self.wasm_deploys
}
pub(crate) fn transfers(&self) -> &Vec<DeployHash> {
&self.transfers
}
pub(crate) fn random_bit(&self) -> bool {
self.random_bit
}
pub(crate) fn destructure(self) -> (ProtoBlockHash, Vec<DeployHash>, Vec<DeployHash>, bool) {
(
self.hash,
self.wasm_deploys,
self.transfers,
self.random_bit,
)
}
}
impl Display for ProtoBlock {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"proto block {}, deploys [{}], random bit {}",
self.hash.inner(),
DisplayIter::new(self.wasm_deploys.iter().chain(self.transfers.iter())),
self.random_bit(),
)
}
}
impl BlockLike for ProtoBlock {
fn deploys(&self) -> Vec<&DeployHash> {
self.wasm_deploys()
.iter()
.chain(self.transfers())
.collect::<Vec<_>>()
}
}
pub type EraEnd = consensus::EraEnd<PublicKey>;
impl Display for EraEnd {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let slashings = DisplayIter::new(&self.equivocators);
let rewards = DisplayIter::new(
self.rewards
.iter()
.map(|(public_key, amount)| format!("{}: {}", public_key, amount)),
);
write!(f, "era end: slash {}, reward {}", slashings, rewards)
}
}
impl ToBytes for EraEnd {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.equivocators.to_bytes()?);
buffer.extend(self.rewards.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.equivocators.serialized_length() + self.rewards.serialized_length()
}
}
impl FromBytes for EraEnd {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (equivocators, remainder) = Vec::<PublicKey>::from_bytes(bytes)?;
let (rewards, remainder) = BTreeMap::<PublicKey, u64>::from_bytes(remainder)?;
let era_end = EraEnd {
equivocators,
rewards,
};
Ok((era_end, remainder))
}
}
impl DocExample for EraEnd {
fn doc_example() -> &'static Self {
&*ERA_END
}
}
#[derive(Clone, DataSize, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FinalizedBlock {
proto_block: ProtoBlock,
timestamp: Timestamp,
era_end: Option<EraEnd>,
era_id: EraId,
height: u64,
proposer: PublicKey,
}
impl FinalizedBlock {
pub(crate) fn new(
proto_block: ProtoBlock,
timestamp: Timestamp,
era_end: Option<EraEnd>,
era_id: EraId,
height: u64,
proposer: PublicKey,
) -> Self {
FinalizedBlock {
proto_block,
timestamp,
era_end,
era_id,
height,
proposer,
}
}
pub(crate) fn proto_block(&self) -> &ProtoBlock {
&self.proto_block
}
pub(crate) fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub(crate) fn era_end(&self) -> Option<&EraEnd> {
self.era_end.as_ref()
}
pub(crate) fn era_id(&self) -> EraId {
self.era_id
}
pub(crate) fn height(&self) -> u64 {
self.height
}
pub(crate) fn is_genesis_child(&self) -> bool {
self.era_id() == EraId(0) && self.height() == 0
}
pub(crate) fn proposer(&self) -> PublicKey {
self.proposer
}
#[cfg(test)]
pub fn random(rng: &mut TestRng) -> Self {
let deploy_count = rng.gen_range(0, 11);
let deploy_hashes = iter::repeat_with(|| DeployHash::new(Digest::random(rng)))
.take(deploy_count)
.collect();
let random_bit = rng.gen();
let proto_block = ProtoBlock::new(deploy_hashes, vec![], random_bit);
let timestamp = Timestamp::now();
let era_end = if rng.gen_bool(0.1) {
let equivocators_count = rng.gen_range(0, 5);
let rewards_count = rng.gen_range(0, 5);
Some(EraEnd {
equivocators: iter::repeat_with(|| PublicKey::from(&SecretKey::ed25519(rng.gen())))
.take(equivocators_count)
.collect(),
rewards: iter::repeat_with(|| {
let pub_key = PublicKey::from(&SecretKey::ed25519(rng.gen()));
let reward = rng.gen_range(1, BLOCK_REWARD + 1);
(pub_key, reward)
})
.take(rewards_count)
.collect(),
})
} else {
None
};
let era = rng.gen_range(0, 5);
let secret_key: SecretKey = SecretKey::ed25519(rng.gen());
let public_key = PublicKey::from(&secret_key);
FinalizedBlock::new(
proto_block,
timestamp,
era_end,
EraId(era),
era * 10 + rng.gen_range(0, 10),
public_key,
)
}
}
impl DocExample for FinalizedBlock {
fn doc_example() -> &'static Self {
&*FINALIZED_BLOCK
}
}
impl From<BlockHeader> for FinalizedBlock {
fn from(header: BlockHeader) -> Self {
let proto_block =
ProtoBlock::new(header.deploy_hashes().clone(), vec![], header.random_bit);
FinalizedBlock {
proto_block,
timestamp: header.timestamp,
era_end: header.era_end,
era_id: header.era_id,
height: header.height,
proposer: header.proposer,
}
}
}
impl Display for FinalizedBlock {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"finalized block {:10} in era {:?}, height {}, deploys {:10}, random bit {}, \
timestamp {}",
HexFmt(self.proto_block.hash().inner()),
self.era_id,
self.height,
HexList(&self.proto_block.wasm_deploys),
self.proto_block.random_bit,
self.timestamp,
)?;
if let Some(ee) = &self.era_end {
write!(formatter, ", era_end: {}", ee)?;
}
Ok(())
}
}
#[derive(
Copy,
Clone,
DataSize,
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize,
Debug,
JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct BlockHash(Digest);
impl BlockHash {
pub fn new(hash: Digest) -> Self {
BlockHash(hash)
}
pub fn inner(&self) -> &Digest {
&self.0
}
#[cfg(test)]
pub fn random(rng: &mut TestRng) -> Self {
let hash = Digest::random(rng);
BlockHash(hash)
}
}
impl Display for BlockHash {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "block-hash({})", self.0,)
}
}
impl From<Digest> for BlockHash {
fn from(digest: Digest) -> Self {
Self(digest)
}
}
impl AsRef<[u8]> for BlockHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl ToBytes for BlockHash {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for BlockHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (hash, remainder) = Digest::from_bytes(bytes)?;
let block_hash = BlockHash(hash);
Ok((block_hash, remainder))
}
}
#[derive(Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
pub struct BlockHeader {
parent_hash: BlockHash,
state_root_hash: Digest,
body_hash: Digest,
deploy_hashes: Vec<DeployHash>,
random_bit: bool,
accumulated_seed: Digest,
era_end: Option<EraEnd>,
timestamp: Timestamp,
era_id: EraId,
height: u64,
proposer: PublicKey,
}
impl BlockHeader {
pub fn parent_hash(&self) -> &BlockHash {
&self.parent_hash
}
pub fn state_root_hash(&self) -> &Digest {
&self.state_root_hash
}
pub fn body_hash(&self) -> &Digest {
&self.body_hash
}
pub fn deploy_hashes(&self) -> &Vec<DeployHash> {
&self.deploy_hashes
}
pub fn random_bit(&self) -> bool {
self.random_bit
}
pub fn accumulated_seed(&self) -> Digest {
self.accumulated_seed
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn era_end(&self) -> Option<&EraEnd> {
self.era_end.as_ref()
}
pub fn switch_block(&self) -> bool {
self.era_end.is_some()
}
pub fn era_id(&self) -> EraId {
self.era_id
}
pub fn height(&self) -> u64 {
self.height
}
pub fn proposer(&self) -> &PublicKey {
&self.proposer
}
pub(crate) fn is_genesis_child(&self) -> bool {
self.era_id() == EraId(0) && self.height() == 0
}
fn serialize(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.to_bytes()
}
pub fn hash(&self) -> BlockHash {
let serialized_header = Self::serialize(&self)
.unwrap_or_else(|error| panic!("should serialize block header: {}", error));
BlockHash::new(hash::hash(&serialized_header))
}
}
impl Display for BlockHeader {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(
formatter,
"block header parent hash {}, post-state hash {}, body hash {}, deploys [{}], \
random bit {}, accumulated seed {}, timestamp {}",
self.parent_hash.inner(),
self.state_root_hash,
self.body_hash,
DisplayIter::new(self.deploy_hashes.iter()),
self.random_bit,
self.accumulated_seed,
self.timestamp,
)?;
if let Some(ee) = &self.era_end {
write!(formatter, ", era_end: {}", ee)?;
}
Ok(())
}
}
impl ToBytes for BlockHeader {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.parent_hash.to_bytes()?);
buffer.extend(self.state_root_hash.to_bytes()?);
buffer.extend(self.body_hash.to_bytes()?);
buffer.extend(self.deploy_hashes.to_bytes()?);
buffer.extend(self.random_bit.to_bytes()?);
buffer.extend(self.accumulated_seed.to_bytes()?);
buffer.extend(self.era_end.to_bytes()?);
buffer.extend(self.timestamp.to_bytes()?);
buffer.extend(self.era_id.to_bytes()?);
buffer.extend(self.height.to_bytes()?);
buffer.extend(self.proposer.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.parent_hash.serialized_length()
+ self.state_root_hash.serialized_length()
+ self.body_hash.serialized_length()
+ self.deploy_hashes.serialized_length()
+ self.random_bit.serialized_length()
+ self.accumulated_seed.serialized_length()
+ self.era_end.serialized_length()
+ self.timestamp.serialized_length()
+ self.era_id.serialized_length()
+ self.height.serialized_length()
+ self.proposer.serialized_length()
}
}
impl FromBytes for BlockHeader {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (parent_hash, remainder) = BlockHash::from_bytes(bytes)?;
let (state_root_hash, remainder) = Digest::from_bytes(remainder)?;
let (body_hash, remainder) = Digest::from_bytes(remainder)?;
let (deploy_hashes, remainder) = Vec::<DeployHash>::from_bytes(remainder)?;
let (random_bit, remainder) = bool::from_bytes(remainder)?;
let (accumulated_seed, remainder) = Digest::from_bytes(remainder)?;
let (era_end, remainder) = Option::<EraEnd>::from_bytes(remainder)?;
let (timestamp, remainder) = Timestamp::from_bytes(remainder)?;
let (era_id, remainder) = EraId::from_bytes(remainder)?;
let (height, remainder) = u64::from_bytes(remainder)?;
let (proposer, remainder) = PublicKey::from_bytes(remainder)?;
let block_header = BlockHeader {
parent_hash,
state_root_hash,
body_hash,
deploy_hashes,
random_bit,
accumulated_seed,
era_end,
timestamp,
era_id,
height,
proposer,
};
Ok((block_header, remainder))
}
}
#[derive(Debug)]
pub enum BlockValidationError {
SerializationError(bytesrepr::Error),
UnexpectedBodyHash {
expected_by_block_header: Digest,
actual: Digest,
},
UnexpectedBlockHash {
expected_by_block: BlockHash,
actual: BlockHash,
},
}
impl Display for BlockValidationError {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "{:?}", self)
}
}
impl From<bytesrepr::Error> for BlockValidationError {
fn from(err: bytesrepr::Error) -> Self {
BlockValidationError::SerializationError(err)
}
}
#[derive(DataSize, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Block {
hash: BlockHash,
header: BlockHeader,
body: (),
proofs: BTreeMap<PublicKey, Signature>,
}
impl Block {
pub(crate) fn new(
parent_hash: BlockHash,
parent_seed: Digest,
state_root_hash: Digest,
finalized_block: FinalizedBlock,
) -> Self {
let body = ();
let serialized_body = Self::serialize_body(&body)
.unwrap_or_else(|error| panic!("should serialize block body: {}", error));
let body_hash = hash::hash(&serialized_body);
let era_id = finalized_block.era_id();
let height = finalized_block.height();
let mut accumulated_seed = [0; Digest::LENGTH];
let mut hasher = VarBlake2b::new(Digest::LENGTH).expect("should create hasher");
hasher.update(parent_seed);
hasher.update([finalized_block.proto_block.random_bit as u8]);
hasher.finalize_variable(|slice| {
accumulated_seed.copy_from_slice(slice);
});
let header = BlockHeader {
parent_hash,
state_root_hash,
body_hash,
deploy_hashes: finalized_block
.proto_block
.wasm_deploys
.into_iter()
.chain(finalized_block.proto_block.transfers.into_iter())
.collect(),
random_bit: finalized_block.proto_block.random_bit,
accumulated_seed: accumulated_seed.into(),
era_end: finalized_block.era_end,
timestamp: finalized_block.timestamp,
era_id,
height,
proposer: finalized_block.proposer,
};
let hash = header.hash();
Block {
hash,
header,
body,
proofs: BTreeMap::new(),
}
}
pub(crate) fn header(&self) -> &BlockHeader {
&self.header
}
pub(crate) fn take_header(self) -> BlockHeader {
self.header
}
pub fn hash(&self) -> &BlockHash {
&self.hash
}
pub(crate) fn state_root_hash(&self) -> &Digest {
self.header.state_root_hash()
}
pub fn deploy_hashes(&self) -> &Vec<DeployHash> {
self.header.deploy_hashes()
}
pub fn height(&self) -> u64 {
self.header.height()
}
pub(crate) fn append_proof(
&mut self,
pub_key: PublicKey,
proof: Signature,
) -> Option<Signature> {
self.proofs.insert(pub_key, proof)
}
pub fn proofs(&self) -> &BTreeMap<PublicKey, Signature> {
&self.proofs
}
fn serialize_body(body: &()) -> Result<Vec<u8>, bytesrepr::Error> {
body.to_bytes()
}
pub fn verify(&self) -> Result<(), BlockValidationError> {
let serialized_body = Block::serialize_body(&self.body)?;
let actual_body_hash = hash::hash(&serialized_body);
if self.header.body_hash != actual_body_hash {
return Err(BlockValidationError::UnexpectedBodyHash {
expected_by_block_header: self.header.body_hash,
actual: actual_body_hash,
});
}
let actual_header_hash = self.header.hash();
if self.hash != actual_header_hash {
return Err(BlockValidationError::UnexpectedBlockHash {
expected_by_block: self.hash,
actual: actual_header_hash,
});
}
Ok(())
}
#[cfg(test)]
pub fn set_height(&mut self, height: u64) -> &mut Self {
self.header.height = height;
self
}
#[cfg(test)]
pub fn random(rng: &mut TestRng) -> Self {
let parent_hash = BlockHash::new(Digest::random(rng));
let state_root_hash = Digest::random(rng);
let finalized_block = FinalizedBlock::random(rng);
let parent_seed = Digest::random(rng);
let mut block = Block::new(parent_hash, parent_seed, state_root_hash, finalized_block);
let signatures_count = rng.gen_range(0, 11);
for _ in 0..signatures_count {
let secret_key = SecretKey::random(rng);
let public_key = PublicKey::from(&secret_key);
let signature = crypto::sign(block.hash.inner(), &secret_key, &public_key, rng);
block.append_proof(public_key, signature);
}
block
}
}
impl DocExample for Block {
fn doc_example() -> &'static Self {
&*BLOCK
}
}
impl Display for Block {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"executed block {}, parent hash {}, post-state hash {}, body hash {}, deploys [{}], \
random bit {}, timestamp {}, era_id {}, height {}, proofs count {}",
self.hash.inner(),
self.header.parent_hash.inner(),
self.header.state_root_hash,
self.header.body_hash,
DisplayIter::new(self.header.deploy_hashes.iter()),
self.header.random_bit,
self.header.timestamp,
self.header.era_id.0,
self.header.height,
self.proofs.len()
)?;
if let Some(ee) = &self.header.era_end {
write!(formatter, ", era_end: {}", ee)?;
}
Ok(())
}
}
impl ToBytes for Block {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.hash.to_bytes()?);
buffer.extend(self.header.to_bytes()?);
buffer.extend(self.proofs.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.hash.serialized_length()
+ self.header.serialized_length()
+ self.proofs.serialized_length()
}
}
impl FromBytes for Block {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (hash, remainder) = BlockHash::from_bytes(bytes)?;
let (header, remainder) = BlockHeader::from_bytes(remainder)?;
let (proofs, remainder) = BTreeMap::<PublicKey, Signature>::from_bytes(remainder)?;
let block = Block {
hash,
header,
body: (),
proofs,
};
Ok((block, remainder))
}
}
impl BlockLike for Block {
fn deploys(&self) -> Vec<&DeployHash> {
self.deploy_hashes().iter().collect()
}
}
impl BlockLike for BlockHeader {
fn deploys(&self) -> Vec<&DeployHash> {
self.deploy_hashes().iter().collect()
}
}
impl Item for Block {
type Id = BlockHash;
const TAG: Tag = Tag::Block;
const ID_IS_COMPLETE_ITEM: bool = false;
fn id(&self) -> Self::Id {
*self.hash()
}
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BlockByHeight {
Absent(u64),
Block(Box<Block>),
}
impl From<Block> for BlockByHeight {
fn from(block: Block) -> Self {
BlockByHeight::new(block)
}
}
impl BlockByHeight {
pub fn new(block: Block) -> Self {
BlockByHeight::Block(Box::new(block))
}
pub fn height(&self) -> u64 {
match self {
BlockByHeight::Absent(height) => *height,
BlockByHeight::Block(block) => block.height(),
}
}
}
impl Display for BlockByHeight {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BlockByHeight::Absent(height) => write!(f, "Block at height {} was absent.", height),
BlockByHeight::Block(block) => {
let hash: BlockHash = block.header().hash();
write!(f, "Block at {} with hash {} found.", block.height(), hash)
}
}
}
}
impl Item for BlockByHeight {
type Id = u64;
const TAG: Tag = Tag::BlockByHeight;
const ID_IS_COMPLETE_ITEM: bool = false;
fn id(&self) -> Self::Id {
self.height()
}
}
pub(crate) mod json_compatibility {
use super::*;
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
struct Reward {
validator: PublicKey,
amount: u64,
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
struct JsonEraEnd {
equivocators: Vec<PublicKey>,
rewards: Vec<Reward>,
}
impl From<EraEnd> for JsonEraEnd {
fn from(era_end: EraEnd) -> Self {
JsonEraEnd {
equivocators: era_end.equivocators,
rewards: era_end
.rewards
.into_iter()
.map(|(validator, amount)| Reward { validator, amount })
.collect(),
}
}
}
impl From<JsonEraEnd> for EraEnd {
fn from(era_end: JsonEraEnd) -> Self {
let equivocators = era_end.equivocators;
let rewards = era_end
.rewards
.into_iter()
.map(|reward| (reward.validator, reward.amount))
.collect();
EraEnd {
equivocators,
rewards,
}
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
struct JsonBlockHeader {
parent_hash: BlockHash,
state_root_hash: Digest,
body_hash: Digest,
deploy_hashes: Vec<DeployHash>,
random_bit: bool,
accumulated_seed: Digest,
era_end: Option<JsonEraEnd>,
timestamp: Timestamp,
era_id: EraId,
height: u64,
proposer: PublicKey,
}
impl From<BlockHeader> for JsonBlockHeader {
fn from(block_header: BlockHeader) -> Self {
JsonBlockHeader {
parent_hash: block_header.parent_hash,
state_root_hash: block_header.state_root_hash,
body_hash: block_header.body_hash,
deploy_hashes: block_header.deploy_hashes,
random_bit: block_header.random_bit,
accumulated_seed: block_header.accumulated_seed,
era_end: block_header.era_end.map(JsonEraEnd::from),
timestamp: block_header.timestamp,
era_id: block_header.era_id,
height: block_header.height,
proposer: block_header.proposer,
}
}
}
impl From<JsonBlockHeader> for BlockHeader {
fn from(block_header: JsonBlockHeader) -> Self {
BlockHeader {
parent_hash: block_header.parent_hash,
state_root_hash: block_header.state_root_hash,
body_hash: block_header.body_hash,
deploy_hashes: block_header.deploy_hashes,
random_bit: block_header.random_bit,
accumulated_seed: block_header.accumulated_seed,
era_end: block_header.era_end.map(EraEnd::from),
timestamp: block_header.timestamp,
era_id: block_header.era_id,
height: block_header.height,
proposer: block_header.proposer,
}
}
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct JsonBlock {
hash: BlockHash,
header: JsonBlockHeader,
body: (),
proofs: Vec<JsonProof>,
}
impl JsonBlock {
pub fn deploy_hashes(&self) -> &Vec<DeployHash> {
&self.header.deploy_hashes
}
}
impl From<Block> for JsonBlock {
fn from(block: Block) -> Self {
JsonBlock {
hash: block.hash,
header: JsonBlockHeader::from(block.header),
body: block.body,
proofs: block.proofs.into_iter().map(JsonProof::from).collect(),
}
}
}
impl From<JsonBlock> for Block {
fn from(block: JsonBlock) -> Self {
Block {
hash: block.hash,
header: BlockHeader::from(block.header),
body: block.body,
proofs: block.proofs.into_iter().map(JsonProof::into).collect(),
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct JsonProof {
public_key: PublicKey,
signature: Signature,
}
impl From<(PublicKey, Signature)> for JsonProof {
fn from((public_key, signature): (PublicKey, Signature)) -> JsonProof {
JsonProof {
public_key,
signature,
}
}
}
impl From<JsonProof> for (PublicKey, Signature) {
fn from(proof: JsonProof) -> (PublicKey, Signature) {
(proof.public_key, proof.signature)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, DataSize, PartialEq, Eq)]
pub struct FinalitySignature {
pub block_hash: BlockHash,
pub era_id: EraId,
pub signature: Signature,
pub public_key: PublicKey,
}
impl FinalitySignature {
pub fn new(
block_hash: BlockHash,
era_id: EraId,
secret_key: &SecretKey,
public_key: PublicKey,
rng: &mut NodeRng,
) -> Self {
let mut bytes = block_hash.inner().to_vec();
bytes.extend_from_slice(&era_id.0.to_le_bytes());
let signature = crypto::sign(bytes, &secret_key, &public_key, rng);
FinalitySignature {
block_hash,
era_id,
signature,
public_key,
}
}
pub fn verify(&self) -> crypto::Result<()> {
let mut bytes = self.block_hash.inner().to_vec();
bytes.extend_from_slice(&self.era_id.0.to_le_bytes());
crypto::verify(bytes, &self.signature, &self.public_key)
}
}
impl Display for FinalitySignature {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"finality signature for block hash {}, from {}",
&self.block_hash, &self.public_key
)
}
}
#[cfg(test)]
mod tests {
use casper_types::bytesrepr;
use super::*;
use crate::testing::TestRng;
use std::rc::Rc;
#[test]
fn json_block_roundtrip() {
let mut rng = crate::new_rng();
let block = Block::random(&mut rng);
let json_string = serde_json::to_string_pretty(&block).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(block, decoded);
}
#[test]
fn json_finalized_block_roundtrip() {
let mut rng = crate::new_rng();
let finalized_block = FinalizedBlock::random(&mut rng);
let json_string = serde_json::to_string_pretty(&finalized_block).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(finalized_block, decoded);
}
#[test]
fn bytesrepr_roundtrip() {
let mut rng = TestRng::new();
let block = Block::random(&mut rng);
bytesrepr::test_serialization_roundtrip(&block);
}
#[test]
fn bytesrepr_roundtrip_era_end() {
let mut rng = TestRng::new();
let loop_iterations = 50;
for _ in 0..loop_iterations {
let finalized_block = FinalizedBlock::random(&mut rng);
if let Some(era_end) = finalized_block.era_end() {
bytesrepr::test_serialization_roundtrip(era_end);
}
}
}
#[test]
fn random_block_check() {
let mut rng = TestRng::from_seed([1u8; 16]);
let loop_iterations = 50;
for _ in 0..loop_iterations {
Block::random(&mut rng)
.verify()
.expect("block hash should check");
}
}
#[test]
fn block_check_bad_body_hash_sad_path() {
let mut rng = TestRng::from_seed([2u8; 16]);
let mut block = Block::random(&mut rng);
let bogus_block_hash = hash::hash(&[0xde, 0xad, 0xbe, 0xef]);
block.header.body_hash = bogus_block_hash;
let serialized_body =
Block::serialize_body(&block.body).expect("Could not serialize block body");
let actual_body_hash = hash::hash(&serialized_body);
match block.verify() {
Err(BlockValidationError::UnexpectedBodyHash {
expected_by_block_header,
actual,
}) if expected_by_block_header == bogus_block_hash && actual == actual_body_hash => {}
unexpected => panic!("Bad check response: {:?}", unexpected),
}
}
#[test]
fn block_check_bad_block_hash_sad_path() {
let mut rng = TestRng::from_seed([3u8; 16]);
let mut block = Block::random(&mut rng);
let bogus_block_hash: BlockHash = hash::hash(&[0xde, 0xad, 0xbe, 0xef]).into();
block.hash = bogus_block_hash;
let actual_block_hash = block.header.hash();
match block.verify() {
Err(BlockValidationError::UnexpectedBlockHash {
expected_by_block,
actual,
}) if expected_by_block == bogus_block_hash && actual == actual_block_hash => {}
unexpected => panic!("Bad check response: {:?}", unexpected),
}
}
#[test]
fn finality_signature() {
let mut rng = TestRng::new();
let block = Block::random(&mut rng);
let (secret_key, public_key) = crypto::generate_ed25519_keypair();
let secret_rc = Rc::new(secret_key);
let era_id = EraId(1);
let fs = FinalitySignature::new(*block.hash(), era_id, &secret_rc, public_key, &mut rng);
assert!(fs.verify().is_ok());
let signature = fs.signature;
let fs_manufactured = FinalitySignature {
block_hash: *block.hash(),
era_id: EraId(2),
signature,
public_key,
};
assert!(fs_manufactured.verify().is_err());
}
}