1use crate::{
17 Gateway,
18 MAX_FETCH_TIMEOUT,
19 Transport,
20 events::{CertificateRequest, CertificateResponse, Event},
21 helpers::{Pending, Storage, SyncReceiver, fmt_id, max_redundant_requests},
22 ledger_service::{BeginLedgerUpdateError, LedgerService},
23 spawn_blocking,
24};
25
26use snarkos_node_sync::{BftSyncMode, BlockSync, InsertBlockResponseError, Ping, locators::BlockLocators};
27use snarkos_utilities::CallbackHandle;
28
29use snarkvm::{
30 console::{
31 network::{ConsensusVersion, Network},
32 types::Field,
33 },
34 ledger::{CheckBlockError, PendingBlock, authority::Authority, block::Block, narwhal::BatchCertificate},
35 utilities::{cfg_into_iter, cfg_iter, ensure_equals, flatten_error},
36};
37
38use anyhow::{Context, Result, anyhow, bail, ensure};
39#[cfg(feature = "locktick")]
40use locktick::{parking_lot::Mutex, tokio::Mutex as TMutex};
41#[cfg(not(feature = "locktick"))]
42use parking_lot::Mutex;
43#[cfg(not(feature = "serial"))]
44use rayon::prelude::*;
45use std::{
46 collections::{HashMap, HashSet, VecDeque},
47 future::Future,
48 net::SocketAddr,
49 ops::Deref,
50 sync::Arc,
51 time::Duration,
52};
53#[cfg(not(feature = "locktick"))]
54use tokio::sync::Mutex as TMutex;
55use tokio::{sync::oneshot, task::JoinHandle};
56
57#[async_trait::async_trait]
60pub trait SyncCallback<N: Network>: Send + std::marker::Sync {
61 fn add_certificate_from_sync(&self, certificate: BatchCertificate<N>);
63
64 fn commit_certificate_from_sync(&self, certificate: &BatchCertificate<N>);
66}
67
68#[derive(Clone)]
81pub struct Sync<N: Network> {
82 gateway: Gateway<N>,
84 storage: Storage<N>,
86 ledger: Arc<dyn LedgerService<N>>,
88 block_sync: Arc<BlockSync<N>>,
90 pending: Arc<Pending<Field<N>, BatchCertificate<N>>>,
92 sync_callback: Arc<CallbackHandle<Arc<dyn SyncCallback<N>>>>,
94 handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
96 response_lock: Arc<TMutex<()>>,
98
99 pending_blocks: Arc<Mutex<VecDeque<PendingBlock<N>>>>,
107}
108
109impl<N: Network> Sync<N> {
110 const MAX_SYNC_INTERVAL: Duration = Duration::from_secs(30);
113
114 pub fn new(
116 gateway: Gateway<N>,
117 storage: Storage<N>,
118 ledger: Arc<dyn LedgerService<N>>,
119 block_sync: Arc<BlockSync<N>>,
120 ) -> Self {
121 block_sync.set_bft_sync_mode(BftSyncMode::Fast);
123
124 Self {
126 gateway,
127 storage,
128 ledger,
129 block_sync,
130 pending: Default::default(),
131 sync_callback: Default::default(),
132 handles: Default::default(),
133 response_lock: Default::default(),
134 pending_blocks: Default::default(),
135 }
136 }
137
138 pub async fn wait_for_synced(&self) {
141 self.block_sync.wait_for_synced().await;
142 }
143
144 pub fn wait_for_synced_if_syncing(&self) -> Option<futures::future::BoxFuture<()>> {
147 self.block_sync.wait_for_synced_if_syncing()
148 }
149
150 pub fn initialize(&self, sync_callback: Option<Arc<dyn SyncCallback<N>>>) -> Result<()> {
152 if let Some(callback) = sync_callback {
154 self.sync_callback.set(callback).with_context(|| "Failed to set sync callback")?;
155 }
156
157 info!("Syncing storage with the ledger...");
158
159 self.sync_storage_with_ledger_at_bootup()
161 .with_context(|| "Syncing storage with the ledger at bootup failed")?;
162
163 debug!("Finished initial block synchronization at startup");
164 Ok(())
165 }
166
167 pub async fn run(&self, ping: Option<Arc<Ping<N>>>, sync_receiver: SyncReceiver<N>) -> Result<()> {
172 info!("Starting the sync module...");
173
174 let self_ = self.clone();
176 self.spawn(async move {
177 loop {
178 let _ = tokio::time::timeout(Self::MAX_SYNC_INTERVAL, self_.block_sync.wait_for_peer_update()).await;
180
181 self_.try_issuing_block_requests().await;
183
184 }
186 });
187
188 let self_ = self.clone();
190 let ping = ping.clone();
191 self.spawn(async move {
192 loop {
193 let _ =
195 tokio::time::timeout(Self::MAX_SYNC_INTERVAL, self_.block_sync.wait_for_block_responses()).await;
196
197 let ping = ping.clone();
198 let self_ = self_.clone();
199 let hdl = tokio::spawn(async move {
200 self_.try_advancing_block_synchronization(&ping).await;
201 });
202
203 if let Err(err) = hdl.await
204 && let Ok(panic) = err.try_into_panic()
205 {
206 error!("Sync block advancement panicked: {panic:?}");
207 }
208
209 }
212 });
213
214 let self_ = self.clone();
216 self.spawn(async move {
217 loop {
218 tokio::time::sleep(MAX_FETCH_TIMEOUT).await;
220
221 let self__ = self_.clone();
223 let _ = spawn_blocking!({
224 self__.pending.clear_expired_callbacks();
225 Ok(())
226 });
227 }
228 });
229
230 let SyncReceiver {
234 mut rx_block_sync_insert_block_response,
235 mut rx_block_sync_remove_peer,
236 mut rx_block_sync_update_peer_locators,
237 mut rx_certificate_request,
238 mut rx_certificate_response,
239 } = sync_receiver;
240
241 let self_ = self.clone();
248 self.spawn(async move {
249 while let Some((peer_ip, blocks, latest_consensus_version, callback)) =
250 rx_block_sync_insert_block_response.recv().await
251 {
252 let result = self_.insert_block_response(peer_ip, blocks, latest_consensus_version).await;
253
254 if let Err(err) = &result {
256 if err.is_benign() {
257 trace!("Failed to insert block response from '{peer_ip}' - {err}");
258 } else {
259 warn!("Failed to insert block response from '{peer_ip}' - {err}");
260 }
261 }
262
263 callback.send(result).ok();
264 }
265 });
266
267 let self_ = self.clone();
269 self.spawn(async move {
270 while let Some((peer_ip, tx)) = rx_block_sync_remove_peer.recv().await {
271 self_.remove_peer(peer_ip);
272 tx.send(()).ok();
273 }
274 });
275
276 let self_ = self.clone();
283 self.spawn(async move {
284 while let Some((peer_ip, locators, callback)) = rx_block_sync_update_peer_locators.recv().await {
285 let self_clone = self_.clone();
286 tokio::spawn(async move {
287 callback.send(self_clone.update_peer_locators(peer_ip, locators)).ok();
288 });
289 }
290 });
291
292 let self_ = self.clone();
298 self.spawn(async move {
299 while let Some((peer_ip, certificate_request)) = rx_certificate_request.recv().await {
300 self_.send_certificate_response(peer_ip, certificate_request);
301 }
302 });
303
304 let self_ = self.clone();
310 self.spawn(async move {
311 while let Some((peer_ip, certificate_response)) = rx_certificate_response.recv().await {
312 self_.finish_certificate_request(peer_ip, certificate_response);
313 }
314 });
315
316 Ok(())
317 }
318
319 async fn try_issuing_block_requests(&self) {
324 self.block_sync.try_issuing_block_requests(&self.gateway).await;
325 }
326
327 #[cfg(test)]
329 pub(crate) fn testing_only_set_sync_height_testing_only(&self, height: u32) {
330 self.block_sync.set_sync_height(height);
331 }
332}
333
334impl<N: Network> Sync<N> {
336 async fn insert_block_response(
338 &self,
339 peer_ip: SocketAddr,
340 blocks: Vec<Block<N>>,
341 latest_consensus_version: Option<ConsensusVersion>,
342 ) -> Result<(), InsertBlockResponseError<N>> {
343 self.block_sync.insert_block_responses(peer_ip, blocks, latest_consensus_version)
344
345 }
348
349 fn update_peer_locators(&self, peer_ip: SocketAddr, locators: BlockLocators<N>) -> Result<()> {
351 self.block_sync.update_peer_locators(peer_ip, &locators)
352 }
353
354 fn remove_peer(&self, peer_ip: SocketAddr) {
356 self.block_sync.remove_peer(&peer_ip)
357 }
358
359 #[cfg(test)]
360 pub fn testing_only_update_peer_locators_testing_only(
361 &self,
362 peer_ip: SocketAddr,
363 locators: BlockLocators<N>,
364 ) -> Result<()> {
365 self.update_peer_locators(peer_ip, locators)
366 }
367}
368
369impl<N: Network> Sync<N> {
371 fn sync_storage_with_ledger_at_bootup(&self) -> Result<()> {
375 let mut pending_blocks = self.pending_blocks.lock();
376 let latest_ledger_block = self.ledger.latest_block();
377
378 while let Some(block) = pending_blocks.front()
380 && block.height() <= latest_ledger_block.height()
381 {
382 pending_blocks.pop_front();
383 }
384
385 let latest_block: &Block<N> = pending_blocks.back().map(|block| block.deref()).unwrap_or(&latest_ledger_block);
386 let max_height = latest_block.height();
387
388 let max_gc_blocks = u32::try_from(self.storage.max_gc_rounds())?.saturating_div(2);
393
394 let gc_height = max_height.saturating_sub(max_gc_blocks);
398
399 let ledger_blocks = self.ledger.get_blocks(gc_height..(latest_ledger_block.height() + 1))?;
401
402 let blocks = ledger_blocks.iter().chain(pending_blocks.iter().map(|block| block.deref()));
403 debug!("Syncing storage with ledger and pending blocks from height {gc_height} to {max_height}...");
404
405 self.storage.sync_height_with_block(latest_block.height());
409 self.storage.sync_round_with_block(latest_block.round());
411 self.storage
413 .garbage_collect_certificates(latest_block.round())
414 .with_context(|| "Failed to garbage collect certificates")?;
415
416 for block in blocks {
418 if let Authority::Quorum(subdag) = block.authority() {
419 let unconfirmed_transactions = cfg_iter!(block.transactions())
425 .filter_map(|tx| {
426 tx.to_unconfirmed_transaction().map(|unconfirmed| (unconfirmed.id(), unconfirmed)).ok()
427 })
428 .collect::<HashMap<_, _>>();
429
430 for certificates in subdag.values().cloned() {
432 cfg_into_iter!(certificates).try_for_each(|certificate| {
433 let trusted_ledger_certificate = true;
436 self.storage
437 .sync_certificate_with_block(
438 block,
439 certificate,
440 &unconfirmed_transactions,
441 trusted_ledger_certificate,
442 )
443 .with_context(|| format!("Failed to sync certificate with block {}", block.height()))
444 })?;
445 }
446
447 #[cfg(feature = "metrics")]
449 self.gateway.validator_telemetry().insert_subdag(subdag);
450 }
451 }
452
453 if let Some(cb) = self.sync_callback.get() {
455 for block in ledger_blocks.into_iter() {
456 if let Authority::Quorum(subdag) = block.authority() {
457 for round in subdag.values() {
458 for cert in round {
459 cb.add_certificate_from_sync(cert.clone());
460 cb.commit_certificate_from_sync(cert);
461 }
462 }
463 }
464 }
465
466 for block in pending_blocks.iter() {
468 if let Authority::Quorum(subdag) = block.authority() {
469 for round in subdag.values() {
470 for cert in round {
471 cb.add_certificate_from_sync(cert.clone());
472 }
473 }
474 }
475 }
476 }
477
478 self.block_sync.set_sync_height(max_height);
479
480 Ok(())
481 }
482
483 fn compute_sync_height(&self) -> u32 {
486 let ledger_height = self.ledger.latest_block_height();
487 let mut pending_blocks = self.pending_blocks.lock();
488
489 while let Some(b) = pending_blocks.front()
491 && b.height() <= ledger_height
492 {
493 pending_blocks.pop_front();
494 }
495
496 pending_blocks.back().map(|b| b.height()).unwrap_or(0).max(ledger_height)
498 }
499
500 async fn try_advancing_block_synchronization(&self, ping: &Option<Arc<Ping<N>>>) {
502 let new_blocks = match self
504 .try_advancing_block_synchronization_inner()
505 .await
506 .with_context(|| "Block synchronization failed")
507 {
508 Ok(new_blocks) => new_blocks,
509 Err(err) => {
510 error!("{}", &flatten_error(err));
511 false
512 }
513 };
514
515 if let Some(ping) = &ping
516 && new_blocks
517 {
518 match self.get_block_locators() {
519 Ok(locators) => ping.update_block_locators(locators),
520 Err(err) => error!("Failed to update block locators: {err}"),
521 }
522 }
523 }
524
525 async fn try_advancing_block_synchronization_inner(&self) -> Result<bool> {
537 let _lock = self.response_lock.lock().await;
539
540 let ledger_height = self.ledger.latest_block_height();
543 self.block_sync.set_sync_height(ledger_height);
544
545 let tip = self
547 .block_sync
548 .find_sync_peers()
549 .map(|(sync_peers, _)| *sync_peers.values().max().unwrap_or(&0))
550 .unwrap_or(0);
551
552 let max_gc_blocks = u32::try_from(self.storage.max_gc_rounds())?.saturating_div(2);
557
558 let cleanup = |start_height, current_height, error| {
560 let new_blocks = current_height > start_height;
561
562 if new_blocks {
564 self.block_sync.set_sync_height(current_height);
565 }
566
567 if let Some(err) = error { Err(err) } else { Ok(new_blocks) }
568 };
569
570 let max_gc_height = tip.saturating_sub(max_gc_blocks);
574
575 let start_height = self.compute_sync_height();
578
579 let within_gc = start_height >= max_gc_height;
584
585 if within_gc {
586 let previous = self.block_sync.set_bft_sync_mode(BftSyncMode::Dag);
588 let was_in_fast_sync = previous == Some(BftSyncMode::Fast);
589
590 if was_in_fast_sync {
591 debug!("Finished catching up with the network. Switching to DAG sync.");
592 self.sync_storage_with_ledger_at_bootup()?;
593 }
594
595 let mut current_height = start_height;
597 trace!(
598 "Try advancing blocks responses with DAG updates (starting at block {next_height}, current sync speed is {speed})",
599 next_height = current_height + 1,
600 speed = self.block_sync.get_sync_speed(),
601 );
602
603 loop {
605 let next_height = current_height + 1;
606 let Some(block) = self.block_sync.peek_next_block(next_height) else {
607 break;
608 };
609 info!("Trying to sync next block at height {} with the BFT...", block.height());
610 match self.sync_storage_with_block(block, true).await {
612 Ok(_) => {
613 current_height = next_height;
615 }
616 Err(err) => {
617 self.block_sync.remove_block_response(next_height);
619 return cleanup(start_height, current_height, Some(err));
620 }
621 }
622 }
623
624 cleanup(start_height, current_height, None)
625 } else {
626 let previous = self.block_sync.set_bft_sync_mode(BftSyncMode::Fast);
627 let was_in_dag_sync = previous == Some(BftSyncMode::Dag);
628 if was_in_dag_sync {
629 warn!(
631 "Node is switching from DAG sync back to fast sync. The network tip may have advanced faster than this node is syncing."
632 );
633 }
634
635 let mut current_height = start_height;
638
639 trace!(
640 "Try advancing block responses without updating the DAG (starting at block {next_height})",
641 next_height = current_height + 1
642 );
643
644 loop {
647 let next_height = current_height + 1;
648
649 let Some(block) = self.block_sync.peek_next_block(next_height) else {
650 break;
651 };
652 info!("Syncing the ledger to block {}...", block.height());
653
654 match self.sync_storage_with_block(block, false).await {
656 Ok(_) => {
657 current_height = next_height;
659 self.block_sync.count_request_completed();
660 }
661 Err(err) => {
662 self.block_sync.remove_block_response(next_height);
664 return cleanup(start_height, current_height, Some(err));
665 }
666 }
667 }
668
669 let within_gc = current_height >= max_gc_height;
671 if within_gc {
672 info!("Finished catching up with the network. Switching back to DAG sync.");
673 self.block_sync.set_bft_sync_mode(BftSyncMode::Dag);
674 self.sync_storage_with_ledger_at_bootup().with_context(|| "BFT sync (with bootup routine) failed")?;
675 }
676
677 cleanup(start_height, current_height, None)
678 }
679 }
680
681 fn add_block_subdag_to_bft(&self, block: &Block<N>) -> Result<()> {
687 let Authority::Quorum(subdag) = block.authority() else {
689 return Ok(());
690 };
691
692 let unconfirmed_transactions = cfg_iter!(block.transactions())
694 .filter_map(|tx| tx.to_unconfirmed_transaction().map(|unconfirmed| (unconfirmed.id(), unconfirmed)).ok())
695 .collect::<HashMap<_, _>>();
696
697 for certificates in subdag.values() {
699 cfg_into_iter!(certificates.clone()).try_for_each(|certificate| -> Result<()> {
700 let trusted_ledger_certificate = false;
703 self.storage
704 .sync_certificate_with_block(
705 block,
706 certificate.clone(),
707 &unconfirmed_transactions,
708 trusted_ledger_certificate,
709 )
710 .with_context(|| format!("Failed to sync certificate with block {}", block.height()))
711 })?;
712 }
713
714 if let Some(cb) = self.sync_callback.get() {
716 for round in subdag.values() {
717 for certificate in round {
718 cb.add_certificate_from_sync(certificate.clone());
719 }
720 }
721 }
722
723 Ok(())
724 }
725
726 fn is_block_availability_threshold_reached(
731 &self,
732 block: &PendingBlock<N>,
733 successors: &[PendingBlock<N>],
734 ) -> Result<bool> {
735 let leader_certificate = match block.authority() {
737 Authority::Quorum(subdag) => subdag.leader_certificate().clone(),
738 _ => bail!("Received a block with an unexpected authority type."),
739 };
740 let commit_round = leader_certificate.round();
741 let certificate_round =
742 commit_round.checked_add(1).ok_or_else(|| anyhow!("Integer overflow on round number"))?;
743
744 let certificate_committee_lookback = self.ledger.get_committee_lookback_for_round(certificate_round)?;
746
747 let authors = successors
750 .iter()
751 .filter_map(|successor| {
752 let Authority::Quorum(subdag) = successor.authority() else {
753 return None;
754 };
755
756 subdag.get(&certificate_round)
757 })
758 .flatten()
759 .filter_map(|certificate| {
760 if certificate.previous_certificate_ids().contains(&leader_certificate.id()) {
761 Some(certificate.author())
762 } else {
763 None
764 }
765 })
766 .collect::<HashSet<_>>();
767
768 if certificate_committee_lookback.is_availability_threshold_reached(&authors) {
770 trace!(
771 "Block {hash} at height {height} has reached availability threshold",
772 hash = block.hash(),
773 height = block.height()
774 );
775 Ok(true)
776 } else {
777 Ok(false)
778 }
779 }
780
781 async fn sync_storage_with_block(&self, new_block: Block<N>, within_gc_range: bool) -> Result<()> {
796 let new_block_height = new_block.height();
797
798 if self.ledger.contains_block_height(new_block.height()) {
801 debug!("Ledger is already synced with block at height {new_block_height}. Will not sync.",);
802 return Ok(());
803 }
804
805 if within_gc_range {
807 self.add_block_subdag_to_bft(&new_block)?;
808 }
809
810 let _self = self.clone();
812
813 spawn_blocking!({
814 while !_self.try_sync_storage_with_block(&new_block, within_gc_range)? {
815 trace!("Retrying to sync storage with block at height {new_block_height}");
816 }
817
818 Ok(())
819 })
820 }
821
822 fn try_sync_storage_with_block(&self, new_block: &Block<N>, within_gc_range: bool) -> Result<bool> {
833 let mut pending_blocks = self.pending_blocks.lock();
835
836 if let Some(tail) = pending_blocks.back() {
837 if tail.height() >= new_block.height() {
838 debug!(
839 "A unconfirmed block is queued already for height {height}. \
840 Will not sync.",
841 height = new_block.height()
842 );
843 return Ok(true);
844 }
845
846 ensure_equals!(tail.height() + 1, new_block.height(), "Got an out-of-order block");
847 }
848
849 let ledger_block_height = self.ledger.latest_block_height();
851 let new_block_height = new_block.height();
852
853 while let Some(pending_block) = pending_blocks.front() {
856 if pending_block.height() > ledger_block_height {
857 break;
858 }
859
860 trace!(
861 "Pending block {hash} at height {height} became obsolete",
862 hash = pending_block.hash(),
863 height = pending_block.height()
864 );
865 pending_blocks.pop_front();
866 }
867
868 let new_block = match self.ledger.check_block_subdag(new_block.clone(), pending_blocks.make_contiguous()) {
870 Ok(new_block) => new_block,
871 Err(CheckBlockError::InvalidPrefix { index, .. }) => {
873 let height = pending_blocks.get(index).with_context(|| "Invalid prefix index")?.height();
874 debug!("Pending block at height {height} became obsolete. Will retry with updated prefix.",);
875
876 while let Some(pending_block) = pending_blocks.front()
877 && pending_block.height() <= height
878 {
879 trace!("Removing obsolete pending block at height {}.", pending_block.height());
880 pending_blocks.pop_front();
881 }
882
883 return Ok(false);
884 }
885 Err(CheckBlockError::BlockAlreadyExists { .. })
887 | Err(CheckBlockError::InvalidHeight { .. })
888 | Err(CheckBlockError::InvalidRound { .. }) => {
889 debug!(
890 "Tried to sync storage with block at height {new_block_height}, but it was already in the ledger."
891 );
892 return Ok(true);
893 }
894 Err(err) => return Err(err.into_anyhow()),
896 };
897
898 trace!(
899 "Adding new pending block {hash} at height {height}",
900 hash = new_block.hash(),
901 height = new_block.height()
902 );
903 pending_blocks.push_back(new_block);
904
905 let ledger_block_height = self.ledger.latest_block_height();
907
908 let Some(penultimate_index) = pending_blocks.len().checked_sub(1) else {
910 return Ok(true);
911 };
912
913 let commit_height = 'outer: {
920 let pending_blocks = pending_blocks.make_contiguous();
921 for index in (0..penultimate_index).rev() {
922 let block = &pending_blocks[index];
923 let successors = &pending_blocks[index + 1..];
924
925 if self
930 .is_block_availability_threshold_reached(block, successors)
931 .with_context(|| "Availability threshold check failed")?
932 {
933 break 'outer block.height();
934 }
935 }
936
937 trace!("No pending block are ready to be committed ({} block(s) are pending)", pending_blocks.len());
938 return Ok(true);
939 };
940
941 let ledger_update = match self.ledger.begin_ledger_update() {
942 Ok(update) => update,
943 Err(BeginLedgerUpdateError::ShuttingDown) => {
944 info!("BlockSync cannot advance the ledger any more. The node is shutting down.");
945 return Ok(true);
946 }
947 Err(err) => {
948 return Err(anyhow!("Unexpected error when beginning ledger update: {err}"));
949 }
950 };
951
952 let start_height = ledger_block_height + 1;
953 ensure!(commit_height >= start_height, "Invalid commit height");
954 let num_blocks = (commit_height - start_height + 1) as usize;
955
956 if num_blocks > 1 {
958 trace!(
959 "Attempting to commit {chain_length} pending block(s) starting at height {start_height}.",
960 chain_length = pending_blocks.len(),
961 );
962 }
963
964 for pending_block in pending_blocks.drain(0..num_blocks) {
965 let hash = pending_block.hash();
966 let height = pending_block.height();
967 let storage = self.storage.clone();
968
969 let block = match ledger_update.check_block_content(pending_block) {
970 Ok(block) => block,
971 Err(CheckBlockError::InvalidHeight { .. })
972 | Err(CheckBlockError::BlockAlreadyExists { .. })
973 | Err(CheckBlockError::InvalidRound { .. }) => {
974 debug!("Pending block at height {height} became obsolete. Will retry with updated prefix.");
977 return Ok(false);
978 }
979 Err(err) => {
980 return Err(err
981 .into_anyhow()
982 .context(format!("Failed to check contents of pending block {hash} at height {height}")));
983 }
984 };
985
986 trace!("Adding pending block {hash} at height {height} to the ledger");
987 ledger_update.advance_to_next_block(&block)?;
988 storage.sync_height_with_block(block.height());
990 storage.sync_round_with_block(block.round());
992
993 if within_gc_range
994 && let Some(cb) = self.sync_callback.get()
995 && let Authority::Quorum(subdag) = block.authority()
996 {
997 for round in subdag.values() {
998 for certificate in round {
999 cb.commit_certificate_from_sync(certificate);
1000 }
1001 }
1002 }
1003 }
1004
1005 Ok(true)
1006 }
1007}
1008
1009impl<N: Network> Sync<N> {
1011 pub fn is_synced(&self) -> bool {
1013 self.block_sync.is_block_synced()
1014 }
1015
1016 pub fn num_blocks_behind(&self) -> Option<u32> {
1018 self.block_sync.num_blocks_behind()
1019 }
1020
1021 pub fn get_block_locators(&self) -> Result<BlockLocators<N>> {
1023 self.block_sync.get_block_locators()
1024 }
1025}
1026
1027impl<N: Network> Sync<N> {
1029 pub async fn send_certificate_request(
1031 &self,
1032 peer_ip: SocketAddr,
1033 certificate_id: Field<N>,
1034 ) -> Result<BatchCertificate<N>> {
1035 let (callback_sender, callback_receiver) = oneshot::channel();
1037 let num_sent_requests = self.pending.num_sent_requests(certificate_id);
1039 let contains_peer_with_sent_request = self.pending.contains_peer_with_sent_request(certificate_id, peer_ip);
1041 let num_redundant_requests = max_redundant_requests(self.ledger.clone(), self.storage.current_round())?;
1043 let stake_redundancy_reached = || self.pending.request_stake_redundancy_reached(&self.gateway, certificate_id);
1045 let should_send_request = !contains_peer_with_sent_request
1049 && (num_sent_requests < num_redundant_requests || !stake_redundancy_reached()?);
1050
1051 self.pending.insert(certificate_id, peer_ip, Some((callback_sender, should_send_request)));
1053
1054 if should_send_request {
1056 if self.gateway.send(peer_ip, Event::CertificateRequest(certificate_id.into())).await.is_none() {
1058 bail!("Unable to fetch batch certificate {certificate_id} (failed to send request)")
1059 }
1060 } else {
1061 debug!(
1062 "Skipped sending request for certificate {} to '{peer_ip}' ({num_sent_requests} redundant requests)",
1063 fmt_id(certificate_id)
1064 );
1065 }
1066 tokio::time::timeout(MAX_FETCH_TIMEOUT, callback_receiver)
1069 .await
1070 .with_context(|| format!("Unable to fetch batch certificate {} (timeout)", fmt_id(certificate_id)))?
1071 .with_context(|| format!("Unable to fetch batch certificate {}", fmt_id(certificate_id)))
1072 }
1073
1074 fn send_certificate_response(&self, peer_ip: SocketAddr, request: CertificateRequest<N>) {
1076 if let Some(certificate) = self.storage.get_certificate(request.certificate_id) {
1078 let self_ = self.clone();
1080 tokio::spawn(async move {
1081 let _ = self_.gateway.send(peer_ip, Event::CertificateResponse(certificate.into())).await;
1082 });
1083 }
1084 }
1085
1086 fn finish_certificate_request(&self, peer_ip: SocketAddr, response: CertificateResponse<N>) {
1089 let certificate = response.certificate;
1090 let exists = self.pending.get_peers(certificate.id()).unwrap_or_default().contains(&peer_ip);
1092 if exists {
1094 if let Err(error) = self.storage.check_incoming_certificate(&certificate) {
1097 warn!("Skipping invalid certificate {} from '{peer_ip}' - {error}", fmt_id(certificate.id()));
1098 return;
1099 }
1100 self.pending.remove(certificate.id(), Some(certificate));
1102 }
1103 }
1104}
1105
1106impl<N: Network> Sync<N> {
1107 fn spawn<T: Future<Output = ()> + Send + 'static>(&self, future: T) {
1109 self.handles.lock().push(tokio::spawn(future));
1110 }
1111
1112 pub async fn shut_down(&self) {
1114 info!("Shutting down the sync module...");
1115 self.sync_callback.clear();
1117 let _lock = self.response_lock.lock().await;
1119 self.handles.lock().iter().for_each(|handle| handle.abort());
1121 }
1122}
1123
1124#[cfg(test)]
1125mod tests {
1126 use super::*;
1127
1128 use crate::{
1129 BFT,
1130 helpers::now,
1131 ledger_service::{CoreLedgerService, MockLedgerService},
1132 storage_service::BFTMemoryService,
1133 };
1134
1135 use snarkos_account::Account;
1136 use snarkos_node_network::ConnectionMode;
1137 use snarkos_node_sync::BlockSync;
1138 use snarkos_utilities::{NodeDataDir, SimpleStoppable};
1139
1140 use snarkvm::{
1141 console::{
1142 account::{Address, PrivateKey},
1143 network::MainnetV0,
1144 },
1145 ledger::{
1146 narwhal::{BatchCertificate, BatchHeader, Subdag},
1147 store::{ConsensusStore, helpers::memory::ConsensusMemory},
1148 },
1149 prelude::{Ledger, VM},
1150 utilities::TestRng,
1151 };
1152
1153 use aleo_std::StorageMode;
1154 use indexmap::IndexSet;
1155 use rand::RngExt;
1156 use std::{collections::BTreeMap, sync::OnceLock};
1157
1158 type CurrentNetwork = MainnetV0;
1159 type CurrentLedger = Ledger<CurrentNetwork, ConsensusMemory<CurrentNetwork>>;
1160 type CurrentConsensusStore = ConsensusStore<CurrentNetwork, ConsensusMemory<CurrentNetwork>>;
1161
1162 async fn setup_commit_chain(rng: &mut TestRng) -> (Block<CurrentNetwork>, Vec<Block<CurrentNetwork>>) {
1164 static CHAIN_CACHE: OnceLock<(Block<CurrentNetwork>, Vec<Block<CurrentNetwork>>)> = OnceLock::new();
1165
1166 if let Some((genesis, blocks)) = CHAIN_CACHE.get() {
1168 return (genesis.clone(), blocks.clone());
1169 }
1170
1171 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1173
1174 let first_round: u64 = 1;
1176 let num_blocks = 3;
1178 let last_round = first_round + num_blocks * 2;
1180 let first_threshold_round = 5;
1183
1184 let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
1186 let account: Account<CurrentNetwork> = Account::new(rng).unwrap();
1187
1188 let seed: u64 = rng.random();
1190 let vm = VM::from(store).unwrap();
1191 let genesis_pk = *account.private_key();
1192 let genesis = spawn_blocking!(vm.genesis_beacon(&genesis_pk, &mut TestRng::from_seed(seed))).unwrap();
1193
1194 let genesis_rng = &mut TestRng::from_seed(seed);
1196 let private_keys = [
1197 *account.private_key(),
1198 PrivateKey::new(genesis_rng).unwrap(),
1199 PrivateKey::new(genesis_rng).unwrap(),
1200 PrivateKey::new(genesis_rng).unwrap(),
1201 ];
1202
1203 let genesis_clone = genesis.clone();
1205 let ledger = spawn_blocking!(CurrentLedger::load(genesis_clone, StorageMode::new_test(None))).unwrap();
1206 let core_ledger = Arc::new(CoreLedgerService::new(ledger.clone(), SimpleStoppable::new()));
1208
1209 let (round_to_certificates_map, committee) = {
1211 let addresses = vec![
1212 Address::try_from(private_keys[0]).unwrap(),
1213 Address::try_from(private_keys[1]).unwrap(),
1214 Address::try_from(private_keys[2]).unwrap(),
1215 Address::try_from(private_keys[3]).unwrap(),
1216 ];
1217
1218 let committee = ledger.latest_committee().unwrap();
1219
1220 let mut round_to_certificates_map: HashMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> =
1222 HashMap::new();
1223 let mut previous_certificates: IndexSet<BatchCertificate<CurrentNetwork>> = IndexSet::with_capacity(4);
1224
1225 for round in first_round..=last_round {
1226 let mut current_certificates = IndexSet::new();
1227 let previous_certificate_ids: IndexSet<_> = if round == 0 || round == 1 {
1228 IndexSet::new()
1229 } else {
1230 previous_certificates.iter().map(|c| c.id()).collect()
1231 };
1232
1233 let committee_id = committee.id();
1234
1235 let is_certificate_round = !round.is_multiple_of(2);
1237 let prev_leader = if is_certificate_round && let Some(prev_round) = round.checked_sub(1) {
1238 Some(committee.get_leader(prev_round).unwrap())
1239 } else {
1240 None
1241 };
1242
1243 for (i, private_key) in private_keys.iter().enumerate() {
1245 let previous_leader_index =
1246 addresses.iter().position(|&addr| prev_leader.is_some_and(|prev_leader| addr == prev_leader));
1247
1248 let previous_certs = if let Some(previous_leader_index) = previous_leader_index
1251 && round < first_threshold_round
1252 && i != previous_leader_index
1253 {
1254 previous_certificate_ids
1256 .iter()
1257 .cloned()
1258 .enumerate()
1259 .filter(|(idx, _)| *idx != previous_leader_index)
1260 .map(|(_, id)| id)
1261 .collect()
1262 } else {
1263 previous_certificate_ids.clone()
1264 };
1265
1266 let batch_header = BatchHeader::new(
1267 private_key,
1268 round,
1269 now(),
1270 committee_id,
1271 Default::default(),
1272 previous_certs,
1273 rng,
1274 )
1275 .unwrap();
1276
1277 let mut signatures = IndexSet::with_capacity(4);
1279 for (j, private_key_2) in private_keys.iter().enumerate() {
1280 if i != j {
1281 signatures.insert(private_key_2.sign(&[batch_header.batch_id()], rng).unwrap());
1282 }
1283 }
1284 current_certificates.insert(BatchCertificate::from(batch_header, signatures).unwrap());
1285 }
1286
1287 round_to_certificates_map.insert(round, current_certificates.clone());
1289 previous_certificates = current_certificates;
1290 }
1291 (round_to_certificates_map, committee)
1292 };
1293
1294 let storage = Storage::new(core_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1296
1297 let certificates: Vec<_> =
1299 round_to_certificates_map.into_iter().flat_map(|(_, certificates)| certificates.into_iter()).collect();
1300
1301 for certificate in certificates.iter() {
1303 storage.testing_only_insert_certificate_testing_only(certificate.clone());
1304 }
1305
1306 let mut previous_leader_cert = None;
1308 let mut blocks = vec![];
1309
1310 for block_height in 1..=num_blocks {
1311 let leader_round = block_height * 2;
1312
1313 let leader = committee.get_leader(leader_round).unwrap();
1314 let leader_certificate = storage.get_certificate_for_round_with_author(leader_round, leader).unwrap();
1315
1316 let mut subdag_map: BTreeMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> = BTreeMap::new();
1317 let mut leader_cert_map = IndexSet::new();
1318 leader_cert_map.insert(leader_certificate.clone());
1319
1320 let previous_cert_map = storage.get_certificates_for_round(leader_round - 1);
1321
1322 subdag_map.insert(leader_round, leader_cert_map.clone());
1323 subdag_map.insert(leader_round - 1, previous_cert_map.clone());
1324
1325 if leader_round > 2 {
1326 let previous_commit_cert_map: IndexSet<_> = storage
1327 .get_certificates_for_round(leader_round - 2)
1328 .into_iter()
1329 .filter(|cert| {
1330 if let Some(previous_leader_cert) = &previous_leader_cert {
1331 cert != previous_leader_cert
1332 } else {
1333 true
1334 }
1335 })
1336 .collect();
1337 subdag_map.insert(leader_round - 2, previous_commit_cert_map);
1338 }
1339
1340 let subdag = Subdag::from(subdag_map.clone()).unwrap();
1341 previous_leader_cert = Some(leader_certificate);
1342
1343 let core_ledger = core_ledger.clone();
1344 let block = spawn_blocking!({
1345 let ledger_update = core_ledger.begin_ledger_update()?;
1346 let block = ledger_update.prepare_advance_to_next_quorum_block(subdag, Default::default())?;
1347 ledger_update.advance_to_next_block(&block)?;
1348 Ok(block)
1349 })
1350 .unwrap();
1351
1352 blocks.push(block);
1353 }
1354
1355 CHAIN_CACHE.get_or_init(|| (genesis, blocks)).clone()
1356 }
1357
1358 #[tokio::test]
1359 #[tracing_test::traced_test]
1360 async fn test_commit_chain_with_bft() {
1361 let rng = &mut TestRng::default();
1362
1363 let (genesis, mut blocks) = setup_commit_chain(rng).await;
1364 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1365
1366 let storage_mode = StorageMode::new_test(None);
1368
1369 let syncing_ledger = {
1372 let storage_mode = storage_mode.clone();
1373 Arc::new(CoreLedgerService::new(
1374 spawn_blocking!(CurrentLedger::load(genesis, storage_mode)).unwrap(),
1375 SimpleStoppable::new(),
1376 ))
1377 };
1378
1379 let account = Account::new(rng).unwrap();
1380 let syncing_storage =
1381 Storage::new(syncing_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1382 let gateway = Gateway::new(
1383 account.clone(),
1384 syncing_storage.clone(),
1385 syncing_ledger.clone(),
1386 None,
1387 &[],
1388 false,
1389 NodeDataDir::new_test(None),
1390 None,
1391 )
1392 .unwrap();
1393
1394 let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone(), ConnectionMode::Gateway));
1395 let sync = Sync::new(gateway.clone(), syncing_storage.clone(), syncing_ledger.clone(), block_sync.clone());
1396
1397 let syncing_bft = BFT::new(
1398 account.clone(),
1399 syncing_storage.clone(),
1400 syncing_ledger.clone(),
1401 block_sync,
1402 None,
1403 &[],
1404 false,
1405 NodeDataDir::new_test(None),
1406 None,
1407 )
1408 .unwrap();
1409
1410 sync.initialize(Some(Arc::new(syncing_bft.clone()))).unwrap();
1411
1412 let last_block = blocks.pop().unwrap();
1415
1416 for block in blocks {
1418 sync.sync_storage_with_block(block, true).await.unwrap();
1419 assert_eq!(syncing_bft.testing_only_latest_committed_round(), 0);
1421 }
1422
1423 sync.sync_storage_with_block(last_block, true).await.unwrap();
1426
1427 assert_eq!(syncing_bft.testing_only_latest_committed_round(), 4);
1430 }
1431
1432 #[tokio::test]
1435 #[tracing_test::traced_test]
1436 async fn test_sync_updates_storage_with_block_certificates() {
1437 let rng = &mut TestRng::default();
1438
1439 let (genesis, blocks) = setup_commit_chain(rng).await;
1440 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1441 let storage_mode = StorageMode::new_test(None);
1442
1443 let syncing_ledger = Arc::new(CoreLedgerService::new(
1444 spawn_blocking!(CurrentLedger::load(genesis, storage_mode)).unwrap(),
1445 SimpleStoppable::new(),
1446 ));
1447
1448 let account = Account::new(rng).unwrap();
1449 let syncing_storage =
1450 Storage::new(syncing_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1451 let gateway = Gateway::new(
1452 account.clone(),
1453 syncing_storage.clone(),
1454 syncing_ledger.clone(),
1455 None,
1456 &[],
1457 false,
1458 NodeDataDir::new_test(None),
1459 None,
1460 )
1461 .unwrap();
1462
1463 let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone(), ConnectionMode::Gateway));
1464 let sync = Sync::new(gateway.clone(), syncing_storage.clone(), syncing_ledger.clone(), block_sync.clone());
1465
1466 let syncing_bft = BFT::new(
1467 account.clone(),
1468 syncing_storage.clone(),
1469 syncing_ledger.clone(),
1470 block_sync,
1471 None,
1472 &[],
1473 false,
1474 NodeDataDir::new_test(None),
1475 None,
1476 )
1477 .unwrap();
1478
1479 sync.initialize(Some(Arc::new(syncing_bft.clone()))).unwrap();
1480
1481 for block in &blocks {
1483 sync.sync_storage_with_block(block.clone(), true).await.unwrap();
1484 }
1485
1486 let committed_blocks = &blocks[..blocks.len().saturating_sub(1)];
1489
1490 for block in committed_blocks {
1493 let Authority::Quorum(subdag) = block.authority() else {
1494 continue;
1495 };
1496 for certificates in subdag.values() {
1497 for cert in certificates {
1498 assert!(
1499 syncing_ledger.contains_certificate(&cert.id()).unwrap_or(false),
1500 "Sync should have committed block {} so certificate is in the ledger",
1501 block.height()
1502 );
1503 }
1504 }
1505 }
1506
1507 let last_committed_block = committed_blocks.last().unwrap();
1509 assert_eq!(
1510 syncing_ledger.latest_block_height(),
1511 last_committed_block.height(),
1512 "Ledger height should match last committed block"
1513 );
1514 assert_eq!(
1515 syncing_ledger.latest_block().round(),
1516 last_committed_block.round(),
1517 "Ledger round should match last committed block"
1518 );
1519 }
1520
1521 #[tokio::test]
1522 #[tracing_test::traced_test]
1523 async fn test_commit_chain_with_swich_to_bft() {
1524 let rng = &mut TestRng::default();
1525 let (genesis, mut blocks) = setup_commit_chain(rng).await;
1526 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1527 let storage_mode = StorageMode::new_test(None);
1528
1529 let syncing_ledger = {
1532 let storage_mode = storage_mode.clone();
1533 Arc::new(CoreLedgerService::new(
1534 spawn_blocking!(CurrentLedger::load(genesis, storage_mode)).unwrap(),
1535 SimpleStoppable::new(),
1536 ))
1537 };
1538
1539 let account = Account::new(rng).unwrap();
1540 let syncing_storage =
1541 Storage::new(syncing_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1542 let gateway = Gateway::new(
1543 account.clone(),
1544 syncing_storage.clone(),
1545 syncing_ledger.clone(),
1546 None,
1547 &[],
1548 false,
1549 NodeDataDir::new_test(None),
1550 None,
1551 )
1552 .unwrap();
1553
1554 let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone(), ConnectionMode::Gateway));
1555 let sync = Sync::new(gateway.clone(), syncing_storage.clone(), syncing_ledger.clone(), block_sync.clone());
1556
1557 let syncing_bft = BFT::new(
1558 account.clone(),
1559 syncing_storage.clone(),
1560 syncing_ledger.clone(),
1561 block_sync,
1562 None,
1563 &[],
1564 false,
1565 NodeDataDir::new_test(None),
1566 None,
1567 )
1568 .unwrap();
1569
1570 sync.initialize(Some(Arc::new(syncing_bft.clone()))).unwrap();
1571
1572 let last_block = blocks.pop().unwrap();
1574
1575 for block in blocks {
1578 sync.sync_storage_with_block(block, false).await.unwrap();
1579
1580 assert_eq!(syncing_ledger.latest_block_height(), 0);
1582 }
1583
1584 sync.sync_storage_with_ledger_at_bootup().unwrap();
1586
1587 assert_eq!(syncing_ledger.latest_block_height(), 0);
1589 assert_eq!(syncing_bft.testing_only_latest_committed_round(), 0);
1590
1591 sync.sync_storage_with_block(last_block, true).await.unwrap();
1594
1595 assert_eq!(syncing_bft.testing_only_latest_committed_round(), 4);
1598 }
1599
1600 #[tokio::test]
1609 #[tracing_test::traced_test]
1610 async fn test_commit_chain_with_switch_to_fast_sync() {
1611 let rng = &mut TestRng::default();
1612 let (genesis, mut blocks) = setup_commit_chain(rng).await;
1613 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1614 let storage_mode = StorageMode::new_test(None);
1615
1616 let syncing_ledger = {
1617 let storage_mode = storage_mode.clone();
1618 Arc::new(CoreLedgerService::new(
1619 spawn_blocking!(CurrentLedger::load(genesis, storage_mode)).unwrap(),
1620 SimpleStoppable::new(),
1621 ))
1622 };
1623
1624 let account = Account::new(rng).unwrap();
1625 let syncing_storage =
1626 Storage::new(syncing_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1627 let gateway = Gateway::new(
1628 account.clone(),
1629 syncing_storage.clone(),
1630 syncing_ledger.clone(),
1631 None,
1632 &[],
1633 false,
1634 NodeDataDir::new_test(None),
1635 None,
1636 )
1637 .unwrap();
1638
1639 let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone(), ConnectionMode::Gateway));
1640 let sync = Sync::new(gateway.clone(), syncing_storage.clone(), syncing_ledger.clone(), block_sync.clone());
1641
1642 let syncing_bft = BFT::new(
1643 account.clone(),
1644 syncing_storage.clone(),
1645 syncing_ledger.clone(),
1646 block_sync,
1647 None,
1648 &[],
1649 false,
1650 NodeDataDir::new_test(None),
1651 None,
1652 )
1653 .unwrap();
1654
1655 sync.initialize(Some(Arc::new(syncing_bft.clone()))).unwrap();
1656
1657 let last_block = blocks.pop().unwrap();
1659
1660 for block in blocks {
1664 sync.sync_storage_with_block(block, true).await.unwrap();
1665 assert_eq!(syncing_ledger.latest_block_height(), 0);
1666 }
1667
1668 sync.sync_storage_with_block(last_block, false).await.unwrap();
1676
1677 assert_eq!(syncing_ledger.latest_block_height(), 2);
1679 assert!(syncing_ledger.contains_block_height(1));
1680 assert!(syncing_ledger.contains_block_height(2));
1681
1682 assert_eq!(syncing_bft.testing_only_latest_committed_round(), 0);
1686 }
1687
1688 #[tokio::test]
1689 #[tracing_test::traced_test]
1690 async fn test_commit_chain_without_bft() {
1691 let rng = &mut TestRng::default();
1692 let (genesis, mut blocks) = setup_commit_chain(rng).await;
1693 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1694 let storage_mode = StorageMode::new_test(None);
1695
1696 let syncing_ledger = {
1699 let storage_mode = storage_mode.clone();
1700 Arc::new(CoreLedgerService::new(
1701 spawn_blocking!(CurrentLedger::load(genesis, storage_mode)).unwrap(),
1702 SimpleStoppable::new(),
1703 ))
1704 };
1705
1706 let account = Account::new(rng).unwrap();
1707 let syncing_storage =
1708 Storage::new(syncing_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1709 let gateway = Gateway::new(
1710 account.clone(),
1711 syncing_storage.clone(),
1712 syncing_ledger.clone(),
1713 None,
1714 &[],
1715 false,
1716 NodeDataDir::new_test(None),
1717 None,
1718 )
1719 .unwrap();
1720
1721 let block_sync = Arc::new(BlockSync::new(syncing_ledger.clone(), ConnectionMode::Gateway));
1722 let sync = Sync::new(gateway.clone(), syncing_storage.clone(), syncing_ledger.clone(), block_sync.clone());
1723
1724 let syncing_bft = BFT::new(
1725 account.clone(),
1726 syncing_storage.clone(),
1727 syncing_ledger.clone(),
1728 block_sync,
1729 None,
1730 &[],
1731 false,
1732 NodeDataDir::new_test(None),
1733 None,
1734 )
1735 .unwrap();
1736
1737 sync.initialize(Some(Arc::new(syncing_bft.clone()))).unwrap();
1738
1739 let last_block = blocks.pop().unwrap();
1741
1742 for block in blocks {
1744 sync.sync_storage_with_block(block, false).await.unwrap();
1745
1746 assert_eq!(syncing_ledger.latest_block_height(), 0);
1748 }
1749
1750 sync.sync_storage_with_block(last_block, false).await.unwrap();
1753 assert_eq!(syncing_ledger.latest_block_height(), 2);
1754
1755 assert!(syncing_ledger.contains_block_height(1));
1757 assert!(syncing_ledger.contains_block_height(2));
1758 }
1759
1760 #[tokio::test]
1761 #[tracing_test::traced_test]
1762 async fn test_pending_certificates() -> anyhow::Result<()> {
1763 let rng = &mut TestRng::default();
1764 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1766 let commit_round = 2;
1767
1768 let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
1770 let account: Account<CurrentNetwork> = Account::new(rng)?;
1771
1772 let seed: u64 = rng.random();
1774 let vm = VM::from(store).unwrap();
1775 let genesis_pk = *account.private_key();
1776 let genesis = spawn_blocking!(vm.genesis_beacon(&genesis_pk, &mut TestRng::from_seed(seed))).unwrap();
1777
1778 let genesis_rng = &mut TestRng::from_seed(seed);
1780 let private_keys = [
1781 *account.private_key(),
1782 PrivateKey::new(genesis_rng)?,
1783 PrivateKey::new(genesis_rng)?,
1784 PrivateKey::new(genesis_rng)?,
1785 ];
1786
1787 let core_ledger = {
1789 let ledger = spawn_blocking!(CurrentLedger::load(genesis, StorageMode::new_test(None))).unwrap();
1790 Arc::new(CoreLedgerService::new(ledger.clone(), SimpleStoppable::new()))
1791 };
1792
1793 let (round_to_certificates_map, committee) = {
1795 let committee = core_ledger.current_committee().unwrap();
1797 let mut round_to_certificates_map: HashMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> =
1799 HashMap::new();
1800 let mut previous_certificates: IndexSet<BatchCertificate<CurrentNetwork>> = IndexSet::with_capacity(4);
1801
1802 for round in 0..=commit_round + 8 {
1803 let mut current_certificates = IndexSet::new();
1804 let previous_certificate_ids: IndexSet<_> = if round == 0 || round == 1 {
1805 IndexSet::new()
1806 } else {
1807 previous_certificates.iter().map(|c| c.id()).collect()
1808 };
1809 let committee_id = committee.id();
1810 for (i, private_key_1) in private_keys.iter().enumerate() {
1812 let batch_header = BatchHeader::new(
1813 private_key_1,
1814 round,
1815 now(),
1816 committee_id,
1817 Default::default(),
1818 previous_certificate_ids.clone(),
1819 rng,
1820 )
1821 .unwrap();
1822 let mut signatures = IndexSet::with_capacity(4);
1824 for (j, private_key_2) in private_keys.iter().enumerate() {
1825 if i != j {
1826 signatures.insert(private_key_2.sign(&[batch_header.batch_id()], rng).unwrap());
1827 }
1828 }
1829 current_certificates.insert(BatchCertificate::from(batch_header, signatures).unwrap());
1830 }
1831
1832 round_to_certificates_map.insert(round, current_certificates.clone());
1834 previous_certificates = current_certificates.clone();
1835 }
1836 (round_to_certificates_map, committee)
1837 };
1838
1839 let storage = Storage::new(core_ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds).unwrap();
1841 let mut certificates: Vec<BatchCertificate<CurrentNetwork>> = Vec::new();
1843 for i in 1..=commit_round + 8 {
1844 let c = (*round_to_certificates_map.get(&i).unwrap()).clone();
1845 certificates.extend(c);
1846 }
1847 for certificate in certificates.clone().iter() {
1848 storage.testing_only_insert_certificate_testing_only(certificate.clone());
1849 }
1850
1851 let leader_round_1 = commit_round;
1852 let leader_1 = committee.get_leader(leader_round_1).unwrap();
1853 let leader_certificate = storage.get_certificate_for_round_with_author(commit_round, leader_1).unwrap();
1854 let mut subdag_map: BTreeMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> = BTreeMap::new();
1855
1856 let subdag_1 = {
1858 let mut leader_cert_map = IndexSet::new();
1859 leader_cert_map.insert(leader_certificate.clone());
1860 let mut previous_cert_map = IndexSet::new();
1861 for cert in storage.get_certificates_for_round(commit_round - 1) {
1862 previous_cert_map.insert(cert);
1863 }
1864 subdag_map.insert(commit_round, leader_cert_map.clone());
1865 subdag_map.insert(commit_round - 1, previous_cert_map.clone());
1866 Subdag::from(subdag_map.clone())?
1867 };
1868
1869 let core_ledger_cpy = core_ledger.clone();
1870 spawn_blocking!({
1871 let update1 = core_ledger_cpy.begin_ledger_update()?;
1873 let block_1 = update1.prepare_advance_to_next_quorum_block(subdag_1, Default::default())?;
1874
1875 update1.advance_to_next_block(&block_1)?;
1877
1878 Ok(())
1879 })?;
1880
1881 let mut subdag_map_2: BTreeMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> = BTreeMap::new();
1883 let subdag_2 = {
1884 let leader_round_2 = commit_round + 2;
1885 let leader_2 = committee.get_leader(leader_round_2).unwrap();
1886 let leader_certificate_2 = storage.get_certificate_for_round_with_author(leader_round_2, leader_2).unwrap();
1887 let mut leader_cert_map_2 = IndexSet::new();
1888 leader_cert_map_2.insert(leader_certificate_2.clone());
1889 let mut previous_cert_map_2 = IndexSet::new();
1890 for cert in storage.get_certificates_for_round(leader_round_2 - 1) {
1891 previous_cert_map_2.insert(cert);
1892 }
1893 subdag_map_2.insert(leader_round_2, leader_cert_map_2.clone());
1894 subdag_map_2.insert(leader_round_2 - 1, previous_cert_map_2.clone());
1895 Subdag::from(subdag_map_2.clone())?
1896 };
1897
1898 let core_ledger_cpy = core_ledger.clone();
1899 spawn_blocking!({
1900 let update2 = core_ledger_cpy.begin_ledger_update()?;
1901
1902 let block_2 = update2.prepare_advance_to_next_quorum_block(subdag_2, Default::default())?;
1904
1905 update2.advance_to_next_block(&block_2)?;
1907
1908 Ok(())
1909 })?;
1910
1911 let leader_round_3 = commit_round + 4;
1913 let leader_3 = committee.get_leader(leader_round_3).unwrap();
1914 let leader_certificate_3 = storage.get_certificate_for_round_with_author(leader_round_3, leader_3).unwrap();
1915
1916 let mut subdag_map_3: BTreeMap<u64, IndexSet<BatchCertificate<CurrentNetwork>>> = BTreeMap::new();
1918 let subdag_3 = {
1919 let mut leader_cert_map_3 = IndexSet::new();
1920 leader_cert_map_3.insert(leader_certificate_3.clone());
1921 let mut previous_cert_map_3 = IndexSet::new();
1922 for cert in storage.get_certificates_for_round(leader_round_3 - 1) {
1923 previous_cert_map_3.insert(cert);
1924 }
1925 subdag_map_3.insert(leader_round_3, leader_cert_map_3.clone());
1926 subdag_map_3.insert(leader_round_3 - 1, previous_cert_map_3.clone());
1927 Subdag::from(subdag_map_3.clone())?
1928 };
1929
1930 let core_ledger_cpy = core_ledger.clone();
1931 spawn_blocking!({
1932 let update3 = core_ledger_cpy.begin_ledger_update()?;
1933
1934 let block_3 = update3.prepare_advance_to_next_quorum_block(subdag_3, Default::default())?;
1936
1937 update3.advance_to_next_block(&block_3)?;
1939
1940 Ok(())
1941 })?;
1942
1943 let pending_certificates = storage.get_pending_certificates();
1949 for certificate in pending_certificates.clone() {
1951 assert!(!core_ledger.contains_certificate(&certificate.id()).unwrap_or(false));
1952 }
1953 let mut committed_certificates: IndexSet<BatchCertificate<CurrentNetwork>> = IndexSet::new();
1955 {
1956 let subdag_maps = [&subdag_map, &subdag_map_2, &subdag_map_3];
1957 for subdag in subdag_maps.iter() {
1958 for subdag_certificates in subdag.values() {
1959 committed_certificates.extend(subdag_certificates.iter().cloned());
1960 }
1961 }
1962 };
1963 let mut candidate_pending_certificates: IndexSet<BatchCertificate<CurrentNetwork>> = IndexSet::new();
1965 for certificate in certificates.clone() {
1966 if !committed_certificates.contains(&certificate) {
1967 candidate_pending_certificates.insert(certificate);
1968 }
1969 }
1970 assert_eq!(pending_certificates, candidate_pending_certificates);
1972
1973 Ok(())
1974 }
1975
1976 #[tokio::test]
1985 async fn test_finish_certificate_request_rejects_invalid_certificate() -> anyhow::Result<()> {
1986 let rng = &mut TestRng::default();
1987 let max_gc_rounds = BatchHeader::<CurrentNetwork>::MAX_GC_ROUNDS as u64;
1988
1989 let (committee, private_keys) =
1991 snarkvm::ledger::committee::test_helpers::sample_committee_and_keys_for_round(0, 5, rng);
1992 let committee_id = committee.id();
1993
1994 let ledger = Arc::new(MockLedgerService::new(committee));
1996 let storage = Storage::new(ledger.clone(), Arc::new(BFTMemoryService::new()), max_gc_rounds)?;
1997 let account = Account::new(rng)?;
1998 let gateway = Gateway::new(
1999 account,
2000 storage.clone(),
2001 ledger.clone(),
2002 None,
2003 &[],
2004 false,
2005 NodeDataDir::new_test(None),
2006 None,
2007 )?;
2008 let block_sync = Arc::new(BlockSync::new(ledger.clone(), ConnectionMode::Gateway));
2009 let sync = Sync::new(gateway, storage, ledger.clone(), block_sync);
2010
2011 let batch_header =
2013 BatchHeader::new(&private_keys[0], 1, now(), committee_id, Default::default(), IndexSet::new(), rng)?;
2014 let certificate_id = batch_header.batch_id();
2015
2016 let mut valid_signatures = IndexSet::new();
2018 for private_key in private_keys.iter().skip(1) {
2019 valid_signatures.insert(private_key.sign(&[certificate_id], rng)?);
2020 }
2021 let valid_certificate = BatchCertificate::from(batch_header.clone(), valid_signatures)?;
2022
2023 let mut invalid_signatures = IndexSet::new();
2026 for _ in 0..4 {
2027 invalid_signatures.insert(PrivateKey::<CurrentNetwork>::new(rng)?.sign(&[certificate_id], rng)?);
2028 }
2029 let invalid_certificate = BatchCertificate::from(batch_header, invalid_signatures)?;
2030 assert_eq!(invalid_certificate.id(), valid_certificate.id(), "the attack requires a matching ID");
2031
2032 let malicious_peer: SocketAddr = "127.0.0.1:1234".parse().unwrap();
2034 let honest_peer: SocketAddr = "127.0.0.1:5678".parse().unwrap();
2035 let (malicious_sender, _malicious_receiver) = oneshot::channel();
2036 let (honest_sender, honest_receiver) = oneshot::channel();
2037 sync.pending.insert(certificate_id, malicious_peer, Some((malicious_sender, true)));
2038 sync.pending.insert(certificate_id, honest_peer, Some((honest_sender, true)));
2039 assert_eq!(sync.pending.num_callbacks(certificate_id), 2);
2040
2041 sync.finish_certificate_request(malicious_peer, invalid_certificate.into());
2043 assert!(sync.pending.contains(certificate_id), "the forged response cleared the pending entry");
2044 assert_eq!(sync.pending.num_callbacks(certificate_id), 2, "the forged response consumed a callback");
2045
2046 sync.finish_certificate_request(honest_peer, valid_certificate.clone().into());
2048 assert!(!sync.pending.contains(certificate_id));
2049 assert_eq!(honest_receiver.await?, valid_certificate);
2050
2051 Ok(())
2052 }
2053}