use crate::block::BlockValidityError::{
InvalidChallengeRoot, InvalidChunkHeaderRoot, InvalidChunkMask, InvalidReceiptRoot,
InvalidStateRoot, InvalidTransactionRoot,
};
pub use crate::block_header::*;
use crate::challenge::{Challenges, ChallengesResult};
use crate::checked_feature;
use crate::hash::{hash, CryptoHash};
use crate::merkle::{merklize, verify_path, MerklePath};
use crate::num_rational::Rational32;
use crate::sharding::{
ChunkHashHeight, EncodedShardChunk, ReedSolomonWrapper, ShardChunk, ShardChunkHeader,
ShardChunkHeaderV1,
};
use crate::static_clock::StaticClock;
use crate::types::{Balance, BlockHeight, EpochId, Gas, NumBlocks, StateRoot};
use crate::utils::to_timestamp;
use crate::validator_signer::{EmptyValidatorSigner, ValidatorSigner};
use crate::version::{ProtocolVersion, SHARD_CHUNK_HEADER_UPGRADE_VERSION};
use borsh::{BorshDeserialize, BorshSerialize};
use chrono::{DateTime, Utc};
use primitive_types::U256;
use std::ops::Index;
use std::sync::Arc;
use unc_crypto::Signature;
use unc_primitives_core::types::ShardId;
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, Default)]
pub struct GenesisId {
pub chain_id: String,
pub hash: CryptoHash,
}
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
pub enum BlockValidityError {
InvalidStateRoot,
InvalidReceiptRoot,
InvalidChunkHeaderRoot,
InvalidTransactionRoot,
InvalidChunkMask,
InvalidChallengeRoot,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
pub struct BlockV1 {
pub header: BlockHeader,
pub chunks: Vec<ShardChunkHeaderV1>,
pub challenges: Challenges,
pub vrf_value: unc_crypto::vrf::Value,
pub vrf_proof: unc_crypto::vrf::Proof,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
pub struct BlockV2 {
pub header: BlockHeader,
pub chunks: Vec<ShardChunkHeader>,
pub challenges: Challenges,
pub vrf_value: unc_crypto::vrf::Value,
pub vrf_proof: unc_crypto::vrf::Proof,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
pub struct BlockV3 {
pub header: BlockHeader,
pub body: BlockBody,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
pub struct BlockBody {
pub chunks: Vec<ShardChunkHeader>,
pub challenges: Challenges,
pub vrf_value: unc_crypto::vrf::Value,
pub vrf_proof: unc_crypto::vrf::Proof,
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
pub enum Block {
BlockV1(Arc<BlockV1>),
BlockV2(Arc<BlockV2>),
BlockV3(Arc<BlockV3>),
}
pub fn genesis_chunks(
state_roots: Vec<StateRoot>,
shard_ids: &[ShardId],
initial_gas_limit: Gas,
genesis_height: BlockHeight,
genesis_protocol_version: ProtocolVersion,
) -> Vec<ShardChunk> {
let mut rs = ReedSolomonWrapper::new(1, 2);
let state_roots = if state_roots.len() == shard_ids.len() {
state_roots
} else {
assert_eq!(state_roots.len(), 1);
std::iter::repeat(state_roots[0]).take(shard_ids.len()).collect()
};
shard_ids
.into_iter()
.zip(state_roots)
.map(|(&shard_id, state_root)| {
let (encoded_chunk, _) = EncodedShardChunk::new(
CryptoHash::default(),
state_root,
CryptoHash::default(),
genesis_height,
shard_id,
&mut rs,
0,
initial_gas_limit,
0,
CryptoHash::default(),
vec![],
vec![],
vec![],
&[],
CryptoHash::default(),
&EmptyValidatorSigner::default(),
genesis_protocol_version,
)
.expect("Failed to decode genesis chunk");
let mut chunk = encoded_chunk.decode_chunk(1).expect("Failed to decode genesis chunk");
chunk.set_height_included(genesis_height);
chunk
})
.collect()
}
impl Block {
fn block_from_protocol_version(
this_epoch_protocol_version: ProtocolVersion,
next_epoch_protocol_version: ProtocolVersion,
header: BlockHeader,
body: BlockBody,
) -> Block {
if next_epoch_protocol_version < SHARD_CHUNK_HEADER_UPGRADE_VERSION {
let legacy_chunks = body
.chunks
.into_iter()
.map(|chunk| match chunk {
ShardChunkHeader::V1(header) => header,
ShardChunkHeader::V2(_) => panic!(
"Attempted to include VersionedShardChunkHeaderV2 in old protocol version"
),
ShardChunkHeader::V3(_) => panic!(
"Attempted to include VersionedShardChunkHeaderV3 in old protocol version"
),
})
.collect();
Block::BlockV1(Arc::new(BlockV1 {
header,
chunks: legacy_chunks,
challenges: body.challenges,
vrf_value: body.vrf_value,
vrf_proof: body.vrf_proof,
}))
} else if !checked_feature!("stable", BlockHeaderV4, this_epoch_protocol_version) {
Block::BlockV2(Arc::new(BlockV2 {
header,
chunks: body.chunks,
challenges: body.challenges,
vrf_value: body.vrf_value,
vrf_proof: body.vrf_proof,
}))
} else {
Block::BlockV3(Arc::new(BlockV3 { header, body }))
}
}
pub fn genesis(
genesis_protocol_version: ProtocolVersion,
chunks: Vec<ShardChunkHeader>,
timestamp: DateTime<Utc>,
height: BlockHeight,
initial_gas_price: Balance,
initial_total_supply: Balance,
next_bp_hash: CryptoHash,
) -> Self {
let challenges = vec![];
for chunk in &chunks {
assert_eq!(chunk.height_included(), height);
}
let vrf_value = unc_crypto::vrf::Value([0; 32]);
let vrf_proof = unc_crypto::vrf::Proof([0; 64]);
let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
let header = BlockHeader::genesis(
genesis_protocol_version,
height,
Block::compute_state_root(&body.chunks),
Block::compute_block_body_hash_impl(&body),
Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
Block::compute_chunk_headers_root(&body.chunks).0,
Block::compute_chunk_tx_root(&body.chunks),
body.chunks.len() as u64,
Block::compute_challenges_root(&body.challenges),
timestamp,
initial_gas_price,
initial_total_supply,
next_bp_hash,
);
Self::block_from_protocol_version(
genesis_protocol_version,
genesis_protocol_version,
header,
body,
)
}
pub fn produce(
this_epoch_protocol_version: ProtocolVersion,
next_epoch_protocol_version: ProtocolVersion,
prev: &BlockHeader,
height: BlockHeight,
block_ordinal: NumBlocks,
chunks: Vec<ShardChunkHeader>,
epoch_id: EpochId,
next_epoch_id: EpochId,
epoch_sync_data_hash: Option<CryptoHash>,
approvals: Vec<Option<Box<Signature>>>,
gas_price_adjustment_rate: Rational32,
min_gas_price: Balance,
max_gas_price: Balance,
minted_amount: Option<Balance>,
challenges_result: ChallengesResult,
challenges: Challenges,
signer: &dyn ValidatorSigner,
next_bp_hash: CryptoHash,
block_merkle_root: CryptoHash,
timestamp_override: Option<DateTime<chrono::Utc>>,
) -> Self {
let mut prev_validator_power_proposals = vec![];
let mut prev_validator_pledge_proposals = vec![];
let mut gas_used = 0;
let mut chunk_mask = vec![];
let mut balance_burnt = 0;
let mut gas_limit = 0;
for chunk in chunks.iter() {
if chunk.height_included() == height {
prev_validator_power_proposals.extend(chunk.prev_validator_power_proposals());
prev_validator_pledge_proposals.extend(chunk.prev_validator_pledge_proposals());
gas_used += chunk.prev_gas_used();
gas_limit += chunk.gas_limit();
balance_burnt += chunk.prev_balance_burnt();
chunk_mask.push(true);
} else {
chunk_mask.push(false);
}
}
let next_gas_price = Self::compute_next_gas_price(
prev.next_gas_price(),
gas_used,
gas_limit,
gas_price_adjustment_rate,
min_gas_price,
max_gas_price,
);
let new_total_supply = prev.total_supply() + minted_amount.unwrap_or(0) - balance_burnt;
let now = to_timestamp(timestamp_override.unwrap_or_else(StaticClock::utc));
let time = if now <= prev.raw_timestamp() { prev.raw_timestamp() + 1 } else { now };
let (vrf_value, vrf_proof) = signer.compute_vrf_with_proof(prev.random_value().as_ref());
let random_value = hash(vrf_value.0.as_ref());
let last_ds_final_block =
if height == prev.height() + 1 { prev.hash() } else { prev.last_ds_final_block() };
let last_final_block =
if height == prev.height() + 1 && prev.last_ds_final_block() == prev.prev_hash() {
prev.prev_hash()
} else {
prev.last_final_block()
};
match prev {
BlockHeader::BlockHeaderV1(_) => debug_assert_eq!(prev.block_ordinal(), 0),
BlockHeader::BlockHeaderV2(_) => debug_assert_eq!(prev.block_ordinal(), 0),
BlockHeader::BlockHeaderV3(_) => {
debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
}
BlockHeader::BlockHeaderV4(_) => {
debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
}
};
let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
let header = BlockHeader::new(
this_epoch_protocol_version,
next_epoch_protocol_version,
height,
*prev.hash(),
Block::compute_block_body_hash_impl(&body),
Block::compute_state_root(&body.chunks),
Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
Block::compute_chunk_headers_root(&body.chunks).0,
Block::compute_chunk_tx_root(&body.chunks),
Block::compute_outcome_root(&body.chunks),
time,
Block::compute_challenges_root(&body.challenges),
random_value,
prev_validator_power_proposals,
prev_validator_pledge_proposals,
chunk_mask,
block_ordinal,
epoch_id,
next_epoch_id,
next_gas_price,
new_total_supply,
challenges_result,
signer,
*last_final_block,
*last_ds_final_block,
epoch_sync_data_hash,
approvals,
next_bp_hash,
block_merkle_root,
prev.height(),
);
Self::block_from_protocol_version(
this_epoch_protocol_version,
next_epoch_protocol_version,
header,
body,
)
}
pub fn verify_total_supply(
&self,
prev_total_supply: Balance,
minted_amount: Option<Balance>,
) -> bool {
let mut balance_burnt = 0;
for chunk in self.chunks().iter() {
if chunk.height_included() == self.header().height() {
balance_burnt += chunk.prev_balance_burnt();
}
}
let new_total_supply = prev_total_supply + minted_amount.unwrap_or(0) - balance_burnt;
self.header().total_supply() == new_total_supply
}
pub fn verify_gas_price(
&self,
gas_price: Balance,
min_gas_price: Balance,
max_gas_price: Balance,
gas_price_adjustment_rate: Rational32,
) -> bool {
let gas_used = Self::compute_gas_used(self.chunks().iter(), self.header().height());
let gas_limit = Self::compute_gas_limit(self.chunks().iter(), self.header().height());
let expected_price = Self::compute_next_gas_price(
gas_price,
gas_used,
gas_limit,
gas_price_adjustment_rate,
min_gas_price,
max_gas_price,
);
self.header().next_gas_price() == expected_price
}
pub fn compute_next_gas_price(
gas_price: Balance,
gas_used: Gas,
gas_limit: Gas,
gas_price_adjustment_rate: Rational32,
min_gas_price: Balance,
max_gas_price: Balance,
) -> Balance {
if gas_limit == 0 {
return gas_price;
}
let gas_used = u128::from(gas_used);
let gas_limit = u128::from(gas_limit);
let adjustment_rate_numer = *gas_price_adjustment_rate.numer() as u128;
let adjustment_rate_denom = *gas_price_adjustment_rate.denom() as u128;
let numerator = 2 * adjustment_rate_denom * gas_limit
+ 2 * adjustment_rate_numer * gas_used
- adjustment_rate_numer * gas_limit;
let denominator = 2 * adjustment_rate_denom * gas_limit;
let next_gas_price =
U256::from(gas_price) * U256::from(numerator) / U256::from(denominator);
next_gas_price.clamp(U256::from(min_gas_price), U256::from(max_gas_price)).as_u128()
}
pub fn compute_state_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
) -> CryptoHash {
merklize(
&chunks.into_iter().map(|chunk| chunk.prev_state_root()).collect::<Vec<CryptoHash>>(),
)
.0
}
pub fn compute_block_body_hash_impl(body: &BlockBody) -> CryptoHash {
CryptoHash::hash_borsh(body)
}
pub fn compute_chunk_prev_outgoing_receipts_root<
'a,
T: IntoIterator<Item = &'a ShardChunkHeader>,
>(
chunks: T,
) -> CryptoHash {
merklize(
&chunks
.into_iter()
.map(|chunk| chunk.prev_outgoing_receipts_root())
.collect::<Vec<CryptoHash>>(),
)
.0
}
pub fn compute_chunk_headers_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
) -> (CryptoHash, Vec<MerklePath>) {
merklize(
&chunks
.into_iter()
.map(|chunk| ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()))
.collect::<Vec<ChunkHashHeight>>(),
)
}
pub fn compute_chunk_tx_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
) -> CryptoHash {
merklize(&chunks.into_iter().map(|chunk| chunk.tx_root()).collect::<Vec<CryptoHash>>()).0
}
pub fn compute_outcome_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
) -> CryptoHash {
merklize(
&chunks.into_iter().map(|chunk| chunk.prev_outcome_root()).collect::<Vec<CryptoHash>>(),
)
.0
}
pub fn compute_challenges_root(challenges: &Challenges) -> CryptoHash {
merklize(&challenges.iter().map(|challenge| challenge.hash).collect::<Vec<CryptoHash>>()).0
}
pub fn compute_gas_used<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
height: BlockHeight,
) -> Gas {
chunks.into_iter().fold(0, |acc, chunk| {
if chunk.height_included() == height {
acc + chunk.prev_gas_used()
} else {
acc
}
})
}
pub fn compute_gas_limit<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
chunks: T,
height: BlockHeight,
) -> Gas {
chunks.into_iter().fold(0, |acc, chunk| {
if chunk.height_included() == height {
acc + chunk.gas_limit()
} else {
acc
}
})
}
pub fn validate_chunk_header_proof(
chunk: &ShardChunkHeader,
chunk_root: &CryptoHash,
merkle_path: &MerklePath,
) -> bool {
verify_path(
*chunk_root,
merkle_path,
&ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()),
)
}
#[inline]
pub fn header(&self) -> &BlockHeader {
match self {
Block::BlockV1(block) => &block.header,
Block::BlockV2(block) => &block.header,
Block::BlockV3(block) => &block.header,
}
}
pub fn chunks(&self) -> ChunksCollection {
match self {
Block::BlockV1(block) => ChunksCollection::V1(
block.chunks.iter().map(|h| ShardChunkHeader::V1(h.clone())).collect(),
),
Block::BlockV2(block) => ChunksCollection::V2(&block.chunks),
Block::BlockV3(block) => ChunksCollection::V2(&block.body.chunks),
}
}
#[inline]
pub fn challenges(&self) -> &Challenges {
match self {
Block::BlockV1(block) => &block.challenges,
Block::BlockV2(block) => &block.challenges,
Block::BlockV3(block) => &block.body.challenges,
}
}
#[inline]
pub fn vrf_value(&self) -> &unc_crypto::vrf::Value {
match self {
Block::BlockV1(block) => &block.vrf_value,
Block::BlockV2(block) => &block.vrf_value,
Block::BlockV3(block) => &block.body.vrf_value,
}
}
#[inline]
pub fn vrf_proof(&self) -> &unc_crypto::vrf::Proof {
match self {
Block::BlockV1(block) => &block.vrf_proof,
Block::BlockV2(block) => &block.vrf_proof,
Block::BlockV3(block) => &block.body.vrf_proof,
}
}
pub fn hash(&self) -> &CryptoHash {
self.header().hash()
}
pub fn compute_block_body_hash(&self) -> Option<CryptoHash> {
match self {
Block::BlockV1(_) => None,
Block::BlockV2(_) => None,
Block::BlockV3(block) => Some(Self::compute_block_body_hash_impl(&block.body)),
}
}
pub fn check_validity(&self) -> Result<(), BlockValidityError> {
let state_root = Block::compute_state_root(self.chunks().iter());
if self.header().prev_state_root() != &state_root {
return Err(InvalidStateRoot);
}
let chunk_receipts_root =
Block::compute_chunk_prev_outgoing_receipts_root(self.chunks().iter());
if self.header().prev_chunk_outgoing_receipts_root() != &chunk_receipts_root {
return Err(InvalidReceiptRoot);
}
let chunk_headers_root = Block::compute_chunk_headers_root(self.chunks().iter()).0;
if self.header().chunk_headers_root() != &chunk_headers_root {
return Err(InvalidChunkHeaderRoot);
}
let chunk_tx_root = Block::compute_chunk_tx_root(self.chunks().iter());
if self.header().chunk_tx_root() != &chunk_tx_root {
return Err(InvalidTransactionRoot);
}
let outcome_root = Block::compute_outcome_root(self.chunks().iter());
if self.header().outcome_root() != &outcome_root {
return Err(InvalidTransactionRoot);
}
let chunk_mask: Vec<bool> = self
.chunks()
.iter()
.map(|chunk| chunk.height_included() == self.header().height())
.collect();
if self.header().chunk_mask() != &chunk_mask[..] {
return Err(InvalidChunkMask);
}
let challenges_root = Block::compute_challenges_root(self.challenges());
if self.header().challenges_root() != &challenges_root {
return Err(InvalidChallengeRoot);
}
Ok(())
}
}
pub enum ChunksCollection<'a> {
V1(Vec<ShardChunkHeader>),
V2(&'a [ShardChunkHeader]),
}
pub struct VersionedChunksIter<'a> {
chunks: &'a [ShardChunkHeader],
curr_index: usize,
len: usize,
}
impl<'a> VersionedChunksIter<'a> {
fn new(chunks: &'a [ShardChunkHeader]) -> Self {
Self { chunks, curr_index: 0, len: chunks.len() }
}
}
impl<'a> Iterator for VersionedChunksIter<'a> {
type Item = &'a ShardChunkHeader;
fn next(&mut self) -> Option<Self::Item> {
if self.curr_index < self.len {
let item = &self.chunks[self.curr_index];
self.curr_index += 1;
Some(item)
} else {
None
}
}
}
impl<'a> ExactSizeIterator for VersionedChunksIter<'a> {
fn len(&self) -> usize {
self.len - self.curr_index
}
}
impl<'a> Index<usize> for ChunksCollection<'a> {
type Output = ShardChunkHeader;
fn index(&self, index: usize) -> &Self::Output {
match self {
ChunksCollection::V1(chunks) => &chunks[index],
ChunksCollection::V2(chunks) => &chunks[index],
}
}
}
impl<'a> ChunksCollection<'a> {
pub fn len(&self) -> usize {
match self {
ChunksCollection::V1(chunks) => chunks.len(),
ChunksCollection::V2(chunks) => chunks.len(),
}
}
pub fn iter(&'a self) -> VersionedChunksIter<'a> {
match self {
ChunksCollection::V1(chunks) => VersionedChunksIter::new(chunks),
ChunksCollection::V2(chunks) => VersionedChunksIter::new(chunks),
}
}
pub fn get(&self, index: usize) -> Option<&ShardChunkHeader> {
match self {
ChunksCollection::V1(chunks) => chunks.get(index),
ChunksCollection::V2(chunks) => chunks.get(index),
}
}
}
#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize)]
pub struct Tip {
pub height: BlockHeight,
pub last_block_hash: CryptoHash,
pub prev_block_hash: CryptoHash,
pub epoch_id: EpochId,
pub next_epoch_id: EpochId,
}
impl Tip {
pub fn from_header(header: &BlockHeader) -> Tip {
Tip {
height: header.height(),
last_block_hash: *header.hash(),
prev_block_hash: *header.prev_hash(),
epoch_id: header.epoch_id().clone(),
next_epoch_id: header.next_epoch_id().clone(),
}
}
}