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}