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.

This commit is contained in:
Ignotus Peverell 2016-11-15 14:37:49 -08:00
parent 44545525f4
commit 3c5e2b2958
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
7 changed files with 201 additions and 58 deletions

View file

@ -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));
}
}

View file

@ -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<Block> for Block {
fn read(reader: &mut Reader) -> Result<Block, ser::Error> {
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<Block> 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),

View file

@ -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);
}
}

View file

@ -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<u32>) -> 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<u64> {
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<u32> {
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

View file

@ -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);
}

View file

@ -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,

View file

@ -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));
}
}