1use super::{config::*, events::*, wire::*, *};
2use crate::zakura::{
3 handle_pipe_exit, spawn_supervised_pipe, FramedRecv, FramedSend, OrderedSendError, Peer,
4 PeerStreamSession, Service, SinkReject, Stream, StreamMode, ZakuraConnId, ZakuraPeerId,
5 FRAME_HEADER_BYTES,
6};
7use std::{
8 sync::atomic::{AtomicU64, Ordering},
9 time::Instant,
10};
11
12pub const MAX_BS_FRAME_BYTES: u32 = {
19 (MAX_BS_MESSAGE_BYTES + FRAME_HEADER_BYTES) as u32
21};
22
23const BLOCK_SYNC_SERVICE_STREAMS: [Stream; 1] = [Stream {
24 kind: ZAKURA_STREAM_BLOCK_SYNC,
25 version: ZAKURA_BLOCK_SYNC_STREAM_VERSION,
26 frame_cap: MAX_BS_FRAME_BYTES,
27 capability: ZAKURA_CAP_BLOCK_SYNC,
28 mode: StreamMode::Ordered,
29}];
30
31pub(crate) fn block_sync_streams() -> &'static [Stream] {
33 &BLOCK_SYNC_SERVICE_STREAMS
34}
35
36#[derive(Clone, Debug)]
38pub struct BlockSyncPeerSession {
39 peer_id: ZakuraPeerId,
40 direction: ServicePeerDirection,
41 send: FramedSend,
42 cancel_token: CancellationToken,
43}
44
45impl BlockSyncPeerSession {
46 pub(crate) fn new(session: &PeerStreamSession, direction: ServicePeerDirection) -> Self {
47 Self {
48 peer_id: session.peer_id().clone(),
49 direction,
50 send: session.sender(),
51 cancel_token: session.cancel_token(),
52 }
53 }
54
55 #[cfg(test)]
59 pub(super) fn for_test(
60 peer_id: ZakuraPeerId,
61 send: FramedSend,
62 cancel_token: CancellationToken,
63 ) -> Self {
64 Self {
65 peer_id,
66 direction: ServicePeerDirection::Outbound,
67 send,
68 cancel_token,
69 }
70 }
71
72 pub fn peer_id(&self) -> &ZakuraPeerId {
74 &self.peer_id
75 }
76
77 pub fn direction(&self) -> ServicePeerDirection {
79 self.direction
80 }
81
82 pub fn cancel_token(&self) -> CancellationToken {
84 self.cancel_token.clone()
85 }
86
87 pub fn outbound_capacity(&self) -> usize {
89 self.send.capacity()
90 }
91
92 pub fn outbound_max_capacity(&self) -> usize {
94 self.send.max_capacity()
95 }
96
97 pub fn try_send_status(&self, status: BlockSyncStatus) -> Result<(), OrderedSendError> {
99 self.try_send_message(BlockSyncMessage::Status(status))
100 }
101
102 pub async fn send_status(&self, status: BlockSyncStatus) -> Result<(), OrderedSendError> {
104 self.send_message(BlockSyncMessage::Status(status)).await
105 }
106
107 pub fn try_send_get_blocks(
109 &self,
110 start_height: block::Height,
111 count: u32,
112 ) -> Result<(), OrderedSendError> {
113 self.try_send_message(BlockSyncMessage::GetBlocks {
114 start_height,
115 count,
116 })
117 }
118
119 pub fn try_send_block(&self, block: Arc<block::Block>) -> Result<(), OrderedSendError> {
121 self.try_send_message(BlockSyncMessage::Block(block))
122 }
123
124 pub async fn send_block(&self, block: Arc<block::Block>) -> Result<(), OrderedSendError> {
126 self.send_message(BlockSyncMessage::Block(block)).await
127 }
128
129 pub fn try_send_blocks_done(
131 &self,
132 start_height: block::Height,
133 returned: u32,
134 ) -> Result<(), OrderedSendError> {
135 self.try_send_message(BlockSyncMessage::BlocksDone {
136 start_height,
137 returned,
138 })
139 }
140
141 pub async fn send_blocks_done(
143 &self,
144 start_height: block::Height,
145 returned: u32,
146 ) -> Result<(), OrderedSendError> {
147 self.send_message(BlockSyncMessage::BlocksDone {
148 start_height,
149 returned,
150 })
151 .await
152 }
153
154 pub fn try_send_range_unavailable(
156 &self,
157 start_height: block::Height,
158 count: u32,
159 ) -> Result<(), OrderedSendError> {
160 self.try_send_message(BlockSyncMessage::RangeUnavailable {
161 start_height,
162 count,
163 })
164 }
165
166 pub async fn send_range_unavailable(
168 &self,
169 start_height: block::Height,
170 count: u32,
171 ) -> Result<(), OrderedSendError> {
172 self.send_message(BlockSyncMessage::RangeUnavailable {
173 start_height,
174 count,
175 })
176 .await
177 }
178
179 fn try_send_message(&self, msg: BlockSyncMessage) -> Result<(), OrderedSendError> {
180 let frame = msg
181 .encode_frame()
182 .map_err(|error| OrderedSendError::Encode(Box::new(error)))?;
183 match self.send.try_send(frame) {
184 Ok(()) => Ok(()),
185 Err(mpsc::error::TrySendError::Full(_frame)) => Err(OrderedSendError::Full),
186 Err(mpsc::error::TrySendError::Closed(_frame)) => Err(OrderedSendError::Closed),
187 }
188 }
189
190 async fn send_message(&self, msg: BlockSyncMessage) -> Result<(), OrderedSendError> {
191 let frame = msg
192 .encode_frame()
193 .map_err(|error| OrderedSendError::Encode(Box::new(error)))?;
194 self.send
195 .send(frame)
196 .await
197 .map_err(|_error| OrderedSendError::Closed)
198 }
199}
200
201#[derive(Debug)]
203pub(crate) struct BlockSyncService {
204 inner: Arc<BlockSyncServiceInner>,
205 _held_events: Option<Arc<StdMutex<mpsc::Receiver<BlockSyncEvent>>>>,
206 _reactor_task: Option<JoinHandle<()>>,
207}
208
209#[derive(Debug)]
210struct BlockSyncServiceInner {
211 config: ZakuraBlockSyncConfig,
212 lifecycle: mpsc::UnboundedSender<BlockSyncEvent>,
213 routine_wiring: Option<super::state::RoutineWiring>,
217 peers: StdMutex<HashMap<ZakuraPeerId, BlockSyncPeerRecord>>,
218 next_session_id: AtomicU64,
219}
220
221#[derive(Debug)]
222struct BlockSyncPeerRecord {
223 conn_id: ZakuraConnId,
224 session_id: u64,
225 direction: ServicePeerDirection,
226 cancel_token: CancellationToken,
227}
228
229impl BlockSyncService {
230 pub(crate) fn new(config: ZakuraBlockSyncConfig) -> Self {
231 Self::new_with_startup(BlockSyncStartup::inert(config))
232 }
233
234 pub(crate) fn new_with_handle(config: ZakuraBlockSyncConfig, handle: BlockSyncHandle) -> Self {
235 Self {
236 inner: Arc::new(BlockSyncServiceInner {
237 config,
238 lifecycle: handle.lifecycle.clone(),
239 routine_wiring: handle.routine_wiring.clone(),
240 peers: StdMutex::new(HashMap::new()),
241 next_session_id: AtomicU64::new(1),
242 }),
243 _held_events: None,
244 _reactor_task: None,
245 }
246 }
247
248 pub(crate) fn new_with_header_tip(
249 config: ZakuraBlockSyncConfig,
250 header_tip: watch::Receiver<(block::Height, block::Hash)>,
251 ) -> Self {
252 let best_header_tip = *header_tip.borrow();
253 let startup = BlockSyncStartup::new(
254 BlockSyncFrontiers {
255 finalized_height: block::Height::MIN,
256 verified_block_tip: block::Height::MIN,
257 verified_block_hash: block::Hash([0; 32]),
258 },
259 best_header_tip,
260 header_tip,
261 config,
262 );
263 Self::new_with_startup(startup)
264 }
265
266 fn new_with_startup(startup: BlockSyncStartup) -> Self {
267 let config = startup.config.clone();
268 let (handle, _actions, reactor_task) = spawn_block_sync_reactor(startup);
269 Self {
270 inner: Arc::new(BlockSyncServiceInner {
271 config,
272 lifecycle: handle.lifecycle.clone(),
273 routine_wiring: handle.routine_wiring.clone(),
274 peers: StdMutex::new(HashMap::new()),
275 next_session_id: AtomicU64::new(1),
276 }),
277 _held_events: None,
278 _reactor_task: Some(reactor_task),
279 }
280 }
281
282 #[cfg(test)]
283 pub(crate) fn new_for_test(
284 config: ZakuraBlockSyncConfig,
285 ) -> (Self, mpsc::Receiver<BlockSyncEvent>) {
286 let (events, event_rx) = mpsc::channel(config.peer_limits.inbound_queue_depth.max(1));
287 let (lifecycle, mut lifecycle_rx) = mpsc::unbounded_channel();
288 let events_for_lifecycle = events.clone();
289 tokio::spawn(async move {
290 while let Some(event) = lifecycle_rx.recv().await {
291 let _ = events_for_lifecycle.send(event).await;
292 }
293 });
294 (
295 Self {
296 inner: Arc::new(BlockSyncServiceInner {
297 config,
298 lifecycle,
299 routine_wiring: None,
300 peers: StdMutex::new(HashMap::new()),
301 next_session_id: AtomicU64::new(1),
302 }),
303 _held_events: None,
304 _reactor_task: None,
305 },
306 event_rx,
307 )
308 }
309
310 #[cfg(test)]
311 pub(crate) fn new_with_handle_for_test(
312 config: ZakuraBlockSyncConfig,
313 handle: BlockSyncHandle,
314 ) -> Self {
315 Self::new_with_handle(config, handle)
316 }
317
318 #[cfg(test)]
319 pub(crate) fn peer_count(&self) -> usize {
320 self.inner
321 .peers
322 .lock()
323 .expect("block-sync peer map mutex is never poisoned")
324 .len()
325 }
326
327 fn peer_slots_free(&self, direction: ServicePeerDirection) -> bool {
328 let peers = self
329 .inner
330 .peers
331 .lock()
332 .expect("block-sync peer map mutex is never poisoned");
333 let count = peers
334 .values()
335 .filter(|record| record.direction == direction)
336 .count();
337 let cap = match direction {
338 ServicePeerDirection::Inbound => self.inner.config.peer_limits.max_inbound_peers,
339 ServicePeerDirection::Outbound => self.inner.config.peer_limits.max_outbound_peers,
340 };
341 count < cap
342 }
343
344 fn peer_is_parked(&self, peer_id: &ZakuraPeerId) -> bool {
345 self.inner
346 .routine_wiring
347 .as_ref()
348 .is_some_and(|wiring| wiring.registry.is_peer_parked(peer_id, Instant::now()))
349 }
350
351 fn can_admit_peer(&self, peer_id: &ZakuraPeerId, direction: ServicePeerDirection) -> bool {
357 let peers = self
358 .inner
359 .peers
360 .lock()
361 .expect("block-sync peer map mutex is never poisoned");
362 if peers.contains_key(peer_id) {
363 return true;
364 }
365 let count = peers
366 .values()
367 .filter(|record| record.direction == direction)
368 .count();
369 let cap = match direction {
370 ServicePeerDirection::Inbound => self.inner.config.peer_limits.max_inbound_peers,
371 ServicePeerDirection::Outbound => self.inner.config.peer_limits.max_outbound_peers,
372 };
373 count < cap
374 }
375}
376
377impl Service for BlockSyncService {
378 fn name(&self) -> &'static str {
379 "block-sync"
380 }
381
382 fn streams(&self) -> &[Stream] {
383 block_sync_streams()
384 }
385
386 fn wants_peer(
387 &self,
388 peer: &ZakuraPeerId,
389 _negotiated: u64,
390 direction: ServicePeerDirection,
391 ) -> bool {
392 !self.peer_is_parked(peer) && self.peer_slots_free(direction)
393 }
394
395 fn add_peer(&self, mut peer: Peer) {
396 if self.peer_is_parked(&peer.id) {
397 peer.service_cancel_token().cancel();
398 return;
399 }
400
401 if !self.can_admit_peer(&peer.id, peer.direction) {
402 return;
403 }
404
405 let Some((recv, send)) = peer.take_stream(ZAKURA_STREAM_BLOCK_SYNC) else {
406 return;
407 };
408
409 let peer_id = peer.id.clone();
410 let session = PeerStreamSession::new(
411 peer_id.clone(),
412 ZAKURA_STREAM_BLOCK_SYNC,
413 recv,
414 send,
415 peer.service_cancel_token(),
416 );
417 let service_cancel_token = session.cancel_token();
418 let connection_cancel_token = peer.cancel_token();
419 let close_cause = peer.close_cause();
420 let block_sync_session = BlockSyncPeerSession::new(&session, peer.direction);
421 let session_id = self.inner.next_session_id.fetch_add(1, Ordering::Relaxed);
422 let (_session_peer, _stream_kind, recv, send, _session_cancel) = session.into_parts();
423
424 drop(send);
431
432 {
433 let mut peers = self
434 .inner
435 .peers
436 .lock()
437 .expect("block-sync peer map mutex is never poisoned");
438 if peers
439 .get(&peer_id)
440 .is_some_and(|record| record.conn_id > peer.conn_id)
441 {
442 service_cancel_token.cancel();
443 return;
444 }
445 if let Some(old_record) = peers.insert(
446 peer_id.clone(),
447 BlockSyncPeerRecord {
448 conn_id: peer.conn_id,
449 session_id,
450 direction: peer.direction,
451 cancel_token: service_cancel_token.clone(),
452 },
453 ) {
454 old_record.cancel_token.cancel();
455 }
456 }
457
458 let run_cancel = service_cancel_token.clone();
459 let on_teardown = {
460 let lifecycle = self.inner.lifecycle.clone();
461 let peer_id = peer_id.clone();
462 let inner = self.inner.clone();
463 move || {
464 let should_notify = if let Ok(mut peers) = inner.peers.lock() {
465 if peers
466 .get(&peer_id)
467 .is_some_and(|record| record.session_id == session_id)
468 {
469 peers.remove(&peer_id);
470 true
471 } else {
472 false
473 }
474 } else {
475 false
476 };
477
478 if should_notify {
479 let _ = lifecycle.send(BlockSyncEvent::PeerDisconnected(peer_id));
480 }
481 }
482 };
483 let on_panic = {
484 let connection_cancel_token = connection_cancel_token.clone();
485 let close_cause = close_cause.clone();
486 move || {
487 close_cause.record("service_panic");
488 connection_cancel_token.cancel();
489 }
490 };
491 let pipe = {
500 let connection_cancel_token = connection_cancel_token.clone();
501 let close_cause = close_cause.clone();
502 let routine_wiring = self.inner.routine_wiring.clone();
503 let block_sync_session = block_sync_session.clone();
504 let peer_id = peer_id.clone();
505 let direction = peer.direction;
506 async move {
507 let result = match routine_wiring {
508 Some(wiring) => {
509 let generation = wiring.registry.admit(&peer_id, direction, &wiring.config);
510 let routine = super::peer_routine::PeerRoutine::new(
511 peer_id,
512 block_sync_session,
513 recv,
514 wiring.config,
515 generation,
516 wiring.budget,
517 wiring.work,
518 wiring.registry,
519 wiring.received_throughput,
520 wiring.sequencer_input,
521 wiring.sequencer_input_bytes,
522 wiring.sequencer_input_decoded_attributed_memory_bytes,
523 wiring.actions,
524 wiring.routine_to_reactor,
525 wiring.view,
526 run_cancel,
527 wiring.trace,
528 );
529 routine.run().await
530 }
531 None => drain_inbound(recv, run_cancel).await,
532 };
533 handle_pipe_exit("block-sync", &connection_cancel_token, &close_cause, result);
534 }
535 };
536 spawn_supervised_pipe(
539 peer_id.clone(),
540 service_cancel_token.clone(),
541 on_teardown,
542 on_panic,
543 pipe,
544 );
545
546 let _ = self
547 .inner
548 .lifecycle
549 .send(BlockSyncEvent::PeerConnected(block_sync_session));
550 }
551
552 fn remove_peer(&self, peer: &ZakuraPeerId, conn_id: ZakuraConnId) {
553 let removed = {
554 let mut peers = self
555 .inner
556 .peers
557 .lock()
558 .expect("block-sync peer map mutex is never poisoned");
559 if peers
560 .get(peer)
561 .is_some_and(|record| record.conn_id == conn_id)
562 {
563 peers.remove(peer)
564 } else {
565 None
566 }
567 };
568 if let Some(record) = removed {
569 record.cancel_token.cancel();
570 let _ = self
571 .inner
572 .lifecycle
573 .send(BlockSyncEvent::PeerDisconnected(peer.clone()));
574 }
575 }
576
577 fn deliver_frame(
578 &self,
579 _peer_id: ZakuraPeerId,
580 _stream_kind: u16,
581 _frame: Frame,
582 ) -> Result<(), SinkReject> {
583 Ok(())
595 }
596}
597
598async fn drain_inbound(mut recv: FramedRecv, cancel: CancellationToken) -> Result<(), SinkReject> {
604 loop {
605 tokio::select! {
606 () = cancel.cancelled() => return Ok(()),
607 frame = recv.recv() => {
608 if frame.is_none() {
609 return Ok(());
610 }
611 }
612 }
613 }
614}