use std::{borrow::Borrow, io};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{TimeZone, Utc};
use hex::{FromHex, FromHexError};
use crate::{
block::{header::ZCASH_BLOCK_VERSION, merkle, Block, CountedHeader, Hash, Header},
serialization::{
CompactSizeMessage, ReadZcashExt, SerializationError, ZcashDeserialize,
ZcashDeserializeInto, ZcashSerialize,
},
work::{difficulty::CompactDifficulty, equihash},
};
pub const MAX_BLOCK_BYTES: u64 = 2_000_000;
fn check_version(version: u32) -> Result<(), &'static str> {
match version {
version if version >> 31 != 0 => Err("high bit was set in version field"),
version if version < ZCASH_BLOCK_VERSION => Err("version must be at least 4"),
_ => Ok(()),
}
}
impl ZcashSerialize for Header {
#[allow(clippy::unwrap_in_result)]
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
check_version(self.version).map_err(io::Error::other)?;
writer.write_u32::<LittleEndian>(self.version)?;
self.previous_block_hash.zcash_serialize(&mut writer)?;
writer.write_all(&self.merkle_root.0[..])?;
writer.write_all(&self.commitment_bytes[..])?;
writer.write_u32::<LittleEndian>(
self.time
.timestamp()
.try_into()
.expect("deserialized and generated timestamps are u32 values"),
)?;
writer.write_u32::<LittleEndian>(self.difficulty_threshold.0)?;
writer.write_all(&self.nonce[..])?;
self.solution.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for Header {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let version = reader.read_u32::<LittleEndian>()?;
check_version(version).map_err(SerializationError::Parse)?;
Ok(Header {
version,
previous_block_hash: Hash::zcash_deserialize(&mut reader)?,
merkle_root: merkle::Root(reader.read_32_bytes()?),
commitment_bytes: reader.read_32_bytes()?.into(),
time: Utc
.timestamp_opt(reader.read_u32::<LittleEndian>()?.into(), 0)
.single()
.ok_or(SerializationError::Parse(
"out-of-range number of seconds and/or invalid nanosecond",
))?,
difficulty_threshold: CompactDifficulty(reader.read_u32::<LittleEndian>()?),
nonce: reader.read_32_bytes()?.into(),
solution: equihash::Solution::zcash_deserialize(reader)?,
})
}
}
impl ZcashSerialize for CountedHeader {
#[allow(clippy::unwrap_in_result)]
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.header.zcash_serialize(&mut writer)?;
let transaction_count =
CompactSizeMessage::try_from(0).expect("0 is below the message size limit");
transaction_count.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for CountedHeader {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let header = CountedHeader {
header: (&mut reader).zcash_deserialize_into()?,
};
let _transaction_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
Ok(header)
}
}
impl ZcashSerialize for Block {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.header.zcash_serialize(&mut writer)?;
self.transactions.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for Block {
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
let limited_reader = &mut reader.take(MAX_BLOCK_BYTES);
Ok(Block {
header: limited_reader.zcash_deserialize_into()?,
transactions: limited_reader.zcash_deserialize_into()?,
})
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SerializedBlock {
bytes: Vec<u8>,
}
impl<B: Borrow<Block>> From<B> for SerializedBlock {
fn from(block: B) -> Self {
SerializedBlock {
bytes: block
.borrow()
.zcash_serialize_to_vec()
.expect("Writing to a `Vec` should never fail"),
}
}
}
impl AsRef<[u8]> for SerializedBlock {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl From<Vec<u8>> for SerializedBlock {
fn from(bytes: Vec<u8>) -> Self {
Self { bytes }
}
}
impl FromHex for SerializedBlock {
type Error = FromHexError;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let bytes = Vec::from_hex(hex)?;
Ok(SerializedBlock { bytes })
}
}