network: correct data modeling for headers messages

We modeled a Bitcoin `headers` message as being a list of block headers.
However, the actual data structure is slightly different: it's a list of (block
header, transaction count) pairs.  This caused zcashd to reject our headers
messages.

To fix this, introduce a new `CountedHeader` struct with a `block::Header` and
transaction count `usize`, then thread it through the inbound service and the
state.

I tested this locally by running Zebra with these changes and inspecting a
trace-level log of the span of a peer connection that requested a nontrivial
headers packet from us, and verified that it did not reject our message.
This commit is contained in:
Henry de Valence 2020-12-01 15:52:09 -08:00
parent dc77163524
commit b449fe93b2
7 changed files with 49 additions and 16 deletions

View File

@ -18,7 +18,7 @@ use std::fmt;
pub use hash::Hash;
pub use header::BlockTimeError;
pub use header::Header;
pub use header::{CountedHeader, Header};
pub use height::Height;
pub use root_hash::RootHash;

View File

@ -109,3 +109,12 @@ impl Header {
}
}
}
/// A header with a count of the number of transactions in its block.
///
/// This structure is used in the Bitcoin network protocol.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CountedHeader {
pub header: Header,
pub transaction_count: usize,
}

View File

@ -1,12 +1,17 @@
use std::{convert::TryInto, io};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{TimeZone, Utc};
use std::io;
use crate::serialization::ZcashDeserializeInto;
use crate::serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::work::{difficulty::CompactDifficulty, equihash};
use crate::{
serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto,
ZcashSerialize,
},
work::{difficulty::CompactDifficulty, equihash},
};
use super::{merkle, Block, Hash, Header};
use super::{merkle, Block, CountedHeader, Hash, Header};
/// The maximum size of a Zcash block, in bytes.
///
@ -79,6 +84,23 @@ impl ZcashDeserialize for Header {
}
}
impl ZcashSerialize for CountedHeader {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.header.zcash_serialize(&mut writer)?;
writer.write_compactsize(self.transaction_count as u64)?;
Ok(())
}
}
impl ZcashDeserialize for CountedHeader {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(CountedHeader {
header: (&mut reader).zcash_deserialize_into()?,
transaction_count: reader.read_compactsize()?.try_into().unwrap(),
})
}
}
impl ZcashSerialize for Block {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
// All block structs are validated when they are parsed.

View File

@ -182,12 +182,10 @@ pub enum Message {
///
/// Returns block headers in response to a getheaders packet.
///
/// Each block header is accompanied by a transaction count.
///
/// [Bitcoin reference](https://en.bitcoin.it/wiki/Protocol_documentation#headers)
// 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<block::Header>),
Headers(Vec<block::CountedHeader>),
/// A `getdata` message.
///

View File

@ -22,7 +22,7 @@ pub enum Response {
BlockHashes(Vec<block::Hash>),
/// A list of block headers.
BlockHeaders(Vec<block::Header>),
BlockHeaders(Vec<block::CountedHeader>),
/// A list of transactions.
Transactions(Vec<Arc<Transaction>>),

View File

@ -40,5 +40,5 @@ pub enum Response {
BlockHashes(Vec<block::Hash>),
/// The response to a `FindBlockHeaders` request.
BlockHeaders(Vec<block::Header>),
BlockHeaders(Vec<block::CountedHeader>),
}

View File

@ -644,9 +644,13 @@ impl Service<Request> for StateService {
let res: Vec<_> = res
.iter()
.map(|&hash| {
self.best_block(hash.into())
.expect("block for found hash is in the best chain")
.header
let block = self
.best_block(hash.into())
.expect("block for found hash is in the best chain");
block::CountedHeader {
transaction_count: block.transactions.len(),
header: block.header,
}
})
.collect();
async move { Ok(Response::BlockHeaders(res)) }.boxed()