use core::ops::Deref;
use bee_pow::providers::{miner::Miner, NonceProvider, NonceProviderBuilder};
use crypto::hashes::{blake2b::Blake2b256, Digest};
use packable::{
error::{UnexpectedEOF, UnpackError, UnpackErrorExt},
packer::Packer,
unpacker::{CounterUnpacker, SliceUnpacker, Unpacker},
Packable, PackableExt,
};
use crate::{
parent::Parents,
payload::{OptionalPayload, Payload},
protocol::ProtocolParameters,
BlockId, Error, PROTOCOL_VERSION,
};
#[derive(Clone)]
#[must_use]
pub struct BlockBuilder<P: NonceProvider = Miner> {
protocol_version: Option<u8>,
parents: Parents,
payload: Option<Payload>,
nonce_provider: Option<P>,
}
impl<P: NonceProvider> BlockBuilder<P> {
const DEFAULT_NONCE: u64 = 0;
#[inline(always)]
pub fn new(parents: Parents) -> Self {
Self {
protocol_version: None,
parents,
payload: None,
nonce_provider: None,
}
}
#[inline(always)]
pub fn with_protocol_version(mut self, protocol_version: u8) -> Self {
self.protocol_version = Some(protocol_version);
self
}
#[inline(always)]
pub fn with_payload(mut self, payload: Payload) -> Self {
self.payload = Some(payload);
self
}
#[inline(always)]
pub fn with_nonce_provider(mut self, nonce_provider: P) -> Self {
self.nonce_provider = Some(nonce_provider);
self
}
pub fn finish(self, min_pow_score: u32) -> Result<Block, Error> {
verify_payload(self.payload.as_ref())?;
let mut block = Block {
protocol_version: self.protocol_version.unwrap_or(PROTOCOL_VERSION),
parents: self.parents,
payload: self.payload.into(),
nonce: 0,
};
let block_bytes = block.pack_to_vec();
if block_bytes.len() > Block::LENGTH_MAX {
return Err(Error::InvalidBlockLength(block_bytes.len()));
}
let nonce_provider = self.nonce_provider.unwrap_or_else(|| P::Builder::new().finish());
block.nonce = nonce_provider
.nonce(
&block_bytes[..block_bytes.len() - core::mem::size_of::<u64>()],
min_pow_score,
)
.unwrap_or(Self::DEFAULT_NONCE);
Ok(block)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Block {
protocol_version: u8,
parents: Parents,
payload: OptionalPayload,
nonce: u64,
}
impl Block {
pub const LENGTH_MIN: usize = 46;
pub const LENGTH_MAX: usize = 32768;
#[inline(always)]
pub fn build(parents: Parents) -> BlockBuilder {
BlockBuilder::new(parents)
}
#[inline(always)]
pub fn protocol_version(&self) -> u8 {
self.protocol_version
}
#[inline(always)]
pub fn parents(&self) -> &Parents {
&self.parents
}
#[inline(always)]
pub fn payload(&self) -> Option<&Payload> {
self.payload.as_ref()
}
#[inline(always)]
pub fn nonce(&self) -> u64 {
self.nonce
}
#[inline(always)]
pub fn id(&self) -> BlockId {
BlockId::new(Blake2b256::digest(&self.pack_to_vec()).into())
}
#[inline(always)]
pub fn into_parents(self) -> Parents {
self.parents
}
pub fn unpack_strict<T: AsRef<[u8]>>(
bytes: T,
visitor: &<Self as Packable>::UnpackVisitor,
) -> Result<Self, UnpackError<<Self as Packable>::UnpackError, UnexpectedEOF>> {
let mut unpacker = CounterUnpacker::new(SliceUnpacker::new(bytes.as_ref()));
let block = Self::unpack::<_, true>(&mut unpacker, visitor)?;
if u8::unpack::<_, true>(&mut unpacker, &()).is_ok() {
return Err(UnpackError::Packable(Error::RemainingBytesAfterBlock));
}
Ok(block)
}
}
impl Packable for Block {
type UnpackError = Error;
type UnpackVisitor = ProtocolParameters;
fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
self.protocol_version.pack(packer)?;
self.parents.pack(packer)?;
self.payload.pack(packer)?;
self.nonce.pack(packer)?;
Ok(())
}
fn unpack<U: Unpacker, const VERIFY: bool>(
unpacker: &mut U,
visitor: &Self::UnpackVisitor,
) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
let start_opt = unpacker.read_bytes();
let protocol_version = u8::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
if VERIFY && protocol_version != visitor.protocol_version() {
return Err(UnpackError::Packable(Error::ProtocolVersionMismatch {
expected: visitor.protocol_version(),
actual: protocol_version,
}));
}
let parents = Parents::unpack::<_, VERIFY>(unpacker, &())?;
let payload = OptionalPayload::unpack::<_, VERIFY>(unpacker, visitor)?;
if VERIFY {
verify_payload(payload.deref().as_ref()).map_err(UnpackError::Packable)?;
}
let nonce = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
let block = Self {
protocol_version,
parents,
payload,
nonce,
};
if VERIFY {
let block_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) {
end - start
} else {
block.packed_len()
};
if block_len > Block::LENGTH_MAX {
return Err(UnpackError::Packable(Error::InvalidBlockLength(block_len)));
}
}
Ok(block)
}
}
fn verify_payload(payload: Option<&Payload>) -> Result<(), Error> {
if !matches!(
payload,
None | Some(Payload::Transaction(_)) | Some(Payload::Milestone(_)) | Some(Payload::TaggedData(_))
) {
Err(Error::InvalidPayloadKind(payload.unwrap().kind()))
} else {
Ok(())
}
}
#[cfg(feature = "dto")]
#[allow(missing_docs)]
pub mod dto {
use serde::{Deserialize, Serialize};
use super::*;
use crate::{error::dto::DtoError, payload::dto::PayloadDto, protocol::ProtocolParameters};
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct BlockDto {
#[serde(rename = "protocolVersion")]
pub protocol_version: u8,
pub parents: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<PayloadDto>,
pub nonce: String,
}
impl From<&Block> for BlockDto {
fn from(value: &Block) -> Self {
BlockDto {
protocol_version: value.protocol_version(),
parents: value.parents().iter().map(BlockId::to_string).collect(),
payload: value.payload().map(Into::into),
nonce: value.nonce().to_string(),
}
}
}
impl Block {
pub fn try_from_dto(value: &BlockDto, protocol_parameters: &ProtocolParameters) -> Result<Block, DtoError> {
let parents = Parents::new(
value
.parents
.iter()
.map(|m| m.parse::<BlockId>().map_err(|_| DtoError::InvalidField("parents")))
.collect::<Result<Vec<BlockId>, DtoError>>()?,
)?;
let mut builder = BlockBuilder::new(parents)
.with_protocol_version(value.protocol_version)
.with_nonce_provider(
value
.nonce
.parse::<u64>()
.map_err(|_| DtoError::InvalidField("nonce"))?,
);
if let Some(p) = value.payload.as_ref() {
builder = builder.with_payload(Payload::try_from_dto(p, protocol_parameters)?);
}
Ok(builder.finish(protocol_parameters.min_pow_score())?)
}
}
}