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 AwaitUtxoError, BoxError, CheckpointVerifiedBlock, CommitSemanticallyVerifiedError, Config,
60 KnownBlock, ReadRequest, ReadResponse, Request, Response, SemanticallyVerifiedBlock,
61 StateInitError,
62};
63
64pub mod block_iter;
65pub mod chain_tip;
66pub mod watch_receiver;
67
68pub mod check;
69
70pub(crate) mod finalized_state;
71pub(crate) mod non_finalized_state;
72mod pending_utxos;
73mod queued_blocks;
74pub(crate) mod read;
75mod traits;
76mod write;
77
78#[cfg(any(test, feature = "proptest-impl"))]
79pub mod arbitrary;
80
81#[cfg(test)]
82mod tests;
83
84pub use finalized_state::{OutputLocation, TransactionIndex, TransactionLocation};
85use write::NonFinalizedWriteMessage;
86
87use self::queued_blocks::{QueuedCheckpointVerified, QueuedSemanticallyVerified, SentHashes};
88
89pub use self::traits::{ReadState, State};
90
91#[derive(Debug)]
111pub(crate) struct StateService {
112 network: Network,
116
117 full_verifier_utxo_lookahead: block::Height,
123
124 non_finalized_state_queued_blocks: QueuedBlocks,
129
130 finalized_state_queued_blocks: HashMap<block::Hash, QueuedCheckpointVerified>,
135
136 block_write_sender: write::BlockWriteSender,
138
139 finalized_block_write_last_sent_hash: block::Hash,
149
150 non_finalized_block_write_sent_hashes: SentHashes,
153
154 invalid_block_write_reset_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
160
161 non_finalized_rejected_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
169
170 pending_utxos: PendingUtxos,
174
175 last_prune: Instant,
177
178 read_service: ReadStateService,
184
185 max_finalized_queue_height: f64,
192}
193
194#[derive(Clone, Debug)]
207pub struct ReadStateService {
208 network: Network,
212
213 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
220
221 db: ZebraDb,
229
230 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
235}
236
237impl Drop for StateService {
238 fn drop(&mut self) {
239 self.invalid_block_write_reset_receiver.close();
246 self.non_finalized_rejected_receiver.close();
247
248 std::mem::drop(self.block_write_sender.finalized.take());
249 std::mem::drop(self.block_write_sender.non_finalized.take());
250
251 self.clear_finalized_block_queue(CommitBlockError::WriteTaskExited);
252 self.clear_non_finalized_block_queue(CommitBlockError::WriteTaskExited);
253
254 info!("dropping the state: logging database metrics");
256 self.log_db_metrics();
257
258 }
261}
262
263impl Drop for ReadStateService {
264 fn drop(&mut self) {
265 if let Some(block_write_task) = self.block_write_task.take() {
270 if let Some(block_write_task_handle) = Arc::into_inner(block_write_task) {
271 self.db.shutdown(true);
275
276 #[cfg(not(test))]
282 info!("waiting for the block write task to finish");
283 #[cfg(test)]
284 debug!("waiting for the block write task to finish");
285
286 if let Err(thread_panic) = block_write_task_handle.join() {
288 std::panic::resume_unwind(thread_panic);
289 } else {
290 debug!("shutting down the state because the block write task has finished");
291 }
292 }
293 } else {
294 self.db.shutdown(false);
298 }
299 }
300}
301
302impl StateService {
303 const PRUNE_INTERVAL: Duration = Duration::from_secs(30);
304
305 pub async fn new(
313 config: Config,
314 network: &Network,
315 max_checkpoint_height: block::Height,
316 checkpoint_verify_concurrency_limit: usize,
317 ) -> (Self, ReadStateService, LatestChainTip, ChainTipChange) {
318 let (finalized_state, finalized_tip, timer) = {
319 let config = config.clone();
320 let network = network.clone();
321 tokio::task::spawn_blocking(move || {
322 let timer = CodeTimer::start();
323 let finalized_state = FinalizedState::new(
324 &config,
325 &network,
326 #[cfg(feature = "elasticsearch")]
327 true,
328 )
329 .expect(
330 "opening the read-write finalized state database failed; check that the \
331 state cache directory is writable and not locked by another Zebra instance, \
332 and that there is free disk space",
333 );
334 timer.finish_desc("opening finalized state database");
335
336 let timer = CodeTimer::start();
337 let finalized_tip = finalized_state.db.tip_block();
338
339 (finalized_state, finalized_tip, timer)
340 })
341 .await
342 .expect("failed to join blocking task")
343 };
344
345 let finalized_tip_height = finalized_tip
357 .as_ref()
358 .map(|tip| tip.coinbase_height().expect("valid block must have height"));
359 let is_finalized_tip_past_max_checkpoint =
360 finalized_tip_height.is_some_and(|tip_height| tip_height >= max_checkpoint_height);
361 let backup_dir_path = config.non_finalized_state_backup_dir(network);
362
363 if backup_dir_path.is_some() && !is_finalized_tip_past_max_checkpoint {
364 tracing::info!(
365 ?finalized_tip_height,
366 ?max_checkpoint_height,
367 "not restoring the non-finalized state backup, because the finalized tip is absent \
368 or below the max checkpoint height: Zebra will re-download and re-verify the \
369 blocks above its finalized tip"
370 );
371 }
372 let skip_backup_task = config.debug_skip_non_finalized_state_backup_task;
373 let (non_finalized_state, non_finalized_state_sender, non_finalized_state_receiver) =
374 NonFinalizedState::new(network)
375 .with_backup(
376 backup_dir_path.clone(),
377 &finalized_state.db,
378 is_finalized_tip_past_max_checkpoint,
379 config.debug_skip_non_finalized_state_backup_task,
380 )
381 .await;
382
383 let non_finalized_block_write_sent_hashes = SentHashes::new(&non_finalized_state);
384 let initial_tip = non_finalized_state
385 .best_tip_block()
386 .map(|cv_block| cv_block.block.clone())
387 .or(finalized_tip)
388 .map(CheckpointVerifiedBlock::from)
389 .map(ChainTipBlock::from);
390
391 tracing::info!(chain_tip = ?initial_tip.as_ref().map(|tip| (tip.hash, tip.height)), "loaded Zebra state cache");
392
393 let (chain_tip_sender, latest_chain_tip, chain_tip_change) =
394 ChainTipSender::new(initial_tip, network);
395
396 let finalized_state_for_writing = finalized_state.clone();
397 let should_use_finalized_block_write_sender = non_finalized_state.is_chain_set_empty();
398 let sync_backup_dir_path = backup_dir_path.filter(|_| skip_backup_task);
399 let (
400 block_write_sender,
401 invalid_block_write_reset_receiver,
402 non_finalized_rejected_receiver,
403 block_write_task,
404 ) = write::BlockWriteSender::spawn(
405 finalized_state_for_writing,
406 non_finalized_state,
407 chain_tip_sender,
408 non_finalized_state_sender,
409 should_use_finalized_block_write_sender,
410 sync_backup_dir_path,
411 );
412
413 let read_service = ReadStateService::new(
414 &finalized_state,
415 block_write_task,
416 non_finalized_state_receiver,
417 );
418
419 let full_verifier_utxo_lookahead = max_checkpoint_height
420 - HeightDiff::try_from(checkpoint_verify_concurrency_limit)
421 .expect("fits in HeightDiff");
422 let full_verifier_utxo_lookahead =
423 full_verifier_utxo_lookahead.unwrap_or(block::Height::MIN);
424 let non_finalized_state_queued_blocks = QueuedBlocks::default();
425 let pending_utxos = PendingUtxos::default();
426
427 let finalized_block_write_last_sent_hash =
428 tokio::task::spawn_blocking(move || finalized_state.db.finalized_tip_hash())
429 .await
430 .expect("failed to join blocking task");
431
432 let state = Self {
433 network: network.clone(),
434 full_verifier_utxo_lookahead,
435 non_finalized_state_queued_blocks,
436 finalized_state_queued_blocks: HashMap::new(),
437 block_write_sender,
438 finalized_block_write_last_sent_hash,
439 non_finalized_block_write_sent_hashes,
440 invalid_block_write_reset_receiver,
441 non_finalized_rejected_receiver,
442 pending_utxos,
443 last_prune: Instant::now(),
444 read_service: read_service.clone(),
445 max_finalized_queue_height: f64::NAN,
446 };
447 timer.finish_desc("initializing state service");
448
449 tracing::info!("starting legacy chain check");
450 let timer = CodeTimer::start();
451
452 if let (Some(tip), Some(nu5_activation_height)) = (
453 {
454 let read_state = state.read_service.clone();
455 tokio::task::spawn_blocking(move || read_state.best_tip())
456 .await
457 .expect("task should not panic")
458 },
459 NetworkUpgrade::Nu5.activation_height(network),
460 ) {
461 if let Err(error) = check::legacy_chain(
462 nu5_activation_height,
463 any_ancestor_blocks(
464 &state.read_service.latest_non_finalized_state(),
465 &state.read_service.db,
466 tip.1,
467 ),
468 &state.network,
469 MAX_LEGACY_CHAIN_BLOCKS,
470 ) {
471 let legacy_db_path = state.read_service.db.path().to_path_buf();
472 panic!(
473 "Cached state contains a legacy chain.\n\
474 An outdated Zebra version did not know about a recent network upgrade,\n\
475 so it followed a legacy chain using outdated consensus branch rules.\n\
476 Hint: Delete your database, and restart Zebra to do a full sync.\n\
477 Database path: {legacy_db_path:?}\n\
478 Error: {error:?}",
479 );
480 }
481 }
482
483 tracing::info!("cached state consensus branch is valid: no legacy chain found");
484 timer.finish_desc("legacy chain check");
485
486 let db_for_metrics = read_service.db.clone();
488 tokio::spawn(async move {
489 let mut interval = tokio::time::interval(Duration::from_secs(30));
490 loop {
491 interval.tick().await;
492 db_for_metrics.export_metrics();
493 }
494 });
495
496 (state, read_service, latest_chain_tip, chain_tip_change)
497 }
498
499 pub fn log_db_metrics(&self) {
501 self.read_service.db.print_db_metrics();
502 }
503
504 fn queue_and_commit_to_finalized_state(
508 &mut self,
509 checkpoint_verified: CheckpointVerifiedBlock,
510 ) -> oneshot::Receiver<Result<block::Hash, CommitCheckpointVerifiedError>> {
511 let queued_prev_hash = checkpoint_verified.block.header.previous_block_hash;
517 let queued_height = checkpoint_verified.height;
518
519 if self.is_close_to_final_checkpoint(queued_height) {
522 self.non_finalized_block_write_sent_hashes
523 .add_finalized(&checkpoint_verified)
524 }
525
526 let (rsp_tx, rsp_rx) = oneshot::channel();
527 let queued = (checkpoint_verified, rsp_tx);
528
529 if self.block_write_sender.finalized.is_some() {
530 if let Some(duplicate_queued) = self
532 .finalized_state_queued_blocks
533 .insert(queued_prev_hash, queued)
534 {
535 Self::send_checkpoint_verified_block_error(
536 duplicate_queued,
537 CommitBlockError::new_duplicate(
538 Some(queued_prev_hash.into()),
539 KnownBlock::Queue,
540 ),
541 );
542 }
543
544 self.drain_finalized_queue_and_commit();
545 } else {
546 Self::send_checkpoint_verified_block_error(
552 queued,
553 CommitBlockError::new_duplicate(None, KnownBlock::Finalized),
554 );
555
556 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
557 None,
558 KnownBlock::Finalized,
559 ));
560 }
561
562 if self.finalized_state_queued_blocks.is_empty() {
563 self.max_finalized_queue_height = f64::NAN;
564 } else if self.max_finalized_queue_height.is_nan()
565 || self.max_finalized_queue_height < queued_height.0 as f64
566 {
567 self.max_finalized_queue_height = queued_height.0 as f64;
573 }
574
575 metrics::gauge!("state.checkpoint.queued.max.height").set(self.max_finalized_queue_height);
576 metrics::gauge!("state.checkpoint.queued.block.count")
577 .set(self.finalized_state_queued_blocks.len() as f64);
578
579 rsp_rx
580 }
581
582 pub fn drain_finalized_queue_and_commit(&mut self) {
590 use tokio::sync::mpsc::error::{SendError, TryRecvError};
591
592 match self.invalid_block_write_reset_receiver.try_recv() {
599 Ok(reset_tip_hash) => self.finalized_block_write_last_sent_hash = reset_tip_hash,
600 Err(TryRecvError::Disconnected) => {
601 info!("Block commit task closed the block reset channel. Is Zebra shutting down?");
602 return;
603 }
604 Err(TryRecvError::Empty) => {}
606 }
607
608 while let Some(queued_block) = self
609 .finalized_state_queued_blocks
610 .remove(&self.finalized_block_write_last_sent_hash)
611 {
612 let last_sent_finalized_block_height = queued_block.0.height;
613
614 self.finalized_block_write_last_sent_hash = queued_block.0.hash;
615
616 if let Some(finalized_block_write_sender) = &self.block_write_sender.finalized {
619 let send_result = finalized_block_write_sender.send(queued_block);
620
621 if let Err(SendError(queued)) = send_result {
623 Self::send_checkpoint_verified_block_error(
625 queued,
626 CommitBlockError::WriteTaskExited,
627 );
628
629 self.clear_finalized_block_queue(CommitBlockError::WriteTaskExited);
630 } else {
631 metrics::gauge!("state.checkpoint.sent.block.height")
632 .set(last_sent_finalized_block_height.0 as f64);
633 };
634 }
635 }
636 }
637
638 fn drain_non_finalized_rejected_hashes(&mut self) {
651 use tokio::sync::mpsc::error::TryRecvError;
652
653 loop {
654 match self.non_finalized_rejected_receiver.try_recv() {
655 Ok(hash) => {
656 self.non_finalized_block_write_sent_hashes.remove(&hash);
657 }
658 Err(TryRecvError::Empty) => break,
659 Err(TryRecvError::Disconnected) => {
660 info!(
661 "Block commit task closed the non-finalized rejected hash channel. \
662 Is Zebra shutting down?"
663 );
664 break;
665 }
666 }
667 }
668 }
669
670 fn clear_finalized_block_queue(
672 &mut self,
673 error: impl Into<CommitCheckpointVerifiedError> + Clone,
674 ) {
675 for (_hash, queued) in self.finalized_state_queued_blocks.drain() {
676 Self::send_checkpoint_verified_block_error(queued, error.clone());
677 }
678 }
679
680 fn send_checkpoint_verified_block_error(
682 queued: QueuedCheckpointVerified,
683 error: impl Into<CommitCheckpointVerifiedError>,
684 ) {
685 let (finalized, rsp_tx) = queued;
686
687 let _ = rsp_tx.send(Err(error.into()));
690 std::mem::drop(finalized);
691 }
692
693 fn clear_non_finalized_block_queue(
695 &mut self,
696 error: impl Into<CommitSemanticallyVerifiedError> + Clone,
697 ) {
698 for (_hash, queued) in self.non_finalized_state_queued_blocks.drain() {
699 Self::send_semantically_verified_block_error(queued, error.clone());
700 }
701 }
702
703 fn send_semantically_verified_block_error(
705 queued: QueuedSemanticallyVerified,
706 error: impl Into<CommitSemanticallyVerifiedError>,
707 ) {
708 let (finalized, rsp_tx) = queued;
709
710 let _ = rsp_tx.send(Err(error.into()));
713 std::mem::drop(finalized);
714 }
715
716 #[instrument(level = "debug", skip(self, semantically_verified))]
724 fn queue_and_commit_to_non_finalized_state(
725 &mut self,
726 semantically_verified: SemanticallyVerifiedBlock,
727 ) -> oneshot::Receiver<Result<block::Hash, CommitSemanticallyVerifiedError>> {
728 tracing::debug!(block = %semantically_verified.block, "queueing block for contextual verification");
729 let parent_hash = semantically_verified.block.header.previous_block_hash;
730
731 self.drain_non_finalized_rejected_hashes();
736
737 if self
738 .non_finalized_block_write_sent_hashes
739 .contains(&semantically_verified.hash)
740 {
741 let (rsp_tx, rsp_rx) = oneshot::channel();
742 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
743 Some(semantically_verified.hash.into()),
744 KnownBlock::WriteChannel,
745 )
746 .into()));
747 return rsp_rx;
748 }
749
750 if self
751 .read_service
752 .db
753 .contains_height(semantically_verified.height)
754 {
755 let (rsp_tx, rsp_rx) = oneshot::channel();
756 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
757 Some(semantically_verified.height.into()),
758 KnownBlock::Finalized,
759 )
760 .into()));
761 return rsp_rx;
762 }
763
764 let rsp_rx = if let Some((_, old_rsp_tx)) = self
768 .non_finalized_state_queued_blocks
769 .get_mut(&semantically_verified.hash)
770 {
771 tracing::debug!("replacing older queued request with new request");
772 let (mut rsp_tx, rsp_rx) = oneshot::channel();
773 std::mem::swap(old_rsp_tx, &mut rsp_tx);
774 let _ = rsp_tx.send(Err(CommitBlockError::new_duplicate(
775 Some(semantically_verified.hash.into()),
776 KnownBlock::Queue,
777 )
778 .into()));
779 rsp_rx
780 } else {
781 let (rsp_tx, rsp_rx) = oneshot::channel();
782 self.non_finalized_state_queued_blocks
783 .queue((semantically_verified, rsp_tx));
784 rsp_rx
785 };
786
787 if self.block_write_sender.finalized.is_some()
796 && self
797 .non_finalized_state_queued_blocks
798 .has_queued_children(self.finalized_block_write_last_sent_hash)
799 && self.read_service.db.finalized_tip_hash()
800 == self.finalized_block_write_last_sent_hash
801 {
802 std::mem::drop(self.block_write_sender.finalized.take());
805 self.non_finalized_block_write_sent_hashes = SentHashes::default();
807 self.non_finalized_block_write_sent_hashes
809 .can_fork_chain_at_hashes = true;
810 self.send_ready_non_finalized_queued(self.finalized_block_write_last_sent_hash);
812 self.clear_finalized_block_queue(CommitBlockError::new_duplicate(
814 None,
815 KnownBlock::Finalized,
816 ));
817 } else if !self.can_fork_chain_at(&parent_hash) {
818 tracing::trace!("unready to verify, returning early");
819 } else if self.block_write_sender.finalized.is_none() {
820 self.send_ready_non_finalized_queued(parent_hash);
822
823 let finalized_tip_height = self.read_service.db.finalized_tip_height().expect(
824 "Finalized state must have at least one block before committing non-finalized state",
825 );
826
827 self.non_finalized_state_queued_blocks
828 .prune_by_height(finalized_tip_height);
829
830 self.non_finalized_block_write_sent_hashes
831 .prune_by_height(finalized_tip_height);
832 }
833
834 rsp_rx
835 }
836
837 fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
839 self.non_finalized_block_write_sent_hashes
840 .can_fork_chain_at(hash)
841 || &self.read_service.db.finalized_tip_hash() == hash
842 }
843
844 fn is_close_to_final_checkpoint(&self, queued_height: block::Height) -> bool {
852 queued_height >= self.full_verifier_utxo_lookahead
853 }
854
855 #[tracing::instrument(level = "debug", skip(self, new_parent))]
858 fn send_ready_non_finalized_queued(&mut self, new_parent: block::Hash) {
859 use tokio::sync::mpsc::error::SendError;
860 if let Some(non_finalized_block_write_sender) = &self.block_write_sender.non_finalized {
861 let mut new_parents: Vec<block::Hash> = vec![new_parent];
862
863 while let Some(parent_hash) = new_parents.pop() {
864 let queued_children = self
865 .non_finalized_state_queued_blocks
866 .dequeue_children(parent_hash);
867
868 for queued_child in queued_children {
869 let (SemanticallyVerifiedBlock { hash, .. }, _) = queued_child;
870
871 self.non_finalized_block_write_sent_hashes
872 .add(&queued_child.0);
873 let send_result = non_finalized_block_write_sender.send(queued_child.into());
874
875 if let Err(SendError(NonFinalizedWriteMessage::Commit(queued))) = send_result {
876 Self::send_semantically_verified_block_error(
878 queued,
879 CommitBlockError::WriteTaskExited,
880 );
881
882 self.clear_non_finalized_block_queue(CommitBlockError::WriteTaskExited);
883
884 return;
885 };
886
887 new_parents.push(hash);
888 }
889 }
890
891 self.non_finalized_block_write_sent_hashes.finish_batch();
892 };
893 }
894
895 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
897 self.read_service.best_tip()
898 }
899
900 fn send_invalidate_block(
901 &self,
902 hash: block::Hash,
903 ) -> oneshot::Receiver<Result<block::Hash, InvalidateError>> {
904 let (rsp_tx, rsp_rx) = oneshot::channel();
905
906 let Some(sender) = &self.block_write_sender.non_finalized else {
907 let _ = rsp_tx.send(Err(InvalidateError::ProcessingCheckpointedBlocks));
908 return rsp_rx;
909 };
910
911 if let Err(tokio::sync::mpsc::error::SendError(error)) =
912 sender.send(NonFinalizedWriteMessage::Invalidate { hash, rsp_tx })
913 {
914 let NonFinalizedWriteMessage::Invalidate { rsp_tx, .. } = error else {
915 unreachable!("should return the same Invalidate message could not be sent");
916 };
917
918 let _ = rsp_tx.send(Err(InvalidateError::SendInvalidateRequestFailed));
919 }
920
921 rsp_rx
922 }
923
924 fn send_reconsider_block(
925 &self,
926 hash: block::Hash,
927 ) -> oneshot::Receiver<Result<Vec<block::Hash>, ReconsiderError>> {
928 let (rsp_tx, rsp_rx) = oneshot::channel();
929
930 let Some(sender) = &self.block_write_sender.non_finalized else {
931 let _ = rsp_tx.send(Err(ReconsiderError::CheckpointCommitInProgress));
932 return rsp_rx;
933 };
934
935 if let Err(tokio::sync::mpsc::error::SendError(error)) =
936 sender.send(NonFinalizedWriteMessage::Reconsider { hash, rsp_tx })
937 {
938 let NonFinalizedWriteMessage::Reconsider { rsp_tx, .. } = error else {
939 unreachable!("should return the same Reconsider message could not be sent");
940 };
941
942 let _ = rsp_tx.send(Err(ReconsiderError::ReconsiderSendFailed));
943 }
944
945 rsp_rx
946 }
947
948 fn assert_block_can_be_validated(&self, block: &SemanticallyVerifiedBlock) {
950 assert!(
952 block.height > self.network.mandatory_checkpoint_height(),
953 "invalid semantically verified block height: the canopy checkpoint is mandatory, pre-canopy \
954 blocks, and the canopy activation block, must be committed to the state as finalized \
955 blocks"
956 );
957 }
958
959 fn known_sent_hash(&self, hash: &block::Hash) -> Option<KnownBlock> {
960 self.non_finalized_block_write_sent_hashes
961 .contains(hash)
962 .then_some(KnownBlock::WriteChannel)
963 }
964}
965
966impl ReadStateService {
967 pub(crate) fn new(
973 finalized_state: &FinalizedState,
974 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
975 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
976 ) -> Self {
977 let read_service = Self {
978 network: finalized_state.network(),
979 db: finalized_state.db.clone(),
980 non_finalized_state_receiver,
981 block_write_task,
982 };
983
984 tracing::debug!("created new read-only state service");
985
986 read_service
987 }
988
989 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
991 read::best_tip(&self.latest_non_finalized_state(), &self.db)
992 }
993
994 fn latest_non_finalized_state(&self) -> NonFinalizedState {
996 self.non_finalized_state_receiver.cloned_watch_data()
997 }
998
999 fn latest_best_chain(&self) -> Option<Arc<Chain>> {
1001 self.non_finalized_state_receiver
1002 .borrow_mapped(|non_finalized_state| non_finalized_state.best_chain().cloned())
1003 }
1004
1005 #[cfg(any(test, feature = "proptest-impl"))]
1008 pub fn db(&self) -> &ZebraDb {
1009 &self.db
1010 }
1011
1012 pub fn log_db_metrics(&self) {
1014 self.db.print_db_metrics();
1015 }
1016}
1017
1018impl Service<Request> for StateService {
1019 type Response = Response;
1020 type Error = BoxError;
1021 type Future =
1022 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1023
1024 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1025 let poll = self.read_service.poll_ready(cx);
1027
1028 let now = Instant::now();
1030
1031 if self.last_prune + Self::PRUNE_INTERVAL < now {
1032 let tip = self.best_tip();
1033 let old_len = self.pending_utxos.len();
1034
1035 self.pending_utxos.prune();
1036 self.last_prune = now;
1037
1038 let new_len = self.pending_utxos.len();
1039 let prune_count = old_len
1040 .checked_sub(new_len)
1041 .expect("prune does not add any utxo requests");
1042 if prune_count > 0 {
1043 tracing::debug!(
1044 ?old_len,
1045 ?new_len,
1046 ?prune_count,
1047 ?tip,
1048 "pruned utxo requests"
1049 );
1050 } else {
1051 tracing::debug!(len = ?old_len, ?tip, "no utxo requests needed pruning");
1052 }
1053 }
1054
1055 poll
1056 }
1057
1058 #[instrument(name = "state", skip(self, req))]
1059 fn call(&mut self, req: Request) -> Self::Future {
1060 req.count_metric();
1061 let span = Span::current();
1062
1063 match req {
1064 Request::CommitSemanticallyVerifiedBlock(semantically_verified) => {
1069 let timer = CodeTimer::start();
1070 self.assert_block_can_be_validated(&semantically_verified);
1071
1072 self.pending_utxos
1073 .check_against_ordered(&semantically_verified.new_outputs);
1074
1075 let rsp_rx = tokio::task::block_in_place(move || {
1087 span.in_scope(|| {
1088 self.queue_and_commit_to_non_finalized_state(semantically_verified)
1089 })
1090 });
1091
1092 timer.finish_desc("CommitSemanticallyVerifiedBlock");
1098
1099 let span = Span::current();
1103 async move {
1104 rsp_rx
1105 .await
1106 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1107 .and_then(|result| result)
1108 .map_err(BoxError::from)
1109 .map(Response::Committed)
1110 }
1111 .instrument(span)
1112 .boxed()
1113 }
1114
1115 Request::CommitCheckpointVerifiedBlock(finalized) => {
1120 let timer = CodeTimer::start();
1121 self.pending_utxos
1135 .check_against_ordered(&finalized.new_outputs);
1136
1137 let rsp_rx = self.queue_and_commit_to_finalized_state(finalized);
1142
1143 timer.finish_desc("CommitCheckpointVerifiedBlock");
1149
1150 async move {
1154 rsp_rx
1155 .await
1156 .map_err(|_recv_error| CommitBlockError::WriteTaskExited.into())
1157 .and_then(|result| result)
1158 .map_err(BoxError::from)
1159 .map(Response::Committed)
1160 }
1161 .instrument(span)
1162 .boxed()
1163 }
1164
1165 Request::AwaitUtxo(outpoint) => {
1170 let timer = CodeTimer::start();
1171 let response_fut = self.pending_utxos.queue(outpoint);
1173 let response_fut = response_fut
1177 .map(|result| result.map_err(BoxError::from))
1178 .instrument(span)
1179 .boxed();
1180
1181 if let Some(utxo) = self.non_finalized_state_queued_blocks.utxo(&outpoint) {
1184 self.pending_utxos.respond(&outpoint, utxo);
1185
1186 timer.finish_desc("AwaitUtxo/queued-non-finalized");
1188
1189 return response_fut;
1190 }
1191
1192 self.drain_non_finalized_rejected_hashes();
1194
1195 if let Some(utxo) = self.non_finalized_block_write_sent_hashes.utxo(&outpoint) {
1196 self.pending_utxos.respond(&outpoint, utxo);
1197
1198 timer.finish_desc("AwaitUtxo/sent-non-finalized");
1200
1201 return response_fut;
1202 }
1203
1204 let read_service = self.read_service.clone();
1213
1214 async move {
1216 let req = ReadRequest::AnyChainUtxo(outpoint);
1217
1218 let rsp = read_service
1219 .oneshot(req)
1220 .await
1221 .map_err(AwaitUtxoError::ReadStateFailed)?;
1222
1223 if let ReadResponse::AnyChainUtxo(Some(utxo)) = rsp {
1236 timer.finish_desc("AwaitUtxo/any-chain");
1238
1239 return Ok(Response::Utxo(utxo));
1240 }
1241
1242 timer.finish_desc("AwaitUtxo/waiting");
1244
1245 response_fut.await
1246 }
1247 .boxed()
1248 }
1249
1250 Request::KnownBlock(hash) => {
1253 let timer = CodeTimer::start();
1254
1255 self.drain_non_finalized_rejected_hashes();
1256
1257 let known_sent_hash = self.known_sent_hash(&hash);
1258 let known_queued = self
1259 .non_finalized_state_queued_blocks
1260 .has(hash)
1261 .then_some(KnownBlock::Queue);
1262 let read_service = self.read_service.clone();
1263
1264 async move {
1265 if let Some(loc) = known_sent_hash.or(known_queued) {
1266 return Ok(Response::KnownBlock(Some(loc)));
1267 };
1268
1269 let response = read::non_finalized_state_contains_block_hash(
1270 &read_service.latest_non_finalized_state(),
1271 hash,
1272 )
1273 .or_else(|| read::finalized_state_contains_block_hash(&read_service.db, hash));
1275
1276 timer.finish_desc("Request::KnownBlock");
1277
1278 Ok(Response::KnownBlock(response))
1279 }
1280 .boxed()
1281 }
1282
1283 Request::InvalidateBlock(block_hash) => {
1285 let rsp_rx = tokio::task::block_in_place(move || {
1286 span.in_scope(|| self.send_invalidate_block(block_hash))
1287 });
1288
1289 let span = Span::current();
1293 async move {
1294 rsp_rx
1295 .await
1296 .map_err(|_recv_error| InvalidateError::InvalidateRequestDropped)
1297 .and_then(|result| result)
1298 .map_err(BoxError::from)
1299 .map(Response::Invalidated)
1300 }
1301 .instrument(span)
1302 .boxed()
1303 }
1304
1305 Request::ReconsiderBlock(block_hash) => {
1307 let rsp_rx = tokio::task::block_in_place(move || {
1308 span.in_scope(|| self.send_reconsider_block(block_hash))
1309 });
1310
1311 let span = Span::current();
1315 async move {
1316 rsp_rx
1317 .await
1318 .map_err(|_recv_error| ReconsiderError::ReconsiderResponseDropped)
1319 .and_then(|result| result)
1320 .map_err(BoxError::from)
1321 .map(Response::Reconsidered)
1322 }
1323 .instrument(span)
1324 .boxed()
1325 }
1326
1327 Request::Tip
1329 | Request::Depth(_)
1330 | Request::BestChainNextMedianTimePast
1331 | Request::BestChainBlockHash(_)
1332 | Request::BlockLocator
1333 | Request::Transaction(_)
1334 | Request::AnyChainTransaction(_)
1335 | Request::UnspentBestChainUtxo(_)
1336 | Request::Block(_)
1337 | Request::AnyChainBlock(_)
1338 | Request::BlockAndSize(_)
1339 | Request::BlockHeader(_)
1340 | Request::FindBlockHashes { .. }
1341 | Request::FindBlockHeaders { .. }
1342 | Request::CheckBestChainTipNullifiersAndAnchors(_)
1343 | Request::CheckBlockProposalValidity(_) => {
1344 let read_service = self.read_service.clone();
1346
1347 async move {
1348 let req = req
1349 .try_into()
1350 .expect("ReadRequest conversion should not fail");
1351
1352 let rsp = read_service.oneshot(req).await?;
1353 let rsp = rsp.try_into().expect("Response conversion should not fail");
1354
1355 Ok(rsp)
1356 }
1357 .boxed()
1358 }
1359 }
1360 }
1361}
1362
1363impl Service<ReadRequest> for ReadStateService {
1364 type Response = ReadResponse;
1365 type Error = BoxError;
1366 type Future =
1367 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1368
1369 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1370 let block_write_task = self.block_write_task.take();
1374
1375 if let Some(block_write_task) = block_write_task {
1376 if block_write_task.is_finished() {
1377 if let Some(block_write_task) = Arc::into_inner(block_write_task) {
1378 if let Err(thread_panic) = block_write_task.join() {
1380 std::panic::resume_unwind(thread_panic);
1381 }
1382 }
1383 } else {
1384 self.block_write_task = Some(block_write_task);
1386 }
1387 }
1388
1389 self.db.check_for_panics();
1390
1391 Poll::Ready(Ok(()))
1392 }
1393
1394 #[instrument(name = "read_state", skip(self, req))]
1395 fn call(&mut self, req: ReadRequest) -> Self::Future {
1396 req.count_metric();
1397 let timer = CodeTimer::start_desc(req.variant_name());
1398 let span = Span::current();
1399 let timed_span = TimedSpan::new(timer, span);
1400 let state = self.clone();
1401
1402 if let ReadRequest::NonFinalizedBlocksListener { known_chain_tips } = req {
1403 let non_finalized_blocks_listener = NonFinalizedBlocksListener::spawn(
1406 self.non_finalized_state_receiver.clone(),
1407 known_chain_tips,
1408 );
1409
1410 return async move {
1411 Ok(ReadResponse::NonFinalizedBlocksListener(
1412 non_finalized_blocks_listener,
1413 ))
1414 }
1415 .boxed();
1416 };
1417
1418 let request_handler = move || match req {
1419 ReadRequest::UsageInfo => Ok(ReadResponse::UsageInfo(state.db.size())),
1421
1422 ReadRequest::Tip => Ok(ReadResponse::Tip(read::tip(
1424 state.latest_best_chain(),
1425 &state.db,
1426 ))),
1427
1428 ReadRequest::TipPoolValues => {
1430 let (tip_height, tip_hash, value_balance) =
1431 read::tip_with_value_balance(state.latest_best_chain(), &state.db)?
1432 .ok_or(BoxError::from("no chain tip available yet"))?;
1433
1434 Ok(ReadResponse::TipPoolValues {
1435 tip_height,
1436 tip_hash,
1437 value_balance,
1438 })
1439 }
1440
1441 ReadRequest::BlockInfo(hash_or_height) => Ok(ReadResponse::BlockInfo(
1443 read::block_info(state.latest_best_chain(), &state.db, hash_or_height),
1444 )),
1445
1446 ReadRequest::Depth(hash) => Ok(ReadResponse::Depth(read::depth(
1448 state.latest_best_chain(),
1449 &state.db,
1450 hash,
1451 ))),
1452
1453 ReadRequest::BestChainNextMedianTimePast => {
1455 Ok(ReadResponse::BestChainNextMedianTimePast(
1456 read::next_median_time_past(&state.latest_non_finalized_state(), &state.db)?,
1457 ))
1458 }
1459
1460 ReadRequest::Block(hash_or_height) => Ok(ReadResponse::Block(read::block(
1462 state.latest_best_chain(),
1463 &state.db,
1464 hash_or_height,
1465 ))),
1466
1467 ReadRequest::AnyChainBlock(hash_or_height) => Ok(ReadResponse::Block(read::any_block(
1468 state.latest_non_finalized_state().chain_iter(),
1469 &state.db,
1470 hash_or_height,
1471 ))),
1472
1473 ReadRequest::BlockAndSize(hash_or_height) => Ok(ReadResponse::BlockAndSize(
1475 read::block_and_size(state.latest_best_chain(), &state.db, hash_or_height),
1476 )),
1477
1478 ReadRequest::SpentOutputs(hash_or_height) => Ok(ReadResponse::SpentOutputs(
1480 read::spent_outputs_for_block(state.latest_best_chain(), &state.db, hash_or_height),
1481 )),
1482
1483 ReadRequest::BlockHeader(hash_or_height) => {
1485 let best_chain = state.latest_best_chain();
1486
1487 let height = hash_or_height
1488 .height_or_else(|hash| {
1489 read::find::height_by_hash(best_chain.clone(), &state.db, hash)
1490 })
1491 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1492
1493 let hash = hash_or_height
1494 .hash_or_else(|height| {
1495 read::find::hash_by_height(best_chain.clone(), &state.db, height)
1496 })
1497 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1498
1499 let next_height = height.next()?;
1500 let next_block_hash =
1501 read::find::hash_by_height(best_chain.clone(), &state.db, next_height);
1502
1503 let header = read::block_header(best_chain, &state.db, height.into())
1504 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1505
1506 Ok(ReadResponse::BlockHeader {
1507 header,
1508 hash,
1509 height,
1510 next_block_hash,
1511 })
1512 }
1513
1514 ReadRequest::Transaction(hash) => Ok(ReadResponse::Transaction(
1516 read::mined_transaction(state.latest_best_chain(), &state.db, hash),
1517 )),
1518
1519 ReadRequest::AnyChainTransaction(hash) => {
1520 Ok(ReadResponse::AnyChainTransaction(read::any_transaction(
1521 state.latest_non_finalized_state().chain_iter(),
1522 &state.db,
1523 hash,
1524 )))
1525 }
1526
1527 ReadRequest::TransactionIdsForBlock(hash_or_height) => Ok(
1529 ReadResponse::TransactionIdsForBlock(read::transaction_hashes_for_block(
1530 state.latest_best_chain(),
1531 &state.db,
1532 hash_or_height,
1533 )),
1534 ),
1535
1536 ReadRequest::AnyChainTransactionIdsForBlock(hash_or_height) => {
1537 Ok(ReadResponse::AnyChainTransactionIdsForBlock(
1538 read::transaction_hashes_for_any_block(
1539 state.latest_non_finalized_state().chain_iter(),
1540 &state.db,
1541 hash_or_height,
1542 ),
1543 ))
1544 }
1545
1546 #[cfg(feature = "indexer")]
1547 ReadRequest::SpendingTransactionId(spend) => Ok(ReadResponse::TransactionId(
1548 read::spending_transaction_hash(state.latest_best_chain(), &state.db, spend),
1549 )),
1550
1551 ReadRequest::UnspentBestChainUtxo(outpoint) => Ok(ReadResponse::UnspentBestChainUtxo(
1552 read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint),
1553 )),
1554
1555 ReadRequest::AnyChainUtxo(outpoint) => Ok(ReadResponse::AnyChainUtxo(read::any_utxo(
1557 state.latest_non_finalized_state(),
1558 &state.db,
1559 outpoint,
1560 ))),
1561
1562 ReadRequest::BlockLocator => Ok(ReadResponse::BlockLocator(
1564 read::block_locator(state.latest_best_chain(), &state.db).unwrap_or_default(),
1565 )),
1566
1567 ReadRequest::FindBlockHashes { known_blocks, stop } => {
1569 Ok(ReadResponse::BlockHashes(read::find_chain_hashes(
1570 state.latest_best_chain(),
1571 &state.db,
1572 known_blocks,
1573 stop,
1574 MAX_FIND_BLOCK_HASHES_RESULTS,
1575 )))
1576 }
1577
1578 ReadRequest::FindBlockHeaders { known_blocks, stop } => Ok(ReadResponse::BlockHeaders(
1580 read::find_chain_headers(
1581 state.latest_best_chain(),
1582 &state.db,
1583 known_blocks,
1584 stop,
1585 MAX_FIND_BLOCK_HEADERS_RESULTS,
1586 )
1587 .into_iter()
1588 .map(|header| CountedHeader { header })
1589 .collect(),
1590 )),
1591
1592 ReadRequest::FindForkPoint { known_blocks } => {
1593 let locator_len: u64 = known_blocks
1596 .len()
1597 .try_into()
1598 .expect("usize always fits in u64 on supported (<=64-bit) platforms");
1599 if locator_len > block::MAX_BLOCK_LOCATOR_LENGTH {
1600 return Err(BoxError::from(format!(
1601 "FindForkPoint locator length {locator_len} exceeds \
1602 MAX_BLOCK_LOCATOR_LENGTH ({})",
1603 block::MAX_BLOCK_LOCATOR_LENGTH,
1604 )));
1605 }
1606
1607 Ok(ReadResponse::ForkPoint(read::find_fork_point(
1608 state.latest_best_chain(),
1609 &state.db,
1610 known_blocks,
1611 )))
1612 }
1613
1614 ReadRequest::SaplingTree(hash_or_height) => Ok(ReadResponse::SaplingTree(
1615 read::sapling_tree(state.latest_best_chain(), &state.db, hash_or_height),
1616 )),
1617
1618 ReadRequest::OrchardTree(hash_or_height) => Ok(ReadResponse::OrchardTree(
1619 read::orchard_tree(state.latest_best_chain(), &state.db, hash_or_height),
1620 )),
1621
1622 ReadRequest::AnyChainSaplingTree(hash) => {
1623 Ok(ReadResponse::SaplingTree(read::any_sapling_tree(
1624 state.latest_non_finalized_state().chain_iter(),
1625 &state.db,
1626 hash,
1627 )))
1628 }
1629
1630 ReadRequest::AnyChainOrchardTree(hash) => {
1631 Ok(ReadResponse::OrchardTree(read::any_orchard_tree(
1632 state.latest_non_finalized_state().chain_iter(),
1633 &state.db,
1634 hash,
1635 )))
1636 }
1637
1638 ReadRequest::IronwoodTree(hash_or_height) => Ok(ReadResponse::IronwoodTree(
1639 read::ironwood_tree(state.latest_best_chain(), &state.db, hash_or_height),
1640 )),
1641
1642 ReadRequest::AnyChainIronwoodTree(hash) => {
1643 Ok(ReadResponse::IronwoodTree(read::any_ironwood_tree(
1644 state.latest_non_finalized_state().chain_iter(),
1645 &state.db,
1646 hash,
1647 )))
1648 }
1649
1650 ReadRequest::SaplingSubtrees { start_index, limit } => {
1651 let end_index = limit
1652 .and_then(|limit| start_index.0.checked_add(limit.0))
1653 .map(NoteCommitmentSubtreeIndex);
1654
1655 let best_chain = state.latest_best_chain();
1656 let sapling_subtrees = if let Some(end_index) = end_index {
1657 read::sapling_subtrees(best_chain, &state.db, start_index..end_index)
1658 } else {
1659 read::sapling_subtrees(best_chain, &state.db, start_index..)
1664 };
1665
1666 Ok(ReadResponse::SaplingSubtrees(sapling_subtrees))
1667 }
1668
1669 ReadRequest::OrchardSubtrees { start_index, limit } => {
1670 let end_index = limit
1671 .and_then(|limit| start_index.0.checked_add(limit.0))
1672 .map(NoteCommitmentSubtreeIndex);
1673
1674 let best_chain = state.latest_best_chain();
1675 let orchard_subtrees = if let Some(end_index) = end_index {
1676 read::orchard_subtrees(best_chain, &state.db, start_index..end_index)
1677 } else {
1678 read::orchard_subtrees(best_chain, &state.db, start_index..)
1683 };
1684
1685 Ok(ReadResponse::OrchardSubtrees(orchard_subtrees))
1686 }
1687
1688 ReadRequest::IronwoodSubtrees { start_index, limit } => {
1689 let end_index = limit
1690 .and_then(|limit| start_index.0.checked_add(limit.0))
1691 .map(NoteCommitmentSubtreeIndex);
1692
1693 let best_chain = state.latest_best_chain();
1694 let ironwood_subtrees = if let Some(end_index) = end_index {
1695 read::ironwood_subtrees(best_chain, &state.db, start_index..end_index)
1696 } else {
1697 read::ironwood_subtrees(best_chain, &state.db, start_index..)
1702 };
1703
1704 Ok(ReadResponse::IronwoodSubtrees(ironwood_subtrees))
1705 }
1706
1707 ReadRequest::AddressBalance(addresses) => {
1709 let (balance, received) =
1710 read::transparent_balance(state.latest_best_chain(), &state.db, addresses)?;
1711 Ok(ReadResponse::AddressBalance { balance, received })
1712 }
1713
1714 ReadRequest::TransactionIdsByAddresses {
1716 addresses,
1717 height_range,
1718 max_entries,
1719 } => read::transparent_tx_ids(
1720 state.latest_best_chain(),
1721 &state.db,
1722 addresses,
1723 height_range,
1724 max_entries,
1725 )
1726 .map(ReadResponse::AddressesTransactionIds),
1727
1728 ReadRequest::UtxosByAddresses {
1730 addresses,
1731 height_range,
1732 max_entries,
1733 } => read::address_utxos(
1734 &state.network,
1735 state.latest_best_chain(),
1736 &state.db,
1737 addresses,
1738 height_range,
1739 max_entries,
1740 )
1741 .map(ReadResponse::AddressUtxos),
1742
1743 ReadRequest::CheckBestChainTipNullifiersAndAnchors(unmined_tx) => {
1744 let latest_non_finalized_best_chain = state.latest_best_chain();
1745
1746 check::nullifier::tx_no_duplicates_in_chain(
1747 &state.db,
1748 latest_non_finalized_best_chain.as_ref(),
1749 &unmined_tx.transaction,
1750 )?;
1751
1752 check::anchors::tx_anchors_refer_to_final_treestates(
1753 &state.db,
1754 latest_non_finalized_best_chain.as_ref(),
1755 &unmined_tx,
1756 )?;
1757
1758 Ok(ReadResponse::ValidBestChainTipNullifiersAndAnchors)
1759 }
1760
1761 ReadRequest::BestChainBlockHash(height) => Ok(ReadResponse::BlockHash(
1763 read::hash_by_height(state.latest_best_chain(), &state.db, height),
1764 )),
1765
1766 ReadRequest::ChainInfo => {
1768 read::difficulty::get_block_template_chain_info(
1780 &state.latest_non_finalized_state(),
1781 &state.db,
1782 &state.network,
1783 )
1784 .map(ReadResponse::ChainInfo)
1785 }
1786
1787 ReadRequest::SolutionRate { num_blocks, height } => {
1789 let latest_non_finalized_state = state.latest_non_finalized_state();
1790 let (tip_height, tip_hash) =
1798 match read::tip(latest_non_finalized_state.best_chain(), &state.db) {
1799 Some(tip_hash) => tip_hash,
1800 None => return Ok(ReadResponse::SolutionRate(None)),
1801 };
1802
1803 let start_height = height.map_or(tip_height, |height| height.min(tip_height));
1804 let start_hash = if start_height < tip_height {
1805 read::hash_by_height(
1806 latest_non_finalized_state.best_chain(),
1807 &state.db,
1808 start_height,
1809 )
1810 } else {
1811 Some(tip_hash)
1812 };
1813 let num_blocks = num_blocks.unwrap_or_else(|| {
1814 NetworkUpgrade::averaging_window_for_height(&state.network, start_height)
1815 });
1816
1817 let solution_rate = start_hash.and_then(|start_hash| {
1818 read::difficulty::solution_rate(
1819 &latest_non_finalized_state,
1820 &state.db,
1821 num_blocks,
1822 start_hash,
1823 )
1824 });
1825
1826 Ok(ReadResponse::SolutionRate(solution_rate))
1827 }
1828
1829 ReadRequest::CheckBlockProposalValidity(semantically_verified) => {
1830 tracing::debug!(
1831 "attempting to validate and commit block proposal \
1832 onto a cloned non-finalized state"
1833 );
1834 let mut latest_non_finalized_state = state.latest_non_finalized_state();
1835
1836 let Some((_best_tip_height, best_tip_hash)) =
1838 read::best_tip(&latest_non_finalized_state, &state.db)
1839 else {
1840 return Err(
1841 "state is empty: wait for Zebra to sync before submitting a proposal"
1842 .into(),
1843 );
1844 };
1845
1846 if semantically_verified.block.header.previous_block_hash != best_tip_hash {
1847 return Err("proposal is not based on the current best chain tip: \
1848 previous block hash must be the best chain tip"
1849 .into());
1850 }
1851
1852 latest_non_finalized_state.disable_metrics();
1858
1859 write::validate_and_commit_non_finalized(
1860 &state.db,
1861 &mut latest_non_finalized_state,
1862 semantically_verified,
1863 )?;
1864
1865 Ok(ReadResponse::ValidBlockProposal)
1866 }
1867
1868 ReadRequest::TipBlockSize => {
1869 Ok(ReadResponse::TipBlockSize(
1871 state
1872 .best_tip()
1873 .and_then(|(tip_height, _)| {
1874 read::block_info(
1875 state.latest_best_chain(),
1876 &state.db,
1877 tip_height.into(),
1878 )
1879 })
1880 .map(|info| info.size().try_into().expect("u32 should fit in usize"))
1881 .or_else(|| {
1882 find::tip_block(state.latest_best_chain(), &state.db)
1883 .map(|b| b.zcash_serialized_size())
1884 }),
1885 ))
1886 }
1887
1888 ReadRequest::NonFinalizedBlocksListener { .. } => {
1889 unreachable!("should return early");
1890 }
1891
1892 ReadRequest::IsTransparentOutputSpent(outpoint) => {
1894 let is_spent = read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint);
1895 Ok(ReadResponse::IsTransparentOutputSpent(is_spent.is_none()))
1896 }
1897 };
1898
1899 timed_span.spawn_blocking(request_handler)
1900 }
1901}
1902
1903pub async fn init(
1919 config: Config,
1920 network: &Network,
1921 max_checkpoint_height: block::Height,
1922 checkpoint_verify_concurrency_limit: usize,
1923) -> (
1924 BoxService<Request, Response, BoxError>,
1925 ReadStateService,
1926 LatestChainTip,
1927 ChainTipChange,
1928) {
1929 let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
1930 StateService::new(
1931 config,
1932 network,
1933 max_checkpoint_height,
1934 checkpoint_verify_concurrency_limit,
1935 )
1936 .await;
1937
1938 (
1939 BoxService::new(state_service),
1940 read_only_state_service,
1941 latest_chain_tip,
1942 chain_tip_change,
1943 )
1944}
1945
1946pub fn init_read_only(
1953 config: Config,
1954 network: &Network,
1955) -> Result<
1956 (
1957 ReadStateService,
1958 ZebraDb,
1959 tokio::sync::watch::Sender<NonFinalizedState>,
1960 ),
1961 StateInitError,
1962> {
1963 let finalized_state = FinalizedState::new_with_debug(
1964 &config,
1965 network,
1966 true,
1967 #[cfg(feature = "elasticsearch")]
1968 false,
1969 true,
1970 )?;
1971 let (non_finalized_state_sender, non_finalized_state_receiver) =
1972 tokio::sync::watch::channel(NonFinalizedState::new(network));
1973
1974 Ok((
1975 ReadStateService::new(
1976 &finalized_state,
1977 None,
1978 WatchReceiver::new(non_finalized_state_receiver),
1979 ),
1980 finalized_state.db.clone(),
1981 non_finalized_state_sender,
1982 ))
1983}
1984
1985pub fn spawn_init_read_only(
1992 config: Config,
1993 network: &Network,
1994) -> tokio::task::JoinHandle<
1995 Result<
1996 (
1997 ReadStateService,
1998 ZebraDb,
1999 tokio::sync::watch::Sender<NonFinalizedState>,
2000 ),
2001 StateInitError,
2002 >,
2003> {
2004 let network = network.clone();
2005 tokio::task::spawn_blocking(move || init_read_only(config, &network))
2006}
2007
2008#[cfg(any(test, feature = "proptest-impl"))]
2012pub async fn init_test(
2013 network: &Network,
2014) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
2015 let (state_service, _, _, _) =
2018 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
2019
2020 Buffer::new(BoxService::new(state_service), 1)
2021}
2022
2023#[cfg(any(test, feature = "proptest-impl"))]
2028pub async fn init_test_services(
2029 network: &Network,
2030) -> (
2031 Buffer<BoxService<Request, Response, BoxError>, Request>,
2032 ReadStateService,
2033 LatestChainTip,
2034 ChainTipChange,
2035) {
2036 let (state_service, read_state_service, latest_chain_tip, chain_tip_change) =
2039 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
2040
2041 let state_service = Buffer::new(BoxService::new(state_service), 1);
2042
2043 (
2044 state_service,
2045 read_state_service,
2046 latest_chain_tip,
2047 chain_tip_change,
2048 )
2049}