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