zebra_chain/block/
serialize.rs

1//! Serialization and deserialization for Zcash blocks.
2
3use std::{borrow::Borrow, io};
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6use chrono::{TimeZone, Utc};
7
8use crate::{
9    block::{header::ZCASH_BLOCK_VERSION, merkle, Block, CountedHeader, Hash, Header},
10    serialization::{
11        CompactSizeMessage, ReadZcashExt, SerializationError, ZcashDeserialize,
12        ZcashDeserializeInto, ZcashSerialize,
13    },
14    work::{difficulty::CompactDifficulty, equihash},
15};
16
17/// The maximum size of a Zcash block, in bytes.
18///
19/// Post-Sapling, this is also the maximum size of a transaction
20/// in the Zcash specification. (But since blocks also contain a
21/// block header and transaction count, the maximum size of a
22/// transaction in the chain is approximately 1.5 kB smaller.)
23pub const MAX_BLOCK_BYTES: u64 = 2_000_000;
24
25/// Checks if a block header version is valid.
26///
27/// Zebra could encounter a [`Header`] with an invalid version when serializing a block header constructed
28/// in memory with the wrong version in tests or the getblocktemplate RPC.
29///
30/// The getblocktemplate RPC generates a template with version 4. The miner generates the actual block,
31/// and then we deserialize it and do this check.
32///
33/// All other blocks are deserialized when we receive them, and never modified,
34/// so the deserialisation would pick up any errors.
35fn check_version(version: u32) -> Result<(), &'static str> {
36    match version {
37        // The Zcash specification says that:
38        // "The current and only defined block version number for Zcash is 4."
39        // but this is not actually part of the consensus rules, and in fact
40        // broken mining software created blocks that do not have version 4.
41        // There are approximately 4,000 blocks with version 536870912; this
42        // is the bit-reversal of the value 4, indicating that that mining pool
43        // reversed bit-ordering of the version field. Because the version field
44        // was not properly validated, these blocks were added to the chain.
45        //
46        // The only possible way to work around this is to do a similar hack
47        // as the overwintered field in transaction parsing, which we do here:
48        // treat the high bit (which zcashd interprets as a sign bit) as an
49        // indicator that the version field is meaningful.
50        version if version >> 31 != 0 => Err("high bit was set in version field"),
51
52        // # Consensus
53        //
54        // > The block version number MUST be greater than or equal to 4.
55        //
56        // https://zips.z.cash/protocol/protocol.pdf#blockheader
57        version if version < ZCASH_BLOCK_VERSION => Err("version must be at least 4"),
58
59        _ => Ok(()),
60    }
61}
62
63impl ZcashSerialize for Header {
64    #[allow(clippy::unwrap_in_result)]
65    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
66        check_version(self.version).map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))?;
67
68        writer.write_u32::<LittleEndian>(self.version)?;
69        self.previous_block_hash.zcash_serialize(&mut writer)?;
70        writer.write_all(&self.merkle_root.0[..])?;
71        writer.write_all(&self.commitment_bytes[..])?;
72        writer.write_u32::<LittleEndian>(
73            self.time
74                .timestamp()
75                .try_into()
76                .expect("deserialized and generated timestamps are u32 values"),
77        )?;
78        writer.write_u32::<LittleEndian>(self.difficulty_threshold.0)?;
79        writer.write_all(&self.nonce[..])?;
80        self.solution.zcash_serialize(&mut writer)?;
81        Ok(())
82    }
83}
84
85impl ZcashDeserialize for Header {
86    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
87        let version = reader.read_u32::<LittleEndian>()?;
88        check_version(version).map_err(SerializationError::Parse)?;
89
90        Ok(Header {
91            version,
92            previous_block_hash: Hash::zcash_deserialize(&mut reader)?,
93            merkle_root: merkle::Root(reader.read_32_bytes()?),
94            commitment_bytes: reader.read_32_bytes()?.into(),
95            // This can't panic, because all u32 values are valid `Utc.timestamp`s
96            time: Utc
97                .timestamp_opt(reader.read_u32::<LittleEndian>()?.into(), 0)
98                .single()
99                .ok_or(SerializationError::Parse(
100                    "out-of-range number of seconds and/or invalid nanosecond",
101                ))?,
102            difficulty_threshold: CompactDifficulty(reader.read_u32::<LittleEndian>()?),
103            nonce: reader.read_32_bytes()?.into(),
104            solution: equihash::Solution::zcash_deserialize(reader)?,
105        })
106    }
107}
108
109impl ZcashSerialize for CountedHeader {
110    #[allow(clippy::unwrap_in_result)]
111    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
112        self.header.zcash_serialize(&mut writer)?;
113
114        // A header-only message has zero transactions in it.
115        let transaction_count =
116            CompactSizeMessage::try_from(0).expect("0 is below the message size limit");
117        transaction_count.zcash_serialize(&mut writer)?;
118
119        Ok(())
120    }
121}
122
123impl ZcashDeserialize for CountedHeader {
124    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
125        let header = CountedHeader {
126            header: (&mut reader).zcash_deserialize_into()?,
127        };
128
129        // We ignore the number of transactions in a header-only message,
130        // it should always be zero.
131        let _transaction_count: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
132
133        Ok(header)
134    }
135}
136
137impl ZcashSerialize for Block {
138    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
139        // All block structs are validated when they are parsed.
140        // So we don't need to check MAX_BLOCK_BYTES here, until
141        // we start generating our own blocks (see #483).
142        self.header.zcash_serialize(&mut writer)?;
143        self.transactions.zcash_serialize(&mut writer)?;
144        Ok(())
145    }
146}
147
148impl ZcashDeserialize for Block {
149    fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
150        // # Consensus
151        //
152        // > The size of a block MUST be less than or equal to 2000000 bytes.
153        //
154        // https://zips.z.cash/protocol/protocol.pdf#blockheader
155        //
156        // If the limit is reached, we'll get an UnexpectedEof error
157        let limited_reader = &mut reader.take(MAX_BLOCK_BYTES);
158        Ok(Block {
159            header: limited_reader.zcash_deserialize_into()?,
160            transactions: limited_reader.zcash_deserialize_into()?,
161        })
162    }
163}
164
165/// A serialized block.
166///
167/// Stores bytes that are guaranteed to be deserializable into a [`Block`].
168#[derive(Clone, Debug, Eq, Hash, PartialEq)]
169pub struct SerializedBlock {
170    bytes: Vec<u8>,
171}
172
173/// Build a [`SerializedBlock`] by serializing a block.
174impl<B: Borrow<Block>> From<B> for SerializedBlock {
175    fn from(block: B) -> Self {
176        SerializedBlock {
177            bytes: block
178                .borrow()
179                .zcash_serialize_to_vec()
180                .expect("Writing to a `Vec` should never fail"),
181        }
182    }
183}
184
185/// Access the serialized bytes of a [`SerializedBlock`].
186impl AsRef<[u8]> for SerializedBlock {
187    fn as_ref(&self) -> &[u8] {
188        self.bytes.as_ref()
189    }
190}
191
192impl From<Vec<u8>> for SerializedBlock {
193    fn from(bytes: Vec<u8>) -> Self {
194        Self { bytes }
195    }
196}