From 2259c18dd6f753a957cd92d7bb8a181cee55e899 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Wed, 3 Oct 2018 03:39:16 -0700 Subject: [PATCH] Demo lean miner, minor PoW improvements (#1630) --- core/src/global.rs | 5 +- core/src/pow/common.rs | 104 +++++++++++++++++++++++++++--------- core/src/pow/cuckatoo.rs | 56 +++++++++----------- core/src/pow/cuckoo.rs | 41 ++++++--------- core/src/pow/lean.rs | 111 +++++++++++++++++++++++++++++++++++++++ core/src/pow/mod.rs | 13 ++--- 6 files changed, 239 insertions(+), 91 deletions(-) create mode 100644 core/src/pow/lean.rs diff --git a/core/src/global.rs b/core/src/global.rs index f8ba3cca..299476e2 100644 --- a/core/src/global.rs +++ b/core/src/global.rs @@ -19,7 +19,7 @@ use consensus::TargetError; use consensus::{ BLOCK_TIME_SEC, COINBASE_MATURITY, CUT_THROUGH_HORIZON, DEFAULT_MIN_SIZESHIFT, - DIFFICULTY_ADJUST_WINDOW, INITIAL_DIFFICULTY, MEDIAN_TIME_WINDOW, PROOFSIZE, + DIFFICULTY_ADJUST_WINDOW, EASINESS, INITIAL_DIFFICULTY, MEDIAN_TIME_WINDOW, PROOFSIZE, REFERENCE_SIZESHIFT, }; use pow::{self, CuckooContext, Difficulty, EdgeType, PoWContext}; @@ -123,7 +123,6 @@ pub fn set_mining_mode(mode: ChainTypes) { pub fn create_pow_context( edge_bits: u8, proof_size: usize, - easiness_pct: u32, max_sols: u32, ) -> Result>, pow::Error> where @@ -132,7 +131,7 @@ where // Perform whatever tests, configuration etc are needed to determine desired context + edge size // + params // Hardcode to regular cuckoo for now - CuckooContext::::new(edge_bits, proof_size, easiness_pct, max_sols) + CuckooContext::::new(edge_bits, proof_size, EASINESS, max_sols) // Or switch to cuckatoo as follows: // CuckatooContext::::new(edge_bits, proof_size, easiness_pct, max_sols) } diff --git a/core/src/pow/common.rs b/core/src/pow/common.rs index 142b2125..5daa1406 100644 --- a/core/src/pow/common.rs +++ b/core/src/pow/common.rs @@ -15,6 +15,8 @@ //! Common types and traits for cuckoo/cuckatoo family of solvers use blake2::blake2b::blake2b; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + use pow::error::{Error, ErrorKind}; use pow::num::{PrimInt, ToPrimitive}; use pow::siphash::siphash24; @@ -23,8 +25,6 @@ use std::io::Cursor; use std::ops::{BitOrAssign, Mul}; use std::{fmt, mem}; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; - /// Operations needed for edge type (going to be u32 or u64) pub trait EdgeType: PrimInt + ToPrimitive + Mul + BitOrAssign + Hash {} impl EdgeType for u32 {} @@ -100,29 +100,6 @@ pub fn create_siphash_keys(header: Vec) -> Result<[u64; 4], Error> { ]) } -/// Return siphash masked for type -pub fn sipnode( - keys: &[u64; 4], - edge: T, - edge_mask: &T, - uorv: u64, - shift: bool, -) -> Result -where - T: EdgeType, -{ - let hash_u64 = siphash24( - keys, - 2 * edge.to_u64().ok_or(ErrorKind::IntegerCast)? + uorv, - ); - let mut masked = hash_u64 & edge_mask.to_u64().ok_or(ErrorKind::IntegerCast)?; - if shift { - masked = masked << 1; - masked |= uorv; - } - Ok(T::from(masked).ok_or(ErrorKind::IntegerCast)?) -} - /// Macros to clean up integer unwrapping #[macro_export] macro_rules! to_u64 { @@ -151,3 +128,80 @@ macro_rules! to_edge { T::from($n).ok_or(ErrorKind::IntegerCast)? }; } + +/// Utility struct to calculate commonly used Cuckoo parameters calculated +/// from header, nonce, sizeshift, etc. +pub struct CuckooParams +where + T: EdgeType, +{ + pub edge_bits: u8, + pub proof_size: usize, + pub num_edges: u64, + pub siphash_keys: [u64; 4], + pub easiness: T, + pub edge_mask: T, +} + +impl CuckooParams +where + T: EdgeType, +{ + /// Instantiates new params and calculate easiness, edge mask, etc + pub fn new( + edge_bits: u8, + proof_size: usize, + easiness_pct: u32, + cuckatoo: bool, + ) -> Result, Error> { + let num_edges = 1 << edge_bits; + let num_nodes = 2 * num_edges as u64; + let easiness = if cuckatoo { + to_u64!(easiness_pct) * num_nodes / 100 + } else { + to_u64!(easiness_pct) * num_edges / 100 + }; + let edge_mask = if cuckatoo { + to_edge!(num_edges - 1) + } else { + to_edge!(num_edges / 2 - 1) + }; + Ok(CuckooParams { + siphash_keys: [0; 4], + easiness: to_edge!(easiness), + proof_size, + edge_mask, + num_edges, + edge_bits, + }) + } + + /// Reset the main keys used for siphash from the header and nonce + pub fn reset_header_nonce( + &mut self, + mut header: Vec, + nonce: Option, + ) -> Result<(), Error> { + if let Some(n) = nonce { + let len = header.len(); + header.truncate(len - mem::size_of::()); + header.write_u32::(n)?; + } + self.siphash_keys = set_header_nonce(header, nonce)?; + Ok(()) + } + + /// Return siphash masked for type + pub fn sipnode(&self, edge: T, uorv: u64, shift: bool) -> Result { + let hash_u64 = siphash24( + &self.siphash_keys, + 2 * edge.to_u64().ok_or(ErrorKind::IntegerCast)? + uorv, + ); + let mut masked = hash_u64 & self.edge_mask.to_u64().ok_or(ErrorKind::IntegerCast)?; + if shift { + masked = masked << 1; + masked |= uorv; + } + Ok(T::from(masked).ok_or(ErrorKind::IntegerCast)?) + } +} diff --git a/core/src/pow/cuckatoo.rs b/core/src/pow/cuckatoo.rs index b7aaa905..70681df4 100644 --- a/core/src/pow/cuckatoo.rs +++ b/core/src/pow/cuckatoo.rs @@ -18,7 +18,7 @@ use std::mem; use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; use croaring::Bitmap; -use pow::common::{self, EdgeType, Link}; +use pow::common::{self, CuckooParams, EdgeType, Link}; use pow::error::{Error, ErrorKind}; use pow::{PoWContext, Proof}; use util; @@ -158,11 +158,8 @@ pub struct CuckatooContext where T: EdgeType, { - siphash_keys: [u64; 4], - easiness: T, + params: CuckooParams, graph: Graph, - proof_size: usize, - edge_mask: T, } impl PoWContext for CuckatooContext @@ -193,7 +190,8 @@ where } fn find_cycles(&mut self) -> Result, Error> { - self.find_cycles_impl() + let ease = to_u64!(self.params.easiness); + self.find_cycles_iter(0..ease) } fn verify(&self, proof: &Proof) -> Result<(), Error> { @@ -212,22 +210,18 @@ where easiness_pct: u32, max_sols: u32, ) -> Result, Error> { - let num_edges = 1 << edge_bits; - let num_nodes = 2 * num_edges as u64; - let easiness = to_u64!(easiness_pct) * num_nodes / 100; + let params = CuckooParams::new(edge_bits, proof_size, easiness_pct, true)?; + let num_edges = to_edge!(params.num_edges); Ok(CuckatooContext { - siphash_keys: [0; 4], - easiness: to_edge!(easiness), - graph: Graph::new(to_edge!(num_edges), max_sols, proof_size)?, - proof_size: proof_size, - edge_mask: to_edge!(num_edges - 1), + params, + graph: Graph::new(num_edges, max_sols, proof_size)?, }) } /// Get a siphash key as a hex string (for display convenience) pub fn sipkey_hex(&self, index: usize) -> Result { let mut rdr = vec![]; - rdr.write_u64::(self.siphash_keys[index])?; + rdr.write_u64::(self.params.siphash_keys[index])?; Ok(util::to_hex(rdr)) } @@ -239,16 +233,11 @@ where /// Set the header and optional nonce in the last part of the header pub fn set_header_nonce_impl( &mut self, - mut header: Vec, + header: Vec, nonce: Option, solve: bool, ) -> Result<(), Error> { - let len = header.len(); - header.truncate(len - mem::size_of::()); - if let Some(n) = nonce { - header.write_u32::(n)?; - } - self.siphash_keys = common::set_header_nonce(header, nonce)?; + self.params.reset_header_nonce(header, nonce)?; if solve { self.graph.reset()?; } @@ -256,19 +245,26 @@ where } /// Return siphash masked for type - fn sipnode(&self, edge: T, uorv: u64) -> Result { - common::sipnode::(&self.siphash_keys, edge, &self.edge_mask, uorv, false) + pub fn sipnode(&self, edge: T, uorv: u64) -> Result { + self.params.sipnode(edge, uorv, false) } /// Simple implementation of algorithm - pub fn find_cycles_impl(&mut self) -> Result, Error> { - for n in 0..to_u64!(self.easiness) { + + pub fn find_cycles_iter<'a, I>(&mut self, iter: I) -> Result, Error> + where + I: Iterator, + { + let mut val = vec![]; + for n in iter { + val.push(n); let u = self.sipnode(to_edge!(n), 0)?; let v = self.sipnode(to_edge!(n), 1)?; self.graph.add_edge(to_edge!(u), to_edge!(v))?; } self.graph.solutions.pop(); for s in &mut self.graph.solutions { + s.nonces = map_vec!(s.nonces, |n| val[*n as usize]); s.nonces.sort(); } for s in &self.graph.solutions { @@ -286,11 +282,11 @@ where pub fn verify_impl(&self, proof: &Proof) -> Result<(), Error> { let nonces = &proof.nonces; let mut uvs = vec![0u64; 2 * proof.proof_size()]; - let mut xor0: u64 = (self.proof_size as u64 / 2) & 1; + let mut xor0: u64 = (self.params.proof_size as u64 / 2) & 1; let mut xor1: u64 = xor0; for n in 0..proof.proof_size() { - if nonces[n] > to_u64!(self.edge_mask) { + if nonces[n] > to_u64!(self.params.edge_mask) { return Err(ErrorKind::Verification("edge too big".to_owned()))?; } if n > 0 && nonces[n] <= nonces[n - 1] { @@ -314,7 +310,7 @@ where j = i; let mut k = j; loop { - k = (k + 2) % (2 * self.proof_size); + k = (k + 2) % (2 * self.params.proof_size); if k == i { break; } @@ -335,7 +331,7 @@ where break; } } - if n == self.proof_size { + if n == self.params.proof_size { Ok(()) } else { Err(ErrorKind::Verification("cycle too short".to_owned()))? diff --git a/core/src/pow/cuckoo.rs b/core/src/pow/cuckoo.rs index b545b193..df636d3d 100644 --- a/core/src/pow/cuckoo.rs +++ b/core/src/pow/cuckoo.rs @@ -17,7 +17,7 @@ //! simple miner is included, mostly for testing purposes. John Tromp's Tomato //! miner will be much faster in almost every environment. -use pow::common::{self, Edge, EdgeType}; +use pow::common::{self, CuckooParams, Edge, EdgeType}; use pow::error::{Error, ErrorKind}; use pow::num::ToPrimitive; use pow::{PoWContext, Proof}; @@ -32,11 +32,7 @@ pub struct CuckooContext where T: EdgeType, { - edge_bits: u8, - siphash_keys: [u64; 4], - easiness: T, - proof_size: usize, - edge_mask: T, + params: CuckooParams, graph: Vec, _max_sols: u32, } @@ -88,22 +84,17 @@ where easiness_pct: u32, max_sols: u32, ) -> Result, Error> { - let num_edges = 1 << edge_bits; - let easiness = to_u64!(easiness_pct) * num_edges / 100; + let params = CuckooParams::new(edge_bits, proof_size, easiness_pct, false)?; + let num_edges = params.num_edges as usize; Ok(CuckooContext { - edge_bits: edge_bits, - siphash_keys: [0; 4], - easiness: to_edge!(easiness), - proof_size: proof_size, - edge_mask: to_edge!(num_edges / 2 - 1), - graph: vec![T::zero(); num_edges as usize + 1], + params: params, + graph: vec![T::zero(); num_edges + 1], _max_sols: max_sols, }) } fn reset(&mut self) -> Result<(), Error> { - let num_edges = 1 << self.edge_bits; - self.graph = vec![T::zero(); num_edges + 1]; + self.graph = vec![T::zero(); self.params.num_edges as usize + 1]; Ok(()) } @@ -115,7 +106,7 @@ where nonce: Option, solve: bool, ) -> Result<(), Error> { - self.siphash_keys = common::set_header_nonce(header, nonce)?; + self.params.reset_header_nonce(header, nonce)?; if solve { self.reset()?; } @@ -126,7 +117,7 @@ where /// simply materialized as a u64 from a nonce and an offset (generally 0 or /// 1). fn new_node(&self, edge: T, uorv: u64) -> Result { - common::sipnode::(&self.siphash_keys, edge, &self.edge_mask, uorv, true) + self.params.sipnode(edge, uorv, true) } /// Creates a new edge in the cuckoo graph generated by our seed from a @@ -193,7 +184,7 @@ where nu += 1; nv += 1; } - if nu + nv + 1 == self.proof_size { + if nu + nv + 1 == self.params.proof_size { self.solution(&us, nu as u64, &vs, nv as u64) } else { Err(ErrorKind::InvalidCycle(nu + nv + 1))? @@ -223,8 +214,8 @@ where }); } let mut n = 0; - let mut sol = vec![T::zero(); self.proof_size]; - for nonce in 0..to_usize!(self.easiness) { + let mut sol = vec![T::zero(); self.params.proof_size]; + for nonce in 0..to_usize!(self.params.easiness) { let edge = self.new_edge(to_edge!(nonce))?; if cycle.contains(&edge) { sol[n] = to_edge!(nonce); @@ -232,7 +223,7 @@ where cycle.remove(&edge); } } - return if n == self.proof_size { + return if n == self.params.proof_size { Ok(sol) } else { Err(ErrorKind::NoCycle)? @@ -243,7 +234,7 @@ where pub fn find_cycles_impl(&mut self) -> Result, Error> { let mut us = [T::zero(); MAXPATHLEN]; let mut vs = [T::zero(); MAXPATHLEN]; - for nonce in 0..to_usize!(self.easiness) { + for nonce in 0..to_usize!(self.params.easiness) { us[0] = self.new_node(to_edge!(nonce), 0)?; vs[0] = self.new_node(to_edge!(nonce), 1)?; let u = self.graph[to_usize!(us[0])]; @@ -258,7 +249,7 @@ where match sol { Ok(s) => { let mut proof = Proof::new(map_vec!(s.to_vec(), |&n| n.to_u64().unwrap_or(0))); - proof.cuckoo_sizeshift = self.edge_bits; + proof.cuckoo_sizeshift = self.params.edge_bits; return Ok(vec![proof]); } Err(e) => match e.kind() { @@ -275,7 +266,7 @@ where /// nonces form a cycle in a Cuckoo graph. Each nonce generates an edge, we /// build the nodes on both side of that edge and count the connections. pub fn verify_impl(&self, proof: &Proof) -> Result<(), Error> { - let easiness = to_u64!(self.easiness); + let easiness = to_u64!(self.params.easiness); let nonces = &proof.nonces; let mut us = vec![T::zero(); proof.proof_size()]; let mut vs = vec![T::zero(); proof.proof_size()]; diff --git a/core/src/pow/lean.rs b/core/src/pow/lean.rs new file mode 100644 index 00000000..aa10973d --- /dev/null +++ b/core/src/pow/lean.rs @@ -0,0 +1,111 @@ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Lean miner for Cuckatoo Cycle + +use std::time::{Duration, Instant}; + +use croaring::Bitmap; + +use pow::common::CuckooParams; +use pow::cuckatoo::CuckatooContext; +use pow::error::{Error, ErrorKind}; +use pow::siphash::siphash24; +use pow::{PoWContext, Proof}; + +/// Lean miner implementation aiming to be as short and simple as possible. +/// As a consequence, it's a little less than 10 times slower than John +/// Tromp's implementation, as it's not optimized for performance and reuses +/// croaring which is likely sub-optimal for this task. +pub struct Lean { + params: CuckooParams, + edges: Bitmap, +} + +impl Lean { + /// Instantiates a new lean miner based on some Cuckatoo parameters + pub fn new(edge_bits: u8, easiness_pct: u32) -> Lean { + // note that proof size doesn't matter to a lean miner + let params = CuckooParams::new(edge_bits, 42, easiness_pct, true).unwrap(); + + // edge bitmap, before trimming all of them are on + let mut edges = Bitmap::create_with_capacity(params.easiness); + edges.flip_inplace(0..params.easiness.into()); + + Lean { params, edges } + } + + /// Sets the header and nonce to seed the graph + pub fn set_header_nonce(&mut self, header: Vec, nonce: u32) { + self.params.reset_header_nonce(header, Some(nonce)).unwrap(); + } + + /// Trim edges in the Cuckatoo graph. This applies multiple trimming rounds + /// and works well for Cuckatoo size above 18. + pub fn trim(&mut self) { + // trimming successively + while self.edges.cardinality() > (7 * (self.params.easiness >> 8) / 8) as u64 { + self.count_and_kill(); + } + } + + /// Finds the Cuckatoo Cycles on the remaining edges. Delegates the finding + /// to a context, passing the trimmed edges iterator. + pub fn find_cycles(&self, mut ctx: CuckatooContext) -> Result, Error> { + ctx.find_cycles_iter(self.edges.iter().map(|e| e as u64)) + } + + fn count_and_kill(&mut self) { + // on each side u or v of the bipartite graph + for uorv in 0..2 { + let mut nodes = Bitmap::create(); + // increment count for each node + for e in self.edges.iter() { + let node = self.params.sipnode(e, uorv, false).unwrap(); + nodes.add(node); + } + + // then kill edges with lone nodes (no neighbour at ^1) + let mut to_kill = Bitmap::create(); + for e in self.edges.iter() { + let node = self.params.sipnode(e, uorv, false).unwrap(); + if !nodes.contains(node ^ 1) { + to_kill.add(e); + } + } + self.edges.andnot_inplace(&to_kill); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use pow::common; + use pow::cuckatoo::*; + + #[test] + fn lean_miner() { + let nonce = 15465723; + let header = [0u8; 84].to_vec(); // with nonce + let edge_bits = 19; + + let mut lean = Lean::new(edge_bits, 50); + lean.set_header_nonce(header.clone(), nonce); + lean.trim(); + + let mut ctx_u32 = CuckatooContext::::new_impl(edge_bits, 42, 50, 10).unwrap(); + ctx_u32.set_header_nonce(header, Some(nonce), true).unwrap(); + lean.find_cycles(ctx_u32).unwrap(); + } +} diff --git a/core/src/pow/mod.rs b/core/src/pow/mod.rs index e76fd354..2e927475 100644 --- a/core/src/pow/mod.rs +++ b/core/src/pow/mod.rs @@ -41,6 +41,8 @@ mod common; pub mod cuckatoo; pub mod cuckoo; mod error; +#[allow(dead_code)] +pub mod lean; mod siphash; mod types; @@ -61,12 +63,8 @@ const MAX_SOLS: u32 = 10; /// Validates the proof of work of a given header, and that the proof of work /// satisfies the requirements of the header. pub fn verify_size(bh: &BlockHeader, cuckoo_sz: u8) -> Result<(), Error> { - let mut ctx = global::create_pow_context::( - cuckoo_sz, - bh.pow.proof.nonces.len(), - consensus::EASINESS, - MAX_SOLS, - )?; + let mut ctx = + global::create_pow_context::(cuckoo_sz, bh.pow.proof.nonces.len(), MAX_SOLS)?; ctx.set_header_nonce(bh.pre_pow(), None, false)?; ctx.verify(&bh.pow.proof) } @@ -109,8 +107,7 @@ pub fn pow_size( loop { // if we found a cycle (not guaranteed) and the proof hash is higher that the // diff, we're all good - let mut ctx = - global::create_pow_context::(sz, proof_size, consensus::EASINESS, MAX_SOLS)?; + let mut ctx = global::create_pow_context::(sz, proof_size, MAX_SOLS)?; ctx.set_header_nonce(bh.pre_pow(), None, true)?; if let Ok(proofs) = ctx.find_cycles() { bh.pow.proof = proofs[0].clone();