From 947dcf56757311680bfa796a48b57f21648579a4 Mon Sep 17 00:00:00 2001 From: Noah Lanson Date: Tue, 6 Sep 2022 13:27:52 +1000 Subject: [PATCH] Add new type for block version --- src/blockdata/block.rs | 110 ++++++++++++++++++++++++++++++++----- src/blockdata/constants.rs | 16 +++--- src/lib.rs | 2 +- src/util/bip152.rs | 4 +- 4 files changed, 106 insertions(+), 26 deletions(-) diff --git a/src/blockdata/block.rs b/src/blockdata/block.rs index 94ab2c20..5d9c6635 100644 --- a/src/blockdata/block.rs +++ b/src/blockdata/block.rs @@ -19,13 +19,14 @@ use crate::util::hash::bitcoin_merkle_root; use crate::hashes::{Hash, HashEngine}; use crate::hash_types::{Wtxid, BlockHash, TxMerkleNode, WitnessMerkleNode, WitnessCommitment}; use crate::util::uint::Uint256; -use crate::consensus::encode::Encodable; +use crate::consensus::{encode, Encodable, Decodable}; use crate::network::constants::Network; use crate::blockdata::transaction::Transaction; use crate::blockdata::constants::{max_target, WITNESS_SCALE_FACTOR}; use crate::blockdata::script; use crate::VarInt; use crate::internal_macros::impl_consensus_encoding; +use crate::io; /// Bitcoin block header. /// @@ -41,13 +42,8 @@ use crate::internal_macros::impl_consensus_encoding; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] pub struct BlockHeader { - /// Originally protocol version, but repurposed for soft-fork signaling. - /// - /// ### Relevant BIPs - /// - /// * [BIP9 - Version bits with timeout and delay](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki) (current usage) - /// * [BIP34 - Block v2, Height in Coinbase](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki) - pub version: i32, + /// Block version, now repurposed for soft fork signalling. + pub version: BlockVersion, /// Reference to the previous block in the chain. pub prev_blockhash: BlockHash, /// The root hash of the merkle tree of transactions in the block. @@ -159,6 +155,72 @@ impl BlockHeader { } } +/// Bitcoin block version number +/// +/// Originally used as a protocol version, but repurposed for soft-fork signaling. +/// +/// The inner value is a signed integer in Bitcoin Core for historical reasons, if version bits is +/// being used the top three bits must be 001, this gives us a useful range of [0x20000000...0x3FFFFFFF]. +/// +/// > When a block nVersion does not have top bits 001, it is treated as if all bits are 0 for the purposes of deployments. +/// +/// ### Relevant BIPs +/// +/// * [BIP9 - Version bits with timeout and delay](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki) (current usage) +/// * [BIP34 - Block v2, Height in Coinbase](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki) +#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct BlockVersion(pub i32); + +impl BlockVersion { + /// BIP-9 compatible version number that does not signal for any softforks. + pub const NO_SOFT_FORK_SIGNALLING: Self = Self(Self::USE_VERSION_BITS as i32); + /// BIP-9 soft fork signal bits mask. + const VERSION_BITS_MASK: u32 = 0x1FFF_FFFF; + /// 32bit value starting with `001` to use version bits. + /// + /// The value has the top three bits `001` which enables the use of version bits to signal for soft forks. + const USE_VERSION_BITS: u32 = 0x2000_0000; + + /// Check whether the version number is signalling a soft fork at the given bit. + /// + /// A block is signalling for a soft fork under BIP-9 if the first 3 bits are `001` and + /// the version bit for the specific soft fork is toggled on. + pub fn is_signalling_soft_fork(&self, bit: u8) -> bool { + // Only bits [0, 28] inclusive are used for signalling. + if bit > 28 { + return false; + } + + // To signal using version bits, the first three bits must be `001`. + if (self.0 as u32) & !Self::VERSION_BITS_MASK != Self::USE_VERSION_BITS { + return false; + } + + // The bit is set if signalling a soft fork. + (self.0 as u32 & Self::VERSION_BITS_MASK) & (1 << bit) > 0 + } +} + +impl Default for BlockVersion { + fn default() -> BlockVersion { + Self::NO_SOFT_FORK_SIGNALLING + } +} + +impl Encodable for BlockVersion { + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for BlockVersion { + fn consensus_decode(r: &mut R) -> Result { + Decodable::consensus_decode(r).map(BlockVersion) + } +} + /// Bitcoin block. /// /// A collection of transactions with an attached proof of work. @@ -323,7 +385,7 @@ impl Block { // number (including a sign bit). Height is the height of the mined // block in the block chain, where the genesis block is height zero (0). - if self.header.version < 2 { + if self.header.version < BlockVersion(2) { return Err(Bip34Error::Unsupported); } @@ -385,7 +447,7 @@ impl std::error::Error for Bip34Error { mod tests { use crate::hashes::hex::FromHex; - use crate::blockdata::block::{Block, BlockHeader}; + use crate::blockdata::block::{Block, BlockHeader, BlockVersion}; use crate::consensus::encode::{deserialize, serialize}; use crate::util::uint::Uint256; use crate::util::Error::{BlockBadTarget, BlockBadProofOfWork}; @@ -427,7 +489,7 @@ mod tests { assert!(decode.is_ok()); assert!(bad_decode.is_err()); let real_decode = decode.unwrap(); - assert_eq!(real_decode.header.version, 1); + assert_eq!(real_decode.header.version, BlockVersion(1)); assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash); assert_eq!(real_decode.header.merkle_root, real_decode.compute_merkle_root().unwrap()); assert_eq!(serialize(&real_decode.header.merkle_root), merkle); @@ -462,7 +524,7 @@ mod tests { assert!(decode.is_ok()); let real_decode = decode.unwrap(); - assert_eq!(real_decode.header.version, 0x20000000); // VERSIONBITS but no bits set + assert_eq!(real_decode.header.version, BlockVersion(BlockVersion::USE_VERSION_BITS as i32)); // VERSIONBITS but no bits set assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash); assert_eq!(serialize(&real_decode.header.merkle_root), merkle); assert_eq!(real_decode.header.merkle_root, real_decode.compute_merkle_root().unwrap()); @@ -489,13 +551,13 @@ mod tests { let decode: Result = deserialize(&block); assert!(decode.is_ok()); let real_decode = decode.unwrap(); - assert_eq!(real_decode.header.version, 2147483647); + assert_eq!(real_decode.header.version, BlockVersion(2147483647)); let block2 = Vec::from_hex("000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); let decode2: Result = deserialize(&block2); assert!(decode2.is_ok()); let real_decode2 = decode2.unwrap(); - assert_eq!(real_decode2.header.version, -2147483648); + assert_eq!(real_decode2.header.version, BlockVersion(-2147483648)); } #[test] @@ -512,7 +574,7 @@ mod tests { // test with modified header let mut invalid_header: BlockHeader = some_header; - invalid_header.version += 1; + invalid_header.version.0 += 1; match invalid_header.validate_pow(&invalid_header.target()) { Err(BlockBadProofOfWork) => (), _ => panic!("unexpected result from validate_pow"), @@ -527,6 +589,24 @@ mod tests { assert_eq!(header.bits, BlockHeader::compact_target_from_u256(&header.target())); } + + #[test] + fn soft_fork_signalling() { + for i in 0..31 { + let version_int = (0x20000000u32 ^ 1< Block { Network::Bitcoin => { Block { header: BlockHeader { - version: 1, + version: BlockVersion(1), prev_blockhash: Hash::all_zeros(), merkle_root, time: 1231006505, @@ -128,7 +128,7 @@ pub fn genesis_block(network: Network) -> Block { Network::Testnet => { Block { header: BlockHeader { - version: 1, + version: BlockVersion(1), prev_blockhash: Hash::all_zeros(), merkle_root, time: 1296688602, @@ -141,7 +141,7 @@ pub fn genesis_block(network: Network) -> Block { Network::Signet => { Block { header: BlockHeader { - version: 1, + version: BlockVersion(1), prev_blockhash: Hash::all_zeros(), merkle_root, time: 1598918400, @@ -154,7 +154,7 @@ pub fn genesis_block(network: Network) -> Block { Network::Regtest => { Block { header: BlockHeader { - version: 1, + version: BlockVersion(1), prev_blockhash: Hash::all_zeros(), merkle_root, time: 1296688602, @@ -227,7 +227,7 @@ mod test { fn bitcoin_genesis_full_block() { let gen = genesis_block(Network::Bitcoin); - assert_eq!(gen.header.version, 1); + assert_eq!(gen.header.version, BlockVersion(1)); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.merkle_root.to_hex(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); @@ -240,7 +240,7 @@ mod test { #[test] fn testnet_genesis_full_block() { let gen = genesis_block(Network::Testnet); - assert_eq!(gen.header.version, 1); + assert_eq!(gen.header.version, BlockVersion(1)); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.merkle_root.to_hex(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); assert_eq!(gen.header.time, 1296688602); @@ -252,7 +252,7 @@ mod test { #[test] fn signet_genesis_full_block() { let gen = genesis_block(Network::Signet); - assert_eq!(gen.header.version, 1); + assert_eq!(gen.header.version, BlockVersion(1)); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.merkle_root.to_hex(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); assert_eq!(gen.header.time, 1598918400); diff --git a/src/lib.rs b/src/lib.rs index 8ea10b6c..3dfe2a80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ use std::io; use core2::io; pub use crate::address::{Address, AddressType}; -pub use crate::blockdata::block::{self, Block, BlockHeader}; +pub use crate::blockdata::block::{self, Block, BlockHeader, BlockVersion}; pub use crate::blockdata::locktime::{self, absolute, relative}; pub use crate::blockdata::script::{self, Script}; #[allow(deprecated)] diff --git a/src/util/bip152.rs b/src/util/bip152.rs index 5ffc9c59..7ff07040 100644 --- a/src/util/bip152.rs +++ b/src/util/bip152.rs @@ -376,7 +376,7 @@ mod test { use crate::consensus::encode::{deserialize, serialize}; use crate::hashes::hex::FromHex; use crate::{ - Block, BlockHash, BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, + Block, BlockHash, BlockHeader, BlockVersion, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, }; @@ -397,7 +397,7 @@ mod test { fn dummy_block() -> Block { Block { header: BlockHeader { - version: 1, + version: BlockVersion(1), prev_blockhash: BlockHash::hash(&[0]), merkle_root: TxMerkleNode::hash(&[1]), time: 2,