zebra_state/
response.rs

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