1use super::{
2 acks::{PendingAck, PendingAcks},
3 cache,
4 delivery::PendingVerification,
5 durability::{DispatchGate, Durable as _},
6 floor::Floor,
7 mailbox::{CommitmentFallback, Mailbox, Message},
8 stream::Stream,
9 subscriptions::{Key as SubscriptionKey, KeyFor as SubscriptionKeyFor, Subscriptions},
10 variant::NoBuffer,
11 Buffer, Variant,
12};
13use crate::{
14 marshal::{
15 resolver::handler::{self, Annotation, Key, Request},
16 store::{Blocks, Certificates},
17 Config, Identifier as BlockID, Start, Update,
18 },
19 simplex::{
20 scheme::Scheme,
21 types::{verify_certificates, Finalization, Notarization, Subject},
22 },
23 types::{Epoch, Epocher, Height, Round, ViewDelta},
24 Block, Epochable, Heightable, Reporter,
25};
26use bytes::Bytes;
27use commonware_actor::mailbox;
28use commonware_codec::{Decode, Encode, Read};
29use commonware_cryptography::{
30 certificate::{Provider, Verifier},
31 Digestible,
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 spawn_cell,
39 telemetry::{
40 metrics::{Gauge, GaugeExt, MetricsExt as _},
41 traces::TracedExt as _,
42 },
43 BufferPooler, Clock, ContextCell, Handle, Metrics, Spawner, Storage,
44};
45use commonware_storage::archive::Identifier as ArchiveID;
46use commonware_utils::{
47 acknowledgement::Exact,
48 channel::{fallible::OneshotExt, oneshot},
49 futures::{AbortablePool, Pool},
50 Acknowledgement, BoxedError,
51};
52use futures::{
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::{debug, info_span, warn, Instrument as _, Span};
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_timeout: ViewDelta,
123 max_repair: NonZeroUsize,
125 block_codec_config: <V::ApplicationBlock as Read>::Cfg,
127 strategy: T,
129
130 floor: Floor<P::Scheme, V::Commitment>,
133 stream: Stream<E>,
135 pending_acks: PendingAcks<V, A>,
137 tip: Height,
139 block_subscriptions: Subscriptions<V>,
141 dispatch_gate: DispatchGate,
144
145 cache: cache::Manager<E, V, P::Scheme>,
148 finalizations_by_height: FC,
150 finalized_blocks: FB,
152
153 finalized_height: Gauge,
156 processed_height: Gauge,
158}
159
160impl<E, V, P, FC, FB, ES, T, A> Actor<E, V, P, FC, FB, ES, T, A>
161where
162 E: BufferPooler + CryptoRng + Spawner + Metrics + Clock + Storage,
163 V: Variant,
164 P: Provider<Scope = Epoch, Scheme: Scheme<V::Commitment>>,
165 FC: Certificates<
166 BlockDigest = <V::Block as Digestible>::Digest,
167 Commitment = V::Commitment,
168 Scheme = P::Scheme,
169 >,
170 FB: Blocks<Block = V::StoredBlock>,
171 ES: Epocher,
172 T: Strategy,
173 A: Acknowledgement,
174{
175 #[boxed]
177 pub async fn init(
178 context: E,
179 finalizations_by_height: FC,
180 mut finalized_blocks: FB,
181 config: Config<P, ES, T, V::ApplicationBlock, V::Block, V::Commitment>,
182 ) -> (Self, Mailbox<P::Scheme, V>, Option<Height>) {
183 let prunable_config = cache::Config {
185 partition_prefix: format!("{}-cache", config.partition_prefix),
186 prunable_items_per_section: config.prunable_items_per_section,
187 replay_buffer: config.replay_buffer,
188 key_write_buffer: config.key_write_buffer,
189 value_write_buffer: config.value_write_buffer,
190 key_page_cache: config.page_cache.clone(),
191 };
192 let cache = cache::Manager::init(
193 context.child("cache"),
194 prunable_config,
195 config.block_codec_config.clone(),
196 )
197 .await;
198
199 let application_metadata_partition =
201 format!("{}-application-metadata", config.partition_prefix);
202 let stream = Stream::new(context.child("stream"), &application_metadata_partition).await;
203 let last_processed_height = stream.processed_height();
204
205 let pending_floor_anchor = match config.start {
208 Start::Genesis(anchor) => {
209 assert_eq!(
210 anchor.height(),
211 Height::zero(),
212 "genesis anchor must be at height zero"
213 );
214 Self::ensure_genesis_anchor(&mut finalized_blocks, anchor, last_processed_height)
215 .await;
216 None
217 }
218 Start::Floor(finalization) => Some(finalization),
219 };
220 let last_processed_round =
221 Self::latest_processed_round(&finalizations_by_height, last_processed_height).await;
222
223 let finalized_height = context.gauge("finalized_height", "Finalized height of application");
225 let processed_height = context.gauge("processed_height", "Processed height of application");
226 if let Some(last_processed_height) = last_processed_height {
227 let _ = processed_height.try_set(last_processed_height.get());
228 }
229 let floor = pending_floor_anchor.map_or_else(
230 || Floor::resolved(last_processed_height, last_processed_round),
231 |finalization| {
232 Floor::awaiting_anchor(last_processed_height, last_processed_round, finalization)
233 },
234 );
235
236 let (sender, mailbox) = mailbox::new(context.child("mailbox"), config.mailbox_size);
238 (
239 Self {
240 context: ContextCell::new(context),
241 mailbox,
242 provider: config.provider,
243 epocher: config.epocher,
244 view_retention_timeout: config.view_retention_timeout,
245 max_repair: config.max_repair,
246 block_codec_config: config.block_codec_config,
247 strategy: config.strategy,
248 floor,
249 stream,
250 pending_acks: PendingAcks::new(config.max_pending_acks.get()),
251 tip: Height::zero(),
252 block_subscriptions: Subscriptions::new(),
253 dispatch_gate: DispatchGate::default(),
254 cache,
255 finalizations_by_height,
256 finalized_blocks,
257 finalized_height,
258 processed_height,
259 },
260 Mailbox::new(sender),
261 last_processed_height,
262 )
263 }
264
265 async fn ensure_genesis_anchor(
266 finalized_blocks: &mut FB,
267 anchor: V::Block,
268 last_processed_height: Option<Height>,
269 ) {
270 let anchor_height = anchor.height();
271 let anchor_commitment = V::commitment(&anchor);
272 match finalized_blocks
273 .get(ArchiveID::Index(anchor_height.get()))
274 .await
275 {
276 Ok(Some(stored)) => {
277 let stored: V::Block = stored.into();
278 assert_eq!(
279 stored.height(),
280 anchor_height,
281 "stored genesis block height mismatch"
282 );
283 assert!(
284 V::commitment(&stored) == anchor_commitment,
285 "stored genesis block does not match configured anchor"
286 );
287 }
288 Ok(None) => {
289 if let Some(existing) =
290 last_processed_height.filter(|height| anchor_height < *height)
291 {
292 warn!(
293 height = %anchor_height,
294 %existing,
295 "ignoring stale anchor"
296 );
297 return;
298 }
299
300 finalized_blocks
301 .put(anchor.into())
302 .await
303 .expect("failed to store startup anchor");
304 finalized_blocks
305 .sync()
306 .await
307 .expect("failed to sync startup anchor");
308 debug!(height = %anchor_height, "stored genesis block");
309 }
310 Err(err) => panic!("failed to check startup anchor: {err}"),
311 }
312 }
313
314 pub fn start<R, Buf>(
316 mut self,
317 application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
318 buffer: Buf,
319 resolver: (handler::Receiver<V::Commitment>, R),
320 ) -> Handle<()>
321 where
322 R: TargetedResolver<
323 Key = ResolverRequestFor<V>,
324 Subscriber = Annotation,
325 PublicKey = <P::Scheme as Verifier>::PublicKey,
326 >,
327 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
328 {
329 spawn_cell!(self.context, self.run(application, buffer, resolver))
330 }
331
332 pub fn start_unbuffered<R>(
334 self,
335 application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
336 resolver: (handler::Receiver<V::Commitment>, R),
337 ) -> Handle<()>
338 where
339 R: TargetedResolver<
340 Key = ResolverRequestFor<V>,
341 Subscriber = Annotation,
342 PublicKey = <P::Scheme as Verifier>::PublicKey,
343 >,
344 {
345 self.start(
346 application,
347 NoBuffer::<<P::Scheme as Verifier>::PublicKey>::new(),
348 resolver,
349 )
350 }
351
352 async fn run<R, Buf>(
354 mut self,
355 mut application: impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
356 mut buffer: Buf,
357 (mut resolver_rx, mut resolver): (handler::Receiver<V::Commitment>, R),
358 ) where
359 R: TargetedResolver<
360 Key = ResolverRequestFor<V>,
361 Subscriber = Annotation,
362 PublicKey = <P::Scheme as Verifier>::PublicKey,
363 >,
364 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
365 {
366 let mut waiters = AbortablePool::<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>::default();
368
369 let mut syncs = Pool::<PooledSync>::default();
375
376 async {
381 let tip = self.get_latest().await;
383 if let Some((height, digest, round)) = tip {
384 application.report(Update::Tip(round, height, digest));
385 self.tip = height;
386 let _ = self.finalized_height.try_set(height.get());
387 }
388
389 self.cache.load_persisted_epochs().await;
392
393 if let Some(finalization) = self.floor.take_pending_anchor() {
396 self.install_floor(
397 finalization,
398 false,
399 &mut resolver,
400 &mut buffer,
401 &mut application,
402 )
403 .await;
404 }
405
406 if self
408 .try_repair_gaps(&mut buffer, &mut resolver, &mut application)
409 .await
410 {
411 self.sync_finalized().await;
412 }
413
414 self.try_dispatch_blocks(&mut application).await;
416 }
417 .instrument(info_span!("marshal.actor.start"))
418 .await;
419
420 select_loop! {
421 self.context,
422 on_start => {
423 self.block_subscriptions.retain_open();
425 },
426 on_stopped => {
427 debug!("context shutdown, stopping marshal");
428 },
429 sync = syncs.next_completed() => {
434 if let PooledSync::Finalized(seq) = sync {
435 self.dispatch_gate.release(seq);
436 self.try_dispatch_blocks(&mut application).await;
437 }
438 },
439 Ok(completion) = waiters.next_completed() else continue => match completion {
441 Ok(block) => {
442 self.ingest(block, &mut buffer, &mut application, &mut resolver)
443 .await;
444 }
445 Err(key) => {
446 match key {
447 SubscriptionKey::Digest(digest) => {
448 debug!(
449 ?digest,
450 "buffer subscription closed, canceling local subscribers"
451 );
452 }
453 SubscriptionKey::Commitment(commitment) => {
454 debug!(
455 ?commitment,
456 "buffer subscription closed, canceling local subscribers"
457 );
458 }
459 }
460 self.block_subscriptions.remove(&key);
461 }
462 },
463 result = self.pending_acks.current() => {
465 if let Err((height, e)) = self
466 .handle_ack(result, &mut application, &mut buffer, &mut resolver)
467 .await
468 {
469 debug!(
470 ?e,
471 %height,
472 "application acknowledgement dropped, stopping marshal"
473 );
474 return;
475 }
476 },
477 Some(message) = self.mailbox.recv() else {
479 debug!("mailbox closed, shutting down");
480 break;
481 } => {
482 let span = info_span!(
483 parent: message.span(),
484 "marshal.actor.process",
485 operation = message.name(),
486 );
487 self.handle_mailbox_message(
488 message,
489 &mut resolver,
490 &mut waiters,
491 &mut syncs,
492 &mut buffer,
493 &mut application,
494 )
495 .instrument(span)
496 .await;
497 },
498 Some(message) = resolver_rx.recv() else {
500 debug!("handler closed, shutting down");
501 return;
502 } => {
503 self.handle_resolver_message(
504 message,
505 &mut resolver_rx,
506 &mut resolver,
507 &mut syncs,
508 &mut buffer,
509 &mut application,
510 )
511 .await;
512 },
513 }
514 }
515
516 async fn handle_ack<Buf, R>(
519 &mut self,
520 result: <A::Waiter as Future>::Output,
521 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
522 buffer: &mut Buf,
523 resolver: &mut R,
524 ) -> Result<(), (Height, A::Error)>
525 where
526 Buf: Buffer<V>,
527 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
528 {
529 let mut pending = Some(self.pending_acks.complete_current(result));
531 let last_acked_commitment = loop {
532 let (height, commitment, result) = pending.take().expect("pending ack must exist");
533 match result {
534 Ok(()) => {
535 self.update_processed_height(height, resolver);
538 self.update_processed_round(height, resolver).await;
539 }
540 Err(e) => return Err((height, e)),
541 }
542
543 match self.pending_acks.pop_ready() {
546 Some(next) => pending = Some(next),
547 None => break commitment,
548 }
549 };
550
551 self.stream
553 .sync()
554 .await
555 .expect("failed to sync application progress");
556
557 buffer.finalized(last_acked_commitment);
560
561 self.try_dispatch_blocks(application).await;
563
564 Ok(())
565 }
566
567 async fn handle_mailbox_message<Buf, R>(
569 &mut self,
570 message: Message<P::Scheme, V>,
571 resolver: &mut R,
572 waiters: &mut AbortablePool<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
573 syncs: &mut Pool<PooledSync>,
574 buffer: &mut Buf,
575 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
576 ) where
577 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
578 R: TargetedResolver<
579 Key = ResolverRequestFor<V>,
580 Subscriber = Annotation,
581 PublicKey = <P::Scheme as Verifier>::PublicKey,
582 >,
583 {
584 if message.response_closed() {
585 return;
586 }
587
588 match message {
589 Message::GetInfo {
590 identifier,
591 response,
592 ..
593 } => {
594 let info = match identifier {
595 BlockID::Digest(digest) => self
599 .finalized_blocks
600 .get(ArchiveID::Key(&digest))
601 .await
602 .ok()
603 .flatten()
604 .map(|b| (b.height(), digest)),
605 BlockID::Height(height) => self.get_info_by_height(height).await,
606 BlockID::Latest => self.get_latest().await.map(|(h, d, _)| (h, d)),
607 };
608 response.send_lossy(info);
609 }
610 Message::GetVerified {
611 round, response, ..
612 } => {
613 let block = self.cache.get_verified(round).await.map(Into::into);
614 response.send_lossy(block);
615 }
616 Message::Forward {
617 round,
618 commitment,
619 recipients,
620 ..
621 } => {
622 if matches!(&recipients, Recipients::Some(peers) if peers.is_empty()) {
623 return;
624 }
625 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
626 debug!(?commitment, "block not found for forwarding");
627 return;
628 };
629 buffer.send(round, block, recipients);
630 }
631 Message::Proposed {
632 round,
633 block,
634 recipients,
635 ack,
636 ..
637 } => {
638 buffer.send(round, Arc::clone(&block), recipients);
648 self.persist_verified(round, block, ack, buffer, application, resolver)
649 .await;
650 }
651 Message::Verified {
652 round, block, ack, ..
653 } => {
654 self.persist_verified(round, block, ack, buffer, application, resolver)
655 .await;
656 }
657 Message::Certified {
658 round, block, ack, ..
659 } => {
660 self.ingest(Arc::clone(&block), buffer, application, resolver)
661 .await;
662 let digest = block.digest();
663
664 let block_sync = if self.cache.has_verified(round, &digest).await {
672 debug!(?round, "certified block covered by verified write");
673 self.cache.start_sync_verified(round).await
674 } else {
675 self.cache
676 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
677 .await
678 };
679
680 let notarization_sync = self.cache.start_sync_notarizations(round).await;
684 let handle = Handle::from_future(async move {
685 let (notarization, block) = join(notarization_sync, block_sync).await;
686 notarization.and(block)
687 });
688 ack.send_lossy(handle);
689 }
690 Message::Notarization { notarization, .. } => {
691 let round = notarization.round();
692 let commitment = notarization.proposal.payload;
693 let digest = V::commitment_to_inner(commitment);
694
695 let handle = self
702 .cache
703 .put_notarization(round, digest, notarization)
704 .await;
705 syncs.push(async move {
706 handle.durable(round, "notarization").await;
707 PooledSync::Observed
708 });
709
710 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
714 self.ingest(Arc::clone(&block), buffer, application, resolver)
715 .await;
716 if self.cache.has_verified(round, &digest).await {
717 debug!(?round, "notarized block covered by verified write");
718 } else {
719 let handle = self
720 .cache
721 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
722 .await;
723 syncs.push(async move {
724 handle.durable(round, "notarized").await;
725 PooledSync::Observed
726 });
727 }
728 } else {
729 debug!(?round, "notarized block unavailable locally");
730 }
731 }
732 Message::Finalization { finalization, .. } => {
733 let round = finalization.round();
734 let commitment = finalization.proposal.payload;
735 let digest = V::commitment_to_inner(commitment);
736
737 self.cache
739 .put_finalization(round, digest, finalization.clone())
740 .await;
741
742 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
744 if self
747 .ingest(Arc::clone(&block), buffer, application, resolver)
748 .await
749 {
750 return;
751 }
752
753 let height = block.height();
754 self.update_processed_round_floor(height, round, resolver)
755 .await;
756 if self
757 .store_finalization(
758 height,
759 digest,
760 Arc::unwrap_or_clone(block),
761 Some(finalization),
762 application,
763 )
764 .await
765 {
766 self.try_repair_gaps(buffer, resolver, application).await;
769 self.start_finalized_sync(round, syncs).await;
770 debug!(?round, %height, "finalized block stored");
771 }
772 } else {
773 debug!(?round, ?commitment, "finalized block missing");
776 self.floor
777 .fetch_if_permitted(
778 resolver,
779 Request::finalized_block_by_round(commitment, round),
780 )
781 .ignore();
782 }
783 }
784 Message::GetBlock {
785 identifier,
786 response,
787 ..
788 } => match identifier {
789 BlockID::Digest(digest) => {
790 let result = self
791 .find_block_by_digest(buffer, digest)
792 .await
793 .map(Arc::unwrap_or_clone);
794 response.send_lossy(result);
795 }
796 BlockID::Height(height) => {
797 let result = self.get_finalized_block(height).await;
798 response.send_lossy(result);
799 }
800 BlockID::Latest => {
801 let block = match self.get_latest().await {
802 Some((_, digest, _)) => self.find_block_by_digest(buffer, digest).await,
803 None => None,
804 }
805 .map(Arc::unwrap_or_clone);
806 response.send_lossy(block);
807 }
808 },
809 Message::GetFinalization {
810 height, response, ..
811 } => {
812 let finalization = self.get_finalization_by_height(height).await;
813 response.send_lossy(finalization);
814 }
815 Message::GetProcessedHeight { response, .. } => {
816 response.send_lossy(self.stream.processed_height());
817 }
818 Message::HintFinalized {
819 height, targets, ..
820 } => {
821 if self.has_finalization_by_height(height).await {
823 return;
824 }
825
826 self.floor
827 .fetch_targeted_if_permitted(resolver, Request::finalized(height), targets)
828 .ignore();
829 }
830 Message::SubscribeByDigest {
831 span,
832 digest,
833 fallback,
834 response,
835 } => {
836 self.handle_subscribe(
837 span,
838 fallback.into(),
839 SubscriptionKey::Digest(digest),
840 response,
841 resolver,
842 waiters,
843 buffer,
844 )
845 .await;
846 }
847 Message::SubscribeByCommitment {
848 span,
849 commitment,
850 fallback,
851 response,
852 } => {
853 self.handle_subscribe(
854 span,
855 fallback,
856 SubscriptionKey::Commitment(commitment),
857 response,
858 resolver,
859 waiters,
860 buffer,
861 )
862 .await;
863 }
864 Message::HintNotarized {
865 round, commitment, ..
866 } => {
867 if self
868 .find_block_by_commitment(buffer, commitment)
869 .await
870 .is_none()
871 {
872 self.floor
873 .fetch_if_permitted(resolver, Request::notarized(round))
874 .ignore();
875 }
876 }
877 Message::SetFloor { finalization, .. } => {
878 self.install_floor(finalization, true, resolver, buffer, application)
879 .await;
880 }
881 Message::Prune { height, .. } => {
882 if height > self.floor.processed_height() {
884 warn!(%height, floor = %self.floor.processed_height(), "prune height above floor, ignoring");
885 return;
886 }
887
888 self.prune_finalized_archives(height)
889 .await
890 .expect("failed to prune finalized archives");
891
892 }
896 }
897 }
898
899 async fn handle_resolver_message<Buf, R>(
902 &mut self,
903 message: handler::Message<V::Commitment>,
904 resolver_rx: &mut handler::Receiver<V::Commitment>,
905 resolver: &mut R,
906 syncs: &mut Pool<PooledSync>,
907 buffer: &mut Buf,
908 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
909 ) where
910 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
911 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
912 {
913 let mut handled = false;
914 let mut produces = Vec::new();
915 let mut delivers = Vec::new();
916
917 for msg in std::iter::once(message)
921 .chain(std::iter::from_fn(|| resolver_rx.try_recv().ok()))
922 .take(self.max_repair.get())
923 {
924 if msg.response_closed() {
925 continue;
926 }
927 handled = true;
928
929 match msg {
930 handler::Message::Produce { key, response } => {
931 produces.push((key, response));
932 }
933 handler::Message::Deliver {
934 delivery,
935 value,
936 response,
937 } => {
938 let span = info_span!(
939 parent: &delivery.subscribers.first().1,
940 "marshal.resolver.deliver",
941 key = %delivery.key
942 );
943 for (_, subscriber_span) in delivery.subscribers.iter().skip(1) {
944 span.follows_from(subscriber_span.id());
945 }
946 self.handle_deliver(
947 ResolverDelivery {
948 delivery,
949 value,
950 response,
951 },
952 &mut delivers,
953 buffer,
954 application,
955 resolver,
956 )
957 .instrument(span)
958 .await;
959 }
960 }
961 }
962 if !handled {
963 return;
964 }
965
966 self.verify_delivered(delivers, buffer, application, resolver)
968 .await;
969
970 self.try_repair_gaps(buffer, resolver, application).await;
973
974 self.start_finalized_sync(self.floor.processed_round(), syncs)
979 .await;
980
981 join_all(
983 produces
984 .into_iter()
985 .filter(|(_, response)| !response.is_closed())
986 .map(|(key, response)| self.handle_produce(key, response, buffer)),
987 )
988 .await;
989 }
990
991 #[tracing::instrument(name = "marshal.resolver.produce", level = "debug", skip_all, fields(key = %key))]
993 async fn handle_produce<Buf: Buffer<V>>(
994 &self,
995 key: ResolverRequestFor<V>,
996 response: oneshot::Sender<Bytes>,
997 buffer: &Buf,
998 ) {
999 match key {
1000 Key::Block(commitment) => {
1001 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
1002 debug!(?commitment, "block missing on request");
1003 return;
1004 };
1005 response.send_lossy(block.encode());
1006 }
1007 Key::Finalized { height } => {
1008 let Some(finalization) = self.get_finalization_by_height(height).await else {
1009 debug!(%height, "finalization missing on request");
1010 return;
1011 };
1012 let Some(block) = self.get_finalized_block(height).await else {
1013 debug!(%height, "finalized block missing on request");
1014 return;
1015 };
1016 response.send_lossy((finalization, V::into_inner(block)).encode());
1017 }
1018 Key::Notarized { round } => {
1019 let Some(notarization) = self.cache.get_notarization(round).await else {
1020 debug!(?round, "notarization missing on request");
1021 return;
1022 };
1023 let commitment = notarization.proposal.payload;
1024 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
1025 debug!(?commitment, "block missing on request");
1026 return;
1027 };
1028 response.send_lossy((notarization, block).encode());
1029 }
1030 }
1031 }
1032
1033 #[allow(clippy::too_many_arguments)]
1035 async fn handle_subscribe<Buf: Buffer<V>>(
1036 &mut self,
1037 span: Span,
1038 fallback: CommitmentFallback,
1039 key: SubscriptionKeyFor<V>,
1040 response: oneshot::Sender<Arc<V::Block>>,
1041 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1042 waiters: &mut AbortablePool<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
1043 buffer: &mut Buf,
1044 ) {
1045 let digest = match key {
1046 SubscriptionKey::Digest(digest) => digest,
1047 SubscriptionKey::Commitment(commitment) => V::commitment_to_inner(commitment),
1048 };
1049
1050 let block = match key {
1051 SubscriptionKey::Digest(digest) => self.find_block_by_digest(buffer, digest).await,
1052 SubscriptionKey::Commitment(commitment) => {
1053 self.find_block_by_commitment(buffer, commitment).await
1054 }
1055 };
1056 if let Some(block) = block {
1057 response.send_lossy(block);
1058 return;
1059 }
1060
1061 match fallback {
1068 CommitmentFallback::FetchByRound { round } => {
1069 if self
1075 .floor
1076 .fetch_if_permitted(resolver, Request::notarized(round))
1077 .denied()
1078 {
1079 return;
1080 }
1081 debug!(?round, ?digest, "requested block missing");
1082 }
1083 CommitmentFallback::FetchByCommitment { height } => {
1084 let commitment = match key {
1085 SubscriptionKey::Commitment(commitment) => commitment,
1086 SubscriptionKey::Digest(_) => {
1087 unreachable!("digest subscriptions cannot request commitment fallback")
1088 }
1089 };
1090
1091 if self
1094 .floor
1095 .fetch_if_permitted(resolver, Request::certified_block(commitment, height))
1096 .denied()
1097 {
1098 return;
1099 }
1100 debug!(%height, ?commitment, ?digest, "requested certified ancestry block missing");
1101 }
1102 CommitmentFallback::Wait => {}
1103 }
1104
1105 let round = match fallback {
1106 CommitmentFallback::FetchByRound { round } => Some(round),
1107 CommitmentFallback::Wait | CommitmentFallback::FetchByCommitment { .. } => None,
1108 };
1109
1110 match key {
1112 SubscriptionKey::Digest(digest) => {
1113 debug!(?round, ?digest, "registering subscriber");
1114 }
1115 SubscriptionKey::Commitment(commitment) => {
1116 debug!(?round, ?commitment, ?digest, "registering subscriber");
1117 }
1118 }
1119 self.block_subscriptions
1120 .insert(span, key, response, waiters, buffer);
1121 }
1122
1123 async fn install_floor<Buf, R>(
1125 &mut self,
1126 finalization: Finalization<P::Scheme, V::Commitment>,
1127 skip_if_superseded: bool,
1128 resolver: &mut R,
1129 buffer: &mut Buf,
1130 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1131 ) where
1132 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
1133 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1134 {
1135 let round = finalization.round();
1136 if round <= self.floor.processed_round() {
1137 warn!(
1138 ?round,
1139 floor = ?self.floor.processed_round(),
1140 "floor not updated, below existing round floor"
1141 );
1142 return;
1143 }
1144
1145 let Some(scoped) = self.provider.scoped(finalization.epoch()) else {
1146 panic!("floor finalization epoch unavailable");
1147 };
1148 assert!(
1149 finalization.verify(self.context.as_mut(), &scoped, &self.strategy),
1150 "floor finalization must verify"
1151 );
1152
1153 let commitment = finalization.proposal.payload;
1154 let digest = V::commitment_to_inner(commitment);
1155 self.cache
1156 .put_finalization(round, digest, finalization.clone())
1157 .await;
1158
1159 if skip_if_superseded && self.floor.has_pending_anchor_at_or_after(round) {
1162 return;
1163 }
1164
1165 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
1166 self.floor.await_anchor(finalization);
1167 assert!(self.ingest(block, buffer, application, resolver).await);
1168 return;
1169 }
1170
1171 self.pending_acks.clear();
1174
1175 debug!(?round, ?commitment, "starting fetch for floor block");
1176 self.floor.await_anchor(finalization);
1177 self.floor
1178 .fetch_if_permitted(
1179 resolver,
1180 Request::finalized_block_by_round(commitment, round),
1181 )
1182 .ignore();
1183 }
1184
1185 async fn persist_verified<Buf: Buffer<V>>(
1193 &mut self,
1194 round: Round,
1195 block: Arc<V::Block>,
1196 ack: oneshot::Sender<Handle<()>>,
1197 buffer: &mut Buf,
1198 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1199 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1200 ) {
1201 self.ingest(Arc::clone(&block), buffer, application, resolver)
1202 .await;
1203 let digest = block.digest();
1204 let handle = self
1205 .cache
1206 .put_verified(round, digest, Arc::unwrap_or_clone(block).into())
1207 .await;
1208 ack.send_lossy(handle);
1209 }
1210
1211 async fn ingest<Buf: Buffer<V>>(
1225 &mut self,
1226 block: Arc<V::Block>,
1227 buffer: &mut Buf,
1228 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1229 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1230 ) -> bool {
1231 self.block_subscriptions.notify(Arc::clone(&block));
1232
1233 if !self.floor.matches_pending_anchor(V::commitment(&block)) {
1234 return false;
1235 }
1236
1237 self.apply_pending_floor(block, buffer, application, resolver)
1238 .await;
1239 true
1240 }
1241
1242 async fn apply_pending_floor<Buf: Buffer<V>>(
1248 &mut self,
1249 block: Arc<V::Block>,
1250 buffer: &mut Buf,
1251 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1252 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1253 ) {
1254 let height = block.height();
1257 if height > Height::zero() {
1258 let parent_commitment = V::parent_commitment(&block);
1259 assert!(
1260 block.parent() == V::commitment_to_inner(parent_commitment),
1261 "floor block parent commitment mismatch"
1262 );
1263 }
1264
1265 if height <= self.floor.processed_height() {
1269 warn!(
1270 %height,
1271 existing = %self.floor.processed_height(),
1272 "floor not updated, at or below existing"
1273 );
1274 let finalization = self
1275 .floor
1276 .take_pending_anchor()
1277 .expect("pending floor anchor missing");
1278 self.update_processed_round_floor(height, finalization.round(), resolver)
1279 .await;
1280 if self.try_repair_gaps(buffer, resolver, application).await {
1281 self.sync_finalized().await;
1282 }
1283 self.try_dispatch_blocks(application).await;
1284 return;
1285 }
1286
1287 let digest = block.digest();
1288 let finalization = self
1289 .floor
1290 .take_pending_anchor()
1291 .expect("pending floor anchor missing");
1292 let round = finalization.round();
1293 try_join!(
1294 async {
1295 self.finalized_blocks
1296 .put(Arc::unwrap_or_clone(block).into())
1297 .await
1298 .map_err(Box::new)?;
1299 Ok::<_, BoxedError>(())
1300 },
1301 async {
1302 self.finalizations_by_height
1303 .put(height, digest, finalization)
1304 .await
1305 .map_err(Box::new)?;
1306 Ok::<_, BoxedError>(())
1307 }
1308 )
1309 .expect("failed to store floor anchor");
1310 self.sync_finalized().await;
1311
1312 if height > self.tip {
1313 application.report(Update::Tip(round, height, digest));
1314 self.tip = height;
1315 let _ = self.finalized_height.try_set(height.get());
1316 }
1317
1318 let dispatch_floor = height
1321 .previous()
1322 .expect("floor anchor above processed height must have predecessor");
1323 self.update_processed_height(dispatch_floor, resolver);
1324 self.update_processed_round_floor(dispatch_floor, round, resolver)
1325 .await;
1326 self.stream
1327 .sync()
1328 .await
1329 .expect("failed to sync floor metadata");
1330
1331 self.pending_acks.clear();
1334
1335 self.prune_after_floor(height)
1337 .await
1338 .expect("failed to prune data below floor");
1339
1340 if self.try_repair_gaps(buffer, resolver, application).await {
1344 self.sync_finalized().await;
1345 }
1346 self.try_dispatch_blocks(application).await;
1347 }
1348
1349 async fn handle_deliver<Buf: Buffer<V>>(
1353 &mut self,
1354 message: ResolverDelivery<V>,
1355 delivers: &mut Vec<PendingVerification<P::Scheme, V>>,
1356 buffer: &mut Buf,
1357 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1358 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1359 ) {
1360 let ResolverDelivery {
1361 delivery,
1362 mut value,
1363 response,
1364 } = message;
1365 let Delivery {
1366 key, subscribers, ..
1367 } = delivery;
1368 match key {
1369 Key::Block(commitment) => {
1370 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1371 let Ok(block) = V::Block::decode_cfg(value.as_ref(), &block_cfg) else {
1372 response.send_lossy(false);
1373 return;
1374 };
1375 if V::commitment(&block) != commitment {
1376 response.send_lossy(false);
1377 return;
1378 }
1379
1380 let block = Arc::new(block);
1384 if self
1385 .ingest(Arc::clone(&block), buffer, application, resolver)
1386 .await
1387 {
1388 response.send_lossy(true);
1389 return;
1390 }
1391
1392 let height = block.height();
1396 let digest = block.digest();
1397 let annotations = subscribers
1398 .map_into(|(annotation, _)| annotation)
1399 .into_vec();
1400
1401 let finalization = self.cache.get_finalization_for(digest).await;
1405 if let Some(finalization) = &finalization {
1406 self.update_processed_round_floor(height, finalization.round(), resolver)
1407 .await;
1408 }
1409 if finalization.is_some()
1410 || annotations
1411 .iter()
1412 .any(|annotation| matches!(annotation, Annotation::Finalized(_)))
1413 {
1414 self.store_finalization(
1415 height,
1416 digest,
1417 Arc::unwrap_or_clone(block),
1418 finalization,
1419 application,
1420 )
1421 .await;
1422 } else if annotations
1423 .iter()
1424 .any(|annotation| matches!(annotation, Annotation::Certified { .. }))
1425 && height > self.floor.processed_height()
1426 {
1427 if let Some(bounds) = self.epocher.containing(height) {
1428 self.cache
1429 .put_certified(
1430 bounds.epoch(),
1431 height,
1432 digest,
1433 Arc::unwrap_or_clone(block).into(),
1434 )
1435 .await;
1436 }
1437 }
1438 debug!(?digest, %height, "received block");
1439 response.send_lossy(true);
1440 }
1441 Key::Finalized { height } => {
1442 let Some((epoch, certificate_codec_config)) =
1443 self.certificate_codec_config_for_height(height)
1444 else {
1445 debug!(
1446 %height,
1447 floor = %self.floor.processed_height(),
1448 "ignoring stale delivery"
1449 );
1450 response.send_lossy(true);
1451 return;
1452 };
1453
1454 let Ok(finalization) =
1455 Finalization::read_cfg(&mut value, &certificate_codec_config)
1456 else {
1457 response.send_lossy(false);
1458 return;
1459 };
1460
1461 if finalization.epoch() != epoch {
1465 response.send_lossy(false);
1466 return;
1467 }
1468
1469 let Ok(block) =
1472 V::ApplicationBlock::decode_cfg(&mut value, &self.block_codec_config)
1473 else {
1474 response.send_lossy(false);
1475 return;
1476 };
1477
1478 let commitment = finalization.proposal.payload;
1487 if block.height() != height || block.digest() != V::commitment_to_inner(commitment)
1488 {
1489 response.send_lossy(false);
1490 return;
1491 }
1492 delivers.push(PendingVerification::Finalized {
1493 finalization,
1494 block,
1495 response,
1496 });
1497 }
1498 Key::Notarized { round } => {
1499 let Some(scheme) = self.provider.scheme(round.epoch()) else {
1500 debug!(
1501 ?round,
1502 floor = %self.floor.processed_height(),
1503 "ignoring stale delivery"
1504 );
1505 response.send_lossy(true);
1506 return;
1507 };
1508 let certificate_codec_config = scheme.certificate_codec_config();
1509 let Ok(notarization) =
1510 Notarization::read_cfg(&mut value, &certificate_codec_config)
1511 else {
1512 response.send_lossy(false);
1513 return;
1514 };
1515
1516 if notarization.round() != round {
1519 response.send_lossy(false);
1520 return;
1521 }
1522
1523 let commitment = notarization.proposal.payload;
1526 if !V::check_payload(scheme.as_ref(), commitment) {
1527 response.send_lossy(false);
1528 return;
1529 }
1530 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1531 let Ok(block) = V::Block::decode_cfg(value, &block_cfg) else {
1532 response.send_lossy(false);
1533 return;
1534 };
1535
1536 if V::commitment(&block) != notarization.proposal.payload {
1537 response.send_lossy(false);
1538 return;
1539 }
1540 delivers.push(PendingVerification::Notarized {
1541 notarization,
1542 block,
1543 response,
1544 });
1545 }
1546 }
1547 }
1548
1549 #[tracing::instrument(name = "marshal.actor.verify_delivered", level = "info", skip_all, fields(count = delivers.len().traced()))]
1551 async fn verify_delivered<Buf: Buffer<V>>(
1552 &mut self,
1553 mut delivers: Vec<PendingVerification<P::Scheme, V>>,
1554 buffer: &mut Buf,
1555 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1556 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1557 ) {
1558 delivers.retain(|item| !item.response_closed());
1559 if delivers.is_empty() {
1560 return;
1561 }
1562
1563 let certs: Vec<_> = delivers
1565 .iter()
1566 .map(|item| match item {
1567 PendingVerification::Finalized { finalization, .. } => (
1568 Subject::Finalize {
1569 proposal: &finalization.proposal,
1570 },
1571 &finalization.certificate,
1572 ),
1573 PendingVerification::Notarized { notarization, .. } => (
1574 Subject::Notarize {
1575 proposal: ¬arization.proposal,
1576 },
1577 ¬arization.certificate,
1578 ),
1579 })
1580 .collect();
1581
1582 let mut by_epoch: BTreeMap<Epoch, Vec<usize>> = BTreeMap::new();
1584 for (i, item) in delivers.iter().enumerate() {
1585 let epoch = match item {
1586 PendingVerification::Notarized { notarization, .. } => notarization.epoch(),
1587 PendingVerification::Finalized { finalization, .. } => finalization.epoch(),
1588 };
1589 by_epoch.entry(epoch).or_default().push(i);
1590 }
1591
1592 let mut verified = vec![false; delivers.len()];
1594 for (epoch, indices) in &by_epoch {
1595 let Some(scoped) = self.provider.scoped(*epoch) else {
1596 continue;
1597 };
1598 let group: Vec<_> = indices.iter().map(|&i| certs[i]).collect();
1599 let results =
1600 verify_certificates(self.context.as_mut(), &scoped, &group, &self.strategy);
1601 for (j, &idx) in indices.iter().enumerate() {
1602 verified[idx] = results[j];
1603 }
1604 }
1605
1606 for (index, item) in delivers.drain(..).enumerate() {
1608 if !verified[index] {
1609 match item {
1610 PendingVerification::Finalized { response, .. }
1611 | PendingVerification::Notarized { response, .. } => {
1612 response.send_lossy(false);
1613 }
1614 }
1615 continue;
1616 }
1617 match item {
1618 PendingVerification::Finalized {
1619 finalization,
1620 block,
1621 response,
1622 } => {
1623 response.send_lossy(true);
1625 let block = Arc::new(V::from_application_block(
1626 block,
1627 finalization.proposal.payload,
1628 ));
1629 let round = finalization.round();
1630 let height = block.height();
1631 let digest = block.digest();
1632 debug!(?round, %height, "received finalization");
1633
1634 if self
1637 .ingest(Arc::clone(&block), buffer, application, resolver)
1638 .await
1639 {
1640 continue;
1641 }
1642
1643 self.update_processed_round_floor(height, round, resolver)
1644 .await;
1645
1646 self.store_finalization(
1647 height,
1648 digest,
1649 Arc::unwrap_or_clone(block),
1650 Some(finalization),
1651 application,
1652 )
1653 .await;
1654 }
1655 PendingVerification::Notarized {
1656 notarization,
1657 block,
1658 response,
1659 } => {
1660 response.send_lossy(true);
1662 let round = notarization.round();
1663 let commitment = notarization.proposal.payload;
1664 let digest = V::commitment_to_inner(commitment);
1665 debug!(?round, ?digest, "received notarization");
1666
1667 let height = block.height();
1671 let block = Arc::new(block);
1672 let block_sync = self
1673 .cache
1674 .put_notarized(round, digest, block.as_ref().clone().into())
1675 .await;
1676 let notarization_sync = self
1677 .cache
1678 .put_notarization(round, digest, notarization)
1679 .await;
1680 join(
1681 block_sync.durable(round, "notarized"),
1682 notarization_sync.durable(round, "notarization"),
1683 )
1684 .await;
1685
1686 if self
1689 .ingest(Arc::clone(&block), buffer, application, resolver)
1690 .await
1691 {
1692 continue;
1693 }
1694
1695 if let Some(finalization) = self.cache.get_finalization_for(digest).await {
1700 self.update_processed_round_floor(height, finalization.round(), resolver)
1701 .await;
1702
1703 self.store_finalization(
1706 height,
1707 digest,
1708 Arc::unwrap_or_clone(block),
1709 Some(finalization),
1710 application,
1711 )
1712 .await;
1713 }
1714 }
1715 }
1716 }
1717 }
1718
1719 fn certificate_codec_config(
1721 &self,
1722 epoch: Epoch,
1723 ) -> Option<<<P::Scheme as Verifier>::Certificate as Read>::Cfg> {
1724 self.provider
1725 .scoped(epoch)
1726 .map(|scoped| scoped.certificate_codec_config())
1727 }
1728
1729 fn certificate_codec_config_for_height(
1731 &self,
1732 height: Height,
1733 ) -> Option<(Epoch, <<P::Scheme as Verifier>::Certificate as Read>::Cfg)> {
1734 let epoch = self.epocher.containing(height)?.epoch();
1735 self.certificate_codec_config(epoch)
1736 .map(|config| (epoch, config))
1737 }
1738
1739 async fn try_dispatch_blocks(
1778 &mut self,
1779 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1780 ) {
1781 if self.floor.blocks_progress() {
1783 return;
1784 }
1785
1786 let barrier = self.dispatch_gate.barrier();
1790 while self.pending_acks.has_capacity() {
1791 let next_height = self
1792 .pending_acks
1793 .next_dispatch_height(self.stream.next_height());
1794 if barrier.is_some_and(|lowest| next_height >= lowest) {
1795 return;
1796 }
1797 let Some(block) = self.get_finalized_block(next_height).await else {
1798 return;
1799 };
1800 assert_eq!(
1801 block.height(),
1802 next_height,
1803 "finalized block height mismatch"
1804 );
1805
1806 let (height, commitment) = (block.height(), V::commitment(&block));
1807 let (ack, ack_waiter) = A::handle();
1808 application.report(Update::Block(V::owned_into_inner_shared(block), ack));
1809 self.pending_acks.enqueue(PendingAck {
1810 height,
1811 commitment,
1812 receiver: ack_waiter,
1813 });
1814 }
1815 }
1816
1817 #[tracing::instrument(name = "marshal.actor.sync_finalized", level = "info", skip_all)]
1833 async fn sync_finalized(&mut self) {
1834 if let Err(e) = try_join!(
1835 async {
1836 self.finalized_blocks.sync().await.map_err(Box::new)?;
1837 Ok::<_, BoxedError>(())
1838 },
1839 async {
1840 self.finalizations_by_height
1841 .sync()
1842 .await
1843 .map_err(Box::new)?;
1844 Ok::<_, BoxedError>(())
1845 },
1846 ) {
1847 panic!("failed to sync finalization archives: {e}");
1848 }
1849
1850 self.dispatch_gate.clear();
1853 }
1854
1855 #[tracing::instrument(name = "marshal.actor.start_finalized_sync", level = "info", skip_all)]
1871 async fn start_finalized_sync(&mut self, round: Round, syncs: &mut Pool<PooledSync>) {
1872 let Some(seq) = self.dispatch_gate.adopt() else {
1875 return;
1876 };
1877
1878 let (blocks, finalizations) = match try_join!(
1879 async {
1880 let handle = self.finalized_blocks.start_sync().await.map_err(Box::new)?;
1881 Ok::<_, BoxedError>(handle)
1882 },
1883 async {
1884 let handle = self
1885 .finalizations_by_height
1886 .start_sync()
1887 .await
1888 .map_err(Box::new)?;
1889 Ok::<_, BoxedError>(handle)
1890 },
1891 ) {
1892 Ok(handles) => handles,
1893 Err(e) => panic!("failed to start finalization archive sync: {e}"),
1894 };
1895 syncs.push(async move {
1896 let (blocks, finalizations) = join(
1897 blocks.durable(round, "finalized blocks"),
1898 finalizations.durable(round, "finalizations"),
1899 )
1900 .await;
1901 if blocks && finalizations {
1902 PooledSync::Finalized(seq)
1903 } else {
1904 PooledSync::Observed
1907 }
1908 });
1909 }
1910
1911 async fn get_finalized_block(&self, height: Height) -> Option<V::Block> {
1915 match self
1916 .finalized_blocks
1917 .get(ArchiveID::Index(height.get()))
1918 .await
1919 {
1920 Ok(stored) => stored.map(|stored| stored.into()),
1921 Err(e) => panic!("failed to get block: {e}"),
1922 }
1923 }
1924
1925 async fn get_finalization_by_height(
1927 &self,
1928 height: Height,
1929 ) -> Option<Finalization<P::Scheme, V::Commitment>> {
1930 match self
1931 .finalizations_by_height
1932 .get(ArchiveID::Index(height.get()))
1933 .await
1934 {
1935 Ok(finalization) => finalization,
1936 Err(e) => panic!("failed to get finalization: {e}"),
1937 }
1938 }
1939
1940 async fn has_finalization_by_height(&self, height: Height) -> bool {
1943 match self.finalizations_by_height.has(height).await {
1944 Ok(has) => has,
1945 Err(e) => panic!("failed to check finalization: {e}"),
1946 }
1947 }
1948
1949 async fn get_info_by_height(
1952 &self,
1953 height: Height,
1954 ) -> Option<(Height, <V::Block as Digestible>::Digest)> {
1955 if let Some(finalization) = self.get_finalization_by_height(height).await {
1956 return Some((
1957 height,
1958 V::commitment_to_inner(finalization.proposal.payload),
1959 ));
1960 }
1961
1962 self.get_finalized_block(height)
1963 .await
1964 .map(|block| (block.height(), block.digest()))
1965 }
1966
1967 async fn store_finalization(
1981 &mut self,
1982 height: Height,
1983 digest: <V::Block as Digestible>::Digest,
1984 block: V::Block,
1985 finalization: Option<Finalization<P::Scheme, V::Commitment>>,
1986 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1987 ) -> bool {
1988 if height <= self.floor.processed_height() {
1992 debug!(
1993 %height,
1994 floor = %self.floor.processed_height(),
1995 ?digest,
1996 "dropping finalization at or below processed height floor"
1997 );
1998 return false;
1999 }
2000
2001 let stored: V::StoredBlock = block.into();
2003 let round = finalization.as_ref().map(|f| f.round());
2004
2005 if let Err(e) = try_join!(
2007 async {
2009 self.finalized_blocks.put(stored).await.map_err(Box::new)?;
2010 Ok::<_, BoxedError>(())
2011 },
2012 async {
2014 if let Some(finalization) = finalization {
2015 self.finalizations_by_height
2016 .put(height, digest, finalization)
2017 .await
2018 .map_err(Box::new)?;
2019 }
2020 Ok::<_, BoxedError>(())
2021 }
2022 ) {
2023 panic!("failed to finalize: {e}");
2024 }
2025
2026 self.dispatch_gate.defer(height);
2029
2030 if let Some(round) = round.filter(|_| height > self.tip) {
2032 application.report(Update::Tip(round, height, digest));
2033 self.tip = height;
2034 let _ = self.finalized_height.try_set(height.get());
2035 }
2036
2037 true
2038 }
2039
2040 async fn get_latest(&mut self) -> Option<(Height, <V::Block as Digestible>::Digest, Round)> {
2053 let height = self.finalizations_by_height.last_index()?;
2054 let finalization = self
2055 .get_finalization_by_height(height)
2056 .await
2057 .expect("finalization missing");
2058 Some((
2059 height,
2060 V::commitment_to_inner(finalization.proposal.payload),
2061 finalization.round(),
2062 ))
2063 }
2064
2065 async fn find_block_in_storage(
2069 &self,
2070 digest: <V::Block as Digestible>::Digest,
2071 ) -> Option<V::Block> {
2072 if let Some(block) = self.cache.find_block_matching(digest, |_| true).await {
2074 return Some(block.into());
2075 }
2076 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2078 Ok(stored) => stored.map(|stored| stored.into()),
2079 Err(e) => panic!("failed to get block: {e}"),
2080 }
2081 }
2082
2083 async fn find_block_in_storage_by_commitment(
2085 &self,
2086 commitment: V::Commitment,
2087 ) -> Option<V::Block> {
2088 let digest = V::commitment_to_inner(commitment);
2089 if let Some(block) = self
2090 .cache
2091 .find_block_matching(digest, |stored| V::stored_commitment(stored) == commitment)
2092 .await
2093 {
2094 return Some(block.into());
2095 }
2096
2097 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2098 Ok(Some(stored)) => {
2099 (V::stored_commitment(&stored) == commitment).then(|| stored.into())
2100 }
2101 Ok(None) => None,
2102 Err(e) => panic!("failed to get block: {e}"),
2103 }
2104 }
2105
2106 async fn find_block_by_digest<Buf: Buffer<V>>(
2111 &self,
2112 buffer: &Buf,
2113 digest: <V::Block as Digestible>::Digest,
2114 ) -> Option<Arc<V::Block>> {
2115 if let Some(block) = buffer.find_by_digest(digest).await {
2116 return Some(block);
2117 }
2118 self.find_block_in_storage(digest).await.map(Arc::new)
2119 }
2120
2121 async fn find_block_by_commitment<Buf: Buffer<V>>(
2126 &self,
2127 buffer: &Buf,
2128 commitment: V::Commitment,
2129 ) -> Option<Arc<V::Block>> {
2130 if let Some(block) = buffer.find_by_commitment(commitment).await {
2131 return Some(block);
2132 }
2133 self.find_block_in_storage_by_commitment(commitment)
2134 .await
2135 .map(Arc::new)
2136 }
2137
2138 #[tracing::instrument(name = "marshal.actor.try_repair_gaps", level = "info", skip_all)]
2150 async fn try_repair_gaps<Buf: Buffer<V>>(
2151 &mut self,
2152 buffer: &mut Buf,
2153 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2154 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2155 ) -> bool {
2156 if self.floor.blocks_progress() {
2159 return false;
2160 }
2161
2162 let mut wrote = false;
2163 let start = self.floor.processed_height().next();
2164
2165 if let Some(last_finalized) = self.finalizations_by_height.last_index() {
2168 let have_block = self
2169 .finalized_blocks
2170 .last_index()
2171 .is_some_and(|last| last >= last_finalized);
2172 if last_finalized > self.floor.processed_height() && !have_block {
2173 let finalization = self
2175 .get_finalization_by_height(last_finalized)
2176 .await
2177 .expect("finalization missing");
2178 let commitment = finalization.proposal.payload;
2179 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
2180 let digest = block.digest();
2182 wrote |= self
2183 .store_finalization(
2184 last_finalized,
2185 digest,
2186 Arc::unwrap_or_clone(block),
2187 Some(finalization),
2188 application,
2189 )
2190 .await;
2191 } else {
2192 self.floor
2194 .fetch_if_permitted(
2195 resolver,
2196 Request::finalized_block_by_height(commitment, last_finalized),
2197 )
2198 .ignore();
2199 }
2200 }
2201 }
2202
2203 'cache_repair: loop {
2205 let (gap_start, Some(gap_end)) = self.finalized_blocks.next_gap(start) else {
2206 return wrote;
2208 };
2209
2210 let Some(cursor) = self.get_finalized_block(gap_end).await else {
2214 panic!("gapped block missing that should exist: {gap_end}");
2215 };
2216 let (mut height, mut parent_digest, mut parent_commitment) = (
2217 cursor.height(),
2218 cursor.parent(),
2219 V::parent_commitment(&cursor),
2220 );
2221
2222 let gap_start = gap_start.map(Height::next).unwrap_or(start);
2226
2227 while height > gap_start {
2229 if let Some(block) = self
2230 .find_block_by_commitment(buffer, parent_commitment)
2231 .await
2232 {
2233 let finalization = self.cache.get_finalization_for(parent_digest).await;
2234 let next = (block.height(), block.parent(), V::parent_commitment(&block));
2235 wrote |= self
2236 .store_finalization(
2237 next.0,
2238 parent_digest,
2239 Arc::unwrap_or_clone(block),
2240 finalization,
2241 application,
2242 )
2243 .await;
2244 debug!(height = %next.0, "repaired block");
2245 (height, parent_digest, parent_commitment) = next;
2246 } else {
2247 let parent_height = height
2253 .previous()
2254 .expect("cursor above gap start has a parent");
2255 self.floor
2256 .fetch_if_permitted(
2257 resolver,
2258 Request::finalized_block_by_height(parent_commitment, parent_height),
2259 )
2260 .ignore();
2261 break 'cache_repair;
2262 }
2263 }
2264 }
2265
2266 let missing_items = self
2272 .finalized_blocks
2273 .missing_items(start, self.max_repair.get());
2274 let requests: Vec<_> = missing_items.into_iter().map(Request::finalized).collect();
2275 if !requests.is_empty() {
2276 self.floor
2277 .fetch_all_if_permitted(resolver, requests)
2278 .ignore();
2279 }
2280 wrote
2281 }
2282
2283 fn update_processed_height(
2286 &mut self,
2287 height: Height,
2288 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2289 ) {
2290 self.stream.acknowledge(height);
2291 self.floor.set_processed_height(height);
2292 let _ = self
2293 .processed_height
2294 .try_set(self.floor.processed_height().get());
2295
2296 resolver.retain(handler::above_height_floor::<V::Commitment>(height));
2298 }
2299
2300 async fn latest_processed_round(finalizations_by_height: &FC, height: Option<Height>) -> Round {
2302 let Some(height) = height else {
2303 return Round::zero();
2304 };
2305 let Some(finalization_height) = finalizations_by_height
2306 .ranges_from(Height::zero())
2307 .filter_map(|(start, end)| (start <= height).then_some(end.min(height)))
2308 .max()
2309 else {
2310 return Round::zero();
2311 };
2312
2313 match finalizations_by_height
2314 .get(ArchiveID::Index(finalization_height.get()))
2315 .await
2316 {
2317 Ok(Some(finalization)) => finalization.round(),
2318 Ok(None) => panic!("processed finalization missing from stored range"),
2319 Err(err) => panic!("failed to get processed finalization: {err}"),
2320 }
2321 }
2322
2323 async fn update_processed_round(
2325 &mut self,
2326 height: Height,
2327 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2328 ) {
2329 let Some(finalization) = self.get_finalization_by_height(height).await else {
2330 return;
2331 };
2332 self.update_processed_round_floor(height, finalization.round(), resolver)
2333 .await;
2334 }
2335
2336 async fn update_processed_round_floor(
2338 &mut self,
2339 height: Height,
2340 round: Round,
2341 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2342 ) {
2343 if height > self.floor.processed_height() || round <= self.floor.processed_round() {
2344 return;
2345 }
2346
2347 let previous = self.floor.processed_round();
2348 self.floor.set_processed_round(round);
2349
2350 let prune_round = Round::new(
2353 previous.epoch(),
2354 previous.view().saturating_sub(self.view_retention_timeout),
2355 );
2356 self.cache.prune_by_view(prune_round).await;
2357
2358 resolver.retain(handler::above_round_floor::<V::Commitment>(
2360 self.floor.processed_round(),
2361 ));
2362 }
2363
2364 async fn prune_finalized_archives(&mut self, height: Height) -> Result<(), BoxedError> {
2366 try_join!(
2368 async {
2369 self.finalized_blocks
2370 .prune(height)
2371 .await
2372 .map_err(Box::new)?;
2373 Ok::<_, BoxedError>(())
2374 },
2375 async {
2376 self.finalizations_by_height
2377 .prune(height)
2378 .await
2379 .map_err(Box::new)?;
2380 Ok::<_, BoxedError>(())
2381 }
2382 )?;
2383 Ok(())
2384 }
2385
2386 async fn prune_after_floor(&mut self, height: Height) -> Result<(), BoxedError> {
2388 let cache = &mut self.cache;
2389 let finalized_blocks = &mut self.finalized_blocks;
2390 let finalizations_by_height = &mut self.finalizations_by_height;
2391 try_join!(
2392 async {
2393 cache.prune_by_height(height).await;
2394 Ok::<_, BoxedError>(())
2395 },
2396 async {
2397 finalized_blocks.prune(height).await.map_err(Box::new)?;
2398 Ok::<_, BoxedError>(())
2399 },
2400 async {
2401 finalizations_by_height
2402 .prune(height)
2403 .await
2404 .map_err(Box::new)?;
2405 Ok::<_, BoxedError>(())
2406 }
2407 )?;
2408 Ok(())
2409 }
2410}