From 3c5e2b295837bc1a76cae684e0c95caf5f8462bb Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Tue, 15 Nov 2016 14:37:49 -0800 Subject: [PATCH] Block now uses the new Target type. Consensus next_target calculation adjusting every block based on deviation and increasing Cuckoo size gradually. Block time of a minute until we learn more from mining ecosystem, so enough PoW attempts can be made even on CPUs. --- core/src/consensus.rs | 138 +++++++++++++++++++++++++++++++++++----- core/src/core/block.rs | 36 +++++++---- core/src/core/hash.rs | 6 +- core/src/core/mod.rs | 23 ++++++- core/src/core/target.rs | 23 +++---- core/src/genesis.rs | 4 +- core/src/pow/mod.rs | 29 +++++---- 7 files changed, 201 insertions(+), 58 deletions(-) diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 8b9e0a8da..d26578997 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -19,40 +19,146 @@ //! enough, consensus-relevant constants and short functions should be kept //! here. +use std::cmp; + +use core::target::Target; + /// The block subsidy amount pub const REWARD: u64 = 1_000_000_000; -/// Block interval, in seconds, the network will tune its difficulty for. Note -/// that contrarily to bitcoin, we may reduce this value in the future as -/// networks improve and block propagation is optimized (adjusting the reward -/// accordingly). -pub const BLOCK_TIME_SEC: u8 = 30; +/// Block interval, in seconds, the network will tune its next_target for. Note +/// that we may reduce this value in the future as we get more data on mining +/// with Cuckoo Cycle, networks improve and block propagation is optimized +/// (adjusting the reward accordingly). +pub const BLOCK_TIME_SEC: u8 = 60; /// Cuckoo-cycle proof size (cycle length) pub const PROOFSIZE: usize = 42; -/// Default Cuckoo Cycle size shift used is 28. We may decide to increase it -/// when hashrate increases. May also start lower. -pub const SIZESHIFT: u32 = 28; +/// Origin Cuckoo Cycle size shift used by the genesis block. +pub const DEFAULT_SIZESHIFT: u8 = 25; + +/// Maximum Cuckoo Cycle size shift we'll ever use. We adopt a schedule that +/// progressively increases the size as the target becomes lower. +/// Start => 25 +/// MAX_TARGET >> 12 => 26 +/// MAX_TARGET >> 20 => 27 +/// MAX_TARGET >> 28 => 28 +/// MAX_TARGET >> 36 => 29 +pub const MAX_SIZESHIFT: u8 = 29; /// Default Cuckoo Cycle easiness, high enough to have good likeliness to find /// a solution. pub const EASINESS: u32 = 50; +/// Difficulty adjustment somewhat inspired by Ethereum's. Tuned to add or +/// remove 1/1024th of the target for each 10 seconds of deviation from the 30 +/// seconds block time. Increases Cuckoo size shift by one when next_target +/// reaches soft max. +pub fn next_target(ts: i64, prev_ts: i64, prev_target: Target, prev_cuckoo_sz: u8) -> (Target, u8) { + // increase the cuckoo size when the target gets lower than the soft min as + // long as we're not at the max size already; target gets 2x to compensate for + // increased next_target + let soft_min = SOFT_MIN_TARGET >> (((prev_cuckoo_sz - DEFAULT_SIZESHIFT) * 8) as usize); + let (ptarget, clen) = if prev_target < soft_min && prev_cuckoo_sz < MAX_SIZESHIFT { + (prev_target << 1, prev_cuckoo_sz + 1) + } else { + (prev_target, prev_cuckoo_sz) + }; + + // target is increased/decreased by multiples of 1/1024th of itself + let incdec = ptarget >> 10; + // signed deviation from desired value divided by ten and bounded in [-3, 3] + let delta = cmp::max(cmp::min((ts - prev_ts - (BLOCK_TIME_SEC as i64)) / 10, 3), + -3); + // increase or decrease the target based on the sign of delta by a shift of + // |delta|-1; keep as-is for delta of zero + let new_target = match delta { + 1...3 => ptarget + (incdec << ((delta - 1) as usize)), + -3...-1 => ptarget - (incdec << ((-delta - 1) as usize)), + _ => ptarget, + }; + + // cannot exceed the maximum target + if new_target > MAX_TARGET { + (MAX_TARGET, clen) + } else { + (new_target, clen) + } +} + +/// Max target hash, lowest next_target +pub const MAX_TARGET: Target = Target([0xf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff]); + +/// Target limit under which we start increasing the size shift on Cuckoo cycle. +pub const SOFT_MIN_TARGET: Target = Target([0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff]); + /// Default number of blocks in the past when cross-block cut-through will start -/// happening. Needs to be long enough to not overlap with a long reorg. Rational +/// happening. Needs to be long enough to not overlap with a long reorg. +/// Rational /// behind the value is the longest bitcoin fork was about 30 blocks, so 5h. We /// add an order of magnitude to be safe and round to 48h of blocks to make it /// easier to reason about. pub const CUT_THROUGH_HORIZON: u32 = 48 * 3600 / (BLOCK_TIME_SEC as u32); -/// Max target hash, lowest difficulty -pub const MAX_TARGET: [u32; PROOFSIZE] = - [0xfff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff]; - /// The maximum number of inputs or outputs a transaction may have /// and be deserializable. Only for DoS protection. pub const MAX_IN_OUT_LEN: u64 = 50000; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + /// Checks different next_target adjustments and difficulty boundaries + fn next_target_adjustment() { + // can't do lower than min + assert_eq!(next_target(60, 0, MAX_TARGET, 26), (MAX_TARGET, 26)); + assert_eq!(next_target(90, 30, MAX_TARGET, 26), (MAX_TARGET, 26)); + assert_eq!(next_target(60, 0, MAX_TARGET, 26), (MAX_TARGET, 26)); + + // lower next_target if gap too short, even negative + assert_eq!(next_target(50, 0, MAX_TARGET, 26).0, + MAX_TARGET - (MAX_TARGET >> 10)); + assert_eq!(next_target(40, 0, MAX_TARGET, 26).0, + MAX_TARGET - ((MAX_TARGET >> 10) << 1)); + assert_eq!(next_target(0, 20, MAX_TARGET, 26).0, + MAX_TARGET - ((MAX_TARGET >> 10) << 2)); + + // raise next_target if gap too wide + let lower_target = MAX_TARGET >> 8; + assert_eq!(next_target(70, 0, lower_target, 26).0, + lower_target + (lower_target >> 10)); + assert_eq!(next_target(80, 0, lower_target, 26).0, + lower_target + ((lower_target >> 10) << 1)); + assert_eq!(next_target(200, 0, lower_target, 26).0, + lower_target + ((lower_target >> 10) << 2)); + + // close enough, no adjustment + assert_eq!(next_target(65, 0, lower_target, 26).0, lower_target); + assert_eq!(next_target(55, 0, lower_target, 26).0, lower_target); + + // increase cuckoo size if next_target goes above soft max, target is doubled, + // up to 29 + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 1, 25), + ((SOFT_MIN_TARGET >> 1) << 1, 26)); + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 9, 26), + ((SOFT_MIN_TARGET >> 9) << 1, 27)); + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 17, 27), + ((SOFT_MIN_TARGET >> 17) << 1, 28)); + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 25, 28), + ((SOFT_MIN_TARGET >> 25) << 1, 29)); + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 33, 29), + (SOFT_MIN_TARGET >> 33, 29)); + + // should only increase on the according previous size + assert_eq!(next_target(60, 0, SOFT_MIN_TARGET >> 1, 26), + (SOFT_MIN_TARGET >> 1, 26)); + } +} diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 9be84eb81..5f223e08c 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -23,19 +23,28 @@ use std::collections::HashSet; use core::Committed; use core::{Input, Output, Proof, TxProof, Transaction}; use core::transaction::merkle_inputs_outputs; -use consensus::{PROOFSIZE, REWARD, MAX_IN_OUT_LEN}; +use consensus::{PROOFSIZE, REWARD, DEFAULT_SIZESHIFT, MAX_IN_OUT_LEN, MAX_TARGET}; use core::hash::{Hash, Hashed, ZERO_HASH}; +use core::target::Target; use ser::{self, Readable, Reader, Writeable, Writer}; /// Block header, fairly standard compared to other blockchains. pub struct BlockHeader { + /// Height of this block since the genesis block (height 0) pub height: u64, + /// Hash of the block previous to this in the chain. pub previous: Hash, + /// Timestamp at which the block was built. pub timestamp: time::Tm, - pub td: u64, // total difficulty up to this block + /// Length of the cuckoo cycle used to mine this block. + pub cuckoo_len: u8, + /// Difficulty used to mine the block. + pub target: Target, pub utxo_merkle: Hash, pub tx_merkle: Hash, + /// Nonce increment used to mine this block. pub nonce: u64, + /// Proof of work data. pub pow: Proof, } @@ -45,7 +54,8 @@ impl Default for BlockHeader { height: 0, previous: ZERO_HASH, timestamp: time::at_utc(time::Timespec { sec: 0, nsec: 0 }), - td: 0, + cuckoo_len: 20, // only for tests + target: MAX_TARGET, utxo_merkle: ZERO_HASH, tx_merkle: ZERO_HASH, nonce: 0, @@ -62,6 +72,9 @@ impl Writeable for BlockHeader { [write_u64, self.height], [write_fixed_bytes, &self.previous], [write_i64, self.timestamp.to_timespec().sec], + [write_u8, self.cuckoo_len]); + try!(self.target.write(writer)); + ser_multiwrite!(writer, [write_fixed_bytes, &self.utxo_merkle], [write_fixed_bytes, &self.tx_merkle]); // make sure to not introduce any variable length data before the nonce to @@ -71,7 +84,7 @@ impl Writeable for BlockHeader { for n in 0..42 { try!(writer.write_u32(self.pow.0[n])); } - writer.write_u64(self.td) + Ok(()) } } @@ -114,13 +127,11 @@ impl Writeable for Block { /// from a binary stream. impl Readable for Block { fn read(reader: &mut Reader) -> Result { - let (height, previous, timestamp, utxo_merkle, tx_merkle, nonce) = ser_multiread!(reader, - read_u64, - read_32_bytes, - read_i64, - read_32_bytes, - read_32_bytes, - read_u64); + let (height, previous, timestamp, cuckoo_len) = + ser_multiread!(reader, read_u64, read_32_bytes, read_i64, read_u8); + let target = try!(Target::read(reader)); + let (utxo_merkle, tx_merkle, nonce) = + ser_multiread!(reader, read_32_bytes, read_32_bytes, read_u64); // cuckoo cycle of 42 nodes let mut pow = [0; PROOFSIZE]; @@ -148,7 +159,8 @@ impl Readable for Block { sec: timestamp, nsec: 0, }), - td: td, + cuckoo_len: cuckoo_len, + target: target, utxo_merkle: Hash::from_vec(utxo_merkle), tx_merkle: Hash::from_vec(tx_merkle), pow: Proof(pow), diff --git a/core/src/core/hash.rs b/core/src/core/hash.rs index bf7940ea7..55ba3358d 100644 --- a/core/src/core/hash.rs +++ b/core/src/core/hash.rs @@ -23,6 +23,8 @@ use tiny_keccak::Keccak; use ser::{self, AsFixedBytes}; +pub const ZERO_HASH: Hash = Hash([0; 32]); + /// A hash to uniquely (or close enough) identify one of the main blockchain /// constructs. Used pervasively for blocks, transactions and ouputs. #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -56,15 +58,13 @@ impl Hash { } } -pub const ZERO_HASH: Hash = Hash([0; 32]); - /// Serializer that outputs a hash of the serialized object pub struct HashWriter { state: Keccak, } impl HashWriter { - fn finalize(self, output: &mut [u8]) { + pub fn finalize(self, output: &mut [u8]) { self.state.finalize(output); } } diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 33d05f8a2..1f7e25be5 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -29,7 +29,7 @@ use secp::pedersen::*; use consensus::PROOFSIZE; pub use self::block::{Block, BlockHeader}; pub use self::transaction::{Transaction, Input, Output, TxProof}; -use self::hash::{Hash, Hashed, ZERO_HASH}; +use self::hash::{Hash, Hashed, HashWriter, ZERO_HASH}; use ser::{Writeable, Writer, Error}; /// Implemented by types that hold inputs and outputs including Pedersen @@ -109,11 +109,24 @@ impl Clone for Proof { } } +impl Hashed for Proof { + fn hash(&self) -> Hash { + let mut hasher = HashWriter::default(); + let mut ret = [0; 32]; + for p in self.0.iter() { + hasher.write_u32(*p).unwrap(); + } + hasher.finalize(&mut ret); + Hash(ret) + } +} + impl Proof { /// Builds a proof with all bytes zeroed out pub fn zero() -> Proof { Proof([0; 42]) } + /// Builds a proof from a vector of exactly PROOFSIZE (42) u32. pub fn from_vec(v: Vec) -> Proof { assert!(v.len() == PROOFSIZE); @@ -123,6 +136,7 @@ impl Proof { } Proof(p) } + /// Converts the proof to a vector of u64s pub fn to_u64s(&self) -> Vec { let mut nonces = Vec::with_capacity(PROOFSIZE); @@ -131,10 +145,17 @@ impl Proof { } nonces } + /// Converts the proof to a vector of u32s pub fn to_u32s(&self) -> Vec { self.0.to_vec() } + + /// Converts the proof to a proof-of-work Target so they can be compared. + /// Hashes the Cuckoo Proof data. + pub fn to_target(self) -> target::Target { + target::Target(self.hash().0) + } } /// Two hashes that will get hashed together in a Merkle tree to build the next diff --git a/core/src/core/target.rs b/core/src/core/target.rs index 6b8f7df01..95acce257 100644 --- a/core/src/core/target.rs +++ b/core/src/core/target.rs @@ -244,14 +244,15 @@ mod test { #[test] fn shift_truncate() { - assert_eq!((Target::join(0, 0xffff).unwrap() >> 8) << 8, Target::join(0, 0xff00).unwrap()); + assert_eq!((Target::join(0, 0xffff).unwrap() >> 8) << 8, + Target::join(0, 0xff00).unwrap()); } #[test] fn split_fit() { let t = Target::join(10 * 8, ::std::u32::MAX).unwrap(); let (exp, mant) = t.split(); - assert_eq!(exp, 10*8); + assert_eq!(exp, 10 * 8); assert_eq!(mant, ::std::u32::MAX); } @@ -268,29 +269,29 @@ mod test { #[test] fn addition() { assert_eq!(Target::join(0, 10).unwrap() + Target::join(0, 20).unwrap(), - Target::join(0, 30).unwrap()); + Target::join(0, 30).unwrap()); // single overflow assert_eq!(Target::join(0, 250).unwrap() + Target::join(0, 250).unwrap(), - Target::join(0, 500).unwrap()); + Target::join(0, 500).unwrap()); // multiple overflows assert_eq!(Target::join(0, 300).unwrap() + Target::join(0, 300).unwrap(), - Target::join(0, 600).unwrap()); + Target::join(0, 600).unwrap()); assert_eq!(Target::join(10, 300).unwrap() + Target::join(10, 300).unwrap(), - Target::join(10, 600).unwrap()); + Target::join(10, 600).unwrap()); // cascading overflows assert_eq!(Target::join(8, 0xffff).unwrap() + Target::join(8, 0xffff).unwrap(), - Target::join(8, 0x1fffe).unwrap()); + Target::join(8, 0x1fffe).unwrap()); } #[test] fn subtraction() { assert_eq!(Target::join(0, 40).unwrap() - Target::join(0, 10).unwrap(), - Target::join(0, 30).unwrap()); + Target::join(0, 30).unwrap()); assert_eq!(Target::join(0, 300).unwrap() - Target::join(0, 100).unwrap(), - Target::join(0, 200).unwrap()); + Target::join(0, 200).unwrap()); assert_eq!(Target::join(0, 0xffff).unwrap() - Target::join(0, 0xffff).unwrap(), - Target::join(0, 0).unwrap()); + Target::join(0, 0).unwrap()); assert_eq!(Target::join(0, 0xffff).unwrap() - Target::join(0, 0xff01).unwrap(), - Target::join(0, 0xfe).unwrap()); + Target::join(0, 0xfe).unwrap()); } } diff --git a/core/src/genesis.rs b/core/src/genesis.rs index 58a76d68f..40b8692d6 100644 --- a/core/src/genesis.rs +++ b/core/src/genesis.rs @@ -17,6 +17,7 @@ use time; use core; +use consensus::{DEFAULT_SIZESHIFT, MAX_TARGET}; use tiny_keccak::Keccak; @@ -38,7 +39,8 @@ pub fn genesis() -> core::Block { tm_mday: 4, ..time::empty_tm() }, - td: 0, + cuckoo_len: DEFAULT_SIZESHIFT, + target: MAX_TARGET, utxo_merkle: core::hash::Hash::from_vec(empty_h.to_vec()), tx_merkle: core::hash::Hash::from_vec(empty_h.to_vec()), nonce: 0, diff --git a/core/src/pow/mod.rs b/core/src/pow/mod.rs index 936a0eddf..fdefd8615 100644 --- a/core/src/pow/mod.rs +++ b/core/src/pow/mod.rs @@ -27,9 +27,10 @@ mod cuckoo; use time; -use consensus::{SIZESHIFT, EASINESS}; +use consensus::EASINESS; use core::{Block, Proof}; use core::hash::{Hash, Hashed}; +use core::target::Target; use pow::cuckoo::{Cuckoo, Miner, Error}; use ser; @@ -87,21 +88,21 @@ impl PowHeader { } /// Validates the proof of work of a given header. -pub fn verify(b: &Block, target: Proof) -> bool { - verify_size(b, target, SIZESHIFT) +pub fn verify(b: &Block, target: Target) -> bool { + verify_size(b, target, b.header.cuckoo_len as u32) } /// Same as default verify function but uses the much easier Cuckoo20 (mostly /// for tests). -pub fn verify20(b: &Block, target: Proof) -> bool { +pub fn verify20(b: &Block, target: Target) -> bool { verify_size(b, target, 20) } -pub fn verify_size(b: &Block, target: Proof, sizeshift: u32) -> bool { +pub fn verify_size(b: &Block, target: Target, sizeshift: u32) -> bool { let hash = PowHeader::from_block(b).hash(); // make sure the hash is smaller than our target before going into more // expensive validation - if target < b.header.pow { + if target < b.header.pow.to_target() { return false; } Cuckoo::new(hash.to_slice(), sizeshift).verify(b.header.pow, EASINESS as u64) @@ -110,17 +111,17 @@ pub fn verify_size(b: &Block, target: Proof, sizeshift: u32) -> bool { /// Runs a naive single-threaded proof of work computation over the provided /// block, until the required difficulty target is reached. May take a /// while for a low target... -pub fn pow(b: &Block, target: Proof) -> Result<(Proof, u64), Error> { - pow_size(b, target, SIZESHIFT) +pub fn pow(b: &Block, target: Target) -> Result<(Proof, u64), Error> { + pow_size(b, target, b.header.cuckoo_len as u32) } /// Same as default pow function but uses the much easier Cuckoo20 (mostly for /// tests). -pub fn pow20(b: &Block, target: Proof) -> Result<(Proof, u64), Error> { +pub fn pow20(b: &Block, target: Target) -> Result<(Proof, u64), Error> { pow_size(b, target, 20) } -fn pow_size(b: &Block, target: Proof, sizeshift: u32) -> Result<(Proof, u64), Error> { +fn pow_size(b: &Block, target: Target, sizeshift: u32) -> Result<(Proof, u64), Error> { let mut pow_header = PowHeader::from_block(b); let start_nonce = pow_header.nonce; @@ -133,7 +134,7 @@ fn pow_size(b: &Block, target: Proof, sizeshift: u32) -> Result<(Proof, u64), Er // if we found a cycle (not guaranteed) and the proof is lower that the target, // we're all good if let Ok(proof) = Miner::new(pow_hash.to_slice(), EASINESS, sizeshift).mine() { - if proof <= target { + if proof.to_target() <= target { return Ok((proof, pow_header.nonce)); } } @@ -159,11 +160,11 @@ mod test { #[test] fn genesis_pow() { let mut b = genesis::genesis(); - let (proof, nonce) = pow20(&b, Proof(MAX_TARGET)).unwrap(); + let (proof, nonce) = pow20(&b, MAX_TARGET).unwrap(); assert!(nonce > 0); - assert!(proof < Proof(MAX_TARGET)); + assert!(proof.to_target() < MAX_TARGET); b.header.pow = proof; b.header.nonce = nonce; - assert!(verify20(&b, Proof(MAX_TARGET))); + assert!(verify20(&b, MAX_TARGET)); } }