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}