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

getblocks, headers, getheaders, tx, getdata, notfound #44

Merged
merged 16 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from 8 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
16 changes: 15 additions & 1 deletion zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::io;

use crate::merkle_tree::MerkleTreeRootHash;
use crate::note_commitment_tree::SaplingNoteTreeRootHash;
use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::sha256d_writer::Sha256dWriter;
use crate::transaction::Transaction;

Expand Down Expand Up @@ -34,6 +34,20 @@ impl From<BlockHeader> for BlockHeaderHash {
}
}

impl ZcashSerialize for BlockHeaderHash {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), SerializationError> {
writer.write_all(&self.0)?;
Ok(())
}
}

impl ZcashDeserialize for BlockHeaderHash {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let bytes = reader.read_32_bytes()?;
Ok(BlockHeaderHash(bytes))
}
}

/// Block header.
///
/// How are blocks chained together? They are chained together via the
Expand Down
30 changes: 30 additions & 0 deletions zebra-chain/src/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,36 @@ pub trait ReadZcashExt: io::Read {
self.read_exact(&mut bytes)?;
Ok(bytes)
}

/// Convenience method to read a `Vec<T>` with a leading count in a safer manner.
#[inline]
fn read_list<T: ZcashDeserialize>(
&mut self,
max_count: usize,
) -> Result<Vec<T>, SerializationError> {
// This prevents the inferred type for zcash_deserialize from
// taking ownership of &mut self. This wouldn't really be an
// issue if the target impl's `Copy`, but we need to own it.
let mut self2 = self;

let count = self2.read_compactsize()? as usize;

// Preallocate a buffer, performing a single allocation in the
// honest case. Although the size of the received 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_type_size.
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
let mut items = Vec::with_capacity(std::cmp::min(count, max_count));

for _ in 0..count {
items.push(T::zcash_deserialize(&mut self2)?);
}

return Ok(items);
}
}

/// Mark all types implementing `Read` as implementing the extension.
Expand Down
117 changes: 66 additions & 51 deletions zebra-network/src/protocol/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ use failure::Error;
use tokio::codec::{Decoder, Encoder};

use zebra_chain::{
block::{BlockHeader, BlockHeaderHash},
serialization::{ReadZcashExt, WriteZcashExt, ZcashDeserialize, ZcashSerialize},
transaction::Transaction,
types::{BlockHeight, Sha256dChecksum},
};

use crate::{constants, Network};

use super::{message::Message, types::*};
use super::{inv::InventoryHash, message::Message, types::*};

/// The length of a Bitcoin message header.
const HEADER_LEN: usize = 24usize;
Expand Down Expand Up @@ -381,26 +383,11 @@ impl Codec {
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
// 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)?);
}
let addrs: Vec<MetaAddr> = reader.read_list(max_count)?;

Ok(Message::Addr(addrs))
}
Expand All @@ -415,58 +402,86 @@ impl Codec {
bail!("unimplemented message type")
}

fn read_getblocks<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("getblocks");
bail!("unimplemented message type")
fn read_getblocks<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
let version = Version(reader.read_u32::<LittleEndian>()?);

let max_count = self.builder.max_len / 32;

let block_locator_hashes: Vec<BlockHeaderHash> = reader.read_list(max_count)?;

let hash_stop = BlockHeaderHash(reader.read_32_bytes()?);

Ok(Message::GetBlocks {
version,
block_locator_hashes,
hash_stop,
})
}

fn read_headers<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("headers");
bail!("unimplemented message type")
/// Deserialize a `headers` message.
///
/// See [Zcash block header] for the enumeration of these fields.
///
/// [Zcash block header](https://zips.z.cash/protocol/protocol.pdf#page=84)
fn read_headers<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
const ENCODED_HEADER_SIZE: usize = 4 + 32 + 32 + 32 + 4 + 4 + 32 + 3 + 1344;
let max_count = self.builder.max_len / ENCODED_HEADER_SIZE;

let headers: Vec<BlockHeader> = reader.read_list(max_count)?;

Ok(Message::Headers(headers))
}

fn read_getheaders<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("getheaders");
bail!("unimplemented message type")
fn read_getheaders<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
let version = Version(reader.read_u32::<LittleEndian>()?);

let max_count = self.builder.max_len / 32;

let block_locator_hashes: Vec<BlockHeaderHash> = reader.read_list(max_count)?;

let hash_stop = BlockHeaderHash(reader.read_32_bytes()?);

Ok(Message::GetHeaders {
version,
block_locator_hashes,
hash_stop,
})
}

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
// 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)?);
}

let hashes: Vec<InventoryHash> = reader.read_list(max_count)?;
Ok(Message::Inv(hashes))
}

fn read_getdata<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("getdata");
bail!("unimplemented message type")
fn read_getdata<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
// 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 hashes: Vec<InventoryHash> = reader.read_list(max_count)?;
Ok(Message::GetData(hashes))
}

fn read_notfound<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("notfound");
bail!("unimplemented message type")
fn read_notfound<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
// 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 hashes: Vec<InventoryHash> = reader.read_list(max_count)?;

Ok(Message::GetData(hashes))
}

fn read_tx<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
trace!("tx");
bail!("unimplemented message type")
fn read_tx<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
Ok(Message::Tx {
version: Version(reader.read_u32::<LittleEndian>()?),

transaction: Transaction::zcash_deserialize(&mut reader)?,
})
}

fn read_mempool<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {
Expand Down
76 changes: 68 additions & 8 deletions zebra-network/src/protocol/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::net;

use chrono::{DateTime, Utc};

use zebra_chain::block::Block;
use zebra_chain::block::{Block, BlockHeader, BlockHeaderHash};
use zebra_chain::{transaction::Transaction, types::BlockHeight};

use crate::meta_addr::MetaAddr;
Expand Down Expand Up @@ -144,21 +144,82 @@ pub enum Message {

/// A `getblocks` message.
///
/// Requests the list of blocks starting right after the last
/// known hash in `block_locator_hashes`, up to `hash_stop` or 500
/// blocks, whichever comes first.
///
/// You can send in fewer known hashes down to a minimum of just
/// one hash. However, the purpose of the block locator object is
/// to detect a wrong branch in the caller's main chain. If the
/// peer detects that you are off the main chain, it will send in
/// block hashes which are earlier than your last known block. So
/// if you just send in your last known hash and it is off the
/// main chain, the peer starts over at block #1.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getblocks)
GetBlocks {/* XXX add fields */},
// The locator hashes are processed by a node in the order as they
// appear in the message. If a block hash is found in the node's
// main chain, the list of its children is returned back via the
// inv message and the remaining locators are ignored, no matter
// if the requested limit was reached, or not.
GetBlocks {
/// The protocol version.
version: Version,
Copy link
Contributor

Choose a reason for hiding this comment

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

later: how does this version field interact with the versions of the blocks themselves?

Copy link
Contributor

Choose a reason for hiding this comment

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

also, like, is this something we need to specify in the message itself, or can the codec fill it in? if the codec can't fill it in (<=> the version in the message is independent of the transport version), then how do version mismatches happen?

Copy link
Contributor

Choose a reason for hiding this comment

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

(to be clear this could all be a new issue or go somewhere else, it's 'later' because it should be non-blocking for this PR)


/// Block locators, from newest back to genesis block.
block_locator_hashes: Vec<BlockHeaderHash>,

/// `BlockHeaderHash` of the last desired block.
///
/// Set to zero to get as many blocks as possible (500).
dconnolly marked this conversation as resolved.
Show resolved Hide resolved
hash_stop: BlockHeaderHash,
},

/// A `headers` message.
///
/// Returns block headers in response to a getheaders packet.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#headers)
Headers {/* XXX add fields */},
// Note that the block headers in this packet include a
// transaction count (a var_int, so there can be more than 81
// bytes per header) as opposed to the block headers that are
// hashed by miners.
Headers(Vec<BlockHeader>),

/// A `getheaders` message.
///
/// Requests a series of block headers starting right after the
/// last known hash in `block_locator_hashes`, up to `hash_stop`
/// or 2000 blocks, whichever comes first.
///
/// You can send in fewer known hashes down to a minimum of just
/// one hash. However, the purpose of the block locator object is
/// to detect a wrong branch in the caller's main chain. If the
/// peer detects that you are off the main chain, it will send in
/// block hashes which are earlier than your last known block. So
/// if you just send in your last known hash and it is off the
/// main chain, the peer starts over at block #1.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getheaders)
GetHeaders {/* XXX add fields */},
GetHeaders {
/// The protocol version.
version: Version,

/// Block locators, from newest back to genesis block.
block_locator_hashes: Vec<BlockHeaderHash>,

/// `BlockHeaderHash` of the last desired block header.
///
/// Set to zero to get as many block headers as possible (2000).
hash_stop: BlockHeaderHash,
},

/// An `inv` message.
///
/// Allows a node to advertise its knowledge of one or more
/// objects. It can be received unsolicited, or in reply to
/// `getblocks`.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#inv)
// 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
Expand All @@ -167,9 +228,9 @@ pub enum Message {

/// A `getdata` message.
///
/// `getdata` is used in response to `inv`, to retrieve the content of
/// a specific object, and is usually sent after receiving an `inv`
/// packet, after filtering known elements.
/// `getdata` is used in response to `inv`, to retrieve the
/// content of a specific object, and is usually sent after
/// receiving an `inv` packet, after filtering known elements.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getdata)
GetData(Vec<InventoryHash>),
Expand All @@ -190,7 +251,6 @@ pub enum Message {
version: Version,

/// The `Transaction` type itself.
// XXX Is this ~aesthetic~?
transaction: Transaction,
},

Expand Down