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 is_finalized_tip_past_max_checkpoint = if let Some(tip) = &finalized_tip {
356 tip.coinbase_height().expect("valid block must have height") >= max_checkpoint_height
357 } else {
358 false
359 };
360 let backup_dir_path = config.non_finalized_state_backup_dir(network);
361 let skip_backup_task = config.debug_skip_non_finalized_state_backup_task;
362 let (non_finalized_state, non_finalized_state_sender, non_finalized_state_receiver) =
363 NonFinalizedState::new(network)
364 .with_backup(
365 backup_dir_path.clone(),
366 &finalized_state.db,
367 is_finalized_tip_past_max_checkpoint,
368 config.debug_skip_non_finalized_state_backup_task,
369 )
370 .await;
371
372 let non_finalized_block_write_sent_hashes = SentHashes::new(&non_finalized_state);
373 let initial_tip = non_finalized_state
374 .best_tip_block()
375 .map(|cv_block| cv_block.block.clone())
376 .or(finalized_tip)
377 .map(CheckpointVerifiedBlock::from)
378 .map(ChainTipBlock::from);
379
380 tracing::info!(chain_tip = ?initial_tip.as_ref().map(|tip| (tip.hash, tip.height)), "loaded Zebra state cache");
381
382 let (chain_tip_sender, latest_chain_tip, chain_tip_change) =
383 ChainTipSender::new(initial_tip, network);
384
385 let finalized_state_for_writing = finalized_state.clone();
386 let should_use_finalized_block_write_sender = non_finalized_state.is_chain_set_empty();
387 let sync_backup_dir_path = backup_dir_path.filter(|_| skip_backup_task);
388 let (
389 block_write_sender,
390 invalid_block_write_reset_receiver,
391 non_finalized_rejected_receiver,
392 block_write_task,
393 ) = write::BlockWriteSender::spawn(
394 finalized_state_for_writing,
395 non_finalized_state,
396 chain_tip_sender,
397 non_finalized_state_sender,
398 should_use_finalized_block_write_sender,
399 sync_backup_dir_path,
400 );
401
402 let read_service = ReadStateService::new(
403 &finalized_state,
404 block_write_task,
405 non_finalized_state_receiver,
406 );
407
408 let full_verifier_utxo_lookahead = max_checkpoint_height
409 - HeightDiff::try_from(checkpoint_verify_concurrency_limit)
410 .expect("fits in HeightDiff");
411 let full_verifier_utxo_lookahead =
412 full_verifier_utxo_lookahead.unwrap_or(block::Height::MIN);
413 let non_finalized_state_queued_blocks = QueuedBlocks::default();
414 let pending_utxos = PendingUtxos::default();
415
416 let finalized_block_write_last_sent_hash =
417 tokio::task::spawn_blocking(move || finalized_state.db.finalized_tip_hash())
418 .await
419 .expect("failed to join blocking task");
420
421 let state = Self {
422 network: network.clone(),
423 full_verifier_utxo_lookahead,
424 non_finalized_state_queued_blocks,
425 finalized_state_queued_blocks: HashMap::new(),
426 block_write_sender,
427 finalized_block_write_last_sent_hash,
428 non_finalized_block_write_sent_hashes,
429 invalid_block_write_reset_receiver,
430 non_finalized_rejected_receiver,
431 pending_utxos,
432 last_prune: Instant::now(),
433 read_service: read_service.clone(),
434 max_finalized_queue_height: f64::NAN,
435 };
436 timer.finish_desc("initializing state service");
437
438 tracing::info!("starting legacy chain check");
439 let timer = CodeTimer::start();
440
441 if let (Some(tip), Some(nu5_activation_height)) = (
442 {
443 let read_state = state.read_service.clone();
444 tokio::task::spawn_blocking(move || read_state.best_tip())
445 .await
446 .expect("task should not panic")
447 },
448 NetworkUpgrade::Nu5.activation_height(network),
449 ) {
450 if let Err(error) = check::legacy_chain(
451 nu5_activation_height,
452 any_ancestor_blocks(
453 &state.read_service.latest_non_finalized_state(),
454 &state.read_service.db,
455 tip.1,
456 ),
457 &state.network,
458 MAX_LEGACY_CHAIN_BLOCKS,
459 ) {
460 let legacy_db_path = state.read_service.db.path().to_path_buf();
461 panic!(
462 "Cached state contains a legacy chain.\n\
463 An outdated Zebra version did not know about a recent network upgrade,\n\
464 so it followed a legacy chain using outdated consensus branch rules.\n\
465 Hint: Delete your database, and restart Zebra to do a full sync.\n\
466 Database path: {legacy_db_path:?}\n\
467 Error: {error:?}",
468 );
469 }
470 }
471
472 tracing::info!("cached state consensus branch is valid: no legacy chain found");
473 timer.finish_desc("legacy chain check");
474
475 let db_for_metrics = read_service.db.clone();
477 tokio::spawn(async move {
478 let mut interval = tokio::time::interval(Duration::from_secs(30));
479 loop {
480 interval.tick().await;
481 db_for_metrics.export_metrics();
482 }
483 });
484
485 (state, read_service, latest_chain_tip, chain_tip_change)
486 }
487
488 pub fn log_db_metrics(&self) {
490 self.read_service.db.print_db_metrics();
491 }
492
493 fn queue_and_commit_to_finalized_state(
497 &mut self,
498 checkpoint_verified: CheckpointVerifiedBlock,
499 ) -> oneshot::Receiver<Result<block::Hash, CommitCheckpointVerifiedError>> {
500 let queued_prev_hash = checkpoint_verified.block.header.previous_block_hash;
506 let queued_height = checkpoint_verified.height;
507
508 if self.is_close_to_final_checkpoint(queued_height) {
511 self.non_finalized_block_write_sent_hashes
512 .add_finalized(&checkpoint_verified)
513 }
514
515 let (rsp_tx, rsp_rx) = oneshot::channel();
516 let queued = (checkpoint_verified, rsp_tx);
517
518 if self.block_write_sender.finalized.is_some() {
519 if let Some(duplicate_queued) = self
521 .finalized_state_queued_blocks
522 .insert(queued_prev_hash, queued)
523 {
524 Self::send_checkpoint_verified_block_error(
525 duplicate_queued,
526 CommitBlockError::new_duplicate(
527 Some(queued_prev_hash.into()),
528 KnownBlock::Queue,
529 ),
530 );
531 }
532
533 self.drain_finalized_queue_and_commit();
534 } else {
535 Self::send_checkpoint_verified_block_error(
541 queued,
542 CommitBlockError::new_duplicate(None, KnownBlock::Finalized),
543 );
544
545 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
546 None,
547 KnownBlock::Finalized,
548 ));
549 }
550
551 if self.finalized_state_queued_blocks.is_empty() {
552 self.max_finalized_queue_height = f64::NAN;
553 } else if self.max_finalized_queue_height.is_nan()
554 || self.max_finalized_queue_height < queued_height.0 as f64
555 {
556 self.max_finalized_queue_height = queued_height.0 as f64;
562 }
563
564 metrics::gauge!("state.checkpoint.queued.max.height").set(self.max_finalized_queue_height);
565 metrics::gauge!("state.checkpoint.queued.block.count")
566 .set(self.finalized_state_queued_blocks.len() as f64);
567
568 rsp_rx
569 }
570
571 pub fn drain_finalized_queue_and_commit(&mut self) {
579 use tokio::sync::mpsc::error::{SendError, TryRecvError};
580
581 match self.invalid_block_write_reset_receiver.try_recv() {
588 Ok(reset_tip_hash) => self.finalized_block_write_last_sent_hash = reset_tip_hash,
589 Err(TryRecvError::Disconnected) => {
590 info!("Block commit task closed the block reset channel. Is Zebra shutting down?");
591 return;
592 }
593 Err(TryRecvError::Empty) => {}
595 }
596
597 while let Some(queued_block) = self
598 .finalized_state_queued_blocks
599 .remove(&self.finalized_block_write_last_sent_hash)
600 {
601 let last_sent_finalized_block_height = queued_block.0.height;
602
603 self.finalized_block_write_last_sent_hash = queued_block.0.hash;
604
605 if let Some(finalized_block_write_sender) = &self.block_write_sender.finalized {
608 let send_result = finalized_block_write_sender.send(queued_block);
609
610 if let Err(SendError(queued)) = send_result {
612 Self::send_checkpoint_verified_block_error(
614 queued,
615 CommitBlockError::WriteTaskExited,
616 );
617
618 self.clear_finalized_block_queue(CommitBlockError::WriteTaskExited);
619 } else {
620 metrics::gauge!("state.checkpoint.sent.block.height")
621 .set(last_sent_finalized_block_height.0 as f64);
622 };
623 }
624 }
625 }
626
627 fn drain_non_finalized_rejected_hashes(&mut self) {
640 use tokio::sync::mpsc::error::TryRecvError;
641
642 loop {
643 match self.non_finalized_rejected_receiver.try_recv() {
644 Ok(hash) => {
645 self.non_finalized_block_write_sent_hashes.remove(&hash);
646 }
647 Err(TryRecvError::Empty) => break,
648 Err(TryRecvError::Disconnected) => {
649 info!(
650 "Block commit task closed the non-finalized rejected hash channel. \
651 Is Zebra shutting down?"
652 );
653 break;
654 }
655 }
656 }
657 }
658
659 fn clear_finalized_block_queue(
661 &mut self,
662 error: impl Into<CommitCheckpointVerifiedError> + Clone,
663 ) {
664 for (_hash, queued) in self.finalized_state_queued_blocks.drain() {
665 Self::send_checkpoint_verified_block_error(queued, error.clone());
666 }
667 }
668
669 fn send_checkpoint_verified_block_error(
671 queued: QueuedCheckpointVerified,
672 error: impl Into<CommitCheckpointVerifiedError>,
673 ) {
674 let (finalized, rsp_tx) = queued;
675
676 let _ = rsp_tx.send(Err(error.into()));
679 std::mem::drop(finalized);
680 }
681
682 fn clear_non_finalized_block_queue(
684 &mut self,
685 error: impl Into<CommitSemanticallyVerifiedError> + Clone,
686 ) {
687 for (_hash, queued) in self.non_finalized_state_queued_blocks.drain() {
688 Self::send_semantically_verified_block_error(queued, error.clone());
689 }
690 }
691
692 fn send_semantically_verified_block_error(
694 queued: QueuedSemanticallyVerified,
695 error: impl Into<CommitSemanticallyVerifiedError>,
696 ) {
697 let (finalized, rsp_tx) = queued;
698
699 let _ = rsp_tx.send(Err(error.into()));
702 std::mem::drop(finalized);
703 }
704
705 #[instrument(level = "debug", skip(self, semantically_verified))]
713 fn queue_and_commit_to_non_finalized_state(
714 &mut self,
715 semantically_verified: SemanticallyVerifiedBlock,
716 ) -> oneshot::Receiver<Result<block::Hash, CommitSemanticallyVerifiedError>> {
717 tracing::debug!(block = %semantically_verified.block, "queueing block for contextual verification");
718 let parent_hash = semantically_verified.block.header.previous_block_hash;
719
720 self.drain_non_finalized_rejected_hashes();
725
726 if self
727 .non_finalized_block_write_sent_hashes
728 .contains(&semantically_verified.hash)
729 {
730 let (rsp_tx, rsp_rx) = oneshot::channel();
731 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
732 Some(semantically_verified.hash.into()),
733 KnownBlock::WriteChannel,
734 )
735 .into()));
736 return rsp_rx;
737 }
738
739 if self
740 .read_service
741 .db
742 .contains_height(semantically_verified.height)
743 {
744 let (rsp_tx, rsp_rx) = oneshot::channel();
745 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
746 Some(semantically_verified.height.into()),
747 KnownBlock::Finalized,
748 )
749 .into()));
750 return rsp_rx;
751 }
752
753 let rsp_rx = if let Some((_, old_rsp_tx)) = self
757 .non_finalized_state_queued_blocks
758 .get_mut(&semantically_verified.hash)
759 {
760 tracing::debug!("replacing older queued request with new request");
761 let (mut rsp_tx, rsp_rx) = oneshot::channel();
762 std::mem::swap(old_rsp_tx, &mut rsp_tx);
763 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
764 Some(semantically_verified.hash.into()),
765 KnownBlock::Queue,
766 )
767 .into()));
768 rsp_rx
769 } else {
770 let (rsp_tx, rsp_rx) = oneshot::channel();
771 self.non_finalized_state_queued_blocks
772 .queue((semantically_verified, rsp_tx));
773 rsp_rx
774 };
775
776 if self.block_write_sender.finalized.is_some()
785 && self
786 .non_finalized_state_queued_blocks
787 .has_queued_children(self.finalized_block_write_last_sent_hash)
788 && self.read_service.db.finalized_tip_hash()
789 == self.finalized_block_write_last_sent_hash
790 {
791 std::mem::drop(self.block_write_sender.finalized.take());
794 self.non_finalized_block_write_sent_hashes = SentHashes::default();
796 self.non_finalized_block_write_sent_hashes
798 .can_fork_chain_at_hashes = true;
799 self.send_ready_non_finalized_queued(self.finalized_block_write_last_sent_hash);
801 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
803 None,
804 KnownBlock::Finalized,
805 ));
806 } else if !self.can_fork_chain_at(&parent_hash) {
807 tracing::trace!("unready to verify, returning early");
808 } else if self.block_write_sender.finalized.is_none() {
809 self.send_ready_non_finalized_queued(parent_hash);
811
812 let finalized_tip_height = self.read_service.db.finalized_tip_height().expect(
813 "Finalized state must have at least one block before committing non-finalized state",
814 );
815
816 self.non_finalized_state_queued_blocks
817 .prune_by_height(finalized_tip_height);
818
819 self.non_finalized_block_write_sent_hashes
820 .prune_by_height(finalized_tip_height);
821 }
822
823 rsp_rx
824 }
825
826 fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
828 self.non_finalized_block_write_sent_hashes
829 .can_fork_chain_at(hash)
830 || &self.read_service.db.finalized_tip_hash() == hash
831 }
832
833 fn is_close_to_final_checkpoint(&self, queued_height: block::Height) -> bool {
841 queued_height >= self.full_verifier_utxo_lookahead
842 }
843
844 #[tracing::instrument(level = "debug", skip(self, new_parent))]
847 fn send_ready_non_finalized_queued(&mut self, new_parent: block::Hash) {
848 use tokio::sync::mpsc::error::SendError;
849 if let Some(non_finalized_block_write_sender) = &self.block_write_sender.non_finalized {
850 let mut new_parents: Vec<block::Hash> = vec![new_parent];
851
852 while let Some(parent_hash) = new_parents.pop() {
853 let queued_children = self
854 .non_finalized_state_queued_blocks
855 .dequeue_children(parent_hash);
856
857 for queued_child in queued_children {
858 let (SemanticallyVerifiedBlock { hash, .. }, _) = queued_child;
859
860 self.non_finalized_block_write_sent_hashes
861 .add(&queued_child.0);
862 let send_result = non_finalized_block_write_sender.send(queued_child.into());
863
864 if let Err(SendError(NonFinalizedWriteMessage::Commit(queued))) = send_result {
865 Self::send_semantically_verified_block_error(
867 queued,
868 CommitBlockError::WriteTaskExited,
869 );
870
871 self.clear_non_finalized_block_queue(CommitBlockError::WriteTaskExited);
872
873 return;
874 };
875
876 new_parents.push(hash);
877 }
878 }
879
880 self.non_finalized_block_write_sent_hashes.finish_batch();
881 };
882 }
883
884 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
886 self.read_service.best_tip()
887 }
888
889 fn send_invalidate_block(
890 &self,
891 hash: block::Hash,
892 ) -> oneshot::Receiver<Result<block::Hash, InvalidateError>> {
893 let (rsp_tx, rsp_rx) = oneshot::channel();
894
895 let Some(sender) = &self.block_write_sender.non_finalized else {
896 let _ = rsp_tx.send(Err(InvalidateError::ProcessingCheckpointedBlocks));
897 return rsp_rx;
898 };
899
900 if let Err(tokio::sync::mpsc::error::SendError(error)) =
901 sender.send(NonFinalizedWriteMessage::Invalidate { hash, rsp_tx })
902 {
903 let NonFinalizedWriteMessage::Invalidate { rsp_tx, .. } = error else {
904 unreachable!("should return the same Invalidate message could not be sent");
905 };
906
907 let _ = rsp_tx.send(Err(InvalidateError::SendInvalidateRequestFailed));
908 }
909
910 rsp_rx
911 }
912
913 fn send_reconsider_block(
914 &self,
915 hash: block::Hash,
916 ) -> oneshot::Receiver<Result<Vec<block::Hash>, ReconsiderError>> {
917 let (rsp_tx, rsp_rx) = oneshot::channel();
918
919 let Some(sender) = &self.block_write_sender.non_finalized else {
920 let _ = rsp_tx.send(Err(ReconsiderError::CheckpointCommitInProgress));
921 return rsp_rx;
922 };
923
924 if let Err(tokio::sync::mpsc::error::SendError(error)) =
925 sender.send(NonFinalizedWriteMessage::Reconsider { hash, rsp_tx })
926 {
927 let NonFinalizedWriteMessage::Reconsider { rsp_tx, .. } = error else {
928 unreachable!("should return the same Reconsider message could not be sent");
929 };
930
931 let _ = rsp_tx.send(Err(ReconsiderError::ReconsiderSendFailed));
932 }
933
934 rsp_rx
935 }
936
937 fn assert_block_can_be_validated(&self, block: &SemanticallyVerifiedBlock) {
939 assert!(
941 block.height > self.network.mandatory_checkpoint_height(),
942 "invalid semantically verified block height: the canopy checkpoint is mandatory, pre-canopy \
943 blocks, and the canopy activation block, must be committed to the state as finalized \
944 blocks"
945 );
946 }
947
948 fn known_sent_hash(&self, hash: &block::Hash) -> Option<KnownBlock> {
949 self.non_finalized_block_write_sent_hashes
950 .contains(hash)
951 .then_some(KnownBlock::WriteChannel)
952 }
953}
954
955impl ReadStateService {
956 pub(crate) fn new(
962 finalized_state: &FinalizedState,
963 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
964 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
965 ) -> Self {
966 let read_service = Self {
967 network: finalized_state.network(),
968 db: finalized_state.db.clone(),
969 non_finalized_state_receiver,
970 block_write_task,
971 };
972
973 tracing::debug!("created new read-only state service");
974
975 read_service
976 }
977
978 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
980 read::best_tip(&self.latest_non_finalized_state(), &self.db)
981 }
982
983 fn latest_non_finalized_state(&self) -> NonFinalizedState {
985 self.non_finalized_state_receiver.cloned_watch_data()
986 }
987
988 fn latest_best_chain(&self) -> Option<Arc<Chain>> {
990 self.non_finalized_state_receiver
991 .borrow_mapped(|non_finalized_state| non_finalized_state.best_chain().cloned())
992 }
993
994 #[cfg(any(test, feature = "proptest-impl"))]
997 pub fn db(&self) -> &ZebraDb {
998 &self.db
999 }
1000
1001 pub fn log_db_metrics(&self) {
1003 self.db.print_db_metrics();
1004 }
1005}
1006
1007impl Service<Request> for StateService {
1008 type Response = Response;
1009 type Error = BoxError;
1010 type Future =
1011 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1012
1013 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1014 let poll = self.read_service.poll_ready(cx);
1016
1017 let now = Instant::now();
1019
1020 if self.last_prune + Self::PRUNE_INTERVAL < now {
1021 let tip = self.best_tip();
1022 let old_len = self.pending_utxos.len();
1023
1024 self.pending_utxos.prune();
1025 self.last_prune = now;
1026
1027 let new_len = self.pending_utxos.len();
1028 let prune_count = old_len
1029 .checked_sub(new_len)
1030 .expect("prune does not add any utxo requests");
1031 if prune_count > 0 {
1032 tracing::debug!(
1033 ?old_len,
1034 ?new_len,
1035 ?prune_count,
1036 ?tip,
1037 "pruned utxo requests"
1038 );
1039 } else {
1040 tracing::debug!(len = ?old_len, ?tip, "no utxo requests needed pruning");
1041 }
1042 }
1043
1044 poll
1045 }
1046
1047 #[instrument(name = "state", skip(self, req))]
1048 fn call(&mut self, req: Request) -> Self::Future {
1049 req.count_metric();
1050 let span = Span::current();
1051
1052 match req {
1053 Request::CommitSemanticallyVerifiedBlock(semantically_verified) => {
1058 let timer = CodeTimer::start();
1059 self.assert_block_can_be_validated(&semantically_verified);
1060
1061 self.pending_utxos
1062 .check_against_ordered(&semantically_verified.new_outputs);
1063
1064 let rsp_rx = tokio::task::block_in_place(move || {
1076 span.in_scope(|| {
1077 self.queue_and_commit_to_non_finalized_state(semantically_verified)
1078 })
1079 });
1080
1081 timer.finish_desc("CommitSemanticallyVerifiedBlock");
1087
1088 let span = Span::current();
1092 async move {
1093 rsp_rx
1094 .await
1095 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1096 .and_then(|result| result)
1097 .map_err(BoxError::from)
1098 .map(Response::Committed)
1099 }
1100 .instrument(span)
1101 .boxed()
1102 }
1103
1104 Request::CommitCheckpointVerifiedBlock(finalized) => {
1109 let timer = CodeTimer::start();
1110 self.pending_utxos
1124 .check_against_ordered(&finalized.new_outputs);
1125
1126 let rsp_rx = self.queue_and_commit_to_finalized_state(finalized);
1131
1132 timer.finish_desc("CommitCheckpointVerifiedBlock");
1138
1139 async move {
1143 rsp_rx
1144 .await
1145 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1146 .and_then(|result| result)
1147 .map_err(BoxError::from)
1148 .map(Response::Committed)
1149 }
1150 .instrument(span)
1151 .boxed()
1152 }
1153
1154 Request::AwaitUtxo(outpoint) => {
1157 let timer = CodeTimer::start();
1158 let response_fut = self.pending_utxos.queue(outpoint);
1160 let response_fut = response_fut.instrument(span).boxed();
1164
1165 if let Some(utxo) = self.non_finalized_state_queued_blocks.utxo(&outpoint) {
1168 self.pending_utxos.respond(&outpoint, utxo);
1169
1170 timer.finish_desc("AwaitUtxo/queued-non-finalized");
1172
1173 return response_fut;
1174 }
1175
1176 self.drain_non_finalized_rejected_hashes();
1178
1179 if let Some(utxo) = self.non_finalized_block_write_sent_hashes.utxo(&outpoint) {
1180 self.pending_utxos.respond(&outpoint, utxo);
1181
1182 timer.finish_desc("AwaitUtxo/sent-non-finalized");
1184
1185 return response_fut;
1186 }
1187
1188 let read_service = self.read_service.clone();
1197
1198 async move {
1200 let req = ReadRequest::AnyChainUtxo(outpoint);
1201
1202 let rsp = read_service.oneshot(req).await?;
1203
1204 if let ReadResponse::AnyChainUtxo(Some(utxo)) = rsp {
1217 timer.finish_desc("AwaitUtxo/any-chain");
1219
1220 return Ok(Response::Utxo(utxo));
1221 }
1222
1223 timer.finish_desc("AwaitUtxo/waiting");
1225
1226 response_fut.await
1227 }
1228 .boxed()
1229 }
1230
1231 Request::KnownBlock(hash) => {
1234 let timer = CodeTimer::start();
1235
1236 self.drain_non_finalized_rejected_hashes();
1237
1238 let sent_hash_response = self.known_sent_hash(&hash);
1239 let read_service = self.read_service.clone();
1240
1241 async move {
1242 if sent_hash_response.is_some() {
1243 return Ok(Response::KnownBlock(sent_hash_response));
1244 };
1245
1246 let response = read::non_finalized_state_contains_block_hash(
1247 &read_service.latest_non_finalized_state(),
1248 hash,
1249 )
1250 .or_else(|| read::finalized_state_contains_block_hash(&read_service.db, hash));
1252
1253 timer.finish_desc("Request::KnownBlock");
1254
1255 Ok(Response::KnownBlock(response))
1256 }
1257 .boxed()
1258 }
1259
1260 Request::InvalidateBlock(block_hash) => {
1262 let rsp_rx = tokio::task::block_in_place(move || {
1263 span.in_scope(|| self.send_invalidate_block(block_hash))
1264 });
1265
1266 let span = Span::current();
1270 async move {
1271 rsp_rx
1272 .await
1273 .map_err(|_recv_error| InvalidateError::InvalidateRequestDropped)
1274 .and_then(|result| result)
1275 .map_err(BoxError::from)
1276 .map(Response::Invalidated)
1277 }
1278 .instrument(span)
1279 .boxed()
1280 }
1281
1282 Request::ReconsiderBlock(block_hash) => {
1284 let rsp_rx = tokio::task::block_in_place(move || {
1285 span.in_scope(|| self.send_reconsider_block(block_hash))
1286 });
1287
1288 let span = Span::current();
1292 async move {
1293 rsp_rx
1294 .await
1295 .map_err(|_recv_error| ReconsiderError::ReconsiderResponseDropped)
1296 .and_then(|result| result)
1297 .map_err(BoxError::from)
1298 .map(Response::Reconsidered)
1299 }
1300 .instrument(span)
1301 .boxed()
1302 }
1303
1304 Request::Tip
1306 | Request::Depth(_)
1307 | Request::BestChainNextMedianTimePast
1308 | Request::BestChainBlockHash(_)
1309 | Request::BlockLocator
1310 | Request::Transaction(_)
1311 | Request::AnyChainTransaction(_)
1312 | Request::UnspentBestChainUtxo(_)
1313 | Request::Block(_)
1314 | Request::AnyChainBlock(_)
1315 | Request::BlockAndSize(_)
1316 | Request::BlockHeader(_)
1317 | Request::FindBlockHashes { .. }
1318 | Request::FindBlockHeaders { .. }
1319 | Request::CheckBestChainTipNullifiersAndAnchors(_)
1320 | Request::CheckBlockProposalValidity(_) => {
1321 let read_service = self.read_service.clone();
1323
1324 async move {
1325 let req = req
1326 .try_into()
1327 .expect("ReadRequest conversion should not fail");
1328
1329 let rsp = read_service.oneshot(req).await?;
1330 let rsp = rsp.try_into().expect("Response conversion should not fail");
1331
1332 Ok(rsp)
1333 }
1334 .boxed()
1335 }
1336 }
1337 }
1338}
1339
1340impl Service<ReadRequest> for ReadStateService {
1341 type Response = ReadResponse;
1342 type Error = BoxError;
1343 type Future =
1344 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1345
1346 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1347 let block_write_task = self.block_write_task.take();
1351
1352 if let Some(block_write_task) = block_write_task {
1353 if block_write_task.is_finished() {
1354 if let Some(block_write_task) = Arc::into_inner(block_write_task) {
1355 if let Err(thread_panic) = block_write_task.join() {
1357 std::panic::resume_unwind(thread_panic);
1358 }
1359 }
1360 } else {
1361 self.block_write_task = Some(block_write_task);
1363 }
1364 }
1365
1366 self.db.check_for_panics();
1367
1368 Poll::Ready(Ok(()))
1369 }
1370
1371 #[instrument(name = "read_state", skip(self, req))]
1372 fn call(&mut self, req: ReadRequest) -> Self::Future {
1373 req.count_metric();
1374 let timer = CodeTimer::start_desc(req.variant_name());
1375 let span = Span::current();
1376 let timed_span = TimedSpan::new(timer, span);
1377 let state = self.clone();
1378
1379 if let ReadRequest::NonFinalizedBlocksListener { known_chain_tips } = req {
1380 let non_finalized_blocks_listener = NonFinalizedBlocksListener::spawn(
1383 self.non_finalized_state_receiver.clone(),
1384 known_chain_tips,
1385 );
1386
1387 return async move {
1388 Ok(ReadResponse::NonFinalizedBlocksListener(
1389 non_finalized_blocks_listener,
1390 ))
1391 }
1392 .boxed();
1393 };
1394
1395 let request_handler = move || match req {
1396 ReadRequest::UsageInfo => Ok(ReadResponse::UsageInfo(state.db.size())),
1398
1399 ReadRequest::Tip => Ok(ReadResponse::Tip(read::tip(
1401 state.latest_best_chain(),
1402 &state.db,
1403 ))),
1404
1405 ReadRequest::TipPoolValues => {
1407 let (tip_height, tip_hash, value_balance) =
1408 read::tip_with_value_balance(state.latest_best_chain(), &state.db)?
1409 .ok_or(BoxError::from("no chain tip available yet"))?;
1410
1411 Ok(ReadResponse::TipPoolValues {
1412 tip_height,
1413 tip_hash,
1414 value_balance,
1415 })
1416 }
1417
1418 ReadRequest::BlockInfo(hash_or_height) => Ok(ReadResponse::BlockInfo(
1420 read::block_info(state.latest_best_chain(), &state.db, hash_or_height),
1421 )),
1422
1423 ReadRequest::Depth(hash) => Ok(ReadResponse::Depth(read::depth(
1425 state.latest_best_chain(),
1426 &state.db,
1427 hash,
1428 ))),
1429
1430 ReadRequest::BestChainNextMedianTimePast => {
1432 Ok(ReadResponse::BestChainNextMedianTimePast(
1433 read::next_median_time_past(&state.latest_non_finalized_state(), &state.db)?,
1434 ))
1435 }
1436
1437 ReadRequest::Block(hash_or_height) => Ok(ReadResponse::Block(read::block(
1439 state.latest_best_chain(),
1440 &state.db,
1441 hash_or_height,
1442 ))),
1443
1444 ReadRequest::AnyChainBlock(hash_or_height) => Ok(ReadResponse::Block(read::any_block(
1445 state.latest_non_finalized_state().chain_iter(),
1446 &state.db,
1447 hash_or_height,
1448 ))),
1449
1450 ReadRequest::BlockAndSize(hash_or_height) => Ok(ReadResponse::BlockAndSize(
1452 read::block_and_size(state.latest_best_chain(), &state.db, hash_or_height),
1453 )),
1454
1455 ReadRequest::BlockHeader(hash_or_height) => {
1457 let best_chain = state.latest_best_chain();
1458
1459 let height = hash_or_height
1460 .height_or_else(|hash| {
1461 read::find::height_by_hash(best_chain.clone(), &state.db, hash)
1462 })
1463 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1464
1465 let hash = hash_or_height
1466 .hash_or_else(|height| {
1467 read::find::hash_by_height(best_chain.clone(), &state.db, height)
1468 })
1469 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1470
1471 let next_height = height.next()?;
1472 let next_block_hash =
1473 read::find::hash_by_height(best_chain.clone(), &state.db, next_height);
1474
1475 let header = read::block_header(best_chain, &state.db, height.into())
1476 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1477
1478 Ok(ReadResponse::BlockHeader {
1479 header,
1480 hash,
1481 height,
1482 next_block_hash,
1483 })
1484 }
1485
1486 ReadRequest::Transaction(hash) => Ok(ReadResponse::Transaction(
1488 read::mined_transaction(state.latest_best_chain(), &state.db, hash),
1489 )),
1490
1491 ReadRequest::AnyChainTransaction(hash) => {
1492 Ok(ReadResponse::AnyChainTransaction(read::any_transaction(
1493 state.latest_non_finalized_state().chain_iter(),
1494 &state.db,
1495 hash,
1496 )))
1497 }
1498
1499 ReadRequest::TransactionIdsForBlock(hash_or_height) => Ok(
1501 ReadResponse::TransactionIdsForBlock(read::transaction_hashes_for_block(
1502 state.latest_best_chain(),
1503 &state.db,
1504 hash_or_height,
1505 )),
1506 ),
1507
1508 ReadRequest::AnyChainTransactionIdsForBlock(hash_or_height) => {
1509 Ok(ReadResponse::AnyChainTransactionIdsForBlock(
1510 read::transaction_hashes_for_any_block(
1511 state.latest_non_finalized_state().chain_iter(),
1512 &state.db,
1513 hash_or_height,
1514 ),
1515 ))
1516 }
1517
1518 #[cfg(feature = "indexer")]
1519 ReadRequest::SpendingTransactionId(spend) => Ok(ReadResponse::TransactionId(
1520 read::spending_transaction_hash(state.latest_best_chain(), &state.db, spend),
1521 )),
1522
1523 ReadRequest::UnspentBestChainUtxo(outpoint) => Ok(ReadResponse::UnspentBestChainUtxo(
1524 read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint),
1525 )),
1526
1527 ReadRequest::AnyChainUtxo(outpoint) => Ok(ReadResponse::AnyChainUtxo(read::any_utxo(
1529 state.latest_non_finalized_state(),
1530 &state.db,
1531 outpoint,
1532 ))),
1533
1534 ReadRequest::BlockLocator => Ok(ReadResponse::BlockLocator(
1536 read::block_locator(state.latest_best_chain(), &state.db).unwrap_or_default(),
1537 )),
1538
1539 ReadRequest::FindBlockHashes { known_blocks, stop } => {
1541 Ok(ReadResponse::BlockHashes(read::find_chain_hashes(
1542 state.latest_best_chain(),
1543 &state.db,
1544 known_blocks,
1545 stop,
1546 MAX_FIND_BLOCK_HASHES_RESULTS,
1547 )))
1548 }
1549
1550 ReadRequest::FindBlockHeaders { known_blocks, stop } => Ok(ReadResponse::BlockHeaders(
1552 read::find_chain_headers(
1553 state.latest_best_chain(),
1554 &state.db,
1555 known_blocks,
1556 stop,
1557 MAX_FIND_BLOCK_HEADERS_RESULTS,
1558 )
1559 .into_iter()
1560 .map(|header| CountedHeader { header })
1561 .collect(),
1562 )),
1563
1564 ReadRequest::FindForkPoint { known_blocks } => {
1565 let locator_len: u64 = known_blocks
1568 .len()
1569 .try_into()
1570 .expect("usize always fits in u64 on supported (<=64-bit) platforms");
1571 if locator_len > block::MAX_BLOCK_LOCATOR_LENGTH {
1572 return Err(BoxError::from(format!(
1573 "FindForkPoint locator length {locator_len} exceeds \
1574 MAX_BLOCK_LOCATOR_LENGTH ({})",
1575 block::MAX_BLOCK_LOCATOR_LENGTH,
1576 )));
1577 }
1578
1579 Ok(ReadResponse::ForkPoint(read::find_fork_point(
1580 state.latest_best_chain(),
1581 &state.db,
1582 known_blocks,
1583 )))
1584 }
1585
1586 ReadRequest::SaplingTree(hash_or_height) => Ok(ReadResponse::SaplingTree(
1587 read::sapling_tree(state.latest_best_chain(), &state.db, hash_or_height),
1588 )),
1589
1590 ReadRequest::OrchardTree(hash_or_height) => Ok(ReadResponse::OrchardTree(
1591 read::orchard_tree(state.latest_best_chain(), &state.db, hash_or_height),
1592 )),
1593
1594 ReadRequest::IronwoodTree(hash_or_height) => Ok(ReadResponse::IronwoodTree(
1595 read::ironwood_tree(state.latest_best_chain(), &state.db, hash_or_height),
1596 )),
1597
1598 ReadRequest::SaplingSubtrees { start_index, limit } => {
1599 let end_index = limit
1600 .and_then(|limit| start_index.0.checked_add(limit.0))
1601 .map(NoteCommitmentSubtreeIndex);
1602
1603 let best_chain = state.latest_best_chain();
1604 let sapling_subtrees = if let Some(end_index) = end_index {
1605 read::sapling_subtrees(best_chain, &state.db, start_index..end_index)
1606 } else {
1607 read::sapling_subtrees(best_chain, &state.db, start_index..)
1612 };
1613
1614 Ok(ReadResponse::SaplingSubtrees(sapling_subtrees))
1615 }
1616
1617 ReadRequest::OrchardSubtrees { start_index, limit } => {
1618 let end_index = limit
1619 .and_then(|limit| start_index.0.checked_add(limit.0))
1620 .map(NoteCommitmentSubtreeIndex);
1621
1622 let best_chain = state.latest_best_chain();
1623 let orchard_subtrees = if let Some(end_index) = end_index {
1624 read::orchard_subtrees(best_chain, &state.db, start_index..end_index)
1625 } else {
1626 read::orchard_subtrees(best_chain, &state.db, start_index..)
1631 };
1632
1633 Ok(ReadResponse::OrchardSubtrees(orchard_subtrees))
1634 }
1635
1636 ReadRequest::IronwoodSubtrees { start_index, limit } => {
1637 let end_index = limit
1638 .and_then(|limit| start_index.0.checked_add(limit.0))
1639 .map(NoteCommitmentSubtreeIndex);
1640
1641 let best_chain = state.latest_best_chain();
1642 let ironwood_subtrees = if let Some(end_index) = end_index {
1643 read::ironwood_subtrees(best_chain, &state.db, start_index..end_index)
1644 } else {
1645 read::ironwood_subtrees(best_chain, &state.db, start_index..)
1650 };
1651
1652 Ok(ReadResponse::IronwoodSubtrees(ironwood_subtrees))
1653 }
1654
1655 ReadRequest::AddressBalance(addresses) => {
1657 let (balance, received) =
1658 read::transparent_balance(state.latest_best_chain(), &state.db, addresses)?;
1659 Ok(ReadResponse::AddressBalance { balance, received })
1660 }
1661
1662 ReadRequest::TransactionIdsByAddresses {
1664 addresses,
1665 height_range,
1666 } => read::transparent_tx_ids(
1667 state.latest_best_chain(),
1668 &state.db,
1669 addresses,
1670 height_range,
1671 )
1672 .map(ReadResponse::AddressesTransactionIds),
1673
1674 ReadRequest::UtxosByAddresses(addresses) => read::address_utxos(
1676 &state.network,
1677 state.latest_best_chain(),
1678 &state.db,
1679 addresses,
1680 )
1681 .map(ReadResponse::AddressUtxos),
1682
1683 ReadRequest::CheckBestChainTipNullifiersAndAnchors(unmined_tx) => {
1684 let latest_non_finalized_best_chain = state.latest_best_chain();
1685
1686 check::nullifier::tx_no_duplicates_in_chain(
1687 &state.db,
1688 latest_non_finalized_best_chain.as_ref(),
1689 &unmined_tx.transaction,
1690 )?;
1691
1692 check::anchors::tx_anchors_refer_to_final_treestates(
1693 &state.db,
1694 latest_non_finalized_best_chain.as_ref(),
1695 &unmined_tx,
1696 )?;
1697
1698 Ok(ReadResponse::ValidBestChainTipNullifiersAndAnchors)
1699 }
1700
1701 ReadRequest::BestChainBlockHash(height) => Ok(ReadResponse::BlockHash(
1703 read::hash_by_height(state.latest_best_chain(), &state.db, height),
1704 )),
1705
1706 ReadRequest::ChainInfo => {
1708 read::difficulty::get_block_template_chain_info(
1720 &state.latest_non_finalized_state(),
1721 &state.db,
1722 &state.network,
1723 )
1724 .map(ReadResponse::ChainInfo)
1725 }
1726
1727 ReadRequest::SolutionRate { num_blocks, height } => {
1729 let latest_non_finalized_state = state.latest_non_finalized_state();
1730 let (tip_height, tip_hash) =
1738 match read::tip(latest_non_finalized_state.best_chain(), &state.db) {
1739 Some(tip_hash) => tip_hash,
1740 None => return Ok(ReadResponse::SolutionRate(None)),
1741 };
1742
1743 let start_hash = match height {
1744 Some(height) if height < tip_height => read::hash_by_height(
1745 latest_non_finalized_state.best_chain(),
1746 &state.db,
1747 height,
1748 ),
1749 _ => Some(tip_hash),
1751 };
1752
1753 let solution_rate = start_hash.and_then(|start_hash| {
1754 read::difficulty::solution_rate(
1755 &latest_non_finalized_state,
1756 &state.db,
1757 num_blocks,
1758 start_hash,
1759 )
1760 });
1761
1762 Ok(ReadResponse::SolutionRate(solution_rate))
1763 }
1764
1765 ReadRequest::CheckBlockProposalValidity(semantically_verified) => {
1766 tracing::debug!(
1767 "attempting to validate and commit block proposal \
1768 onto a cloned non-finalized state"
1769 );
1770 let mut latest_non_finalized_state = state.latest_non_finalized_state();
1771
1772 let Some((_best_tip_height, best_tip_hash)) =
1774 read::best_tip(&latest_non_finalized_state, &state.db)
1775 else {
1776 return Err(
1777 "state is empty: wait for Zebra to sync before submitting a proposal"
1778 .into(),
1779 );
1780 };
1781
1782 if semantically_verified.block.header.previous_block_hash != best_tip_hash {
1783 return Err("proposal is not based on the current best chain tip: \
1784 previous block hash must be the best chain tip"
1785 .into());
1786 }
1787
1788 latest_non_finalized_state.disable_metrics();
1794
1795 write::validate_and_commit_non_finalized(
1796 &state.db,
1797 &mut latest_non_finalized_state,
1798 semantically_verified,
1799 )?;
1800
1801 Ok(ReadResponse::ValidBlockProposal)
1802 }
1803
1804 ReadRequest::TipBlockSize => {
1805 Ok(ReadResponse::TipBlockSize(
1807 state
1808 .best_tip()
1809 .and_then(|(tip_height, _)| {
1810 read::block_info(
1811 state.latest_best_chain(),
1812 &state.db,
1813 tip_height.into(),
1814 )
1815 })
1816 .map(|info| info.size().try_into().expect("u32 should fit in usize"))
1817 .or_else(|| {
1818 find::tip_block(state.latest_best_chain(), &state.db)
1819 .map(|b| b.zcash_serialized_size())
1820 }),
1821 ))
1822 }
1823
1824 ReadRequest::NonFinalizedBlocksListener { .. } => {
1825 unreachable!("should return early");
1826 }
1827
1828 ReadRequest::IsTransparentOutputSpent(outpoint) => {
1830 let is_spent = read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint);
1831 Ok(ReadResponse::IsTransparentOutputSpent(is_spent.is_none()))
1832 }
1833 };
1834
1835 timed_span.spawn_blocking(request_handler)
1836 }
1837}
1838
1839pub async fn init(
1855 config: Config,
1856 network: &Network,
1857 max_checkpoint_height: block::Height,
1858 checkpoint_verify_concurrency_limit: usize,
1859) -> (
1860 BoxService<Request, Response, BoxError>,
1861 ReadStateService,
1862 LatestChainTip,
1863 ChainTipChange,
1864) {
1865 let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
1866 StateService::new(
1867 config,
1868 network,
1869 max_checkpoint_height,
1870 checkpoint_verify_concurrency_limit,
1871 )
1872 .await;
1873
1874 (
1875 BoxService::new(state_service),
1876 read_only_state_service,
1877 latest_chain_tip,
1878 chain_tip_change,
1879 )
1880}
1881
1882pub fn init_read_only(
1889 config: Config,
1890 network: &Network,
1891) -> Result<
1892 (
1893 ReadStateService,
1894 ZebraDb,
1895 tokio::sync::watch::Sender<NonFinalizedState>,
1896 ),
1897 StateInitError,
1898> {
1899 let finalized_state = FinalizedState::new_with_debug(
1900 &config,
1901 network,
1902 true,
1903 #[cfg(feature = "elasticsearch")]
1904 false,
1905 true,
1906 )?;
1907 let (non_finalized_state_sender, non_finalized_state_receiver) =
1908 tokio::sync::watch::channel(NonFinalizedState::new(network));
1909
1910 Ok((
1911 ReadStateService::new(
1912 &finalized_state,
1913 None,
1914 WatchReceiver::new(non_finalized_state_receiver),
1915 ),
1916 finalized_state.db.clone(),
1917 non_finalized_state_sender,
1918 ))
1919}
1920
1921pub fn spawn_init_read_only(
1928 config: Config,
1929 network: &Network,
1930) -> tokio::task::JoinHandle<
1931 Result<
1932 (
1933 ReadStateService,
1934 ZebraDb,
1935 tokio::sync::watch::Sender<NonFinalizedState>,
1936 ),
1937 StateInitError,
1938 >,
1939> {
1940 let network = network.clone();
1941 tokio::task::spawn_blocking(move || init_read_only(config, &network))
1942}
1943
1944#[cfg(any(test, feature = "proptest-impl"))]
1948pub async fn init_test(
1949 network: &Network,
1950) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
1951 let (state_service, _, _, _) =
1954 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1955
1956 Buffer::new(BoxService::new(state_service), 1)
1957}
1958
1959#[cfg(any(test, feature = "proptest-impl"))]
1964pub async fn init_test_services(
1965 network: &Network,
1966) -> (
1967 Buffer<BoxService<Request, Response, BoxError>, Request>,
1968 ReadStateService,
1969 LatestChainTip,
1970 ChainTipChange,
1971) {
1972 let (state_service, read_state_service, latest_chain_tip, chain_tip_change) =
1975 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1976
1977 let state_service = Buffer::new(BoxService::new(state_service), 1);
1978
1979 (
1980 state_service,
1981 read_state_service,
1982 latest_chain_tip,
1983 chain_tip_change,
1984 )
1985}