Correct block version parsing.

This commit is contained in:
Henry de Valence 2020-03-18 16:57:49 -07:00 committed by Deirdre Connolly
parent 09f65c671f
commit dd8ba287bf
4 changed files with 51 additions and 16 deletions

View File

@ -75,6 +75,16 @@ impl ZcashDeserialize for BlockHeaderHash {
/// back to the genesis block (the first block in the blockchain).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BlockHeader {
/// The block's version field. This is supposed to be `4`:
///
/// > The current and only defined block version number for Zcash is 4.
///
/// but this was not enforced by the consensus rules, and defective mining
/// software created blocks with other versions, so instead it's effectively
/// a free field. The only constraint is that it must be at least `4` when
/// interpreted as an `i32`.
pub version: u32,
/// A SHA-256d hash in internal byte order of the previous blocks
/// header. This ensures no previous block can be changed without
/// also changing this blocks header.
@ -119,8 +129,7 @@ pub struct BlockHeader {
impl ZcashSerialize for BlockHeader {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
// "The current and only defined block version number for Zcash is 4."
writer.write_u32::<LittleEndian>(4)?;
writer.write_u32::<LittleEndian>(self.version)?;
self.previous_block_hash.zcash_serialize(&mut writer)?;
writer.write_all(&self.merkle_root_hash.0[..])?;
writer.write_all(&self.final_sapling_root_hash.0[..])?;
@ -134,14 +143,38 @@ impl ZcashSerialize for BlockHeader {
impl ZcashDeserialize for BlockHeader {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
// The Zcash specification says that
// "The current and only defined block version number for Zcash is 4."
let version = reader.read_u32::<LittleEndian>()?;
// but this is not actually part of the consensus rules, and in fact
// broken mining software created blocks that do not have version 4.
// There are approximately 4,000 blocks with version 536870912; this
// is the bit-reversal of the value 4, indicating that that mining pool
// reversed bit-ordering of the version field. Because the version field
// was not properly validated, these blocks were added to the chain.
//
// The only possible way to work around this is to do a similar hack
// as the overwintered field in transaction parsing, which we do here:
// treat the high bit (which zcashd interprets as a sign bit) as an
// indicator that the version field is meaningful.
//
//
let (version, future_version_flag) = {
const LOW_31_BITS: u32 = (1 << 31) - 1;
let raw_version = reader.read_u32::<LittleEndian>()?;
(raw_version & LOW_31_BITS, raw_version >> 31 != 0)
};
if version != 4 {
return Err(SerializationError::Parse("bad block header"));
if future_version_flag {
return Err(SerializationError::Parse(
"high bit was set in version field",
));
}
if version < 4 {
return Err(SerializationError::Parse("version must be at least 4"));
}
Ok(BlockHeader {
version,
previous_block_hash: BlockHeaderHash::zcash_deserialize(&mut reader)?,
merkle_root_hash: MerkleTreeRootHash(reader.read_32_bytes()?),
final_sapling_root_hash: SaplingNoteTreeRootHash(reader.read_32_bytes()?),

View File

@ -16,6 +16,7 @@ impl Arbitrary for BlockHeader {
fn arbitrary_with(_args: ()) -> Self::Strategy {
(
(4u32..2_147_483_647u32),
any::<BlockHeaderHash>(),
any::<MerkleTreeRootHash>(),
any::<SaplingNoteTreeRootHash>(),
@ -26,21 +27,23 @@ impl Arbitrary for BlockHeader {
)
.prop_map(
|(
block_hash,
version,
previous_block_hash,
merkle_root_hash,
sapling_root_hash,
final_sapling_root_hash,
timestamp,
bits,
nonce,
equihash_solution,
solution,
)| BlockHeader {
previous_block_hash: block_hash,
merkle_root_hash: merkle_root_hash,
final_sapling_root_hash: sapling_root_hash,
version,
previous_block_hash,
merkle_root_hash,
final_sapling_root_hash,
time: Utc.timestamp(timestamp, 0),
bits: bits,
nonce: nonce,
solution: equihash_solution,
bits,
nonce,
solution,
},
)
.boxed()
@ -68,6 +71,7 @@ fn blockheaderhash_from_blockheader() {
let some_bytes = [0; 32];
let blockheader = BlockHeader {
version: 4,
previous_block_hash: BlockHeaderHash(some_bytes),
merkle_root_hash: MerkleTreeRootHash(some_bytes),
final_sapling_root_hash: SaplingNoteTreeRootHash(some_bytes),

View File

@ -1,7 +1,6 @@
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_script")]
#[cfg(test)]
mod tests {
#[test]

View File

@ -16,7 +16,6 @@
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebrad")]
//#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)]
#![forbid(unsafe_code)]
// Tracing causes false positives on this lint: