1use crate::helpers::{check_timestamp_for_liveness, fmt_id};
17use snarkos_node_bft_ledger_service::LedgerService;
18use snarkos_node_bft_storage_service::StorageService;
19use snarkvm::{
20 ledger::{
21 block::{Block, Transaction},
22 narwhal::{BatchCertificate, BatchHeader, Transmission, TransmissionID},
23 },
24 prelude::{Address, Field, Network, Result, anyhow, bail, ensure},
25 utilities::{cfg_into_iter, cfg_iter, cfg_sorted_by},
26};
27
28use anyhow::Context;
29use indexmap::{IndexMap, IndexSet, map::Entry};
30#[cfg(feature = "locktick")]
31use locktick::parking_lot::RwLock;
32use lru::LruCache;
33#[cfg(not(feature = "locktick"))]
34use parking_lot::RwLock;
35#[cfg(not(feature = "serial"))]
36use rayon::prelude::*;
37use std::{
38 collections::{HashMap, HashSet},
39 num::NonZeroUsize,
40 sync::{
41 Arc,
42 atomic::{AtomicU32, AtomicU64, Ordering},
43 },
44};
45
46#[derive(Clone, Debug)]
47pub struct Storage<N: Network>(Arc<StorageInner<N>>);
48
49impl<N: Network> std::ops::Deref for Storage<N> {
50 type Target = Arc<StorageInner<N>>;
51
52 fn deref(&self) -> &Self::Target {
53 &self.0
54 }
55}
56
57#[derive(Debug)]
77pub struct StorageInner<N: Network> {
78 ledger: Arc<dyn LedgerService<N>>,
80 current_height: AtomicU32,
83 current_round: AtomicU64,
92 gc_round: AtomicU64,
94 max_gc_rounds: u64,
96 rounds: RwLock<IndexMap<u64, IndexSet<(Field<N>, Address<N>)>>>,
99 unprocessed_certificates: RwLock<LruCache<Field<N>, BatchCertificate<N>>>,
101 certificates: RwLock<IndexMap<Field<N>, BatchCertificate<N>>>,
103 batch_ids: RwLock<IndexMap<Field<N>, u64>>,
105 transmissions: Arc<dyn StorageService<N>>,
107}
108
109impl<N: Network> Storage<N> {
110 pub fn new(
112 ledger: Arc<dyn LedgerService<N>>,
113 transmissions: Arc<dyn StorageService<N>>,
114 max_gc_rounds: u64,
115 ) -> Result<Self> {
116 let committee = ledger.current_committee().expect("Ledger is missing a committee.");
119 let current_round = committee.starting_round().max(1);
121 let unprocessed_cache_size = NonZeroUsize::new((N::LATEST_MAX_CERTIFICATES() * 2) as usize).unwrap();
123
124 let storage = Self(Arc::new(StorageInner {
126 ledger,
127 current_height: Default::default(),
128 current_round: AtomicU64::new(current_round),
129 gc_round: Default::default(),
130 max_gc_rounds,
131 rounds: Default::default(),
132 unprocessed_certificates: RwLock::new(LruCache::new(unprocessed_cache_size)),
133 certificates: Default::default(),
134 batch_ids: Default::default(),
135 transmissions,
136 }));
137
138 storage.garbage_collect_certificates(current_round)?;
141
142 Ok(storage)
144 }
145}
146
147impl<N: Network> Storage<N> {
148 pub fn current_height(&self) -> u32 {
150 self.current_height.load(Ordering::SeqCst)
152 }
153}
154
155impl<N: Network> Storage<N> {
156 pub fn current_round(&self) -> u64 {
158 self.current_round.load(Ordering::SeqCst)
160 }
161
162 pub fn gc_round(&self) -> u64 {
167 self.gc_round.load(Ordering::SeqCst)
169 }
170
171 pub fn max_gc_rounds(&self) -> u64 {
173 self.max_gc_rounds
174 }
175
176 pub fn increment_to_next_round(&self, current_round: u64) -> Result<u64> {
179 let next_round = current_round + 1;
181
182 {
184 let storage_round = self.current_round();
186 if next_round < storage_round {
188 return Ok(storage_round);
189 }
190
191 trace!("Incrementing storage from round {storage_round} to {next_round}");
192 }
193
194 let current_committee = self.ledger.current_committee()?;
196 let starting_round = current_committee.starting_round();
198 if next_round < starting_round {
200 let latest_block_round = self.ledger.latest_round();
202 info!(
204 "Syncing primary round ({next_round}) with the current committee's starting round ({starting_round}). Syncing with the latest block round {latest_block_round}..."
205 );
206 self.sync_round_with_block(latest_block_round);
208 return Ok(latest_block_round);
210 }
211
212 self.update_current_round(next_round);
214
215 #[cfg(feature = "metrics")]
216 metrics::gauge(metrics::bft::LAST_STORED_ROUND, next_round as f64);
217
218 let storage_round = self.current_round();
220 let gc_round = self.gc_round();
222 ensure!(next_round == storage_round, "The next round {next_round} does not match in storage ({storage_round})");
224 ensure!(next_round >= gc_round, "The next round {next_round} is behind the GC round {gc_round}");
226
227 info!("Starting round {next_round}...");
229 Ok(next_round)
230 }
231
232 fn update_current_round(&self, next_round: u64) {
234 self.current_round.store(next_round, Ordering::SeqCst);
236 }
237
238 pub(crate) fn garbage_collect_certificates(&self, next_round: u64) -> Result<()> {
240 let current_gc_round = self.gc_round();
242 let next_gc_round = next_round.saturating_sub(self.max_gc_rounds);
244 if next_gc_round > current_gc_round {
246 if self
247 .gc_round
248 .compare_exchange(current_gc_round, next_gc_round, Ordering::SeqCst, Ordering::SeqCst)
249 .is_err()
250 {
251 bail!("Concurrent updates to GC round detected.");
252 }
253
254 for gc_round in current_gc_round..=next_gc_round {
256 for id in self.get_certificate_ids_for_round(gc_round).into_iter() {
258 trace!(
259 "Garbage collecting certificate {id} at round {gc_round} (cut-off is round {next_gc_round})"
260 );
261 self.remove_certificate(id);
262 }
263 }
264 self.gc_round.store(next_gc_round, Ordering::SeqCst);
266 } else if next_gc_round < current_gc_round {
267 bail!("Attempted to decrease GC round from {current_gc_round} to {next_gc_round}");
268 }
269
270 Ok(())
271 }
272}
273
274impl<N: Network> Storage<N> {
275 pub fn contains_certificates_for_round(&self, round: u64) -> bool {
277 self.rounds.read().contains_key(&round)
279 }
280
281 pub fn contains_certificate(&self, certificate_id: Field<N>) -> bool {
283 self.certificates.read().contains_key(&certificate_id)
285 }
286
287 pub fn contains_certificate_in_round_from(&self, round: u64, author: Address<N>) -> bool {
289 self.rounds.read().get(&round).is_some_and(|set| set.iter().any(|(_, a)| a == &author))
290 }
291
292 pub fn contains_unprocessed_certificate(&self, certificate_id: Field<N>) -> bool {
294 self.unprocessed_certificates.read().contains(&certificate_id)
296 }
297
298 pub fn contains_batch(&self, batch_id: Field<N>) -> bool {
300 self.batch_ids.read().contains_key(&batch_id)
302 }
303
304 pub fn contains_transmission(&self, transmission_id: impl Into<TransmissionID<N>>) -> bool {
306 self.transmissions.contains_transmission(transmission_id.into())
307 }
308
309 pub fn get_transmission(&self, transmission_id: impl Into<TransmissionID<N>>) -> Option<Transmission<N>> {
312 self.transmissions.get_transmission(transmission_id.into())
313 }
314
315 pub fn get_round_for_certificate(&self, certificate_id: Field<N>) -> Option<u64> {
318 self.certificates.read().get(&certificate_id).map(|certificate| certificate.round())
320 }
321
322 pub fn get_round_for_batch(&self, batch_id: Field<N>) -> Option<u64> {
325 self.batch_ids.read().get(&batch_id).copied()
327 }
328
329 pub fn get_certificate_round(&self, certificate_id: Field<N>) -> Option<u64> {
332 self.certificates.read().get(&certificate_id).map(|certificate| certificate.round())
334 }
335
336 pub fn get_certificate(&self, certificate_id: Field<N>) -> Option<BatchCertificate<N>> {
339 self.certificates.read().get(&certificate_id).cloned()
341 }
342
343 pub fn get_unprocessed_certificate(&self, certificate_id: Field<N>) -> Option<BatchCertificate<N>> {
346 self.unprocessed_certificates.read().peek(&certificate_id).cloned()
348 }
349
350 pub fn get_certificate_for_round_with_author(&self, round: u64, author: Address<N>) -> Option<BatchCertificate<N>> {
354 if let Some(entries) = self.rounds.read().get(&round) {
356 let certificates = self.certificates.read();
357 entries.iter().find_map(
358 |(certificate_id, a)| if a == &author { certificates.get(certificate_id).cloned() } else { None },
359 )
360 } else {
361 Default::default()
362 }
363 }
364
365 pub fn get_certificates_for_round(&self, round: u64) -> IndexSet<BatchCertificate<N>> {
368 if round == 0 {
370 return Default::default();
371 }
372 if let Some(entries) = self.rounds.read().get(&round) {
374 let certificates = self.certificates.read();
375 entries.iter().flat_map(|(certificate_id, _)| certificates.get(certificate_id).cloned()).collect()
376 } else {
377 Default::default()
378 }
379 }
380
381 pub fn get_certificate_ids_for_round(&self, round: u64) -> IndexSet<Field<N>> {
384 if round == 0 {
386 return Default::default();
387 }
388 if let Some(entries) = self.rounds.read().get(&round) {
390 entries.iter().map(|(certificate_id, _)| *certificate_id).collect()
391 } else {
392 Default::default()
393 }
394 }
395
396 pub fn get_certificate_authors_for_round(&self, round: u64) -> HashSet<Address<N>> {
399 if round == 0 {
401 return Default::default();
402 }
403 if let Some(entries) = self.rounds.read().get(&round) {
405 entries.iter().map(|(_, author)| *author).collect()
406 } else {
407 Default::default()
408 }
409 }
410
411 pub(crate) fn get_pending_certificates(&self) -> IndexSet<BatchCertificate<N>> {
414 let rounds = self.rounds.read();
416 let certificates = self.certificates.read();
417
418 cfg_sorted_by!(rounds.clone(), |a, _, b, _| a.cmp(b))
420 .flat_map(|(_, certificates_for_round)| {
421 cfg_into_iter!(certificates_for_round).filter_map(|(certificate_id, _)| {
423 if self.ledger.contains_certificate(&certificate_id).unwrap_or(false) {
425 None
426 } else {
427 certificates.get(&certificate_id).cloned()
429 }
430 })
431 })
432 .collect()
433 }
434
435 pub fn check_batch_header(
459 &self,
460 batch_header: &BatchHeader<N>,
461 transmissions: HashMap<TransmissionID<N>, Transmission<N>>,
462 aborted_transmissions: HashSet<TransmissionID<N>>,
463 ) -> Result<Option<HashMap<TransmissionID<N>, Transmission<N>>>> {
464 let round = batch_header.round();
466 let gc_round = self.gc_round();
468 let gc_log = format!("(gc = {gc_round})");
470
471 if self.contains_batch(batch_header.batch_id()) {
473 debug!("Batch for round {round} already exists in storage {gc_log}");
474 return Ok(None);
475 }
476
477 let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(round) else {
479 bail!("Storage failed to retrieve the committee lookback for round {round} {gc_log}")
480 };
481 if !committee_lookback.is_committee_member(batch_header.author()) {
483 bail!("Author {} is not in the committee for round {round} {gc_log}", batch_header.author())
484 }
485
486 check_timestamp_for_liveness(batch_header.timestamp())?;
488
489 let missing_transmissions = self
491 .transmissions
492 .find_missing_transmissions(batch_header, transmissions, aborted_transmissions)
493 .map_err(|e| anyhow!("{e} for round {round} {gc_log}"))?;
494
495 let previous_round = round.saturating_sub(1);
497 if previous_round > gc_round {
499 let Ok(previous_committee_lookback) = self.ledger.get_committee_lookback_for_round(previous_round) else {
501 bail!("Missing committee for the previous round {previous_round} in storage {gc_log}")
502 };
503 if !self.contains_certificates_for_round(previous_round) {
505 bail!("Missing certificates for the previous round {previous_round} in storage {gc_log}")
506 }
507 if !cfg!(feature = "test_network")
512 && batch_header.previous_certificate_ids().len() > previous_committee_lookback.num_members()
513 {
514 bail!("Too many previous certificates for round {round} {gc_log}")
515 }
516 let mut previous_authors = HashSet::with_capacity(batch_header.previous_certificate_ids().len());
518 for previous_certificate_id in batch_header.previous_certificate_ids() {
520 let Some(previous_certificate) = self.get_certificate(*previous_certificate_id) else {
522 bail!(
523 "Missing previous certificate '{}' for certificate in round {round} {gc_log}",
524 fmt_id(previous_certificate_id)
525 )
526 };
527 if previous_certificate.round() != previous_round {
529 bail!("Round {round} certificate contains a round {previous_round} certificate {gc_log}")
530 }
531 if previous_authors.contains(&previous_certificate.author()) {
533 bail!("Round {round} certificate contains a duplicate author {gc_log}")
534 }
535 previous_authors.insert(previous_certificate.author());
537 }
538 if !previous_committee_lookback.is_quorum_threshold_reached(&previous_authors) {
540 bail!("Previous certificates for a batch in round {round} did not reach quorum threshold {gc_log}")
541 }
542 }
543
544 Ok(Some(missing_transmissions))
545 }
546
547 pub fn check_incoming_certificate(&self, certificate: &BatchCertificate<N>) -> Result<()> {
559 let certificate_author = certificate.author();
561 let certificate_round = certificate.round();
562
563 let committee_lookback = self.ledger.get_committee_lookback_for_round(certificate_round)?;
565
566 let mut signers: HashSet<Address<N>> =
569 certificate.signatures().map(|signature| signature.to_address()).collect();
570 signers.insert(certificate_author);
571 ensure!(
572 committee_lookback.is_quorum_threshold_reached(&signers),
573 "Certificate '{}' for round {certificate_round} does not meet quorum requirements",
574 certificate.id()
575 );
576
577 cfg_iter!(&signers).try_for_each(|signer| {
579 ensure!(
580 committee_lookback.is_committee_member(*signer),
581 "Signer '{signer}' of certificate '{}' for round {certificate_round} is not in the committee",
582 certificate.id()
583 );
584 Ok(())
585 })?;
586
587 Ok(())
588 }
589
590 pub fn check_certificate(
612 &self,
613 certificate: &BatchCertificate<N>,
614 transmissions: HashMap<TransmissionID<N>, Transmission<N>>,
615 aborted_transmissions: HashSet<TransmissionID<N>>,
616 ) -> Result<HashMap<TransmissionID<N>, Transmission<N>>> {
617 let round = certificate.round();
619 let gc_round = self.gc_round();
621 let gc_log = format!("(gc = {gc_round})");
623
624 if self.contains_certificate(certificate.id()) {
626 bail!("Certificate for round {round} already exists in storage {gc_log}")
627 }
628
629 if self.contains_certificate_in_round_from(round, certificate.author()) {
631 bail!("Certificate with this author for round {round} already exists in storage {gc_log}")
632 }
633
634 let Some(missing_transmissions) =
636 self.check_batch_header(certificate.batch_header(), transmissions, aborted_transmissions)?
637 else {
638 bail!("Certificate for round {round} already exists in storage {gc_log}")
639 };
640
641 check_timestamp_for_liveness(certificate.timestamp())?;
643
644 let Ok(committee_lookback) = self.ledger.get_committee_lookback_for_round(round) else {
646 bail!("Storage failed to retrieve the committee for round {round} {gc_log}")
647 };
648
649 let mut signers = HashSet::with_capacity(certificate.signatures().len() + 1);
651 signers.insert(certificate.author());
653
654 for signature in certificate.signatures() {
656 let signer = signature.to_address();
658 if !committee_lookback.is_committee_member(signer) {
660 bail!("Signer {signer} is not in the committee for round {round} {gc_log}")
661 }
662 signers.insert(signer);
664 }
665
666 if !committee_lookback.is_quorum_threshold_reached(&signers) {
668 bail!("Signatures for a batch in round {round} did not reach quorum threshold {gc_log}")
669 }
670
671 Ok(missing_transmissions)
672 }
673
674 pub fn insert_certificate(
692 &self,
693 certificate: BatchCertificate<N>,
694 transmissions: HashMap<TransmissionID<N>, Transmission<N>>,
695 aborted_transmissions: HashSet<TransmissionID<N>>,
696 ) -> Result<()> {
697 ensure!(certificate.round() > self.gc_round(), "Certificate round is at or below the GC round");
699 let missing_transmissions =
701 self.check_certificate(&certificate, transmissions, aborted_transmissions.clone())?;
702 self.insert_certificate_atomic(certificate, aborted_transmissions, missing_transmissions);
704 Ok(())
705 }
706
707 fn insert_certificate_atomic(
713 &self,
714 certificate: BatchCertificate<N>,
715 aborted_transmission_ids: HashSet<TransmissionID<N>>,
716 missing_transmissions: HashMap<TransmissionID<N>, Transmission<N>>,
717 ) {
718 let round = certificate.round();
720 let certificate_id = certificate.id();
722 let author = certificate.author();
724
725 self.rounds.write().entry(round).or_default().insert((certificate_id, author));
727 let transmission_ids = certificate.transmission_ids().clone();
729 self.certificates.write().insert(certificate_id, certificate);
731 self.unprocessed_certificates.write().pop(&certificate_id);
733 self.batch_ids.write().insert(certificate_id, round);
735 self.transmissions.insert_transmissions(
737 certificate_id,
738 transmission_ids,
739 aborted_transmission_ids,
740 missing_transmissions,
741 );
742 }
743
744 pub fn insert_unprocessed_certificate(&self, certificate: BatchCertificate<N>) -> Result<()> {
748 ensure!(certificate.round() > self.gc_round(), "Certificate round is at or below the GC round");
750 self.unprocessed_certificates.write().put(certificate.id(), certificate);
752
753 Ok(())
754 }
755
756 fn remove_certificate(&self, certificate_id: Field<N>) -> bool {
763 let Some(certificate) = self.get_certificate(certificate_id) else {
765 warn!("Certificate {certificate_id} does not exist in storage");
766 return false;
767 };
768 let round = certificate.round();
770 let author = certificate.author();
772
773 match self.rounds.write().entry(round) {
779 Entry::Occupied(mut entry) => {
780 entry.get_mut().swap_remove(&(certificate_id, author));
782 if entry.get().is_empty() {
784 entry.swap_remove();
785 }
786 }
787 Entry::Vacant(_) => {}
788 }
789 self.certificates.write().swap_remove(&certificate_id);
791 self.unprocessed_certificates.write().pop(&certificate_id);
793 self.batch_ids.write().swap_remove(&certificate_id);
795 self.transmissions.remove_transmissions(&certificate_id, certificate.transmission_ids());
797 true
799 }
800}
801
802impl<N: Network> Storage<N> {
803 pub(crate) fn sync_height_with_block(&self, next_height: u32) {
805 if next_height > self.current_height() {
807 self.current_height.store(next_height, Ordering::SeqCst);
809 }
810 }
811
812 pub(crate) fn sync_round_with_block(&self, next_round: u64) {
814 let next_round = next_round.max(1);
816 if next_round > self.current_round() {
818 self.update_current_round(next_round);
820 info!("Synced to round {next_round}...");
822 } else {
823 trace!(
824 "Skipping sync to round {next_round} as it is less than or equal to the current round ({})",
825 self.current_round()
826 );
827 }
828 }
829
830 pub(crate) fn sync_certificate_with_block(
832 &self,
833 block: &Block<N>,
834 certificate: BatchCertificate<N>,
835 unconfirmed_transactions: &HashMap<N::TransactionID, Transaction<N>>,
836 trusted_ledger_certificate: bool,
837 ) -> Result<()> {
838 let gc_round = self.gc_round();
840 if certificate.round() <= gc_round {
841 trace!("Got certificate for round {} below GC round ({gc_round}). Will not store it.", certificate.round());
842 return Ok(());
843 }
844
845 if self.contains_certificate(certificate.id()) {
847 trace!("Got certificate {} for round {} more than once.", certificate.id(), certificate.round());
848 return Ok(());
849 }
850 let mut missing_transmissions = HashMap::new();
852
853 let mut aborted_transmissions = HashSet::new();
855
856 let aborted_solutions: IndexSet<_> = block.aborted_solution_ids().iter().collect();
858 let aborted_transactions: IndexSet<_> = block.aborted_transaction_ids().iter().collect();
859
860 for transmission_id in certificate.transmission_ids() {
862 if missing_transmissions.contains_key(transmission_id) {
864 continue;
865 }
866 if self.contains_transmission(*transmission_id) {
868 continue;
869 }
870 match transmission_id {
872 TransmissionID::Ratification => (),
873 TransmissionID::Solution(solution_id, _) => {
874 if let Some(solution) = block.get_solution(solution_id) {
881 missing_transmissions.insert(*transmission_id, (*solution).into());
882 } else if let Ok(Some(solution)) = self.ledger.get_solution(solution_id) {
883 missing_transmissions.insert(*transmission_id, solution.into());
884 } else if aborted_solutions.contains(solution_id)
885 || self.ledger.contains_transmission(transmission_id).unwrap_or(false)
886 {
887 aborted_transmissions.insert(*transmission_id);
888 } else {
889 bail!("Missing solution {solution_id} for block {}", block.height());
890 }
891 }
892 TransmissionID::Transaction(transaction_id, _) => {
893 if let Some(transaction) = unconfirmed_transactions.get(transaction_id) {
900 missing_transmissions.insert(*transmission_id, transaction.clone().into());
901 } else if let Ok(Some(transaction)) = self.ledger.get_unconfirmed_transaction(*transaction_id) {
902 missing_transmissions.insert(*transmission_id, transaction.into());
903 } else if aborted_transactions.contains(transaction_id)
904 || self.ledger.contains_transmission(transmission_id).unwrap_or(false)
905 {
906 aborted_transmissions.insert(*transmission_id);
907 } else {
908 bail!("Missing transaction {transaction_id} for block {}", block.height());
909 }
910 }
911 }
912 }
913 let certificate_id = fmt_id(certificate.id());
915 debug!(
916 "Syncing certificate '{certificate_id}' for round {} with {} transmissions",
917 certificate.round(),
918 certificate.transmission_ids().len()
919 );
920
921 if trusted_ledger_certificate {
922 self.insert_certificate_atomic(certificate, aborted_transmissions, missing_transmissions);
923 Ok(())
924 } else {
925 self.insert_certificate(certificate, missing_transmissions, aborted_transmissions).with_context(|| {
926 format!("Failed to insert certificate '{certificate_id}' from block {}", block.height())
927 })
928 }
929 }
930}
931
932#[cfg(test)]
933impl<N: Network> Storage<N> {
934 pub fn ledger(&self) -> &Arc<dyn LedgerService<N>> {
936 &self.ledger
937 }
938
939 pub fn rounds_iter(&self) -> impl Iterator<Item = (u64, IndexSet<(Field<N>, Address<N>)>)> {
941 self.rounds.read().clone().into_iter()
942 }
943
944 pub fn certificates_iter(&self) -> impl Iterator<Item = (Field<N>, BatchCertificate<N>)> {
946 self.certificates.read().clone().into_iter()
947 }
948
949 pub fn batch_ids_iter(&self) -> impl Iterator<Item = (Field<N>, u64)> {
951 self.batch_ids.read().clone().into_iter()
952 }
953
954 pub fn transmissions_iter(
956 &self,
957 ) -> impl Iterator<Item = (TransmissionID<N>, (Transmission<N>, IndexSet<Field<N>>))> {
958 self.transmissions.as_hashmap().into_iter()
959 }
960
961 #[cfg(test)]
965 #[doc(hidden)]
966 pub(crate) fn testing_only_insert_certificate_testing_only(&self, certificate: BatchCertificate<N>) {
967 let round = certificate.round();
969 let certificate_id = certificate.id();
971 let author = certificate.author();
973
974 self.rounds.write().entry(round).or_default().insert((certificate_id, author));
976 let transmission_ids = certificate.transmission_ids().clone();
978 self.certificates.write().insert(certificate_id, certificate);
980 self.batch_ids.write().insert(certificate_id, round);
982
983 let missing_transmissions = transmission_ids
985 .iter()
986 .map(|id| (*id, Transmission::Transaction(snarkvm::ledger::narwhal::Data::Buffer(bytes::Bytes::new()))))
987 .collect::<HashMap<_, _>>();
988 self.transmissions.insert_transmissions(
990 certificate_id,
991 transmission_ids,
992 Default::default(),
993 missing_transmissions,
994 );
995 }
996}
997
998#[cfg(test)]
999pub(crate) mod tests {
1000 use super::*;
1001 use snarkos_node_bft_ledger_service::MockLedgerService;
1002 use snarkos_node_bft_storage_service::BFTMemoryService;
1003 use snarkvm::{
1004 ledger::narwhal::{Data, batch_certificate::test_helpers::sample_batch_certificate_for_round_with_committee},
1005 prelude::{Rng, TestRng},
1006 };
1007
1008 use ::bytes::Bytes;
1009 use indexmap::indexset;
1010
1011 type CurrentNetwork = snarkvm::prelude::MainnetV0;
1012
1013 pub fn assert_storage<N: Network>(
1015 storage: &Storage<N>,
1016 rounds: &[(u64, IndexSet<(Field<N>, Address<N>)>)],
1017 certificates: &[(Field<N>, BatchCertificate<N>)],
1018 batch_ids: &[(Field<N>, u64)],
1019 transmissions: &HashMap<TransmissionID<N>, (Transmission<N>, IndexSet<Field<N>>)>,
1020 ) {
1021 assert_eq!(storage.rounds_iter().collect::<Vec<_>>(), *rounds);
1023 assert_eq!(storage.certificates_iter().collect::<Vec<_>>(), *certificates);
1025 assert_eq!(storage.batch_ids_iter().collect::<Vec<_>>(), *batch_ids);
1027 assert_eq!(storage.transmissions_iter().collect::<HashMap<_, _>>(), *transmissions);
1029 }
1030
1031 fn sample_transmission(rng: &mut TestRng) -> Transmission<CurrentNetwork> {
1033 let s = |rng: &mut TestRng| Data::Buffer(Bytes::from((0..512).map(|_| rng.random::<u8>()).collect::<Vec<_>>()));
1035 let t =
1037 |rng: &mut TestRng| Data::Buffer(Bytes::from((0..2048).map(|_| rng.random::<u8>()).collect::<Vec<_>>()));
1038 match rng.random::<bool>() {
1040 true => Transmission::Solution(s(rng)),
1041 false => Transmission::Transaction(t(rng)),
1042 }
1043 }
1044
1045 pub(crate) fn sample_transmissions(
1047 certificate: &BatchCertificate<CurrentNetwork>,
1048 rng: &mut TestRng,
1049 ) -> (
1050 HashMap<TransmissionID<CurrentNetwork>, Transmission<CurrentNetwork>>,
1051 HashMap<TransmissionID<CurrentNetwork>, (Transmission<CurrentNetwork>, IndexSet<Field<CurrentNetwork>>)>,
1052 ) {
1053 let certificate_id = certificate.id();
1055
1056 let mut missing_transmissions = HashMap::new();
1057 let mut transmissions = HashMap::<_, (_, IndexSet<Field<CurrentNetwork>>)>::new();
1058 for transmission_id in certificate.transmission_ids() {
1059 let transmission = sample_transmission(rng);
1061 missing_transmissions.insert(*transmission_id, transmission.clone());
1063 transmissions
1065 .entry(*transmission_id)
1066 .or_insert((transmission, Default::default()))
1067 .1
1068 .insert(certificate_id);
1069 }
1070 (missing_transmissions, transmissions)
1071 }
1072
1073 #[test]
1076 fn test_certificate_insert_remove() {
1077 let rng = &mut TestRng::default();
1078
1079 let committee = snarkvm::ledger::committee::test_helpers::sample_committee(rng);
1081 let ledger = Arc::new(MockLedgerService::new(committee));
1083 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1085
1086 assert_storage(&storage, &[], &[], &[], &Default::default());
1088
1089 let certificate = snarkvm::ledger::narwhal::batch_certificate::test_helpers::sample_batch_certificate(rng);
1091 let certificate_id = certificate.id();
1093 let round = certificate.round();
1095 let author = certificate.author();
1097
1098 let (missing_transmissions, transmissions) = sample_transmissions(&certificate, rng);
1100
1101 storage.insert_certificate_atomic(certificate.clone(), Default::default(), missing_transmissions);
1103 assert!(storage.contains_certificate(certificate_id));
1105 assert_eq!(storage.get_certificates_for_round(round), indexset! { certificate.clone() });
1107 assert_eq!(storage.get_certificate_for_round_with_author(round, author), Some(certificate.clone()));
1109
1110 {
1112 let rounds = [(round, indexset! { (certificate_id, author) })];
1114 let certificates = [(certificate_id, certificate.clone())];
1116 let batch_ids = [(certificate_id, round)];
1118 assert_storage(&storage, &rounds, &certificates, &batch_ids, &transmissions);
1120 }
1121
1122 let candidate_certificate = storage.get_certificate(certificate_id).unwrap();
1124 assert_eq!(certificate, candidate_certificate);
1126
1127 assert!(storage.remove_certificate(certificate_id));
1129 assert!(!storage.contains_certificate(certificate_id));
1131 assert!(storage.get_certificates_for_round(round).is_empty());
1133 assert_eq!(storage.get_certificate_for_round_with_author(round, author), None);
1135 assert_storage(&storage, &[], &[], &[], &Default::default());
1137 }
1138
1139 #[test]
1140 fn test_certificate_duplicate() {
1141 let rng = &mut TestRng::default();
1142
1143 let committee = snarkvm::ledger::committee::test_helpers::sample_committee(rng);
1145 let ledger = Arc::new(MockLedgerService::new(committee));
1147 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1149
1150 assert_storage(&storage, &[], &[], &[], &Default::default());
1152
1153 let certificate = snarkvm::ledger::narwhal::batch_certificate::test_helpers::sample_batch_certificate(rng);
1155 let certificate_id = certificate.id();
1157 let round = certificate.round();
1159 let author = certificate.author();
1161
1162 let rounds = [(round, indexset! { (certificate_id, author) })];
1164 let certificates = [(certificate_id, certificate.clone())];
1166 let batch_ids = [(certificate_id, round)];
1168 let (missing_transmissions, transmissions) = sample_transmissions(&certificate, rng);
1170
1171 storage.insert_certificate_atomic(certificate.clone(), Default::default(), missing_transmissions.clone());
1173 assert!(storage.contains_certificate(certificate_id));
1175 assert_storage(&storage, &rounds, &certificates, &batch_ids, &transmissions);
1177
1178 storage.insert_certificate_atomic(certificate.clone(), Default::default(), Default::default());
1180 assert!(storage.contains_certificate(certificate_id));
1182 assert_storage(&storage, &rounds, &certificates, &batch_ids, &transmissions);
1184
1185 storage.insert_certificate_atomic(certificate, Default::default(), missing_transmissions);
1187 assert!(storage.contains_certificate(certificate_id));
1189 assert_storage(&storage, &rounds, &certificates, &batch_ids, &transmissions);
1191 }
1192
1193 #[test]
1197 fn test_certificate_insert_with_aborted_transmissions() {
1198 use std::collections::HashSet;
1199
1200 let rng = &mut TestRng::default();
1201
1202 let committee = snarkvm::ledger::committee::test_helpers::sample_committee(rng);
1203 let ledger = Arc::new(MockLedgerService::new(committee));
1204 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1205
1206 let certificate = snarkvm::ledger::narwhal::batch_certificate::test_helpers::sample_batch_certificate(rng);
1207 let certificate_id = certificate.id();
1208 let round = certificate.round();
1209 let transmission_ids: Vec<_> = certificate.transmission_ids().iter().copied().collect();
1210
1211 if transmission_ids.len() < 2 {
1212 let (missing_transmissions, _) = sample_transmissions(&certificate, rng);
1214 storage.insert_certificate_atomic(certificate.clone(), HashSet::new(), missing_transmissions);
1215 for id in certificate.transmission_ids() {
1216 assert!(storage.contains_transmission(*id));
1217 }
1218 return;
1219 }
1220
1221 let (all_missing, _) = sample_transmissions(&certificate, rng);
1222 let aborted_id = transmission_ids[0];
1223 let aborted_transmission_ids: HashSet<_> = [aborted_id].into_iter().collect();
1224 let mut missing_transmissions = all_missing;
1225 missing_transmissions.remove(&aborted_id);
1226
1227 storage.insert_certificate_atomic(certificate.clone(), aborted_transmission_ids, missing_transmissions);
1228
1229 assert!(storage.contains_certificate(certificate_id));
1230 assert_eq!(storage.get_certificates_for_round(round), indexset! { certificate.clone() });
1231
1232 for id in certificate.transmission_ids() {
1234 assert!(
1235 storage.contains_transmission(*id),
1236 "contains_transmission should be true for all transmission IDs including aborted {id:?}"
1237 );
1238 }
1239
1240 assert!(
1242 storage.get_transmission(aborted_id).is_none(),
1243 "Aborted transmission should not have content in storage"
1244 );
1245 for id in transmission_ids.iter().skip(1) {
1246 assert!(
1247 storage.get_transmission(*id).is_some(),
1248 "Non-aborted transmission {id:?} should have content in storage"
1249 );
1250 }
1251 }
1252
1253 #[test]
1255 fn test_valid_incoming_certificate() {
1256 let rng = &mut TestRng::default();
1257
1258 let (committee, private_keys) =
1260 snarkvm::ledger::committee::test_helpers::sample_committee_and_keys_for_round(0, 5, rng);
1261 let ledger = Arc::new(MockLedgerService::new(committee));
1263 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1265
1266 let mut previous_certs = IndexSet::default();
1268
1269 for round in 1..=100 {
1270 let mut new_certs = IndexSet::default();
1271
1272 for private_key in private_keys.iter() {
1274 let other_keys: Vec<_> = private_keys.iter().cloned().filter(|k| k != private_key).collect();
1275
1276 let certificate = sample_batch_certificate_for_round_with_committee(
1277 round,
1278 previous_certs.clone(),
1279 private_key,
1280 &other_keys,
1281 rng,
1282 );
1283 storage.check_incoming_certificate(&certificate).expect("Valid certificate rejected");
1284 new_certs.insert(certificate.id());
1285
1286 let (missing_transmissions, _transmissions) = sample_transmissions(&certificate, rng);
1288 storage.insert_certificate_atomic(certificate, Default::default(), missing_transmissions);
1289 }
1290
1291 previous_certs = new_certs;
1292 }
1293 }
1294
1295 #[test]
1297 fn test_invalid_incoming_certificate_missing_signature() {
1298 let rng = &mut TestRng::default();
1299
1300 let (committee, private_keys) =
1302 snarkvm::ledger::committee::test_helpers::sample_committee_and_keys_for_round(0, 10, rng);
1303 let ledger = Arc::new(MockLedgerService::new(committee));
1305 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1307
1308 let mut previous_certs = IndexSet::default();
1310
1311 for round in 1..=5 {
1312 let mut new_certs = IndexSet::default();
1313
1314 for private_key in private_keys.iter() {
1316 if round < 5 {
1317 let other_keys: Vec<_> = private_keys.iter().cloned().filter(|k| k != private_key).collect();
1318
1319 let certificate = sample_batch_certificate_for_round_with_committee(
1320 round,
1321 previous_certs.clone(),
1322 private_key,
1323 &other_keys,
1324 rng,
1325 );
1326 storage.check_incoming_certificate(&certificate).expect("Valid certificate rejected");
1327 new_certs.insert(certificate.id());
1328
1329 let (missing_transmissions, _transmissions) = sample_transmissions(&certificate, rng);
1331 storage.insert_certificate_atomic(certificate, Default::default(), missing_transmissions);
1332 } else {
1333 let other_keys: Vec<_> = private_keys[0..=3].iter().cloned().filter(|k| k != private_key).collect();
1335
1336 let certificate = sample_batch_certificate_for_round_with_committee(
1337 round,
1338 previous_certs.clone(),
1339 private_key,
1340 &other_keys,
1341 rng,
1342 );
1343 assert!(storage.check_incoming_certificate(&certificate).is_err());
1344 }
1345 }
1346
1347 previous_certs = new_certs;
1348 }
1349 }
1350
1351 #[test]
1353 fn test_invalid_certificate_insufficient_previous_certs() {
1354 let rng = &mut TestRng::default();
1355
1356 let (committee, private_keys) =
1358 snarkvm::ledger::committee::test_helpers::sample_committee_and_keys_for_round(0, 10, rng);
1359 let ledger = Arc::new(MockLedgerService::new(committee));
1361 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1363
1364 let mut previous_certs = IndexSet::default();
1366
1367 for round in 1..=6 {
1368 let mut new_certs = IndexSet::default();
1369
1370 for private_key in private_keys.iter() {
1372 let other_keys: Vec<_> = private_keys.iter().cloned().filter(|k| k != private_key).collect();
1373
1374 let certificate = sample_batch_certificate_for_round_with_committee(
1375 round,
1376 previous_certs.clone(),
1377 private_key,
1378 &other_keys,
1379 rng,
1380 );
1381
1382 let (_missing_transmissions, transmissions) = sample_transmissions(&certificate, rng);
1384 let transmissions = transmissions.into_iter().map(|(k, (t, _))| (k, t)).collect();
1385
1386 if round <= 5 {
1387 new_certs.insert(certificate.id());
1388 storage
1389 .insert_certificate(certificate, transmissions, Default::default())
1390 .expect("Valid certificate rejected");
1391 } else {
1392 assert!(storage.insert_certificate(certificate, transmissions, Default::default()).is_err());
1393 }
1394 }
1395
1396 if round < 5 {
1397 previous_certs = new_certs;
1398 } else {
1399 previous_certs = new_certs.into_iter().skip(6).collect();
1401 }
1402 }
1403 }
1404
1405 #[test]
1407 fn test_invalid_certificate_wrong_round_number() {
1408 let rng = &mut TestRng::default();
1409
1410 let (committee, private_keys) =
1412 snarkvm::ledger::committee::test_helpers::sample_committee_and_keys_for_round(0, 10, rng);
1413 let ledger = Arc::new(MockLedgerService::new(committee));
1415 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1417
1418 let mut previous_certs = IndexSet::default();
1420
1421 for round in 1..=6 {
1422 let mut new_certs = IndexSet::default();
1423
1424 for private_key in private_keys.iter() {
1426 let cert_round = round.min(5); let other_keys: Vec<_> = private_keys.iter().cloned().filter(|k| k != private_key).collect();
1428
1429 let certificate = sample_batch_certificate_for_round_with_committee(
1430 cert_round,
1431 previous_certs.clone(),
1432 private_key,
1433 &other_keys,
1434 rng,
1435 );
1436
1437 let (_missing_transmissions, transmissions) = sample_transmissions(&certificate, rng);
1439 let transmissions = transmissions.into_iter().map(|(k, (t, _))| (k, t)).collect();
1440
1441 if round <= 5 {
1442 new_certs.insert(certificate.id());
1443 storage
1444 .insert_certificate(certificate, transmissions, Default::default())
1445 .expect("Valid certificate rejected");
1446 } else {
1447 assert!(storage.insert_certificate(certificate, transmissions, Default::default()).is_err());
1448 }
1449 }
1450
1451 if round < 5 {
1452 previous_certs = new_certs;
1453 } else {
1454 previous_certs = new_certs.into_iter().skip(6).collect();
1456 }
1457 }
1458 }
1459}
1460
1461#[cfg(test)]
1462pub mod prop_tests {
1463 use super::*;
1464 use crate::helpers::{now, storage::tests::assert_storage};
1465 use snarkos_node_bft_events::committee_prop_tests::{CommitteeContext, ValidatorSet};
1466 use snarkos_node_bft_ledger_service::MockLedgerService;
1467 use snarkos_node_bft_storage_service::BFTMemoryService;
1468 use snarkvm::{
1469 ledger::{
1470 narwhal::{BatchHeader, Data},
1471 puzzle::SolutionID,
1472 },
1473 prelude::{Signature, Uniform},
1474 };
1475
1476 use ::bytes::Bytes;
1477 use indexmap::indexset;
1478 use proptest::{
1479 collection,
1480 prelude::{Arbitrary, BoxedStrategy, Just, Strategy, any},
1481 prop_oneof,
1482 sample::{Selector, size_range},
1483 };
1484 use rand::{CryptoRng, SeedableRng, TryCryptoRng, TryRng};
1485 use rand_chacha::ChaChaRng;
1486 use std::fmt::Debug;
1487 use test_strategy::proptest;
1488
1489 type CurrentNetwork = snarkvm::prelude::MainnetV0;
1490
1491 impl Arbitrary for Storage<CurrentNetwork> {
1492 type Parameters = CommitteeContext;
1493 type Strategy = BoxedStrategy<Storage<CurrentNetwork>>;
1494
1495 fn arbitrary() -> Self::Strategy {
1496 (any::<CommitteeContext>(), 0..BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64)
1497 .prop_map(|(CommitteeContext(committee, _), gc_rounds)| {
1498 let ledger = Arc::new(MockLedgerService::new(committee));
1499 Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), gc_rounds).unwrap()
1500 })
1501 .boxed()
1502 }
1503
1504 fn arbitrary_with(context: Self::Parameters) -> Self::Strategy {
1505 (Just(context), 0..BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64)
1506 .prop_map(|(CommitteeContext(committee, _), gc_rounds)| {
1507 let ledger = Arc::new(MockLedgerService::new(committee));
1508 Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), gc_rounds).unwrap()
1509 })
1510 .boxed()
1511 }
1512 }
1513
1514 #[derive(Debug)]
1517 pub struct CryptoTestRng(ChaChaRng);
1518
1519 impl Arbitrary for CryptoTestRng {
1520 type Parameters = ();
1521 type Strategy = BoxedStrategy<CryptoTestRng>;
1522
1523 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1524 use proptest::prelude::RngCore as ProptestRngCore;
1525 Just(0).prop_perturb(|_, mut rng| CryptoTestRng(ChaChaRng::seed_from_u64(rng.next_u64()))).boxed()
1526 }
1527 }
1528
1529 impl TryRng for CryptoTestRng {
1530 type Error = core::convert::Infallible;
1531
1532 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
1533 TryRng::try_next_u32(&mut self.0)
1534 }
1535
1536 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
1537 TryRng::try_next_u64(&mut self.0)
1538 }
1539
1540 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
1541 TryRng::try_fill_bytes(&mut self.0, dest)
1542 }
1543 }
1544
1545 impl TryCryptoRng for CryptoTestRng {}
1546
1547 #[derive(Debug, Clone)]
1548 pub struct AnyTransmission(pub Transmission<CurrentNetwork>);
1549
1550 impl Arbitrary for AnyTransmission {
1551 type Parameters = ();
1552 type Strategy = BoxedStrategy<AnyTransmission>;
1553
1554 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1555 any_transmission().prop_map(AnyTransmission).boxed()
1556 }
1557 }
1558
1559 #[derive(Debug, Clone)]
1560 pub struct AnyTransmissionID(pub TransmissionID<CurrentNetwork>);
1561
1562 impl Arbitrary for AnyTransmissionID {
1563 type Parameters = ();
1564 type Strategy = BoxedStrategy<AnyTransmissionID>;
1565
1566 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1567 any_transmission_id().prop_map(AnyTransmissionID).boxed()
1568 }
1569 }
1570
1571 fn any_transmission() -> BoxedStrategy<Transmission<CurrentNetwork>> {
1572 prop_oneof![
1573 (collection::vec(any::<u8>(), 512..=512))
1574 .prop_map(|bytes| Transmission::Solution(Data::Buffer(Bytes::from(bytes)))),
1575 (collection::vec(any::<u8>(), 2048..=2048))
1576 .prop_map(|bytes| Transmission::Transaction(Data::Buffer(Bytes::from(bytes)))),
1577 ]
1578 .boxed()
1579 }
1580
1581 pub fn any_solution_id() -> BoxedStrategy<SolutionID<CurrentNetwork>> {
1582 any::<u64>().prop_map(|x| x.into()).boxed()
1583 }
1584
1585 pub fn any_transaction_id() -> BoxedStrategy<<CurrentNetwork as Network>::TransactionID> {
1586 any::<u64>()
1587 .prop_map(|seed| {
1588 let rng = &mut ChaChaRng::seed_from_u64(seed);
1589 <CurrentNetwork as Network>::TransactionID::from(Field::rand(rng))
1590 })
1591 .boxed()
1592 }
1593
1594 pub fn any_transmission_id() -> BoxedStrategy<TransmissionID<CurrentNetwork>> {
1595 prop_oneof![
1596 (any_transaction_id(), any::<<CurrentNetwork as Network>::TransmissionChecksum>())
1597 .prop_map(|(id, cs)| TransmissionID::Transaction(id, cs)),
1598 (any_solution_id(), any::<<CurrentNetwork as Network>::TransmissionChecksum>())
1599 .prop_map(|(id, cs)| TransmissionID::Solution(id, cs)),
1600 ]
1601 .boxed()
1602 }
1603
1604 pub fn sign_batch_header<R: CryptoRng>(
1605 validator_set: &ValidatorSet,
1606 batch_header: &BatchHeader<CurrentNetwork>,
1607 rng: &mut R,
1608 ) -> IndexSet<Signature<CurrentNetwork>> {
1609 let mut signatures = IndexSet::with_capacity(validator_set.0.len());
1610 for validator in validator_set.0.iter() {
1611 let private_key = validator.private_key;
1612 signatures.insert(private_key.sign(&[batch_header.batch_id()], rng).unwrap());
1613 }
1614 signatures
1615 }
1616
1617 #[proptest]
1618 fn test_certificate_duplicate(
1619 context: CommitteeContext,
1620 #[any(size_range(1..16).lift())] transmissions: Vec<(AnyTransmissionID, AnyTransmission)>,
1621 mut rng: CryptoTestRng,
1622 selector: Selector,
1623 ) {
1624 let CommitteeContext(committee, ValidatorSet(validators)) = context;
1625 let committee_id = committee.id();
1626
1627 let ledger = Arc::new(MockLedgerService::new(committee));
1629 let storage = Storage::<CurrentNetwork>::new(ledger, Arc::new(BFTMemoryService::new()), 1).unwrap();
1630
1631 assert_storage(&storage, &[], &[], &[], &Default::default());
1633
1634 let signer = selector.select(&validators);
1636
1637 let mut transmission_map = IndexMap::new();
1638
1639 for (AnyTransmissionID(id), AnyTransmission(t)) in transmissions.iter() {
1640 transmission_map.insert(*id, t.clone());
1641 }
1642
1643 let batch_header = BatchHeader::new(
1644 &signer.private_key,
1645 0,
1646 now(),
1647 committee_id,
1648 transmission_map.keys().cloned().collect(),
1649 Default::default(),
1650 &mut rng,
1651 )
1652 .unwrap();
1653
1654 let mut validators = validators.clone();
1657 validators.remove(signer);
1658
1659 let certificate = BatchCertificate::from(
1660 batch_header.clone(),
1661 sign_batch_header(&ValidatorSet(validators), &batch_header, &mut rng),
1662 )
1663 .unwrap();
1664
1665 let certificate_id = certificate.id();
1667 let mut internal_transmissions = HashMap::<_, (_, IndexSet<Field<CurrentNetwork>>)>::new();
1668 for (AnyTransmissionID(id), AnyTransmission(t)) in transmissions.iter().cloned() {
1669 internal_transmissions.entry(id).or_insert((t, Default::default())).1.insert(certificate_id);
1670 }
1671
1672 let round = certificate.round();
1674 let author = certificate.author();
1676
1677 let rounds = [(round, indexset! { (certificate_id, author) })];
1679 let certificates = [(certificate_id, certificate.clone())];
1681 let batch_ids = [(certificate_id, round)];
1683
1684 let missing_transmissions: HashMap<TransmissionID<CurrentNetwork>, Transmission<CurrentNetwork>> =
1686 transmission_map.into_iter().collect();
1687 storage.insert_certificate_atomic(certificate.clone(), Default::default(), missing_transmissions.clone());
1688 assert!(storage.contains_certificate(certificate_id));
1690 assert_storage(&storage, &rounds, &certificates, &batch_ids, &internal_transmissions);
1692
1693 storage.insert_certificate_atomic(certificate.clone(), Default::default(), Default::default());
1695 assert!(storage.contains_certificate(certificate_id));
1697 assert_storage(&storage, &rounds, &certificates, &batch_ids, &internal_transmissions);
1699
1700 storage.insert_certificate_atomic(certificate, Default::default(), missing_transmissions);
1702 assert!(storage.contains_certificate(certificate_id));
1704 assert_storage(&storage, &rounds, &certificates, &batch_ids, &internal_transmissions);
1706 }
1707}