diff --git a/zebra-chain/src/types.rs b/zebra-chain/src/types.rs index 43be396c..ae26c4e9 100644 --- a/zebra-chain/src/types.rs +++ b/zebra-chain/src/types.rs @@ -19,34 +19,6 @@ impl<'a> From<&'a [u8]> for Sha256dChecksum { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct BlockHeight(pub u32); -/// InventoryType -/// -/// [Bitcoin·reference](https://en.bitcoin.it/wiki/Protocol_documentation#Inventory_Vectors) -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(u8)] -pub enum InventoryType { - /// Any data of with this number may be ignored. - Error = 0x00, - - /// Hash is related to a transaction. - MsgTx = 0x01, - - /// Hash is related to a data block. - MsgBlock = 0x02, - - /// Hash of a block header, but only to be used in getdata - /// message. Indicates the reply should be a merkleblock message - /// rather than a block message; this only works if a bloom filter - /// has been set. - // XXX: Since we don't intend to include the bloom filter to - // start, do we need this? - MsgFilteredBlock = 0x03, -} - -/// Inventory Vector -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct InventoryVector(pub InventoryType, pub [u8; 32]); - #[cfg(test)] mod tests { use super::*; diff --git a/zebra-network/src/protocol.rs b/zebra-network/src/protocol.rs index f86d644e..24cbfb04 100644 --- a/zebra-network/src/protocol.rs +++ b/zebra-network/src/protocol.rs @@ -3,3 +3,5 @@ pub mod codec; pub mod message; pub mod types; + +pub mod inv; diff --git a/zebra-network/src/protocol/codec.rs b/zebra-network/src/protocol/codec.rs index 6071ec3b..d5c5df88 100644 --- a/zebra-network/src/protocol/codec.rs +++ b/zebra-network/src/protocol/codec.rs @@ -9,7 +9,7 @@ use failure::Error; use tokio::codec::{Decoder, Encoder}; use zebra_chain::{ - serialization::{ReadZcashExt, WriteZcashExt, ZcashSerialize}, + serialization::{ReadZcashExt, WriteZcashExt, ZcashDeserialize, ZcashSerialize}, types::{BlockHeight, Sha256dChecksum}, }; @@ -197,6 +197,12 @@ impl Codec { Pong(nonce) => { writer.write_u64::(nonce.0)?; } + Inv(ref hashes) => { + writer.write_compactsize(hashes.len() as u64)?; + for hash in hashes { + hash.zcash_serialize(&mut writer)?; + } + } Block { ref block } => { block .zcash_serialize(&mut writer) @@ -395,9 +401,28 @@ impl Codec { bail!("unimplemented message type") } - fn read_inv(&self, mut _reader: R) -> Result { - trace!("inv"); - bail!("unimplemented message type") + fn read_inv(&self, mut reader: R) -> Result { + use super::inv::InventoryHash; + + let count = reader.read_compactsize()? as usize; + // Preallocate a buffer, performing a single allocation in the honest + // case. Although the size of the recieved data buffer is bounded by the + // codec's max_len field, it's still possible for someone to send a + // short message with a large count field, so if we naively trust + // the count field we could be tricked into preallocating a large + // buffer. Instead, calculate the maximum count for a valid message from + // the codec's max_len using ENCODED_INVHASH_SIZE. + // + // encoding: 4 byte type tag + 32 byte hash + const ENCODED_INVHASH_SIZE: usize = 4 + 32; + let max_count = self.builder.max_len / ENCODED_INVHASH_SIZE; + let mut hashes = Vec::with_capacity(std::cmp::min(count, max_count)); + + for _ in 0..count { + hashes.push(InventoryHash::zcash_deserialize(&mut reader)?); + } + + Ok(Message::Inventory(hashes)) } fn read_getdata(&self, mut _reader: R) -> Result { diff --git a/zebra-network/src/protocol/inv.rs b/zebra-network/src/protocol/inv.rs new file mode 100644 index 00000000..65d46acb --- /dev/null +++ b/zebra-network/src/protocol/inv.rs @@ -0,0 +1,74 @@ +//! Inventory items for the Bitcoin protocol. + +// XXX the exact optimal arrangement of all of these parts is a little unclear +// until we have more pieces in place the optimal global arrangement of items is +// a little unclear. + +use std::io::{Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use zebra_chain::serialization::{ + ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, +}; + +/// Stub-- delete later. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct TxHash(pub [u8; 32]); +/// Stub-- delete later. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct BlockHash(pub [u8; 32]); + +/// An inventory hash which refers to some advertised or requested data. +/// +/// Bitcoin calls this an "inventory vector" but it is just a typed hash, not a +/// container, so we do not use that term to avoid confusion with `Vec`. +/// +/// [Bitcoin·reference](https://en.bitcoin.it/wiki/Protocol_documentation#Inventory_Vectors) +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum InventoryHash { + /// An error. + /// + /// The Bitcoin wiki just says "Any data of with this number may be ignored", + /// so we don't include a typed hash. + Error, + /// A hash of a transaction. + Tx(TxHash), + /// A hash of a block. + Block(BlockHash), + /// A hash of a filtered block. + /// + /// The Bitcoin wiki says: Hash of a block header, but only to be used in + /// getdata message. Indicates the reply should be a merkleblock message + /// rather than a block message; this only works if a bloom filter has been + /// set. + FilteredBlock(BlockHash), +} + +impl ZcashSerialize for InventoryHash { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + let (code, bytes) = match *self { + InventoryHash::Error => (0, [0; 32]), + InventoryHash::Tx(hash) => (1, hash.0), + InventoryHash::Block(hash) => (2, hash.0), + InventoryHash::FilteredBlock(hash) => (3, hash.0), + }; + writer.write_u32::(code)?; + writer.write_all(&bytes)?; + Ok(()) + } +} + +impl ZcashDeserialize for InventoryHash { + fn zcash_deserialize(mut reader: R) -> Result { + let code = reader.read_u32::()?; + let bytes = reader.read_32_bytes()?; + match code { + 0 => Ok(InventoryHash::Error), + 1 => Ok(InventoryHash::Tx(TxHash(bytes))), + 2 => Ok(InventoryHash::Block(BlockHash(bytes))), + 3 => Ok(InventoryHash::FilteredBlock(BlockHash(bytes))), + _ => Err(SerializationError::ParseError("invalid inventory code")), + } + } +} diff --git a/zebra-network/src/protocol/message.rs b/zebra-network/src/protocol/message.rs index c25f8c93..08449cf2 100644 --- a/zebra-network/src/protocol/message.rs +++ b/zebra-network/src/protocol/message.rs @@ -9,6 +9,7 @@ use zebra_chain::{transaction::Transaction, types::BlockHeight}; use crate::meta_addr::MetaAddr; +use super::inv::InventoryHash; use super::types::*; /// A Bitcoin-like network message for the Zcash protocol. @@ -162,13 +163,7 @@ pub enum Message { // XXX the bitcoin reference above suggests this can be 1.8 MB in bitcoin -- maybe // larger in Zcash, since Zcash objects could be bigger (?) -- does this tilt towards // having serialization be async? - Inventory { - /// Number of inventory entries. - count: u64, - - /// Inventory vectors. - inventory: Vec, - }, + Inventory(Vec), /// A `getdata` message. /// @@ -177,25 +172,13 @@ pub enum Message { /// packet, after filtering known elements. /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getdata) - GetData { - /// Number of inventory entries. - count: u64, - - /// Inventory vectors. - inventory: Vec, - }, + GetData(Vec), /// A `notfound` message. /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#notfound) // See note above on `Inventory`. - NotFound { - /// Number of inventory entries. - count: u64, - - /// Inventory vectors. - inventory: Vec, - }, + NotFound(Vec), /// A `tx` message. ///