diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index 80a6f92f5..2ffed136a 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -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 block’s /// header. This ensures no previous block can be changed without /// also changing this block’s header. @@ -119,8 +129,7 @@ pub struct BlockHeader { impl ZcashSerialize for BlockHeader { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - // "The current and only defined block version number for Zcash is 4." - writer.write_u32::(4)?; + writer.write_u32::(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(mut reader: R) -> Result { + // The Zcash specification says that // "The current and only defined block version number for Zcash is 4." - let version = reader.read_u32::()?; + // 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::()?; + (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()?), diff --git a/zebra-chain/src/block/tests.rs b/zebra-chain/src/block/tests.rs index 750d5d688..c64f9db8c 100644 --- a/zebra-chain/src/block/tests.rs +++ b/zebra-chain/src/block/tests.rs @@ -16,6 +16,7 @@ impl Arbitrary for BlockHeader { fn arbitrary_with(_args: ()) -> Self::Strategy { ( + (4u32..2_147_483_647u32), any::(), any::(), any::(), @@ -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), diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs index cb4261a09..dd0318305 100644 --- a/zebra-script/src/lib.rs +++ b/zebra-script/src/lib.rs @@ -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] diff --git a/zebrad/src/lib.rs b/zebrad/src/lib.rs index 1fc59ae8b..acba1c600 100644 --- a/zebrad/src/lib.rs +++ b/zebrad/src/lib.rs @@ -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: