Parse inv messages, refactor inventory vectors.

This removes the inventory vector structs from `zebra-chain` (as they
are really part of the network protocol) and refactors them into a
single `InventoryHash` type.  This corresponds to Bitcoin's "inventory
vector" but with a different, better name (it's not a vector, it's just
a typed hash of some other item).
This commit is contained in:
Henry de Valence 2019-09-25 14:52:20 -07:00 committed by Deirdre Connolly
parent 64b210b53c
commit 958fca8e68
5 changed files with 109 additions and 53 deletions

View File

@ -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::*;

View File

@ -3,3 +3,5 @@
pub mod codec;
pub mod message;
pub mod types;
pub mod inv;

View File

@ -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::<LittleEndian>(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<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
// 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<R: Read>(&self, mut _reader: R) -> Result<Message, Error> {

View File

@ -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<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")),
}
}
}

View File

@ -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<zebra_chain::types::InventoryVector>,
},
Inventory(Vec<InventoryHash>),
/// 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<zebra_chain::types::InventoryVector>,
},
GetData(Vec<InventoryHash>),
/// 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.
///