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 self.handle_ack(result, &mut application, &mut buffer, &mut resolver)
466 .await;
467 },
468 Some(message) = self.mailbox.recv() else {
470 debug!("mailbox closed, shutting down");
471 break;
472 } => {
473 let span = info_span!(
474 parent: message.span(),
475 "marshal.actor.process",
476 operation = message.name(),
477 );
478 self.handle_mailbox_message(
479 message,
480 &mut resolver,
481 &mut waiters,
482 &mut syncs,
483 &mut buffer,
484 &mut application,
485 )
486 .instrument(span)
487 .await;
488 },
489 Some(message) = resolver_rx.recv() else {
491 debug!("handler closed, shutting down");
492 return;
493 } => {
494 self.handle_resolver_message(
495 message,
496 &mut resolver_rx,
497 &mut resolver,
498 &mut syncs,
499 &mut buffer,
500 &mut application,
501 )
502 .await;
503 },
504 }
505 }
506
507 async fn handle_ack<Buf, R>(
510 &mut self,
511 result: <A::Waiter as Future>::Output,
512 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
513 buffer: &mut Buf,
514 resolver: &mut R,
515 ) where
516 Buf: Buffer<V>,
517 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
518 {
519 let mut pending = Some(self.pending_acks.complete_current(result));
521 let last_acked_commitment = loop {
522 let (height, commitment, result) = pending.take().expect("pending ack must exist");
523 match result {
524 Ok(()) => {
525 self.update_processed_height(height, resolver);
528 self.update_processed_round(height, resolver).await;
529 }
530 Err(e) => {
531 panic!("application did not acknowledge block at height {height}: {e:?}");
533 }
534 }
535
536 match self.pending_acks.pop_ready() {
539 Some(next) => pending = Some(next),
540 None => break commitment,
541 }
542 };
543
544 self.stream
546 .sync()
547 .await
548 .expect("failed to sync application progress");
549
550 buffer.finalized(last_acked_commitment);
553
554 self.try_dispatch_blocks(application).await;
556 }
557
558 async fn handle_mailbox_message<Buf, R>(
560 &mut self,
561 message: Message<P::Scheme, V>,
562 resolver: &mut R,
563 waiters: &mut AbortablePool<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
564 syncs: &mut Pool<PooledSync>,
565 buffer: &mut Buf,
566 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
567 ) where
568 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
569 R: TargetedResolver<
570 Key = ResolverRequestFor<V>,
571 Subscriber = Annotation,
572 PublicKey = <P::Scheme as Verifier>::PublicKey,
573 >,
574 {
575 if message.response_closed() {
576 return;
577 }
578
579 match message {
580 Message::GetInfo {
581 identifier,
582 response,
583 ..
584 } => {
585 let info = match identifier {
586 BlockID::Digest(digest) => self
590 .finalized_blocks
591 .get(ArchiveID::Key(&digest))
592 .await
593 .ok()
594 .flatten()
595 .map(|b| (b.height(), digest)),
596 BlockID::Height(height) => self.get_info_by_height(height).await,
597 BlockID::Latest => self.get_latest().await.map(|(h, d, _)| (h, d)),
598 };
599 response.send_lossy(info);
600 }
601 Message::GetVerified {
602 round, response, ..
603 } => {
604 let block = self.cache.get_verified(round).await.map(Into::into);
605 response.send_lossy(block);
606 }
607 Message::Forward {
608 round,
609 commitment,
610 recipients,
611 ..
612 } => {
613 if matches!(&recipients, Recipients::Some(peers) if peers.is_empty()) {
614 return;
615 }
616 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
617 debug!(?commitment, "block not found for forwarding");
618 return;
619 };
620 buffer.send(round, block, recipients);
621 }
622 Message::Proposed {
623 round,
624 block,
625 recipients,
626 ack,
627 ..
628 } => {
629 buffer.send(round, Arc::clone(&block), recipients);
639 self.persist_verified(round, block, ack, buffer, application, resolver)
640 .await;
641 }
642 Message::Verified {
643 round, block, ack, ..
644 } => {
645 self.persist_verified(round, block, ack, buffer, application, resolver)
646 .await;
647 }
648 Message::Certified {
649 round, block, ack, ..
650 } => {
651 self.ingest(Arc::clone(&block), buffer, application, resolver)
652 .await;
653 let digest = block.digest();
654
655 let block_sync = if self.cache.has_verified(round, &digest).await {
663 debug!(?round, "certified block covered by verified write");
664 self.cache.start_sync_verified(round).await
665 } else {
666 self.cache
667 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
668 .await
669 };
670
671 let notarization_sync = self.cache.start_sync_notarizations(round).await;
675 let handle = Handle::from_future(async move {
676 let (notarization, block) = join(notarization_sync, block_sync).await;
677 notarization.and(block)
678 });
679 ack.send_lossy(handle);
680 }
681 Message::Notarization { notarization, .. } => {
682 let round = notarization.round();
683 let commitment = notarization.proposal.payload;
684 let digest = V::commitment_to_inner(commitment);
685
686 let handle = self
693 .cache
694 .put_notarization(round, digest, notarization)
695 .await;
696 syncs.push(async move {
697 handle.durable(round, "notarization").await;
698 PooledSync::Observed
699 });
700
701 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
705 self.ingest(Arc::clone(&block), buffer, application, resolver)
706 .await;
707 if self.cache.has_verified(round, &digest).await {
708 debug!(?round, "notarized block covered by verified write");
709 } else {
710 let handle = self
711 .cache
712 .put_notarized(round, digest, Arc::unwrap_or_clone(block).into())
713 .await;
714 syncs.push(async move {
715 handle.durable(round, "notarized").await;
716 PooledSync::Observed
717 });
718 }
719 } else {
720 debug!(?round, "notarized block unavailable locally");
721 }
722 }
723 Message::Finalization { finalization, .. } => {
724 let round = finalization.round();
725 let commitment = finalization.proposal.payload;
726 let digest = V::commitment_to_inner(commitment);
727
728 self.cache
730 .put_finalization(round, digest, finalization.clone())
731 .await;
732
733 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
735 if self
738 .ingest(Arc::clone(&block), buffer, application, resolver)
739 .await
740 {
741 return;
742 }
743
744 let height = block.height();
745 self.update_processed_round_floor(height, round, resolver)
746 .await;
747 if self
748 .store_finalization(
749 height,
750 digest,
751 Arc::unwrap_or_clone(block),
752 Some(finalization),
753 application,
754 )
755 .await
756 {
757 self.try_repair_gaps(buffer, resolver, application).await;
760 self.start_finalized_sync(round, syncs).await;
761 debug!(?round, %height, "finalized block stored");
762 }
763 } else {
764 debug!(?round, ?commitment, "finalized block missing");
767 self.floor
768 .fetch_if_permitted(
769 resolver,
770 Request::finalized_block_by_round(commitment, round),
771 )
772 .ignore();
773 }
774 }
775 Message::GetBlock {
776 identifier,
777 response,
778 ..
779 } => match identifier {
780 BlockID::Digest(digest) => {
781 let result = self
782 .find_block_by_digest(buffer, digest)
783 .await
784 .map(Arc::unwrap_or_clone);
785 response.send_lossy(result);
786 }
787 BlockID::Height(height) => {
788 let result = self.get_finalized_block(height).await;
789 response.send_lossy(result);
790 }
791 BlockID::Latest => {
792 let block = match self.get_latest().await {
793 Some((_, digest, _)) => self.find_block_by_digest(buffer, digest).await,
794 None => None,
795 }
796 .map(Arc::unwrap_or_clone);
797 response.send_lossy(block);
798 }
799 },
800 Message::GetFinalization {
801 height, response, ..
802 } => {
803 let finalization = self.get_finalization_by_height(height).await;
804 response.send_lossy(finalization);
805 }
806 Message::GetProcessedHeight { response, .. } => {
807 response.send_lossy(self.stream.processed_height());
808 }
809 Message::HintFinalized {
810 height, targets, ..
811 } => {
812 if self.has_finalization_by_height(height).await {
814 return;
815 }
816
817 self.floor
818 .fetch_targeted_if_permitted(resolver, Request::finalized(height), targets)
819 .ignore();
820 }
821 Message::SubscribeByDigest {
822 span,
823 digest,
824 fallback,
825 response,
826 } => {
827 self.handle_subscribe(
828 span,
829 fallback.into(),
830 SubscriptionKey::Digest(digest),
831 response,
832 resolver,
833 waiters,
834 buffer,
835 )
836 .await;
837 }
838 Message::SubscribeByCommitment {
839 span,
840 commitment,
841 fallback,
842 response,
843 } => {
844 self.handle_subscribe(
845 span,
846 fallback,
847 SubscriptionKey::Commitment(commitment),
848 response,
849 resolver,
850 waiters,
851 buffer,
852 )
853 .await;
854 }
855 Message::HintNotarized {
856 round, commitment, ..
857 } => {
858 if self
859 .find_block_by_commitment(buffer, commitment)
860 .await
861 .is_none()
862 {
863 self.floor
864 .fetch_if_permitted(resolver, Request::notarized(round))
865 .ignore();
866 }
867 }
868 Message::SetFloor { finalization, .. } => {
869 self.install_floor(finalization, true, resolver, buffer, application)
870 .await;
871 }
872 Message::Prune { height, .. } => {
873 if height > self.floor.processed_height() {
875 warn!(%height, floor = %self.floor.processed_height(), "prune height above floor, ignoring");
876 return;
877 }
878
879 self.prune_finalized_archives(height)
880 .await
881 .expect("failed to prune finalized archives");
882
883 }
887 }
888 }
889
890 async fn handle_resolver_message<Buf, R>(
893 &mut self,
894 message: handler::Message<V::Commitment>,
895 resolver_rx: &mut handler::Receiver<V::Commitment>,
896 resolver: &mut R,
897 syncs: &mut Pool<PooledSync>,
898 buffer: &mut Buf,
899 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
900 ) where
901 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
902 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
903 {
904 let mut handled = false;
905 let mut produces = Vec::new();
906 let mut delivers = Vec::new();
907
908 for msg in std::iter::once(message)
912 .chain(std::iter::from_fn(|| resolver_rx.try_recv().ok()))
913 .take(self.max_repair.get())
914 {
915 if msg.response_closed() {
916 continue;
917 }
918 handled = true;
919
920 match msg {
921 handler::Message::Produce { key, response } => {
922 produces.push((key, response));
923 }
924 handler::Message::Deliver {
925 delivery,
926 value,
927 response,
928 } => {
929 let span = info_span!(
930 parent: &delivery.subscribers.first().1,
931 "marshal.resolver.deliver",
932 key = %delivery.key
933 );
934 for (_, subscriber_span) in delivery.subscribers.iter().skip(1) {
935 span.follows_from(subscriber_span.id());
936 }
937 self.handle_deliver(
938 ResolverDelivery {
939 delivery,
940 value,
941 response,
942 },
943 &mut delivers,
944 buffer,
945 application,
946 resolver,
947 )
948 .instrument(span)
949 .await;
950 }
951 }
952 }
953 if !handled {
954 return;
955 }
956
957 self.verify_delivered(delivers, buffer, application, resolver)
959 .await;
960
961 self.try_repair_gaps(buffer, resolver, application).await;
964
965 self.start_finalized_sync(self.floor.processed_round(), syncs)
970 .await;
971
972 join_all(
974 produces
975 .into_iter()
976 .filter(|(_, response)| !response.is_closed())
977 .map(|(key, response)| self.handle_produce(key, response, buffer)),
978 )
979 .await;
980 }
981
982 #[tracing::instrument(name = "marshal.resolver.produce", level = "debug", skip_all, fields(key = %key))]
984 async fn handle_produce<Buf: Buffer<V>>(
985 &self,
986 key: ResolverRequestFor<V>,
987 response: oneshot::Sender<Bytes>,
988 buffer: &Buf,
989 ) {
990 match key {
991 Key::Block(commitment) => {
992 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
993 debug!(?commitment, "block missing on request");
994 return;
995 };
996 response.send_lossy(block.encode());
997 }
998 Key::Finalized { height } => {
999 let Some(finalization) = self.get_finalization_by_height(height).await else {
1000 debug!(%height, "finalization missing on request");
1001 return;
1002 };
1003 let Some(block) = self.get_finalized_block(height).await else {
1004 debug!(%height, "finalized block missing on request");
1005 return;
1006 };
1007 response.send_lossy((finalization, V::into_inner(block)).encode());
1008 }
1009 Key::Notarized { round } => {
1010 let Some(notarization) = self.cache.get_notarization(round).await else {
1011 debug!(?round, "notarization missing on request");
1012 return;
1013 };
1014 let commitment = notarization.proposal.payload;
1015 let Some(block) = self.find_block_by_commitment(buffer, commitment).await else {
1016 debug!(?commitment, "block missing on request");
1017 return;
1018 };
1019 response.send_lossy((notarization, block).encode());
1020 }
1021 }
1022 }
1023
1024 #[allow(clippy::too_many_arguments)]
1026 async fn handle_subscribe<Buf: Buffer<V>>(
1027 &mut self,
1028 span: Span,
1029 fallback: CommitmentFallback,
1030 key: SubscriptionKeyFor<V>,
1031 response: oneshot::Sender<Arc<V::Block>>,
1032 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1033 waiters: &mut AbortablePool<Result<Arc<V::Block>, SubscriptionKeyFor<V>>>,
1034 buffer: &mut Buf,
1035 ) {
1036 let digest = match key {
1037 SubscriptionKey::Digest(digest) => digest,
1038 SubscriptionKey::Commitment(commitment) => V::commitment_to_inner(commitment),
1039 };
1040
1041 let block = match key {
1042 SubscriptionKey::Digest(digest) => self.find_block_by_digest(buffer, digest).await,
1043 SubscriptionKey::Commitment(commitment) => {
1044 self.find_block_by_commitment(buffer, commitment).await
1045 }
1046 };
1047 if let Some(block) = block {
1048 response.send_lossy(block);
1049 return;
1050 }
1051
1052 match fallback {
1059 CommitmentFallback::FetchByRound { round } => {
1060 if self
1066 .floor
1067 .fetch_if_permitted(resolver, Request::notarized(round))
1068 .denied()
1069 {
1070 return;
1071 }
1072 debug!(?round, ?digest, "requested block missing");
1073 }
1074 CommitmentFallback::FetchByCommitment { height } => {
1075 let commitment = match key {
1076 SubscriptionKey::Commitment(commitment) => commitment,
1077 SubscriptionKey::Digest(_) => {
1078 unreachable!("digest subscriptions cannot request commitment fallback")
1079 }
1080 };
1081
1082 if self
1085 .floor
1086 .fetch_if_permitted(resolver, Request::certified_block(commitment, height))
1087 .denied()
1088 {
1089 return;
1090 }
1091 debug!(%height, ?commitment, ?digest, "requested certified ancestry block missing");
1092 }
1093 CommitmentFallback::Wait => {}
1094 }
1095
1096 let round = match fallback {
1097 CommitmentFallback::FetchByRound { round } => Some(round),
1098 CommitmentFallback::Wait | CommitmentFallback::FetchByCommitment { .. } => None,
1099 };
1100
1101 match key {
1103 SubscriptionKey::Digest(digest) => {
1104 debug!(?round, ?digest, "registering subscriber");
1105 }
1106 SubscriptionKey::Commitment(commitment) => {
1107 debug!(?round, ?commitment, ?digest, "registering subscriber");
1108 }
1109 }
1110 self.block_subscriptions
1111 .insert(span, key, response, waiters, buffer);
1112 }
1113
1114 async fn install_floor<Buf, R>(
1116 &mut self,
1117 finalization: Finalization<P::Scheme, V::Commitment>,
1118 skip_if_superseded: bool,
1119 resolver: &mut R,
1120 buffer: &mut Buf,
1121 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1122 ) where
1123 Buf: Buffer<V, PublicKey = <P::Scheme as Verifier>::PublicKey>,
1124 R: Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1125 {
1126 let round = finalization.round();
1127 if round <= self.floor.processed_round() {
1128 warn!(
1129 ?round,
1130 floor = ?self.floor.processed_round(),
1131 "floor not updated, below existing round floor"
1132 );
1133 return;
1134 }
1135
1136 let Some(scoped) = self.provider.scoped(finalization.epoch()) else {
1137 panic!("floor finalization epoch unavailable");
1138 };
1139 assert!(
1140 finalization.verify(self.context.as_mut(), &scoped, &self.strategy),
1141 "floor finalization must verify"
1142 );
1143
1144 let commitment = finalization.proposal.payload;
1145 let digest = V::commitment_to_inner(commitment);
1146 self.cache
1147 .put_finalization(round, digest, finalization.clone())
1148 .await;
1149
1150 if skip_if_superseded && self.floor.has_pending_anchor_at_or_after(round) {
1153 return;
1154 }
1155
1156 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
1157 self.floor.await_anchor(finalization);
1158 assert!(self.ingest(block, buffer, application, resolver).await);
1159 return;
1160 }
1161
1162 self.pending_acks.clear();
1165
1166 debug!(?round, ?commitment, "starting fetch for floor block");
1167 self.floor.await_anchor(finalization);
1168 self.floor
1169 .fetch_if_permitted(
1170 resolver,
1171 Request::finalized_block_by_round(commitment, round),
1172 )
1173 .ignore();
1174 }
1175
1176 async fn persist_verified<Buf: Buffer<V>>(
1184 &mut self,
1185 round: Round,
1186 block: Arc<V::Block>,
1187 ack: oneshot::Sender<Handle<()>>,
1188 buffer: &mut Buf,
1189 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1190 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1191 ) {
1192 self.ingest(Arc::clone(&block), buffer, application, resolver)
1193 .await;
1194 let digest = block.digest();
1195 let handle = self
1196 .cache
1197 .put_verified(round, digest, Arc::unwrap_or_clone(block).into())
1198 .await;
1199 ack.send_lossy(handle);
1200 }
1201
1202 async fn ingest<Buf: Buffer<V>>(
1216 &mut self,
1217 block: Arc<V::Block>,
1218 buffer: &mut Buf,
1219 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1220 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1221 ) -> bool {
1222 self.block_subscriptions.notify(Arc::clone(&block));
1223
1224 if !self.floor.matches_pending_anchor(V::commitment(&block)) {
1225 return false;
1226 }
1227
1228 self.apply_pending_floor(block, buffer, application, resolver)
1229 .await;
1230 true
1231 }
1232
1233 async fn apply_pending_floor<Buf: Buffer<V>>(
1239 &mut self,
1240 block: Arc<V::Block>,
1241 buffer: &mut Buf,
1242 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1243 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1244 ) {
1245 let height = block.height();
1248 if height > Height::zero() {
1249 let parent_commitment = V::parent_commitment(&block);
1250 assert!(
1251 block.parent() == V::commitment_to_inner(parent_commitment),
1252 "floor block parent commitment mismatch"
1253 );
1254 }
1255
1256 if height <= self.floor.processed_height() {
1260 warn!(
1261 %height,
1262 existing = %self.floor.processed_height(),
1263 "floor not updated, at or below existing"
1264 );
1265 let finalization = self
1266 .floor
1267 .take_pending_anchor()
1268 .expect("pending floor anchor missing");
1269 self.update_processed_round_floor(height, finalization.round(), resolver)
1270 .await;
1271 if self.try_repair_gaps(buffer, resolver, application).await {
1272 self.sync_finalized().await;
1273 }
1274 self.try_dispatch_blocks(application).await;
1275 return;
1276 }
1277
1278 let digest = block.digest();
1279 let finalization = self
1280 .floor
1281 .take_pending_anchor()
1282 .expect("pending floor anchor missing");
1283 let round = finalization.round();
1284 try_join!(
1285 async {
1286 self.finalized_blocks
1287 .put(Arc::unwrap_or_clone(block).into())
1288 .await
1289 .map_err(Box::new)?;
1290 Ok::<_, BoxedError>(())
1291 },
1292 async {
1293 self.finalizations_by_height
1294 .put(height, digest, finalization)
1295 .await
1296 .map_err(Box::new)?;
1297 Ok::<_, BoxedError>(())
1298 }
1299 )
1300 .expect("failed to store floor anchor");
1301 self.sync_finalized().await;
1302
1303 if height > self.tip {
1304 application.report(Update::Tip(round, height, digest));
1305 self.tip = height;
1306 let _ = self.finalized_height.try_set(height.get());
1307 }
1308
1309 let dispatch_floor = height
1312 .previous()
1313 .expect("floor anchor above processed height must have predecessor");
1314 self.update_processed_height(dispatch_floor, resolver);
1315 self.update_processed_round_floor(dispatch_floor, round, resolver)
1316 .await;
1317 self.stream
1318 .sync()
1319 .await
1320 .expect("failed to sync floor metadata");
1321
1322 self.pending_acks.clear();
1325
1326 self.prune_after_floor(height)
1328 .await
1329 .expect("failed to prune data below floor");
1330
1331 if self.try_repair_gaps(buffer, resolver, application).await {
1335 self.sync_finalized().await;
1336 }
1337 self.try_dispatch_blocks(application).await;
1338 }
1339
1340 async fn handle_deliver<Buf: Buffer<V>>(
1344 &mut self,
1345 message: ResolverDelivery<V>,
1346 delivers: &mut Vec<PendingVerification<P::Scheme, V>>,
1347 buffer: &mut Buf,
1348 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1349 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1350 ) {
1351 let ResolverDelivery {
1352 delivery,
1353 mut value,
1354 response,
1355 } = message;
1356 let Delivery {
1357 key, subscribers, ..
1358 } = delivery;
1359 match key {
1360 Key::Block(commitment) => {
1361 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1362 let Ok(block) = V::Block::decode_cfg(value.as_ref(), &block_cfg) else {
1363 response.send_lossy(false);
1364 return;
1365 };
1366 if V::commitment(&block) != commitment {
1367 response.send_lossy(false);
1368 return;
1369 }
1370
1371 let block = Arc::new(block);
1375 if self
1376 .ingest(Arc::clone(&block), buffer, application, resolver)
1377 .await
1378 {
1379 response.send_lossy(true);
1380 return;
1381 }
1382
1383 let height = block.height();
1387 let digest = block.digest();
1388 let annotations = subscribers
1389 .map_into(|(annotation, _)| annotation)
1390 .into_vec();
1391
1392 let finalization = self.cache.get_finalization_for(digest).await;
1396 if let Some(finalization) = &finalization {
1397 self.update_processed_round_floor(height, finalization.round(), resolver)
1398 .await;
1399 }
1400 if finalization.is_some()
1401 || annotations
1402 .iter()
1403 .any(|annotation| matches!(annotation, Annotation::Finalized(_)))
1404 {
1405 self.store_finalization(
1406 height,
1407 digest,
1408 Arc::unwrap_or_clone(block),
1409 finalization,
1410 application,
1411 )
1412 .await;
1413 } else if annotations
1414 .iter()
1415 .any(|annotation| matches!(annotation, Annotation::Certified { .. }))
1416 && height > self.floor.processed_height()
1417 {
1418 if let Some(bounds) = self.epocher.containing(height) {
1419 self.cache
1420 .put_certified(
1421 bounds.epoch(),
1422 height,
1423 digest,
1424 Arc::unwrap_or_clone(block).into(),
1425 )
1426 .await;
1427 }
1428 }
1429 debug!(?digest, %height, "received block");
1430 response.send_lossy(true);
1431 }
1432 Key::Finalized { height } => {
1433 let Some((epoch, certificate_codec_config)) =
1434 self.certificate_codec_config_for_height(height)
1435 else {
1436 debug!(
1437 %height,
1438 floor = %self.floor.processed_height(),
1439 "ignoring stale delivery"
1440 );
1441 response.send_lossy(true);
1442 return;
1443 };
1444
1445 let Ok(finalization) =
1446 Finalization::read_cfg(&mut value, &certificate_codec_config)
1447 else {
1448 response.send_lossy(false);
1449 return;
1450 };
1451
1452 if finalization.epoch() != epoch {
1456 response.send_lossy(false);
1457 return;
1458 }
1459
1460 let Ok(block) =
1463 V::ApplicationBlock::decode_cfg(&mut value, &self.block_codec_config)
1464 else {
1465 response.send_lossy(false);
1466 return;
1467 };
1468
1469 let commitment = finalization.proposal.payload;
1478 if block.height() != height || block.digest() != V::commitment_to_inner(commitment)
1479 {
1480 response.send_lossy(false);
1481 return;
1482 }
1483 delivers.push(PendingVerification::Finalized {
1484 finalization,
1485 block,
1486 response,
1487 });
1488 }
1489 Key::Notarized { round } => {
1490 let Some(scheme) = self.provider.scheme(round.epoch()) else {
1491 debug!(
1492 ?round,
1493 floor = %self.floor.processed_height(),
1494 "ignoring stale delivery"
1495 );
1496 response.send_lossy(true);
1497 return;
1498 };
1499 let certificate_codec_config = scheme.certificate_codec_config();
1500 let Ok(notarization) =
1501 Notarization::read_cfg(&mut value, &certificate_codec_config)
1502 else {
1503 response.send_lossy(false);
1504 return;
1505 };
1506
1507 if notarization.round() != round {
1510 response.send_lossy(false);
1511 return;
1512 }
1513
1514 let commitment = notarization.proposal.payload;
1517 if !V::check_payload(scheme.as_ref(), commitment) {
1518 response.send_lossy(false);
1519 return;
1520 }
1521 let block_cfg = V::block_cfg(&self.block_codec_config, commitment);
1522 let Ok(block) = V::Block::decode_cfg(value, &block_cfg) else {
1523 response.send_lossy(false);
1524 return;
1525 };
1526
1527 if V::commitment(&block) != notarization.proposal.payload {
1528 response.send_lossy(false);
1529 return;
1530 }
1531 delivers.push(PendingVerification::Notarized {
1532 notarization,
1533 block,
1534 response,
1535 });
1536 }
1537 }
1538 }
1539
1540 #[tracing::instrument(name = "marshal.actor.verify_delivered", level = "info", skip_all, fields(count = delivers.len().traced()))]
1542 async fn verify_delivered<Buf: Buffer<V>>(
1543 &mut self,
1544 mut delivers: Vec<PendingVerification<P::Scheme, V>>,
1545 buffer: &mut Buf,
1546 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1547 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
1548 ) {
1549 delivers.retain(|item| !item.response_closed());
1550 if delivers.is_empty() {
1551 return;
1552 }
1553
1554 let certs: Vec<_> = delivers
1556 .iter()
1557 .map(|item| match item {
1558 PendingVerification::Finalized { finalization, .. } => (
1559 Subject::Finalize {
1560 proposal: &finalization.proposal,
1561 },
1562 &finalization.certificate,
1563 ),
1564 PendingVerification::Notarized { notarization, .. } => (
1565 Subject::Notarize {
1566 proposal: ¬arization.proposal,
1567 },
1568 ¬arization.certificate,
1569 ),
1570 })
1571 .collect();
1572
1573 let mut by_epoch: BTreeMap<Epoch, Vec<usize>> = BTreeMap::new();
1575 for (i, item) in delivers.iter().enumerate() {
1576 let epoch = match item {
1577 PendingVerification::Notarized { notarization, .. } => notarization.epoch(),
1578 PendingVerification::Finalized { finalization, .. } => finalization.epoch(),
1579 };
1580 by_epoch.entry(epoch).or_default().push(i);
1581 }
1582
1583 let mut verified = vec![false; delivers.len()];
1585 for (epoch, indices) in &by_epoch {
1586 let Some(scoped) = self.provider.scoped(*epoch) else {
1587 continue;
1588 };
1589 let group: Vec<_> = indices.iter().map(|&i| certs[i]).collect();
1590 let results =
1591 verify_certificates(self.context.as_mut(), &scoped, &group, &self.strategy);
1592 for (j, &idx) in indices.iter().enumerate() {
1593 verified[idx] = results[j];
1594 }
1595 }
1596
1597 for (index, item) in delivers.drain(..).enumerate() {
1599 if !verified[index] {
1600 match item {
1601 PendingVerification::Finalized { response, .. }
1602 | PendingVerification::Notarized { response, .. } => {
1603 response.send_lossy(false);
1604 }
1605 }
1606 continue;
1607 }
1608 match item {
1609 PendingVerification::Finalized {
1610 finalization,
1611 block,
1612 response,
1613 } => {
1614 response.send_lossy(true);
1616 let block = Arc::new(V::from_application_block(
1617 block,
1618 finalization.proposal.payload,
1619 ));
1620 let round = finalization.round();
1621 let height = block.height();
1622 let digest = block.digest();
1623 debug!(?round, %height, "received finalization");
1624
1625 if self
1628 .ingest(Arc::clone(&block), buffer, application, resolver)
1629 .await
1630 {
1631 continue;
1632 }
1633
1634 self.update_processed_round_floor(height, round, resolver)
1635 .await;
1636
1637 self.store_finalization(
1638 height,
1639 digest,
1640 Arc::unwrap_or_clone(block),
1641 Some(finalization),
1642 application,
1643 )
1644 .await;
1645 }
1646 PendingVerification::Notarized {
1647 notarization,
1648 block,
1649 response,
1650 } => {
1651 response.send_lossy(true);
1653 let round = notarization.round();
1654 let commitment = notarization.proposal.payload;
1655 let digest = V::commitment_to_inner(commitment);
1656 debug!(?round, ?digest, "received notarization");
1657
1658 let height = block.height();
1662 let block = Arc::new(block);
1663 let block_sync = self
1664 .cache
1665 .put_notarized(round, digest, block.as_ref().clone().into())
1666 .await;
1667 let notarization_sync = self
1668 .cache
1669 .put_notarization(round, digest, notarization)
1670 .await;
1671 join(
1672 block_sync.durable(round, "notarized"),
1673 notarization_sync.durable(round, "notarization"),
1674 )
1675 .await;
1676
1677 if self
1680 .ingest(Arc::clone(&block), buffer, application, resolver)
1681 .await
1682 {
1683 continue;
1684 }
1685
1686 if let Some(finalization) = self.cache.get_finalization_for(digest).await {
1691 self.update_processed_round_floor(height, finalization.round(), resolver)
1692 .await;
1693
1694 self.store_finalization(
1697 height,
1698 digest,
1699 Arc::unwrap_or_clone(block),
1700 Some(finalization),
1701 application,
1702 )
1703 .await;
1704 }
1705 }
1706 }
1707 }
1708 }
1709
1710 fn certificate_codec_config(
1712 &self,
1713 epoch: Epoch,
1714 ) -> Option<<<P::Scheme as Verifier>::Certificate as Read>::Cfg> {
1715 self.provider
1716 .scoped(epoch)
1717 .map(|scoped| scoped.certificate_codec_config())
1718 }
1719
1720 fn certificate_codec_config_for_height(
1722 &self,
1723 height: Height,
1724 ) -> Option<(Epoch, <<P::Scheme as Verifier>::Certificate as Read>::Cfg)> {
1725 let epoch = self.epocher.containing(height)?.epoch();
1726 self.certificate_codec_config(epoch)
1727 .map(|config| (epoch, config))
1728 }
1729
1730 async fn try_dispatch_blocks(
1769 &mut self,
1770 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1771 ) {
1772 if self.floor.blocks_progress() {
1774 return;
1775 }
1776
1777 let barrier = self.dispatch_gate.barrier();
1781 while self.pending_acks.has_capacity() {
1782 let next_height = self
1783 .pending_acks
1784 .next_dispatch_height(self.stream.next_height());
1785 if barrier.is_some_and(|lowest| next_height >= lowest) {
1786 return;
1787 }
1788 let Some(block) = self.get_finalized_block(next_height).await else {
1789 return;
1790 };
1791 assert_eq!(
1792 block.height(),
1793 next_height,
1794 "finalized block height mismatch"
1795 );
1796
1797 let (height, commitment) = (block.height(), V::commitment(&block));
1798 let (ack, ack_waiter) = A::handle();
1799 application.report(Update::Block(V::owned_into_inner_shared(block), ack));
1800 self.pending_acks.enqueue(PendingAck {
1801 height,
1802 commitment,
1803 receiver: ack_waiter,
1804 });
1805 }
1806 }
1807
1808 #[tracing::instrument(name = "marshal.actor.sync_finalized", level = "info", skip_all)]
1824 async fn sync_finalized(&mut self) {
1825 if let Err(e) = try_join!(
1826 async {
1827 self.finalized_blocks.sync().await.map_err(Box::new)?;
1828 Ok::<_, BoxedError>(())
1829 },
1830 async {
1831 self.finalizations_by_height
1832 .sync()
1833 .await
1834 .map_err(Box::new)?;
1835 Ok::<_, BoxedError>(())
1836 },
1837 ) {
1838 panic!("failed to sync finalization archives: {e}");
1839 }
1840
1841 self.dispatch_gate.clear();
1844 }
1845
1846 #[tracing::instrument(name = "marshal.actor.start_finalized_sync", level = "info", skip_all)]
1862 async fn start_finalized_sync(&mut self, round: Round, syncs: &mut Pool<PooledSync>) {
1863 let Some(seq) = self.dispatch_gate.adopt() else {
1866 return;
1867 };
1868
1869 let (blocks, finalizations) = match try_join!(
1870 async {
1871 let handle = self.finalized_blocks.start_sync().await.map_err(Box::new)?;
1872 Ok::<_, BoxedError>(handle)
1873 },
1874 async {
1875 let handle = self
1876 .finalizations_by_height
1877 .start_sync()
1878 .await
1879 .map_err(Box::new)?;
1880 Ok::<_, BoxedError>(handle)
1881 },
1882 ) {
1883 Ok(handles) => handles,
1884 Err(e) => panic!("failed to start finalization archive sync: {e}"),
1885 };
1886 syncs.push(async move {
1887 let (blocks, finalizations) = join(
1888 blocks.durable(round, "finalized blocks"),
1889 finalizations.durable(round, "finalizations"),
1890 )
1891 .await;
1892 if blocks && finalizations {
1893 PooledSync::Finalized(seq)
1894 } else {
1895 PooledSync::Observed
1898 }
1899 });
1900 }
1901
1902 async fn get_finalized_block(&self, height: Height) -> Option<V::Block> {
1906 match self
1907 .finalized_blocks
1908 .get(ArchiveID::Index(height.get()))
1909 .await
1910 {
1911 Ok(stored) => stored.map(|stored| stored.into()),
1912 Err(e) => panic!("failed to get block: {e}"),
1913 }
1914 }
1915
1916 async fn get_finalization_by_height(
1918 &self,
1919 height: Height,
1920 ) -> Option<Finalization<P::Scheme, V::Commitment>> {
1921 match self
1922 .finalizations_by_height
1923 .get(ArchiveID::Index(height.get()))
1924 .await
1925 {
1926 Ok(finalization) => finalization,
1927 Err(e) => panic!("failed to get finalization: {e}"),
1928 }
1929 }
1930
1931 async fn has_finalization_by_height(&self, height: Height) -> bool {
1934 match self.finalizations_by_height.has(height).await {
1935 Ok(has) => has,
1936 Err(e) => panic!("failed to check finalization: {e}"),
1937 }
1938 }
1939
1940 async fn get_info_by_height(
1943 &self,
1944 height: Height,
1945 ) -> Option<(Height, <V::Block as Digestible>::Digest)> {
1946 if let Some(finalization) = self.get_finalization_by_height(height).await {
1947 return Some((
1948 height,
1949 V::commitment_to_inner(finalization.proposal.payload),
1950 ));
1951 }
1952
1953 self.get_finalized_block(height)
1954 .await
1955 .map(|block| (block.height(), block.digest()))
1956 }
1957
1958 async fn store_finalization(
1972 &mut self,
1973 height: Height,
1974 digest: <V::Block as Digestible>::Digest,
1975 block: V::Block,
1976 finalization: Option<Finalization<P::Scheme, V::Commitment>>,
1977 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
1978 ) -> bool {
1979 if height <= self.floor.processed_height() {
1983 debug!(
1984 %height,
1985 floor = %self.floor.processed_height(),
1986 ?digest,
1987 "dropping finalization at or below processed height floor"
1988 );
1989 return false;
1990 }
1991
1992 let stored: V::StoredBlock = block.into();
1994 let round = finalization.as_ref().map(|f| f.round());
1995
1996 if let Err(e) = try_join!(
1998 async {
2000 self.finalized_blocks.put(stored).await.map_err(Box::new)?;
2001 Ok::<_, BoxedError>(())
2002 },
2003 async {
2005 if let Some(finalization) = finalization {
2006 self.finalizations_by_height
2007 .put(height, digest, finalization)
2008 .await
2009 .map_err(Box::new)?;
2010 }
2011 Ok::<_, BoxedError>(())
2012 }
2013 ) {
2014 panic!("failed to finalize: {e}");
2015 }
2016
2017 self.dispatch_gate.defer(height);
2020
2021 if let Some(round) = round.filter(|_| height > self.tip) {
2023 application.report(Update::Tip(round, height, digest));
2024 self.tip = height;
2025 let _ = self.finalized_height.try_set(height.get());
2026 }
2027
2028 true
2029 }
2030
2031 async fn get_latest(&mut self) -> Option<(Height, <V::Block as Digestible>::Digest, Round)> {
2044 let height = self.finalizations_by_height.last_index()?;
2045 let finalization = self
2046 .get_finalization_by_height(height)
2047 .await
2048 .expect("finalization missing");
2049 Some((
2050 height,
2051 V::commitment_to_inner(finalization.proposal.payload),
2052 finalization.round(),
2053 ))
2054 }
2055
2056 async fn find_block_in_storage(
2060 &self,
2061 digest: <V::Block as Digestible>::Digest,
2062 ) -> Option<V::Block> {
2063 if let Some(block) = self.cache.find_block_matching(digest, |_| true).await {
2065 return Some(block.into());
2066 }
2067 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2069 Ok(stored) => stored.map(|stored| stored.into()),
2070 Err(e) => panic!("failed to get block: {e}"),
2071 }
2072 }
2073
2074 async fn find_block_in_storage_by_commitment(
2076 &self,
2077 commitment: V::Commitment,
2078 ) -> Option<V::Block> {
2079 let digest = V::commitment_to_inner(commitment);
2080 if let Some(block) = self
2081 .cache
2082 .find_block_matching(digest, |stored| V::stored_commitment(stored) == commitment)
2083 .await
2084 {
2085 return Some(block.into());
2086 }
2087
2088 match self.finalized_blocks.get(ArchiveID::Key(&digest)).await {
2089 Ok(Some(stored)) => {
2090 (V::stored_commitment(&stored) == commitment).then(|| stored.into())
2091 }
2092 Ok(None) => None,
2093 Err(e) => panic!("failed to get block: {e}"),
2094 }
2095 }
2096
2097 async fn find_block_by_digest<Buf: Buffer<V>>(
2102 &self,
2103 buffer: &Buf,
2104 digest: <V::Block as Digestible>::Digest,
2105 ) -> Option<Arc<V::Block>> {
2106 if let Some(block) = buffer.find_by_digest(digest).await {
2107 return Some(block);
2108 }
2109 self.find_block_in_storage(digest).await.map(Arc::new)
2110 }
2111
2112 async fn find_block_by_commitment<Buf: Buffer<V>>(
2117 &self,
2118 buffer: &Buf,
2119 commitment: V::Commitment,
2120 ) -> Option<Arc<V::Block>> {
2121 if let Some(block) = buffer.find_by_commitment(commitment).await {
2122 return Some(block);
2123 }
2124 self.find_block_in_storage_by_commitment(commitment)
2125 .await
2126 .map(Arc::new)
2127 }
2128
2129 #[tracing::instrument(name = "marshal.actor.try_repair_gaps", level = "info", skip_all)]
2141 async fn try_repair_gaps<Buf: Buffer<V>>(
2142 &mut self,
2143 buffer: &mut Buf,
2144 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2145 application: &mut impl Reporter<Activity = Update<V::ApplicationBlock, A>>,
2146 ) -> bool {
2147 if self.floor.blocks_progress() {
2150 return false;
2151 }
2152
2153 let mut wrote = false;
2154 let start = self.floor.processed_height().next();
2155
2156 if let Some(last_finalized) = self.finalizations_by_height.last_index() {
2159 let have_block = self
2160 .finalized_blocks
2161 .last_index()
2162 .is_some_and(|last| last >= last_finalized);
2163 if last_finalized > self.floor.processed_height() && !have_block {
2164 let finalization = self
2166 .get_finalization_by_height(last_finalized)
2167 .await
2168 .expect("finalization missing");
2169 let commitment = finalization.proposal.payload;
2170 if let Some(block) = self.find_block_by_commitment(buffer, commitment).await {
2171 let digest = block.digest();
2173 wrote |= self
2174 .store_finalization(
2175 last_finalized,
2176 digest,
2177 Arc::unwrap_or_clone(block),
2178 Some(finalization),
2179 application,
2180 )
2181 .await;
2182 } else {
2183 self.floor
2185 .fetch_if_permitted(
2186 resolver,
2187 Request::finalized_block_by_height(commitment, last_finalized),
2188 )
2189 .ignore();
2190 }
2191 }
2192 }
2193
2194 'cache_repair: loop {
2196 let (gap_start, Some(gap_end)) = self.finalized_blocks.next_gap(start) else {
2197 return wrote;
2199 };
2200
2201 let Some(cursor) = self.get_finalized_block(gap_end).await else {
2205 panic!("gapped block missing that should exist: {gap_end}");
2206 };
2207 let (mut height, mut parent_digest, mut parent_commitment) = (
2208 cursor.height(),
2209 cursor.parent(),
2210 V::parent_commitment(&cursor),
2211 );
2212
2213 let gap_start = gap_start.map(Height::next).unwrap_or(start);
2217
2218 while height > gap_start {
2220 if let Some(block) = self
2221 .find_block_by_commitment(buffer, parent_commitment)
2222 .await
2223 {
2224 let finalization = self.cache.get_finalization_for(parent_digest).await;
2225 let next = (block.height(), block.parent(), V::parent_commitment(&block));
2226 wrote |= self
2227 .store_finalization(
2228 next.0,
2229 parent_digest,
2230 Arc::unwrap_or_clone(block),
2231 finalization,
2232 application,
2233 )
2234 .await;
2235 debug!(height = %next.0, "repaired block");
2236 (height, parent_digest, parent_commitment) = next;
2237 } else {
2238 let parent_height = height
2244 .previous()
2245 .expect("cursor above gap start has a parent");
2246 self.floor
2247 .fetch_if_permitted(
2248 resolver,
2249 Request::finalized_block_by_height(parent_commitment, parent_height),
2250 )
2251 .ignore();
2252 break 'cache_repair;
2253 }
2254 }
2255 }
2256
2257 let missing_items = self
2263 .finalized_blocks
2264 .missing_items(start, self.max_repair.get());
2265 let requests: Vec<_> = missing_items.into_iter().map(Request::finalized).collect();
2266 if !requests.is_empty() {
2267 self.floor
2268 .fetch_all_if_permitted(resolver, requests)
2269 .ignore();
2270 }
2271 wrote
2272 }
2273
2274 fn update_processed_height(
2277 &mut self,
2278 height: Height,
2279 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2280 ) {
2281 self.stream.acknowledge(height);
2282 self.floor.set_processed_height(height);
2283 let _ = self
2284 .processed_height
2285 .try_set(self.floor.processed_height().get());
2286
2287 resolver.retain(handler::above_height_floor::<V::Commitment>(height));
2289 }
2290
2291 async fn latest_processed_round(finalizations_by_height: &FC, height: Option<Height>) -> Round {
2293 let Some(height) = height else {
2294 return Round::zero();
2295 };
2296 let Some(finalization_height) = finalizations_by_height
2297 .ranges_from(Height::zero())
2298 .filter_map(|(start, end)| (start <= height).then_some(end.min(height)))
2299 .max()
2300 else {
2301 return Round::zero();
2302 };
2303
2304 match finalizations_by_height
2305 .get(ArchiveID::Index(finalization_height.get()))
2306 .await
2307 {
2308 Ok(Some(finalization)) => finalization.round(),
2309 Ok(None) => panic!("processed finalization missing from stored range"),
2310 Err(err) => panic!("failed to get processed finalization: {err}"),
2311 }
2312 }
2313
2314 async fn update_processed_round(
2316 &mut self,
2317 height: Height,
2318 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2319 ) {
2320 let Some(finalization) = self.get_finalization_by_height(height).await else {
2321 return;
2322 };
2323 self.update_processed_round_floor(height, finalization.round(), resolver)
2324 .await;
2325 }
2326
2327 async fn update_processed_round_floor(
2329 &mut self,
2330 height: Height,
2331 round: Round,
2332 resolver: &mut impl Resolver<Key = ResolverRequestFor<V>, Subscriber = Annotation>,
2333 ) {
2334 if height > self.floor.processed_height() || round <= self.floor.processed_round() {
2335 return;
2336 }
2337
2338 let previous = self.floor.processed_round();
2339 self.floor.set_processed_round(round);
2340
2341 let prune_round = Round::new(
2344 previous.epoch(),
2345 previous.view().saturating_sub(self.view_retention_timeout),
2346 );
2347 self.cache.prune_by_view(prune_round).await;
2348
2349 resolver.retain(handler::above_round_floor::<V::Commitment>(
2351 self.floor.processed_round(),
2352 ));
2353 }
2354
2355 async fn prune_finalized_archives(&mut self, height: Height) -> Result<(), BoxedError> {
2357 try_join!(
2359 async {
2360 self.finalized_blocks
2361 .prune(height)
2362 .await
2363 .map_err(Box::new)?;
2364 Ok::<_, BoxedError>(())
2365 },
2366 async {
2367 self.finalizations_by_height
2368 .prune(height)
2369 .await
2370 .map_err(Box::new)?;
2371 Ok::<_, BoxedError>(())
2372 }
2373 )?;
2374 Ok(())
2375 }
2376
2377 async fn prune_after_floor(&mut self, height: Height) -> Result<(), BoxedError> {
2379 let cache = &mut self.cache;
2380 let finalized_blocks = &mut self.finalized_blocks;
2381 let finalizations_by_height = &mut self.finalizations_by_height;
2382 try_join!(
2383 async {
2384 cache.prune_by_height(height).await;
2385 Ok::<_, BoxedError>(())
2386 },
2387 async {
2388 finalized_blocks.prune(height).await.map_err(Box::new)?;
2389 Ok::<_, BoxedError>(())
2390 },
2391 async {
2392 finalizations_by_height
2393 .prune(height)
2394 .await
2395 .map_err(Box::new)?;
2396 Ok::<_, BoxedError>(())
2397 }
2398 )?;
2399 Ok(())
2400 }
2401}