Skip to main content

zebra_state/
response.rs

1//! State [`tower::Service`] response types.
2
3use std::{
4    collections::{BTreeMap, HashSet},
5    sync::Arc,
6};
7
8use chrono::{DateTime, Utc};
9
10use zebra_chain::{
11    amount::{Amount, NonNegative},
12    block::{self, Block, ChainHistoryMmrRootHash},
13    block_info::BlockInfo,
14    orchard, sapling,
15    serialization::DateTime32,
16    subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
17    transaction::{self, Transaction},
18    transparent,
19    value_balance::ValueBalance,
20};
21
22use zebra_chain::work::difficulty::CompactDifficulty;
23
24// Allow *only* these unused imports, so that rustdoc link resolution
25// will work with inline links.
26#[allow(unused_imports)]
27use crate::{ReadRequest, Request};
28
29use crate::{
30    service::read::AddressUtxos, ContextuallyVerifiedBlock, NonFinalizedState, TransactionLocation,
31    WatchReceiver, MAX_BLOCK_REORG_HEIGHT,
32};
33
34#[cfg(test)]
35mod tests;
36
37#[derive(Clone, Debug, PartialEq, Eq)]
38/// A response to a [`StateService`](crate::service::StateService) [`Request`].
39pub enum Response {
40    /// Response to [`Request::CommitSemanticallyVerifiedBlock`] and [`Request::CommitCheckpointVerifiedBlock`]
41    /// indicating that a block was successfully committed to the state.
42    Committed(block::Hash),
43
44    /// Response to [`Request::InvalidateBlock`] indicating that a block was found and
45    /// invalidated in the state.
46    Invalidated(block::Hash),
47
48    /// Response to [`Request::ReconsiderBlock`] indicating that a previously invalidated
49    /// block was reconsidered and re-committed to the non-finalized state. Contains a list
50    /// of block hashes that were reconsidered in the state and successfully re-committed.
51    Reconsidered(Vec<block::Hash>),
52
53    /// Response to [`Request::Depth`] with the depth of the specified block.
54    Depth(Option<u32>),
55
56    /// Response to [`Request::Tip`] with the current best chain tip.
57    //
58    // TODO: remove this request, and replace it with a call to
59    //       `LatestChainTip::best_tip_height_and_hash()`
60    Tip(Option<(block::Height, block::Hash)>),
61
62    /// Response to [`Request::BlockLocator`] with a block locator object.
63    BlockLocator(Vec<block::Hash>),
64
65    /// Response to [`Request::Transaction`] with the specified transaction.
66    Transaction(Option<Arc<Transaction>>),
67
68    /// Response to [`Request::AnyChainTransaction`] with the specified transaction.
69    AnyChainTransaction(Option<AnyTx>),
70
71    /// Response to [`Request::UnspentBestChainUtxo`] with the UTXO
72    UnspentBestChainUtxo(Option<transparent::Utxo>),
73
74    /// Response to [`Request::Block`] with the specified block.
75    Block(Option<Arc<Block>>),
76
77    /// Response to [`Request::BlockAndSize`] with the specified block and size.
78    BlockAndSize(Option<(Arc<Block>, usize)>),
79
80    /// The response to a `BlockHeader` request.
81    BlockHeader {
82        /// The header of the requested block
83        header: Arc<block::Header>,
84        /// The hash of the requested block
85        hash: block::Hash,
86        /// The height of the requested block
87        height: block::Height,
88        /// The hash of the next block after the requested block
89        next_block_hash: Option<block::Hash>,
90    },
91
92    /// The response to a `AwaitUtxo` request, from any non-finalized chains, finalized chain,
93    /// pending unverified blocks, or blocks received after the request was sent.
94    Utxo(transparent::Utxo),
95
96    /// The response to a `FindBlockHashes` request.
97    BlockHashes(Vec<block::Hash>),
98
99    /// The response to a `FindBlockHeaders` request.
100    BlockHeaders(Vec<block::CountedHeader>),
101
102    /// Response to [`Request::CheckBestChainTipNullifiersAndAnchors`].
103    ///
104    /// Does not check transparent UTXO inputs
105    ValidBestChainTipNullifiersAndAnchors,
106
107    /// Response to [`Request::BestChainNextMedianTimePast`].
108    /// Contains the median-time-past for the *next* block on the best chain.
109    BestChainNextMedianTimePast(DateTime32),
110
111    /// Response to [`Request::BestChainBlockHash`] with the specified block hash.
112    BlockHash(Option<block::Hash>),
113
114    /// Response to [`Request::KnownBlock`].
115    KnownBlock(Option<KnownBlock>),
116
117    /// Response to [`Request::CheckBlockProposalValidity`]
118    ValidBlockProposal,
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
122/// An enum of block stores in the state where a block hash could be found.
123pub enum KnownBlock {
124    /// Block is in the finalized portion of the best chain.
125    Finalized,
126
127    /// Block is in the best chain.
128    BestChain,
129
130    /// Block is in a side chain.
131    SideChain,
132
133    /// Block is in a block write channel
134    WriteChannel,
135
136    /// Block is queued to be validated and committed, or rejected and dropped.
137    Queue,
138}
139
140impl std::fmt::Display for KnownBlock {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            KnownBlock::Finalized => write!(f, "finalized state"),
144            KnownBlock::BestChain => write!(f, "best chain"),
145            KnownBlock::SideChain => write!(f, "side chain"),
146            KnownBlock::WriteChannel => write!(f, "block write channel"),
147            KnownBlock::Queue => write!(f, "validation/commit queue"),
148        }
149    }
150}
151
152/// Information about a transaction in any chain.
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub enum AnyTx {
155    /// A transaction in the best chain.
156    Mined(MinedTx),
157    /// A transaction in a side chain, and the hash of the block it is in.
158    Side((Arc<Transaction>, block::Hash)),
159}
160
161impl From<AnyTx> for Arc<Transaction> {
162    fn from(any_tx: AnyTx) -> Self {
163        match any_tx {
164            AnyTx::Mined(mined_tx) => mined_tx.tx,
165            AnyTx::Side((tx, _)) => tx,
166        }
167    }
168}
169
170/// Information about a transaction in the best chain
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub struct MinedTx {
173    /// The transaction.
174    pub tx: Arc<Transaction>,
175
176    /// The transaction height.
177    pub height: block::Height,
178
179    /// The number of confirmations for this transaction
180    /// (1 + depth of block the transaction was found in)
181    pub confirmations: u32,
182
183    /// The time of the block where the transaction was mined.
184    pub block_time: DateTime<Utc>,
185
186    /// The best-chain tip hash captured in the same state snapshot used to
187    /// compute `confirmations`.
188    ///
189    /// Callers that combine this response with other state queries should
190    /// pin those follow-up queries to this hash (or to the resolved block
191    /// hash for the transaction) rather than issuing a separate `Tip` /
192    /// `BestChainBlockHash` request, which would re-sample the chain and
193    /// can race with reorgs or new blocks. See issue #10550.
194    pub best_chain_tip_hash: block::Hash,
195}
196
197impl MinedTx {
198    /// Creates a new [`MinedTx`]
199    pub fn new(
200        tx: Arc<Transaction>,
201        height: block::Height,
202        confirmations: u32,
203        block_time: DateTime<Utc>,
204        best_chain_tip_hash: block::Hash,
205    ) -> Self {
206        Self {
207            tx,
208            height,
209            confirmations,
210            block_time,
211            best_chain_tip_hash,
212        }
213    }
214}
215
216/// How many non-finalized block references to buffer in [`NonFinalizedBlocksListener`] before blocking sends.
217///
218/// # Correctness
219///
220/// This should be large enough to typically avoid blocking the sender when the non-finalized state is full so
221/// that the [`NonFinalizedBlocksListener`] reliably receives updates whenever the non-finalized state changes.
222///
223/// If the buffer does fill, sends apply backpressure (the sender awaits a free slot) rather than
224/// dropping blocks, so the listener still receives every block once the consumer catches up.
225// `MAX_BLOCK_REORG_HEIGHT` is a small `u32` constant (the reorg limit), so widening it to `usize`
226// and doubling it cannot overflow on any supported platform.
227const NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE: usize = 2 * MAX_BLOCK_REORG_HEIGHT as usize;
228
229/// A listener for changes in the non-finalized state.
230#[derive(Clone, Debug)]
231pub struct NonFinalizedBlocksListener(
232    pub Arc<tokio::sync::mpsc::Receiver<(zebra_chain::block::Hash, Arc<zebra_chain::block::Block>)>>,
233);
234
235impl NonFinalizedBlocksListener {
236    /// Sends the blocks in `non_finalized_state` that satisfy `take_cond` to `sender`, in
237    /// ascending height order.
238    ///
239    /// Walks each chain from its tip downwards, taking blocks while `take_cond` holds and stopping
240    /// at the first block that fails it, so it sends the blocks a listener hasn't been sent yet by
241    /// stopping at the first block it already has.
242    ///
243    /// Returns an error if the receiver has been dropped.
244    async fn take_and_send_blocks<'a>(
245        sender: &tokio::sync::mpsc::Sender<(block::Hash, Arc<Block>)>,
246        non_finalized_state: &'a NonFinalizedState,
247        take_cond: impl Fn(&&ContextuallyVerifiedBlock) -> bool + Copy + 'a,
248    ) -> Result<(), tokio::sync::mpsc::error::SendError<(block::Hash, Arc<Block>)>> {
249        let new_blocks = non_finalized_state
250            .chain_iter()
251            .flat_map(move |chain| {
252                // Take blocks from the chain in reverse height order until we reach a block the
253                // listener already has, then restore ascending height order.
254                let mut blocks: Vec<_> =
255                    chain.blocks.values().rev().take_while(take_cond).collect();
256                blocks.reverse();
257                blocks
258            })
259            .map(|cv_block| (cv_block.hash, cv_block.block.clone()));
260
261        for new_block_with_hash in new_blocks {
262            sender.send(new_block_with_hash).await?;
263        }
264
265        Ok(())
266    }
267
268    /// Spawns a task to listen for changes in the non-finalized state and sends any blocks in the non-finalized state
269    /// to the caller that have not already been sent.
270    ///
271    /// `known_chain_tips` holds the hashes of chain tips the caller already has. On the first send
272    /// only, each non-finalized chain is walked from its tip downwards and blocks are sent until a
273    /// hash in this set is reached, so any block at or below a known tip on the same chain is
274    /// skipped. If it is empty, every block currently in the non-finalized state is sent. After the
275    /// first send, those blocks are already tracked as sent, so later sends only forward blocks that
276    /// weren't in the previously seen non-finalized state.
277    ///
278    /// Returns a new instance of [`NonFinalizedBlocksListener`] for the caller to listen for new blocks in the non-finalized state.
279    pub fn spawn(
280        mut non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
281        known_chain_tips: HashSet<block::Hash>,
282    ) -> Self {
283        let (sender, receiver) = tokio::sync::mpsc::channel(NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE);
284
285        tokio::spawn(async move {
286            // `prev_non_finalized_state` starts as the current non-finalized state. The first send
287            // below skips blocks at or below the caller's known chain tips; afterwards those blocks
288            // are already in `prev_non_finalized_state`, so later sends only need to check it.
289            let mut prev_non_finalized_state = non_finalized_state_receiver.cloned_watch_data();
290
291            // Send the blocks the caller is missing relative to its known chain tips. This checks
292            // `known_chain_tips` once; from here on those blocks are covered by the state check.
293            if Self::take_and_send_blocks(&sender, &prev_non_finalized_state, |b| {
294                !known_chain_tips.contains(&b.hash)
295            })
296            .await
297            .is_err()
298            {
299                tracing::debug!("non-finalized blocks receiver closed, ending task");
300                return;
301            }
302
303            // # Correctness
304            //
305            // This loop should check that the non-finalized state receiver has changed sooner
306            // than the non-finalized state could possibly have changed to avoid missing updates, so
307            // the logic here should be quicker than the contextual verification logic that precedes
308            // commits to the non-finalized state.
309            //
310            // See the `NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE` documentation for more details.
311            loop {
312                let non_finalized_state = non_finalized_state_receiver.cloned_watch_data();
313
314                // Send blocks that weren't in the last seen copy of the non-finalized state. The
315                // caller's known tips are already covered by `prev_non_finalized_state`.
316                if Self::take_and_send_blocks(&sender, &non_finalized_state, |b| {
317                    !prev_non_finalized_state.any_chain_contains(&b.hash)
318                })
319                .await
320                .is_err()
321                {
322                    tracing::debug!("non-finalized blocks receiver closed, ending task");
323                    return;
324                }
325
326                prev_non_finalized_state = non_finalized_state;
327
328                // Wait for the next update to the non-finalized state.
329                if let Err(error) = non_finalized_state_receiver.changed().await {
330                    warn!(
331                        ?error,
332                        "non-finalized state receiver closed, is Zebra shutting down?"
333                    );
334                    break;
335                }
336            }
337        });
338
339        Self(Arc::new(receiver))
340    }
341
342    /// Consumes `self`, unwrapping the inner [`Arc`] and returning the non-finalized state change channel receiver.
343    ///
344    /// # Panics
345    ///
346    /// If the `Arc` has more than one strong reference, this will panic.
347    pub fn unwrap(
348        self,
349    ) -> tokio::sync::mpsc::Receiver<(zebra_chain::block::Hash, Arc<zebra_chain::block::Block>)>
350    {
351        Arc::try_unwrap(self.0).unwrap()
352    }
353}
354
355impl PartialEq for NonFinalizedBlocksListener {
356    fn eq(&self, other: &Self) -> bool {
357        Arc::ptr_eq(&self.0, &other.0)
358    }
359}
360
361impl Eq for NonFinalizedBlocksListener {}
362
363#[derive(Clone, Debug, PartialEq, Eq)]
364/// A response to a read-only
365/// [`ReadStateService`](crate::service::ReadStateService)'s [`ReadRequest`].
366pub enum ReadResponse {
367    /// Response to [`ReadRequest::UsageInfo`] with the current best chain tip.
368    UsageInfo(u64),
369
370    /// Response to [`ReadRequest::Tip`] with the current best chain tip.
371    Tip(Option<(block::Height, block::Hash)>),
372
373    /// Response to [`ReadRequest::TipPoolValues`] with
374    /// the current best chain tip and its [`ValueBalance`].
375    TipPoolValues {
376        /// The current best chain tip height.
377        tip_height: block::Height,
378        /// The current best chain tip hash.
379        tip_hash: block::Hash,
380        /// The value pool balance at the current best chain tip.
381        value_balance: ValueBalance<NonNegative>,
382    },
383
384    /// Response to [`ReadRequest::BlockInfo`] with
385    /// the block info after the specified block.
386    BlockInfo(Option<BlockInfo>),
387
388    /// Response to [`ReadRequest::Depth`] with the depth of the specified block.
389    Depth(Option<u32>),
390
391    /// Response to [`ReadRequest::Block`] with the specified block.
392    Block(Option<Arc<Block>>),
393
394    /// Response to [`ReadRequest::BlockAndSize`] with the specified block and
395    /// serialized size.
396    BlockAndSize(Option<(Arc<Block>, usize)>),
397
398    /// The response to a `BlockHeader` request.
399    BlockHeader {
400        /// The header of the requested block
401        header: Arc<block::Header>,
402        /// The hash of the requested block
403        hash: block::Hash,
404        /// The height of the requested block
405        height: block::Height,
406        /// The hash of the next block after the requested block
407        next_block_hash: Option<block::Hash>,
408    },
409
410    /// Response to [`ReadRequest::Transaction`] with the specified transaction.
411    Transaction(Option<MinedTx>),
412
413    /// Response to [`Request::Transaction`] with the specified transaction.
414    AnyChainTransaction(Option<AnyTx>),
415
416    /// Response to [`ReadRequest::TransactionIdsForBlock`],
417    /// with an list of transaction hashes in block order,
418    /// or `None` if the block was not found.
419    TransactionIdsForBlock(Option<Arc<[transaction::Hash]>>),
420
421    /// Response to [`ReadRequest::AnyChainTransactionIdsForBlock`], with an list of
422    /// transaction hashes in block order and a flag indicating if the block is
423    /// in the best chain, or `None` if the block was not found.
424    AnyChainTransactionIdsForBlock(Option<(Arc<[transaction::Hash]>, bool)>),
425
426    /// Response to [`ReadRequest::SpendingTransactionId`],
427    /// with an list of transaction hashes in block order,
428    /// or `None` if the block was not found.
429    #[cfg(feature = "indexer")]
430    TransactionId(Option<transaction::Hash>),
431
432    /// Response to [`ReadRequest::BlockLocator`] with a block locator object.
433    BlockLocator(Vec<block::Hash>),
434
435    /// The response to a `FindBlockHashes` request.
436    BlockHashes(Vec<block::Hash>),
437
438    /// The response to a `FindBlockHeaders` request.
439    BlockHeaders(Vec<block::CountedHeader>),
440
441    /// The response to a `FindForkPoint` request.
442    /// Returns the height and hash of the fork point, or `None` if no locator entry is
443    /// on the best chain.
444    ForkPoint(Option<(block::Height, block::Hash)>),
445
446    /// The response to a `UnspentBestChainUtxo` request, from verified blocks in the
447    /// _best_ non-finalized chain, or the finalized chain.
448    UnspentBestChainUtxo(Option<transparent::Utxo>),
449
450    /// The response to an `AnyChainUtxo` request, from verified blocks in
451    /// _any_ non-finalized chain, or the finalized chain.
452    ///
453    /// This response is purely informational, there is no guarantee that
454    /// the UTXO remains unspent in the best chain.
455    AnyChainUtxo(Option<transparent::Utxo>),
456
457    /// Response to [`ReadRequest::SaplingTree`] with the specified Sapling note commitment tree.
458    SaplingTree(Option<Arc<sapling::tree::NoteCommitmentTree>>),
459
460    /// Response to [`ReadRequest::OrchardTree`] with the specified Orchard note commitment tree.
461    OrchardTree(Option<Arc<orchard::tree::NoteCommitmentTree>>),
462
463    /// Response to [`ReadRequest::IronwoodTree`] with the specified Ironwood note commitment tree.
464    IronwoodTree(Option<Arc<orchard::tree::NoteCommitmentTree>>),
465
466    /// Response to [`ReadRequest::SaplingSubtrees`] with the specified Sapling note commitment
467    /// subtrees.
468    SaplingSubtrees(
469        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling_crypto::Node>>,
470    ),
471
472    /// Response to [`ReadRequest::OrchardSubtrees`] with the specified Orchard note commitment
473    /// subtrees.
474    OrchardSubtrees(
475        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
476    ),
477
478    /// Response to [`ReadRequest::IronwoodSubtrees`] with the specified Ironwood note commitment
479    /// subtrees. Ironwood reuses the Orchard note type.
480    IronwoodSubtrees(
481        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
482    ),
483
484    /// Response to [`ReadRequest::AddressBalance`] with the total balance of the addresses,
485    /// and the total received funds, including change.
486    AddressBalance {
487        /// The total balance of the addresses.
488        balance: Amount<NonNegative>,
489        /// The total received funds in zatoshis, including change.
490        received: u64,
491    },
492
493    /// Response to [`ReadRequest::TransactionIdsByAddresses`]
494    /// with the obtained transaction ids, in the order they appear in blocks.
495    AddressesTransactionIds(BTreeMap<TransactionLocation, transaction::Hash>),
496
497    /// Response to [`ReadRequest::UtxosByAddresses`] with found utxos and transaction data.
498    AddressUtxos(AddressUtxos),
499
500    /// Response to [`ReadRequest::CheckBestChainTipNullifiersAndAnchors`].
501    ///
502    /// Does not check transparent UTXO inputs
503    ValidBestChainTipNullifiersAndAnchors,
504
505    /// Response to [`ReadRequest::BestChainNextMedianTimePast`].
506    /// Contains the median-time-past for the *next* block on the best chain.
507    BestChainNextMedianTimePast(DateTime32),
508
509    /// Response to [`ReadRequest::BestChainBlockHash`] with the specified block hash.
510    BlockHash(Option<block::Hash>),
511
512    /// Response to [`ReadRequest::ChainInfo`] with the state
513    /// information needed by the `getblocktemplate` RPC method.
514    ChainInfo(GetBlockTemplateChainInfo),
515
516    /// Response to [`ReadRequest::SolutionRate`]
517    SolutionRate(Option<u128>),
518
519    /// Response to [`ReadRequest::CheckBlockProposalValidity`]
520    ValidBlockProposal,
521
522    /// Response to [`ReadRequest::TipBlockSize`]
523    TipBlockSize(Option<usize>),
524
525    /// Response to [`ReadRequest::NonFinalizedBlocksListener`]
526    NonFinalizedBlocksListener(NonFinalizedBlocksListener),
527
528    /// Response to [`ReadRequest::IsTransparentOutputSpent`]
529    IsTransparentOutputSpent(bool),
530}
531
532/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
533#[derive(Clone, Debug, Eq, PartialEq)]
534pub struct GetBlockTemplateChainInfo {
535    // Data fetched directly from the state tip.
536    //
537    /// The current state tip height.
538    /// The block template for the candidate block has this hash as the previous block hash.
539    pub tip_hash: block::Hash,
540
541    /// The current state tip height.
542    /// The block template for the candidate block is the next block after this block.
543    /// Depends on the `tip_hash`.
544    pub tip_height: block::Height,
545
546    /// The FlyClient chain history root as of the end of the chain tip block.
547    /// Depends on the `tip_hash`.
548    pub chain_history_root: Option<ChainHistoryMmrRootHash>,
549
550    // Data derived from the state tip and recent blocks, and the current local clock.
551    //
552    /// The expected difficulty of the candidate block.
553    /// Depends on the `tip_hash`, and the local clock on testnet.
554    pub expected_difficulty: CompactDifficulty,
555
556    /// The current system time, adjusted to fit within `min_time` and `max_time`.
557    /// Always depends on the local clock and the `tip_hash`.
558    pub cur_time: DateTime32,
559
560    /// The mininimum time the miner can use in this block.
561    /// Depends on the `tip_hash`, and the local clock on testnet.
562    pub min_time: DateTime32,
563
564    /// The maximum time the miner can use in this block.
565    /// Depends on the `tip_hash`, and the local clock on testnet.
566    pub max_time: DateTime32,
567}
568
569/// Conversion from read-only [`ReadResponse`]s to read-write [`Response`]s.
570///
571/// Used to return read requests concurrently from the [`StateService`](crate::service::StateService).
572impl TryFrom<ReadResponse> for Response {
573    type Error = &'static str;
574
575    fn try_from(response: ReadResponse) -> Result<Response, Self::Error> {
576        match response {
577            ReadResponse::Tip(height_and_hash) => Ok(Response::Tip(height_and_hash)),
578            ReadResponse::Depth(depth) => Ok(Response::Depth(depth)),
579            ReadResponse::BestChainNextMedianTimePast(median_time_past) => Ok(Response::BestChainNextMedianTimePast(median_time_past)),
580            ReadResponse::BlockHash(hash) => Ok(Response::BlockHash(hash)),
581
582            ReadResponse::Block(block) => Ok(Response::Block(block)),
583            ReadResponse::BlockAndSize(block) => Ok(Response::BlockAndSize(block)),
584            ReadResponse::BlockHeader {
585                header,
586                hash,
587                height,
588                next_block_hash
589            } => Ok(Response::BlockHeader {
590                header,
591                hash,
592                height,
593                next_block_hash
594            }),
595            ReadResponse::Transaction(tx_info) => {
596                Ok(Response::Transaction(tx_info.map(|tx_info| tx_info.tx)))
597            }
598            ReadResponse::AnyChainTransaction(tx) => Ok(Response::AnyChainTransaction(tx)),
599            ReadResponse::UnspentBestChainUtxo(utxo) => Ok(Response::UnspentBestChainUtxo(utxo)),
600
601
602            ReadResponse::AnyChainUtxo(_) => Err("ReadService does not track pending UTXOs. \
603                                                  Manually unwrap the response, and handle pending UTXOs."),
604
605            ReadResponse::BlockLocator(hashes) => Ok(Response::BlockLocator(hashes)),
606            ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
607            ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),
608
609            ReadResponse::ValidBestChainTipNullifiersAndAnchors => Ok(Response::ValidBestChainTipNullifiersAndAnchors),
610
611            ReadResponse::UsageInfo(_)
612            | ReadResponse::TipPoolValues { .. }
613            | ReadResponse::BlockInfo(_)
614            | ReadResponse::TransactionIdsForBlock(_)
615            | ReadResponse::AnyChainTransactionIdsForBlock(_)
616            | ReadResponse::SaplingTree(_)
617            | ReadResponse::OrchardTree(_)
618            | ReadResponse::IronwoodTree(_)
619            | ReadResponse::SaplingSubtrees(_)
620            | ReadResponse::OrchardSubtrees(_)
621            | ReadResponse::IronwoodSubtrees(_)
622            | ReadResponse::AddressBalance { .. }
623            | ReadResponse::AddressesTransactionIds(_)
624            | ReadResponse::AddressUtxos(_)
625            | ReadResponse::ChainInfo(_)
626            | ReadResponse::NonFinalizedBlocksListener(_)
627            | ReadResponse::IsTransparentOutputSpent(_)
628            | ReadResponse::ForkPoint(_) => {
629                Err("there is no corresponding Response for this ReadResponse")
630            }
631
632            #[cfg(feature = "indexer")]
633            ReadResponse::TransactionId(_) => Err("there is no corresponding Response for this ReadResponse"),
634
635            ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
636
637            ReadResponse::SolutionRate(_) | ReadResponse::TipBlockSize(_) => {
638                Err("there is no corresponding Response for this ReadResponse")
639            }
640        }
641    }
642}