zebra_network/protocol/external/inv.rs
1//! Inventory items for the Zcash network protocol.
2
3use std::{
4 cmp::min,
5 io::{Read, Write},
6};
7
8use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
9
10use zebra_chain::{
11 block,
12 serialization::{
13 ReadZcashExt, SerializationError, TrustedPreallocate, ZcashDeserialize,
14 ZcashDeserializeInto, ZcashSerialize,
15 },
16 transaction::{
17 self,
18 UnminedTxId::{self, *},
19 WtxId,
20 },
21};
22
23use super::MAX_PROTOCOL_MESSAGE_LEN;
24
25/// An inventory hash which refers to some advertised or requested data.
26///
27/// Bitcoin calls this an "inventory vector" but it is just a typed hash, not a
28/// container, so we do not use that term to avoid confusion with `Vec<T>`.
29///
30/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Inventory_Vectors)
31#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
32pub enum InventoryHash {
33 /// An error.
34 ///
35 /// The Bitcoin wiki just says "Any data of with this number may be ignored",
36 /// so we don't include a typed hash.
37 Error,
38 /// A hash of a transaction.
39 Tx(transaction::Hash),
40 /// A hash of a block.
41 Block(block::Hash),
42 /// A hash of a filtered block.
43 ///
44 /// The Bitcoin wiki says: Hash of a block header, but only to be used in
45 /// getdata message. Indicates the reply should be a merkleblock message
46 /// rather than a block message; this only works if a bloom filter has been
47 /// set.
48 FilteredBlock(block::Hash),
49 /// A pair with the hash of a V5 transaction and the [Authorizing Data Commitment][auth_digest].
50 ///
51 /// Introduced by [ZIP-239][zip239], which is analogous to Bitcoin's [BIP-339][bip339].
52 ///
53 /// [auth_digest]: https://zips.z.cash/zip-0244#authorizing-data-commitment
54 /// [zip239]: https://zips.z.cash/zip-0239
55 /// [bip339]: https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki
56 Wtx(transaction::WtxId),
57}
58
59impl InventoryHash {
60 /// Creates a new inventory hash from a legacy transaction ID.
61 ///
62 /// # Correctness
63 ///
64 /// This method must only be used for v1-v4 transaction IDs.
65 /// [`transaction::Hash`] does not uniquely identify unmined v5 transactions.
66 #[allow(dead_code)]
67 pub fn from_legacy_tx_id(legacy_tx_id: transaction::Hash) -> InventoryHash {
68 InventoryHash::Tx(legacy_tx_id)
69 }
70
71 /// Returns the block hash for this inventory hash,
72 /// if this inventory hash is a non-filtered block variant.
73 pub fn block_hash(&self) -> Option<block::Hash> {
74 match self {
75 InventoryHash::Error => None,
76 InventoryHash::Tx(_legacy_tx_id) => None,
77 InventoryHash::Block(hash) => Some(*hash),
78 // Zebra does not support filtered blocks
79 InventoryHash::FilteredBlock(_ignored_hash) => None,
80 InventoryHash::Wtx(_wtx_id) => None,
81 }
82 }
83
84 /// Returns the unmined transaction ID for this inventory hash,
85 /// if this inventory hash is a transaction variant.
86 pub fn unmined_tx_id(&self) -> Option<UnminedTxId> {
87 match self {
88 InventoryHash::Error => None,
89 InventoryHash::Tx(legacy_tx_id) => Some(UnminedTxId::from_legacy_id(*legacy_tx_id)),
90 InventoryHash::Block(_hash) => None,
91 InventoryHash::FilteredBlock(_hash) => None,
92 InventoryHash::Wtx(wtx_id) => Some(UnminedTxId::from(wtx_id)),
93 }
94 }
95
96 /// Returns the serialized Zcash network protocol code for the current variant.
97 fn code(&self) -> u32 {
98 match self {
99 InventoryHash::Error => 0,
100 InventoryHash::Tx(_tx_id) => 1,
101 InventoryHash::Block(_hash) => 2,
102 InventoryHash::FilteredBlock(_hash) => 3,
103 InventoryHash::Wtx(_wtx_id) => 5,
104 }
105 }
106}
107
108impl From<WtxId> for InventoryHash {
109 fn from(wtx_id: WtxId) -> InventoryHash {
110 InventoryHash::Wtx(wtx_id)
111 }
112}
113
114impl From<&WtxId> for InventoryHash {
115 fn from(wtx_id: &WtxId) -> InventoryHash {
116 InventoryHash::from(*wtx_id)
117 }
118}
119
120impl From<UnminedTxId> for InventoryHash {
121 fn from(tx_id: UnminedTxId) -> InventoryHash {
122 match tx_id {
123 Legacy(hash) => InventoryHash::Tx(hash),
124 Witnessed(wtx_id) => InventoryHash::Wtx(wtx_id),
125 }
126 }
127}
128
129impl From<&UnminedTxId> for InventoryHash {
130 fn from(tx_id: &UnminedTxId) -> InventoryHash {
131 InventoryHash::from(*tx_id)
132 }
133}
134
135impl From<block::Hash> for InventoryHash {
136 fn from(hash: block::Hash) -> InventoryHash {
137 // Auto-convert to Block rather than FilteredBlock because filtered
138 // blocks aren't useful for Zcash.
139 InventoryHash::Block(hash)
140 }
141}
142
143impl ZcashSerialize for InventoryHash {
144 fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
145 writer.write_u32::<LittleEndian>(self.code())?;
146 match self {
147 // Zebra does not supply error codes
148 InventoryHash::Error => writer.write_all(&[0; 32]),
149 InventoryHash::Tx(tx_id) => tx_id.zcash_serialize(writer),
150 InventoryHash::Block(hash) => hash.zcash_serialize(writer),
151 InventoryHash::FilteredBlock(hash) => hash.zcash_serialize(writer),
152 InventoryHash::Wtx(wtx_id) => wtx_id.zcash_serialize(writer),
153 }
154 }
155}
156
157impl ZcashDeserialize for InventoryHash {
158 fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
159 let code = reader.read_u32::<LittleEndian>()?;
160 match code {
161 0 => {
162 // ignore the standard 32-byte error code
163 let _bytes = reader.read_32_bytes()?;
164 Ok(InventoryHash::Error)
165 }
166
167 1 => Ok(InventoryHash::Tx(reader.zcash_deserialize_into()?)),
168 2 => Ok(InventoryHash::Block(reader.zcash_deserialize_into()?)),
169 3 => Ok(InventoryHash::FilteredBlock(
170 reader.zcash_deserialize_into()?,
171 )),
172 5 => Ok(InventoryHash::Wtx(reader.zcash_deserialize_into()?)),
173
174 _ => Err(SerializationError::Parse("invalid inventory code")),
175 }
176 }
177}
178
179/// The minimum serialized size of an [`InventoryHash`].
180pub(crate) const MIN_INV_HASH_SIZE: usize = 36;
181
182/// The maximum number of inventory items in a network message received from a peer.
183///
184/// After [ZIP-239](https://zips.z.cash/zip-0239#deployment), this would allow a message filled
185/// with `MSG_WTX` entries to be around 3.4 MB, so we also need a separate constant to limit the
186/// number of `inv` entries that we send.
187///
188/// Same as `MAX_INV_SZ` in `zcashd`:
189/// <https://github.com/zcash/zcash/blob/adfc7218435faa1c8985a727f997a795dcffa0c7/src/net.h#L50>
190pub const MAX_INV_IN_RECEIVED_MESSAGE: u64 = 50_000;
191
192/// The maximum number of transaction inventory items in a network message received from a peer.
193///
194/// After [ZIP-239](https://zips.z.cash/zip-0239#deployment), this would allow a message filled
195/// with `MSG_WTX` entries to be around 3.4 MB, so we also need a separate constant to limit the
196/// number of `inv` entries that we send.
197///
198/// This constant is not critical to compatibility: it just needs to be less than or equal to
199/// `zcashd`'s `MAX_INV_SZ`:
200/// <https://github.com/zcash/zcash/blob/adfc7218435faa1c8985a727f997a795dcffa0c7/src/net.h#L50>
201pub const MAX_TX_INV_IN_SENT_MESSAGE: u64 = 25_000;
202
203impl TrustedPreallocate for InventoryHash {
204 fn max_allocation() -> u64 {
205 // An Inventory hash takes at least 36 bytes, and we reserve at least one byte for the
206 // Vector length so we can never receive more than ((MAX_PROTOCOL_MESSAGE_LEN - 1) / 36) in
207 // a single message
208 let message_size_limit = ((MAX_PROTOCOL_MESSAGE_LEN - 1) / MIN_INV_HASH_SIZE) as u64;
209
210 min(message_size_limit, MAX_INV_IN_RECEIVED_MESSAGE)
211 }
212}