snarkvm_ledger_narwhal_batch_certificate/
lib.rs#![forbid(unsafe_code)]
#![warn(clippy::cast_possible_truncation)]
mod bytes;
mod serialize;
mod string;
use console::{
account::{Address, Signature},
prelude::*,
types::Field,
};
use narwhal_batch_header::BatchHeader;
use narwhal_transmission_id::TransmissionID;
use core::hash::{Hash, Hasher};
use indexmap::IndexSet;
use std::collections::HashSet;
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
#[derive(Clone)]
pub struct BatchCertificate<N: Network> {
batch_header: BatchHeader<N>,
signatures: IndexSet<Signature<N>>,
}
impl<N: Network> BatchCertificate<N> {
pub const MAX_SIGNATURES: u16 = BatchHeader::<N>::MAX_CERTIFICATES;
}
impl<N: Network> BatchCertificate<N> {
pub fn from(batch_header: BatchHeader<N>, signatures: IndexSet<Signature<N>>) -> Result<Self> {
ensure!(signatures.len() <= Self::MAX_SIGNATURES as usize, "Invalid number of signatures");
let signature_authors = signatures.iter().map(|signature| signature.to_address()).collect::<HashSet<_>>();
ensure!(
!signature_authors.contains(&batch_header.author()),
"The author's signature was included in the signers"
);
ensure!(signature_authors.len() == signatures.len(), "A duplicate author was found in the set of signatures");
cfg_iter!(signatures).try_for_each(|signature| {
if !signature.verify(&signature.to_address(), &[batch_header.batch_id()]) {
bail!("Invalid batch certificate signature")
}
Ok(())
})?;
Self::from_unchecked(batch_header, signatures)
}
pub fn from_unchecked(batch_header: BatchHeader<N>, signatures: IndexSet<Signature<N>>) -> Result<Self> {
ensure!(!signatures.is_empty(), "Batch certificate must contain signatures");
Ok(Self { batch_header, signatures })
}
}
impl<N: Network> PartialEq for BatchCertificate<N> {
fn eq(&self, other: &Self) -> bool {
self.batch_id() == other.batch_id()
}
}
impl<N: Network> Eq for BatchCertificate<N> {}
impl<N: Network> Hash for BatchCertificate<N> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.batch_header.batch_id().hash(state);
}
}
impl<N: Network> BatchCertificate<N> {
pub const fn id(&self) -> Field<N> {
self.batch_header.batch_id()
}
pub const fn batch_header(&self) -> &BatchHeader<N> {
&self.batch_header
}
pub const fn batch_id(&self) -> Field<N> {
self.batch_header().batch_id()
}
pub const fn author(&self) -> Address<N> {
self.batch_header().author()
}
pub const fn round(&self) -> u64 {
self.batch_header().round()
}
pub fn timestamp(&self) -> i64 {
self.batch_header().timestamp()
}
pub const fn committee_id(&self) -> Field<N> {
self.batch_header().committee_id()
}
pub const fn transmission_ids(&self) -> &IndexSet<TransmissionID<N>> {
self.batch_header().transmission_ids()
}
pub const fn previous_certificate_ids(&self) -> &IndexSet<Field<N>> {
self.batch_header().previous_certificate_ids()
}
pub fn signatures(&self) -> Box<dyn '_ + ExactSizeIterator<Item = &Signature<N>>> {
Box::new(self.signatures.iter())
}
}
#[cfg(any(test, feature = "test-helpers"))]
pub mod test_helpers {
use super::*;
use console::{account::PrivateKey, network::MainnetV0, prelude::TestRng, types::Field};
use indexmap::IndexSet;
type CurrentNetwork = MainnetV0;
pub fn sample_batch_certificate(rng: &mut TestRng) -> BatchCertificate<CurrentNetwork> {
sample_batch_certificate_for_round(rng.gen(), rng)
}
pub fn sample_batch_certificate_for_round(round: u64, rng: &mut TestRng) -> BatchCertificate<CurrentNetwork> {
let certificate_ids = (0..10).map(|_| Field::<CurrentNetwork>::rand(rng)).collect::<IndexSet<_>>();
sample_batch_certificate_for_round_with_previous_certificate_ids(round, certificate_ids, rng)
}
pub fn sample_batch_certificate_for_round_with_previous_certificate_ids(
round: u64,
previous_certificate_ids: IndexSet<Field<CurrentNetwork>>,
rng: &mut TestRng,
) -> BatchCertificate<CurrentNetwork> {
let batch_header =
narwhal_batch_header::test_helpers::sample_batch_header_for_round_with_previous_certificate_ids(
round,
previous_certificate_ids,
rng,
);
let mut signatures = IndexSet::with_capacity(5);
for _ in 0..5 {
let private_key = PrivateKey::new(rng).unwrap();
signatures.insert(private_key.sign(&[batch_header.batch_id()], rng).unwrap());
}
BatchCertificate::from(batch_header, signatures).unwrap()
}
pub fn sample_batch_certificates(rng: &mut TestRng) -> IndexSet<BatchCertificate<CurrentNetwork>> {
let mut sample = IndexSet::with_capacity(10);
for _ in 0..10 {
sample.insert(sample_batch_certificate(rng));
}
sample
}
pub fn sample_batch_certificate_with_previous_certificates(
round: u64,
rng: &mut TestRng,
) -> (BatchCertificate<CurrentNetwork>, Vec<BatchCertificate<CurrentNetwork>>) {
assert!(round > 1, "Round must be greater than 1");
let previous_round = round - 1; let current_round = round;
assert_eq!(previous_round % 2, 0, "Previous round must be even");
let previous_certificates = vec![
sample_batch_certificate_for_round(previous_round, rng),
sample_batch_certificate_for_round(previous_round, rng),
sample_batch_certificate_for_round(previous_round, rng),
sample_batch_certificate_for_round(previous_round, rng),
];
let previous_certificate_ids: IndexSet<_> = previous_certificates.iter().map(|c| c.id()).collect();
let certificate = sample_batch_certificate_for_round_with_previous_certificate_ids(
current_round,
previous_certificate_ids,
rng,
);
(certificate, previous_certificates)
}
}
#[cfg(test)]
mod tests {
use super::*;
type CurrentNetwork = console::network::MainnetV0;
#[test]
fn test_maximum_signatures() {
assert_eq!(BatchHeader::<CurrentNetwork>::MAX_CERTIFICATES, BatchCertificate::<CurrentNetwork>::MAX_SIGNATURES);
}
}