zebra_state/service.rs
1//! [`tower::Service`]s for Zebra's cached chain state.
2//!
3//! Zebra provides cached state access via two main services:
4//! - [`StateService`]: a read-write service that writes blocks to the state,
5//! and redirects most read requests to the [`ReadStateService`].
6//! - [`ReadStateService`]: a read-only service that answers from the most
7//! recent committed block.
8//!
9//! Most users should prefer [`ReadStateService`], unless they need to write blocks to the state.
10//!
11//! Zebra also provides access to the best chain tip via:
12//! - [`LatestChainTip`]: a read-only channel that contains the latest committed
13//! tip.
14//! - [`ChainTipChange`]: a read-only channel that can asynchronously await
15//! chain tip changes.
16
17use 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/// A read-write service for Zebra's cached blockchain state.
91///
92/// This service modifies and provides access to:
93/// - the non-finalized state: the most recent blocks, up to
94/// [`MAX_BLOCK_REORG_HEIGHT`](crate::MAX_BLOCK_REORG_HEIGHT) of them.
95/// Zebra allows chain forks in the non-finalized state,
96/// stores it in memory, and re-downloads it when restarted.
97/// - the finalized state: older blocks that have many confirmations.
98/// Zebra stores the single best chain in the finalized state,
99/// and re-loads it from disk when restarted.
100///
101/// Read requests to this service are buffered, then processed concurrently.
102/// Block write requests are buffered, then queued, then processed in order by a separate task.
103///
104/// Most state users can get faster read responses using the [`ReadStateService`],
105/// because its requests do not share a [`tower::buffer::Buffer`] with block write requests.
106///
107/// To quickly get the latest block, use [`LatestChainTip`] or [`ChainTipChange`].
108/// They can read the latest block directly, without queueing any requests.
109#[derive(Debug)]
110pub(crate) struct StateService {
111 // Configuration
112 //
113 /// The configured Zcash network.
114 network: Network,
115
116 /// The height that we start storing UTXOs from finalized blocks.
117 ///
118 /// This height should be lower than the last few checkpoints,
119 /// so the full verifier can verify UTXO spends from those blocks,
120 /// even if they haven't been committed to the finalized state yet.
121 full_verifier_utxo_lookahead: block::Height,
122
123 // Queued Blocks
124 //
125 /// Queued blocks for the [`NonFinalizedState`] that arrived out of order.
126 /// These blocks are awaiting their parent blocks before they can do contextual verification.
127 non_finalized_state_queued_blocks: QueuedBlocks,
128
129 /// Queued blocks for the [`FinalizedState`] that arrived out of order.
130 /// These blocks are awaiting their parent blocks before they can do contextual verification.
131 ///
132 /// Indexed by their parent block hash.
133 finalized_state_queued_blocks: HashMap<block::Hash, QueuedCheckpointVerified>,
134
135 /// Channels to send blocks to the block write task.
136 block_write_sender: write::BlockWriteSender,
137
138 /// The [`block::Hash`] of the most recent block sent on
139 /// `finalized_block_write_sender` or `non_finalized_block_write_sender`.
140 ///
141 /// On startup, this is:
142 /// - the finalized tip, if there are stored blocks, or
143 /// - the genesis block's parent hash, if the database is empty.
144 ///
145 /// If `invalid_block_write_reset_receiver` gets a reset, this is:
146 /// - the hash of the last valid committed block (the parent of the invalid block).
147 finalized_block_write_last_sent_hash: block::Hash,
148
149 /// A set of block hashes that have been sent to the block write task.
150 /// Hashes of blocks below the finalized tip height are periodically pruned.
151 non_finalized_block_write_sent_hashes: SentHashes,
152
153 /// If an invalid block is sent on `finalized_block_write_sender`
154 /// or `non_finalized_block_write_sender`,
155 /// this channel gets the [`block::Hash`] of the valid tip.
156 //
157 // TODO: add tests for finalized and non-finalized resets (#2654)
158 invalid_block_write_reset_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
159
160 /// Receives the hash of every non-finalized block that the write task
161 /// rejected, so the corresponding entry can be removed from
162 /// `non_finalized_block_write_sent_hashes`.
163 ///
164 /// Without this, a rejected same-hash block locks out a later honest
165 /// re-delivery of a block at the same hash as a "duplicate" until restart
166 /// or reorg.
167 non_finalized_rejected_receiver: tokio::sync::mpsc::UnboundedReceiver<block::Hash>,
168
169 // Pending UTXO Request Tracking
170 //
171 /// The set of outpoints with pending requests for their associated transparent::Output.
172 pending_utxos: PendingUtxos,
173
174 /// Instant tracking the last time `pending_utxos` was pruned.
175 last_prune: Instant,
176
177 // Updating Concurrently Readable State
178 //
179 /// A cloneable [`ReadStateService`], used to answer concurrent read requests.
180 ///
181 /// TODO: move users of read [`Request`]s to [`ReadStateService`], and remove `read_service`.
182 read_service: ReadStateService,
183
184 // Metrics
185 //
186 /// A metric tracking the maximum height that's currently in `finalized_state_queued_blocks`
187 ///
188 /// Set to `f64::NAN` if `finalized_state_queued_blocks` is empty, because grafana shows NaNs
189 /// as a break in the graph.
190 max_finalized_queue_height: f64,
191}
192
193/// A read-only service for accessing Zebra's cached blockchain state.
194///
195/// This service provides read-only access to:
196/// - the non-finalized state: the most recent blocks, up to
197/// [`MAX_BLOCK_REORG_HEIGHT`](crate::MAX_BLOCK_REORG_HEIGHT) of them.
198/// - the finalized state: older blocks that have many confirmations.
199///
200/// Requests to this service are processed in parallel,
201/// ignoring any blocks queued by the read-write [`StateService`].
202///
203/// This quick response behavior is better for most state users.
204/// It allows other async tasks to make progress while concurrently reading data from disk.
205#[derive(Clone, Debug)]
206pub struct ReadStateService {
207 // Configuration
208 //
209 /// The configured Zcash network.
210 network: Network,
211
212 // Shared Concurrently Readable State
213 //
214 /// A watch channel with a cached copy of the [`NonFinalizedState`].
215 ///
216 /// This state is only updated between requests,
217 /// so it might include some block data that is also on `disk`.
218 non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
219
220 /// The shared inner on-disk database for the finalized state.
221 ///
222 /// RocksDB allows reads and writes via a shared reference,
223 /// but [`ZebraDb`] doesn't expose any write methods or types.
224 ///
225 /// This chain is updated concurrently with requests,
226 /// so it might include some block data that is also in `best_mem`.
227 db: ZebraDb,
228
229 /// A shared handle to a task that writes blocks to the [`NonFinalizedState`] or [`FinalizedState`],
230 /// once the queues have received all their parent blocks.
231 ///
232 /// Used to check for panics when writing blocks.
233 block_write_task: Option<Arc<std::thread::JoinHandle<()>>>,
234}
235
236impl Drop for StateService {
237 fn drop(&mut self) {
238 // The state service owns the state, tasks, and channels,
239 // so dropping it should shut down everything.
240
241 // Close the channels (non-blocking)
242 // This makes the block write thread exit the next time it checks the channels.
243 // We want to do this here so we get any errors or panics from the block write task before it shuts down.
244 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 // Log database metrics before shutting down
254 info!("dropping the state: logging database metrics");
255 self.log_db_metrics();
256
257 // Then drop self.read_service, which checks the block write task for panics,
258 // and tries to shut down the database.
259 }
260}
261
262impl Drop for ReadStateService {
263 fn drop(&mut self) {
264 // The read state service shares the state,
265 // so dropping it should check if we can shut down.
266
267 // TODO: move this into a try_shutdown() method
268 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 // We're the last database user, so we can tell it to shut down (blocking):
271 // - flushes the database to disk, and
272 // - drops the database, which cleans up any database tasks correctly.
273 self.db.shutdown(true);
274
275 // We are the last state with a reference to this thread, so we can
276 // wait until the block write task finishes, then check for panics (blocking).
277 // (We'd also like to abort the thread, but std::thread::JoinHandle can't do that.)
278
279 // This log is verbose during tests.
280 #[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 // TODO: move this into a check_for_panics() method
286 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 // Even if we're not the last database user, try shutting it down.
294 //
295 // TODO: rename this to try_shutdown()?
296 self.db.shutdown(false);
297 }
298 }
299}
300
301impl StateService {
302 const PRUNE_INTERVAL: Duration = Duration::from_secs(30);
303
304 /// Creates a new state service for the state `config` and `network`.
305 ///
306 /// Uses the `max_checkpoint_height` and `checkpoint_verify_concurrency_limit`
307 /// to work out when it is near the final checkpoint.
308 ///
309 /// Returns the read-write and read-only state services,
310 /// and read-only watch channels for its best chain tip.
311 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 // # Correctness
345 //
346 // The state service must set the finalized block write sender to `None`
347 // if there are blocks in the restored non-finalized state that are above
348 // the max checkpoint height so that non-finalized blocks can be written, otherwise,
349 // Zebra will be unable to commit semantically verified blocks, and its chain sync will stall.
350 //
351 // The state service must not set the finalized block write sender to `None` if there
352 // aren't blocks in the restored non-finalized state that are above the max checkpoint height,
353 // otherwise, unless checkpoint sync is disabled in the zebra-consensus configuration,
354 // Zebra will be unable to commit checkpoint verified blocks, and its chain sync will stall.
355 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 // Spawn a background task to periodically export RocksDB metrics to Prometheus
476 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 /// Call read only state service to log rocksdb database metrics.
489 pub fn log_db_metrics(&self) {
490 self.read_service.db.print_db_metrics();
491 }
492
493 /// Queue a checkpoint verified block for verification and storage in the finalized state.
494 ///
495 /// Returns a channel receiver that provides the result of the block commit.
496 fn queue_and_commit_to_finalized_state(
497 &mut self,
498 checkpoint_verified: CheckpointVerifiedBlock,
499 ) -> oneshot::Receiver<Result<block::Hash, CommitCheckpointVerifiedError>> {
500 // # Correctness & Performance
501 //
502 // This method must not block, access the database, or perform CPU-intensive tasks,
503 // because it is called directly from the tokio executor's Future threads.
504
505 let queued_prev_hash = checkpoint_verified.block.header.previous_block_hash;
506 let queued_height = checkpoint_verified.height;
507
508 // If we're close to the final checkpoint, make the block's UTXOs available for
509 // semantic block verification, even when it is in the channel.
510 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 // We're still committing checkpoint verified blocks
520 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 // We've finished committing checkpoint verified blocks to the finalized state,
536 // so drop any repeated queued blocks, and return an error.
537 //
538 // TODO: track the latest sent height, and drop any blocks under that height
539 // every time we send some blocks (like QueuedSemanticallyVerifiedBlocks)
540 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 // if there are still blocks in the queue, then either:
557 // - the new block was lower than the old maximum, and there was a gap before it,
558 // so the maximum is still the same (and we skip this code), or
559 // - the new block is higher than the old maximum, and there is at least one gap
560 // between the finalized tip and the new maximum
561 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 /// Finds finalized state queue blocks to be committed to the state in order,
572 /// removes them from the queue, and sends them to the block commit task.
573 ///
574 /// After queueing a finalized block, this method checks whether the newly
575 /// queued block (and any of its descendants) can be committed to the state.
576 ///
577 /// Returns an error if the block commit channel has been closed.
578 pub fn drain_finalized_queue_and_commit(&mut self) {
579 use tokio::sync::mpsc::error::{SendError, TryRecvError};
580
581 // # Correctness & Performance
582 //
583 // This method must not block, access the database, or perform CPU-intensive tasks,
584 // because it is called directly from the tokio executor's Future threads.
585
586 // If a block failed, we need to start again from a valid tip.
587 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 // There are no errors, so we can just use the last block hash we sent
594 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 we've finished sending finalized blocks, ignore any repeated blocks.
606 // (Blocks can be repeated after a syncer reset.)
607 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 the receiver is closed, we can't send any more blocks.
611 if let Err(SendError(queued)) = send_result {
612 // If Zebra is shutting down, drop blocks and return an error.
613 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 /// Drains every hash queued on `non_finalized_rejected_receiver` and
628 /// removes it from `non_finalized_block_write_sent_hashes`.
629 ///
630 /// This closes the lockout window where a rejected block keeps its hash
631 /// recorded as "sent", so a subsequent honest re-delivery of a block at
632 /// the same hash is not short-circuited as a false "duplicate".
633 ///
634 /// # Correctness & Performance
635 ///
636 /// Like the other drain methods on `StateService`, this must not block,
637 /// access the database, or perform CPU-intensive work, because it is
638 /// called directly from the tokio executor's Future threads.
639 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 /// Drops all finalized state queue blocks, and sends an error on their result channels.
660 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 /// Send an error on a `QueuedCheckpointVerified` block's result channel, and drop the block
670 fn send_checkpoint_verified_block_error(
671 queued: QueuedCheckpointVerified,
672 error: impl Into<CommitCheckpointVerifiedError>,
673 ) {
674 let (finalized, rsp_tx) = queued;
675
676 // The block sender might have already given up on this block,
677 // so ignore any channel send errors.
678 let _ = rsp_tx.send(Err(error.into()));
679 std::mem::drop(finalized);
680 }
681
682 /// Drops all non-finalized state queue blocks, and sends an error on their result channels.
683 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 /// Send an error on a `QueuedSemanticallyVerified` block's result channel, and drop the block
693 fn send_semantically_verified_block_error(
694 queued: QueuedSemanticallyVerified,
695 error: impl Into<CommitSemanticallyVerifiedError>,
696 ) {
697 let (finalized, rsp_tx) = queued;
698
699 // The block sender might have already given up on this block,
700 // so ignore any channel send errors.
701 let _ = rsp_tx.send(Err(error.into()));
702 std::mem::drop(finalized);
703 }
704
705 /// Queue a semantically verified block for contextual verification and check if any queued
706 /// blocks are ready to be verified and committed to the state.
707 ///
708 /// This function encodes the logic for [committing non-finalized blocks][1]
709 /// in RFC0005.
710 ///
711 /// [1]: https://zebra.zfnd.org/dev/rfcs/0005-state-updates.html#committing-non-finalized-blocks
712 #[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 // Drop hashes of any blocks the write task has rejected before checking
721 // the SentHashes membership below. Without this, a rejected same-hash
722 // block would lock out a later honest re-delivery of a block at the
723 // same hash as a false "duplicate".
724 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 // [`Request::CommitSemanticallyVerifiedBlock`] contract: a request to commit a block which
754 // has been queued but not yet committed to the state fails the older request and replaces
755 // it with the newer request.
756 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 // We've finished sending checkpoint verified blocks when:
777 // - we've sent the verified block for the last checkpoint, and
778 // - it has been successfully written to disk.
779 //
780 // We detect the last checkpoint by looking for non-finalized blocks
781 // that are a child of the last block we sent.
782 //
783 // TODO: configure the state with the last checkpoint hash instead?
784 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 // Tell the block write task to stop committing checkpoint verified blocks to the finalized state,
792 // and move on to committing semantically verified blocks to the non-finalized state.
793 std::mem::drop(self.block_write_sender.finalized.take());
794 // Remove any checkpoint-verified block hashes from `non_finalized_block_write_sent_hashes`.
795 self.non_finalized_block_write_sent_hashes = SentHashes::default();
796 // Mark `SentHashes` as usable by the `can_fork_chain_at()` method.
797 self.non_finalized_block_write_sent_hashes
798 .can_fork_chain_at_hashes = true;
799 // Send blocks from non-finalized queue
800 self.send_ready_non_finalized_queued(self.finalized_block_write_last_sent_hash);
801 // We've finished committing checkpoint verified blocks to finalized state, so drop any repeated queued blocks.
802 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 // Wait until block commit task is ready to write non-finalized blocks before dequeuing them
810 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 /// Returns `true` if `hash` is a valid previous block hash for new non-finalized blocks.
827 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 /// Returns `true` if `queued_height` is near the final checkpoint.
834 ///
835 /// The semantic block verifier needs access to UTXOs from checkpoint verified blocks
836 /// near the final checkpoint, so that it can verify blocks that spend those UTXOs.
837 ///
838 /// If it doesn't have the required UTXOs, some blocks will time out,
839 /// but succeed after a syncer restart.
840 fn is_close_to_final_checkpoint(&self, queued_height: block::Height) -> bool {
841 queued_height >= self.full_verifier_utxo_lookahead
842 }
843
844 /// Sends all queued blocks whose parents have recently arrived starting from `new_parent`
845 /// in breadth-first ordering to the block write task which will attempt to validate and commit them
846 #[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 // If Zebra is shutting down, drop blocks and return an error.
866 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 /// Return the tip of the current best chain.
885 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 /// Assert some assumptions about the semantically verified `block` before it is queued.
938 fn assert_block_can_be_validated(&self, block: &SemanticallyVerifiedBlock) {
939 // required by `Request::CommitSemanticallyVerifiedBlock` call
940 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 /// Creates a new read-only state service, using the provided finalized state and
957 /// block write task handle.
958 ///
959 /// Returns the newly created service,
960 /// and a watch channel for updating the shared recent non-finalized chain.
961 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 /// Return the tip of the current best chain.
979 pub fn best_tip(&self) -> Option<(block::Height, block::Hash)> {
980 read::best_tip(&self.latest_non_finalized_state(), &self.db)
981 }
982
983 /// Gets a clone of the latest non-finalized state from the `non_finalized_state_receiver`
984 fn latest_non_finalized_state(&self) -> NonFinalizedState {
985 self.non_finalized_state_receiver.cloned_watch_data()
986 }
987
988 /// Gets a clone of the latest, best non-finalized chain from the `non_finalized_state_receiver`
989 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 /// Test-only access to the inner database.
995 /// Can be used to modify the database without doing any consensus checks.
996 #[cfg(any(test, feature = "proptest-impl"))]
997 pub fn db(&self) -> &ZebraDb {
998 &self.db
999 }
1000
1001 /// Logs rocksdb metrics using the read only state service.
1002 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 // Check for panics in the block write task
1015 let poll = self.read_service.poll_ready(cx);
1016
1017 // Prune outdated UTXO requests
1018 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 // Uses non_finalized_state_queued_blocks and pending_utxos in the StateService
1054 // Accesses shared writeable state in the StateService, NonFinalizedState, and ZebraDb.
1055 //
1056 // The expected error type for this request is `CommitSemanticallyVerifiedError`.
1057 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 // # Performance
1065 //
1066 // Allow other async tasks to make progress while blocks are being verified
1067 // and written to disk. But wait for the blocks to finish committing,
1068 // so that `StateService` multi-block queries always observe a consistent state.
1069 //
1070 // Since each block is spawned into its own task,
1071 // there shouldn't be any other code running in the same task,
1072 // so we don't need to worry about blocking it:
1073 // https://docs.rs/tokio/latest/tokio/task/fn.block_in_place.html
1074
1075 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 // TODO:
1082 // - check for panics in the block write task here,
1083 // as well as in poll_ready()
1084
1085 // The work is all done, the future just waits on a channel for the result
1086 timer.finish_desc("CommitSemanticallyVerifiedBlock");
1087
1088 // Await the channel response, flatten the result, map receive errors to
1089 // `CommitSemanticallyVerifiedError::WriteTaskExited`.
1090 // Then flatten the nested Result and convert any errors to a BoxError.
1091 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 // Uses finalized_state_queued_blocks and pending_utxos in the StateService.
1105 // Accesses shared writeable state in the StateService.
1106 //
1107 // The expected error type for this request is `CommitCheckpointVerifiedError`.
1108 Request::CommitCheckpointVerifiedBlock(finalized) => {
1109 let timer = CodeTimer::start();
1110 // # Consensus
1111 //
1112 // A semantic block verification could have called AwaitUtxo
1113 // before this checkpoint verified block arrived in the state.
1114 // So we need to check for pending UTXO requests sent by running
1115 // semantic block verifications.
1116 //
1117 // This check is redundant for most checkpoint verified blocks,
1118 // because semantic verification can only succeed near the final
1119 // checkpoint, when all the UTXOs are available for the verifying block.
1120 //
1121 // (Checkpoint block UTXOs are verified using block hash checkpoints
1122 // and transaction merkle tree block header commitments.)
1123 self.pending_utxos
1124 .check_against_ordered(&finalized.new_outputs);
1125
1126 // # Performance
1127 //
1128 // This method doesn't block, access the database, or perform CPU-intensive tasks,
1129 // so we can run it directly in the tokio executor's Future threads.
1130 let rsp_rx = self.queue_and_commit_to_finalized_state(finalized);
1131
1132 // TODO:
1133 // - check for panics in the block write task here,
1134 // as well as in poll_ready()
1135
1136 // The work is all done, the future just waits on a channel for the result
1137 timer.finish_desc("CommitCheckpointVerifiedBlock");
1138
1139 // Await the channel response, flatten the result, map receive errors to
1140 // `CommitCheckpointVerifiedError::WriteTaskExited`.
1141 // Then flatten the nested Result and convert any errors to a BoxError.
1142 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 // Uses pending_utxos and non_finalized_state_queued_blocks in the StateService.
1155 // If the UTXO isn't in the queued blocks, runs concurrently using the ReadStateService.
1156 Request::AwaitUtxo(outpoint) => {
1157 let timer = CodeTimer::start();
1158 // Prepare the AwaitUtxo future from PendingUxtos.
1159 let response_fut = self.pending_utxos.queue(outpoint);
1160 // Only instrument `response_fut`, the ReadStateService already
1161 // instruments its requests with the same span.
1162
1163 let response_fut = response_fut.instrument(span).boxed();
1164
1165 // Check the non-finalized block queue outside the returned future,
1166 // so we can access mutable state fields.
1167 if let Some(utxo) = self.non_finalized_state_queued_blocks.utxo(&outpoint) {
1168 self.pending_utxos.respond(&outpoint, utxo);
1169
1170 // We're finished, the returned future gets the UTXO from the respond() channel.
1171 timer.finish_desc("AwaitUtxo/queued-non-finalized");
1172
1173 return response_fut;
1174 }
1175
1176 // Check the sent non-finalized blocks
1177 if let Some(utxo) = self.non_finalized_block_write_sent_hashes.utxo(&outpoint) {
1178 self.pending_utxos.respond(&outpoint, utxo);
1179
1180 // We're finished, the returned future gets the UTXO from the respond() channel.
1181 timer.finish_desc("AwaitUtxo/sent-non-finalized");
1182
1183 return response_fut;
1184 }
1185
1186 // We ignore any UTXOs in FinalizedState.finalized_state_queued_blocks,
1187 // because it is only used during checkpoint verification.
1188 //
1189 // This creates a rare race condition, but it doesn't seem to happen much in practice.
1190 // See #5126 for details.
1191
1192 // Manually send a request to the ReadStateService,
1193 // to get UTXOs from any non-finalized chain or the finalized chain.
1194 let read_service = self.read_service.clone();
1195
1196 // Run the request in an async block, so we can await the response.
1197 async move {
1198 let req = ReadRequest::AnyChainUtxo(outpoint);
1199
1200 let rsp = read_service.oneshot(req).await?;
1201
1202 // Optional TODO:
1203 // - make pending_utxos.respond() async using a channel,
1204 // so we can respond to all waiting requests here
1205 //
1206 // This change is not required for correctness, because:
1207 // - any waiting requests should have returned when the block was sent to the state
1208 // - otherwise, the request returns immediately if:
1209 // - the block is in the non-finalized queue, or
1210 // - the block is in any non-finalized chain or the finalized state
1211 //
1212 // And if the block is in the finalized queue,
1213 // that's rare enough that a retry is ok.
1214 if let ReadResponse::AnyChainUtxo(Some(utxo)) = rsp {
1215 // We got a UTXO, so we replace the response future with the result own.
1216 timer.finish_desc("AwaitUtxo/any-chain");
1217
1218 return Ok(Response::Utxo(utxo));
1219 }
1220
1221 // We're finished, but the returned future is waiting on the respond() channel.
1222 timer.finish_desc("AwaitUtxo/waiting");
1223
1224 response_fut.await
1225 }
1226 .boxed()
1227 }
1228
1229 // Used by sync, inbound, and block verifier to check if a block is already in the state
1230 // before downloading or validating it.
1231 Request::KnownBlock(hash) => {
1232 let timer = CodeTimer::start();
1233 let sent_hash_response = self.known_sent_hash(&hash);
1234 let read_service = self.read_service.clone();
1235
1236 async move {
1237 if sent_hash_response.is_some() {
1238 return Ok(Response::KnownBlock(sent_hash_response));
1239 };
1240
1241 let response = read::non_finalized_state_contains_block_hash(
1242 &read_service.latest_non_finalized_state(),
1243 hash,
1244 )
1245 // TODO: Move this to a blocking task, perhaps by moving some of this logic to the ReadStateService.
1246 .or_else(|| read::finalized_state_contains_block_hash(&read_service.db, hash));
1247
1248 timer.finish_desc("Request::KnownBlock");
1249
1250 Ok(Response::KnownBlock(response))
1251 }
1252 .boxed()
1253 }
1254
1255 // The expected error type for this request is `InvalidateError`
1256 Request::InvalidateBlock(block_hash) => {
1257 let rsp_rx = tokio::task::block_in_place(move || {
1258 span.in_scope(|| self.send_invalidate_block(block_hash))
1259 });
1260
1261 // Await the channel response, flatten the result, map receive errors to
1262 // `InvalidateError::InvalidateRequestDropped`.
1263 // Then flatten the nested Result and convert any errors to a BoxError.
1264 let span = Span::current();
1265 async move {
1266 rsp_rx
1267 .await
1268 .map_err(|_recv_error| InvalidateError::InvalidateRequestDropped)
1269 .and_then(|result| result)
1270 .map_err(BoxError::from)
1271 .map(Response::Invalidated)
1272 }
1273 .instrument(span)
1274 .boxed()
1275 }
1276
1277 // The expected error type for this request is `ReconsiderError`
1278 Request::ReconsiderBlock(block_hash) => {
1279 let rsp_rx = tokio::task::block_in_place(move || {
1280 span.in_scope(|| self.send_reconsider_block(block_hash))
1281 });
1282
1283 // Await the channel response, flatten the result, map receive errors to
1284 // `ReconsiderError::ReconsiderResponseDropped`.
1285 // Then flatten the nested Result and convert any errors to a BoxError.
1286 let span = Span::current();
1287 async move {
1288 rsp_rx
1289 .await
1290 .map_err(|_recv_error| ReconsiderError::ReconsiderResponseDropped)
1291 .and_then(|result| result)
1292 .map_err(BoxError::from)
1293 .map(Response::Reconsidered)
1294 }
1295 .instrument(span)
1296 .boxed()
1297 }
1298
1299 // Runs concurrently using the ReadStateService
1300 Request::Tip
1301 | Request::Depth(_)
1302 | Request::BestChainNextMedianTimePast
1303 | Request::BestChainBlockHash(_)
1304 | Request::BlockLocator
1305 | Request::Transaction(_)
1306 | Request::AnyChainTransaction(_)
1307 | Request::UnspentBestChainUtxo(_)
1308 | Request::Block(_)
1309 | Request::AnyChainBlock(_)
1310 | Request::BlockAndSize(_)
1311 | Request::BlockHeader(_)
1312 | Request::FindBlockHashes { .. }
1313 | Request::FindBlockHeaders { .. }
1314 | Request::CheckBestChainTipNullifiersAndAnchors(_)
1315 | Request::CheckBlockProposalValidity(_) => {
1316 // Redirect the request to the concurrent ReadStateService
1317 let read_service = self.read_service.clone();
1318
1319 async move {
1320 let req = req
1321 .try_into()
1322 .expect("ReadRequest conversion should not fail");
1323
1324 let rsp = read_service.oneshot(req).await?;
1325 let rsp = rsp.try_into().expect("Response conversion should not fail");
1326
1327 Ok(rsp)
1328 }
1329 .boxed()
1330 }
1331 }
1332 }
1333}
1334
1335impl Service<ReadRequest> for ReadStateService {
1336 type Response = ReadResponse;
1337 type Error = BoxError;
1338 type Future =
1339 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
1340
1341 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1342 // Check for panics in the block write task
1343 //
1344 // TODO: move into a check_for_panics() method
1345 let block_write_task = self.block_write_task.take();
1346
1347 if let Some(block_write_task) = block_write_task {
1348 if block_write_task.is_finished() {
1349 if let Some(block_write_task) = Arc::into_inner(block_write_task) {
1350 // We are the last state with a reference to this task, so we can propagate any panics
1351 if let Err(thread_panic) = block_write_task.join() {
1352 std::panic::resume_unwind(thread_panic);
1353 }
1354 }
1355 } else {
1356 // It hasn't finished, so we need to put it back
1357 self.block_write_task = Some(block_write_task);
1358 }
1359 }
1360
1361 self.db.check_for_panics();
1362
1363 Poll::Ready(Ok(()))
1364 }
1365
1366 #[instrument(name = "read_state", skip(self, req))]
1367 fn call(&mut self, req: ReadRequest) -> Self::Future {
1368 req.count_metric();
1369 let timer = CodeTimer::start_desc(req.variant_name());
1370 let span = Span::current();
1371 let timed_span = TimedSpan::new(timer, span);
1372 let state = self.clone();
1373
1374 if let ReadRequest::NonFinalizedBlocksListener { known_chain_tips } = req {
1375 // The non-finalized blocks listener is used to notify the state service
1376 // about new blocks that have been added to the non-finalized state.
1377 let non_finalized_blocks_listener = NonFinalizedBlocksListener::spawn(
1378 self.non_finalized_state_receiver.clone(),
1379 known_chain_tips,
1380 );
1381
1382 return async move {
1383 Ok(ReadResponse::NonFinalizedBlocksListener(
1384 non_finalized_blocks_listener,
1385 ))
1386 }
1387 .boxed();
1388 };
1389
1390 let request_handler = move || match req {
1391 // Used by the `getblockchaininfo` RPC.
1392 ReadRequest::UsageInfo => Ok(ReadResponse::UsageInfo(state.db.size())),
1393
1394 // Used by the StateService.
1395 ReadRequest::Tip => Ok(ReadResponse::Tip(read::tip(
1396 state.latest_best_chain(),
1397 &state.db,
1398 ))),
1399
1400 // Used by `getblockchaininfo` RPC method.
1401 ReadRequest::TipPoolValues => {
1402 let (tip_height, tip_hash, value_balance) =
1403 read::tip_with_value_balance(state.latest_best_chain(), &state.db)?
1404 .ok_or(BoxError::from("no chain tip available yet"))?;
1405
1406 Ok(ReadResponse::TipPoolValues {
1407 tip_height,
1408 tip_hash,
1409 value_balance,
1410 })
1411 }
1412
1413 // Used by getblock
1414 ReadRequest::BlockInfo(hash_or_height) => Ok(ReadResponse::BlockInfo(
1415 read::block_info(state.latest_best_chain(), &state.db, hash_or_height),
1416 )),
1417
1418 // Used by the StateService.
1419 ReadRequest::Depth(hash) => Ok(ReadResponse::Depth(read::depth(
1420 state.latest_best_chain(),
1421 &state.db,
1422 hash,
1423 ))),
1424
1425 // Used by the StateService.
1426 ReadRequest::BestChainNextMedianTimePast => {
1427 Ok(ReadResponse::BestChainNextMedianTimePast(
1428 read::next_median_time_past(&state.latest_non_finalized_state(), &state.db)?,
1429 ))
1430 }
1431
1432 // Used by the get_block (raw) RPC and the StateService.
1433 ReadRequest::Block(hash_or_height) => Ok(ReadResponse::Block(read::block(
1434 state.latest_best_chain(),
1435 &state.db,
1436 hash_or_height,
1437 ))),
1438
1439 ReadRequest::AnyChainBlock(hash_or_height) => Ok(ReadResponse::Block(read::any_block(
1440 state.latest_non_finalized_state().chain_iter(),
1441 &state.db,
1442 hash_or_height,
1443 ))),
1444
1445 // Used by the get_block (raw) RPC and the StateService.
1446 ReadRequest::BlockAndSize(hash_or_height) => Ok(ReadResponse::BlockAndSize(
1447 read::block_and_size(state.latest_best_chain(), &state.db, hash_or_height),
1448 )),
1449
1450 // Used by the get_block (verbose) RPC and the StateService.
1451 ReadRequest::BlockHeader(hash_or_height) => {
1452 let best_chain = state.latest_best_chain();
1453
1454 let height = hash_or_height
1455 .height_or_else(|hash| {
1456 read::find::height_by_hash(best_chain.clone(), &state.db, hash)
1457 })
1458 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1459
1460 let hash = hash_or_height
1461 .hash_or_else(|height| {
1462 read::find::hash_by_height(best_chain.clone(), &state.db, height)
1463 })
1464 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1465
1466 let next_height = height.next()?;
1467 let next_block_hash =
1468 read::find::hash_by_height(best_chain.clone(), &state.db, next_height);
1469
1470 let header = read::block_header(best_chain, &state.db, height.into())
1471 .ok_or_else(|| BoxError::from("block hash or height not found"))?;
1472
1473 Ok(ReadResponse::BlockHeader {
1474 header,
1475 hash,
1476 height,
1477 next_block_hash,
1478 })
1479 }
1480
1481 // For the get_raw_transaction RPC and the StateService.
1482 ReadRequest::Transaction(hash) => Ok(ReadResponse::Transaction(
1483 read::mined_transaction(state.latest_best_chain(), &state.db, hash),
1484 )),
1485
1486 ReadRequest::AnyChainTransaction(hash) => {
1487 Ok(ReadResponse::AnyChainTransaction(read::any_transaction(
1488 state.latest_non_finalized_state().chain_iter(),
1489 &state.db,
1490 hash,
1491 )))
1492 }
1493
1494 // Used by the getblock (verbose) RPC.
1495 ReadRequest::TransactionIdsForBlock(hash_or_height) => Ok(
1496 ReadResponse::TransactionIdsForBlock(read::transaction_hashes_for_block(
1497 state.latest_best_chain(),
1498 &state.db,
1499 hash_or_height,
1500 )),
1501 ),
1502
1503 ReadRequest::AnyChainTransactionIdsForBlock(hash_or_height) => {
1504 Ok(ReadResponse::AnyChainTransactionIdsForBlock(
1505 read::transaction_hashes_for_any_block(
1506 state.latest_non_finalized_state().chain_iter(),
1507 &state.db,
1508 hash_or_height,
1509 ),
1510 ))
1511 }
1512
1513 #[cfg(feature = "indexer")]
1514 ReadRequest::SpendingTransactionId(spend) => Ok(ReadResponse::TransactionId(
1515 read::spending_transaction_hash(state.latest_best_chain(), &state.db, spend),
1516 )),
1517
1518 ReadRequest::UnspentBestChainUtxo(outpoint) => Ok(ReadResponse::UnspentBestChainUtxo(
1519 read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint),
1520 )),
1521
1522 // Manually used by the StateService to implement part of AwaitUtxo.
1523 ReadRequest::AnyChainUtxo(outpoint) => Ok(ReadResponse::AnyChainUtxo(read::any_utxo(
1524 state.latest_non_finalized_state(),
1525 &state.db,
1526 outpoint,
1527 ))),
1528
1529 // Used by the StateService.
1530 ReadRequest::BlockLocator => Ok(ReadResponse::BlockLocator(
1531 read::block_locator(state.latest_best_chain(), &state.db).unwrap_or_default(),
1532 )),
1533
1534 // Used by the StateService.
1535 ReadRequest::FindBlockHashes { known_blocks, stop } => {
1536 Ok(ReadResponse::BlockHashes(read::find_chain_hashes(
1537 state.latest_best_chain(),
1538 &state.db,
1539 known_blocks,
1540 stop,
1541 MAX_FIND_BLOCK_HASHES_RESULTS,
1542 )))
1543 }
1544
1545 // Used by the StateService.
1546 ReadRequest::FindBlockHeaders { known_blocks, stop } => Ok(ReadResponse::BlockHeaders(
1547 read::find_chain_headers(
1548 state.latest_best_chain(),
1549 &state.db,
1550 known_blocks,
1551 stop,
1552 MAX_FIND_BLOCK_HEADERS_RESULTS,
1553 )
1554 .into_iter()
1555 .map(|header| CountedHeader { header })
1556 .collect(),
1557 )),
1558
1559 ReadRequest::FindForkPoint { known_blocks } => {
1560 // Reject over-long locators before doing any work, so an untrusted
1561 // caller can't force unbounded lookups.
1562 let locator_len: u64 = known_blocks
1563 .len()
1564 .try_into()
1565 .expect("usize always fits in u64 on supported (<=64-bit) platforms");
1566 if locator_len > block::MAX_BLOCK_LOCATOR_LENGTH {
1567 return Err(BoxError::from(format!(
1568 "FindForkPoint locator length {locator_len} exceeds \
1569 MAX_BLOCK_LOCATOR_LENGTH ({})",
1570 block::MAX_BLOCK_LOCATOR_LENGTH,
1571 )));
1572 }
1573
1574 Ok(ReadResponse::ForkPoint(read::find_fork_point(
1575 state.latest_best_chain(),
1576 &state.db,
1577 known_blocks,
1578 )))
1579 }
1580
1581 ReadRequest::SaplingTree(hash_or_height) => Ok(ReadResponse::SaplingTree(
1582 read::sapling_tree(state.latest_best_chain(), &state.db, hash_or_height),
1583 )),
1584
1585 ReadRequest::OrchardTree(hash_or_height) => Ok(ReadResponse::OrchardTree(
1586 read::orchard_tree(state.latest_best_chain(), &state.db, hash_or_height),
1587 )),
1588
1589 ReadRequest::IronwoodTree(hash_or_height) => Ok(ReadResponse::IronwoodTree(
1590 read::ironwood_tree(state.latest_best_chain(), &state.db, hash_or_height),
1591 )),
1592
1593 ReadRequest::SaplingSubtrees { start_index, limit } => {
1594 let end_index = limit
1595 .and_then(|limit| start_index.0.checked_add(limit.0))
1596 .map(NoteCommitmentSubtreeIndex);
1597
1598 let best_chain = state.latest_best_chain();
1599 let sapling_subtrees = if let Some(end_index) = end_index {
1600 read::sapling_subtrees(best_chain, &state.db, start_index..end_index)
1601 } else {
1602 // If there is no end bound, just return all the trees.
1603 // If the end bound would overflow, just returns all the trees, because that's what
1604 // `zcashd` does. (It never calculates an end bound, so it just keeps iterating until
1605 // the trees run out.)
1606 read::sapling_subtrees(best_chain, &state.db, start_index..)
1607 };
1608
1609 Ok(ReadResponse::SaplingSubtrees(sapling_subtrees))
1610 }
1611
1612 ReadRequest::OrchardSubtrees { start_index, limit } => {
1613 let end_index = limit
1614 .and_then(|limit| start_index.0.checked_add(limit.0))
1615 .map(NoteCommitmentSubtreeIndex);
1616
1617 let best_chain = state.latest_best_chain();
1618 let orchard_subtrees = if let Some(end_index) = end_index {
1619 read::orchard_subtrees(best_chain, &state.db, start_index..end_index)
1620 } else {
1621 // If there is no end bound, just return all the trees.
1622 // If the end bound would overflow, just returns all the trees, because that's what
1623 // `zcashd` does. (It never calculates an end bound, so it just keeps iterating until
1624 // the trees run out.)
1625 read::orchard_subtrees(best_chain, &state.db, start_index..)
1626 };
1627
1628 Ok(ReadResponse::OrchardSubtrees(orchard_subtrees))
1629 }
1630
1631 ReadRequest::IronwoodSubtrees { start_index, limit } => {
1632 let end_index = limit
1633 .and_then(|limit| start_index.0.checked_add(limit.0))
1634 .map(NoteCommitmentSubtreeIndex);
1635
1636 let best_chain = state.latest_best_chain();
1637 let ironwood_subtrees = if let Some(end_index) = end_index {
1638 read::ironwood_subtrees(best_chain, &state.db, start_index..end_index)
1639 } else {
1640 // If there is no end bound, just return all the trees.
1641 // If the end bound would overflow, just returns all the trees, because that's what
1642 // `zcashd` does. (It never calculates an end bound, so it just keeps iterating until
1643 // the trees run out.)
1644 read::ironwood_subtrees(best_chain, &state.db, start_index..)
1645 };
1646
1647 Ok(ReadResponse::IronwoodSubtrees(ironwood_subtrees))
1648 }
1649
1650 // For the get_address_balance RPC.
1651 ReadRequest::AddressBalance(addresses) => {
1652 let (balance, received) =
1653 read::transparent_balance(state.latest_best_chain(), &state.db, addresses)?;
1654 Ok(ReadResponse::AddressBalance { balance, received })
1655 }
1656
1657 // For the get_address_tx_ids RPC.
1658 ReadRequest::TransactionIdsByAddresses {
1659 addresses,
1660 height_range,
1661 } => read::transparent_tx_ids(
1662 state.latest_best_chain(),
1663 &state.db,
1664 addresses,
1665 height_range,
1666 )
1667 .map(ReadResponse::AddressesTransactionIds),
1668
1669 // For the get_address_utxos RPC.
1670 ReadRequest::UtxosByAddresses(addresses) => read::address_utxos(
1671 &state.network,
1672 state.latest_best_chain(),
1673 &state.db,
1674 addresses,
1675 )
1676 .map(ReadResponse::AddressUtxos),
1677
1678 ReadRequest::CheckBestChainTipNullifiersAndAnchors(unmined_tx) => {
1679 let latest_non_finalized_best_chain = state.latest_best_chain();
1680
1681 check::nullifier::tx_no_duplicates_in_chain(
1682 &state.db,
1683 latest_non_finalized_best_chain.as_ref(),
1684 &unmined_tx.transaction,
1685 )?;
1686
1687 check::anchors::tx_anchors_refer_to_final_treestates(
1688 &state.db,
1689 latest_non_finalized_best_chain.as_ref(),
1690 &unmined_tx,
1691 )?;
1692
1693 Ok(ReadResponse::ValidBestChainTipNullifiersAndAnchors)
1694 }
1695
1696 // Used by the get_block and get_block_hash RPCs.
1697 ReadRequest::BestChainBlockHash(height) => Ok(ReadResponse::BlockHash(
1698 read::hash_by_height(state.latest_best_chain(), &state.db, height),
1699 )),
1700
1701 // Used by get_block_template and getblockchaininfo RPCs.
1702 ReadRequest::ChainInfo => {
1703 // # Correctness
1704 //
1705 // It is ok to do these lookups using multiple database calls. Finalized state updates
1706 // can only add overlapping blocks, and block hashes are unique across all chain forks.
1707 //
1708 // If there is a large overlap between the non-finalized and finalized states,
1709 // where the finalized tip is above the non-finalized tip,
1710 // Zebra is receiving a lot of blocks, or this request has been delayed for a long time.
1711 //
1712 // In that case, the `getblocktemplate` RPC will return an error because Zebra
1713 // is not synced to the tip. That check happens before the RPC makes this request.
1714 read::difficulty::get_block_template_chain_info(
1715 &state.latest_non_finalized_state(),
1716 &state.db,
1717 &state.network,
1718 )
1719 .map(ReadResponse::ChainInfo)
1720 }
1721
1722 // Used by getmininginfo, getnetworksolps, and getnetworkhashps RPCs.
1723 ReadRequest::SolutionRate { num_blocks, height } => {
1724 let latest_non_finalized_state = state.latest_non_finalized_state();
1725 // # Correctness
1726 //
1727 // It is ok to do these lookups using multiple database calls. Finalized state updates
1728 // can only add overlapping blocks, and block hashes are unique across all chain forks.
1729 //
1730 // The worst that can happen here is that the default `start_hash` will be below
1731 // the chain tip.
1732 let (tip_height, tip_hash) =
1733 match read::tip(latest_non_finalized_state.best_chain(), &state.db) {
1734 Some(tip_hash) => tip_hash,
1735 None => return Ok(ReadResponse::SolutionRate(None)),
1736 };
1737
1738 let start_hash = match height {
1739 Some(height) if height < tip_height => read::hash_by_height(
1740 latest_non_finalized_state.best_chain(),
1741 &state.db,
1742 height,
1743 ),
1744 // use the chain tip hash if height is above it or not provided.
1745 _ => Some(tip_hash),
1746 };
1747
1748 let solution_rate = start_hash.and_then(|start_hash| {
1749 read::difficulty::solution_rate(
1750 &latest_non_finalized_state,
1751 &state.db,
1752 num_blocks,
1753 start_hash,
1754 )
1755 });
1756
1757 Ok(ReadResponse::SolutionRate(solution_rate))
1758 }
1759
1760 ReadRequest::CheckBlockProposalValidity(semantically_verified) => {
1761 tracing::debug!(
1762 "attempting to validate and commit block proposal \
1763 onto a cloned non-finalized state"
1764 );
1765 let mut latest_non_finalized_state = state.latest_non_finalized_state();
1766
1767 // The previous block of a valid proposal must be on the best chain tip.
1768 let Some((_best_tip_height, best_tip_hash)) =
1769 read::best_tip(&latest_non_finalized_state, &state.db)
1770 else {
1771 return Err(
1772 "state is empty: wait for Zebra to sync before submitting a proposal"
1773 .into(),
1774 );
1775 };
1776
1777 if semantically_verified.block.header.previous_block_hash != best_tip_hash {
1778 return Err("proposal is not based on the current best chain tip: \
1779 previous block hash must be the best chain tip"
1780 .into());
1781 }
1782
1783 // This clone of the non-finalized state is dropped when this closure returns.
1784 // The non-finalized state that's used in the rest of the state (including finalizing
1785 // blocks into the db) is not mutated here.
1786 //
1787 // TODO: Convert `CommitSemanticallyVerifiedError` to a new `ValidateProposalError`?
1788 latest_non_finalized_state.disable_metrics();
1789
1790 write::validate_and_commit_non_finalized(
1791 &state.db,
1792 &mut latest_non_finalized_state,
1793 semantically_verified,
1794 )?;
1795
1796 Ok(ReadResponse::ValidBlockProposal)
1797 }
1798
1799 ReadRequest::TipBlockSize => {
1800 // Respond with the length of the obtained block if any.
1801 Ok(ReadResponse::TipBlockSize(
1802 state
1803 .best_tip()
1804 .and_then(|(tip_height, _)| {
1805 read::block_info(
1806 state.latest_best_chain(),
1807 &state.db,
1808 tip_height.into(),
1809 )
1810 })
1811 .map(|info| info.size().try_into().expect("u32 should fit in usize"))
1812 .or_else(|| {
1813 find::tip_block(state.latest_best_chain(), &state.db)
1814 .map(|b| b.zcash_serialized_size())
1815 }),
1816 ))
1817 }
1818
1819 ReadRequest::NonFinalizedBlocksListener { .. } => {
1820 unreachable!("should return early");
1821 }
1822
1823 // Used by `gettxout` RPC method.
1824 ReadRequest::IsTransparentOutputSpent(outpoint) => {
1825 let is_spent = read::unspent_utxo(state.latest_best_chain(), &state.db, outpoint);
1826 Ok(ReadResponse::IsTransparentOutputSpent(is_spent.is_none()))
1827 }
1828 };
1829
1830 timed_span.spawn_blocking(request_handler)
1831 }
1832}
1833
1834/// Initialize a state service from the provided [`Config`].
1835/// Returns a boxed state service, a read-only state service,
1836/// and receivers for state chain tip updates.
1837///
1838/// Each `network` has its own separate on-disk database.
1839///
1840/// The state uses the `max_checkpoint_height` and `checkpoint_verify_concurrency_limit`
1841/// to work out when it is near the final checkpoint.
1842///
1843/// To share access to the state, wrap the returned service in a `Buffer`,
1844/// or clone the returned [`ReadStateService`].
1845///
1846/// It's possible to construct multiple state services in the same application (as
1847/// long as they, e.g., use different storage locations), but doing so is
1848/// probably not what you want.
1849pub async fn init(
1850 config: Config,
1851 network: &Network,
1852 max_checkpoint_height: block::Height,
1853 checkpoint_verify_concurrency_limit: usize,
1854) -> (
1855 BoxService<Request, Response, BoxError>,
1856 ReadStateService,
1857 LatestChainTip,
1858 ChainTipChange,
1859) {
1860 let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
1861 StateService::new(
1862 config,
1863 network,
1864 max_checkpoint_height,
1865 checkpoint_verify_concurrency_limit,
1866 )
1867 .await;
1868
1869 (
1870 BoxService::new(state_service),
1871 read_only_state_service,
1872 latest_chain_tip,
1873 chain_tip_change,
1874 )
1875}
1876
1877/// Initialize a read state service from the provided [`Config`].
1878/// Returns a read-only state service,
1879///
1880/// Each `network` has its own separate on-disk database.
1881///
1882/// To share access to the state, clone the returned [`ReadStateService`].
1883pub fn init_read_only(
1884 config: Config,
1885 network: &Network,
1886) -> Result<
1887 (
1888 ReadStateService,
1889 ZebraDb,
1890 tokio::sync::watch::Sender<NonFinalizedState>,
1891 ),
1892 StateInitError,
1893> {
1894 let finalized_state = FinalizedState::new_with_debug(
1895 &config,
1896 network,
1897 true,
1898 #[cfg(feature = "elasticsearch")]
1899 false,
1900 true,
1901 )?;
1902 let (non_finalized_state_sender, non_finalized_state_receiver) =
1903 tokio::sync::watch::channel(NonFinalizedState::new(network));
1904
1905 Ok((
1906 ReadStateService::new(
1907 &finalized_state,
1908 None,
1909 WatchReceiver::new(non_finalized_state_receiver),
1910 ),
1911 finalized_state.db.clone(),
1912 non_finalized_state_sender,
1913 ))
1914}
1915
1916/// Calls [`init_read_only`] with the provided [`Config`] and [`Network`] from a blocking task.
1917///
1918/// Returns a [`tokio::task::JoinHandle`] whose output is a [`Result`]: awaiting it yields a
1919/// [`JoinError`](tokio::task::JoinError) if the blocking task panicked or was cancelled, and
1920/// otherwise an `Err(`[`StateInitError`]`)` if the read-only state could not be opened (for
1921/// example, a missing read-only database).
1922pub fn spawn_init_read_only(
1923 config: Config,
1924 network: &Network,
1925) -> tokio::task::JoinHandle<
1926 Result<
1927 (
1928 ReadStateService,
1929 ZebraDb,
1930 tokio::sync::watch::Sender<NonFinalizedState>,
1931 ),
1932 StateInitError,
1933 >,
1934> {
1935 let network = network.clone();
1936 tokio::task::spawn_blocking(move || init_read_only(config, &network))
1937}
1938
1939/// Returns a [`StateService`] with an ephemeral [`Config`] and a buffer with a single slot.
1940///
1941/// This can be used to create a state service for testing. See also [`init`].
1942#[cfg(any(test, feature = "proptest-impl"))]
1943pub async fn init_test(
1944 network: &Network,
1945) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
1946 // TODO: pass max_checkpoint_height and checkpoint_verify_concurrency limit
1947 // if we ever need to test final checkpoint sent UTXO queries
1948 let (state_service, _, _, _) =
1949 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1950
1951 Buffer::new(BoxService::new(state_service), 1)
1952}
1953
1954/// Initializes a state service with an ephemeral [`Config`] and a buffer with a single slot,
1955/// then returns the read-write service, read-only service, and tip watch channels.
1956///
1957/// This can be used to create a state service for testing. See also [`init`].
1958#[cfg(any(test, feature = "proptest-impl"))]
1959pub async fn init_test_services(
1960 network: &Network,
1961) -> (
1962 Buffer<BoxService<Request, Response, BoxError>, Request>,
1963 ReadStateService,
1964 LatestChainTip,
1965 ChainTipChange,
1966) {
1967 // TODO: pass max_checkpoint_height and checkpoint_verify_concurrency limit
1968 // if we ever need to test final checkpoint sent UTXO queries
1969 let (state_service, read_state_service, latest_chain_tip, chain_tip_change) =
1970 StateService::new(Config::ephemeral(), network, block::Height::MAX, 0).await;
1971
1972 let state_service = Buffer::new(BoxService::new(state_service), 1);
1973
1974 (
1975 state_service,
1976 read_state_service,
1977 latest_chain_tip,
1978 chain_tip_change,
1979 )
1980}