1use super::{
2 Buffer, Retirement, Variant,
3 acks::{PendingAck, PendingAcks},
4 cache,
5 delivery::PendingVerification,
6 durability::{DispatchGate, Durable as _},
7 floor::{Floor, State as FloorState},
8 mailbox::{CommitmentFallback, Mailbox, Message},
9 stream::Stream,
10 subscriptions::{Key as SubscriptionKey, KeyFor as SubscriptionKeyFor, Subscriptions},
11 variant::NoBuffer,
12};
13use crate::{
14 Block, Epochable, Heightable, Reporter,
15 marshal::{
16 Config, Identifier as BlockID, Start, Update,
17 resolver::handler::{self, Annotation, Key, Request},
18 store::{Blocks, Certificates},
19 },
20 simplex::{
21 scheme::Scheme,
22 types::{Finalization, Notarization, Subject, verify_certificates},
23 },
24 types::{Epoch, Epocher, Height, Round, ViewDelta},
25};
26use bytes::Bytes;
27use commonware_actor::mailbox;
28use commonware_codec::{Decode, Encode, Read};
29use commonware_cryptography::{
30 Digestible,
31 certificate::{Provider, Scoped, Verifier},
32};
33use commonware_macros::{boxed, select_loop};
34use commonware_p2p::Recipients;
35use commonware_parallel::Strategy;
36use commonware_resolver::{Delivery, Resolver, TargetedResolver};
37use commonware_runtime::{
38 BufferPooler, Clock, ContextCell, Handle, Metrics, Spawner, Storage, spawn_cell,
39 telemetry::{
40 metrics::{Gauge, GaugeExt, MetricsExt as _},
41 traces::TracedExt as _,
42 },
43};
44use commonware_storage::archive::Identifier as ArchiveID;
45use commonware_utils::{
46 Acknowledgement, BoxedError,
47 acknowledgement::Exact,
48 channel::{fallible::OneshotExt, oneshot},
49 futures::{AbortablePool, Pool},
50};
51use futures::{
52 FutureExt as _, TryFutureExt as _,
53 future::{join, join_all},
54 try_join,
55};
56use rand_core::CryptoRng;
57use std::{collections::BTreeMap, future::Future, num::NonZeroUsize, sync::Arc};
58use tracing::{Instrument as _, Span, debug, info_span, warn};
59
60type ResolverRequestFor<V> = Key<<V as Variant>::Commitment>;
63
64struct ResolverDelivery<V: Variant> {
67 delivery: Delivery<ResolverRequestFor<V>, Annotation>,
68 value: Bytes,
69 response: oneshot::Sender<bool>,
70}
71
72enum PooledSync {
74 Observed,
76 Finalized(u64),
80}
81
82pub struct Actor<E, V, P, FC, FB, ES, T, A = Exact>
95where
96 E: BufferPooler + CryptoRng + Spawner + Metrics + Clock + Storage,
97 V: Variant,
98 P: Provider<Scope = Epoch, Scheme: Scheme<V::Commitment>>,
99 FC: Certificates<
100 BlockDigest = <V::Block as Digestible>::Digest,
101 Commitment = V::Commitment,
102 Scheme = P::Scheme,
103 >,
104 FB: Blocks<Block = V::StoredBlock>,
105 ES: Epocher,
106 T: Strategy,
107 A: Acknowledgement,
108{
109 context: ContextCell<E>,
111
112 mailbox: mailbox::Receiver<Message<P::Scheme, V>>,
115
116 provider: P,
119 epocher: ES,
121 view_retention: ViewDelta,
123 max_repair: NonZeroUsize,
125 block_codec_config: <V::ApplicationBlock as Read>::Cfg,
127 strategy: T,
129
130 floor: FloorState<P::Scheme, V::Commitment>,
133 stream: Stream<E>,
135 pending_acks: PendingAcks<V, A>,
137 cleared_acks: Vec<(Height, V::Commitment)>,
139 tip: Height,
141 block_subscriptions: Subscriptions<V>,
143 dispatch_gate: DispatchGate,
146
147 cache: cache::Manager<E, V, P::Scheme>,
150 finalizations_by_height: FC,
152 finalized_blocks: FB,
154
155 finalized_height: Gauge,
158 processed_height: Gauge,
160}
161
162impl<E, V, P, FC, FB, ES, T, A> Actor<E, V, P, FC, FB, ES, T, A>
163where
164 E: BufferPooler + CryptoRng + Spawner + Metrics + Clock + Storage,
165 V: Variant,
166 P: Provider<Scope = Epoch, Scheme: Scheme<V::Commitment>>,
167 FC: Certificates<
168 BlockDigest = <V::Block as Digestible>::Digest,
169 Commitment = V::Commitment,
170 Scheme = P::Scheme,
171 >,
172 FB: Blocks<Block = V::StoredBlock>,
173 ES: Epocher,
174 T: Strategy,
175 A: Acknowledgement,
176{
177 #[boxed]
179 pub async fn init(
180 context: E,
181 finalizations_by_height: FC,
182 mut finalized_blocks: FB,
183 config: Config<P, ES, T, V::ApplicationBlock, V::Block, V::Commitment>,
184 ) -> (Self, Mailbox<P::Scheme, V>, Floor) {
185 let prunable_config = cache::Config {
187 partition_prefix: format!("{}-cache", config.partition_prefix),
188 prunable_items_per_section: config.prunable_items_per_section,
189 replay_buffer: config.replay_buffer,
190 key_write_buffer: config.key_write_buffer,
191 value_write_buffer: config.value_write_buffer,
192 key_page_cache: config.page_cache.clone(),
193 };
194 let cache = cache::Manager::init(
195 context.child("cache"),
196 prunable_config,
197 config.block_codec_config.clone(),
198 )
199 .await;
200
201 let application_metadata_partition =
203 format!("{}-application-metadata", config.partition_prefix);
204 let stream = Stream::new(context.child("stream"), &application_metadata_partition).await;
205 let last_processed_height = stream.processed_height();
206
207 let pending_floor_anchor = match config.start {
210 Start::Genesis(anchor) => {
211 assert_eq!(
212 anchor.height(),
213 Height::zero(),
214 "genesis anchor must be at height zero"
215 );
216 finalized_blocks =
217 Self::ensure_genesis_anchor(finalized_blocks, anchor, last_processed_height)
218 .await;
219 None
220 }
221 Start::Floor(finalization) => Some(finalization),
222 };
223 let last_processed_round = Self::latest_processed_round(
224 &finalizations_by_height,
225 &finalized_blocks,
226 last_processed_height,
227 )
228 .await;
229
230 let finalized_height = context.gauge("finalized_height", "Finalized height of application");
232 let processed_height = context.gauge("processed_height", "Processed height of application");
233 if let Some(last_processed_height) = last_processed_height {
234 let _ = processed_height.try_set(last_processed_height.get());
235 }
236 let floor_state = pending_floor_anchor.map_or_else(
237 || FloorState::resolved(last_processed_height, last_processed_round),
238 |finalization| {
239 FloorState::awaiting_anchor(
240 last_processed_height,
241 last_processed_round,
242 finalization,
243 )
244 },
245 );
246 let floor = floor_state.snapshot();
247
248 let (sender, mailbox) = mailbox::new(context.child("mailbox"), config.mailbox_size);
250 (
251 Self {
252 context: ContextCell::new(context),
253 mailbox,
254 provider: config.provider,
255 epocher: config.epocher,
256 view_retention: config.view_retention,
257 max_repair: config.max_repair,
258 block_codec_config: config.block_codec_config,
259 strategy: config.strategy,
260 floor: floor_state,
261 stream,
262 pending_acks: PendingAcks::new(config.max_pending_acks.get()),
263 cleared_acks: Vec::new(),
264 tip: Height::zero(),
265 block_subscriptions: Subscriptions::new(),
266 dispatch_gate: DispatchGate::default(),
267 cache,
268 finalizations_by_height,
269 finalized_blocks,
270 finalized_height,
271 processed_height,
272 },
273 Mailbox::new(sender, config.max_pending_acks),
274 floor,
275 )
276 }
277
278 async fn ensure_genesis_anchor(
279 mut finalized_blocks: FB,
280 anchor: V::Block,
281 last_processed_height: Option<Height>,
282 ) -> FB {
283 let anchor_height = anchor.height();
284 let anchor_commitment = V::commitment(&anchor);
285 match finalized_blocks
286 .get(ArchiveID::Index(anchor_height.get()))
287 .await
288 {
289 Ok(Some(stored)) => {
290 let stored: V::Block = stored.into();
291 assert_eq!(
292 stored.height(),
293 anchor_height,
294 "stored genesis block height mismatch"
295 );
296 assert!(
297 V::commitment(&stored) == anchor_commitment,
298 "stored genesis block does not match configured anchor"
299 );
300 }
301 Ok(None) => {
302 if let Some(existing) =
303 last_processed_height.filter(|height| anchor_height < *height)
304 {
305 warn!(
306 height = %anchor_height,
307 %existing,
308 "ignoring stale anchor"
309 );
310 return finalized_blocks;
311 }
312
313 finalized_blocks = finalized_blocks
314 .put(anchor.into())
315 .await
316 .expect("failed to store startup anchor")
317 .sync()
318 .await
319 .expect("failed to sync startup anchor");
320 debug!(height = %anchor_height, "stored genesis block");
321 }
322 Err(err) => panic!("failed to check startup anchor: {err}"),
323 }
324 finalized_blocks
325 }
326
327 pub fn start<R, Buf>(
329 self,
330 application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
331 buffer: Buf,
332 resolver: (handler::Receiver<V::Commitment>, R),
333 ) -> Handle<()>
334 where
335 R: TargetedResolver<
336 Key = ResolverRequestFor<V>,
337 Subscriber = Annotation,
338 PublicKey = <P::Scheme as Verifier>::PublicKey,
339 >,
340 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
341 {
342 let mut actor = Box::new(self);
343 spawn_cell!(actor.context, actor.run(application, buffer, resolver))
344 }
345
346 pub fn start_unbuffered<R>(
348 self,
349 application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
350 resolver: (handler::Receiver<V::Commitment>, R),
351 ) -> Handle<()>
352 where
353 R: TargetedResolver<
354 Key = ResolverRequestFor<V>,
355 Subscriber = Annotation,
356 PublicKey = <P::Scheme as Verifier>::PublicKey,
357 >,
358 {
359 self.start(
360 application,
361 NoBuffer::<<P::Scheme as Verifier>::PublicKey>::new(),
362 resolver,
363 )
364 }
365
366 async fn run<R, Buf>(
368 mut self: Box<Self>,
369 mut application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
370 mut buffer: Buf,
371 (mut resolver_rx, mut resolver): (handler::Receiver<V::Commitment>, R),
372 ) where
373 R: TargetedResolver<
374 Key = ResolverRequestFor<V>,
375 Subscriber = Annotation,
376 PublicKey = <P::Scheme as Verifier>::PublicKey,
377 >,
378 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
379 {
380 let mut waiters = AbortablePool::<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>::default();
382
383 let mut syncs = Pool::<PooledSync>::default();
389
390 (self, application, buffer, resolver) = async move {
395 let tip = self.get_latest().await;
397 if let Some((height, digest, round)) = tip {
398 application.report(Update::Tip(round, height, digest));
399 self.tip = height;
400 let _ = self.finalized_height.try_set(height.get());
401 }
402
403 self.cache = self.cache.load_persisted_epochs().await;
406
407 if let Some(finalization) = self.floor.take_pending_anchor() {
410 self = self
411 .install_floor(
412 finalization,
413 false,
414 &mut resolver,
415 &mut buffer,
416 &mut application,
417 )
418 .await;
419 }
420
421 let repaired;
423 (self, repaired) = self
424 .try_repair_gaps(&mut buffer, &mut resolver, &mut application)
425 .await;
426 if repaired {
427 self = self.sync_finalized().await;
428 }
429
430 self = self.try_dispatch_blocks(&mut application).await;
432
433 (self, application, buffer, resolver)
434 }
435 .instrument(info_span!("marshal.actor.start"))
436 .await;
437
438 select_loop! {
439 self.context,
440 on_start => {
441 self.block_subscriptions.retain_open();
443 },
444 on_stopped => {
445 debug!("context shutdown, stopping marshal");
446 },
447 sync = syncs.next_completed() => {
452 if let PooledSync::Finalized(seq) = sync {
453 self.dispatch_gate.release(seq);
454 self = self.try_dispatch_blocks(&mut application).await;
455 }
456 },
457 Ok(completion) = waiters.next_completed() else continue => match completion {
459 Ok(block) => {
460 (self, _) = self
461 .ingest(block, &mut buffer, &mut application, &mut resolver)
462 .await;
463 }
464 Err(key) => {
465 match key {
467 SubscriptionKey::Digest(digest) => {
468 debug!(
469 ?digest,
470 "buffer subscription closed, canceling local subscribers"
471 );
472 }
473 SubscriptionKey::Commitment(commitment) => {
474 debug!(
475 ?commitment,
476 "buffer subscription closed, canceling local subscribers"
477 );
478 }
479 }
480 self.block_subscriptions.remove(&key);
481 }
482 },
483 result = self.pending_acks.current() => {
485 let next = match self
486 .handle_ack(result, &mut application, &mut buffer, &mut resolver)
487 .await
488 {
489 Ok(next) => next,
490 Err((height, e)) => {
491 debug!(
492 ?e,
493 %height,
494 "application acknowledgement dropped, stopping marshal"
495 );
496 return;
497 }
498 };
499 self = next;
500 },
501 Some(message) = self.mailbox.recv() else {
503 debug!("mailbox closed, shutting down");
504 break;
505 } => {
506 let span = info_span!(
507 parent: message.span(),
508 "marshal.actor.process",
509 operation = message.name(),
510 );
511 self = self
512 .handle_mailbox_message(
513 message,
514 &mut resolver,
515 &mut waiters,
516 &mut syncs,
517 &mut buffer,
518 &mut application,
519 )
520 .instrument(span)
521 .await;
522 },
523 Some(message) = resolver_rx.recv() else {
525 debug!("handler closed, shutting down");
526 return;
527 } => {
528 self = self
529 .handle_resolver_message(
530 message,
531 &mut resolver_rx,
532 &mut resolver,
533 &mut syncs,
534 &mut buffer,
535 &mut application,
536 )
537 .await;
538 },
539 }
540 }
541
542 async fn handle_ack<Buf, R>(
545 mut self: Box<Self>,
546 result: <A::Waiter as Future>::Output,
547 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
548 buffer: &mut Buf,
549 resolver: &mut R,
550 ) -> Result<Box<Self>, (Height, A::Error)>
551 where
552 Buf: Buffer<V>,
553 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
554 {
555 let mut pending = Some(self.pending_acks.complete_current(result));
557 let mut processed_commitments = Vec::new();
558 let processed_round = loop {
559 let (height, commitment, result) = pending.take().expect("pending ack must exist");
560 match result {
561 Ok(()) => {
562 self.update_processed_height(height, resolver);
565 self = self
566 .update_processed_round(height, buffer, application, resolver)
567 .await;
568 }
569 Err(e) => return Err((height, e)),
570 }
571 processed_commitments.push(commitment);
572
573 match self.pending_acks.pop_ready() {
576 Some(next) => pending = Some(next),
577 None => break self.floor.round(),
578 }
579 };
580
581 self.stream = self
583 .stream
584 .sync()
585 .await
586 .expect("failed to sync application progress");
587
588 buffer.retire(Retirement {
591 round_floor: processed_round,
592 exact_retirements: processed_commitments,
593 });
594
595 Ok(self.try_dispatch_blocks(application).await)
597 }
598
599 async fn handle_mailbox_message<Buf, R>(
601 mut self: Box<Self>,
602 message: Message<P::Scheme, V>,
603 resolver: &mut R,
604 waiters: &mut AbortablePool<'_, Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
605 syncs: &mut Pool<'_, PooledSync>,
606 buffer: &mut Buf,
607 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
608 ) -> Box<Self>
609 where
610 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
611 R: TargetedResolver<
612 Key = ResolverRequestFor<V>,
613 Subscriber = Annotation,
614 PublicKey = <P::Scheme as Verifier>::PublicKey,
615 >,
616 {
617 if message.response_closed() {
618 return self;
619 }
620
621 match message {
622 Message::GetInfo {
623 identifier,
624 response,
625 ..
626 } => {
627 let info = match identifier {
628 BlockID::Digest(digest) => self
632 .finalized_blocks
633 .get(ArchiveID::Key(&digest))
634 .await
635 .ok()
636 .flatten()
637 .map(|b| (b.height(), digest)),
638 BlockID::Height(height) => self.get_info_by_height(height).await,
639 BlockID::Latest => self.get_latest().await.map(|(h, d, _)| (h, d)),
640 };
641 response.send_lossy(info);
642 }
643 Message::GetVerified {
644 round, response, ..
645 } => {
646 let block = self.cache.get_verified(round).await.map(Into::into);
647 response.send_lossy(block);
648 }
649 Message::Forward {
650 round,
651 commitment,
652 recipients,
653 ..
654 } => {
655 if matches!(&recipients, Recipients::Some(peers) if peers.is_empty()) {
656 return self;
657 }
658 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
659 debug!(?commitment, "block not found for forwarding");
660 return self;
661 };
662 buffer.send(round, block, recipients);
663 }
664 Message::Proposed {
665 round,
666 block,
667 recipients,
668 ack,
669 ..
670 } => {
671 buffer.send(round, Arc::clone(&block), recipients);
681 self = self
682 .persist_verified(round, block, ack, buffer, application, resolver)
683 .await;
684 }
685 Message::Verified {
686 round, block, ack, ..
687 } => {
688 self = self
689 .persist_verified(round, block, ack, buffer, application, resolver)
690 .await;
691 }
692 Message::Certified {
693 round, block, ack, ..
694 } => {
695 (self, _) = self
696 .ingest(Arc::clone(&block), buffer, application, resolver)
697 .await;
698 let digest = block.digest();
699
700 let block_sync;
708 if self.cache.has_verified(round, &digest).await {
709 debug!(?round, "certified block covered by verified write");
710 (self.cache, block_sync) = self.cache.start_sync_verified(round).await;
711 } else {
712 (self.cache, block_sync) = self
713 .cache
714 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
715 .await;
716 }
717
718 let notarization_sync;
722 (self.cache, notarization_sync) = self.cache.start_sync_notarizations(round).await;
723 let handle = Handle::from_future(async move {
724 let (notarization, block) = join(notarization_sync, block_sync).await;
725 notarization.and(block)
726 });
727 ack.send_lossy(handle);
728 }
729 Message::Notarization { notarization, .. } => {
730 let round = notarization.round();
731 let commitment = notarization.proposal.payload;
732 let digest = V::commitment_to_inner(commitment);
733
734 let handle;
741 (self.cache, handle) = self
742 .cache
743 .put_notarization(round, digest, notarization)
744 .await;
745 syncs.push(async move {
746 handle.durable(round, "notarization").await;
747 PooledSync::Observed
748 });
749
750 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
754 (self, _) = self
755 .ingest(Arc::clone(&block), buffer, application, resolver)
756 .await;
757 if self.cache.has_verified(round, &digest).await {
758 debug!(?round, "notarized block covered by verified write");
759 } else {
760 let handle;
761 (self.cache, handle) = self
762 .cache
763 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
764 .await;
765 syncs.push(async move {
766 handle.durable(round, "notarized").await;
767 PooledSync::Observed
768 });
769 }
770 } else {
771 debug!(?round, "notarized block unavailable locally");
772 }
773 }
774 Message::Finalization { finalization, .. } => {
775 let round = finalization.round();
776 let commitment = finalization.proposal.payload;
777 let digest = V::commitment_to_inner(commitment);
778
779 self.cache = self
781 .cache
782 .put_finalization(round, digest, finalization.clone())
783 .await;
784
785 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
787 let anchored;
790 (self, anchored) = self
791 .ingest(Arc::clone(&block), buffer, application, resolver)
792 .await;
793 if anchored {
794 return self;
795 }
796
797 let height = block.height();
798 let stored;
799 (self, stored) = self
800 .update_processed_round_floor(height, round, buffer, application, resolver)
801 .await
802 .store_finalization(
803 height,
804 digest,
805 Arc::unwrap_or_clone(block),
806 Some(finalization),
807 application,
808 )
809 .await;
810 if stored {
811 (self, _) = self.try_repair_gaps(buffer, resolver, application).await;
814 self = self.start_finalized_sync(round, syncs).await;
815 debug!(?round, %height, "finalized block stored");
816 }
817 } else {
818 debug!(?round, ?commitment, "finalized block missing");
821 self.floor
822 .fetch_if_permitted(
823 resolver,
824 Request::finalized_block_by_round(commitment, round),
825 )
826 .ignore();
827 }
828 }
829 Message::GetBlock {
830 identifier,
831 response,
832 ..
833 } => match identifier {
834 BlockID::Digest(digest) => {
835 let result = self
836 .find_block_by_digest(buffer, digest)
837 .await
838 .map(Arc::unwrap_or_clone);
839 response.send_lossy(result);
840 }
841 BlockID::Height(height) => {
842 let result = self.get_finalized_block(height).await;
843 response.send_lossy(result);
844 }
845 BlockID::Latest => {
846 let block = match self.get_latest().await {
847 Some((_, digest, _)) => self.find_block_by_digest(buffer, digest).await,
848 None => None,
849 }
850 .map(Arc::unwrap_or_clone);
851 response.send_lossy(block);
852 }
853 },
854 Message::GetFinalization {
855 height, response, ..
856 } => {
857 let finalization = self.get_finalization_by_height(height).await;
858 response.send_lossy(finalization);
859 }
860 Message::GetProcessedHeight { response, .. } => {
861 response.send_lossy(self.stream.processed_height());
862 }
863 Message::HintFinalized {
864 height, targets, ..
865 } => {
866 if self.has_finalization_by_height(height).await {
868 return self;
869 }
870
871 self.floor
872 .fetch_targeted_if_permitted(resolver, Request::finalized(height), targets)
873 .ignore();
874 }
875 Message::SubscribeByDigest {
876 span,
877 digest,
878 fallback,
879 response,
880 } => {
881 self.handle_subscribe(
882 span,
883 fallback.into(),
884 SubscriptionKey::Digest(digest),
885 response,
886 resolver,
887 waiters,
888 buffer,
889 )
890 .await;
891 }
892 Message::SubscribeByCommitment {
893 span,
894 commitment,
895 fallback,
896 response,
897 } => {
898 self.handle_subscribe(
899 span,
900 fallback,
901 SubscriptionKey::Commitment(commitment),
902 response,
903 resolver,
904 waiters,
905 buffer,
906 )
907 .await;
908 }
909 Message::HintNotarized {
910 round, commitment, ..
911 } => {
912 if self
913 .find_block_by_commitment(buffer, commitment)
914 .await
915 .is_none()
916 {
917 self.floor
918 .fetch_if_permitted(resolver, Request::notarized(round))
919 .ignore();
920 }
921 }
922 Message::SetFloor { finalization, .. } => {
923 self = self
924 .install_floor(finalization, true, resolver, buffer, application)
925 .await;
926 }
927 Message::Prune { height, .. } => {
928 if height > self.floor.processed_height() {
930 warn!(%height, floor = %self.floor.processed_height(), "prune height above floor, ignoring");
931 return self;
932 }
933
934 self = self.prune_finalized_archives(height).await;
935 }
936 }
937 self
938 }
939
940 async fn handle_resolver_message<Buf, R>(
943 mut self: Box<Self>,
944 message: handler::Message<V::Commitment>,
945 resolver_rx: &mut handler::Receiver<V::Commitment>,
946 resolver: &mut R,
947 syncs: &mut Pool<'_, PooledSync>,
948 buffer: &mut Buf,
949 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
950 ) -> Box<Self>
951 where
952 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
953 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
954 {
955 let mut handled = false;
956 let mut produces = Vec::new();
957 let mut delivers = Vec::new();
958
959 for msg in std::iter::once(message)
963 .chain(std::iter::from_fn(|| resolver_rx.try_recv().ok()))
964 .take(self.max_repair.get())
965 {
966 if msg.response_closed() {
967 continue;
968 }
969 handled = true;
970
971 match msg {
972 handler::Message::Produce { key, response } => {
973 produces.push((key, response));
974 }
975 handler::Message::Deliver {
976 delivery,
977 value,
978 response,
979 } => {
980 let span = info_span!(
981 parent: &delivery.subscribers.first().1,
982 "marshal.resolver.deliver",
983 key = %delivery.key
984 );
985 for (_, subscriber_span) in delivery.subscribers.iter().skip(1) {
986 span.follows_from(subscriber_span.id());
987 }
988 self = self
989 .handle_deliver(
990 ResolverDelivery {
991 delivery,
992 value,
993 response,
994 },
995 &mut delivers,
996 buffer,
997 application,
998 resolver,
999 )
1000 .instrument(span)
1001 .await;
1002 }
1003 }
1004 }
1005 if !handled {
1006 return self;
1007 }
1008
1009 self = self
1011 .verify_delivered(delivers, buffer, application, resolver)
1012 .await;
1013
1014 (self, _) = self.try_repair_gaps(buffer, resolver, application).await;
1017
1018 let round = self.floor.round();
1023 self = self.start_finalized_sync(round, syncs).await;
1024
1025 join_all(
1027 produces
1028 .into_iter()
1029 .filter(|(_, response)| !response.is_closed())
1030 .map(|(key, response)| self.handle_produce(key, response, buffer)),
1031 )
1032 .await;
1033
1034 self
1035 }
1036
1037 #[tracing::instrument(name = "marshal.resolver.produce", level = "debug", skip_all, fields(key = %key))]
1039 async fn handle_produce<Buf: Buffer<V>>(
1040 &self,
1041 key: ResolverRequestFor<V>,
1042 response: oneshot::Sender<Bytes>,
1043 buffer: &Buf,
1044 ) {
1045 match key {
1046 Key::Block(commitment) => {
1047 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
1048 debug!(?commitment, "block missing on request");
1049 return;
1050 };
1051 response.send_lossy(block.encode());
1052 }
1053 Key::Finalized { height } => {
1054 let Some(finalization) = self.get_finalization_by_height(height).await else {
1055 debug!(%height, "finalization missing on request");
1056 return;
1057 };
1058 let Some(block) = self.get_finalized_block(height).await else {
1059 debug!(%height, "finalized block missing on request");
1060 return;
1061 };
1062 response.send_lossy((finalization, V::into_inner(block)).encode());
1063 }
1064 Key::Notarized { round } => {
1065 let Some(notarization) = self.cache.get_notarization(round).await else {
1066 debug!(?round, "notarization missing on request");
1067 return;
1068 };
1069 let commitment = notarization.proposal.payload;
1070 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
1071 debug!(?commitment, "block missing on request");
1072 return;
1073 };
1074 response.send_lossy((notarization, block).encode());
1075 }
1076 }
1077 }
1078
1079 #[allow(clippy::too_many_arguments)]
1081 async fn handle_subscribe<Buf: Buffer<V>>(
1082 &mut self,
1083 span: Span,
1084 fallback: CommitmentFallback,
1085 key: SubscriptionKeyFor<V>,
1086 response: oneshot::Sender<Arc<V::Block>>,
1087 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1088 waiters: &mut AbortablePool<'_, Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
1089 buffer: &mut Buf,
1090 ) {
1091 let digest = match key {
1092 SubscriptionKey::Digest(digest) => digest,
1093 SubscriptionKey::Commitment(commitment) => V::commitment_to_inner(commitment),
1094 };
1095
1096 let block = match key {
1097 SubscriptionKey::Digest(digest) => self.find_block_by_digest(buffer, digest).await,
1098 SubscriptionKey::Commitment(commitment) => {
1099 self.find_block_by_commitment(buffer, commitment).await
1100 }
1101 };
1102 if let Some(block) = block {
1103 response.send_lossy(block);
1104 return;
1105 }
1106
1107 match fallback {
1114 CommitmentFallback::FetchByRound { round } => {
1115 self.floor
1121 .fetch_if_permitted(resolver, Request::notarized(round))
1122 .ignore();
1123 debug!(?round, ?digest, "notarized block unavailable");
1124 }
1125 CommitmentFallback::FetchByCommitment { height } => {
1126 let commitment = match key {
1127 SubscriptionKey::Commitment(commitment) => commitment,
1128 SubscriptionKey::Digest(_) => {
1129 unreachable!("digest subscriptions cannot request commitment fallback")
1130 }
1131 };
1132
1133 self.floor
1136 .fetch_if_permitted(resolver, Request::certified_block(commitment, height))
1137 .ignore();
1138 debug!(%height, ?commitment, ?digest, "certified ancestry block unavailable");
1139 }
1140 CommitmentFallback::Wait => {}
1141 }
1142
1143 match key {
1145 SubscriptionKey::Digest(digest) => {
1146 debug!(?fallback, ?digest, "registering subscriber");
1147 }
1148 SubscriptionKey::Commitment(commitment) => {
1149 debug!(?fallback, ?commitment, ?digest, "registering subscriber");
1150 }
1151 }
1152 self.block_subscriptions
1153 .insert(span, key, response, waiters, buffer);
1154 }
1155
1156 async fn install_floor<Buf, R>(
1158 mut self: Box<Self>,
1159 finalization: Finalization<P::Scheme, V::Commitment>,
1160 skip_if_superseded: bool,
1161 resolver: &mut R,
1162 buffer: &mut Buf,
1163 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1164 ) -> Box<Self>
1165 where
1166 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
1167 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1168 {
1169 let round = finalization.round();
1170 let processed_round = self.floor.round();
1171 if round <= processed_round {
1172 warn!(
1173 ?round,
1174 floor = ?processed_round,
1175 "floor not updated, below existing round floor"
1176 );
1177 return self;
1178 }
1179
1180 let Some(scoped) = self.provider.scoped(finalization.epoch()) else {
1181 panic!("floor finalization epoch unavailable");
1182 };
1183 assert!(
1184 finalization.verify(self.context.as_mut(), &scoped, &self.strategy),
1185 "floor finalization must verify"
1186 );
1187
1188 let commitment = finalization.proposal.payload;
1189 let digest = V::commitment_to_inner(commitment);
1190 self.cache = self
1191 .cache
1192 .put_finalization(round, digest, finalization.clone())
1193 .await;
1194
1195 if skip_if_superseded && self.floor.has_pending_anchor_at_or_after(round) {
1198 return self;
1199 }
1200
1201 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
1202 self.floor.await_anchor(finalization);
1203 let anchored;
1204 (self, anchored) = self.ingest(block, buffer, application, resolver).await;
1205 assert!(anchored, "failed to ingest pending floor anchor");
1206 return self;
1207 }
1208
1209 self.cleared_acks.extend(self.pending_acks.clear());
1213
1214 debug!(?round, ?commitment, "starting fetch for floor block");
1215 self.floor.await_anchor(finalization);
1216 self.floor
1217 .fetch_if_permitted(
1218 resolver,
1219 Request::finalized_block_by_round(commitment, round),
1220 )
1221 .ignore();
1222 self
1223 }
1224
1225 async fn persist_verified<Buf: Buffer<V>>(
1233 mut self: Box<Self>,
1234 round: Round,
1235 block: Arc<V::Block>,
1236 ack: oneshot::Sender<Handle<()>>,
1237 buffer: &mut Buf,
1238 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1239 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1240 ) -> Box<Self> {
1241 (self, _) = self
1242 .ingest(Arc::clone(&block), buffer, application, resolver)
1243 .await;
1244 let digest = block.digest();
1245 let handle;
1246 (self.cache, handle) = self
1247 .cache
1248 .put_verified(round, digest, Arc::unwrap_or_clone(block).into())
1249 .await;
1250 ack.send_lossy(handle);
1251 self
1252 }
1253
1254 async fn ingest<Buf: Buffer<V>>(
1268 mut self: Box<Self>,
1269 block: Arc<V::Block>,
1270 buffer: &mut Buf,
1271 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1272 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1273 ) -> (Box<Self>, bool) {
1274 self.block_subscriptions.notify(Arc::clone(&block));
1275
1276 if !self.floor.matches_pending_anchor(V::commitment(&block)) {
1277 return (self, false);
1278 }
1279
1280 self = self
1281 .apply_pending_floor(block, buffer, application, resolver)
1282 .await;
1283 (self, true)
1284 }
1285
1286 async fn apply_pending_floor<Buf: Buffer<V>>(
1292 mut self: Box<Self>,
1293 block: Arc<V::Block>,
1294 buffer: &mut Buf,
1295 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1296 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1297 ) -> Box<Self> {
1298 let height = block.height();
1301 if height > Height::zero() {
1302 let parent_commitment = V::parent_commitment(&block);
1303 assert!(
1304 block.parent() == V::commitment_to_inner(parent_commitment),
1305 "floor block parent commitment mismatch"
1306 );
1307 }
1308
1309 if height <= self.floor.processed_height() {
1313 warn!(
1314 %height,
1315 existing = %self.floor.processed_height(),
1316 "floor not updated, at or below existing"
1317 );
1318 let finalization = self
1319 .floor
1320 .take_pending_anchor()
1321 .expect("pending floor anchor missing");
1322 self = self
1323 .update_processed_round_floor(
1324 height,
1325 finalization.round(),
1326 buffer,
1327 application,
1328 resolver,
1329 )
1330 .await;
1331 let commitments = self.take_superseded_ack_commitments();
1332 buffer.retire(Retirement {
1333 round_floor: self.floor.round(),
1334 exact_retirements: commitments,
1335 });
1336 let repaired;
1337 (self, repaired) = self.try_repair_gaps(buffer, resolver, application).await;
1338 if repaired {
1339 self = self.sync_finalized().await;
1340 }
1341 return self.try_dispatch_blocks(application).await;
1342 }
1343
1344 let digest = block.digest();
1345 let finalization = self
1346 .floor
1347 .take_pending_anchor()
1348 .expect("pending floor anchor missing");
1349 let round = finalization.round();
1350 (self.finalized_blocks, self.finalizations_by_height) = try_join!(
1351 self.finalized_blocks
1352 .put(Arc::unwrap_or_clone(block).into())
1353 .map_err(BoxedError::from),
1354 self.finalizations_by_height
1355 .put(height, digest, finalization)
1356 .map_err(BoxedError::from),
1357 )
1358 .expect("failed to store floor anchor");
1359 self = self.sync_finalized().await;
1360
1361 if height > self.tip {
1362 application.report(Update::Tip(round, height, digest));
1363 self.tip = height;
1364 let _ = self.finalized_height.try_set(height.get());
1365 }
1366
1367 let dispatch_floor = height
1370 .previous()
1371 .expect("floor anchor above processed height must have predecessor");
1372 self.update_processed_height(dispatch_floor, resolver);
1373 self = self
1374 .update_processed_round_floor(dispatch_floor, round, buffer, application, resolver)
1375 .await;
1376 self.stream = self
1377 .stream
1378 .sync()
1379 .await
1380 .expect("failed to sync floor metadata");
1381
1382 self.cleared_acks.extend(self.pending_acks.clear());
1385
1386 let commitments = self.take_superseded_ack_commitments();
1389 buffer.retire(Retirement {
1390 round_floor: self.floor.round(),
1391 exact_retirements: commitments,
1392 });
1393
1394 self = self.prune_after_floor(height).await;
1396
1397 let repaired;
1401 (self, repaired) = self.try_repair_gaps(buffer, resolver, application).await;
1402 if repaired {
1403 self = self.sync_finalized().await;
1404 }
1405 self.try_dispatch_blocks(application).await
1406 }
1407
1408 fn take_superseded_ack_commitments(&mut self) -> Vec<V::Commitment> {
1412 let processed_height = self.floor.processed_height();
1413 std::mem::take(&mut self.cleared_acks)
1414 .into_iter()
1415 .filter_map(|(height, commitment)| (height <= processed_height).then_some(commitment))
1416 .collect()
1417 }
1418
1419 async fn handle_deliver<Buf: Buffer<V>>(
1423 mut self: Box<Self>,
1424 message: ResolverDelivery<V>,
1425 delivers: &mut Vec<PendingVerification<P::Scheme, V>>,
1426 buffer: &mut Buf,
1427 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1428 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1429 ) -> Box<Self> {
1430 let ResolverDelivery {
1431 delivery,
1432 mut value,
1433 response,
1434 } = message;
1435 let Delivery {
1436 key, subscribers, ..
1437 } = delivery;
1438 match key {
1439 Key::Block(commitment) => {
1440 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1441 let Ok(block) = V::Block::decode_cfg(value.as_ref(), &block_cfg) else {
1442 response.send_lossy(false);
1443 return self;
1444 };
1445 if V::commitment(&block) != commitment {
1446 response.send_lossy(false);
1447 return self;
1448 }
1449
1450 let block = Arc::new(block);
1454 let anchored;
1455 (self, anchored) = self
1456 .ingest(Arc::clone(&block), buffer, application, resolver)
1457 .await;
1458 if anchored {
1459 response.send_lossy(true);
1460 return self;
1461 }
1462
1463 let height = block.height();
1467 let digest = block.digest();
1468 let annotations = subscribers
1469 .map_into(|(annotation, _)| annotation)
1470 .into_vec();
1471
1472 let finalization = self.cache.get_finalization_for(digest).await;
1476 if let Some(finalization) = &finalization {
1477 self = self
1478 .update_processed_round_floor(
1479 height,
1480 finalization.round(),
1481 buffer,
1482 application,
1483 resolver,
1484 )
1485 .await;
1486 }
1487 if finalization.is_some()
1488 || annotations
1489 .iter()
1490 .any(|annotation| matches!(annotation, Annotation::Finalized(_)))
1491 {
1492 (self, _) = self
1493 .store_finalization(
1494 height,
1495 digest,
1496 Arc::unwrap_or_clone(block),
1497 finalization,
1498 application,
1499 )
1500 .await;
1501 } else if annotations.iter().any(|annotation| {
1502 matches!(
1503 annotation,
1504 Annotation::Certified { height: bound } if height <= *bound
1505 )
1506 }) && height > self.floor.processed_height()
1507 && let Some(bounds) = self.epocher.containing(height)
1508 {
1509 self.cache = self
1510 .cache
1511 .put_certified(
1512 bounds.epoch(),
1513 height,
1514 digest,
1515 Arc::unwrap_or_clone(block).into(),
1516 )
1517 .await;
1518 }
1519 debug!(?digest, %height, "received block");
1520 response.send_lossy(true);
1521 }
1522 Key::Finalized { height } => {
1523 let Some((epoch, scoped)) = self.scoped_for_height(height) else {
1524 debug!(
1525 %height,
1526 floor = %self.floor.processed_height(),
1527 "ignoring stale delivery"
1528 );
1529 response.send_lossy(true);
1530 return self;
1531 };
1532 let certificate_codec_config = scoped.certificate_codec_config();
1533
1534 let Ok(finalization) =
1535 Finalization::read_cfg(&mut value, &certificate_codec_config)
1536 else {
1537 response.send_lossy(false);
1538 return self;
1539 };
1540
1541 if finalization.epoch() != epoch {
1545 response.send_lossy(false);
1546 return self;
1547 }
1548
1549 let Ok(block) =
1552 V::ApplicationBlock::decode_cfg(&mut value, &self.block_codec_config)
1553 else {
1554 response.send_lossy(false);
1555 return self;
1556 };
1557
1558 let commitment = finalization.proposal.payload;
1567 if block.height() != height || block.digest() != V::commitment_to_inner(commitment)
1568 {
1569 response.send_lossy(false);
1570 return self;
1571 }
1572 delivers.push(PendingVerification::Finalized {
1573 scoped,
1574 finalization,
1575 block,
1576 response,
1577 });
1578 }
1579 Key::Notarized { round } => {
1580 let Some(scheme) = self.provider.scheme(round.epoch()) else {
1584 debug!(
1585 ?round,
1586 floor = %self.floor.processed_height(),
1587 "ignoring stale delivery"
1588 );
1589 response.send_lossy(true);
1590 return self;
1591 };
1592 let certificate_codec_config = scheme.certificate_codec_config();
1593 let Ok(notarization) =
1594 Notarization::read_cfg(&mut value, &certificate_codec_config)
1595 else {
1596 response.send_lossy(false);
1597 return self;
1598 };
1599
1600 if notarization.round() != round {
1603 response.send_lossy(false);
1604 return self;
1605 }
1606
1607 let commitment = notarization.proposal.payload;
1610 if !V::check_payload(scheme.as_ref(), commitment) {
1611 response.send_lossy(false);
1612 return self;
1613 }
1614 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1615 let Ok(block) = V::Block::decode_cfg(value, &block_cfg) else {
1616 response.send_lossy(false);
1617 return self;
1618 };
1619
1620 if V::commitment(&block) != notarization.proposal.payload {
1621 response.send_lossy(false);
1622 return self;
1623 }
1624 delivers.push(PendingVerification::Notarized {
1625 scoped: Scoped::scheme(scheme),
1626 notarization,
1627 block,
1628 response,
1629 });
1630 }
1631 }
1632 self
1633 }
1634
1635 #[tracing::instrument(name = "marshal.actor.verify_delivered", level = "info", skip_all, fields(count = delivers.len().traced()))]
1637 async fn verify_delivered<Buf: Buffer<V>>(
1638 mut self: Box<Self>,
1639 mut delivers: Vec<PendingVerification<P::Scheme, V>>,
1640 buffer: &mut Buf,
1641 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1642 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1643 ) -> Box<Self> {
1644 delivers.retain(|item| !item.response_closed());
1645 if delivers.is_empty() {
1646 return self;
1647 }
1648
1649 let certs: Vec<_> = delivers
1651 .iter()
1652 .map(|item| match item {
1653 PendingVerification::Finalized { finalization, .. } => (
1654 Subject::Finalize {
1655 proposal: &finalization.proposal,
1656 },
1657 &finalization.certificate,
1658 ),
1659 PendingVerification::Notarized { notarization, .. } => (
1660 Subject::Notarize {
1661 proposal: ¬arization.proposal,
1662 },
1663 ¬arization.certificate,
1664 ),
1665 })
1666 .collect();
1667
1668 let mut by_epoch: BTreeMap<Epoch, Vec<usize>> = BTreeMap::new();
1670 for (i, item) in delivers.iter().enumerate() {
1671 let epoch = match item {
1672 PendingVerification::Notarized { notarization, .. } => notarization.epoch(),
1673 PendingVerification::Finalized { finalization, .. } => finalization.epoch(),
1674 };
1675 by_epoch.entry(epoch).or_default().push(i);
1676 }
1677
1678 let mut verified = vec![false; delivers.len()];
1681 for indices in by_epoch.values() {
1682 let scoped = delivers[indices[0]].scoped();
1683 let group: Vec<_> = indices.iter().map(|&i| certs[i]).collect();
1684 let results =
1685 verify_certificates(self.context.as_mut(), scoped, &group, &self.strategy);
1686 for (j, &idx) in indices.iter().enumerate() {
1687 verified[idx] = results[j];
1688 }
1689 }
1690
1691 for (index, item) in delivers.drain(..).enumerate() {
1693 if !verified[index] {
1694 match item {
1695 PendingVerification::Finalized { response, .. }
1696 | PendingVerification::Notarized { response, .. } => {
1697 response.send_lossy(false);
1698 }
1699 }
1700 continue;
1701 }
1702 match item {
1703 PendingVerification::Finalized {
1704 finalization,
1705 block,
1706 response,
1707 ..
1708 } => {
1709 response.send_lossy(true);
1711 let block = Arc::new(V::from_application_block(
1712 block,
1713 finalization.proposal.payload,
1714 ));
1715 let round = finalization.round();
1716 let height = block.height();
1717 let digest = block.digest();
1718 debug!(?round, %height, "received finalization");
1719
1720 let anchored;
1723 (self, anchored) = self
1724 .ingest(Arc::clone(&block), buffer, application, resolver)
1725 .await;
1726 if anchored {
1727 continue;
1728 }
1729
1730 (self, _) = self
1731 .update_processed_round_floor(height, round, buffer, application, resolver)
1732 .await
1733 .store_finalization(
1734 height,
1735 digest,
1736 Arc::unwrap_or_clone(block),
1737 Some(finalization),
1738 application,
1739 )
1740 .await;
1741 }
1742 PendingVerification::Notarized {
1743 notarization,
1744 block,
1745 response,
1746 ..
1747 } => {
1748 response.send_lossy(true);
1750 let round = notarization.round();
1751 let commitment = notarization.proposal.payload;
1752 let digest = V::commitment_to_inner(commitment);
1753 debug!(?round, ?digest, "received notarization");
1754
1755 let height = block.height();
1759 let block = Arc::new(block);
1760 let block_sync;
1761 (self.cache, block_sync) = self
1762 .cache
1763 .put_notarized(round, digest, block.as_ref().clone().into())
1764 .await;
1765 let notarization_sync;
1766 (self.cache, notarization_sync) = self
1767 .cache
1768 .put_notarization(round, digest, notarization)
1769 .await;
1770 join(
1771 block_sync.durable(round, "notarized"),
1772 notarization_sync.durable(round, "notarization"),
1773 )
1774 .await;
1775
1776 let anchored;
1779 (self, anchored) = self
1780 .ingest(Arc::clone(&block), buffer, application, resolver)
1781 .await;
1782 if anchored {
1783 continue;
1784 }
1785
1786 if let Some(finalization) = self.cache.get_finalization_for(digest).await {
1791 self = self
1792 .update_processed_round_floor(
1793 height,
1794 finalization.round(),
1795 buffer,
1796 application,
1797 resolver,
1798 )
1799 .await;
1800
1801 (self, _) = self
1804 .store_finalization(
1805 height,
1806 digest,
1807 Arc::unwrap_or_clone(block),
1808 Some(finalization),
1809 application,
1810 )
1811 .await;
1812 }
1813 }
1814 }
1815 }
1816 self
1817 }
1818
1819 fn scoped_for_height(&self, height: Height) -> Option<(Epoch, Scoped<P::Scheme>)> {
1821 let epoch = self.epocher.containing(height)?.epoch();
1822 let scoped = self.provider.scoped(epoch)?;
1823 Some((epoch, scoped))
1824 }
1825
1826 async fn try_dispatch_blocks(
1865 mut self: Box<Self>,
1866 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1867 ) -> Box<Self> {
1868 if self.floor.blocks_progress() {
1870 return self;
1871 }
1872
1873 let barrier = self.dispatch_gate.barrier();
1877 while self.pending_acks.has_capacity() {
1878 let next_height = self
1879 .pending_acks
1880 .next_dispatch_height(self.stream.next_height());
1881 if barrier.is_some_and(|lowest| next_height >= lowest) {
1882 return self;
1883 }
1884 let Some(block) = self.get_finalized_block(next_height).await else {
1885 return self;
1886 };
1887 assert_eq!(
1888 block.height(),
1889 next_height,
1890 "finalized block height mismatch"
1891 );
1892
1893 let (height, commitment) = (block.height(), V::commitment(&block));
1894 let (ack, ack_waiter) = A::handle();
1895 application.report(Update::Block(V::owned_into_inner_shared(block), ack));
1896 self.pending_acks.enqueue(PendingAck {
1897 height,
1898 commitment,
1899 receiver: ack_waiter,
1900 });
1901 }
1902 self
1903 }
1904
1905 #[tracing::instrument(name = "marshal.actor.sync_finalized", level = "info", skip_all)]
1921 async fn sync_finalized(mut self: Box<Self>) -> Box<Self> {
1922 (self.finalized_blocks, self.finalizations_by_height) = try_join!(
1923 self.finalized_blocks.sync().map_err(BoxedError::from),
1924 self.finalizations_by_height
1925 .sync()
1926 .map_err(BoxedError::from),
1927 )
1928 .unwrap_or_else(|e| panic!("failed to sync finalization archives: {e}"));
1929
1930 self.dispatch_gate.clear();
1933 self
1934 }
1935
1936 #[tracing::instrument(name = "marshal.actor.start_finalized_sync", level = "info", skip_all)]
1952 async fn start_finalized_sync(
1953 mut self: Box<Self>,
1954 round: Round,
1955 syncs: &mut Pool<'_, PooledSync>,
1956 ) -> Box<Self> {
1957 let Some(seq) = self.dispatch_gate.adopt() else {
1960 return self;
1961 };
1962
1963 let (blocks, finalizations);
1964 (
1965 (self.finalized_blocks, blocks),
1966 (self.finalizations_by_height, finalizations),
1967 ) = try_join!(
1968 self.finalized_blocks.start_sync().map_err(BoxedError::from),
1969 self.finalizations_by_height
1970 .start_sync()
1971 .map_err(BoxedError::from),
1972 )
1973 .unwrap_or_else(|e| panic!("failed to start finalization archive sync: {e}"));
1974 syncs.push(async move {
1975 let (blocks, finalizations) = join(
1976 blocks.durable(round, "finalized blocks"),
1977 finalizations.durable(round, "finalizations"),
1978 )
1979 .await;
1980 if blocks && finalizations {
1981 PooledSync::Finalized(seq)
1982 } else {
1983 PooledSync::Observed
1986 }
1987 });
1988 self
1989 }
1990
1991 async fn get_finalized_block(&self, height: Height) -> Option<V::Block> {
1995 match self
1996 .finalized_blocks
1997 .get(ArchiveID::Index(height.get()))
1998 .await
1999 {
2000 Ok(stored) => stored.map(|stored| stored.into()),
2001 Err(e) => panic!("failed to get block: {e}"),
2002 }
2003 }
2004
2005 async fn get_finalization_by_height(
2007 &self,
2008 height: Height,
2009 ) -> Option<Finalization<P::Scheme, V::Commitment>> {
2010 match self
2011 .finalizations_by_height
2012 .get(ArchiveID::Index(height.get()))
2013 .await
2014 {
2015 Ok(finalization) => finalization,
2016 Err(e) => panic!("failed to get finalization: {e}"),
2017 }
2018 }
2019
2020 async fn has_finalization_by_height(&self, height: Height) -> bool {
2023 match self.finalizations_by_height.has(height).await {
2024 Ok(has) => has,
2025 Err(e) => panic!("failed to check finalization: {e}"),
2026 }
2027 }
2028
2029 async fn get_info_by_height(
2032 &self,
2033 height: Height,
2034 ) -> Option<(Height, <V::Block as Digestible>::Digest)> {
2035 if let Some(finalization) = self.get_finalization_by_height(height).await {
2036 return Some((
2037 height,
2038 V::commitment_to_inner(finalization.proposal.payload),
2039 ));
2040 }
2041
2042 self.get_finalized_block(height)
2043 .await
2044 .map(|block| (block.height(), block.digest()))
2045 }
2046
2047 async fn store_finalization(
2061 mut self: Box<Self>,
2062 height: Height,
2063 digest: <V::Block as Digestible>::Digest,
2064 block: V::Block,
2065 finalization: Option<Finalization<P::Scheme, V::Commitment>>,
2066 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2067 ) -> (Box<Self>, bool) {
2068 if height <= self.floor.processed_height() {
2072 debug!(
2073 %height,
2074 floor = %self.floor.processed_height(),
2075 ?digest,
2076 "dropping finalization at or below processed height floor"
2077 );
2078 return (self, false);
2079 }
2080
2081 let stored: V::StoredBlock = block.into();
2083 let round = finalization.as_ref().map(|f| f.round());
2084
2085 let finalizations_by_height = self.finalizations_by_height;
2087 (self.finalized_blocks, self.finalizations_by_height) = try_join!(
2088 self.finalized_blocks.put(stored).map_err(BoxedError::from),
2090 async {
2092 let store = if let Some(finalization) = finalization {
2093 finalizations_by_height
2094 .put(height, digest, finalization)
2095 .await
2096 .map_err(BoxedError::from)?
2097 } else {
2098 finalizations_by_height
2099 };
2100 Ok::<_, BoxedError>(store)
2101 }
2102 )
2103 .unwrap_or_else(|e| panic!("failed to finalize: {e}"));
2104
2105 self.dispatch_gate.defer(height);
2108
2109 if let Some(round) = round.filter(|_| height > self.tip) {
2111 application.report(Update::Tip(round, height, digest));
2112 self.tip = height;
2113 let _ = self.finalized_height.try_set(height.get());
2114 }
2115
2116 (self, true)
2117 }
2118
2119 async fn get_latest(&self) -> Option<(Height, <V::Block as Digestible>::Digest, Round)> {
2132 let height = self.finalizations_by_height.last_index()?;
2133 let finalization = self
2134 .get_finalization_by_height(height)
2135 .await
2136 .expect("finalization missing");
2137 Some((
2138 height,
2139 V::commitment_to_inner(finalization.proposal.payload),
2140 finalization.round(),
2141 ))
2142 }
2143
2144 async fn find_block_in_storage(
2148 &self,
2149 digest: <V::Block as Digestible>::Digest,
2150 ) -> Option<V::Block> {
2151 if let Some(block) = self.cache.find_block_matching(digest, |_| true).await {
2153 return Some(block.into());
2154 }
2155 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2157 Ok(stored) => stored.map(|stored| stored.into()),
2158 Err(e) => panic!("failed to get block: {e}"),
2159 }
2160 }
2161
2162 async fn find_block_in_storage_by_commitment(
2164 &self,
2165 commitment: V::Commitment,
2166 ) -> Option<V::Block> {
2167 let digest = V::commitment_to_inner(commitment);
2168 if let Some(block) = self
2169 .cache
2170 .find_block_matching(digest, |stored| V::stored_commitment(stored) == commitment)
2171 .await
2172 {
2173 return Some(block.into());
2174 }
2175
2176 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2177 Ok(Some(stored)) => {
2178 (V::stored_commitment(&stored) == commitment).then(|| stored.into())
2179 }
2180 Ok(None) => None,
2181 Err(e) => panic!("failed to get block: {e}"),
2182 }
2183 }
2184
2185 async fn find_block_by_digest<Buf: Buffer<V>>(
2190 &self,
2191 buffer: &Buf,
2192 digest: <V::Block as Digestible>::Digest,
2193 ) -> Option<Arc<V::Block>> {
2194 if let Some(block) = buffer.find_by_digest(digest).await {
2195 return Some(block);
2196 }
2197 self.find_block_in_storage(digest).await.map(Arc::new)
2198 }
2199
2200 async fn find_block_by_commitment<Buf: Buffer<V>>(
2205 &self,
2206 buffer: &Buf,
2207 commitment: V::Commitment,
2208 ) -> Option<Arc<V::Block>> {
2209 if let Some(block) = buffer.find_by_commitment(commitment).await {
2210 return Some(block);
2211 }
2212 self.find_block_in_storage_by_commitment(commitment)
2213 .await
2214 .map(Arc::new)
2215 }
2216
2217 #[tracing::instrument(name = "marshal.actor.try_repair_gaps", level = "info", skip_all)]
2229 async fn try_repair_gaps<Buf: Buffer<V>>(
2230 mut self: Box<Self>,
2231 buffer: &mut Buf,
2232 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2233 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2234 ) -> (Box<Self>, bool) {
2235 if self.floor.blocks_progress() {
2238 return (self, false);
2239 }
2240
2241 let mut wrote = false;
2242 let start = self.floor.processed_height().next();
2243
2244 if let Some(last_finalized) = self.finalizations_by_height.last_index() {
2247 let have_block = self
2248 .finalized_blocks
2249 .last_index()
2250 .is_some_and(|last| last >= last_finalized);
2251 if last_finalized > self.floor.processed_height() && !have_block {
2252 let finalization = self
2254 .get_finalization_by_height(last_finalized)
2255 .await
2256 .expect("finalization missing");
2257 let commitment = finalization.proposal.payload;
2258 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
2259 let digest = block.digest();
2261 let stored;
2262 (self, stored) = self
2263 .store_finalization(
2264 last_finalized,
2265 digest,
2266 Arc::unwrap_or_clone(block),
2267 Some(finalization),
2268 application,
2269 )
2270 .await;
2271 wrote |= stored;
2272 } else {
2273 self.floor
2275 .fetch_if_permitted(
2276 resolver,
2277 Request::finalized_block_by_height(commitment, last_finalized),
2278 )
2279 .ignore();
2280 }
2281 }
2282 }
2283
2284 'cache_repair: loop {
2286 let (gap_start, Some(gap_end)) = self.finalized_blocks.next_gap(start) else {
2287 return (self, wrote);
2289 };
2290
2291 let Some(cursor) = self.get_finalized_block(gap_end).await else {
2295 panic!("gapped block missing that should exist: {gap_end}");
2296 };
2297 let (mut height, mut parent_digest, mut parent_commitment) = (
2298 cursor.height(),
2299 cursor.parent(),
2300 V::parent_commitment(&cursor),
2301 );
2302
2303 let gap_start = gap_start.map(Height::next).unwrap_or(start);
2307
2308 while height > gap_start {
2310 if let Some(block) = self
2311 .find_block_by_commitment(buffer, parent_commitment)
2312 .await
2313 {
2314 let finalization = self.cache.get_finalization_for(parent_digest).await;
2315 let next = (block.height(), block.parent(), V::parent_commitment(&block));
2316 let stored;
2317 (self, stored) = self
2318 .store_finalization(
2319 next.0,
2320 parent_digest,
2321 Arc::unwrap_or_clone(block),
2322 finalization,
2323 application,
2324 )
2325 .await;
2326 wrote |= stored;
2327 debug!(height = %next.0, "repaired block");
2328 (height, parent_digest, parent_commitment) = next;
2329 } else {
2330 let parent_height = height
2336 .previous()
2337 .expect("cursor above gap start has a parent");
2338 self.floor
2339 .fetch_if_permitted(
2340 resolver,
2341 Request::finalized_block_by_height(parent_commitment, parent_height),
2342 )
2343 .ignore();
2344 break 'cache_repair;
2345 }
2346 }
2347 }
2348
2349 let missing_items = self
2355 .finalized_blocks
2356 .missing_items(start, self.max_repair.get());
2357 let requests: Vec<_> = missing_items.into_iter().map(Request::finalized).collect();
2358 if !requests.is_empty() {
2359 self.floor
2360 .fetch_all_if_permitted(resolver, requests)
2361 .ignore();
2362 }
2363 (self, wrote)
2364 }
2365
2366 fn update_processed_height(
2369 &mut self,
2370 height: Height,
2371 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2372 ) {
2373 self.stream.acknowledge(height);
2374 self.floor.set_processed_height(height);
2375 let _ = self
2376 .processed_height
2377 .try_set(self.floor.processed_height().get());
2378
2379 resolver.retain(handler::above_height_floor::<V::Commitment>(height));
2381 }
2382
2383 async fn latest_processed_round(
2388 finalizations_by_height: &FC,
2389 finalized_blocks: &FB,
2390 height: Option<Height>,
2391 ) -> Round {
2392 let processed_round = height.and_then(|height| {
2393 finalizations_by_height
2394 .ranges_from(Height::zero())
2395 .filter_map(|(start, end)| (start <= height).then_some(end.min(height)))
2396 .max()
2397 });
2398 let processed_round = match processed_round {
2399 Some(finalization_height) => match finalizations_by_height
2400 .get(ArchiveID::Index(finalization_height.get()))
2401 .await
2402 {
2403 Ok(Some(finalization)) => finalization.round(),
2404 Ok(None) => panic!("processed finalization missing from stored range"),
2405 Err(err) => panic!("failed to get processed finalization: {err}"),
2406 },
2407 None => Round::zero(),
2408 };
2409
2410 let successor = match height {
2411 Some(height) if height.get() == u64::MAX => return processed_round,
2412 Some(height) => height.next(),
2413 None => Height::zero(),
2414 };
2415 let (block, finalization) = join(
2416 finalized_blocks.get(ArchiveID::Index(successor.get())),
2417 finalizations_by_height.get(ArchiveID::Index(successor.get())),
2418 )
2419 .await;
2420 let block = block.unwrap_or_else(|err| panic!("failed to get successor block: {err}"));
2421 let finalization = finalization
2422 .unwrap_or_else(|err| panic!("failed to get successor finalization: {err}"));
2423 let (Some(block), Some(finalization)) = (block, finalization) else {
2424 return processed_round;
2425 };
2426 assert!(
2427 V::stored_commitment(&block) == finalization.proposal.payload,
2428 "successor block does not match stored finalization"
2429 );
2430 processed_round.max(finalization.round())
2431 }
2432
2433 async fn update_processed_round<Buf: Buffer<V>>(
2435 self: Box<Self>,
2436 height: Height,
2437 buffer: &mut Buf,
2438 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2439 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2440 ) -> Box<Self> {
2441 let Some(finalization) = self.get_finalization_by_height(height).await else {
2442 return self;
2443 };
2444 self.update_processed_round_floor(
2445 height,
2446 finalization.round(),
2447 buffer,
2448 application,
2449 resolver,
2450 )
2451 .await
2452 }
2453
2454 async fn update_processed_round_floor<Buf: Buffer<V>>(
2460 mut self: Box<Self>,
2461 height: Height,
2462 round: Round,
2463 buffer: &mut Buf,
2464 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2465 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2466 ) -> Box<Self> {
2467 let processed_round = self.floor.round();
2468 if height > self.floor.processed_height() || round <= processed_round {
2469 return self;
2470 }
2471
2472 self.floor.set_processed_round(round);
2473
2474 let prune_round = Round::new(
2477 processed_round.epoch(),
2478 processed_round.view().saturating_sub(self.view_retention),
2479 );
2480 self.cache = self.cache.prune_by_view(prune_round).await;
2481
2482 resolver.retain(handler::above_round_floor::<V::Commitment>(round));
2484
2485 if self.floor.take_superseded_anchor(round).is_none() {
2488 return self;
2489 }
2490 let commitments = self.take_superseded_ack_commitments();
2491 buffer.retire(Retirement {
2492 round_floor: round,
2493 exact_retirements: commitments,
2494 });
2495 let repaired;
2496 (self, repaired) = self.try_repair_gaps(buffer, resolver, application).await;
2497 if repaired {
2498 self = self.sync_finalized().await;
2499 }
2500 self.try_dispatch_blocks(application).await
2501 }
2502
2503 async fn prune_finalized_archives(mut self: Box<Self>, height: Height) -> Box<Self> {
2505 (self.finalized_blocks, self.finalizations_by_height) = try_join!(
2507 self.finalized_blocks
2508 .prune(height)
2509 .map_err(BoxedError::from),
2510 self.finalizations_by_height
2511 .prune(height)
2512 .map_err(BoxedError::from),
2513 )
2514 .unwrap_or_else(|e| panic!("failed to prune finalized archives: {e}"));
2515 self
2516 }
2517
2518 async fn prune_after_floor(mut self: Box<Self>, height: Height) -> Box<Self> {
2520 (
2521 self.cache,
2522 self.finalized_blocks,
2523 self.finalizations_by_height,
2524 ) = try_join!(
2525 self.cache.prune_by_height(height).map(Ok::<_, BoxedError>),
2526 self.finalized_blocks
2527 .prune(height)
2528 .map_err(BoxedError::from),
2529 self.finalizations_by_height
2530 .prune(height)
2531 .map_err(BoxedError::from),
2532 )
2533 .unwrap_or_else(|e| panic!("failed to prune data below floor: {e}"));
2534 self
2535 }
2536}