1use std::sync::{
4 atomic::{AtomicU64, Ordering},
5 Arc,
6};
7
8use futures::future::BoxFuture;
9use serde_json::{Number, Value};
10use tokio::sync::watch;
11use zakura_chain::block;
12
13use super::{
14 commit_state_trace as cs_trace, BlockApplyResult, BlockApplyToken, BlockSyncBlockMeta,
15 HeaderSyncCommitFailureKind, ZakuraPeerId, ZakuraTrace, COMMIT_STATE_TABLE,
16};
17
18#[derive(Copy, Clone, Debug, Eq, PartialEq)]
20pub struct Frontier {
21 pub height: block::Height,
23 pub hash: block::Hash,
25}
26
27impl Frontier {
28 pub fn new(height: block::Height, hash: block::Hash) -> Self {
30 Self { height, hash }
31 }
32}
33
34#[derive(Copy, Clone, Debug, Eq, PartialEq)]
36pub struct ChainFrontier {
37 pub finalized: Frontier,
39 pub verified_body: Frontier,
41 pub best_header: Frontier,
43}
44
45#[derive(Copy, Clone, Debug, Eq, PartialEq)]
47pub enum FrontierChange {
48 Snapshot,
50 VerifiedGrow,
52 VerifiedReset,
54 HeaderAdvanced,
56 HeaderReanchored,
58}
59
60#[derive(Copy, Clone, Debug, Eq, PartialEq)]
62pub struct FrontierUpdate {
63 pub frontier: ChainFrontier,
65 pub change: FrontierChange,
67}
68
69#[derive(Clone, Debug)]
71pub struct ZakuraSyncExchange {
72 inner: Arc<ZakuraSyncExchangeInner>,
73}
74
75#[derive(Debug)]
76struct ZakuraSyncExchangeInner {
77 frontier: watch::Sender<FrontierUpdate>,
78 trace: ZakuraTrace,
79 sequence: AtomicU64,
80}
81
82impl ZakuraSyncExchange {
83 pub fn new(initial: FrontierUpdate, trace: ZakuraTrace) -> Self {
85 let (frontier, _receiver) = watch::channel(initial);
86
87 Self {
88 inner: Arc::new(ZakuraSyncExchangeInner {
89 frontier,
90 trace,
91 sequence: AtomicU64::new(0),
92 }),
93 }
94 }
95
96 pub fn current_frontier(&self) -> FrontierUpdate {
98 *self.inner.frontier.borrow()
99 }
100
101 pub fn subscribe_frontier(&self) -> watch::Receiver<FrontierUpdate> {
103 self.inner.frontier.subscribe()
104 }
105
106 pub fn publish_frontier(&self, requested: FrontierUpdate, source: &'static str) {
108 let mut transition = None;
109
110 self.inner.frontier.send_if_modified(|current| {
111 let old = *current;
112 let sequence = self
113 .inner
114 .sequence
115 .fetch_add(1, Ordering::Relaxed)
116 .saturating_add(1);
117
118 match apply_frontier_update(old, requested) {
119 Some(update) => {
120 *current = update;
121 transition = Some((sequence, old, update, "accepted"));
122 true
123 }
124 None => {
125 let ignored = FrontierUpdate {
126 frontier: old.frontier,
127 change: requested.change,
128 };
129 transition = Some((sequence, old, ignored, "ignored"));
130 false
131 }
132 }
133 });
134
135 if let Some((sequence, old, new, result)) = transition {
136 self.trace_transition(sequence, source, old, new, result);
137 }
138 }
139
140 fn trace_transition(
141 &self,
142 sequence: u64,
143 source: &'static str,
144 old: FrontierUpdate,
145 new: FrontierUpdate,
146 result: &'static str,
147 ) {
148 self.inner.trace.emit_with(COMMIT_STATE_TABLE, |row| {
149 row.insert(
150 cs_trace::EVENT.to_string(),
151 Value::String(cs_trace::SYNC_FRONTIER_TRANSITION.to_string()),
152 );
153 row.insert(
154 cs_trace::SOURCE.to_string(),
155 Value::String(source.to_string()),
156 );
157 row.insert(
158 cs_trace::SEQUENCE.to_string(),
159 Value::Number(Number::from(sequence)),
160 );
161 row.insert(
162 cs_trace::CAUSE.to_string(),
163 Value::String(frontier_change_label(new.change).to_string()),
164 );
165 row.insert(
166 cs_trace::RESULT.to_string(),
167 Value::String(result.to_string()),
168 );
169 insert_frontier_fields(row, "old", old.frontier);
170 insert_frontier_fields(row, "new", new.frontier);
171 });
172 }
173}
174
175#[derive(Copy, Clone, Debug, Eq, PartialEq)]
177pub struct HeaderRangeCommit {
178 pub start_height: block::Height,
180 pub tip: Frontier,
182}
183
184#[derive(Copy, Clone, Debug, Eq, PartialEq)]
186pub struct BlockBodySubmit {
187 pub token: BlockApplyToken,
189 pub height: block::Height,
191 pub hash: block::Hash,
193 pub result: BlockApplyResult,
195 pub local_frontier: Option<ChainFrontier>,
197}
198
199pub trait HeaderSyncStatePortImpl: Send + Sync + 'static {
201 fn current_frontier(&self) -> ChainFrontier;
203
204 fn subscribe_frontier(&self) -> watch::Receiver<FrontierUpdate>;
206
207 fn commit_header_range(
209 &self,
210 peer: ZakuraPeerId,
211 anchor: block::Hash,
212 start_height: block::Height,
213 headers: Vec<Arc<block::Header>>,
214 body_sizes: Vec<u32>,
215 finalized: bool,
216 ) -> BoxFuture<'static, Result<HeaderRangeCommit, HeaderSyncCommitFailureKind>>;
217
218 fn publish_best_header(&self, tip: Frontier) -> BoxFuture<'static, ()>;
220
221 fn publish_header_reanchor(&self, old: Frontier, new: Frontier) -> BoxFuture<'static, ()>;
223}
224
225pub trait BlockSyncStatePortImpl: Send + Sync + 'static {
227 fn current_frontier(&self) -> ChainFrontier;
229
230 fn subscribe_frontier(&self) -> watch::Receiver<FrontierUpdate>;
232
233 fn query_missing_bodies(
235 &self,
236 verified_body: block::Height,
237 best_header: block::Height,
238 ) -> BoxFuture<'static, Result<Vec<BlockSyncBlockMeta>, zakura_chain::BoxError>>;
239
240 fn submit_block_body(
242 &self,
243 token: BlockApplyToken,
244 block: Arc<block::Block>,
245 ) -> BoxFuture<'static, BlockBodySubmit>;
246
247 fn read_committed_blocks(
249 &self,
250 start: block::Height,
251 count: u32,
252 ) -> BoxFuture<
253 'static,
254 Result<Vec<(block::Height, Arc<block::Block>, usize)>, zakura_chain::BoxError>,
255 >;
256
257 fn publish_body_progress(&self, update: FrontierUpdate) -> BoxFuture<'static, ()>;
259}
260
261#[derive(Clone)]
263pub struct HeaderSyncStatePort {
264 inner: Arc<dyn HeaderSyncStatePortImpl>,
265}
266
267impl HeaderSyncStatePort {
268 pub fn new(inner: Arc<dyn HeaderSyncStatePortImpl>) -> Self {
270 Self { inner }
271 }
272
273 pub fn current_frontier(&self) -> ChainFrontier {
275 self.inner.current_frontier()
276 }
277
278 pub fn subscribe_frontier(&self) -> watch::Receiver<FrontierUpdate> {
280 self.inner.subscribe_frontier()
281 }
282
283 pub fn commit_header_range(
285 &self,
286 peer: ZakuraPeerId,
287 anchor: block::Hash,
288 start_height: block::Height,
289 headers: Vec<Arc<block::Header>>,
290 body_sizes: Vec<u32>,
291 finalized: bool,
292 ) -> BoxFuture<'static, Result<HeaderRangeCommit, HeaderSyncCommitFailureKind>> {
293 self.inner
294 .commit_header_range(peer, anchor, start_height, headers, body_sizes, finalized)
295 }
296
297 pub fn publish_best_header(&self, tip: Frontier) -> BoxFuture<'static, ()> {
299 self.inner.publish_best_header(tip)
300 }
301
302 pub fn publish_header_reanchor(&self, old: Frontier, new: Frontier) -> BoxFuture<'static, ()> {
304 self.inner.publish_header_reanchor(old, new)
305 }
306}
307
308impl std::fmt::Debug for HeaderSyncStatePort {
309 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 formatter.write_str("HeaderSyncStatePort")
311 }
312}
313
314#[derive(Clone)]
316pub struct BlockSyncStatePort {
317 inner: Arc<dyn BlockSyncStatePortImpl>,
318}
319
320impl BlockSyncStatePort {
321 pub fn new(inner: Arc<dyn BlockSyncStatePortImpl>) -> Self {
323 Self { inner }
324 }
325
326 pub fn current_frontier(&self) -> ChainFrontier {
328 self.inner.current_frontier()
329 }
330
331 pub fn subscribe_frontier(&self) -> watch::Receiver<FrontierUpdate> {
333 self.inner.subscribe_frontier()
334 }
335
336 pub fn query_missing_bodies(
338 &self,
339 verified_body: block::Height,
340 best_header: block::Height,
341 ) -> BoxFuture<'static, Result<Vec<BlockSyncBlockMeta>, zakura_chain::BoxError>> {
342 self.inner.query_missing_bodies(verified_body, best_header)
343 }
344
345 pub fn submit_block_body(
347 &self,
348 token: BlockApplyToken,
349 block: Arc<block::Block>,
350 ) -> BoxFuture<'static, BlockBodySubmit> {
351 self.inner.submit_block_body(token, block)
352 }
353
354 pub fn read_committed_blocks(
356 &self,
357 start: block::Height,
358 count: u32,
359 ) -> BoxFuture<
360 'static,
361 Result<Vec<(block::Height, Arc<block::Block>, usize)>, zakura_chain::BoxError>,
362 > {
363 self.inner.read_committed_blocks(start, count)
364 }
365
366 pub fn publish_body_progress(&self, update: FrontierUpdate) -> BoxFuture<'static, ()> {
368 self.inner.publish_body_progress(update)
369 }
370}
371
372impl std::fmt::Debug for BlockSyncStatePort {
373 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 formatter.write_str("BlockSyncStatePort")
375 }
376}
377
378pub fn chain_frontier_from_parts(
380 finalized_height: block::Height,
381 verified_body: Frontier,
382 best_header: Frontier,
383) -> ChainFrontier {
384 let finalized_hash = if finalized_height == verified_body.height {
385 verified_body.hash
386 } else {
387 block::Hash([0; 32])
388 };
389
390 ChainFrontier {
391 finalized: Frontier::new(finalized_height, finalized_hash),
392 verified_body,
393 best_header,
394 }
395}
396
397pub fn apply_frontier_update(
401 current: FrontierUpdate,
402 requested: FrontierUpdate,
403) -> Option<FrontierUpdate> {
404 let mut frontier = current.frontier;
405
406 match requested.change {
407 FrontierChange::Snapshot => {
408 frontier.finalized =
409 higher_frontier(current.frontier.finalized, requested.frontier.finalized);
410 frontier.verified_body = higher_frontier(
411 current.frontier.verified_body,
412 requested.frontier.verified_body,
413 );
414 frontier.best_header =
415 higher_frontier(current.frontier.best_header, requested.frontier.best_header);
416 if frontier == current.frontier {
417 return None;
418 }
419 }
420 FrontierChange::VerifiedGrow => {
421 if requested.frontier.verified_body.height < current.frontier.verified_body.height {
422 return None;
423 }
424 frontier.finalized =
425 higher_frontier(current.frontier.finalized, requested.frontier.finalized);
426 frontier.verified_body = requested.frontier.verified_body;
427 }
428 FrontierChange::VerifiedReset => {
429 frontier.finalized =
430 higher_frontier(current.frontier.finalized, requested.frontier.finalized);
431 frontier.verified_body = requested.frontier.verified_body;
432 }
433 FrontierChange::HeaderAdvanced => {
434 if requested.frontier.best_header.height <= current.frontier.best_header.height {
435 return None;
436 }
437 frontier.best_header = requested.frontier.best_header;
438 }
439 FrontierChange::HeaderReanchored => {
440 if requested.frontier.best_header == current.frontier.best_header {
441 return None;
442 }
443 frontier.best_header = requested.frontier.best_header;
444 }
445 }
446
447 Some(FrontierUpdate {
448 frontier,
449 change: requested.change,
450 })
451}
452
453fn higher_frontier(left: Frontier, right: Frontier) -> Frontier {
454 if right.height > left.height {
455 right
456 } else {
457 left
458 }
459}
460
461fn frontier_change_label(change: FrontierChange) -> &'static str {
462 match change {
463 FrontierChange::Snapshot => "snapshot",
464 FrontierChange::VerifiedGrow => "verified_grow",
465 FrontierChange::VerifiedReset => "verified_reset",
466 FrontierChange::HeaderAdvanced => "header_advanced",
467 FrontierChange::HeaderReanchored => "header_reanchored",
468 }
469}
470
471fn insert_frontier_fields(
472 row: &mut serde_json::Map<String, Value>,
473 prefix: &'static str,
474 frontier: ChainFrontier,
475) {
476 let (
477 finalized_height,
478 finalized_hash,
479 verified_body_height,
480 verified_body_hash,
481 best_header_height,
482 best_header_hash,
483 ) = match prefix {
484 "old" => (
485 cs_trace::OLD_FINALIZED_HEIGHT,
486 cs_trace::OLD_FINALIZED_HASH,
487 cs_trace::OLD_VERIFIED_BODY_HEIGHT,
488 cs_trace::OLD_VERIFIED_BODY_HASH,
489 cs_trace::OLD_BEST_HEADER_HEIGHT,
490 cs_trace::OLD_BEST_HEADER_HASH,
491 ),
492 "new" => (
493 cs_trace::NEW_FINALIZED_HEIGHT,
494 cs_trace::NEW_FINALIZED_HASH,
495 cs_trace::NEW_VERIFIED_BODY_HEIGHT,
496 cs_trace::NEW_VERIFIED_BODY_HASH,
497 cs_trace::NEW_BEST_HEADER_HEIGHT,
498 cs_trace::NEW_BEST_HEADER_HASH,
499 ),
500 _ => return,
501 };
502
503 insert_height(row, finalized_height, frontier.finalized.height);
504 insert_hash(row, finalized_hash, frontier.finalized.hash);
505 insert_height(row, verified_body_height, frontier.verified_body.height);
506 insert_hash(row, verified_body_hash, frontier.verified_body.hash);
507 insert_height(row, best_header_height, frontier.best_header.height);
508 insert_hash(row, best_header_hash, frontier.best_header.hash);
509}
510
511fn insert_height(
512 row: &mut serde_json::Map<String, Value>,
513 key: &'static str,
514 height: block::Height,
515) {
516 row.insert(
517 key.to_string(),
518 Value::Number(Number::from(u64::from(height.0))),
519 );
520}
521
522fn insert_hash(row: &mut serde_json::Map<String, Value>, key: &'static str, hash: block::Hash) {
523 row.insert(key.to_string(), Value::String(format!("{hash}")));
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529 use std::{
530 sync::{Arc, Barrier},
531 thread,
532 };
533
534 fn frontier(height: u32, seed: u8) -> Frontier {
535 Frontier::new(block::Height(height), block::Hash([seed; 32]))
536 }
537
538 fn update(
539 finalized: Frontier,
540 verified_body: Frontier,
541 best_header: Frontier,
542 change: FrontierChange,
543 ) -> FrontierUpdate {
544 FrontierUpdate {
545 frontier: ChainFrontier {
546 finalized,
547 verified_body,
548 best_header,
549 },
550 change,
551 }
552 }
553
554 #[test]
555 fn grow_advances_verified_body() {
556 let current = update(
557 frontier(1, 1),
558 frontier(2, 2),
559 frontier(5, 5),
560 FrontierChange::Snapshot,
561 );
562 let requested = update(
563 frontier(3, 3),
564 frontier(4, 4),
565 frontier(9, 9),
566 FrontierChange::VerifiedGrow,
567 );
568
569 let updated = apply_frontier_update(current, requested).expect("grow is accepted");
570
571 assert_eq!(updated.frontier.verified_body, frontier(4, 4));
572 assert_eq!(updated.frontier.best_header, frontier(5, 5));
573 }
574
575 #[test]
576 fn grow_may_not_lower_best_header() {
577 let current = update(
578 frontier(1, 1),
579 frontier(2, 2),
580 frontier(9, 9),
581 FrontierChange::Snapshot,
582 );
583 let requested = update(
584 frontier(3, 3),
585 frontier(4, 4),
586 frontier(5, 5),
587 FrontierChange::VerifiedGrow,
588 );
589
590 let updated = apply_frontier_update(current, requested).expect("grow is accepted");
591
592 assert_eq!(updated.frontier.finalized, frontier(3, 3));
593 assert_eq!(updated.frontier.verified_body, frontier(4, 4));
594 assert_eq!(updated.frontier.best_header, frontier(9, 9));
595 }
596
597 #[test]
598 fn stale_lower_grow_is_ignored() {
599 let current = update(
600 frontier(1, 1),
601 frontier(8, 8),
602 frontier(9, 9),
603 FrontierChange::Snapshot,
604 );
605 let requested = update(
606 frontier(1, 1),
607 frontier(7, 7),
608 frontier(9, 9),
609 FrontierChange::VerifiedGrow,
610 );
611
612 assert!(apply_frontier_update(current, requested).is_none());
613 }
614
615 #[test]
616 fn equal_height_grow_refreshes_verified_hash() {
617 let current = update(
618 frontier(1, 1),
619 frontier(8, 8),
620 frontier(9, 9),
621 FrontierChange::Snapshot,
622 );
623 let requested = update(
624 frontier(1, 1),
625 frontier(8, 18),
626 frontier(9, 19),
627 FrontierChange::VerifiedGrow,
628 );
629
630 let updated =
631 apply_frontier_update(current, requested).expect("equal height grow is accepted");
632
633 assert_eq!(updated.frontier.verified_body, frontier(8, 18));
634 assert_eq!(updated.frontier.best_header, frontier(9, 9));
635 }
636
637 #[test]
638 fn reset_may_lower_verified_body() {
639 let current = update(
640 frontier(5, 5),
641 frontier(8, 8),
642 frontier(10, 10),
643 FrontierChange::Snapshot,
644 );
645 let requested = update(
646 frontier(4, 4),
647 frontier(6, 6),
648 frontier(2, 2),
649 FrontierChange::VerifiedReset,
650 );
651
652 let updated = apply_frontier_update(current, requested).expect("reset is accepted");
653
654 assert_eq!(updated.frontier.finalized, frontier(5, 5));
655 assert_eq!(updated.frontier.verified_body, frontier(6, 6));
656 assert_eq!(updated.frontier.best_header, frontier(10, 10));
657 }
658
659 #[test]
660 fn finalized_height_never_decreases_across_mixed_updates() {
661 let mut current = update(
662 frontier(10, 10),
663 frontier(10, 10),
664 frontier(12, 12),
665 FrontierChange::Snapshot,
666 );
667 for requested in [
668 update(
669 frontier(9, 9),
670 frontier(11, 11),
671 frontier(12, 12),
672 FrontierChange::VerifiedGrow,
673 ),
674 update(
675 frontier(1, 1),
676 frontier(7, 7),
677 frontier(0, 0),
678 FrontierChange::VerifiedReset,
679 ),
680 update(
681 frontier(8, 8),
682 frontier(8, 8),
683 frontier(20, 20),
684 FrontierChange::HeaderAdvanced,
685 ),
686 update(
687 frontier(3, 3),
688 frontier(3, 3),
689 frontier(13, 13),
690 FrontierChange::HeaderReanchored,
691 ),
692 ] {
693 if let Some(updated) = apply_frontier_update(current, requested) {
694 current = updated;
695 assert!(
696 current.frontier.finalized.height >= block::Height(10),
697 "finalized frontier must never move below the original finalized height"
698 );
699 }
700 }
701 }
702
703 #[test]
704 fn snapshot_does_not_lower_existing_frontiers() {
705 let current = update(
706 frontier(5, 5),
707 frontier(8, 8),
708 frontier(12, 12),
709 FrontierChange::Snapshot,
710 );
711 let requested = update(
712 frontier(4, 4),
713 frontier(7, 7),
714 frontier(11, 11),
715 FrontierChange::Snapshot,
716 );
717
718 assert!(apply_frontier_update(current, requested).is_none());
719 }
720
721 #[test]
722 fn header_reanchor_lowers_only_best_header() {
723 let current = update(
724 frontier(5, 5),
725 frontier(8, 8),
726 frontier(12, 12),
727 FrontierChange::Snapshot,
728 );
729 let requested = update(
730 frontier(1, 1),
731 frontier(2, 2),
732 frontier(9, 9),
733 FrontierChange::HeaderReanchored,
734 );
735
736 let updated = apply_frontier_update(current, requested).expect("reanchor is accepted");
737
738 assert_eq!(updated.frontier.finalized, frontier(5, 5));
739 assert_eq!(updated.frontier.verified_body, frontier(8, 8));
740 assert_eq!(updated.frontier.best_header, frontier(9, 9));
741 }
742
743 #[test]
744 fn header_advance_cannot_change_verified_body() {
745 let current = update(
746 frontier(5, 5),
747 frontier(8, 8),
748 frontier(12, 12),
749 FrontierChange::Snapshot,
750 );
751 let requested = update(
752 frontier(1, 1),
753 frontier(2, 2),
754 frontier(13, 13),
755 FrontierChange::HeaderAdvanced,
756 );
757
758 let updated = apply_frontier_update(current, requested).expect("advance is accepted");
759
760 assert_eq!(updated.frontier.finalized, frontier(5, 5));
761 assert_eq!(updated.frontier.verified_body, frontier(8, 8));
762 assert_eq!(updated.frontier.best_header, frontier(13, 13));
763 }
764
765 #[test]
766 fn stale_header_advance_is_ignored() {
767 let current = update(
768 frontier(5, 5),
769 frontier(8, 8),
770 frontier(12, 12),
771 FrontierChange::Snapshot,
772 );
773 let requested = update(
774 frontier(20, 20),
775 frontier(21, 21),
776 frontier(12, 22),
777 FrontierChange::HeaderAdvanced,
778 );
779
780 assert!(apply_frontier_update(current, requested).is_none());
781 }
782
783 #[test]
784 fn exchange_keeps_latest_value_without_external_receivers() {
785 let initial = update(
786 frontier(0, 0),
787 frontier(0, 0),
788 frontier(0, 0),
789 FrontierChange::Snapshot,
790 );
791 let exchange = ZakuraSyncExchange::new(initial, ZakuraTrace::noop());
792 let accepted = update(
793 frontier(0, 0),
794 frontier(0, 0),
795 frontier(10, 10),
796 FrontierChange::HeaderAdvanced,
797 );
798
799 exchange.publish_frontier(accepted, "test");
800
801 assert_eq!(exchange.current_frontier(), accepted);
802
803 let stale = update(
804 frontier(0, 0),
805 frontier(0, 0),
806 frontier(9, 9),
807 FrontierChange::HeaderAdvanced,
808 );
809 exchange.publish_frontier(stale, "test");
810
811 assert_eq!(exchange.current_frontier(), accepted);
812 }
813
814 #[test]
815 fn concurrent_publishers_converge_to_highest_frontiers() {
816 const PUBLISHERS: u32 = 64;
817
818 let initial = update(
819 frontier(0, 0),
820 frontier(0, 0),
821 frontier(0, 0),
822 FrontierChange::Snapshot,
823 );
824 let exchange = ZakuraSyncExchange::new(initial, ZakuraTrace::noop());
825 let barrier = Arc::new(Barrier::new((PUBLISHERS * 2 + 1) as usize));
826 let mut publishers = Vec::new();
827
828 for height in 1..=PUBLISHERS {
829 let seed = u8::try_from(height).expect("test publisher height fits in u8");
830 let header_exchange = exchange.clone();
831 let header_barrier = barrier.clone();
832 publishers.push(thread::spawn(move || {
833 header_barrier.wait();
834 header_exchange.publish_frontier(
835 update(
836 frontier(0, 0),
837 frontier(0, 0),
838 frontier(height, seed),
839 FrontierChange::HeaderAdvanced,
840 ),
841 "test",
842 );
843 }));
844
845 let seed = u8::try_from(height).expect("test publisher height fits in u8");
846 let grow_exchange = exchange.clone();
847 let grow_barrier = barrier.clone();
848 publishers.push(thread::spawn(move || {
849 grow_barrier.wait();
850 grow_exchange.publish_frontier(
851 update(
852 frontier(height, seed),
853 frontier(height, seed),
854 frontier(0, 0),
855 FrontierChange::VerifiedGrow,
856 ),
857 "test",
858 );
859 }));
860 }
861
862 barrier.wait();
863 for publisher in publishers {
864 publisher.join().expect("publisher thread should not panic");
865 }
866
867 let current = exchange.current_frontier().frontier;
868 let seed = u8::try_from(PUBLISHERS).expect("test publisher height fits in u8");
869 assert_eq!(current.finalized, frontier(PUBLISHERS, seed));
870 assert_eq!(current.verified_body, frontier(PUBLISHERS, seed));
871 assert_eq!(current.best_header, frontier(PUBLISHERS, seed));
872 }
873
874 #[test]
875 fn concurrent_header_advance_is_not_lost_to_verified_grow() {
876 let initial = update(
877 frontier(0, 0),
878 frontier(5, 5),
879 frontier(10, 10),
880 FrontierChange::Snapshot,
881 );
882 let exchange = ZakuraSyncExchange::new(initial, ZakuraTrace::noop());
883 let barrier = Arc::new(Barrier::new(3));
884
885 let header_exchange = exchange.clone();
886 let header_barrier = barrier.clone();
887 let header = thread::spawn(move || {
888 header_barrier.wait();
889 header_exchange.publish_frontier(
890 update(
891 frontier(0, 0),
892 frontier(0, 0),
893 frontier(20, 20),
894 FrontierChange::HeaderAdvanced,
895 ),
896 "test",
897 );
898 });
899
900 let grow_exchange = exchange.clone();
901 let grow_barrier = barrier.clone();
902 let grow = thread::spawn(move || {
903 grow_barrier.wait();
904 grow_exchange.publish_frontier(
905 update(
906 frontier(0, 0),
907 frontier(8, 8),
908 frontier(0, 0),
909 FrontierChange::VerifiedGrow,
910 ),
911 "test",
912 );
913 });
914
915 barrier.wait();
916 header.join().expect("header publisher should not panic");
917 grow.join().expect("grow publisher should not panic");
918
919 let current = exchange.current_frontier().frontier;
920 assert_eq!(current.verified_body, frontier(8, 8));
921 assert_eq!(current.best_header, frontier(20, 20));
922 }
923}