Demo lean miner, minor PoW improvements (#1630)

This commit is contained in:
Ignotus Peverell 2018-10-03 03:39:16 -07:00 committed by Yeastplume
parent 4d70968e70
commit 2259c18dd6
6 changed files with 239 additions and 91 deletions

View file

@ -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<T>(
edge_bits: u8,
proof_size: usize,
easiness_pct: u32,
max_sols: u32,
) -> Result<Box<impl PoWContext<T>>, 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::<T>::new(edge_bits, proof_size, easiness_pct, max_sols)
CuckooContext::<T>::new(edge_bits, proof_size, EASINESS, max_sols)
// Or switch to cuckatoo as follows:
// CuckatooContext::<T>::new(edge_bits, proof_size, easiness_pct, max_sols)
}

View file

@ -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<u8>) -> Result<[u64; 4], Error> {
])
}
/// Return siphash masked for type
pub fn sipnode<T>(
keys: &[u64; 4],
edge: T,
edge_mask: &T,
uorv: u64,
shift: bool,
) -> Result<T, Error>
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<T>
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<T> CuckooParams<T>
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<CuckooParams<T>, 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<u8>,
nonce: Option<u32>,
) -> Result<(), Error> {
if let Some(n) = nonce {
let len = header.len();
header.truncate(len - mem::size_of::<u32>());
header.write_u32::<LittleEndian>(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<T, Error> {
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)?)
}
}

View file

@ -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<T>
where
T: EdgeType,
{
siphash_keys: [u64; 4],
easiness: T,
params: CuckooParams<T>,
graph: Graph<T>,
proof_size: usize,
edge_mask: T,
}
impl<T> PoWContext<T> for CuckatooContext<T>
@ -193,7 +190,8 @@ where
}
fn find_cycles(&mut self) -> Result<Vec<Proof>, 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<CuckatooContext<T>, 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<String, Error> {
let mut rdr = vec![];
rdr.write_u64::<BigEndian>(self.siphash_keys[index])?;
rdr.write_u64::<BigEndian>(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<u8>,
header: Vec<u8>,
nonce: Option<u32>,
solve: bool,
) -> Result<(), Error> {
let len = header.len();
header.truncate(len - mem::size_of::<u32>());
if let Some(n) = nonce {
header.write_u32::<LittleEndian>(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<T, Error> {
common::sipnode::<T>(&self.siphash_keys, edge, &self.edge_mask, uorv, false)
pub fn sipnode(&self, edge: T, uorv: u64) -> Result<T, Error> {
self.params.sipnode(edge, uorv, false)
}
/// Simple implementation of algorithm
pub fn find_cycles_impl(&mut self) -> Result<Vec<Proof>, Error> {
for n in 0..to_u64!(self.easiness) {
pub fn find_cycles_iter<'a, I>(&mut self, iter: I) -> Result<Vec<Proof>, Error>
where
I: Iterator<Item = u64>,
{
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()))?

View file

@ -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<T>
where
T: EdgeType,
{
edge_bits: u8,
siphash_keys: [u64; 4],
easiness: T,
proof_size: usize,
edge_mask: T,
params: CuckooParams<T>,
graph: Vec<T>,
_max_sols: u32,
}
@ -88,22 +84,17 @@ where
easiness_pct: u32,
max_sols: u32,
) -> Result<CuckooContext<T>, 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<u32>,
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<T, Error> {
common::sipnode::<T>(&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<Vec<Proof>, 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()];

111
core/src/pow/lean.rs Normal file
View file

@ -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<u32>,
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<u8>, 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<u32>) -> Result<Vec<Proof>, 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::<u32>::new_impl(edge_bits, 42, 50, 10).unwrap();
ctx_u32.set_header_nonce(header, Some(nonce), true).unwrap();
lean.find_cycles(ctx_u32).unwrap();
}
}

View file

@ -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::<u64>(
cuckoo_sz,
bh.pow.proof.nonces.len(),
consensus::EASINESS,
MAX_SOLS,
)?;
let mut ctx =
global::create_pow_context::<u64>(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::<u32>(sz, proof_size, consensus::EASINESS, MAX_SOLS)?;
let mut ctx = global::create_pow_context::<u32>(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();