use crate::{ADDigest, BlockId, Digest32, EcPoint};
use num_bigint::BigInt;
use sigma_ser::vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
use sigma_ser::{
ScorexParsingError, ScorexSerializable, ScorexSerializationError, ScorexSerializeResult,
};
use sigma_util::hash::blake2b256_hash;
use std::io::Write;
use crate::votes::Votes;
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Header {
#[cfg_attr(feature = "json", serde(rename = "version"))]
pub version: u8,
#[cfg_attr(feature = "json", serde(rename = "id"))]
pub id: BlockId,
#[cfg_attr(feature = "json", serde(rename = "parentId"))]
pub parent_id: BlockId,
#[cfg_attr(feature = "json", serde(rename = "adProofsRoot"))]
pub ad_proofs_root: Digest32,
#[cfg_attr(feature = "json", serde(rename = "stateRoot"))]
pub state_root: ADDigest,
#[cfg_attr(feature = "json", serde(rename = "transactionsRoot"))]
pub transaction_root: Digest32,
#[cfg_attr(feature = "json", serde(rename = "timestamp"))]
pub timestamp: u64,
#[cfg_attr(feature = "json", serde(rename = "nBits"))]
pub n_bits: u64,
#[cfg_attr(feature = "json", serde(rename = "height"))]
pub height: u32,
#[cfg_attr(feature = "json", serde(rename = "extensionHash"))]
pub extension_root: Digest32,
#[cfg_attr(feature = "json", serde(rename = "powSolutions"))]
pub autolykos_solution: AutolykosSolution,
#[cfg_attr(feature = "json", serde(rename = "votes"))]
pub votes: Votes,
}
impl Header {
pub fn serialize_without_pow(&self) -> Result<Vec<u8>, ScorexSerializationError> {
use byteorder::{BigEndian, WriteBytesExt};
let mut data = Vec::new();
let mut w = &mut data;
w.put_u8(self.version)?;
self.parent_id.0.scorex_serialize(&mut w)?;
self.ad_proofs_root.scorex_serialize(&mut w)?;
self.transaction_root.scorex_serialize(&mut w)?;
self.state_root.scorex_serialize(&mut w)?;
w.put_u64(self.timestamp)?;
self.extension_root.scorex_serialize(&mut w)?;
let mut n_bits_writer = vec![];
#[allow(clippy::unwrap_used)]
n_bits_writer
.write_u32::<BigEndian>(self.n_bits as u32)
.unwrap();
w.write_all(&n_bits_writer)?;
w.put_u32(self.height)?;
w.write_all(&self.votes.0)?;
if self.version > 1 {
w.put_i8(0)?;
}
Ok(data)
}
}
impl ScorexSerializable for Header {
fn scorex_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> ScorexSerializeResult {
let bytes = self.serialize_without_pow()?;
w.write_all(&bytes)?;
self.autolykos_solution.serialize_bytes(self.version, w)?;
Ok(())
}
fn scorex_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, ScorexParsingError> {
let version = r.get_u8()?;
let parent_id = BlockId(Digest32::scorex_parse(r)?);
let ad_proofs_root = Digest32::scorex_parse(r)?;
let transaction_root = Digest32::scorex_parse(r)?;
let state_root = ADDigest::scorex_parse(r)?;
let timestamp = r.get_u64()?;
let extension_root = Digest32::scorex_parse(r)?;
let mut n_bits_buf = [0u8, 0, 0, 0];
r.read_exact(&mut n_bits_buf)?;
let n_bits = {
use byteorder::{BigEndian, ReadBytesExt};
let mut reader = std::io::Cursor::new(n_bits_buf);
#[allow(clippy::unwrap_used)]
{
reader.read_u32::<BigEndian>().unwrap() as u64
}
};
let height = r.get_u32()?;
let mut votes_bytes = [0u8, 0, 0];
r.read_exact(&mut votes_bytes)?;
let votes = Votes(votes_bytes);
if version > 1 {
let new_field_size = r.get_u8()?;
if new_field_size > 0 {
let mut field_bytes: Vec<u8> =
std::iter::repeat(0).take(new_field_size as usize).collect();
r.read_exact(&mut field_bytes)?;
}
}
let autolykos_solution = if version == 1 {
let miner_pk = EcPoint::scorex_parse(r)?.into();
let pow_onetime_pk = Some(EcPoint::scorex_parse(r)?.into());
let mut nonce: Vec<u8> = std::iter::repeat(0).take(8).collect();
r.read_exact(&mut nonce)?;
let d_bytes_len = r.get_u8()?;
let mut d_bytes: Vec<u8> = std::iter::repeat(0).take(d_bytes_len as usize).collect();
r.read_exact(&mut d_bytes)?;
let pow_distance = Some(BigInt::from_signed_bytes_be(&d_bytes));
AutolykosSolution {
miner_pk,
pow_onetime_pk,
nonce,
pow_distance,
}
} else {
let pow_onetime_pk = None;
let pow_distance = None;
let miner_pk = EcPoint::scorex_parse(r)?.into();
let mut nonce: Vec<u8> = std::iter::repeat(0).take(8).collect();
r.read_exact(&mut nonce)?;
AutolykosSolution {
miner_pk,
pow_onetime_pk,
nonce,
pow_distance,
}
};
let mut header = Header {
version,
id: BlockId(Digest32::zero()),
parent_id,
ad_proofs_root,
state_root,
transaction_root,
timestamp,
n_bits,
height,
extension_root,
autolykos_solution: autolykos_solution.clone(),
votes,
};
let mut id_bytes = header.serialize_without_pow()?;
let mut data = Vec::new();
autolykos_solution.serialize_bytes(version, &mut data)?;
id_bytes.extend(data);
let id = BlockId(blake2b256_hash(&id_bytes).into());
header.id = id;
Ok(header)
}
}
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct AutolykosSolution {
#[cfg_attr(feature = "json", serde(rename = "pk"))]
pub miner_pk: Box<EcPoint>,
#[cfg_attr(feature = "json", serde(default, rename = "w"))]
pub pow_onetime_pk: Option<Box<EcPoint>>,
#[cfg_attr(
feature = "json",
serde(
rename = "n",
serialize_with = "crate::json::autolykos_solution::as_base16_string",
deserialize_with = "crate::json::autolykos_solution::from_base16_string"
)
)]
pub nonce: Vec<u8>,
#[cfg_attr(
feature = "json",
serde(
default,
rename = "d",
serialize_with = "crate::json::autolykos_solution::bigint_as_str",
deserialize_with = "crate::json::autolykos_solution::bigint_from_serde_json_number"
)
)]
pub pow_distance: Option<BigInt>,
}
impl AutolykosSolution {
pub fn serialize_bytes<W: WriteSigmaVlqExt>(
&self,
version: u8,
w: &mut W,
) -> Result<(), ScorexSerializationError> {
if version == 1 {
self.miner_pk.scorex_serialize(w)?;
self.pow_onetime_pk
.as_ref()
.ok_or(ScorexSerializationError::Misc(
"pow_onetime_pk must == Some(_) for autolykos v1",
))?
.scorex_serialize(w)?;
w.write_all(&self.nonce)?;
let d_bytes = self
.pow_distance
.as_ref()
.ok_or(ScorexSerializationError::Misc(
"pow_distance must be == Some(_) for autolykos v1",
))?
.to_signed_bytes_be();
w.put_u8(d_bytes.len() as u8)?;
w.write_all(&d_bytes)?;
} else {
self.miner_pk.scorex_serialize(w)?;
w.write_all(&self.nonce)?;
}
Ok(())
}
}
#[cfg(feature = "arbitrary")]
#[allow(clippy::unwrap_used)]
mod arbitrary {
use crate::*;
use num_bigint::BigInt;
use proptest::array::{uniform3, uniform32};
use proptest::prelude::*;
use super::{AutolykosSolution, BlockId, Header, Votes};
impl Arbitrary for Header {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
uniform32(1u8..),
uniform32(1u8..),
uniform32(1u8..),
uniform32(1u8..),
946_674_000_000..2_500_400_300_000u64,
any::<u32>(), 1_000_000u32..10_000_000u32,
prop::sample::select(vec![1_u8, 2]),
any::<Box<AutolykosSolution>>(),
uniform3(1u8..),
)
.prop_map(
|(
parent_id,
ad_proofs_root,
transaction_root,
extension_root,
timestamp,
n_bits,
height,
version,
autolykos_solution,
votes,
)| {
let parent_id = BlockId(Digest(parent_id));
let ad_proofs_root = Digest(ad_proofs_root);
let transaction_root = Digest(transaction_root);
let extension_root = Digest(extension_root);
let votes = Votes(votes);
let mut header = Self {
version,
id: BlockId(Digest32::zero()),
parent_id,
ad_proofs_root,
state_root: ADDigest::zero(),
transaction_root,
timestamp,
n_bits: n_bits as u64,
height,
extension_root,
autolykos_solution: *autolykos_solution.clone(),
votes,
};
let mut id_bytes = header.serialize_without_pow().unwrap();
let mut data = Vec::new();
let mut w = &mut data;
autolykos_solution.serialize_bytes(version, &mut w).unwrap();
id_bytes.extend(data);
let id = BlockId(blake2b256_hash(&id_bytes));
header.id = id;
if header.version > 1 {
header.autolykos_solution.pow_onetime_pk = None;
header.autolykos_solution.pow_distance = None;
}
header
},
)
.boxed()
}
type Strategy = BoxedStrategy<Header>;
}
impl Arbitrary for AutolykosSolution {
type Parameters = ();
type Strategy = BoxedStrategy<AutolykosSolution>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<Box<EcPoint>>(),
prop::collection::vec(0_u8.., 8),
any::<Box<EcPoint>>(),
any::<u64>(),
)
.prop_map(
|(miner_pk, nonce, pow_onetime_pk, pow_distance)| AutolykosSolution {
miner_pk,
nonce,
pow_onetime_pk: Some(pow_onetime_pk),
pow_distance: Some(BigInt::from(pow_distance)),
},
)
.boxed()
}
}
}
#[allow(clippy::unwrap_used, clippy::panic)]
#[cfg(test)]
#[cfg(feature = "arbitrary")]
mod tests {
use std::str::FromStr;
use num_bigint::BigInt;
use crate::header::Header;
use proptest::prelude::*;
use sigma_ser::scorex_serialize_roundtrip;
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn ser_roundtrip(v in any::<Header>()) {
assert_eq![scorex_serialize_roundtrip(&v), v]
}
}
#[test]
fn parse_block_header() {
let json = r#"{
"extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
"difficulty": "626412390187008",
"votes": "040000",
"timestamp": 1618929697400,
"size": 221,
"stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
"height": 471746,
"nBits": 117586360,
"version": 2,
"id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
"adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
"transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
"extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
"powSolutions": {
"pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
"w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"n": "5939ecfee6b0d7f4",
"d": "1234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
},
"adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
"transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
"parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
"1234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
)
.unwrap())
);
}
#[test]
fn parse_block_header_explorer_v1() {
let json = r#"{
"extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f",
"difficulty": "626412390187008",
"votes": [4,0,0],
"timestamp": 1618929697400,
"size": 221,
"stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713",
"height": 471746,
"nBits": 117586360,
"version": 2,
"id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b",
"adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39",
"transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b",
"extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44",
"powSolutions": {
"pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669",
"w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"n": "5939ecfee6b0d7f4",
"d": 1234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
},
"adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae",
"transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e",
"parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34"
}"#;
let header: Header = serde_json::from_str(json).unwrap();
assert_eq!(header.height, 471746);
assert_eq!(
header.autolykos_solution.pow_distance,
Some(BigInt::from_str(
"1234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
)
.unwrap())
);
}
}