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
187impl MinedTx {
188    /// Creates a new [`MinedTx`]
189    pub fn new(
190        tx: Arc<Transaction>,
191        height: block::Height,
192        confirmations: u32,
193        block_time: DateTime<Utc>,
194    ) -> Self {
195        Self {
196            tx,
197            height,
198            confirmations,
199            block_time,
200        }
201    }
202}
203
204/// How many non-finalized block references to buffer in [`NonFinalizedBlocksListener`] before blocking sends.
205///
206/// # Correctness
207///
208/// This should be large enough to typically avoid blocking the sender when the non-finalized state is full so
209/// that the [`NonFinalizedBlocksListener`] reliably receives updates whenever the non-finalized state changes.
210///
211/// If the buffer does fill, sends apply backpressure (the sender awaits a free slot) rather than
212/// dropping blocks, so the listener still receives every block once the consumer catches up.
213// `MAX_BLOCK_REORG_HEIGHT` is a small `u32` constant (the reorg limit), so widening it to `usize`
214// and doubling it cannot overflow on any supported platform.
215const NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE: usize = 2 * MAX_BLOCK_REORG_HEIGHT as usize;
216
217/// A listener for changes in the non-finalized state.
218#[derive(Clone, Debug)]
219pub struct NonFinalizedBlocksListener(
220    pub Arc<tokio::sync::mpsc::Receiver<(zebra_chain::block::Hash, Arc<zebra_chain::block::Block>)>>,
221);
222
223impl NonFinalizedBlocksListener {
224    /// Sends the blocks in `non_finalized_state` that satisfy `take_cond` to `sender`, in
225    /// ascending height order.
226    ///
227    /// Walks each chain from its tip downwards, taking blocks while `take_cond` holds and stopping
228    /// at the first block that fails it, so it sends the blocks a listener hasn't been sent yet by
229    /// stopping at the first block it already has.
230    ///
231    /// Returns an error if the receiver has been dropped.
232    async fn take_and_send_blocks<'a>(
233        sender: &tokio::sync::mpsc::Sender<(block::Hash, Arc<Block>)>,
234        non_finalized_state: &'a NonFinalizedState,
235        take_cond: impl Fn(&&ContextuallyVerifiedBlock) -> bool + Copy + 'a,
236    ) -> Result<(), tokio::sync::mpsc::error::SendError<(block::Hash, Arc<Block>)>> {
237        let new_blocks = non_finalized_state
238            .chain_iter()
239            .flat_map(move |chain| {
240                // Take blocks from the chain in reverse height order until we reach a block the
241                // listener already has, then restore ascending height order.
242                let mut blocks: Vec<_> =
243                    chain.blocks.values().rev().take_while(take_cond).collect();
244                blocks.reverse();
245                blocks
246            })
247            .map(|cv_block| (cv_block.hash, cv_block.block.clone()));
248
249        for new_block_with_hash in new_blocks {
250            sender.send(new_block_with_hash).await?;
251        }
252
253        Ok(())
254    }
255
256    /// Spawns a task to listen for changes in the non-finalized state and sends any blocks in the non-finalized state
257    /// to the caller that have not already been sent.
258    ///
259    /// `known_chain_tips` holds the hashes of chain tips the caller already has. On the first send
260    /// only, each non-finalized chain is walked from its tip downwards and blocks are sent until a
261    /// hash in this set is reached, so any block at or below a known tip on the same chain is
262    /// skipped. If it is empty, every block currently in the non-finalized state is sent. After the
263    /// first send, those blocks are already tracked as sent, so later sends only forward blocks that
264    /// weren't in the previously seen non-finalized state.
265    ///
266    /// Returns a new instance of [`NonFinalizedBlocksListener`] for the caller to listen for new blocks in the non-finalized state.
267    pub fn spawn(
268        mut non_finalized_state_receiver: WatchReceiver<NonFinalizedState>,
269        known_chain_tips: HashSet<block::Hash>,
270    ) -> Self {
271        let (sender, receiver) = tokio::sync::mpsc::channel(NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE);
272
273        tokio::spawn(async move {
274            // `prev_non_finalized_state` starts as the current non-finalized state. The first send
275            // below skips blocks at or below the caller's known chain tips; afterwards those blocks
276            // are already in `prev_non_finalized_state`, so later sends only need to check it.
277            let mut prev_non_finalized_state = non_finalized_state_receiver.cloned_watch_data();
278
279            // Send the blocks the caller is missing relative to its known chain tips. This checks
280            // `known_chain_tips` once; from here on those blocks are covered by the state check.
281            if Self::take_and_send_blocks(&sender, &prev_non_finalized_state, |b| {
282                !known_chain_tips.contains(&b.hash)
283            })
284            .await
285            .is_err()
286            {
287                tracing::debug!("non-finalized blocks receiver closed, ending task");
288                return;
289            }
290
291            // # Correctness
292            //
293            // This loop should check that the non-finalized state receiver has changed sooner
294            // than the non-finalized state could possibly have changed to avoid missing updates, so
295            // the logic here should be quicker than the contextual verification logic that precedes
296            // commits to the non-finalized state.
297            //
298            // See the `NON_FINALIZED_STATE_CHANGE_BUFFER_SIZE` documentation for more details.
299            loop {
300                let non_finalized_state = non_finalized_state_receiver.cloned_watch_data();
301
302                // Send blocks that weren't in the last seen copy of the non-finalized state. The
303                // caller's known tips are already covered by `prev_non_finalized_state`.
304                if Self::take_and_send_blocks(&sender, &non_finalized_state, |b| {
305                    !prev_non_finalized_state.any_chain_contains(&b.hash)
306                })
307                .await
308                .is_err()
309                {
310                    tracing::debug!("non-finalized blocks receiver closed, ending task");
311                    return;
312                }
313
314                prev_non_finalized_state = non_finalized_state;
315
316                // Wait for the next update to the non-finalized state.
317                if let Err(error) = non_finalized_state_receiver.changed().await {
318                    warn!(
319                        ?error,
320                        "non-finalized state receiver closed, is Zebra shutting down?"
321                    );
322                    break;
323                }
324            }
325        });
326
327        Self(Arc::new(receiver))
328    }
329
330    /// Consumes `self`, unwrapping the inner [`Arc`] and returning the non-finalized state change channel receiver.
331    ///
332    /// # Panics
333    ///
334    /// If the `Arc` has more than one strong reference, this will panic.
335    pub fn unwrap(
336        self,
337    ) -> tokio::sync::mpsc::Receiver<(zebra_chain::block::Hash, Arc<zebra_chain::block::Block>)>
338    {
339        Arc::try_unwrap(self.0).unwrap()
340    }
341}
342
343impl PartialEq for NonFinalizedBlocksListener {
344    fn eq(&self, other: &Self) -> bool {
345        Arc::ptr_eq(&self.0, &other.0)
346    }
347}
348
349impl Eq for NonFinalizedBlocksListener {}
350
351#[derive(Clone, Debug, PartialEq, Eq)]
352/// A response to a read-only
353/// [`ReadStateService`](crate::service::ReadStateService)'s [`ReadRequest`].
354pub enum ReadResponse {
355    /// Response to [`ReadRequest::UsageInfo`] with the current best chain tip.
356    UsageInfo(u64),
357
358    /// Response to [`ReadRequest::Tip`] with the current best chain tip.
359    Tip(Option<(block::Height, block::Hash)>),
360
361    /// Response to [`ReadRequest::TipPoolValues`] with
362    /// the current best chain tip and its [`ValueBalance`].
363    TipPoolValues {
364        /// The current best chain tip height.
365        tip_height: block::Height,
366        /// The current best chain tip hash.
367        tip_hash: block::Hash,
368        /// The value pool balance at the current best chain tip.
369        value_balance: ValueBalance<NonNegative>,
370    },
371
372    /// Response to [`ReadRequest::BlockInfo`] with
373    /// the block info after the specified block.
374    BlockInfo(Option<BlockInfo>),
375
376    /// Response to [`ReadRequest::Depth`] with the depth of the specified block.
377    Depth(Option<u32>),
378
379    /// Response to [`ReadRequest::Block`] with the specified block.
380    Block(Option<Arc<Block>>),
381
382    /// Response to [`ReadRequest::BlockAndSize`] with the specified block and
383    /// serialized size.
384    BlockAndSize(Option<(Arc<Block>, usize)>),
385
386    /// The response to a `BlockHeader` request.
387    BlockHeader {
388        /// The header of the requested block
389        header: Arc<block::Header>,
390        /// The hash of the requested block
391        hash: block::Hash,
392        /// The height of the requested block
393        height: block::Height,
394        /// The hash of the next block after the requested block
395        next_block_hash: Option<block::Hash>,
396    },
397
398    /// Response to [`ReadRequest::Transaction`] with the specified transaction.
399    Transaction(Option<MinedTx>),
400
401    /// Response to [`Request::Transaction`] with the specified transaction.
402    AnyChainTransaction(Option<AnyTx>),
403
404    /// Response to [`ReadRequest::TransactionIdsForBlock`],
405    /// with an list of transaction hashes in block order,
406    /// or `None` if the block was not found.
407    TransactionIdsForBlock(Option<Arc<[transaction::Hash]>>),
408
409    /// Response to [`ReadRequest::AnyChainTransactionIdsForBlock`], with an list of
410    /// transaction hashes in block order and a flag indicating if the block is
411    /// in the best chain, or `None` if the block was not found.
412    AnyChainTransactionIdsForBlock(Option<(Arc<[transaction::Hash]>, bool)>),
413
414    /// Response to [`ReadRequest::SpendingTransactionId`],
415    /// with an list of transaction hashes in block order,
416    /// or `None` if the block was not found.
417    #[cfg(feature = "indexer")]
418    TransactionId(Option<transaction::Hash>),
419
420    /// Response to [`ReadRequest::BlockLocator`] with a block locator object.
421    BlockLocator(Vec<block::Hash>),
422
423    /// The response to a `FindBlockHashes` request.
424    BlockHashes(Vec<block::Hash>),
425
426    /// The response to a `FindBlockHeaders` request.
427    BlockHeaders(Vec<block::CountedHeader>),
428
429    /// The response to a `FindForkPoint` request.
430    /// Returns the height and hash of the fork point, or `None` if no locator entry is
431    /// on the best chain.
432    ForkPoint(Option<(block::Height, block::Hash)>),
433
434    /// The response to a `UnspentBestChainUtxo` request, from verified blocks in the
435    /// _best_ non-finalized chain, or the finalized chain.
436    UnspentBestChainUtxo(Option<transparent::Utxo>),
437
438    /// The response to an `AnyChainUtxo` request, from verified blocks in
439    /// _any_ non-finalized chain, or the finalized chain.
440    ///
441    /// This response is purely informational, there is no guarantee that
442    /// the UTXO remains unspent in the best chain.
443    AnyChainUtxo(Option<transparent::Utxo>),
444
445    /// Response to [`ReadRequest::SaplingTree`] with the specified Sapling note commitment tree.
446    SaplingTree(Option<Arc<sapling::tree::NoteCommitmentTree>>),
447
448    /// Response to [`ReadRequest::OrchardTree`] with the specified Orchard note commitment tree.
449    OrchardTree(Option<Arc<orchard::tree::NoteCommitmentTree>>),
450
451    /// Response to [`ReadRequest::IronwoodTree`] with the specified Ironwood note commitment tree.
452    IronwoodTree(Option<Arc<orchard::tree::NoteCommitmentTree>>),
453
454    /// Response to [`ReadRequest::SaplingSubtrees`] with the specified Sapling note commitment
455    /// subtrees.
456    SaplingSubtrees(
457        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling_crypto::Node>>,
458    ),
459
460    /// Response to [`ReadRequest::OrchardSubtrees`] with the specified Orchard note commitment
461    /// subtrees.
462    OrchardSubtrees(
463        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
464    ),
465
466    /// Response to [`ReadRequest::IronwoodSubtrees`] with the specified Ironwood note commitment
467    /// subtrees. Ironwood reuses the Orchard note type.
468    IronwoodSubtrees(
469        BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>,
470    ),
471
472    /// Response to [`ReadRequest::AddressBalance`] with the total balance of the addresses,
473    /// and the total received funds, including change.
474    AddressBalance {
475        /// The total balance of the addresses.
476        balance: Amount<NonNegative>,
477        /// The total received funds in zatoshis, including change.
478        received: u64,
479    },
480
481    /// Response to [`ReadRequest::TransactionIdsByAddresses`]
482    /// with the obtained transaction ids, in the order they appear in blocks.
483    AddressesTransactionIds(BTreeMap<TransactionLocation, transaction::Hash>),
484
485    /// Response to [`ReadRequest::UtxosByAddresses`] with found utxos and transaction data.
486    AddressUtxos(AddressUtxos),
487
488    /// Response to [`ReadRequest::CheckBestChainTipNullifiersAndAnchors`].
489    ///
490    /// Does not check transparent UTXO inputs
491    ValidBestChainTipNullifiersAndAnchors,
492
493    /// Response to [`ReadRequest::BestChainNextMedianTimePast`].
494    /// Contains the median-time-past for the *next* block on the best chain.
495    BestChainNextMedianTimePast(DateTime32),
496
497    /// Response to [`ReadRequest::BestChainBlockHash`] with the specified block hash.
498    BlockHash(Option<block::Hash>),
499
500    /// Response to [`ReadRequest::ChainInfo`] with the state
501    /// information needed by the `getblocktemplate` RPC method.
502    ChainInfo(GetBlockTemplateChainInfo),
503
504    /// Response to [`ReadRequest::SolutionRate`]
505    SolutionRate(Option<u128>),
506
507    /// Response to [`ReadRequest::CheckBlockProposalValidity`]
508    ValidBlockProposal,
509
510    /// Response to [`ReadRequest::TipBlockSize`]
511    TipBlockSize(Option<usize>),
512
513    /// Response to [`ReadRequest::NonFinalizedBlocksListener`]
514    NonFinalizedBlocksListener(NonFinalizedBlocksListener),
515
516    /// Response to [`ReadRequest::IsTransparentOutputSpent`]
517    IsTransparentOutputSpent(bool),
518}
519
520/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
521#[derive(Clone, Debug, Eq, PartialEq)]
522pub struct GetBlockTemplateChainInfo {
523    // Data fetched directly from the state tip.
524    //
525    /// The current state tip height.
526    /// The block template for the candidate block has this hash as the previous block hash.
527    pub tip_hash: block::Hash,
528
529    /// The current state tip height.
530    /// The block template for the candidate block is the next block after this block.
531    /// Depends on the `tip_hash`.
532    pub tip_height: block::Height,
533
534    /// The FlyClient chain history root as of the end of the chain tip block.
535    /// Depends on the `tip_hash`.
536    pub chain_history_root: Option<ChainHistoryMmrRootHash>,
537
538    // Data derived from the state tip and recent blocks, and the current local clock.
539    //
540    /// The expected difficulty of the candidate block.
541    /// Depends on the `tip_hash`, and the local clock on testnet.
542    pub expected_difficulty: CompactDifficulty,
543
544    /// The current system time, adjusted to fit within `min_time` and `max_time`.
545    /// Always depends on the local clock and the `tip_hash`.
546    pub cur_time: DateTime32,
547
548    /// The mininimum time the miner can use in this block.
549    /// Depends on the `tip_hash`, and the local clock on testnet.
550    pub min_time: DateTime32,
551
552    /// The maximum time the miner can use in this block.
553    /// Depends on the `tip_hash`, and the local clock on testnet.
554    pub max_time: DateTime32,
555}
556
557/// Conversion from read-only [`ReadResponse`]s to read-write [`Response`]s.
558///
559/// Used to return read requests concurrently from the [`StateService`](crate::service::StateService).
560impl TryFrom<ReadResponse> for Response {
561    type Error = &'static str;
562
563    fn try_from(response: ReadResponse) -> Result<Response, Self::Error> {
564        match response {
565            ReadResponse::Tip(height_and_hash) => Ok(Response::Tip(height_and_hash)),
566            ReadResponse::Depth(depth) => Ok(Response::Depth(depth)),
567            ReadResponse::BestChainNextMedianTimePast(median_time_past) => Ok(Response::BestChainNextMedianTimePast(median_time_past)),
568            ReadResponse::BlockHash(hash) => Ok(Response::BlockHash(hash)),
569
570            ReadResponse::Block(block) => Ok(Response::Block(block)),
571            ReadResponse::BlockAndSize(block) => Ok(Response::BlockAndSize(block)),
572            ReadResponse::BlockHeader {
573                header,
574                hash,
575                height,
576                next_block_hash
577            } => Ok(Response::BlockHeader {
578                header,
579                hash,
580                height,
581                next_block_hash
582            }),
583            ReadResponse::Transaction(tx_info) => {
584                Ok(Response::Transaction(tx_info.map(|tx_info| tx_info.tx)))
585            }
586            ReadResponse::AnyChainTransaction(tx) => Ok(Response::AnyChainTransaction(tx)),
587            ReadResponse::UnspentBestChainUtxo(utxo) => Ok(Response::UnspentBestChainUtxo(utxo)),
588
589
590            ReadResponse::AnyChainUtxo(_) => Err("ReadService does not track pending UTXOs. \
591                                                  Manually unwrap the response, and handle pending UTXOs."),
592
593            ReadResponse::BlockLocator(hashes) => Ok(Response::BlockLocator(hashes)),
594            ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
595            ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),
596
597            ReadResponse::ValidBestChainTipNullifiersAndAnchors => Ok(Response::ValidBestChainTipNullifiersAndAnchors),
598
599            ReadResponse::UsageInfo(_)
600            | ReadResponse::TipPoolValues { .. }
601            | ReadResponse::BlockInfo(_)
602            | ReadResponse::TransactionIdsForBlock(_)
603            | ReadResponse::AnyChainTransactionIdsForBlock(_)
604            | ReadResponse::SaplingTree(_)
605            | ReadResponse::OrchardTree(_)
606            | ReadResponse::IronwoodTree(_)
607            | ReadResponse::SaplingSubtrees(_)
608            | ReadResponse::OrchardSubtrees(_)
609            | ReadResponse::IronwoodSubtrees(_)
610            | ReadResponse::AddressBalance { .. }
611            | ReadResponse::AddressesTransactionIds(_)
612            | ReadResponse::AddressUtxos(_)
613            | ReadResponse::ChainInfo(_)
614            | ReadResponse::NonFinalizedBlocksListener(_)
615            | ReadResponse::IsTransparentOutputSpent(_)
616            | ReadResponse::ForkPoint(_) => {
617                Err("there is no corresponding Response for this ReadResponse")
618            }
619
620            #[cfg(feature = "indexer")]
621            ReadResponse::TransactionId(_) => Err("there is no corresponding Response for this ReadResponse"),
622
623            ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
624
625            ReadResponse::SolutionRate(_) | ReadResponse::TipBlockSize(_) => {
626                Err("there is no corresponding Response for this ReadResponse")
627            }
628        }
629    }
630}