1use super::{
2 bbr::{rounded_usize, BbrState},
3 config::*,
4 request::*,
5 work_queue::WorkQueue,
6 *,
7};
8use crate::zakura::{
9 chain_frontier_from_parts, Frontier, FrontierUpdate, ServicePeerDirection, ServicePeerSnapshot,
10 ZakuraBlockSyncCandidateState,
11};
12
13pub(super) const EFFECTIVE_BS_OUTBOUND_INFLIGHT_PER_PEER: usize = MAX_BS_INFLIGHT_REQUESTS as usize;
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
23pub struct BlockSyncFrontiers {
24 pub finalized_height: block::Height,
26 pub verified_block_tip: block::Height,
28 pub verified_block_hash: block::Hash,
30}
31
32#[derive(Clone, Debug)]
34pub struct BlockSyncStartup {
35 pub frontiers: BlockSyncFrontiers,
37 pub best_header_tip: (block::Height, block::Hash),
39 pub header_tip: Option<watch::Receiver<(block::Height, block::Hash)>>,
41 pub frontier_updates: Option<watch::Receiver<FrontierUpdate>>,
43 pub config: ZakuraBlockSyncConfig,
45 pub shutdown: CancellationToken,
47 pub state_queries_enabled: bool,
49 pub trace: ZakuraTrace,
51}
52
53impl BlockSyncStartup {
54 pub fn new(
56 frontiers: BlockSyncFrontiers,
57 best_header_tip: (block::Height, block::Hash),
58 header_tip: watch::Receiver<(block::Height, block::Hash)>,
59 config: ZakuraBlockSyncConfig,
60 ) -> Self {
61 Self {
62 frontiers,
63 best_header_tip,
64 header_tip: Some(header_tip),
65 frontier_updates: None,
66 config,
67 shutdown: CancellationToken::new(),
68 state_queries_enabled: true,
69 trace: ZakuraTrace::noop(),
70 }
71 }
72
73 pub fn new_with_exchange(
75 frontiers: BlockSyncFrontiers,
76 best_header_tip: (block::Height, block::Hash),
77 frontier_updates: watch::Receiver<FrontierUpdate>,
78 config: ZakuraBlockSyncConfig,
79 ) -> Self {
80 Self {
81 frontiers,
82 best_header_tip,
83 header_tip: None,
84 frontier_updates: Some(frontier_updates),
85 config,
86 shutdown: CancellationToken::new(),
87 state_queries_enabled: true,
88 trace: ZakuraTrace::noop(),
89 }
90 }
91
92 pub fn frontier_update_from_parts(
94 frontiers: BlockSyncFrontiers,
95 best_header_tip: (block::Height, block::Hash),
96 ) -> FrontierUpdate {
97 FrontierUpdate {
98 frontier: chain_frontier_from_parts(
99 frontiers.finalized_height,
100 Frontier::new(frontiers.verified_block_tip, frontiers.verified_block_hash),
101 Frontier::new(best_header_tip.0, best_header_tip.1),
102 ),
103 change: crate::zakura::FrontierChange::Snapshot,
104 }
105 }
106
107 pub(super) fn inert(config: ZakuraBlockSyncConfig) -> Self {
108 Self {
109 frontiers: BlockSyncFrontiers {
110 finalized_height: block::Height::MIN,
111 verified_block_tip: block::Height::MIN,
112 verified_block_hash: block::Hash([0; 32]),
113 },
114 best_header_tip: (block::Height::MIN, block::Hash([0; 32])),
115 header_tip: None,
116 frontier_updates: None,
117 config,
118 shutdown: CancellationToken::new(),
119 state_queries_enabled: false,
120 trace: ZakuraTrace::noop(),
121 }
122 }
123}
124
125#[derive(Clone, Debug)]
132pub struct BlockSyncHandle {
133 pub(super) events: mpsc::Sender<BlockSyncEvent>,
134 pub(super) lifecycle: mpsc::UnboundedSender<BlockSyncEvent>,
135 pub(super) peers: watch::Receiver<ServicePeerSnapshot>,
136 pub(super) status: watch::Receiver<BlockSyncStatus>,
137 pub(super) candidates: watch::Receiver<ZakuraBlockSyncCandidateState>,
138 pub(super) routine_wiring: Option<RoutineWiring>,
142}
143
144#[derive(Clone, Debug)]
148pub(super) struct RoutineWiring {
149 pub(super) config: ZakuraBlockSyncConfig,
150 pub(super) budget: ByteBudget,
151 pub(super) work: Arc<WorkQueue>,
152 pub(super) registry: Arc<super::peer_registry::PeerRegistry>,
153 pub(super) received_throughput: Arc<std::sync::Mutex<ThroughputMeter>>,
154 pub(super) sequencer_input: mpsc::Sender<super::sequencer_task::SequencedBody>,
155 pub(super) sequencer_input_bytes: Arc<std::sync::atomic::AtomicU64>,
156 pub(super) sequencer_input_decoded_attributed_memory_bytes: Arc<std::sync::atomic::AtomicU64>,
157 pub(super) actions: mpsc::Sender<BlockSyncAction>,
158 pub(super) routine_to_reactor: mpsc::Sender<super::events::RoutineToReactor>,
159 pub(super) view: watch::Receiver<super::sequencer_task::SequencerView>,
160 pub(super) trace: ZakuraTrace,
161}
162
163impl BlockSyncHandle {
164 pub async fn send(
166 &self,
167 event: BlockSyncEvent,
168 ) -> Result<(), mpsc::error::SendError<BlockSyncEvent>> {
169 self.events.send(event).await
170 }
171
172 pub fn try_send(
174 &self,
175 event: BlockSyncEvent,
176 ) -> Result<(), mpsc::error::TrySendError<BlockSyncEvent>> {
177 self.events.try_send(event)
178 }
179
180 pub fn send_control(
182 &self,
183 event: BlockSyncEvent,
184 ) -> Result<(), mpsc::error::SendError<BlockSyncEvent>> {
185 self.lifecycle
186 .send(event)
187 .map_err(|error| mpsc::error::SendError(error.0))
188 }
189
190 pub fn send_lifecycle(
192 &self,
193 event: BlockSyncEvent,
194 ) -> Result<(), mpsc::error::SendError<BlockSyncEvent>> {
195 self.send_control(event)
196 }
197
198 pub fn peer_snapshot(&self) -> ServicePeerSnapshot {
200 *self.peers.borrow()
201 }
202
203 pub fn subscribe_status(&self) -> watch::Receiver<BlockSyncStatus> {
205 self.status.clone()
206 }
207
208 pub fn local_status(&self) -> BlockSyncStatus {
210 *self.status.borrow()
211 }
212
213 pub fn subscribe_candidate_state(&self) -> watch::Receiver<ZakuraBlockSyncCandidateState> {
215 self.candidates.clone()
216 }
217
218 pub fn candidate_state(&self) -> ZakuraBlockSyncCandidateState {
220 self.candidates.borrow().clone()
221 }
222}
223
224#[derive(Debug)]
225pub(super) struct BlockSyncState {
226 pub(super) finalized_height: block::Height,
227 pub(super) verified_block_hash: block::Hash,
228 pub(super) servable_high: block::Height,
229 pub(super) servable_hash: block::Hash,
230 pub(super) best_header_tip: block::Height,
231 pub(super) best_header_hash: block::Hash,
232 pub(super) peers: HashMap<ZakuraPeerId, PeerBlockState>,
237 pub(super) parked_peers: HashSet<ZakuraPeerId>,
238 pub(super) work_queue: Arc<WorkQueue>,
244 pub(super) budget: ByteBudget,
245 pub(super) needed_heights: Vec<block::Height>,
246 pub(super) status_refresh: RateMeter,
247 pub(super) pending_status_refresh: bool,
248 pub(super) last_advertised_status: BlockSyncStatus,
249 pub(super) received_throughput: Arc<std::sync::Mutex<ThroughputMeter>>,
254}
255
256impl BlockSyncState {
257 pub(super) fn new(startup: &BlockSyncStartup) -> Self {
258 let last_advertised_status = BlockSyncStatus {
259 servable_low: block::Height::MIN,
260 servable_high: startup.frontiers.verified_block_tip,
261 tip_hash: startup.frontiers.verified_block_hash,
262 max_blocks_per_response: startup.config.advertised_max_blocks_per_response(),
263 max_inflight_requests: startup.config.advertised_max_inflight_requests(),
264 max_response_bytes: startup.config.advertised_max_response_bytes(),
265 };
266
267 Self {
268 finalized_height: startup.frontiers.finalized_height,
269 verified_block_hash: startup.frontiers.verified_block_hash,
270 servable_high: startup.frontiers.verified_block_tip,
271 servable_hash: startup.frontiers.verified_block_hash,
272 best_header_tip: startup.best_header_tip.0,
273 best_header_hash: startup.best_header_tip.1,
274 peers: HashMap::new(),
275 parked_peers: HashSet::new(),
276 work_queue: Arc::new(WorkQueue::new(startup.frontiers.verified_block_tip)),
277 budget: ByteBudget::new(startup.config.max_inflight_block_bytes),
278 needed_heights: Vec::new(),
279 status_refresh: RateMeter::new(startup.config.status_refresh_interval),
280 pending_status_refresh: false,
281 last_advertised_status,
282 received_throughput: Arc::new(std::sync::Mutex::new(ThroughputMeter::new(
283 Instant::now(),
284 ))),
285 }
286 }
287
288 pub(super) fn peer_snapshot(&self, limits: ServicePeerLimits) -> ServicePeerSnapshot {
289 let inbound = self
290 .peers
291 .values()
292 .filter(|peer| peer.direction == ServicePeerDirection::Inbound)
293 .count();
294 let outbound = self
295 .peers
296 .values()
297 .filter(|peer| peer.direction == ServicePeerDirection::Outbound)
298 .count();
299 ServicePeerSnapshot::new(inbound, outbound, limits)
300 }
301}
302
303#[derive(Clone, Debug)]
307pub(super) struct DownloadWindow {
308 pub(super) max_inflight_requests: u32,
309 pub(super) outstanding: Vec<OutstandingBlockRange>,
310 bbr: BbrState,
314 cwnd_unit: CwndUnit,
316 pub(super) startup_request_cap: usize,
318 pub(super) block_liveness_deadline: Option<Instant>,
320 pub(super) last_request_at: Option<Instant>,
322 pub(super) last_block_at: Option<Instant>,
324 pub(super) requests_without_block_progress: u32,
326 max_requests_without_block_progress: u32,
328 initial_block_probe_requests: u32,
330}
331
332#[derive(Copy, Clone, Debug, Eq, PartialEq)]
333pub(super) enum LivenessOutcome {
334 Ok,
335 Disarm,
336 Disconnect,
337}
338
339impl DownloadWindow {
340 pub(super) fn new(config: &ZakuraBlockSyncConfig) -> Self {
341 Self {
342 max_inflight_requests: config.advertised_max_inflight_requests(),
343 outstanding: Vec::new(),
344 bbr: BbrState::new(config),
345 cwnd_unit: config.bbr_cwnd_unit,
346 startup_request_cap: usize::try_from(config.initial_inflight_requests)
347 .unwrap_or(usize::MAX)
348 .max(1),
349 block_liveness_deadline: None,
350 last_request_at: None,
351 last_block_at: None,
352 requests_without_block_progress: 0,
353 max_requests_without_block_progress: config.max_requests_without_block_progress,
354 initial_block_probe_requests: config.initial_block_probe_requests,
355 }
356 }
357
358 pub(super) fn delivery_snapshot(&self, now: Instant) -> DeliverySnapshot {
359 self.bbr.delivery_snapshot(now)
360 }
361
362 pub(super) fn record_delivery(
369 &mut self,
370 now: Instant,
371 elapsed: Duration,
372 blocks: u32,
373 delivered_bytes: u64,
374 snapshot: DeliverySnapshot,
375 ) {
376 let inflight = match self.cwnd_unit {
383 CwndUnit::Blocks => self.outstanding.len() as u64,
385 CwndUnit::Bytes => self.outstanding_reserved_bytes(),
386 };
387 self.bbr
388 .record_delivery(now, elapsed, blocks, delivered_bytes, inflight, snapshot);
389 }
390
391 pub(super) fn bbr_effective_cwnd(&self) -> usize {
398 match self.cwnd_unit {
399 CwndUnit::Blocks => self.bbr.effective_cwnd(),
400 CwndUnit::Bytes => {
401 let cwnd_bytes = self.bbr.effective_cwnd() as u64;
402 let rep = self.representative_body_bytes();
403 usize::try_from((cwnd_bytes / rep.max(1)).max(1)).unwrap_or(usize::MAX)
404 }
405 }
406 }
407
408 pub(super) fn bbr_effective_cwnd_bytes(&self) -> Option<u64> {
410 matches!(self.cwnd_unit, CwndUnit::Bytes).then(|| self.bbr.effective_cwnd() as u64)
411 }
412
413 fn representative_body_bytes(&self) -> u64 {
418 let outstanding = self.outstanding.len() as u64;
419 if outstanding == 0 {
420 return block::MAX_BLOCK_BYTES;
421 }
422 (self.outstanding_reserved_bytes() / outstanding).max(1)
423 }
424
425 pub(super) fn bbr_rtprop_ms(&self, now: Instant) -> Option<u64> {
430 self.bbr.rtprop_ms(now)
431 }
432
433 pub(super) fn bbr_btlbw_milliblocks(&self, now: Instant) -> Option<u64> {
437 matches!(self.cwnd_unit, CwndUnit::Blocks)
438 .then(|| self.bbr.btlbw_milliblocks_per_sec(now))
439 .flatten()
440 }
441
442 pub(super) fn bbr_btlbw_bytes_per_sec(&self, now: Instant) -> Option<u64> {
446 if !matches!(self.cwnd_unit, CwndUnit::Bytes) {
447 return None;
448 }
449 self.bbr
450 .btlbw_units_per_sec(now)
451 .map(|rate| rate.round() as u64)
453 }
454
455 pub(super) fn bbr_inflight_bytes(&self) -> u64 {
458 self.outstanding_reserved_bytes()
459 }
460
461 pub(super) fn bbr_delivered(&self) -> u64 {
464 self.bbr.delivered()
465 }
466
467 pub(super) fn bbr_phase_code(&self) -> u64 {
469 self.bbr.phase_code()
470 }
471
472 pub(super) fn bbr_smoothed_elapsed_ms(&self) -> Option<u64> {
474 self.bbr.smoothed_elapsed_ms()
475 }
476
477 pub(super) fn bbr_delay_cap(&self) -> Option<u64> {
479 self.bbr
480 .delay_cap()
481 .map(|cap| u64::try_from(cap).unwrap_or(u64::MAX))
482 }
483
484 pub(super) fn bbr_reliability_permille(&self) -> u64 {
487 self.bbr.reliability_permille()
488 }
489
490 pub(super) fn available_slots(&self) -> usize {
491 self.available_slots_at(Instant::now())
492 }
493
494 pub(super) fn available_slots_at(&self, now: Instant) -> usize {
495 self.available_slots_with_bonus_at(0, now)
496 }
497
498 pub(super) fn available_slots_with_bonus_at(&self, bonus: usize, now: Instant) -> usize {
513 let hard_cap = self.hard_outbound_capacity();
517 match self.cwnd_unit {
518 CwndUnit::Blocks => {
519 let cwnd_slots = self
520 .bbr
521 .effective_cwnd()
522 .saturating_add(bonus)
523 .min(hard_cap);
524 cwnd_slots.saturating_sub(self.outstanding.len())
525 }
526 CwndUnit::Bytes => {
527 let outstanding = self.outstanding.len();
533 if outstanding >= hard_cap {
534 return 0;
535 }
536 if !self.bbr.has_fresh_bdp(now) {
537 let startup_cap = self.startup_request_cap.saturating_add(bonus).min(hard_cap);
538 if outstanding >= startup_cap {
539 return 0;
540 }
541 }
542 let reserved = self.outstanding_reserved_bytes();
550 let representative = self.representative_body_bytes();
551 let bonus_bytes = (bonus as u64).saturating_mul(representative);
552 let cwnd_bytes = (self.bbr.effective_cwnd() as u64).saturating_add(bonus_bytes);
553 usize::try_from(cwnd_bytes.saturating_sub(reserved)).unwrap_or(usize::MAX)
554 }
555 }
556 }
557
558 pub(super) fn cwnd_byte_headroom_at(&self, bonus: usize, now: Instant) -> Option<u64> {
568 match self.cwnd_unit {
569 CwndUnit::Blocks => None,
570 CwndUnit::Bytes => Some(self.available_slots_with_bonus_at(bonus, now) as u64),
573 }
574 }
575
576 #[cfg(test)]
577 pub(super) fn available_slots_with_bonus(&self, bonus: usize) -> usize {
578 self.available_slots_with_bonus_at(bonus, Instant::now())
579 }
580
581 #[cfg(test)]
582 pub(super) fn cwnd_byte_headroom(&self, bonus: usize) -> Option<u64> {
583 self.cwnd_byte_headroom_at(bonus, Instant::now())
584 }
585
586 pub(super) fn scaled_floor_bonus(&self, base: usize) -> usize {
592 rounded_usize(base as f64 * self.bbr.reliability_factor(), 0)
596 }
597
598 #[cfg(test)]
599 pub(super) fn reliability_factor(&self) -> f64 {
600 self.bbr.reliability_factor()
601 }
602
603 fn outstanding_reserved_bytes(&self) -> u64 {
607 self.outstanding.iter().fold(0u64, |acc, range| {
608 acc.saturating_add(range.reserved_bytes())
609 })
610 }
611
612 pub(super) fn record_timeout(&mut self, timed_out: usize) {
617 self.bbr.dip_on_timeout();
618 self.bbr.penalize_reliability(timed_out);
619 }
620
621 pub(super) fn penalize_short_response(&mut self, missing: usize) {
633 if missing > 0 {
634 self.bbr.penalize_reliability(1);
635 }
636 }
637
638 pub(super) fn credit_late_delivery(&mut self) {
643 self.bbr.credit_late_success();
644 }
645
646 pub(super) fn has_block_progress(&self) -> bool {
647 self.last_block_at.is_some()
648 }
649
650 pub(super) fn no_progress_request_cap(&self) -> u32 {
651 if self.has_block_progress() {
652 self.max_requests_without_block_progress
653 } else {
654 self.initial_block_probe_requests
655 }
656 }
657
658 pub(super) fn arm_liveness(&mut self, now: Instant, timeout: Duration) {
659 self.last_request_at = Some(now);
660 self.requests_without_block_progress =
661 self.requests_without_block_progress.saturating_add(1);
662 if self.block_liveness_deadline.is_none() {
663 self.block_liveness_deadline = Some(now + timeout);
664 }
665 }
666
667 pub(super) fn note_block_progress(&mut self, now: Instant, timeout: Duration) {
668 self.last_block_at = Some(now);
669 self.requests_without_block_progress = 0;
670 self.block_liveness_deadline = if self.outstanding.is_empty() {
671 None
672 } else {
673 Some(now + timeout)
674 };
675 }
676
677 pub(super) fn disarm_liveness_after_progress_if_idle(&mut self) {
678 if self.outstanding.is_empty()
679 && matches!(
680 (self.last_request_at, self.last_block_at),
681 (Some(request_at), Some(block_at)) if block_at >= request_at
682 )
683 {
684 self.block_liveness_deadline = None;
685 }
686 }
687
688 pub(super) fn clear_liveness_if_idle(&mut self) {
689 if self.outstanding.is_empty() {
690 self.block_liveness_deadline = None;
691 }
692 }
693
694 pub(super) fn note_view_reset(&mut self) {
701 self.requests_without_block_progress = 0;
702 self.clear_liveness_if_idle();
703 }
704
705 pub(super) fn extend_liveness_deadline(&mut self, now: Instant, timeout: Duration) {
710 self.block_liveness_deadline = Some(now + timeout);
711 }
712
713 pub(super) fn check_liveness(&self, now: Instant) -> LivenessOutcome {
714 match self.block_liveness_deadline {
715 None => LivenessOutcome::Ok,
716 Some(deadline) if self.last_request_at.is_none() && now >= deadline => {
723 LivenessOutcome::Disarm
724 }
725 Some(deadline) if now < deadline => LivenessOutcome::Ok,
726 Some(_) => LivenessOutcome::Disconnect,
727 }
728 }
729
730 pub(super) fn hard_outbound_capacity(&self) -> usize {
731 usize::try_from(self.max_inflight_requests)
732 .expect("u32 max inflight requests fits in usize on supported targets")
733 .min(EFFECTIVE_BS_OUTBOUND_INFLIGHT_PER_PEER)
734 }
735
736 pub(super) fn outstanding_index_for_height(&self, height: block::Height) -> Option<usize> {
737 self.outstanding
738 .iter()
739 .position(|outstanding| outstanding.request.contains(height))
740 }
741
742 pub(super) fn outstanding_index_for_start(&self, start_height: block::Height) -> Option<usize> {
743 self.outstanding
744 .iter()
745 .position(|outstanding| outstanding.request.start_height == start_height)
746 }
747}
748
749#[derive(Debug)]
756pub(super) struct PeerBlockState {
757 pub(super) session: BlockSyncPeerSession,
758 pub(super) direction: ServicePeerDirection,
759 pub(super) refresh_meter: RateMeter,
765 pub(super) served_blocks_inflight: u32,
766 pub(super) served_block_requests: VecDeque<(block::Height, Instant)>,
767}
768
769impl PeerBlockState {
770 pub(super) fn new(session: BlockSyncPeerSession, config: &ZakuraBlockSyncConfig) -> Self {
771 Self {
772 direction: session.direction(),
773 session,
774 refresh_meter: RateMeter::new(config.status_refresh_interval),
775 served_blocks_inflight: 0,
776 served_block_requests: VecDeque::new(),
777 }
778 }
779
780 pub(super) fn try_start_serving_blocks(
781 &mut self,
782 local_inflight_cap: u32,
783 start_height: block::Height,
784 ) -> bool {
785 if self.served_blocks_inflight >= local_inflight_cap {
786 return false;
787 }
788 self.served_blocks_inflight = self.served_blocks_inflight.saturating_add(1);
789 self.served_block_requests
790 .push_back((start_height, Instant::now()));
791 true
792 }
793
794 pub(super) fn serving_blocks_elapsed(&self, start_height: block::Height) -> Option<Duration> {
795 self.served_block_requests
796 .iter()
797 .find_map(|(start, started)| (*start == start_height).then(|| started.elapsed()))
798 }
799
800 pub(super) fn finish_serving_blocks(
801 &mut self,
802 start_height: block::Height,
803 ) -> Option<Duration> {
804 self.served_blocks_inflight = self.served_blocks_inflight.saturating_sub(1);
805 self.served_block_requests
806 .iter()
807 .position(|(start, _)| *start == start_height)
808 .and_then(|index| self.served_block_requests.remove(index))
809 .map(|(_, started)| started.elapsed())
810 }
811}
812
813#[derive(Clone, Debug)]
814pub(super) struct OutstandingBlockRange {
815 pub(super) request: BlockRangeRequest,
816 pub(super) queued_at: Instant,
817 pub(super) deadline: Instant,
818 pub(super) delivery_snapshot: DeliverySnapshot,
819 pub(super) delivered_bytes: u64,
820 pub(super) received: ReceivedBlockTracker,
821}
822
823#[derive(Copy, Clone, Debug)]
824pub(super) struct DeliverySnapshot {
825 pub(super) delivered: u64,
826 pub(super) delivered_at: Instant,
827}
828
829impl OutstandingBlockRange {
830 pub(super) fn reserved_bytes(&self) -> u64 {
832 self.request
833 .expected_blocks
834 .iter()
835 .filter(|expected| !self.has_received(expected.height))
836 .fold(0u64, |acc, expected| {
837 acc.saturating_add(expected.estimated_bytes)
838 })
839 }
840
841 pub(super) fn estimated_bytes_for_height(&self, height: block::Height) -> Option<u64> {
842 self.request.estimated_bytes_for_height(height)
843 }
844
845 pub(super) fn has_received(&self, height: block::Height) -> bool {
846 self.request
847 .offset_for_height(height)
848 .is_some_and(|offset| self.received.contains_offset(offset))
849 }
850
851 pub(super) fn mark_received(&mut self, height: block::Height) {
852 if let Some(offset) = self.request.offset_for_height(height) {
853 self.received.insert_offset(offset);
854 }
855 }
856
857 pub(super) fn record_body_bytes(&mut self, bytes: u64) {
858 self.delivered_bytes = self.delivered_bytes.saturating_add(bytes);
859 }
860
861 pub(super) fn mark_received_through(&mut self, tip: block::Height) -> u64 {
865 self.request
866 .expected_blocks
867 .iter()
868 .filter(|expected| {
869 expected.height <= tip
870 && self
871 .request
872 .offset_for_height(expected.height)
873 .is_some_and(|offset| self.received.insert_offset(offset))
874 })
875 .fold(0u64, |acc, expected| {
876 acc.saturating_add(expected.estimated_bytes)
877 })
878 }
879
880 pub(super) fn is_complete(&self) -> bool {
881 self.received.len() == self.request.expected_blocks.len()
882 }
883}
884
885#[derive(Copy, Clone, Debug, Eq, PartialEq)]
887pub(super) enum BlockBudgetLedger {
888 Reserved(u64),
889 Released,
890}
891
892impl BlockBudgetLedger {
893 pub(super) fn reserved(estimate: u64) -> Self {
894 Self::Reserved(estimate)
895 }
896
897 pub(super) fn reserved_charge(self) -> u64 {
898 match self {
899 Self::Reserved(bytes) => bytes,
900 Self::Released => 0,
901 }
902 }
903
904 pub(super) fn is_reserved(self) -> bool {
905 matches!(self, Self::Reserved(_))
906 }
907
908 pub(super) fn release_reserved(&mut self) -> u64 {
910 let released = self.reserved_charge();
911 *self = Self::Released;
912 released
913 }
914}
915
916const RECEIVED_TRACKER_OFFSET_CAPACITY: u32 = u128::BITS;
919
920const _: () = assert!(MAX_BS_BLOCKS_PER_REQUEST <= RECEIVED_TRACKER_OFFSET_CAPACITY);
927
928#[derive(Clone, Debug, Default)]
929pub(super) struct ReceivedBlockTracker {
930 bits: u128,
931 count: usize,
932}
933
934impl ReceivedBlockTracker {
935 pub(super) fn len(&self) -> usize {
936 self.count
937 }
938
939 fn contains_offset(&self, offset: u32) -> bool {
940 Self::bit_for_offset(offset).is_some_and(|bit| self.bits & bit != 0)
941 }
942
943 fn insert_offset(&mut self, offset: u32) -> bool {
944 let Some(bit) = Self::bit_for_offset(offset) else {
945 return false;
946 };
947 if self.bits & bit != 0 {
948 return false;
949 }
950 self.bits |= bit;
951 self.count = self.count.saturating_add(1);
952 true
953 }
954
955 fn bit_for_offset(offset: u32) -> Option<u128> {
956 1u128.checked_shl(offset)
957 }
958}
959
960#[derive(Clone, Debug)]
961pub(super) struct RateMeter {
962 pub(super) next_allowed: Instant,
963 pub(super) interval: Duration,
964}
965
966impl RateMeter {
967 pub(super) fn new(interval: Duration) -> Self {
968 Self {
969 next_allowed: Instant::now(),
970 interval,
971 }
972 }
973
974 pub(super) fn try_take(&mut self, now: Instant) -> bool {
975 if now < self.next_allowed {
976 return false;
977 }
978 self.next_allowed = now + self.interval;
979 true
980 }
981
982 pub(super) fn is_ready(&self, now: Instant) -> bool {
983 now >= self.next_allowed
984 }
985
986 pub(super) fn mark_taken(&mut self, now: Instant) {
987 self.next_allowed = now + self.interval;
988 }
989}
990
991#[derive(Clone, Debug)]
998pub(super) struct ThroughputMeter {
999 bytes: u64,
1000 blocks: u64,
1001 window_start: Instant,
1002 last_bytes_per_sec: u64,
1003 last_blocks_per_sec: u64,
1004}
1005
1006impl ThroughputMeter {
1007 pub(super) fn new(now: Instant) -> Self {
1008 Self {
1009 bytes: 0,
1010 blocks: 0,
1011 window_start: now,
1012 last_bytes_per_sec: 0,
1013 last_blocks_per_sec: 0,
1014 }
1015 }
1016
1017 pub(super) fn record(&mut self, bytes: u64) {
1018 self.bytes = self.bytes.saturating_add(bytes);
1019 self.blocks = self.blocks.saturating_add(1);
1020 }
1021
1022 pub(super) fn sample(&mut self, now: Instant) {
1026 let elapsed = now
1027 .saturating_duration_since(self.window_start)
1028 .as_secs_f64();
1029 if elapsed <= 0.0 {
1030 return;
1031 }
1032 self.last_bytes_per_sec = (self.bytes as f64 / elapsed) as u64;
1035 self.last_blocks_per_sec = (self.blocks as f64 / elapsed) as u64;
1036 self.bytes = 0;
1037 self.blocks = 0;
1038 self.window_start = now;
1039 }
1040
1041 pub(super) fn bytes_per_sec(&self) -> u64 {
1042 self.last_bytes_per_sec
1043 }
1044
1045 pub(super) fn blocks_per_sec(&self) -> u64 {
1046 self.last_blocks_per_sec
1047 }
1048}
1049
1050pub(crate) use crate::zakura::transport::ByteBudget;
1055
1056pub(super) fn next_height(height: block::Height) -> Option<block::Height> {
1057 height.0.checked_add(1).map(block::Height)
1058}
1059
1060pub(super) fn previous_height(height: block::Height) -> Option<block::Height> {
1061 height.0.checked_sub(1).map(block::Height)
1062}
1063
1064pub(super) fn height_after_count(start: block::Height, count: u32) -> Option<block::Height> {
1065 start.0.checked_add(count).map(block::Height)
1066}