use std::collections::HashSet;
use casper_types::{Gas, PublicKey};
use datasize::DataSize;
use num_traits::Zero;
use thiserror::Error;
use crate::{
components::block_proposer::DeployInfo,
types::{chainspec::DeployConfig, BlockPayload, DeployHash, DeployWithApprovals, Timestamp},
};
#[derive(Debug, Error)]
pub(crate) enum AddError {
#[error("would exceed maximum transfer count per block")]
TransferCount,
#[error("would exceed maximum deploy count per block")]
DeployCount,
#[error("would exceed maximum approval count per block")]
ApprovalCount,
#[error("would exceed maximum gas per block")]
GasLimit,
#[error("would exceed maximum block size")]
BlockSize,
#[error("duplicate deploy")]
Duplicate,
#[error("payment amount could not be converted to gas")]
InvalidGasAmount,
#[error("deploy is not valid in this context")]
InvalidDeploy,
}
#[derive(Clone, DataSize, Debug)]
pub struct AppendableBlock {
deploy_config: DeployConfig,
deploys: Vec<DeployWithApprovals>,
transfers: Vec<DeployWithApprovals>,
deploy_and_transfer_set: HashSet<DeployHash>,
timestamp: Timestamp,
#[data_size(skip)]
total_gas: Gas,
total_size: usize,
total_approvals: usize,
}
impl AppendableBlock {
pub(crate) fn new(deploy_config: DeployConfig, timestamp: Timestamp) -> Self {
AppendableBlock {
deploy_config,
deploys: Vec::new(),
transfers: Vec::new(),
timestamp,
deploy_and_transfer_set: HashSet::new(),
total_gas: Gas::zero(),
total_size: 0,
total_approvals: 0,
}
}
pub(crate) fn total_size(&self) -> usize {
self.total_size
}
pub(crate) fn add_transfer(
&mut self,
transfer: DeployWithApprovals,
deploy_info: &DeployInfo,
) -> Result<(), AddError> {
if self
.deploy_and_transfer_set
.contains(transfer.deploy_hash())
{
return Err(AddError::Duplicate);
}
if !deploy_info
.header
.is_valid(&self.deploy_config, self.timestamp)
{
return Err(AddError::InvalidDeploy);
}
if self.has_max_transfer_count() {
return Err(AddError::TransferCount);
}
if self.would_exceed_max_approvals(transfer.approvals().len()) {
return Err(AddError::ApprovalCount);
}
self.deploy_and_transfer_set.insert(*transfer.deploy_hash());
self.total_approvals += transfer.approvals().len();
self.transfers.push(transfer);
Ok(())
}
pub(crate) fn add_deploy(
&mut self,
deploy: DeployWithApprovals,
deploy_info: &DeployInfo,
) -> Result<(), AddError> {
if self.deploy_and_transfer_set.contains(deploy.deploy_hash()) {
return Err(AddError::Duplicate);
}
if !deploy_info
.header
.is_valid(&self.deploy_config, self.timestamp)
{
return Err(AddError::InvalidDeploy);
}
if self.has_max_deploy_count() {
return Err(AddError::DeployCount);
}
if self.would_exceed_max_approvals(deploy.approvals().len()) {
return Err(AddError::ApprovalCount);
}
let new_total_size = self
.total_size
.checked_add(deploy_info.size)
.filter(|size| *size <= self.deploy_config.max_block_size as usize)
.ok_or(AddError::BlockSize)?;
let payment_amount = deploy_info.payment_amount;
let gas_price = deploy_info.header.gas_price();
let gas = Gas::from_motes(payment_amount, gas_price).ok_or(AddError::InvalidGasAmount)?;
let new_total_gas = self.total_gas.checked_add(gas).ok_or(AddError::GasLimit)?;
if new_total_gas > Gas::from(self.deploy_config.block_gas_limit) {
return Err(AddError::GasLimit);
}
self.total_gas = new_total_gas;
self.total_size = new_total_size;
self.total_approvals += deploy.approvals().len();
self.deploy_and_transfer_set.insert(*deploy.deploy_hash());
self.deploys.push(deploy);
Ok(())
}
pub(crate) fn into_block_payload(
self,
accusations: Vec<PublicKey>,
random_bit: bool,
) -> BlockPayload {
let AppendableBlock {
deploys, transfers, ..
} = self;
BlockPayload::new(deploys, transfers, accusations, random_bit)
}
fn has_max_transfer_count(&self) -> bool {
self.transfers.len() == self.deploy_config.block_max_transfer_count as usize
}
fn has_max_deploy_count(&self) -> bool {
self.deploys.len() == self.deploy_config.block_max_deploy_count as usize
}
fn would_exceed_max_approvals(&self, additional_approvals: usize) -> bool {
self.total_approvals + additional_approvals
> self.deploy_config.block_max_approval_count as usize
}
}