use snarkos_node_sync::locators::NUM_RECENT_BLOCKS;
use snarkvm::{
console::{
account::{Address, Signature},
network::Network,
types::Field,
},
prelude::{FromBytes, IoResult, Read, ToBytes, Write, error},
};
use std::{collections::HashMap, ops::Deref};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedProposals<N: Network>(pub HashMap<Address<N>, (u64, Field<N>, Signature<N>)>);
impl<N: Network> SignedProposals<N> {
pub fn is_valid(&self, expected_signer: Address<N>) -> bool {
self.0.iter().all(|(_, (_, _, signature))| signature.to_address() == expected_signer)
}
}
impl<N: Network> ToBytes for SignedProposals<N> {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
u32::try_from(self.0.len()).map_err(error)?.write_le(&mut writer)?;
for (address, (round, batch_id, signature)) in &self.0 {
address.write_le(&mut writer)?;
round.write_le(&mut writer)?;
batch_id.write_le(&mut writer)?;
signature.write_le(&mut writer)?;
}
Ok(())
}
}
impl<N: Network> FromBytes for SignedProposals<N> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let num_signed_proposals = u32::read_le(&mut reader)?;
let max_certificates = N::LATEST_MAX_CERTIFICATES();
if num_signed_proposals as usize > max_certificates as usize * NUM_RECENT_BLOCKS {
return Err(error(format!(
"Number of signed proposals ({num_signed_proposals}) is greater than the maximum ({max_certificates} * {NUM_RECENT_BLOCKS})",
)));
}
let mut signed_proposals = HashMap::with_capacity(num_signed_proposals as usize);
for _ in 0..num_signed_proposals {
let address = FromBytes::read_le(&mut reader)?;
let round = FromBytes::read_le(&mut reader)?;
let batch_id = FromBytes::read_le(&mut reader)?;
let signature = FromBytes::read_le(&mut reader)?;
signed_proposals.insert(address, (round, batch_id, signature));
}
Ok(Self(signed_proposals))
}
}
impl<N: Network> Deref for SignedProposals<N> {
type Target = HashMap<Address<N>, (u64, Field<N>, Signature<N>)>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<N: Network> Default for SignedProposals<N> {
fn default() -> Self {
Self(Default::default())
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use snarkvm::{
console::{account::PrivateKey, network::MainnetV0},
utilities::{TestRng, Uniform},
};
use rand::Rng;
type CurrentNetwork = MainnetV0;
const ITERATIONS: usize = 100;
pub(crate) fn sample_signed_proposals(
signer: &PrivateKey<CurrentNetwork>,
rng: &mut TestRng,
) -> SignedProposals<CurrentNetwork> {
let mut signed_proposals: HashMap<_, _> = Default::default();
for _ in 0..CurrentNetwork::LATEST_MAX_CERTIFICATES() {
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let address = Address::try_from(&private_key).unwrap();
let round = rng.r#gen();
let batch_id = Field::rand(rng);
let signature = signer.sign(&[batch_id], rng).unwrap();
signed_proposals.insert(address, (round, batch_id, signature));
}
SignedProposals(signed_proposals)
}
#[test]
fn test_bytes() {
let rng = &mut TestRng::default();
let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
for _ in 0..ITERATIONS {
let expected = sample_signed_proposals(&singer_private_key, rng);
let expected_bytes = expected.to_bytes_le().unwrap();
assert_eq!(expected, SignedProposals::read_le(&expected_bytes[..]).unwrap());
}
}
#[test]
fn test_is_valid() {
let rng = &mut TestRng::default();
for _ in 0..ITERATIONS {
let singer_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let singer_address = Address::try_from(&singer_private_key).unwrap();
let signed_proposals = sample_signed_proposals(&singer_private_key, rng);
assert!(signed_proposals.is_valid(singer_address));
}
}
}