use std::time::{SystemTime, UNIX_EPOCH};
use bincode;
use chia_protocol::{Coin, SpendBundle};
use crate::error::BuilderError;
use crate::merkle_util::empty_on_additions_err;
use crate::primitives::{Bytes32, Cost, Signature};
use crate::traits::BlockSigner;
use crate::types::block::L2Block;
use crate::types::header::L2BlockHeader;
use crate::{
compute_additions_root, compute_filter_hash, compute_removals_root, compute_spends_root,
DFSP_ACTIVATION_HEIGHT, EMPTY_ROOT, MAX_BLOCK_SIZE, MAX_COST_PER_BLOCK,
MAX_SLASH_PROPOSALS_PER_BLOCK, MAX_SLASH_PROPOSAL_PAYLOAD_BYTES, VERSION_V2, ZERO_HASH,
};
pub struct BlockBuilder {
pub height: u64,
pub epoch: u64,
pub parent_hash: Bytes32,
pub l1_height: u32,
pub l1_hash: Bytes32,
pub proposer_index: u32,
pub spend_bundles: Vec<SpendBundle>,
pub slash_proposal_payloads: Vec<Vec<u8>>,
pub total_cost: Cost,
pub total_fees: u64,
pub additions: Vec<Coin>,
pub removals: Vec<Bytes32>,
pub l1_collateral_coin_id: Option<Bytes32>,
pub l1_reserve_coin_id: Option<Bytes32>,
pub l1_prev_epoch_finalizer_coin_id: Option<Bytes32>,
pub l1_curr_epoch_finalizer_coin_id: Option<Bytes32>,
pub l1_network_coin_id: Option<Bytes32>,
pub collateral_registry_root: Bytes32,
pub cid_state_root: Bytes32,
pub node_registry_root: Bytes32,
pub namespace_update_root: Bytes32,
pub dfsp_finalize_commitment_root: Bytes32,
pub extension_data: Bytes32,
}
impl BlockBuilder {
#[must_use]
pub fn new(
height: u64,
epoch: u64,
parent_hash: Bytes32,
l1_height: u32,
l1_hash: Bytes32,
proposer_index: u32,
) -> Self {
Self {
height,
epoch,
parent_hash,
l1_height,
l1_hash,
proposer_index,
spend_bundles: Vec::new(),
slash_proposal_payloads: Vec::new(),
total_cost: 0,
total_fees: 0,
additions: Vec::new(),
removals: Vec::new(),
l1_collateral_coin_id: None,
l1_reserve_coin_id: None,
l1_prev_epoch_finalizer_coin_id: None,
l1_curr_epoch_finalizer_coin_id: None,
l1_network_coin_id: None,
collateral_registry_root: EMPTY_ROOT,
cid_state_root: EMPTY_ROOT,
node_registry_root: EMPTY_ROOT,
namespace_update_root: EMPTY_ROOT,
dfsp_finalize_commitment_root: EMPTY_ROOT,
extension_data: ZERO_HASH,
}
}
fn probe_header_stub(&self) -> L2BlockHeader {
let mut h = L2BlockHeader::new(
self.height,
self.epoch,
self.parent_hash,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
self.l1_height,
self.l1_hash,
self.proposer_index,
0,
0,
0,
0,
0,
0,
EMPTY_ROOT,
);
h.l1_collateral_coin_id = self.l1_collateral_coin_id;
h.l1_reserve_coin_id = self.l1_reserve_coin_id;
h.l1_prev_epoch_finalizer_coin_id = self.l1_prev_epoch_finalizer_coin_id;
h.l1_curr_epoch_finalizer_coin_id = self.l1_curr_epoch_finalizer_coin_id;
h.l1_network_coin_id = self.l1_network_coin_id;
h.collateral_registry_root = self.collateral_registry_root;
h.cid_state_root = self.cid_state_root;
h.node_registry_root = self.node_registry_root;
h.namespace_update_root = self.namespace_update_root;
h.dfsp_finalize_commitment_root = self.dfsp_finalize_commitment_root;
h.extension_data = self.extension_data;
h
}
fn serialized_l2_block_probe_len(&self, spend_bundles: &[SpendBundle]) -> usize {
let header = self.probe_header_stub();
let block = L2Block::new(
header,
spend_bundles.to_vec(),
self.slash_proposal_payloads.clone(),
Signature::default(),
);
bincode::serialize(&block)
.map(|bytes| bytes.len())
.unwrap_or(usize::MAX)
}
pub fn add_spend_bundle(
&mut self,
bundle: SpendBundle,
cost: Cost,
fee: u64,
) -> Result<(), BuilderError> {
let next_cost = self.total_cost.saturating_add(cost);
if next_cost > MAX_COST_PER_BLOCK {
return Err(BuilderError::CostBudgetExceeded {
current: self.total_cost,
addition: cost,
max: MAX_COST_PER_BLOCK,
});
}
let base_bytes = self.serialized_l2_block_probe_len(&self.spend_bundles);
let mut probe_bundles = self.spend_bundles.clone();
probe_bundles.push(bundle.clone());
let with_bytes = self.serialized_l2_block_probe_len(&probe_bundles);
if with_bytes > MAX_BLOCK_SIZE as usize {
return Err(BuilderError::SizeBudgetExceeded {
current: u32::try_from(base_bytes).unwrap_or(u32::MAX),
addition: u32::try_from(with_bytes.saturating_sub(base_bytes)).unwrap_or(u32::MAX),
max: MAX_BLOCK_SIZE,
});
}
self.additions
.extend(empty_on_additions_err(bundle.additions()));
for cs in &bundle.coin_spends {
self.removals.push(cs.coin.coin_id());
}
self.total_cost += cost;
self.total_fees += fee;
self.spend_bundles.push(bundle);
Ok(())
}
#[must_use]
pub fn remaining_cost(&self) -> Cost {
MAX_COST_PER_BLOCK.saturating_sub(self.total_cost)
}
#[must_use]
pub fn spend_bundle_count(&self) -> usize {
self.spend_bundles.len()
}
pub fn add_slash_proposal(&mut self, payload: Vec<u8>) -> Result<(), BuilderError> {
if self.slash_proposal_payloads.len() >= MAX_SLASH_PROPOSALS_PER_BLOCK as usize {
return Err(BuilderError::TooManySlashProposals {
max: MAX_SLASH_PROPOSALS_PER_BLOCK,
});
}
let len = payload.len();
if len > MAX_SLASH_PROPOSAL_PAYLOAD_BYTES as usize {
return Err(BuilderError::SlashProposalTooLarge {
size: u32::try_from(len).unwrap_or(u32::MAX),
max: MAX_SLASH_PROPOSAL_PAYLOAD_BYTES,
});
}
self.slash_proposal_payloads.push(payload);
Ok(())
}
pub fn set_l1_proofs(
&mut self,
collateral: Bytes32,
reserve: Bytes32,
prev_finalizer: Bytes32,
curr_finalizer: Bytes32,
network_coin: Bytes32,
) {
self.l1_collateral_coin_id = Some(collateral);
self.l1_reserve_coin_id = Some(reserve);
self.l1_prev_epoch_finalizer_coin_id = Some(prev_finalizer);
self.l1_curr_epoch_finalizer_coin_id = Some(curr_finalizer);
self.l1_network_coin_id = Some(network_coin);
}
pub fn set_dfsp_roots(
&mut self,
collateral_registry_root: Bytes32,
cid_state_root: Bytes32,
node_registry_root: Bytes32,
namespace_update_root: Bytes32,
dfsp_finalize_commitment_root: Bytes32,
) {
self.collateral_registry_root = collateral_registry_root;
self.cid_state_root = cid_state_root;
self.node_registry_root = node_registry_root;
self.namespace_update_root = namespace_update_root;
self.dfsp_finalize_commitment_root = dfsp_finalize_commitment_root;
}
pub fn set_extension_data(&mut self, extension_data: Bytes32) {
self.extension_data = extension_data;
}
pub fn build(
self,
state_root: Bytes32,
receipts_root: Bytes32,
signer: &dyn BlockSigner,
) -> Result<L2Block, BuilderError> {
self.build_with_dfsp_activation(state_root, receipts_root, signer, DFSP_ACTIVATION_HEIGHT)
}
pub fn build_with_dfsp_activation(
self,
state_root: Bytes32,
receipts_root: Bytes32,
signer: &dyn BlockSigner,
dfsp_activation_height: u64,
) -> Result<L2Block, BuilderError> {
if self.spend_bundles.is_empty() {
return Err(BuilderError::EmptyBlock);
}
let spends_root = compute_spends_root(&self.spend_bundles);
let additions_root = compute_additions_root(&self.additions);
let removals_root = compute_removals_root(&self.removals);
let slash_proposals_root =
L2Block::slash_proposals_root_from(&self.slash_proposal_payloads);
let filter_hash = compute_filter_hash(self.parent_hash, &self.additions, &self.removals);
let version = L2BlockHeader::protocol_version_for_height_with_activation(
self.height,
dfsp_activation_height,
);
if version == VERSION_V2 {
let dfsp = [
self.collateral_registry_root,
self.cid_state_root,
self.node_registry_root,
self.namespace_update_root,
self.dfsp_finalize_commitment_root,
];
if dfsp.iter().all(|r| *r == EMPTY_ROOT) {
return Err(BuilderError::MissingDfspRoots);
}
}
let spend_bundle_count = usize_to_u32_count(self.spend_bundles.len());
let additions_count = usize_to_u32_count(self.additions.len());
let removals_rows: usize = self
.spend_bundles
.iter()
.map(|sb| sb.coin_spends.len())
.sum();
let removals_count = usize_to_u32_count(removals_rows);
let slash_proposal_count = usize_to_u32_count(self.slash_proposal_payloads.len());
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let mut header = L2BlockHeader::new(
self.height,
self.epoch,
self.parent_hash,
state_root,
spends_root,
additions_root,
removals_root,
receipts_root,
self.l1_height,
self.l1_hash,
self.proposer_index,
spend_bundle_count,
self.total_cost,
self.total_fees,
additions_count,
removals_count,
0,
filter_hash,
);
header.version = version;
header.timestamp = timestamp;
header.slash_proposal_count = slash_proposal_count;
header.slash_proposals_root = slash_proposals_root;
header.extension_data = self.extension_data;
header.l1_collateral_coin_id = self.l1_collateral_coin_id;
header.l1_reserve_coin_id = self.l1_reserve_coin_id;
header.l1_prev_epoch_finalizer_coin_id = self.l1_prev_epoch_finalizer_coin_id;
header.l1_curr_epoch_finalizer_coin_id = self.l1_curr_epoch_finalizer_coin_id;
header.l1_network_coin_id = self.l1_network_coin_id;
header.collateral_registry_root = self.collateral_registry_root;
header.cid_state_root = self.cid_state_root;
header.node_registry_root = self.node_registry_root;
header.namespace_update_root = self.namespace_update_root;
header.dfsp_finalize_commitment_root = self.dfsp_finalize_commitment_root;
let spend_bundles = self.spend_bundles;
let slash_proposal_payloads = self.slash_proposal_payloads;
let mut block = L2Block::new(
header,
spend_bundles,
slash_proposal_payloads,
Signature::default(),
);
let measured = block.compute_size();
block.header.block_size = usize_to_u32_count(measured);
let header_hash = block.header.hash();
let sig = signer
.sign_block(&header_hash)
.map_err(|e| BuilderError::SigningFailed(e.to_string()))?;
block.proposer_signature = sig;
Ok(block)
}
}
#[inline]
fn usize_to_u32_count(n: usize) -> u32 {
u32::try_from(n).unwrap_or(u32::MAX)
}