1use std::{
18 collections::HashMap,
19 future::Future,
20 pin::Pin,
21 sync::Arc,
22 task::{Context, Poll},
23 time::{Duration, Instant},
24};
25
26use futures::future::FutureExt;
27use tokio::sync::oneshot;
28use tower::{util::BoxService, Service, ServiceExt};
29use tracing::{instrument, Instrument, Span};
30
31#[cfg(any(test, feature = "proptest-impl"))]
32use tower::buffer::Buffer;
33
34use zebra_chain::{
35 block::{self, CountedHeader, HeightDiff},
36 diagnostic::CodeTimer,
37 parameters::{Network, NetworkUpgrade},
38 serialization::ZcashSerialize,
39 subtree::NoteCommitmentSubtreeIndex,
40};
41
42use crate::{
43 constants::{
44 MAX_FIND_BLOCK_HASHES_RESULTS, MAX_FIND_BLOCK_HEADERS_RESULTS, MAX_LEGACY_CHAIN_BLOCKS,
45 },
46 error::{CommitBlockError, CommitCheckpointVerifiedError, InvalidateError, ReconsiderError},
47 request::TimedSpan,
48 response::NonFinalizedBlocksListener,
49 service::{
50 block_iter::any_ancestor_blocks,
51 chain_tip::{ChainTipBlock, ChainTipChange, ChainTipSender, LatestChainTip},
52 finalized_state::{FinalizedState, ZebraDb},
53 non_finalized_state::{Chain, NonFinalizedState},
54 pending_utxos::PendingUtxos,
55 queued_blocks::QueuedBlocks,
56 read::find,
57 watch_receiver::WatchReceiver,
58 },
59 BoxError, CheckpointVerifiedBlock, CommitSemanticallyVerifiedError, Config, KnownBlock,
60 ReadRequest, ReadResponse, Request, Response, SemanticallyVerifiedBlock, StateInitError,
61};
62
63pub mod block_iter;
64pub mod chain_tip;
65pub mod watch_receiver;
66
67pub mod check;
68
69pub(crate) mod finalized_state;
70pub(crate) mod non_finalized_state;
71mod pending_utxos;
72mod queued_blocks;
73pub(crate) mod read;
74mod traits;
75mod write;
76
77#[cfg(any(test, feature = "proptest-impl"))]
78pub mod arbitrary;
79
80#[cfg(test)]
81mod tests;
82
83pub use finalized_state::{OutputLocation, TransactionIndex, TransactionLocation};
84use write::NonFinalizedWriteMessage;
85
86use self::queued_blocks::{QueuedCheckpointVerified, QueuedSemanticallyVerified, SentHashes};
87
88pub use self::traits::{ReadState, State};
89
90#[derive(Debug)]
110pub(crate) struct StateService {
111 network: Network,
115
116 full_verifier_utxo_lookahead: block::Height,
122
123 non_finalized_state_queued_blocks: QueuedBlocks,
128
129 finalized_state_queued_blocks: HashMap<block::Hash, QueuedCheckpointVerified>,
134
135 block_write_sender: write::BlockWriteSender,
137
138 finalized_block_write_last_sent_hash: block::Hash,
148
149 non_finalized_block_write_sent_hashes: SentHashes,
152
153 invalid_block_write_reset_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
159
160 non_finalized_rejected_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
168
169 pending_utxos: PendingUtxos,
173
174 last_prune: Instant,
176
177 read_service: ReadStateService,
183
184 max_finalized_queue_height: f64,
191}
192
193#[derive(Clone, Debug)]
206pub struct ReadStateService {
207 network: Network,
211
212 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
219
220 db: ZebraDb,
228
229 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
234}
235
236impl Drop for StateService {
237 fn drop(&mut self) {
238 self.invalid_block_write_reset_receiver.close();
245 self.non_finalized_rejected_receiver.close();
246
247 std::mem::drop(self.block_write_sender.finalized.take());
248 std::mem::drop(self.block_write_sender.non_finalized.take());
249
250 self.clear_finalized_block_queue(CommitBlockError::WriteTaskExited);
251 self.clear_non_finalized_block_queue(CommitBlockError::WriteTaskExited);
252
253 info!("dropping the state: logging database metrics");
255 self.log_db_metrics();
256
257 }
260}
261
262impl Drop for ReadStateService {
263 fn drop(&mut self) {
264 if let Some(block_write_task) = self.block_write_task.take() {
269 if let Some(block_write_task_handle) = Arc::into_inner(block_write_task) {
270 self.db.shutdown(true);
274
275 #[cfg(not(test))]
281 info!("waiting for the block write task to finish");
282 #[cfg(test)]
283 debug!("waiting for the block write task to finish");
284
285 if let Err(thread_panic) = block_write_task_handle.join() {
287 std::panic::resume_unwind(thread_panic);
288 } else {
289 debug!("shutting down the state because the block write task has finished");
290 }
291 }
292 } else {
293 self.db.shutdown(false);
297 }
298 }
299}
300
301impl StateService {
302 const PRUNE_INTERVAL: Duration = Duration::from_secs(30);
303
304 pub async fn new(
312 config: Config,
313 network: &Network,
314 max_checkpoint_height: block::Height,
315 checkpoint_verify_concurrency_limit: usize,
316 ) -> (Self, ReadStateService, LatestChainTip, ChainTipChange) {
317 let (finalized_state, finalized_tip, timer) = {
318 let config = config.clone();
319 let network = network.clone();
320 tokio::task::spawn_blocking(move || {
321 let timer = CodeTimer::start();
322 let finalized_state = FinalizedState::new(
323 &config,
324 &network,
325 #[cfg(feature = "elasticsearch")]
326 true,
327 )
328 .expect(
329 "opening the read-write finalized state database failed; check that the \
330 state cache directory is writable and not locked by another Zebra instance, \
331 and that there is free disk space",
332 );
333 timer.finish_desc("opening finalized state database");
334
335 let timer = CodeTimer::start();
336 let finalized_tip = finalized_state.db.tip_block();
337
338 (finalized_state, finalized_tip, timer)
339 })
340 .await
341 .expect("failed to join blocking task")
342 };
343
344 let finalized_tip_height = finalized_tip
356 .as_ref()
357 .map(|tip| tip.coinbase_height().expect("valid block must have height"));
358 let is_finalized_tip_past_max_checkpoint =
359 finalized_tip_height.is_some_and(|tip_height| tip_height >= max_checkpoint_height);
360 let backup_dir_path = config.non_finalized_state_backup_dir(network);
361
362 if backup_dir_path.is_some() && !is_finalized_tip_past_max_checkpoint {
363 tracing::info!(
364 ?finalized_tip_height,
365 ?max_checkpoint_height,
366 "not restoring the non-finalized state backup, because the finalized tip is absent \
367 or below the max checkpoint height: Zebra will re-download and re-verify the \
368 blocks above its finalized tip"
369 );
370 }
371 let skip_backup_task = config.debug_skip_non_finalized_state_backup_task;
372 let (non_finalized_state, non_finalized_state_sender, non_finalized_state_receiver) =
373 NonFinalizedState::new(network)
374 .with_backup(
375 backup_dir_path.clone(),
376 &finalized_state.db,
377 is_finalized_tip_past_max_checkpoint,
378 config.debug_skip_non_finalized_state_backup_task,
379 )
380 .await;
381
382 let non_finalized_block_write_sent_hashes = SentHashes::new(&non_finalized_state);
383 let initial_tip = non_finalized_state
384 .best_tip_block()
385 .map(|cv_block| cv_block.block.clone())
386 .or(finalized_tip)
387 .map(CheckpointVerifiedBlock::from)
388 .map(ChainTipBlock::from);
389
390 tracing::info!(chain_tip = ?initial_tip.as_ref().map(|tip| (tip.hash, tip.height)), "loaded Zebra state cache");
391
392 let (chain_tip_sender, latest_chain_tip, chain_tip_change) =
393 ChainTipSender::new(initial_tip, network);
394
395 let finalized_state_for_writing = finalized_state.clone();
396 let should_use_finalized_block_write_sender = non_finalized_state.is_chain_set_empty();
397 let sync_backup_dir_path = backup_dir_path.filter(|_| skip_backup_task);
398 let (
399 block_write_sender,
400 invalid_block_write_reset_receiver,
401 non_finalized_rejected_receiver,
402 block_write_task,
403 ) = write::BlockWriteSender::spawn(
404 finalized_state_for_writing,
405 non_finalized_state,
406 chain_tip_sender,
407 non_finalized_state_sender,
408 should_use_finalized_block_write_sender,
409 sync_backup_dir_path,
410 );
411
412 let read_service = ReadStateService::new(
413 &finalized_state,
414 block_write_task,
415 non_finalized_state_receiver,
416 );
417
418 let full_verifier_utxo_lookahead = max_checkpoint_height
419 - HeightDiff::try_from(checkpoint_verify_concurrency_limit)
420 .expect("fits in HeightDiff");
421 let full_verifier_utxo_lookahead =
422 full_verifier_utxo_lookahead.unwrap_or(block::Height::MIN);
423 let non_finalized_state_queued_blocks = QueuedBlocks::default();
424 let pending_utxos = PendingUtxos::default();
425
426 let finalized_block_write_last_sent_hash =
427 tokio::task::spawn_blocking(move || finalized_state.db.finalized_tip_hash())
428 .await
429 .expect("failed to join blocking task");
430
431 let state = Self {
432 network: network.clone(),
433 full_verifier_utxo_lookahead,
434 non_finalized_state_queued_blocks,
435 finalized_state_queued_blocks: HashMap::new(),
436 block_write_sender,
437 finalized_block_write_last_sent_hash,
438 non_finalized_block_write_sent_hashes,
439 invalid_block_write_reset_receiver,
440 non_finalized_rejected_receiver,
441 pending_utxos,
442 last_prune: Instant::now(),
443 read_service: read_service.clone(),
444 max_finalized_queue_height: f64::NAN,
445 };
446 timer.finish_desc("initializing state service");
447
448 tracing::info!("starting legacy chain check");
449 let timer = CodeTimer::start();
450
451 if let (Some(tip), Some(nu5_activation_height)) = (
452 {
453 let read_state = state.read_service.clone();
454 tokio::task::spawn_blocking(move || read_state.best_tip())
455 .await
456 .expect("task should not panic")
457 },
458 NetworkUpgrade::Nu5.activation_height(network),
459 ) {
460 if let Err(error) = check::legacy_chain(
461 nu5_activation_height,
462 any_ancestor_blocks(
463 &state.read_service.latest_non_finalized_state(),
464 &state.read_service.db,
465 tip.1,
466 ),
467 &state.network,
468 MAX_LEGACY_CHAIN_BLOCKS,
469 ) {
470 let legacy_db_path = state.read_service.db.path().to_path_buf();
471 panic!(
472 "Cached state contains a legacy chain.\n\
473 An outdated Zebra version did not know about a recent network upgrade,\n\
474 so it followed a legacy chain using outdated consensus branch rules.\n\
475 Hint: Delete your database, and restart Zebra to do a full sync.\n\
476 Database path: {legacy_db_path:?}\n\
477 Error: {error:?}",
478 );
479 }
480 }
481
482 tracing::info!("cached state consensus branch is valid: no legacy chain found");
483 timer.finish_desc("legacy chain check");
484
485 let db_for_metrics = read_service.db.clone();
487 tokio::spawn(async move {
488 let mut interval = tokio::time::interval(Duration::from_secs(30));
489 loop {
490 interval.tick().await;
491 db_for_metrics.export_metrics();
492 }
493 });
494
495 (state, read_service, latest_chain_tip, chain_tip_change)
496 }
497
498 pub fn log_db_metrics(&self) {
500 self.read_service.db.print_db_metrics();
501 }
502
503 fn queue_and_commit_to_finalized_state(
507 &mut self,
508 checkpoint_verified: CheckpointVerifiedBlock,
509 ) -> oneshot::Receiver<Result<block::Hash, CommitCheckpointVerifiedError>> {
510 let queued_prev_hash = checkpoint_verified.block.header.previous_block_hash;
516 let queued_height = checkpoint_verified.height;
517
518 if self.is_close_to_final_checkpoint(queued_height) {
521 self.non_finalized_block_write_sent_hashes
522 .add_finalized(&checkpoint_verified)
523 }
524
525 let (rsp_tx, rsp_rx) = oneshot::channel();
526 let queued = (checkpoint_verified, rsp_tx);
527
528 if self.block_write_sender.finalized.is_some() {
529 if let Some(duplicate_queued) = self
531 .finalized_state_queued_blocks
532 .insert(queued_prev_hash, queued)
533 {
534 Self::send_checkpoint_verified_block_error(
535 duplicate_queued,
536 CommitBlockError::new_duplicate(
537 Some(queued_prev_hash.into()),
538 KnownBlock::Queue,
539 ),
540 );
541 }
542
543 self.drain_finalized_queue_and_commit();
544 } else {
545 Self::send_checkpoint_verified_block_error(
551 queued,
552 CommitBlockError::new_duplicate(None, KnownBlock::Finalized),
553 );
554
555 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
556 None,
557 KnownBlock::Finalized,
558 ));
559 }
560
561 if self.finalized_state_queued_blocks.is_empty() {
562 self.max_finalized_queue_height = f64::NAN;
563 } else if self.max_finalized_queue_height.is_nan()
564 || self.max_finalized_queue_height < queued_height.0 as f64
565 {
566 self.max_finalized_queue_height = queued_height.0 as f64;
572 }
573
574 metrics::gauge!("state.checkpoint.queued.max.height").set(self.max_finalized_queue_height);
575 metrics::gauge!("state.checkpoint.queued.block.count")
576 .set(self.finalized_state_queued_blocks.len() as f64);
577
578 rsp_rx
579 }
580
581 pub fn drain_finalized_queue_and_commit(&mut self) {
589 use tokio::sync::mpsc::error::{SendError, TryRecvError};
590
591 match self.invalid_block_write_reset_receiver.try_recv() {
598 Ok(reset_tip_hash) => self.finalized_block_write_last_sent_hash = reset_tip_hash,
599 Err(TryRecvError::Disconnected) => {
600 info!("Block commit task closed the block reset channel. Is Zebra shutting down?");
601 return;
602 }
603 Err(TryRecvError::Empty) => {}
605 }
606
607 while let Some(queued_block) = self
608 .finalized_state_queued_blocks
609 .remove(&self.finalized_block_write_last_sent_hash)
610 {
611 let last_sent_finalized_block_height = queued_block.0.height;
612
613 self.finalized_block_write_last_sent_hash = queued_block.0.hash;
614
615 if let Some(finalized_block_write_sender) = &self.block_write_sender.finalized {
618 let send_result = finalized_block_write_sender.send(queued_block);
619
620 if let Err(SendError(queued)) = send_result {
622 Self::send_checkpoint_verified_block_error(
624 queued,
625 CommitBlockError::WriteTaskExited,
626 );
627
628 self.clear_finalized_block_queue(CommitBlockError::WriteTaskExited);
629 } else {
630 metrics::gauge!("state.checkpoint.sent.block.height")
631 .set(last_sent_finalized_block_height.0 as f64);
632 };
633 }
634 }
635 }
636
637 fn drain_non_finalized_rejected_hashes(&mut self) {
650 use tokio::sync::mpsc::error::TryRecvError;
651
652 loop {
653 match self.non_finalized_rejected_receiver.try_recv() {
654 Ok(hash) => {
655 self.non_finalized_block_write_sent_hashes.remove(&hash);
656 }
657 Err(TryRecvError::Empty) => break,
658 Err(TryRecvError::Disconnected) => {
659 info!(
660 "Block commit task closed the non-finalized rejected hash channel. \
661 Is Zebra shutting down?"
662 );
663 break;
664 }
665 }
666 }
667 }
668
669 fn clear_finalized_block_queue(
671 &mut self,
672 error: impl Into<CommitCheckpointVerifiedError> + Clone,
673 ) {
674 for (_hash, queued) in self.finalized_state_queued_blocks.drain() {
675 Self::send_checkpoint_verified_block_error(queued, error.clone());
676 }
677 }
678
679 fn send_checkpoint_verified_block_error(
681 queued: QueuedCheckpointVerified,
682 error: impl Into<CommitCheckpointVerifiedError>,
683 ) {
684 let (finalized, rsp_tx) = queued;
685
686 let _ = rsp_tx.send(Err(error.into()));
689 std::mem::drop(finalized);
690 }
691
692 fn clear_non_finalized_block_queue(
694 &mut self,
695 error: impl Into<CommitSemanticallyVerifiedError> + Clone,
696 ) {
697 for (_hash, queued) in self.non_finalized_state_queued_blocks.drain() {
698 Self::send_semantically_verified_block_error(queued, error.clone());
699 }
700 }
701
702 fn send_semantically_verified_block_error(
704 queued: QueuedSemanticallyVerified,
705 error: impl Into<CommitSemanticallyVerifiedError>,
706 ) {
707 let (finalized, rsp_tx) = queued;
708
709 let _ = rsp_tx.send(Err(error.into()));
712 std::mem::drop(finalized);
713 }
714
715 #[instrument(level = "debug", skip(self, semantically_verified))]
723 fn queue_and_commit_to_non_finalized_state(
724 &mut self,
725 semantically_verified: SemanticallyVerifiedBlock,
726 ) -> oneshot::Receiver<Result<block::Hash, CommitSemanticallyVerifiedError>> {
727 tracing::debug!(block = %semantically_verified.block, "queueing block for contextual verification");
728 let parent_hash = semantically_verified.block.header.previous_block_hash;
729
730 self.drain_non_finalized_rejected_hashes();
735
736 if self
737 .non_finalized_block_write_sent_hashes
738 .contains(&semantically_verified.hash)
739 {
740 let (rsp_tx, rsp_rx) = oneshot::channel();
741 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
742 Some(semantically_verified.hash.into()),
743 KnownBlock::WriteChannel,
744 )
745 .into()));
746 return rsp_rx;
747 }
748
749 if self
750 .read_service
751 .db
752 .contains_height(semantically_verified.height)
753 {
754 let (rsp_tx, rsp_rx) = oneshot::channel();
755 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
756 Some(semantically_verified.height.into()),
757 KnownBlock::Finalized,
758 )
759 .into()));
760 return rsp_rx;
761 }
762
763 let rsp_rx = if let Some((_, old_rsp_tx)) = self
767 .non_finalized_state_queued_blocks
768 .get_mut(&semantically_verified.hash)
769 {
770 tracing::debug!("replacing older queued request with new request");
771 let (mut rsp_tx, rsp_rx) = oneshot::channel();
772 std::mem::swap(old_rsp_tx, &mut rsp_tx);
773 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
774 Some(semantically_verified.hash.into()),
775 KnownBlock::Queue,
776 )
777 .into()));
778 rsp_rx
779 } else {
780 let (rsp_tx, rsp_rx) = oneshot::channel();
781 self.non_finalized_state_queued_blocks
782 .queue((semantically_verified, rsp_tx));
783 rsp_rx
784 };
785
786 if self.block_write_sender.finalized.is_some()
795 && self
796 .non_finalized_state_queued_blocks
797 .has_queued_children(self.finalized_block_write_last_sent_hash)
798 && self.read_service.db.finalized_tip_hash()
799 == self.finalized_block_write_last_sent_hash
800 {
801 std::mem::drop(self.block_write_sender.finalized.take());
804 self.non_finalized_block_write_sent_hashes = SentHashes::default();
806 self.non_finalized_block_write_sent_hashes
808 .can_fork_chain_at_hashes = true;
809 self.send_ready_non_finalized_queued(self.finalized_block_write_last_sent_hash);
811 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
813 None,
814 KnownBlock::Finalized,
815 ));
816 } else if !self.can_fork_chain_at(&parent_hash) {
817 tracing::trace!("unready to verify, returning early");
818 } else if self.block_write_sender.finalized.is_none() {
819 self.send_ready_non_finalized_queued(parent_hash);
821
822 let finalized_tip_height = self.read_service.db.finalized_tip_height().expect(
823 "Finalized state must have at least one block before committing non-finalized state",
824 );
825
826 self.non_finalized_state_queued_blocks
827 .prune_by_height(finalized_tip_height);
828
829 self.non_finalized_block_write_sent_hashes
830 .prune_by_height(finalized_tip_height);
831 }
832
833 rsp_rx
834 }
835
836 fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
838 self.non_finalized_block_write_sent_hashes
839 .can_fork_chain_at(hash)
840 || &self.read_service.db.finalized_tip_hash() == hash
841 }
842
843 fn is_close_to_final_checkpoint(&self, queued_height: block::Height) -> bool {
851 queued_height >= self.full_verifier_utxo_lookahead
852 }
853
854 #[tracing::instrument(level = "debug", skip(self, new_parent))]
857 fn send_ready_non_finalized_queued(&mut self, new_parent: block::Hash) {
858 use tokio::sync::mpsc::error::SendError;
859 if let Some(non_finalized_block_write_sender) = &self.block_write_sender.non_finalized {
860 let mut new_parents: Vec<block::Hash> = vec![new_parent];
861
862 while let Some(parent_hash) = new_parents.pop() {
863 let queued_children = self
864 .non_finalized_state_queued_blocks
865 .dequeue_children(parent_hash);
866
867 for queued_child in queued_children {
868 let (SemanticallyVerifiedBlock { hash, .. }, _) = queued_child;
869
870 self.non_finalized_block_write_sent_hashes
871 .add(&queued_child.0);
872 let send_result = non_finalized_block_write_sender.send(queued_child.into());
873
874 if let Err(SendError(NonFinalizedWriteMessage::Commit(queued))) = send_result {
875 Self::send_semantically_verified_block_error(
877 queued,
878 CommitBlockError::WriteTaskExited,
879 );
880
881 self.clear_non_finalized_block_queue(CommitBlockError::WriteTaskExited);
882
883 return;
884 };
885
886 new_parents.push(hash);
887 }
888 }
889
890 self.non_finalized_block_write_sent_hashes.finish_batch();
891 };
892 }
893
894 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
896 self.read_service.best_tip()
897 }
898
899 fn send_invalidate_block(
900 &self,
901 hash: block::Hash,
902 ) -> oneshot::Receiver<Result<block::Hash, InvalidateError>> {
903 let (rsp_tx, rsp_rx) = oneshot::channel();
904
905 let Some(sender) = &self.block_write_sender.non_finalized else {
906 let _ = rsp_tx.send(Err(InvalidateError::ProcessingCheckpointedBlocks));
907 return rsp_rx;
908 };
909
910 if let Err(tokio::sync::mpsc::error::SendError(error)) =
911 sender.send(NonFinalizedWriteMessage::Invalidate { hash, rsp_tx })
912 {
913 let NonFinalizedWriteMessage::Invalidate { rsp_tx, .. } = error else {
914 unreachable!("should return the same Invalidate message could not be sent");
915 };
916
917 let _ = rsp_tx.send(Err(InvalidateError::SendInvalidateRequestFailed));
918 }
919
920 rsp_rx
921 }
922
923 fn send_reconsider_block(
924 &self,
925 hash: block::Hash,
926 ) -> oneshot::Receiver<Result<Vec<block::Hash>, ReconsiderError>> {
927 let (rsp_tx, rsp_rx) = oneshot::channel();
928
929 let Some(sender) = &self.block_write_sender.non_finalized else {
930 let _ = rsp_tx.send(Err(ReconsiderError::CheckpointCommitInProgress));
931 return rsp_rx;
932 };
933
934 if let Err(tokio::sync::mpsc::error::SendError(error)) =
935 sender.send(NonFinalizedWriteMessage::Reconsider { hash, rsp_tx })
936 {
937 let NonFinalizedWriteMessage::Reconsider { rsp_tx, .. } = error else {
938 unreachable!("should return the same Reconsider message could not be sent");
939 };
940
941 let _ = rsp_tx.send(Err(ReconsiderError::ReconsiderSendFailed));
942 }
943
944 rsp_rx
945 }
946
947 fn assert_block_can_be_validated(&self, block: &SemanticallyVerifiedBlock) {
949 assert!(
951 block.height > self.network.mandatory_checkpoint_height(),
952 "invalid semantically verified block height: the canopy checkpoint is mandatory, pre-canopy \
953 blocks, and the canopy activation block, must be committed to the state as finalized \
954 blocks"
955 );
956 }
957
958 fn known_sent_hash(&self, hash: &block::Hash) -> Option<KnownBlock> {
959 self.non_finalized_block_write_sent_hashes
960 .contains(hash)
961 .then_some(KnownBlock::WriteChannel)
962 }
963}
964
965impl ReadStateService {
966 pub(crate) fn new(
972 finalized_state: &FinalizedState,
973 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
974 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
975 ) -> Self {
976 let read_service = Self {
977 network: finalized_state.network(),
978 db: finalized_state.db.clone(),
979 non_finalized_state_receiver,
980 block_write_task,
981 };
982
983 tracing::debug!("created new read-only state service");
984
985 read_service
986 }
987
988 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
990 read::best_tip(&self.latest_non_finalized_state(), &self.db)
991 }
992
993 fn latest_non_finalized_state(&self) -> NonFinalizedState {
995 self.non_finalized_state_receiver.cloned_watch_data()
996 }
997
998 fn latest_best_chain(&self) -> Option<Arc<Chain>> {
1000 self.non_finalized_state_receiver
1001 .borrow_mapped(|non_finalized_state| non_finalized_state.best_chain().cloned())
1002 }
1003
1004 #[cfg(any(test, feature = "proptest-impl"))]
1007 pub fn db(&self) -> &ZebraDb {
1008 &self.db
1009 }
1010
1011 pub fn log_db_metrics(&self) {
1013 self.db.print_db_metrics();
1014 }
1015}
1016
1017impl Service<Request> for StateService {
1018 type Response = Response;
1019 type Error = BoxError;
1020 type Future =
1021 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1022
1023 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1024 let poll = self.read_service.poll_ready(cx);
1026
1027 let now = Instant::now();
1029
1030 if self.last_prune + Self::PRUNE_INTERVAL < now {
1031 let tip = self.best_tip();
1032 let old_len = self.pending_utxos.len();
1033
1034 self.pending_utxos.prune();
1035 self.last_prune = now;
1036
1037 let new_len = self.pending_utxos.len();
1038 let prune_count = old_len
1039 .checked_sub(new_len)
1040 .expect("prune does not add any utxo requests");
1041 if prune_count > 0 {
1042 tracing::debug!(
1043 ?old_len,
1044 ?new_len,
1045 ?prune_count,
1046 ?tip,
1047 "pruned utxo requests"
1048 );
1049 } else {
1050 tracing::debug!(len = ?old_len, ?tip, "no utxo requests needed pruning");
1051 }
1052 }
1053
1054 poll
1055 }
1056
1057 #[instrument(name = "state", skip(self, req))]
1058 fn call(&mut self, req: Request) -> Self::Future {
1059 req.count_metric();
1060 let span = Span::current();
1061
1062 match req {
1063 Request::CommitSemanticallyVerifiedBlock(semantically_verified) => {
1068 let timer = CodeTimer::start();
1069 self.assert_block_can_be_validated(&semantically_verified);
1070
1071 self.pending_utxos
1072 .check_against_ordered(&semantically_verified.new_outputs);
1073
1074 let rsp_rx = tokio::task::block_in_place(move || {
1086 span.in_scope(|| {
1087 self.queue_and_commit_to_non_finalized_state(semantically_verified)
1088 })
1089 });
1090
1091 timer.finish_desc("CommitSemanticallyVerifiedBlock");
1097
1098 let span = Span::current();
1102 async move {
1103 rsp_rx
1104 .await
1105 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1106 .and_then(|result| result)
1107 .map_err(BoxError::from)
1108 .map(Response::Committed)
1109 }
1110 .instrument(span)
1111 .boxed()
1112 }
1113
1114 Request::CommitCheckpointVerifiedBlock(finalized) => {
1119 let timer = CodeTimer::start();
1120 self.pending_utxos
1134 .check_against_ordered(&finalized.new_outputs);
1135
1136 let rsp_rx = self.queue_and_commit_to_finalized_state(finalized);
1141
1142 timer.finish_desc("CommitCheckpointVerifiedBlock");
1148
1149 async move {
1153 rsp_rx
1154 .await
1155 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1156 .and_then(|result| result)
1157 .map_err(BoxError::from)
1158 .map(Response::Committed)
1159 }
1160 .instrument(span)
1161 .boxed()
1162 }
1163
1164 Request::AwaitUtxo(outpoint) => {
1167 let timer = CodeTimer::start();
1168 let response_fut = self.pending_utxos.queue(outpoint);
1170 let response_fut = response_fut.instrument(span).boxed();
1174
1175 if let Some(utxo) = self.non_finalized_state_queued_blocks.utxo(&outpoint) {
1178 self.pending_utxos.respond(&outpoint, utxo);
1179
1180 timer.finish_desc("AwaitUtxo/queued-non-finalized");
1182
1183 return response_fut;
1184 }
1185
1186 self.drain_non_finalized_rejected_hashes();
1188
1189 if let Some(utxo) = self.non_finalized_block_write_sent_hashes.utxo(&outpoint) {
1190 self.pending_utxos.respond(&outpoint, utxo);
1191
1192 timer.finish_desc("AwaitUtxo/sent-non-finalized");
1194
1195 return response_fut;
1196 }
1197
1198 let read_service = self.read_service.clone();
1207
1208 async move {
1210 let req = ReadRequest::AnyChainUtxo(outpoint);
1211
1212 let rsp = read_service.oneshot(req).await?;
1213
1214 if let ReadResponse::AnyChainUtxo(Some(utxo)) = rsp {
1227 timer.finish_desc("AwaitUtxo/any-chain");
1229
1230 return Ok(Response::Utxo(utxo));
1231 }
1232
1233 timer.finish_desc("AwaitUtxo/waiting");
1235
1236 response_fut.await
1237 }
1238 .boxed()
1239 }
1240
1241 Request::KnownBlock(hash) => {
1244 let timer = CodeTimer::start();
1245
1246 self.drain_non_finalized_rejected_hashes();
1247
1248 let sent_hash_response = self.known_sent_hash(&hash);
1249 let read_service = self.read_service.clone();
1250
1251 async move {
1252 if sent_hash_response.is_some() {
1253 return Ok(Response::KnownBlock(sent_hash_response));
1254 };
1255
1256 let response = read::non_finalized_state_contains_block_hash(
1257 &read_service.latest_non_finalized_state(),
1258 hash,
1259 )
1260 .or_else(|| read::finalized_state_contains_block_hash(&read_service.db, hash));
1262
1263 timer.finish_desc("Request::KnownBlock");
1264
1265 Ok(Response::KnownBlock(response))
1266 }
1267 .boxed()
1268 }
1269
1270 Request::InvalidateBlock(block_hash) => {
1272 let rsp_rx = tokio::task::block_in_place(move || {
1273 span.in_scope(|| self.send_invalidate_block(block_hash))
1274 });
1275
1276 let span = Span::current();
1280 async move {
1281 rsp_rx
1282 .await
1283 .map_err(|_recv_error| InvalidateError::InvalidateRequestDropped)
1284 .and_then(|result| result)
1285 .map_err(BoxError::from)
1286 .map(Response::Invalidated)
1287 }
1288 .instrument(span)
1289 .boxed()
1290 }
1291
1292 Request::ReconsiderBlock(block_hash) => {
1294 let rsp_rx = tokio::task::block_in_place(move || {
1295 span.in_scope(|| self.send_reconsider_block(block_hash))
1296 });
1297
1298 let span = Span::current();
1302 async move {
1303 rsp_rx
1304 .await
1305 .map_err(|_recv_error| ReconsiderError::ReconsiderResponseDropped)
1306 .and_then(|result| result)
1307 .map_err(BoxError::from)
1308 .map(Response::Reconsidered)
1309 }
1310 .instrument(span)
1311 .boxed()
1312 }
1313
1314 Request::Tip
1316 | Request::Depth(_)
1317 | Request::BestChainNextMedianTimePast
1318 | Request::BestChainBlockHash(_)
1319 | Request::BlockLocator
1320 | Request::Transaction(_)
1321 | Request::AnyChainTransaction(_)
1322 | Request::UnspentBestChainUtxo(_)
1323 | Request::Block(_)
1324 | Request::AnyChainBlock(_)
1325 | Request::BlockAndSize(_)
1326 | Request::BlockHeader(_)
1327 | Request::FindBlockHashes { .. }
1328 | Request::FindBlockHeaders { .. }
1329 | Request::CheckBestChainTipNullifiersAndAnchors(_)
1330 | Request::CheckBlockProposalValidity(_) => {
1331 let read_service = self.read_service.clone();
1333
1334 async move {
1335 let req = req
1336 .try_into()
1337 .expect("ReadRequest conversion should not fail");
1338
1339 let rsp = read_service.oneshot(req).await?;
1340 let rsp = rsp.try_into().expect("Response conversion should not fail");
1341
1342 Ok(rsp)
1343 }
1344 .boxed()
1345 }
1346 }
1347 }
1348}
1349
1350impl Service<ReadRequest> for ReadStateService {
1351 type Response = ReadResponse;
1352 type Error = BoxError;
1353 type Future =
1354 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1355
1356 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1357 let block_write_task = self.block_write_task.take();
1361
1362 if let Some(block_write_task) = block_write_task {
1363 if block_write_task.is_finished() {
1364 if let Some(block_write_task) = Arc::into_inner(block_write_task) {
1365 if let Err(thread_panic) = block_write_task.join() {
1367 std::panic::resume_unwind(thread_panic);
1368 }
1369 }
1370 } else {
1371 self.block_write_task = Some(block_write_task);
1373 }
1374 }
1375
1376 self.db.check_for_panics();
1377
1378 Poll::Ready(Ok(()))
1379 }
1380
1381 #[instrument(name = "read_state", skip(self, req))]
1382 fn call(&mut self, req: ReadRequest) -> Self::Future {
1383 req.count_metric();
1384 let timer = CodeTimer::start_desc(req.variant_name());
1385 let span = Span::current();
1386 let timed_span = TimedSpan::new(timer, span);
1387 let state = self.clone();
1388
1389 if let ReadRequest::NonFinalizedBlocksListener { known_chain_tips } = req {
1390 let non_finalized_blocks_listener = NonFinalizedBlocksListener::spawn(
1393 self.non_finalized_state_receiver.clone(),
1394 known_chain_tips,
1395 );
1396
1397 return async move {
1398 Ok(ReadResponse::NonFinalizedBlocksListener(
1399 non_finalized_blocks_listener,
1400 ))
1401 }
1402 .boxed();
1403 };
1404
1405 let request_handler = move || match req {
1406 ReadRequest::UsageInfo => Ok(ReadResponse::UsageInfo(state.db.size())),
1408
1409 ReadRequest::Tip => Ok(ReadResponse::Tip(read::tip(
1411 state.latest_best_chain(),
1412 &state.db,
1413 ))),
1414
1415 ReadRequest::TipPoolValues => {
1417 let (tip_height, tip_hash, value_balance) =
1418 read::tip_with_value_balance(state.latest_best_chain(), &state.db)?
1419 .ok_or(BoxError::from("no chain tip available yet"))?;
1420
1421 Ok(ReadResponse::TipPoolValues {
1422 tip_height,
1423 tip_hash,
1424 value_balance,
1425 })
1426 }
1427
1428 ReadRequest::BlockInfo(hash_or_height) => Ok(ReadResponse::BlockInfo(
1430 read::block_info(state.latest_best_chain(), &state.db, hash_or_height),
1431 )),
1432
1433 ReadRequest::Depth(hash) => Ok(ReadResponse::Depth(read::depth(
1435 state.latest_best_chain(),
1436 &state.db,
1437 hash,
1438 ))),
1439
1440 ReadRequest::BestChainNextMedianTimePast => {
1442 Ok(ReadResponse::BestChainNextMedianTimePast(
1443 read::next_median_time_past(&state.latest_non_finalized_state(), &state.db)?,
1444 ))
1445 }
1446
1447 ReadRequest::Block(hash_or_height) => Ok(ReadResponse::Block(read::block(
1449 state.latest_best_chain(),
1450 &state.db,
1451 hash_or_height,
1452 ))),
1453
1454 ReadRequest::AnyChainBlock(hash_or_height) => Ok(ReadResponse::Block(read::any_block(
1455 state.latest_non_finalized_state().chain_iter(),
1456 &state.db,
1457 hash_or_height,
1458 ))),
1459
1460 ReadRequest::BlockAndSize(hash_or_height) => Ok(ReadResponse::BlockAndSize(
1462 read::block_and_size(state.latest_best_chain(), &state.db, hash_or_height),
1463 )),
1464
1465 ReadRequest::BlockHeader(hash_or_height) => {
1467 let best_chain = state.latest_best_chain();
1468
1469 let height = hash_or_height
1470 .height_or_else(|hash| {
1471 read::find::height_by_hash(best_chain.clone(), &state.db, hash)
1472 })
1473 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1474
1475 let hash = hash_or_height
1476 .hash_or_else(|height| {
1477 read::find::hash_by_height(best_chain.clone(), &state.db, height)
1478 })
1479 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1480
1481 let next_height = height.next()?;
1482 let next_block_hash =
1483 read::find::hash_by_height(best_chain.clone(), &state.db, next_height);
1484
1485 let header = read::block_header(best_chain, &state.db, height.into())
1486 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1487
1488 Ok(ReadResponse::BlockHeader {
1489 header,
1490 hash,
1491 height,
1492 next_block_hash,
1493 })
1494 }
1495
1496 ReadRequest::Transaction(hash) => Ok(ReadResponse::Transaction(
1498 read::mined_transaction(state.latest_best_chain(), &state.db, hash),
1499 )),
1500
1501 ReadRequest::AnyChainTransaction(hash) => {
1502 Ok(ReadResponse::AnyChainTransaction(read::any_transaction(
1503 state.latest_non_finalized_state().chain_iter(),
1504 &state.db,
1505 hash,
1506 )))
1507 }
1508
1509 ReadRequest::TransactionIdsForBlock(hash_or_height) => Ok(
1511 ReadResponse::TransactionIdsForBlock(read::transaction_hashes_for_block(
1512 state.latest_best_chain(),
1513 &state.db,
1514 hash_or_height,
1515 )),
1516 ),
1517
1518 ReadRequest::AnyChainTransactionIdsForBlock(hash_or_height) => {
1519 Ok(ReadResponse::AnyChainTransactionIdsForBlock(
1520 read::transaction_hashes_for_any_block(
1521 state.latest_non_finalized_state().chain_iter(),
1522 &state.db,
1523 hash_or_height,
1524 ),
1525 ))
1526 }
1527
1528 #[cfg(feature = "indexer")]
1529 ReadRequest::SpendingTransactionId(spend) => Ok(ReadResponse::TransactionId(
1530 read::spending_transaction_hash(state.latest_best_chain(), &state.db, spend),
1531 )),
1532
1533 ReadRequest::UnspentBestChainUtxo(outpoint) => Ok(ReadResponse::UnspentBestChainUtxo(
1534 read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint),
1535 )),
1536
1537 ReadRequest::AnyChainUtxo(outpoint) => Ok(ReadResponse::AnyChainUtxo(read::any_utxo(
1539 state.latest_non_finalized_state(),
1540 &state.db,
1541 outpoint,
1542 ))),
1543
1544 ReadRequest::BlockLocator => Ok(ReadResponse::BlockLocator(
1546 read::block_locator(state.latest_best_chain(), &state.db).unwrap_or_default(),
1547 )),
1548
1549 ReadRequest::FindBlockHashes { known_blocks, stop } => {
1551 Ok(ReadResponse::BlockHashes(read::find_chain_hashes(
1552 state.latest_best_chain(),
1553 &state.db,
1554 known_blocks,
1555 stop,
1556 MAX_FIND_BLOCK_HASHES_RESULTS,
1557 )))
1558 }
1559
1560 ReadRequest::FindBlockHeaders { known_blocks, stop } => Ok(ReadResponse::BlockHeaders(
1562 read::find_chain_headers(
1563 state.latest_best_chain(),
1564 &state.db,
1565 known_blocks,
1566 stop,
1567 MAX_FIND_BLOCK_HEADERS_RESULTS,
1568 )
1569 .into_iter()
1570 .map(|header| CountedHeader { header })
1571 .collect(),
1572 )),
1573
1574 ReadRequest::FindForkPoint { known_blocks } => {
1575 let locator_len: u64 = known_blocks
1578 .len()
1579 .try_into()
1580 .expect("usize always fits in u64 on supported (<=64-bit) platforms");
1581 if locator_len > block::MAX_BLOCK_LOCATOR_LENGTH {
1582 return Err(BoxError::from(format!(
1583 "FindForkPoint locator length {locator_len} exceeds \
1584 MAX_BLOCK_LOCATOR_LENGTH ({})",
1585 block::MAX_BLOCK_LOCATOR_LENGTH,
1586 )));
1587 }
1588
1589 Ok(ReadResponse::ForkPoint(read::find_fork_point(
1590 state.latest_best_chain(),
1591 &state.db,
1592 known_blocks,
1593 )))
1594 }
1595
1596 ReadRequest::SaplingTree(hash_or_height) => Ok(ReadResponse::SaplingTree(
1597 read::sapling_tree(state.latest_best_chain(), &state.db, hash_or_height),
1598 )),
1599
1600 ReadRequest::OrchardTree(hash_or_height) => Ok(ReadResponse::OrchardTree(
1601 read::orchard_tree(state.latest_best_chain(), &state.db, hash_or_height),
1602 )),
1603
1604 ReadRequest::IronwoodTree(hash_or_height) => Ok(ReadResponse::IronwoodTree(
1605 read::ironwood_tree(state.latest_best_chain(), &state.db, hash_or_height),
1606 )),
1607
1608 ReadRequest::SaplingSubtrees { start_index, limit } => {
1609 let end_index = limit
1610 .and_then(|limit| start_index.0.checked_add(limit.0))
1611 .map(NoteCommitmentSubtreeIndex);
1612
1613 let best_chain = state.latest_best_chain();
1614 let sapling_subtrees = if let Some(end_index) = end_index {
1615 read::sapling_subtrees(best_chain, &state.db, start_index..end_index)
1616 } else {
1617 read::sapling_subtrees(best_chain, &state.db, start_index..)
1622 };
1623
1624 Ok(ReadResponse::SaplingSubtrees(sapling_subtrees))
1625 }
1626
1627 ReadRequest::OrchardSubtrees { start_index, limit } => {
1628 let end_index = limit
1629 .and_then(|limit| start_index.0.checked_add(limit.0))
1630 .map(NoteCommitmentSubtreeIndex);
1631
1632 let best_chain = state.latest_best_chain();
1633 let orchard_subtrees = if let Some(end_index) = end_index {
1634 read::orchard_subtrees(best_chain, &state.db, start_index..end_index)
1635 } else {
1636 read::orchard_subtrees(best_chain, &state.db, start_index..)
1641 };
1642
1643 Ok(ReadResponse::OrchardSubtrees(orchard_subtrees))
1644 }
1645
1646 ReadRequest::IronwoodSubtrees { start_index, limit } => {
1647 let end_index = limit
1648 .and_then(|limit| start_index.0.checked_add(limit.0))
1649 .map(NoteCommitmentSubtreeIndex);
1650
1651 let best_chain = state.latest_best_chain();
1652 let ironwood_subtrees = if let Some(end_index) = end_index {
1653 read::ironwood_subtrees(best_chain, &state.db, start_index..end_index)
1654 } else {
1655 read::ironwood_subtrees(best_chain, &state.db, start_index..)
1660 };
1661
1662 Ok(ReadResponse::IronwoodSubtrees(ironwood_subtrees))
1663 }
1664
1665 ReadRequest::AddressBalance(addresses) => {
1667 let (balance, received) =
1668 read::transparent_balance(state.latest_best_chain(), &state.db, addresses)?;
1669 Ok(ReadResponse::AddressBalance { balance, received })
1670 }
1671
1672 ReadRequest::TransactionIdsByAddresses {
1674 addresses,
1675 height_range,
1676 } => read::transparent_tx_ids(
1677 state.latest_best_chain(),
1678 &state.db,
1679 addresses,
1680 height_range,
1681 )
1682 .map(ReadResponse::AddressesTransactionIds),
1683
1684 ReadRequest::UtxosByAddresses(addresses) => read::address_utxos(
1686 &state.network,
1687 state.latest_best_chain(),
1688 &state.db,
1689 addresses,
1690 )
1691 .map(ReadResponse::AddressUtxos),
1692
1693 ReadRequest::CheckBestChainTipNullifiersAndAnchors(unmined_tx) => {
1694 let latest_non_finalized_best_chain = state.latest_best_chain();
1695
1696 check::nullifier::tx_no_duplicates_in_chain(
1697 &state.db,
1698 latest_non_finalized_best_chain.as_ref(),
1699 &unmined_tx.transaction,
1700 )?;
1701
1702 check::anchors::tx_anchors_refer_to_final_treestates(
1703 &state.db,
1704 latest_non_finalized_best_chain.as_ref(),
1705 &unmined_tx,
1706 )?;
1707
1708 Ok(ReadResponse::ValidBestChainTipNullifiersAndAnchors)
1709 }
1710
1711 ReadRequest::BestChainBlockHash(height) => Ok(ReadResponse::BlockHash(
1713 read::hash_by_height(state.latest_best_chain(), &state.db, height),
1714 )),
1715
1716 ReadRequest::ChainInfo => {
1718 read::difficulty::get_block_template_chain_info(
1730 &state.latest_non_finalized_state(),
1731 &state.db,
1732 &state.network,
1733 )
1734 .map(ReadResponse::ChainInfo)
1735 }
1736
1737 ReadRequest::SolutionRate { num_blocks, height } => {
1739 let latest_non_finalized_state = state.latest_non_finalized_state();
1740 let (tip_height, tip_hash) =
1748 match read::tip(latest_non_finalized_state.best_chain(), &state.db) {
1749 Some(tip_hash) => tip_hash,
1750 None => return Ok(ReadResponse::SolutionRate(None)),
1751 };
1752
1753 let start_hash = match height {
1754 Some(height) if height < tip_height => read::hash_by_height(
1755 latest_non_finalized_state.best_chain(),
1756 &state.db,
1757 height,
1758 ),
1759 _ => Some(tip_hash),
1761 };
1762
1763 let solution_rate = start_hash.and_then(|start_hash| {
1764 read::difficulty::solution_rate(
1765 &latest_non_finalized_state,
1766 &state.db,
1767 num_blocks,
1768 start_hash,
1769 )
1770 });
1771
1772 Ok(ReadResponse::SolutionRate(solution_rate))
1773 }
1774
1775 ReadRequest::CheckBlockProposalValidity(semantically_verified) => {
1776 tracing::debug!(
1777 "attempting to validate and commit block proposal \
1778 onto a cloned non-finalized state"
1779 );
1780 let mut latest_non_finalized_state = state.latest_non_finalized_state();
1781
1782 let Some((_best_tip_height, best_tip_hash)) =
1784 read::best_tip(&latest_non_finalized_state, &state.db)
1785 else {
1786 return Err(
1787 "state is empty: wait for Zebra to sync before submitting a proposal"
1788 .into(),
1789 );
1790 };
1791
1792 if semantically_verified.block.header.previous_block_hash != best_tip_hash {
1793 return Err("proposal is not based on the current best chain tip: \
1794 previous block hash must be the best chain tip"
1795 .into());
1796 }
1797
1798 latest_non_finalized_state.disable_metrics();
1804
1805 write::validate_and_commit_non_finalized(
1806 &state.db,
1807 &mut latest_non_finalized_state,
1808 semantically_verified,
1809 )?;
1810
1811 Ok(ReadResponse::ValidBlockProposal)
1812 }
1813
1814 ReadRequest::TipBlockSize => {
1815 Ok(ReadResponse::TipBlockSize(
1817 state
1818 .best_tip()
1819 .and_then(|(tip_height, _)| {
1820 read::block_info(
1821 state.latest_best_chain(),
1822 &state.db,
1823 tip_height.into(),
1824 )
1825 })
1826 .map(|info| info.size().try_into().expect("u32 should fit in usize"))
1827 .or_else(|| {
1828 find::tip_block(state.latest_best_chain(), &state.db)
1829 .map(|b| b.zcash_serialized_size())
1830 }),
1831 ))
1832 }
1833
1834 ReadRequest::NonFinalizedBlocksListener { .. } => {
1835 unreachable!("should return early");
1836 }
1837
1838 ReadRequest::IsTransparentOutputSpent(outpoint) => {
1840 let is_spent = read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint);
1841 Ok(ReadResponse::IsTransparentOutputSpent(is_spent.is_none()))
1842 }
1843 };
1844
1845 timed_span.spawn_blocking(request_handler)
1846 }
1847}
1848
1849pub async fn init(
1865 config: Config,
1866 network: &Network,
1867 max_checkpoint_height: block::Height,
1868 checkpoint_verify_concurrency_limit: usize,
1869) -> (
1870 BoxService<Request, Response, BoxError>,
1871 ReadStateService,
1872 LatestChainTip,
1873 ChainTipChange,
1874) {
1875 let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
1876 StateService::new(
1877 config,
1878 network,
1879 max_checkpoint_height,
1880 checkpoint_verify_concurrency_limit,
1881 )
1882 .await;
1883
1884 (
1885 BoxService::new(state_service),
1886 read_only_state_service,
1887 latest_chain_tip,
1888 chain_tip_change,
1889 )
1890}
1891
1892pub fn init_read_only(
1899 config: Config,
1900 network: &Network,
1901) -> Result<
1902 (
1903 ReadStateService,
1904 ZebraDb,
1905 tokio::sync::watch::Sender<NonFinalizedState>,
1906 ),
1907 StateInitError,
1908> {
1909 let finalized_state = FinalizedState::new_with_debug(
1910 &config,
1911 network,
1912 true,
1913 #[cfg(feature = "elasticsearch")]
1914 false,
1915 true,
1916 )?;
1917 let (non_finalized_state_sender, non_finalized_state_receiver) =
1918 tokio::sync::watch::channel(NonFinalizedState::new(network));
1919
1920 Ok((
1921 ReadStateService::new(
1922 &finalized_state,
1923 None,
1924 WatchReceiver::new(non_finalized_state_receiver),
1925 ),
1926 finalized_state.db.clone(),
1927 non_finalized_state_sender,
1928 ))
1929}
1930
1931pub fn spawn_init_read_only(
1938 config: Config,
1939 network: &Network,
1940) -> tokio::task::JoinHandle<
1941 Result<
1942 (
1943 ReadStateService,
1944 ZebraDb,
1945 tokio::sync::watch::Sender<NonFinalizedState>,
1946 ),
1947 StateInitError,
1948 >,
1949> {
1950 let network = network.clone();
1951 tokio::task::spawn_blocking(move || init_read_only(config, &network))
1952}
1953
1954#[cfg(any(test, feature = "proptest-impl"))]
1958pub async fn init_test(
1959 network: &Network,
1960) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
1961 let (state_service, _, _, _) =
1964 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1965
1966 Buffer::new(BoxService::new(state_service), 1)
1967}
1968
1969#[cfg(any(test, feature = "proptest-impl"))]
1974pub async fn init_test_services(
1975 network: &Network,
1976) -> (
1977 Buffer<BoxService<Request, Response, BoxError>, Request>,
1978 ReadStateService,
1979 LatestChainTip,
1980 ChainTipChange,
1981) {
1982 let (state_service, read_state_service, latest_chain_tip, chain_tip_change) =
1985 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1986
1987 let state_service = Buffer::new(BoxService::new(state_service), 1);
1988
1989 (
1990 state_service,
1991 read_state_service,
1992 latest_chain_tip,
1993 chain_tip_change,
1994 )
1995}