diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index 1450bc9db..f226997b4 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -132,6 +132,34 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E return Err(Error::Orphan); } + // check version, enforces scheduled hard fork + if !consensus::valid_header_version(header.height, header.version) { + error!("Invalid block header version received ({}), maybe update Grin?", + header.version); + return Err(Error::InvalidBlockVersion(header.version)); + } + + if header.timestamp > + time::now_utc() + time::Duration::seconds(12 * (consensus::BLOCK_TIME_SEC as i64)) + { + // refuse blocks more than 12 blocks intervals in future (as in bitcoin) + // TODO add warning in p2p code if local time is too different from peers + return Err(Error::InvalidBlockTime); + } + + if !ctx.opts.intersects(SKIP_POW) { + let cycle_size = if ctx.opts.intersects(EASY_POW) { + global::sizeshift() + } else { + consensus::DEFAULT_SIZESHIFT + }; + debug!("Validating block with cuckoo size {}", cycle_size); + if !(ctx.pow_verifier)(header, cycle_size as u32) { + return Err(Error::InvalidPow); + } + } + + // first I/O cost, better as late as possible let prev = try!(ctx.store.get_block_header(&header.previous).map_err( &Error::StoreErr, )); @@ -144,13 +172,6 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E // time progression (but not in CI mode) return Err(Error::InvalidBlockTime); } - if header.timestamp > - time::now_utc() + time::Duration::seconds(12 * (consensus::BLOCK_TIME_SEC as i64)) - { - // refuse blocks more than 12 blocks intervals in future (as in bitcoin) - // TODO add warning in p2p code if local time is too different from peers - return Err(Error::InvalidBlockTime); - } if !ctx.opts.intersects(SKIP_POW) { // verify the proof of work and related parameters @@ -166,16 +187,6 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E if header.difficulty < difficulty { return Err(Error::DifficultyTooLow); } - - let cycle_size = if ctx.opts.intersects(EASY_POW) { - global::sizeshift() - } else { - consensus::DEFAULT_SIZESHIFT - }; - debug!("Validating block with cuckoo size {}", cycle_size); - if !(ctx.pow_verifier)(header, cycle_size as u32) { - return Err(Error::InvalidPow); - } } Ok(()) diff --git a/chain/src/types.rs b/chain/src/types.rs index 47ca289d2..636c60870 100644 --- a/chain/src/types.rs +++ b/chain/src/types.rs @@ -72,6 +72,8 @@ pub enum Error { OutputNotFound, /// output spent OutputSpent, + /// Invalid block version, either a mistake or outdated software + InvalidBlockVersion(u16), /// Internal issue when trying to save or load data from store StoreErr(grin_store::Error), /// Error serializing or deserializing a type diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 66eebb80b..9ac9af1c4 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -81,6 +81,29 @@ pub fn exceeds_weight(input_len: usize, output_len: usize, kernel_len: usize) -> kernel_len * BLOCK_KERNEL_WEIGHT > MAX_BLOCK_WEIGHT } +/// Fork every 250,000 blocks for first 2 years, simple number and just a +/// little less than 6 months. +pub const HARD_FORK_INTERVAL: u64 = 250_000; + +/// Check whether the block version is valid at a given height, implements +/// 6 months interval scheduled hard forks for the first 2 years. +pub fn valid_header_version(height: u64, version: u16) -> bool { + // uncomment below as we go from hard fork to hard fork + if height <= HARD_FORK_INTERVAL && version == 1 { + true + /* } else if height <= 2 * HARD_FORK_INTERVAL && version == 2 { + true */ + /* } else if height <= 3 * HARD_FORK_INTERVAL && version == 3 { + true */ + /* } else if height <= 4 * HARD_FORK_INTERVAL && version == 4 { + true */ + /* } else if height > 4 * HARD_FORK_INTERVAL && version > 4 { + true */ + } else { + false + } +} + /// The minimum mining difficulty we'll allow pub const MINIMUM_DIFFICULTY: u64 = 10; @@ -292,4 +315,27 @@ mod test { ); } + #[test] + fn hard_fork_1() { + assert!(valid_header_version(0, 1)); + assert!(valid_header_version(10, 1)); + assert!(!valid_header_version(10, 2)); + assert!(valid_header_version(250_000, 1)); + assert!(!valid_header_version(250_001, 1)); + assert!(!valid_header_version(500_000, 1)); + assert!(!valid_header_version(250_001, 2)); + } + + // #[test] + // fn hard_fork_2() { + // assert!(valid_header_version(0, 1)); + // assert!(valid_header_version(10, 1)); + // assert!(valid_header_version(10, 2)); + // assert!(valid_header_version(250_000, 1)); + // assert!(!valid_header_version(250_001, 1)); + // assert!(!valid_header_version(500_000, 1)); + // assert!(valid_header_version(250_001, 2)); + // assert!(valid_header_version(500_000, 2)); + // assert!(!valid_header_version(500_001, 2)); + // } } diff --git a/core/src/core/block.rs b/core/src/core/block.rs index c14c7643b..e8f060c66 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -27,14 +27,6 @@ use ser::{self, Readable, Reader, Writeable, Writer}; use global; use keychain; -bitflags! { - /// Options for block validation - pub flags BlockFeatures: u8 { - /// No flags - const DEFAULT_BLOCK = 0b00000000, - } -} - /// Errors thrown by Block validation #[derive(Debug, PartialEq)] pub enum Error { @@ -61,6 +53,8 @@ impl From for Error { /// Block header, fairly standard compared to other blockchains. #[derive(Clone, Debug, PartialEq)] pub struct BlockHeader { + /// Version of the block + pub version: u16, /// Height of this block since the genesis block (height 0) pub height: u64, /// Hash of the block previous to this in the chain. @@ -73,8 +67,6 @@ pub struct BlockHeader { pub range_proof_root: Hash, /// Merklish root of all transaction kernels in the UTXO set pub kernel_root: Hash, - /// Features specific to this block, allowing possible future extensions - pub features: BlockFeatures, /// Nonce increment used to mine this block. pub nonce: u64, /// Proof of work data. @@ -89,6 +81,7 @@ impl Default for BlockHeader { fn default() -> BlockHeader { let proof_size = global::proofsize(); BlockHeader { + version: 1, height: 0, previous: ZERO_HASH, timestamp: time::at_utc(time::Timespec { sec: 0, nsec: 0 }), @@ -97,7 +90,6 @@ impl Default for BlockHeader { utxo_root: ZERO_HASH, range_proof_root: ZERO_HASH, kernel_root: ZERO_HASH, - features: DEFAULT_BLOCK, nonce: 0, pow: Proof::zero(proof_size), } @@ -109,13 +101,13 @@ impl Writeable for BlockHeader { fn write(&self, writer: &mut W) -> Result<(), ser::Error> { ser_multiwrite!( writer, + [write_u16, self.version], [write_u64, self.height], [write_fixed_bytes, &self.previous], [write_i64, self.timestamp.to_timespec().sec], [write_fixed_bytes, &self.utxo_root], [write_fixed_bytes, &self.range_proof_root], - [write_fixed_bytes, &self.kernel_root], - [write_u8, self.features.bits()] + [write_fixed_bytes, &self.kernel_root] ); try!(writer.write_u64(self.nonce)); @@ -132,18 +124,19 @@ impl Writeable for BlockHeader { /// Deserialization of a block header impl Readable for BlockHeader { fn read(reader: &mut Reader) -> Result { - let height = try!(reader.read_u64()); - let previous = try!(Hash::read(reader)); + let (version, height) = ser_multiread!(reader, read_u16, read_u64); + let previous = Hash::read(reader)?; let timestamp = reader.read_i64()?; - let utxo_root = try!(Hash::read(reader)); - let rproof_root = try!(Hash::read(reader)); - let kernel_root = try!(Hash::read(reader)); - let (features, nonce) = ser_multiread!(reader, read_u8, read_u64); - let difficulty = try!(Difficulty::read(reader)); - let total_difficulty = try!(Difficulty::read(reader)); - let pow = try!(Proof::read(reader)); + let utxo_root = Hash::read(reader)?; + let rproof_root = Hash::read(reader)?; + let kernel_root = Hash::read(reader)?; + let nonce = reader.read_u64()?; + let difficulty = Difficulty::read(reader)?; + let total_difficulty = Difficulty::read(reader)?; + let pow = Proof::read(reader)?; Ok(BlockHeader { + version: version, height: height, previous: previous, timestamp: time::at_utc(time::Timespec { @@ -153,9 +146,6 @@ impl Readable for BlockHeader { utxo_root: utxo_root, range_proof_root: rproof_root, kernel_root: kernel_root, - features: BlockFeatures::from_bits(features).ok_or( - ser::Error::CorruptedData, - )?, pow: pow, nonce: nonce, difficulty: difficulty, diff --git a/core/src/genesis.rs b/core/src/genesis.rs index d32b3b8ce..a5e070569 100644 --- a/core/src/genesis.rs +++ b/core/src/genesis.rs @@ -29,6 +29,7 @@ pub fn genesis() -> core::Block { let empty_hash = [].hash(); core::Block { header: core::BlockHeader { + version: 1, height: 0, previous: core::hash::Hash([0xff; 32]), timestamp: time::Tm { @@ -42,7 +43,6 @@ pub fn genesis() -> core::Block { utxo_root: empty_hash, range_proof_root: empty_hash, kernel_root: empty_hash, - features: core::DEFAULT_BLOCK, nonce: global::get_genesis_nonce(), pow: core::Proof::zero(proof_size), // TODO get actual PoW solution },