use crate::block_id::{BlockIdDeserializer, BlockIdSerializer};
use crate::prehash::PreHashed;
use crate::secure_share::{Id, SecureShare, SecureShareContent};
use crate::slot::{Slot, SlotDeserializer, SlotSerializer};
use crate::{block_id::BlockId, error::ModelsError};
use massa_hash::{Hash, HashDeserializer};
use massa_serialization::{
DeserializeError, Deserializer, SerializeError, Serializer, U32VarIntDeserializer,
U32VarIntSerializer, U64VarIntDeserializer, U64VarIntSerializer,
};
use massa_signature::PublicKey;
use nom::error::{context, ErrorKind};
use nom::sequence::tuple;
use nom::Parser;
use nom::{
error::{ContextError, ParseError},
IResult,
};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::fmt::Formatter;
use std::ops::Bound::{Excluded, Included};
use std::{fmt::Display, str::FromStr};
use transition::Versioned;
pub const ENDORSEMENT_ID_SIZE_BYTES: usize = massa_hash::HASH_SIZE_BYTES;
#[allow(missing_docs)]
#[transition::versioned(versions("0"))]
#[derive(
Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, SerializeDisplay, DeserializeFromStr,
)]
pub struct EndorsementId(Hash);
const ENDORSEMENTID_PREFIX: char = 'E';
const ENDORSEMENTID_VERSION: u64 = 0;
impl PreHashed for EndorsementId {}
impl Id for EndorsementId {
fn new(hash: Hash) -> Self {
EndorsementId::EndorsementIdV0(EndorsementIdV0(hash))
}
fn get_hash(&self) -> &Hash {
match self {
EndorsementId::EndorsementIdV0(endorsement_id) => endorsement_id.get_hash(),
}
}
}
#[transition::impl_version(versions("0"))]
impl EndorsementId {
fn get_hash(&self) -> &Hash {
&self.0
}
}
impl std::fmt::Display for EndorsementId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EndorsementId::EndorsementIdV0(id) => write!(f, "{}", id),
}
}
}
#[transition::impl_version(versions("0"))]
impl std::fmt::Display for EndorsementId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let u64_serializer = U64VarIntSerializer::new();
let mut bytes: Vec<u8> = Vec::new();
u64_serializer
.serialize(&ENDORSEMENTID_VERSION, &mut bytes)
.map_err(|_| std::fmt::Error)?;
bytes.extend(self.0.to_bytes());
write!(
f,
"{}{}",
ENDORSEMENTID_PREFIX,
bs58::encode(bytes).with_check().into_string()
)
}
}
impl std::fmt::Debug for EndorsementId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl FromStr for EndorsementId {
type Err = ModelsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
match chars.next() {
Some(prefix) if prefix == ENDORSEMENTID_PREFIX => {
let data = chars.collect::<String>();
let decoded_bs58_check = bs58::decode(data)
.with_check(None)
.into_vec()
.map_err(|_| ModelsError::EndorsementIdParseError)?;
let endorsement_id_deserializer = EndorsementIdDeserializer::new();
let (rest, endorsement_id) = endorsement_id_deserializer
.deserialize::<DeserializeError>(&decoded_bs58_check[..])
.map_err(|_| ModelsError::EndorsementIdParseError)?;
if rest.is_empty() {
Ok(endorsement_id)
} else {
Err(ModelsError::EndorsementIdParseError)
}
}
_ => Err(ModelsError::EndorsementIdParseError),
}
}
}
#[transition::impl_version(versions("0"))]
impl FromStr for EndorsementId {
type Err = ModelsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
match chars.next() {
Some(prefix) if prefix == ENDORSEMENTID_PREFIX => {
let data = chars.collect::<String>();
let decoded_bs58_check = bs58::decode(data)
.with_check(None)
.into_vec()
.map_err(|_| ModelsError::EndorsementIdParseError)?;
let endorsement_id_deserializer = EndorsementIdDeserializer::new();
let (rest, endorsement_id) = endorsement_id_deserializer
.deserialize::<DeserializeError>(&decoded_bs58_check[..])
.map_err(|_| ModelsError::EndorsementIdParseError)?;
if rest.is_empty() {
Ok(endorsement_id)
} else {
Err(ModelsError::EndorsementIdParseError)
}
}
_ => Err(ModelsError::EndorsementIdParseError),
}
}
}
struct EndorsementIdDeserializer {
version_deserializer: U64VarIntDeserializer,
hash_deserializer: HashDeserializer,
}
impl EndorsementIdDeserializer {
pub fn new() -> Self {
Self {
version_deserializer: U64VarIntDeserializer::new(Included(0), Included(u64::MAX)),
hash_deserializer: HashDeserializer::new(),
}
}
}
impl Deserializer<EndorsementId> for EndorsementIdDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], EndorsementId, E> {
if buffer.len() < 2 {
return Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof)));
}
let (rest, endorsement_id_version) = self
.version_deserializer
.deserialize(buffer)
.map_err(|_: nom::Err<E>| {
nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))
})?;
match endorsement_id_version {
<EndorsementId!["0"]>::VERSION => {
let (rest, endorsement_id) = self.deserialize(rest)?;
Ok((rest, EndorsementIdVariant!["0"](endorsement_id)))
}
_ => Err(nom::Err::Error(E::from_error_kind(buffer, ErrorKind::Eof))),
}
}
}
#[transition::impl_version(versions("0"), structures("EndorsementId"))]
impl Deserializer<EndorsementId> for EndorsementIdDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], EndorsementId, E> {
context("Failed OperationId deserialization", |input| {
self.hash_deserializer.deserialize(input)
})
.map(EndorsementId)
.parse(buffer)
}
}
impl Display for Endorsement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Endorsed block: {} at slot {}",
self.endorsed_block, self.slot
)?;
writeln!(f, "Index: {}", self.index)?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Endorsement {
pub slot: Slot,
pub index: u32,
pub endorsed_block: BlockId,
}
#[cfg(any(test, feature = "test-exports"))]
impl SecureShareEndorsement {
pub fn check_invariants(&self) -> Result<(), Box<dyn std::error::Error>> {
if let Err(e) = self.verify_signature() {
return Err(e.into());
}
if self.content.slot.thread >= crate::config::THREAD_COUNT {
Err("Endorsement slot on non-existent thread".into())
} else if self.content.index >= crate::config::ENDORSEMENT_COUNT {
Err("Endorsement index out of range".into())
} else {
Ok(())
}
}
}
pub type SecureShareEndorsement = SecureShare<Endorsement, EndorsementId>;
impl SecureShareContent for Endorsement {
fn compute_signed_hash(&self, public_key: &PublicKey, content_hash: &Hash) -> Hash {
let mut signed_data: Vec<u8> = Vec::new();
signed_data.extend(public_key.to_bytes());
signed_data.extend(EndorsementDenunciationData::new(self.slot, self.index).to_bytes());
signed_data.extend(content_hash.to_bytes());
Hash::compute_from(&signed_data)
}
}
#[derive(Clone)]
pub struct EndorsementSerializer {
slot_serializer: SlotSerializer,
u32_serializer: U32VarIntSerializer,
block_id_serializer: BlockIdSerializer,
}
impl EndorsementSerializer {
pub fn new() -> Self {
EndorsementSerializer {
slot_serializer: SlotSerializer::new(),
u32_serializer: U32VarIntSerializer::new(),
block_id_serializer: BlockIdSerializer::new(),
}
}
}
impl Default for EndorsementSerializer {
fn default() -> Self {
Self::new()
}
}
impl Serializer<Endorsement> for EndorsementSerializer {
fn serialize(&self, value: &Endorsement, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
self.slot_serializer.serialize(&value.slot, buffer)?;
self.u32_serializer.serialize(&value.index, buffer)?;
self.block_id_serializer
.serialize(&value.endorsed_block, buffer)?;
Ok(())
}
}
pub struct EndorsementDeserializer {
slot_deserializer: SlotDeserializer,
index_deserializer: U32VarIntDeserializer,
block_id_deserializer: BlockIdDeserializer,
}
impl EndorsementDeserializer {
pub fn new(thread_count: u8, endorsement_count: u32) -> Self {
EndorsementDeserializer {
slot_deserializer: SlotDeserializer::new(
(Included(0), Included(u64::MAX)),
(Included(0), Excluded(thread_count)),
),
index_deserializer: U32VarIntDeserializer::new(
Included(0),
Excluded(endorsement_count),
),
block_id_deserializer: BlockIdDeserializer::new(),
}
}
}
impl Deserializer<Endorsement> for EndorsementDeserializer {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Endorsement, E> {
context(
"Failed endorsement deserialization",
tuple((
context("Failed slot deserialization", |input| {
self.slot_deserializer.deserialize(input)
}),
context("Failed index deserialization", |input| {
self.index_deserializer.deserialize(input)
}),
context("Failed endorsed_block deserialization", |input| {
self.block_id_deserializer.deserialize(input)
}),
)),
)
.map(|(slot, index, endorsed_block)| Endorsement {
slot,
index,
endorsed_block,
})
.parse(buffer)
}
}
pub struct EndorsementSerializerLW {
u32_serializer: U32VarIntSerializer,
}
impl EndorsementSerializerLW {
pub fn new() -> Self {
EndorsementSerializerLW {
u32_serializer: U32VarIntSerializer::new(),
}
}
}
impl Default for EndorsementSerializerLW {
fn default() -> Self {
Self::new()
}
}
impl Serializer<Endorsement> for EndorsementSerializerLW {
fn serialize(&self, value: &Endorsement, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
self.u32_serializer.serialize(&value.index, buffer)?;
Ok(())
}
}
pub struct EndorsementDeserializerLW {
index_deserializer: U32VarIntDeserializer,
slot: Slot,
endorsed_block: BlockId,
}
impl EndorsementDeserializerLW {
pub const fn new(endorsement_count: u32, slot: Slot, endorsed_block: BlockId) -> Self {
EndorsementDeserializerLW {
index_deserializer: U32VarIntDeserializer::new(
Included(0),
Excluded(endorsement_count),
),
slot,
endorsed_block,
}
}
}
impl Deserializer<Endorsement> for EndorsementDeserializerLW {
fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
&self,
buffer: &'a [u8],
) -> IResult<&'a [u8], Endorsement, E> {
context("Failed index deserialization", |input| {
self.index_deserializer.deserialize(input)
})
.map(|index| Endorsement {
slot: self.slot,
index,
endorsed_block: self.endorsed_block,
})
.parse(buffer)
}
}
#[derive(Debug)]
pub struct EndorsementDenunciationData {
slot: Slot,
index: u32,
}
impl EndorsementDenunciationData {
pub fn new(slot: Slot, index: u32) -> Self {
Self { slot, index }
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend(self.slot.to_bytes_key());
buf.extend(self.index.to_le_bytes());
buf
}
}
#[cfg(test)]
mod tests {
use crate::secure_share::{SecureShareContent, SecureShareDeserializer, SecureShareSerializer};
use massa_signature::verify_signature_batch;
use serde_json::Value;
use super::*;
use crate::config::CHAINID;
use massa_serialization::DeserializeError;
use massa_signature::KeyPair;
use serial_test::serial;
#[test]
#[serial]
fn test_endorsement_serialization() {
let sender_keypair = KeyPair::generate(0).unwrap();
let content = Endorsement {
slot: Slot::new(10, 1),
index: 0,
endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
};
let endorsement: SecureShareEndorsement = Endorsement::new_verifiable(
content,
EndorsementSerializer::new(),
&sender_keypair,
*CHAINID,
)
.unwrap();
let mut ser_endorsement: Vec<u8> = Vec::new();
let serializer = SecureShareSerializer::new();
serializer
.serialize(&endorsement, &mut ser_endorsement)
.unwrap();
let (_, res_endorsement): (&[u8], SecureShareEndorsement) =
SecureShareDeserializer::new(EndorsementDeserializer::new(32, 1), *CHAINID)
.deserialize::<DeserializeError>(&ser_endorsement)
.unwrap();
assert_eq!(res_endorsement, endorsement);
}
#[test]
#[serial]
fn test_endorsement_lightweight_serialization() {
let sender_keypair = KeyPair::generate(0).unwrap();
let content = Endorsement {
slot: Slot::new(10, 1),
index: 0,
endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
};
let endorsement: SecureShareEndorsement = Endorsement::new_verifiable(
content,
EndorsementSerializerLW::new(),
&sender_keypair,
*CHAINID,
)
.unwrap();
let mut ser_endorsement: Vec<u8> = Vec::new();
let serializer = SecureShareSerializer::new();
serializer
.serialize(&endorsement, &mut ser_endorsement)
.unwrap();
let parent = BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes()));
let (_, res_endorsement): (&[u8], SecureShareEndorsement) = SecureShareDeserializer::new(
EndorsementDeserializerLW::new(1, Slot::new(10, 1), parent),
*CHAINID,
)
.deserialize::<DeserializeError>(&ser_endorsement)
.unwrap();
assert_eq!(res_endorsement.content.index, endorsement.content.index);
}
#[test]
fn test_verify_sig_batch() {
let sender_keypair = KeyPair::generate(0).unwrap();
let content_1 = Endorsement {
slot: Slot::new(10, 1),
index: 0,
endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk1".as_bytes())),
};
let s_endorsement_1: SecureShareEndorsement = Endorsement::new_verifiable(
content_1,
EndorsementSerializer::new(),
&sender_keypair,
*CHAINID,
)
.unwrap();
let mut serialized = vec![];
SecureShareSerializer::new()
.serialize(&s_endorsement_1, &mut serialized)
.unwrap();
let (_, s_endorsement_1): (&[u8], SecureShare<Endorsement, EndorsementId>) =
SecureShareDeserializer::new(EndorsementDeserializer::new(32, 32), *CHAINID)
.deserialize::<DeserializeError>(&serialized)
.unwrap();
let sender_keypair = KeyPair::generate(0).unwrap();
let content_2 = Endorsement {
slot: Slot::new(2, 5),
index: 0,
endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk2".as_bytes())),
};
let s_endorsement_2: SecureShareEndorsement = Endorsement::new_verifiable(
content_2,
EndorsementSerializerLW::new(),
&sender_keypair,
*CHAINID,
)
.unwrap();
let batch_1 = [(
s_endorsement_1.compute_signed_hash(),
s_endorsement_1.signature,
s_endorsement_1.content_creator_pub_key,
)];
verify_signature_batch(&batch_1).unwrap();
let batch_2 = [
(
s_endorsement_1.compute_signed_hash(),
s_endorsement_1.signature,
s_endorsement_1.content_creator_pub_key,
),
(
s_endorsement_2.compute_signed_hash(),
s_endorsement_2.signature,
s_endorsement_2.content_creator_pub_key,
),
];
verify_signature_batch(&batch_2).unwrap();
}
#[test]
#[serial]
fn test_endorsement_id() {
let expected_endorsement_id =
EndorsementId::from_str("E12Uy7hrAUHpmHQTWu68p17v7VtZJ6syBTWEJH6jwMTWJB6fdSc7")
.unwrap();
let actual_endorsement_id = EndorsementId::new(Hash::compute_from("edm".as_bytes()));
assert_eq!(actual_endorsement_id, expected_endorsement_id);
}
#[test]
#[serial]
fn test_endorsement_id_errors() {
let actual_error = EndorsementId::from_str("SomeInvalidEndorsementId")
.unwrap_err()
.to_string();
let expected_error = "endorsement id parsing error".to_string();
assert_eq!(actual_error, expected_error);
}
#[test]
#[serial]
fn test_endorsement_serde() {
let orig_endorsement = Endorsement {
slot: Slot::new(10, 1),
index: 0,
endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk".as_bytes())),
};
let serialized_endorsement = serde_json::to_string(&orig_endorsement).unwrap();
let res_endorsement: Value = serde_json::from_str(&serialized_endorsement).unwrap();
assert_eq!(
orig_endorsement.slot.period,
res_endorsement["slot"]["period"]
);
assert_eq!(
orig_endorsement.slot.thread,
res_endorsement["slot"]["thread"]
);
assert_eq!(orig_endorsement.index, res_endorsement["index"]);
assert_eq!(
orig_endorsement.endorsed_block.to_string(),
res_endorsement["endorsed_block"]
);
}
}