Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse inv, addr, getaddr messages. #31

Merged
merged 8 commits into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions zebra-chain/src/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 0 additions & 28 deletions zebra-chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
3 changes: 2 additions & 1 deletion zebra-network/src/constants.rs
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
1 change: 0 additions & 1 deletion zebra-network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 31 additions & 2 deletions zebra-network/src/meta_addr.rs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suspicion is that we will want to move it into protocol but I'd rather wait a bit to see how things settle, WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, more metadata thingies related to the protocol.

// 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.
Expand All @@ -17,3 +27,22 @@ pub struct MetaAddr {
/// When the peer was last seen.
pub last_seen: DateTime<Utc>,
}

impl ZcashSerialize for MetaAddr {
fn zcash_serialize<W: Write>(&self, mut writer: W) -> Result<(), SerializationError> {
writer.write_u32::<LittleEndian>(self.last_seen.timestamp() as u32)?;
writer.write_u64::<LittleEndian>(self.services.0)?;
writer.write_socket_addr(self.addr)?;
Ok(())
}
}

impl ZcashDeserialize for MetaAddr {
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(MetaAddr {
last_seen: Utc.timestamp(reader.read_u32::<LittleEndian>()? as i64, 0),
services: Services(reader.read_u64::<LittleEndian>()?),
addr: reader.read_socket_addr()?,
})
}
}
2 changes: 1 addition & 1 deletion zebra-network/src/network.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions zebra-network/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

pub mod codec;
pub mod message;
pub mod types;

pub mod inv;
77 changes: 66 additions & 11 deletions zebra-network/src/protocol/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinions on this -- I'm happy to drop this commit from the PR but aligning names with bitcoin like this makes the table line up nicer.

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",
Expand Down Expand Up @@ -197,6 +197,19 @@ impl Codec {
Pong(nonce) => {
writer.write_u64::<LittleEndian>(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) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be ref hashes so that hashes: &Vec<InventoryHash>, not hashes: Vec<InventoryHash>, which would cause the vector to be moved out of the borrowed Message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Perhaps the Version fields should also use ref match patterns (I think they're all Copy, but taking borrows might be slightly more efficient).

writer.write_compactsize(hashes.len() as u64)?;
for hash in hashes {
hash.zcash_serialize(&mut writer)?;
}
}
Block { ref block } => {
block
.zcash_serialize(&mut writer)
Expand Down Expand Up @@ -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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixup from #22 (comment)

let body = src.split_to(body_len);
self.state = DecodeState::Head;

Expand Down Expand Up @@ -365,9 +379,31 @@ impl Codec {
bail!("unimplemented message type")
}

fn read_addr<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("addr");
bail!("unimplemented message type")
fn read_addr<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
use crate::meta_addr::MetaAddr;

// XXX we may want to factor this logic out into
// fn read_vec<R: Read, T: ZcashDeserialize>(reader: R) -> Result<Vec<T>, 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// 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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


for _ in 0..count {
addrs.push(MetaAddr::zcash_deserialize(&mut reader)?);
}

Ok(Message::Addr(addrs))
}

fn read_getaddr<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
Expand Down Expand Up @@ -395,9 +431,28 @@ impl Codec {
bail!("unimplemented message type")
}

fn read_inv<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("inv");
bail!("unimplemented message type")
fn read_inv<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// 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<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
Expand Down
74 changes: 74 additions & 0 deletions zebra-network/src/protocol/inv.rs
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct TxHash(pub [u8; 32]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the replacement for this would live in zebra-chain, yes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, and the BlockHash should turn into a BlockHeaderHash from your PR, I just didn't want to change them until both were available.

/// Stub-- delete later.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct BlockHash(pub [u8; 32]);
Copy link
Contributor Author

@hdevalence hdevalence Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be replaced with types from #21 before merging; these are just placeholders.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#21 doesn't introduce a transaction hash type (which is part of #35); I would prefer to merge this changeset with placeholders intact until we have all of the types required in place (since it unblocks some other work).


/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

/// container, so we do not use that term to avoid confusion with `Vec<T>`.
///
/// [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<W: Write>(&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::<LittleEndian>(code)?;
writer.write_all(&bytes)?;
Ok(())
}
}

impl ZcashDeserialize for InventoryHash {
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
let code = reader.read_u32::<LittleEndian>()?;
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")),
}
}
}
28 changes: 6 additions & 22 deletions zebra-network/src/protocol/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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<zebra_chain::types::InventoryVector>,
},
Inv(Vec<InventoryHash>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


/// A `getdata` message.
///
Expand All @@ -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<zebra_chain::types::InventoryVector>,
},
GetData(Vec<InventoryHash>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


/// 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<zebra_chain::types::InventoryVector>,
},
NotFound(Vec<InventoryHash>),

/// A `tx` message.
///
Expand Down
File renamed without changes.
5 changes: 1 addition & 4 deletions zebrad/src/commands/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
Loading