diff --git a/zebra-chain/src/serialization.rs b/zebra-chain/src/serialization.rs index 9232d60deda..971b7d1d73f 100644 --- a/zebra-chain/src/serialization.rs +++ b/zebra-chain/src/serialization.rs @@ -236,6 +236,14 @@ pub trait ReadZcashExt: io::Read { self.read_exact(&mut bytes)?; Ok(bytes) } + + /// Convenience method to read a `[u8; 32]`. + #[inline] + fn read_32_bytes(&mut self) -> io::Result<[u8; 32]> { + let mut bytes = [0; 32]; + self.read_exact(&mut bytes)?; + Ok(bytes) + } } /// Mark all types implementing `Read` as implementing the extension. diff --git a/zebra-chain/src/types.rs b/zebra-chain/src/types.rs index 43be396c605..ae26c4e9c9a 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/constants.rs b/zebra-network/src/constants.rs index 7efea3aa9c5..b7eaab84176 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -1,6 +1,7 @@ //! Definitions of constants. -use crate::types::*; +// XXX should these constants be split into protocol also? +use crate::protocol::types::*; /// The User-Agent string provided by the node. pub const USER_AGENT: &'static str = "Zebra v2.0.0-alpha.0"; diff --git a/zebra-network/src/lib.rs b/zebra-network/src/lib.rs index efd2ab45518..46600cc6973 100644 --- a/zebra-network/src/lib.rs +++ b/zebra-network/src/lib.rs @@ -11,7 +11,6 @@ mod network; pub use network::Network; pub mod protocol; -pub mod types; // XXX make this private once connect is removed pub mod meta_addr; diff --git a/zebra-network/src/meta_addr.rs b/zebra-network/src/meta_addr.rs index 2933543ebb6..6f014dbea36 100644 --- a/zebra-network/src/meta_addr.rs +++ b/zebra-network/src/meta_addr.rs @@ -1,13 +1,23 @@ //! An address-with-metadata type used in Bitcoin networking. -use chrono::{DateTime, Utc}; +use std::io::{Read, Write}; use std::net::SocketAddr; -use crate::types::Services; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use chrono::{DateTime, TimeZone, Utc}; + +use zebra_chain::serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, +}; + +use crate::protocol::types::Services; /// An address with metadata on its advertised services and last-seen time. /// /// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#Network_address) +// XXX determine whether we will use this struct in *our* networking handling +// code, or just in the definitions of the networking protocol (in which case +// it should live in the protocol submodule) #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct MetaAddr { /// The peer's address. @@ -17,3 +27,22 @@ pub struct MetaAddr { /// When the peer was last seen. pub last_seen: DateTime, } + +impl ZcashSerialize for MetaAddr { + fn zcash_serialize(&self, mut writer: W) -> Result<(), SerializationError> { + writer.write_u32::(self.last_seen.timestamp() as u32)?; + writer.write_u64::(self.services.0)?; + writer.write_socket_addr(self.addr)?; + Ok(()) + } +} + +impl ZcashDeserialize for MetaAddr { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(MetaAddr { + last_seen: Utc.timestamp(reader.read_u32::()? as i64, 0), + services: Services(reader.read_u64::()?), + addr: reader.read_socket_addr()?, + }) + } +} diff --git a/zebra-network/src/network.rs b/zebra-network/src/network.rs index c211e2a1ec0..81d9ffe6e73 100644 --- a/zebra-network/src/network.rs +++ b/zebra-network/src/network.rs @@ -1,4 +1,4 @@ -use crate::{constants::magics, types::Magic}; +use crate::{constants::magics, protocol::types::Magic}; /// An enum describing the possible network choices. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/zebra-network/src/protocol.rs b/zebra-network/src/protocol.rs index 21dabde18b1..24cbfb048fe 100644 --- a/zebra-network/src/protocol.rs +++ b/zebra-network/src/protocol.rs @@ -2,3 +2,6 @@ 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 42e645353e9..330d383465b 100644 --- a/zebra-network/src/protocol/codec.rs +++ b/zebra-network/src/protocol/codec.rs @@ -9,13 +9,13 @@ use failure::Error; use tokio::codec::{Decoder, Encoder}; use zebra_chain::{ - serialization::{ReadZcashExt, WriteZcashExt, ZcashSerialize}, + serialization::{ReadZcashExt, WriteZcashExt, ZcashDeserialize, ZcashSerialize}, types::{BlockHeight, Sha256dChecksum}, }; -use crate::{constants, types::*, Network}; +use crate::{constants, Network}; -use super::message::Message; +use super::{message::Message, types::*}; /// The length of a Bitcoin message header. const HEADER_LEN: usize = 24usize; @@ -126,7 +126,7 @@ impl Encoder for Codec { GetBlocks { .. } => b"getblocks\0\0\0", Headers { .. } => b"headers\0\0\0\0\0", GetHeaders { .. } => b"getheaders\0\0", - Inventory { .. } => b"inv\0\0\0\0\0\0\0\0\0", // XXX Inventory -> Inv ? + Inv { .. } => b"inv\0\0\0\0\0\0\0\0\0", GetData { .. } => b"getdata\0\0\0\0\0", NotFound { .. } => b"notfound\0\0\0\0", Tx { .. } => b"tx\0\0\0\0\0\0\0\0\0\0", @@ -197,6 +197,19 @@ impl Codec { Pong(nonce) => { writer.write_u64::(nonce.0)?; } + GetAddr => { /* Empty payload -- no-op */ } + Addr(ref addrs) => { + writer.write_compactsize(addrs.len() as u64)?; + for addr in addrs { + addr.zcash_serialize(&mut writer)?; + } + } + 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) @@ -279,7 +292,8 @@ impl Decoder for Codec { } // Now that we know we have the full body, split off the body, - // and reset the decoder state for the next message. + // and reset the decoder state for the next message. Otherwise + // we will attempt to read the next header as the current body. let body = src.split_to(body_len); self.state = DecodeState::Head; @@ -365,9 +379,31 @@ impl Codec { bail!("unimplemented message type") } - fn read_addr(&self, mut _reader: R) -> Result { - trace!("addr"); - bail!("unimplemented message type") + fn read_addr(&self, mut reader: R) -> Result { + use crate::meta_addr::MetaAddr; + + // XXX we may want to factor this logic out into + // fn read_vec(reader: R) -> Result, Error> + // on ReadZcashExt (and similarly for WriteZcashExt) + 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 addr 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_ADDR_SIZE. + // + // addrs are encoded as: timestamp + services + ipv6 + port + const ENCODED_ADDR_SIZE: usize = 4 + 8 + 16 + 2; + let max_count = self.builder.max_len / ENCODED_ADDR_SIZE; + let mut addrs = Vec::with_capacity(std::cmp::min(count, max_count)); + + for _ in 0..count { + addrs.push(MetaAddr::zcash_deserialize(&mut reader)?); + } + + Ok(Message::Addr(addrs)) } fn read_getaddr(&self, mut _reader: R) -> Result { @@ -395,9 +431,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::Inv(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 00000000000..65d46acb29e --- /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 ebf32cd0fee..457202a2be2 100644 --- a/zebra-network/src/protocol/message.rs +++ b/zebra-network/src/protocol/message.rs @@ -8,7 +8,9 @@ use zebra_chain::block::Block; use zebra_chain::{transaction::Transaction, types::BlockHeight}; use crate::meta_addr::MetaAddr; -use crate::types::*; + +use super::inv::InventoryHash; +use super::types::*; /// A Bitcoin-like network message for the Zcash protocol. /// @@ -161,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, - }, + Inv(Vec), /// A `getdata` message. /// @@ -176,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. /// diff --git a/zebra-network/src/types.rs b/zebra-network/src/protocol/types.rs similarity index 100% rename from zebra-network/src/types.rs rename to zebra-network/src/protocol/types.rs diff --git a/zebrad/src/commands/connect.rs b/zebrad/src/commands/connect.rs index 583663f0c30..d9652184963 100644 --- a/zebrad/src/commands/connect.rs +++ b/zebrad/src/commands/connect.rs @@ -51,16 +51,13 @@ impl Runnable for ConnectCmd { impl ConnectCmd { async fn connect(&self) -> Result<(), failure::Error> { - use std::net::Shutdown; - use chrono::Utc; use tokio::{codec::Framed, net::TcpStream, prelude::*}; use zebra_chain::types::BlockHeight; use zebra_network::{ constants, - protocol::{codec::*, message::*}, - types::*, + protocol::{codec::*, message::*, types::*}, Network, }; diff --git a/zebrad/src/lib.rs b/zebrad/src/lib.rs index d13de42ff34..cc825e06400 100644 --- a/zebrad/src/lib.rs +++ b/zebrad/src/lib.rs @@ -6,6 +6,9 @@ //#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] #![forbid(unsafe_code)] +// async_await is considered stable on nightly releases, but the corresponding +// release (1.39.0) is still in beta. Suppress the warning for now. +#![allow(stable_features)] #![feature(async_await)] #[macro_use]