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}