zebra_network/protocol/internal/response.rs
1//! Zebra's internal peer message response format.
2
3use std::{fmt, sync::Arc};
4
5use zebra_chain::{
6 block::{self, Block},
7 transaction::{UnminedTx, UnminedTxId},
8};
9
10use crate::{meta_addr::MetaAddr, protocol::internal::InventoryResponse, PeerSocketAddr};
11
12#[cfg(any(test, feature = "proptest-impl"))]
13use proptest_derive::Arbitrary;
14
15use InventoryResponse::*;
16
17/// A response to a network request, represented in internal format.
18#[derive(Clone, Debug, Eq, PartialEq)]
19#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
20pub enum Response {
21 /// The request does not have a response.
22 ///
23 /// Either:
24 /// * the request does not need a response, or
25 /// * we have no useful data to provide in response to the request,
26 /// and the request was not an inventory request.
27 ///
28 /// (Inventory requests provide a list of missing hashes if none of the hashes were available.)
29 Nil,
30
31 /// A list of peers, used to respond to `GetPeers`.
32 ///
33 /// The list contains `0..=MAX_META_ADDR` peers.
34 //
35 // TODO: make this into a HashMap<PeerSocketAddr, MetaAddr> - a unique list of peer addresses (#2244)
36 Peers(Vec<MetaAddr>),
37
38 /// An ordered list of block hashes.
39 ///
40 /// The list contains zero or more block hashes.
41 //
42 // TODO: make this into an IndexMap - an ordered unique list of hashes (#2244)
43 BlockHashes(Vec<block::Hash>),
44
45 /// An ordered list of block headers.
46 ///
47 /// The list contains zero or more block headers.
48 //
49 // TODO: make this into an IndexMap - an ordered unique list of headers (#2244)
50 BlockHeaders(Vec<block::CountedHeader>),
51
52 /// A list of unmined transaction IDs.
53 ///
54 /// v4 transactions use a legacy transaction ID, and
55 /// v5 transactions use a witnessed transaction ID.
56 ///
57 /// The list contains zero or more transaction IDs.
58 //
59 // TODO: make this into a HashSet - a unique list (#2244)
60 TransactionIds(Vec<UnminedTxId>),
61
62 /// A list of found blocks, and missing block hashes.
63 ///
64 /// Each list contains zero or more entries.
65 ///
66 /// When Zebra doesn't have a block or transaction, it always sends `notfound`.
67 /// `zcashd` sometimes sends no response, and sometimes sends `notfound`.
68 //
69 // TODO: make this into a HashMap<block::Hash, InventoryResponse<Arc<Block>, ()>> - a unique list (#2244)
70 Blocks(Vec<InventoryResponse<(Arc<Block>, Option<PeerSocketAddr>), block::Hash>>),
71
72 /// A list of found unmined transactions, and missing unmined transaction IDs.
73 ///
74 /// Each list contains zero or more entries.
75 //
76 // TODO: make this into a HashMap<UnminedTxId, InventoryResponse<UnminedTx, ()>> - a unique list (#2244)
77 Transactions(Vec<InventoryResponse<(UnminedTx, Option<PeerSocketAddr>), UnminedTxId>>),
78}
79
80impl fmt::Display for Response {
81 #[allow(clippy::unwrap_in_result)]
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 f.write_str(&match self {
84 Response::Nil => "Nil".to_string(),
85
86 Response::Peers(peers) => format!("Peers {{ peers: {} }}", peers.len()),
87
88 Response::BlockHashes(hashes) => format!("BlockHashes {{ hashes: {} }}", hashes.len()),
89 Response::BlockHeaders(headers) => {
90 format!("BlockHeaders {{ headers: {} }}", headers.len())
91 }
92 Response::TransactionIds(ids) => format!("TransactionIds {{ ids: {} }}", ids.len()),
93
94 // Display heights for single-block responses (which Zebra requests and expects)
95 Response::Blocks(blocks) if blocks.len() == 1 => {
96 match blocks.first().expect("len is 1") {
97 Available((block, _)) => format!(
98 "Block {{ height: {}, hash: {} }}",
99 block
100 .coinbase_height()
101 .as_ref()
102 .map(|h| h.0.to_string())
103 .unwrap_or_else(|| "None".into()),
104 block.hash(),
105 ),
106 Missing(hash) => format!("Block {{ missing: {hash} }}"),
107 }
108 }
109 Response::Blocks(blocks) => format!(
110 "Blocks {{ blocks: {}, missing: {} }}",
111 blocks.iter().filter(|r| r.is_available()).count(),
112 blocks.iter().filter(|r| r.is_missing()).count()
113 ),
114
115 Response::Transactions(transactions) => format!(
116 "Transactions {{ transactions: {}, missing: {} }}",
117 transactions.iter().filter(|r| r.is_available()).count(),
118 transactions.iter().filter(|r| r.is_missing()).count()
119 ),
120 })
121 }
122}
123
124impl Response {
125 /// Returns the Zebra internal response type as a string.
126 pub fn command(&self) -> &'static str {
127 match self {
128 Response::Nil => "Nil",
129
130 Response::Peers(_) => "Peers",
131
132 Response::BlockHashes(_) => "BlockHashes",
133 Response::BlockHeaders(_) => "BlockHeaders",
134 Response::TransactionIds(_) => "TransactionIds",
135
136 Response::Blocks(_) => "Blocks",
137 Response::Transactions(_) => "Transactions",
138 }
139 }
140
141 /// Returns true if the response is a block or transaction inventory download.
142 pub fn is_inventory_download(&self) -> bool {
143 matches!(self, Response::Blocks(_) | Response::Transactions(_))
144 }
145
146 /// Returns true if self is the [`Response::Nil`] variant.
147 #[allow(dead_code)]
148 pub fn is_nil(&self) -> bool {
149 matches!(self, Self::Nil)
150 }
151}