use alloc::string::ToString;
use alloc::vec::Vec;
use crate::account::{AccountId, AccountType};
use crate::block::BlockNumber;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{FeeError, Felt, Hasher, Word, ZERO};
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct BlockHeader {
version: u32,
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
proof_commitment: Word,
fee_parameters: FeeParameters,
timestamp: u32,
sub_commitment: Word,
commitment: Word,
}
impl BlockHeader {
#[allow(clippy::too_many_arguments)]
pub fn new(
version: u32,
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
proof_commitment: Word,
fee_parameters: FeeParameters,
timestamp: u32,
) -> Self {
let sub_commitment = Self::compute_sub_commitment(
version,
prev_block_commitment,
chain_commitment,
account_root,
nullifier_root,
tx_commitment,
tx_kernel_commitment,
proof_commitment,
&fee_parameters,
timestamp,
block_num,
);
let commitment = Hasher::merge(&[sub_commitment, note_root]);
Self {
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
proof_commitment,
fee_parameters,
timestamp,
sub_commitment,
commitment,
}
}
pub fn version(&self) -> u32 {
self.version
}
pub fn commitment(&self) -> Word {
self.commitment
}
pub fn sub_commitment(&self) -> Word {
self.sub_commitment
}
pub fn prev_block_commitment(&self) -> Word {
self.prev_block_commitment
}
pub fn block_num(&self) -> BlockNumber {
self.block_num
}
pub fn block_epoch(&self) -> u16 {
self.block_num.block_epoch()
}
pub fn chain_commitment(&self) -> Word {
self.chain_commitment
}
pub fn account_root(&self) -> Word {
self.account_root
}
pub fn nullifier_root(&self) -> Word {
self.nullifier_root
}
pub fn note_root(&self) -> Word {
self.note_root
}
pub fn tx_commitment(&self) -> Word {
self.tx_commitment
}
pub fn tx_kernel_commitment(&self) -> Word {
self.tx_kernel_commitment
}
pub fn proof_commitment(&self) -> Word {
self.proof_commitment
}
pub fn fee_parameters(&self) -> &FeeParameters {
&self.fee_parameters
}
pub fn timestamp(&self) -> u32 {
self.timestamp
}
pub fn epoch_block_num(&self) -> BlockNumber {
BlockNumber::from_epoch(self.block_epoch())
}
#[allow(clippy::too_many_arguments)]
fn compute_sub_commitment(
version: u32,
prev_block_commitment: Word,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
proof_commitment: Word,
fee_parameters: &FeeParameters,
timestamp: u32,
block_num: BlockNumber,
) -> Word {
let mut elements: Vec<Felt> = Vec::with_capacity(40);
elements.extend_from_slice(prev_block_commitment.as_elements());
elements.extend_from_slice(chain_commitment.as_elements());
elements.extend_from_slice(account_root.as_elements());
elements.extend_from_slice(nullifier_root.as_elements());
elements.extend_from_slice(tx_commitment.as_elements());
elements.extend_from_slice(tx_kernel_commitment.as_elements());
elements.extend_from_slice(proof_commitment.as_elements());
elements.extend([block_num.into(), version.into(), timestamp.into(), ZERO]);
elements.extend([
fee_parameters.native_asset_id().suffix(),
fee_parameters.native_asset_id().prefix().as_felt(),
fee_parameters.verification_base_fee().into(),
ZERO,
]);
elements.extend([ZERO, ZERO, ZERO, ZERO]);
Hasher::hash_elements(&elements)
}
}
impl Serializable for BlockHeader {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.version.write_into(target);
self.prev_block_commitment.write_into(target);
self.block_num.write_into(target);
self.chain_commitment.write_into(target);
self.account_root.write_into(target);
self.nullifier_root.write_into(target);
self.note_root.write_into(target);
self.tx_commitment.write_into(target);
self.tx_kernel_commitment.write_into(target);
self.proof_commitment.write_into(target);
self.fee_parameters.write_into(target);
self.timestamp.write_into(target);
}
}
impl Deserializable for BlockHeader {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let version = source.read()?;
let prev_block_commitment = source.read()?;
let block_num = source.read()?;
let chain_commitment = source.read()?;
let account_root = source.read()?;
let nullifier_root = source.read()?;
let note_root = source.read()?;
let tx_commitment = source.read()?;
let tx_kernel_commitment = source.read()?;
let proof_commitment = source.read()?;
let fee_parameters = source.read()?;
let timestamp = source.read()?;
Ok(Self::new(
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
proof_commitment,
fee_parameters,
timestamp,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeeParameters {
native_asset_id: AccountId,
verification_base_fee: u32,
}
impl FeeParameters {
pub fn new(native_asset_id: AccountId, verification_base_fee: u32) -> Result<Self, FeeError> {
if !matches!(native_asset_id.account_type(), AccountType::FungibleFaucet) {
return Err(FeeError::NativeAssetIdNotFungible {
account_type: native_asset_id.account_type(),
});
}
Ok(Self { native_asset_id, verification_base_fee })
}
pub fn native_asset_id(&self) -> AccountId {
self.native_asset_id
}
pub fn verification_base_fee(&self) -> u32 {
self.verification_base_fee
}
}
impl Serializable for FeeParameters {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.native_asset_id.write_into(target);
self.verification_base_fee.write_into(target);
}
}
impl Deserializable for FeeParameters {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let native_asset_id = source.read()?;
let verification_base_fee = source.read()?;
Self::new(native_asset_id, verification_base_fee)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use miden_core::Word;
use winter_rand_utils::rand_value;
use super::*;
use crate::testing::account_id::ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET;
#[test]
fn test_serde() {
let chain_commitment = rand_value::<Word>();
let note_root = rand_value::<Word>();
let tx_kernel_commitment = rand_value::<Word>();
let header = BlockHeader::mock(
0,
Some(chain_commitment),
Some(note_root),
&[],
tx_kernel_commitment,
);
let serialized = header.to_bytes();
let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
assert_eq!(deserialized, header);
}
#[test]
fn fee_parameters_fail_when_native_asset_is_not_fungible() {
assert_matches!(
FeeParameters::new(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap(), 0)
.unwrap_err(),
FeeError::NativeAssetIdNotFungible { .. }
);
}
}