Skip to main content

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