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}