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}