#![forbid(unsafe_code)]
#![warn(clippy::cast_possible_truncation)]
mod bytes;
mod serialize;
mod string;
use console::{account::Address, prelude::*, program::SUBDAG_CERTIFICATES_DEPTH, types::Field};
use narwhal_batch_certificate::BatchCertificate;
use narwhal_batch_header::BatchHeader;
use narwhal_transmission_id::TransmissionID;
use indexmap::IndexSet;
use std::collections::BTreeMap;
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
fn is_sequential<T>(map: &BTreeMap<u64, T>) -> bool {
let mut previous_round = None;
for &round in map.keys() {
match previous_round {
Some(previous) if previous + 1 != round => return false,
_ => previous_round = Some(round),
}
}
true
}
fn sanity_check_subdag_with_dfs<N: Network>(subdag: &BTreeMap<u64, IndexSet<BatchCertificate<N>>>) -> bool {
use std::collections::HashSet;
let mut commit = BTreeMap::<u64, IndexSet<_>>::new();
let mut already_ordered = HashSet::new();
let mut buffer = subdag.iter().next_back().map_or(Default::default(), |(_, leader)| leader.clone());
while let Some(certificate) = buffer.pop() {
commit.entry(certificate.round()).or_default().insert(certificate.clone());
for previous_certificate_id in certificate.previous_certificate_ids() {
let Some(previous_certificate) = subdag
.get(&(certificate.round() - 1))
.and_then(|map| map.iter().find(|certificate| certificate.id() == *previous_certificate_id))
else {
continue;
};
if !already_ordered.insert(previous_certificate.id()) {
continue;
}
buffer.insert(previous_certificate.clone());
}
}
&commit == subdag
}
#[derive(Clone, PartialEq, Eq)]
pub struct Subdag<N: Network> {
subdag: BTreeMap<u64, IndexSet<BatchCertificate<N>>>,
}
impl<N: Network> Subdag<N> {
pub fn from(subdag: BTreeMap<u64, IndexSet<BatchCertificate<N>>>) -> Result<Self> {
ensure!(!subdag.is_empty(), "Subdag cannot be empty");
ensure!(subdag.iter().next_back().map_or(0, |(r, _)| *r) % 2 == 0, "Anchor round must be even");
ensure!(subdag.iter().next_back().map_or(0, |(_, c)| c.len()) == 1, "Subdag cannot have multiple leaders");
ensure!(is_sequential(&subdag), "Subdag rounds must be sequential");
ensure!(sanity_check_subdag_with_dfs(&subdag), "Subdag structure does not match commit");
Ok(Self { subdag })
}
}
impl<N: Network> Subdag<N> {
pub const MAX_ROUNDS: usize = 50;
}
impl<N: Network> Subdag<N> {
pub fn anchor_round(&self) -> u64 {
self.subdag.iter().next_back().map_or(0, |(round, _)| *round)
}
pub fn certificate_ids(&self) -> impl Iterator<Item = Field<N>> + '_ {
self.values().flatten().map(BatchCertificate::id)
}
pub fn leader_certificate(&self) -> &BatchCertificate<N> {
let entry = self.subdag.iter().next_back();
debug_assert!(entry.is_some(), "There must be at least one round of certificates");
let certificates = entry.expect("There must be one round in the subdag").1;
debug_assert!(certificates.len() == 1, "There must be only one leader certificate, by definition");
certificates.iter().next().expect("There must be a leader certificate")
}
pub fn leader_address(&self) -> Address<N> {
self.leader_certificate().author()
}
pub fn transmission_ids(&self) -> impl Iterator<Item = &TransmissionID<N>> {
self.values().flatten().flat_map(BatchCertificate::transmission_ids)
}
pub fn timestamp(&self) -> i64 {
match self.leader_certificate() {
BatchCertificate::V1 { .. } => self.leader_certificate().timestamp(),
BatchCertificate::V2 { .. } => {
let mut timestamps = self.values().flatten().map(BatchCertificate::timestamp).collect::<Vec<_>>();
#[cfg(not(feature = "serial"))]
timestamps.par_sort_unstable();
#[cfg(feature = "serial")]
timestamps.sort_unstable();
timestamps[timestamps.len() / 2]
}
}
}
pub fn to_subdag_root(&self) -> Result<Field<N>> {
let leaves = cfg_iter!(self.subdag)
.map(|(_, certificates)| {
certificates.iter().flat_map(|certificate| certificate.id().to_bits_le()).collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let tree = N::merkle_tree_bhp::<SUBDAG_CERTIFICATES_DEPTH>(&leaves)?;
Ok(*tree.root())
}
}
impl<N: Network> Deref for Subdag<N> {
type Target = BTreeMap<u64, IndexSet<BatchCertificate<N>>>;
fn deref(&self) -> &Self::Target {
&self.subdag
}
}
#[cfg(any(test, feature = "test-helpers"))]
pub mod test_helpers {
use super::*;
use console::{network::Testnet3, prelude::TestRng};
use indexmap::{indexset, IndexSet};
type CurrentNetwork = Testnet3;
pub fn sample_subdag(rng: &mut TestRng) -> Subdag<CurrentNetwork> {
const F: usize = 1;
const AVAILABILITY_THRESHOLD: usize = F + 1;
const QUORUM_THRESHOLD: usize = 2 * F + 1;
let mut subdag = BTreeMap::<u64, IndexSet<_>>::new();
let starting_round = {
loop {
let round = rng.gen_range(2..u64::MAX);
if round % 2 == 0 {
break round;
}
}
};
let mut previous_certificate_ids = IndexSet::new();
for _ in 0..AVAILABILITY_THRESHOLD {
let certificate =
narwhal_batch_certificate::test_helpers::sample_batch_certificate_for_round(starting_round, rng);
previous_certificate_ids.insert(certificate.id());
subdag.entry(starting_round).or_default().insert(certificate);
}
let mut previous_certificate_ids_2 = IndexSet::new();
for _ in 0..QUORUM_THRESHOLD {
let certificate =
narwhal_batch_certificate::test_helpers::sample_batch_certificate_for_round_with_previous_certificate_ids(starting_round + 1, previous_certificate_ids.clone(), rng);
previous_certificate_ids_2.insert(certificate.id());
subdag.entry(starting_round + 1).or_default().insert(certificate);
}
let certificate =
narwhal_batch_certificate::test_helpers::sample_batch_certificate_for_round_with_previous_certificate_ids(
starting_round + 2,
previous_certificate_ids_2,
rng,
);
subdag.insert(starting_round + 2, indexset![certificate]);
Subdag::from(subdag).unwrap()
}
pub fn sample_subdags(rng: &mut TestRng) -> Vec<Subdag<CurrentNetwork>> {
let mut sample = Vec::with_capacity(10);
for _ in 0..10 {
sample.push(sample_subdag(rng));
}
sample
}
}