zebra/zebra-network/src/protocol/external/message.rs

359 lines
13 KiB
Rust
Raw Normal View History

//! Definitions of network messages.
use std::error::Error;
use std::net;
2019-09-13 05:29:17 -07:00
use chrono::{DateTime, Utc};
2019-10-03 22:45:59 -07:00
use zebra_chain::block::{Block, BlockHeader, BlockHeaderHash};
use zebra_chain::{transaction::Transaction, types::BlockHeight};
2019-09-13 05:29:17 -07:00
use super::inv::InventoryHash;
use super::types::*;
use crate::meta_addr::MetaAddr;
/// A Bitcoin-like network message for the Zcash protocol.
///
/// The Zcash network protocol is mostly inherited from Bitcoin, and a list of
/// Bitcoin network messages can be found [on the Bitcoin
/// wiki][btc_wiki_protocol].
///
/// That page describes the wire format of the messages, while this enum stores
/// an internal representation. The internal representation is unlinked from the
/// wire format, and the translation between the two happens only during
/// serialization and deserialization. For instance, Bitcoin identifies messages
/// by a 12-byte ascii command string; we consider this a serialization detail
/// and use the enum discriminant instead. (As a side benefit, this also means
/// that we have a clearly-defined validation boundary for network messages
/// during serialization).
///
/// [btc_wiki_protocol]: https://en.bitcoin.it/wiki/Protocol_documentation
2019-09-15 12:11:07 -07:00
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Message {
/// A `version` message.
///
/// Note that although this is called `version` in Bitcoin, its role is really
/// analogous to a `ClientHello` message in TLS, used to begin a handshake, and
/// is distinct from a simple version number.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#version)
Version {
/// The network version number supported by the sender.
version: Version,
/// The network services advertised by the sender.
services: PeerServices,
/// The time when the version message was sent.
timestamp: DateTime<Utc>,
/// The network address of the node receiving this message, and its
/// advertised network services.
///
/// Q: how does the handshake know the remote peer's services already?
address_recv: (PeerServices, net::SocketAddr),
/// The network address of the node sending this message, and its
/// advertised network services.
address_from: (PeerServices, net::SocketAddr),
/// Node random nonce, randomly generated every time a version
/// packet is sent. This nonce is used to detect connections
/// to self.
nonce: Nonce,
/// The Zcash user agent advertised by the sender.
user_agent: String,
/// The last block received by the emitting node.
Implement ZcashDeserialize for Message::Version. This has a test that the serialization implementation round trips correctly, but is very much a work in progress. Issues with this code include: The deserialization logic for message headers is somewhat ugly because of a lack of a convenient idiom for reading a few bytes at a time into a fixed-size array. Perhaps this could be fixed with an extension trait, or avoided altogether by using `u32`s instead of `[u8; 4]`, even when the latter is the "morally correct type". Deserialization does an extra allocation, copying the body into a temporary buffer. This avoids two problems: 1) capping the number of bytes that can be read by the `Read`er passed into the body parser and 2) allows making two passes over the body data, one to parse it and one to compute the checksum. We could avoid making two passes over the body by computing the checksum simultaneously with the parsing. A convenient way to do this would be to define a ``` struct ChecksumReader<R: Read> { inner: R, digest: Sha256, } impl<R: Read> Read for ChecksumReader<R> { /* ... */ } ``` and implement `Read` for `ChecksumReader` to forward reads from the inner reader, copying data into the digest object as it does so. (It could also have a maximum length to enforce that we do not read past the nominal end of the body). A similar `ChecksumWriter` could be used during serialization, although because the checksum is at the beginning rather than the end of the message it does not help avoid an allocation there. It could also be generic over a `Digest` implementation, although because we need a truncated double-SHA256 we would have to write a custom `Digest` implementation, so this is probably not worthwhile unless we have other checksum types. Finally, this does very minimal testing -- just round-trip serialization on a single message. It would be good to build in support for property-based testing, probably using `proptest`; if we could define generation and shrinking strategies for every component type of every message, we could do strong randomized testing of the serialization.
2019-09-15 12:11:41 -07:00
start_height: BlockHeight,
/// Whether the remote peer should announce relayed
/// transactions or not, see [BIP 0037](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki)
relay: bool,
},
2019-09-09 23:59:49 -07:00
/// A `verack` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#verack)
Verack,
2019-09-09 23:59:49 -07:00
/// A `ping` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#ping)
2019-09-19 13:17:22 -07:00
Ping(
/// A nonce unique to this [`Ping`] message.
Nonce,
),
2019-09-09 23:59:49 -07:00
/// A `pong` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#pong)
Pong(
2019-09-19 13:17:22 -07:00
/// The nonce from the [`Ping`] message this was in response to.
Nonce,
),
2019-09-10 09:57:58 -07:00
/// A `reject` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#reject)
Reject {
/// Type of message rejected.
// It's unclear if this is strictly limited to message command
// codes, so leaving it a String.
message: String,
/// RejectReason code relating to rejected message.
ccode: RejectReason,
/// Human-readable version of rejection reason.
reason: String,
/// Optional extra data provided for some errors.
// Currently, all errors which provide this field fill it with
// the TXID or block header hash of the object being rejected,
// so the field is 32 bytes.
//
// Q: can we tell Rust that this field is optional? Or just
// default its value to an empty array, I guess.
data: Option<[u8; 32]>,
},
2019-09-10 09:57:58 -07:00
/// An `addr` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#addr)
Addr(Vec<MetaAddr>),
2019-09-10 09:57:58 -07:00
/// A `getaddr` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getaddr)
GetAddr,
/// A `block` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#block)
Block {
/// Transaction data format version (note, this is signed).
2020-02-04 22:53:24 -08:00
// XXX does this get folded into the Block struct?
version: Version,
/// The block itself.
2020-02-04 22:53:24 -08:00
block: Box<Block>,
},
2019-09-10 09:57:58 -07:00
/// A `getblocks` message.
///
2019-10-03 22:45:59 -07:00
/// 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.
///
2019-09-10 09:57:58 -07:00
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getblocks)
2019-10-03 22:45:59 -07:00
// 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.
//
// The 500 headers number is from the Bitcoin docs, we are not
// certain (yet) that other implementations of Zcash obey this
// restriction, or if they don't, what happens if we send them too
// many results.
2019-10-03 22:45:59 -07:00
GetBlocks {
/// The protocol version.
version: Version,
/// 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).
hash_stop: BlockHeaderHash,
},
2019-09-10 09:57:58 -07:00
/// A `headers` message.
///
2019-10-03 15:28:47 -07:00
/// Returns block headers in response to a getheaders packet.
///
2019-09-10 09:57:58 -07:00
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#headers)
2019-10-03 15:28:47 -07:00
// 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>),
2019-09-10 09:57:58 -07:00
/// A `getheaders` message.
///
2019-10-03 23:20:01 -07:00
/// 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.
///
2019-09-10 09:57:58 -07:00
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getheaders)
// The 2000 headers number is from the Bitcoin docs, we are not
// certain (yet) that other implementations of Zcash obey this
// restriction, or if they don't, what happens if we send them too
// many results.
2019-10-03 23:20:01 -07:00
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,
},
2019-09-10 09:57:58 -07:00
/// An `inv` message.
///
2019-10-03 15:28:47 -07:00
/// Allows a node to advertise its knowledge of one or more
/// objects. It can be received unsolicited, or in reply to
/// `getblocks`.
///
2019-09-10 09:57:58 -07:00
/// [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
// having serialization be async?
Inv(Vec<InventoryHash>),
2019-09-10 09:57:58 -07:00
/// A `getdata` message.
///
2019-10-03 15:28:47 -07:00
/// `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.
///
2019-09-10 09:57:58 -07:00
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#getdata)
GetData(Vec<InventoryHash>),
2019-09-10 09:57:58 -07:00
/// A `notfound` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#notfound)
// See note above on `Inventory`.
NotFound(Vec<InventoryHash>),
2019-09-10 09:57:58 -07:00
/// A `tx` message.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#tx)
// `flag` is not included (it's optional), and therefore
// `tx_witnesses` aren't either, as they go if `flag` goes.
Tx {
/// Transaction data format version (note, this is signed).
2020-02-04 22:53:24 -08:00
// XXX do we still need this with the transaction data handling?
version: Version,
/// The `Transaction` type itself.
2020-02-04 22:53:24 -08:00
transaction: Box<Transaction>,
},
2019-09-10 09:57:58 -07:00
/// A `mempool` message.
///
/// This was defined in [BIP35], which is included in Zcash.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#mempool)
/// [BIP35]: https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki
Mempool,
2019-09-10 09:57:58 -07:00
/// A `filterload` message.
///
/// This was defined in [BIP37], which is included in Zcash.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock)
/// [BIP37]: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
FilterLoad {
/// The filter itself is simply a bit field of arbitrary
/// byte-aligned size. The maximum size is 36,000 bytes.
filter: Filter,
/// The number of hash functions to use in this filter. The
/// maximum value allowed in this field is 50.
hash_functions_count: u32,
/// A random value to add to the seed value in the hash
/// function used by the bloom filter.
tweak: Tweak,
/// A set of flags that control how matched items are added to the filter.
flags: u8,
},
2019-09-10 09:57:58 -07:00
/// A `filteradd` message.
///
/// This was defined in [BIP37], which is included in Zcash.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock)
/// [BIP37]: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
FilterAdd {
/// The data element to add to the current filter.
// The data field must be smaller than or equal to 520 bytes
// in size (the maximum size of any potentially matched
// object).
//
// A Vec instead of [u8; 520] because of needed traits.
data: Vec<u8>,
},
2019-09-10 09:57:58 -07:00
/// A `filterclear` message.
///
/// This was defined in [BIP37], which is included in Zcash.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock)
/// [BIP37]: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
FilterClear,
}
impl<E> From<E> for Message
where
E: Error,
{
fn from(e: E) -> Self {
Message::Reject {
message: e.to_string(),
// The generic case, impls for specific error types should
// use specific varieties of `RejectReason`.
ccode: RejectReason::Other,
reason: e.source().unwrap().to_string(),
// Allow this to be overriden but not populated by default, methinks.
data: None,
}
}
}
/// Reject Reason CCodes
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#reject)
2019-09-15 12:11:07 -07:00
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum RejectReason {
Malformed = 0x01,
Invalid = 0x10,
Obsolete = 0x11,
Duplicate = 0x12,
Nonstandard = 0x40,
Dust = 0x41,
InsufficientFee = 0x42,
Checkpoint = 0x43,
Other = 0x50,
}