zebra/zebra-network/src/protocol/internal/response.rs

146 lines
5.3 KiB
Rust

//! Zebra's internal peer message response format.
use std::{fmt, sync::Arc};
use zebra_chain::{
block::{self, Block},
transaction::{UnminedTx, UnminedTxId},
};
use crate::{meta_addr::MetaAddr, protocol::internal::InventoryResponse};
#[cfg(any(test, feature = "proptest-impl"))]
use proptest_derive::Arbitrary;
use InventoryResponse::*;
/// A response to a network request, represented in internal format.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub enum Response {
/// The request does not have a response.
///
/// Either:
/// * the request does not need a response, or
/// * we have no useful data to provide in response to the request,
/// and the request was not an inventory request.
///
/// (Inventory requests provide a list of missing hashes if none of the hashes were available.)
Nil,
/// A list of peers, used to respond to `GetPeers`.
///
/// The list contains `0..=MAX_META_ADDR` peers.
//
// TODO: make this into a HashMap<SocketAddr, MetaAddr> - a unique list of peer addresses (#2244)
Peers(Vec<MetaAddr>),
/// An ordered list of block hashes.
///
/// The list contains zero or more block hashes.
//
// TODO: make this into an IndexMap - an ordered unique list of hashes (#2244)
BlockHashes(Vec<block::Hash>),
/// An ordered list of block headers.
///
/// The list contains zero or more block headers.
//
// TODO: make this into an IndexMap - an ordered unique list of headers (#2244)
BlockHeaders(Vec<block::CountedHeader>),
/// A list of unmined transaction IDs.
///
/// v4 transactions use a legacy transaction ID, and
/// v5 transactions use a witnessed transaction ID.
///
/// The list contains zero or more transaction IDs.
//
// TODO: make this into a HashSet - a unique list (#2244)
TransactionIds(Vec<UnminedTxId>),
/// A list of found blocks, and missing block hashes.
///
/// Each list contains zero or more entries.
///
/// When Zebra doesn't have a block or transaction, it always sends `notfound`.
/// `zcashd` sometimes sends no response, and sometimes sends `notfound`.
//
// TODO: make this into a HashMap<block::Hash, InventoryResponse<Arc<Block>, ()>> - a unique list (#2244)
Blocks(Vec<InventoryResponse<Arc<Block>, block::Hash>>),
/// A list of found unmined transactions, and missing unmined transaction IDs.
///
/// Each list contains zero or more entries.
//
// TODO: make this into a HashMap<UnminedTxId, InventoryResponse<UnminedTx, ()>> - a unique list (#2244)
Transactions(Vec<InventoryResponse<UnminedTx, UnminedTxId>>),
}
impl fmt::Display for Response {
#[allow(clippy::unwrap_in_result)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&match self {
Response::Nil => "Nil".to_string(),
Response::Peers(peers) => format!("Peers {{ peers: {} }}", peers.len()),
Response::BlockHashes(hashes) => format!("BlockHashes {{ hashes: {} }}", hashes.len()),
Response::BlockHeaders(headers) => {
format!("BlockHeaders {{ headers: {} }}", headers.len())
}
Response::TransactionIds(ids) => format!("TransactionIds {{ ids: {} }}", ids.len()),
// Display heights for single-block responses (which Zebra requests and expects)
Response::Blocks(blocks) if blocks.len() == 1 => {
match blocks.first().expect("len is 1") {
Available(block) => format!(
"Block {{ height: {}, hash: {} }}",
block
.coinbase_height()
.as_ref()
.map(|h| h.0.to_string())
.unwrap_or_else(|| "None".into()),
block.hash(),
),
Missing(hash) => format!("Block {{ missing: {} }}", hash),
}
}
Response::Blocks(blocks) => format!(
"Blocks {{ blocks: {}, missing: {} }}",
blocks.iter().filter(|r| r.is_available()).count(),
blocks.iter().filter(|r| r.is_missing()).count()
),
Response::Transactions(transactions) => format!(
"Transactions {{ transactions: {}, missing: {} }}",
transactions.iter().filter(|r| r.is_available()).count(),
transactions.iter().filter(|r| r.is_missing()).count()
),
})
}
}
impl Response {
/// Returns the Zebra internal response type as a string.
pub fn command(&self) -> &'static str {
match self {
Response::Nil => "Nil",
Response::Peers(_) => "Peers",
Response::BlockHashes(_) => "BlockHashes",
Response::BlockHeaders(_) => "BlockHeaders",
Response::TransactionIds(_) => "TransactionIds",
Response::Blocks(_) => "Blocks",
Response::Transactions(_) => "Transactions",
}
}
/// Returns true if the response is a block or transaction inventory download.
pub fn is_inventory_download(&self) -> bool {
matches!(self, Response::Blocks(_) | Response::Transactions(_))
}
}