Use memmap for dag cache (#6193)
* Rebase and fix compilation errors (tests not yet fixed) * Use `debug_assert` over `assert` * Fix tests * Assert safety, clean up * Fix up stale cache removal, move one assert to debug_assert * Remove printlns * Add licenses * Fix benches * Inline some no-ops in a hot loop that weren't being inlined * Add spooky comment to make sure no-one removes the inlining annotations * Minor cleanup * Add option to switch between mmap and ram * Flag ethash to use less memory when running light client * Fix tests * Remove todo comment (it's done) * Replace assertion with error return * Fix indentation * Use union instead of `transmute` * Fix benches * Extract to constants * Clean up and fix soundness holes * Fix formatting * Ignore missing-file errors * Make incorrect cache size an error condition instead of a panic, remove dead code * Fix compilation errors from rebase * Fix compilation errors in tests * Fix compilation errors in tests
This commit is contained in:
352
ethash/src/cache.rs
Normal file
352
ethash/src/cache.rs
Normal file
@@ -0,0 +1,352 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use compute::Light;
|
||||
use either::Either;
|
||||
use keccak::{H256, keccak_512};
|
||||
use memmap::{Mmap, Protection};
|
||||
use parking_lot::Mutex;
|
||||
use seed_compute::SeedHashCompute;
|
||||
|
||||
use shared::{ETHASH_CACHE_ROUNDS, NODE_BYTES, NODE_DWORDS, Node, epoch, get_cache_size, to_hex};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::slice;
|
||||
use std::sync::Arc;
|
||||
|
||||
type Cache = Either<Vec<Node>, Mmap>;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum OptimizeFor {
|
||||
Cpu,
|
||||
Memory,
|
||||
}
|
||||
|
||||
impl Default for OptimizeFor {
|
||||
fn default() -> Self {
|
||||
OptimizeFor::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_size(cache: &Cache) -> usize {
|
||||
use self::Either::{Left, Right};
|
||||
|
||||
match *cache {
|
||||
Left(ref vec) => vec.len() * NODE_BYTES,
|
||||
Right(ref mmap) => mmap.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_buffer(path: &Path, num_nodes: usize, ident: &H256, optimize_for: OptimizeFor) -> Cache {
|
||||
let memmap = match optimize_for {
|
||||
OptimizeFor::Cpu => None,
|
||||
OptimizeFor::Memory => make_memmapped_cache(path, num_nodes, ident).ok(),
|
||||
};
|
||||
|
||||
memmap.map(Either::Right).unwrap_or_else(|| {
|
||||
Either::Left(make_memory_cache(num_nodes, ident))
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeCacheBuilder {
|
||||
// TODO: Remove this locking and just use an `Rc`?
|
||||
seedhash: Arc<Mutex<SeedHashCompute>>,
|
||||
optimize_for: OptimizeFor,
|
||||
}
|
||||
|
||||
// TODO: Abstract the "optimize for" logic
|
||||
pub struct NodeCache {
|
||||
builder: NodeCacheBuilder,
|
||||
cache_dir: Cow<'static, Path>,
|
||||
cache_path: PathBuf,
|
||||
epoch: u64,
|
||||
cache: Cache,
|
||||
}
|
||||
|
||||
impl NodeCacheBuilder {
|
||||
pub fn light(&self, cache_dir: &Path, block_number: u64) -> Light {
|
||||
Light::new_with_builder(self, cache_dir, block_number)
|
||||
}
|
||||
|
||||
pub fn light_from_file(&self, cache_dir: &Path, block_number: u64) -> io::Result<Light> {
|
||||
Light::from_file_with_builder(self, cache_dir, block_number)
|
||||
}
|
||||
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(optimize_for: T) -> Self {
|
||||
NodeCacheBuilder {
|
||||
seedhash: Arc::new(Mutex::new(SeedHashCompute::new())),
|
||||
optimize_for: optimize_for.into().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn block_number_to_ident(&self, block_number: u64) -> H256 {
|
||||
self.seedhash.lock().hash_block_number(block_number)
|
||||
}
|
||||
|
||||
fn epoch_to_ident(&self, epoch: u64) -> H256 {
|
||||
self.seedhash.lock().hash_epoch(epoch)
|
||||
}
|
||||
|
||||
pub fn from_file<P: Into<Cow<'static, Path>>>(
|
||||
&self,
|
||||
cache_dir: P,
|
||||
block_number: u64,
|
||||
) -> io::Result<NodeCache> {
|
||||
let cache_dir = cache_dir.into();
|
||||
let ident = self.block_number_to_ident(block_number);
|
||||
|
||||
let path = cache_path(cache_dir.as_ref(), &ident);
|
||||
|
||||
let cache = cache_from_path(&path, self.optimize_for)?;
|
||||
let expected_cache_size = get_cache_size(block_number);
|
||||
|
||||
if byte_size(&cache) == expected_cache_size {
|
||||
Ok(NodeCache {
|
||||
builder: self.clone(),
|
||||
epoch: epoch(block_number),
|
||||
cache_dir: cache_dir,
|
||||
cache_path: path,
|
||||
cache: cache,
|
||||
})
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Node cache is of incorrect size",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_cache<P: Into<Cow<'static, Path>>>(
|
||||
&self,
|
||||
cache_dir: P,
|
||||
block_number: u64,
|
||||
) -> NodeCache {
|
||||
let cache_dir = cache_dir.into();
|
||||
let ident = self.block_number_to_ident(block_number);
|
||||
|
||||
let cache_size = get_cache_size(block_number);
|
||||
|
||||
// We use `debug_assert` since it is impossible for `get_cache_size` to return an unaligned
|
||||
// value with the current implementation. If the implementation changes, CI will catch it.
|
||||
debug_assert!(cache_size % NODE_BYTES == 0, "Unaligned cache size");
|
||||
let num_nodes = cache_size / NODE_BYTES;
|
||||
|
||||
let path = cache_path(cache_dir.as_ref(), &ident);
|
||||
let nodes = new_buffer(&path, num_nodes, &ident, self.optimize_for);
|
||||
|
||||
NodeCache {
|
||||
builder: self.clone(),
|
||||
epoch: epoch(block_number),
|
||||
cache_dir: cache_dir.into(),
|
||||
cache_path: path,
|
||||
cache: nodes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeCache {
|
||||
pub fn cache_path(&self) -> &Path {
|
||||
&self.cache_path
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> io::Result<()> {
|
||||
if let Some(last) = self.epoch.checked_sub(2).map(|ep| {
|
||||
cache_path(self.cache_dir.as_ref(), &self.builder.epoch_to_ident(ep))
|
||||
})
|
||||
{
|
||||
fs::remove_file(last).unwrap_or_else(|error| match error.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
_ => warn!("Error removing stale DAG cache: {:?}", error),
|
||||
});
|
||||
}
|
||||
|
||||
consume_cache(&mut self.cache, &self.cache_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_memmapped_cache(path: &Path, num_nodes: usize, ident: &H256) -> io::Result<Mmap> {
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
file.set_len((num_nodes * NODE_BYTES) as _)?;
|
||||
|
||||
let mut memmap = Mmap::open(&file, Protection::ReadWrite)?;
|
||||
|
||||
unsafe { initialize_memory(memmap.mut_ptr() as *mut Node, num_nodes, ident) };
|
||||
|
||||
Ok(memmap)
|
||||
}
|
||||
|
||||
fn make_memory_cache(num_nodes: usize, ident: &H256) -> Vec<Node> {
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(num_nodes);
|
||||
// Use uninit instead of unnecessarily writing `size_of::<Node>() * num_nodes` 0s
|
||||
unsafe {
|
||||
initialize_memory(nodes.as_mut_ptr(), num_nodes, ident);
|
||||
nodes.set_len(num_nodes);
|
||||
}
|
||||
|
||||
nodes
|
||||
}
|
||||
|
||||
fn cache_path<'a, P: Into<Cow<'a, Path>>>(path: P, ident: &H256) -> PathBuf {
|
||||
let mut buf = path.into().into_owned();
|
||||
buf.push(to_hex(ident));
|
||||
buf
|
||||
}
|
||||
|
||||
fn consume_cache(cache: &mut Cache, path: &Path) -> io::Result<()> {
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
match *cache {
|
||||
Either::Left(ref mut vec) => {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let buf = unsafe {
|
||||
slice::from_raw_parts_mut(vec.as_mut_ptr() as *mut u8, vec.len() * NODE_BYTES)
|
||||
};
|
||||
|
||||
file.write_all(buf).map(|_| ())
|
||||
}
|
||||
Either::Right(ref mmap) => {
|
||||
mmap.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cache_from_path(path: &Path, optimize_for: OptimizeFor) -> io::Result<Cache> {
|
||||
let memmap = match optimize_for {
|
||||
OptimizeFor::Cpu => None,
|
||||
OptimizeFor::Memory => Mmap::open_path(path, Protection::ReadWrite).ok(),
|
||||
};
|
||||
|
||||
memmap.map(Either::Right).ok_or(()).or_else(|_| {
|
||||
read_from_path(path).map(Either::Left)
|
||||
})
|
||||
}
|
||||
|
||||
fn read_from_path(path: &Path) -> io::Result<Vec<Node>> {
|
||||
use std::fs::File;
|
||||
use std::mem;
|
||||
|
||||
let mut file = File::open(path)?;
|
||||
|
||||
let mut nodes: Vec<u8> = Vec::with_capacity(file.metadata().map(|m| m.len() as _).unwrap_or(
|
||||
NODE_BYTES * 1_000_000,
|
||||
));
|
||||
file.read_to_end(&mut nodes)?;
|
||||
|
||||
nodes.shrink_to_fit();
|
||||
|
||||
if nodes.len() % NODE_BYTES != 0 || nodes.capacity() % NODE_BYTES != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Node cache is not a multiple of node size",
|
||||
));
|
||||
}
|
||||
|
||||
let out: Vec<Node> = unsafe {
|
||||
Vec::from_raw_parts(
|
||||
nodes.as_mut_ptr() as *mut _,
|
||||
nodes.len() / NODE_BYTES,
|
||||
nodes.capacity() / NODE_BYTES,
|
||||
)
|
||||
};
|
||||
|
||||
mem::forget(nodes);
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
impl AsRef<[Node]> for NodeCache {
|
||||
fn as_ref(&self) -> &[Node] {
|
||||
match self.cache {
|
||||
Either::Left(ref vec) => vec,
|
||||
Either::Right(ref mmap) => unsafe {
|
||||
let bytes = mmap.ptr();
|
||||
// This isn't a safety issue, so we can keep this a debug lint. We don't care about
|
||||
// people manually messing with the files unless it can cause unsafety, but if we're
|
||||
// generating incorrect files then we want to catch that in CI.
|
||||
debug_assert_eq!(mmap.len() % NODE_BYTES, 0);
|
||||
slice::from_raw_parts(bytes as _, mmap.len() / NODE_BYTES)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This takes a raw pointer and a counter because `memory` may be uninitialized. `memory` _must_ be
|
||||
// a pointer to the beginning of an allocated but possibly-uninitialized block of
|
||||
// `num_nodes * NODE_BYTES` bytes
|
||||
//
|
||||
// We have to use raw pointers to read/write uninit, using "normal" indexing causes LLVM to freak
|
||||
// out. It counts as a read and causes all writes afterwards to be elided. Yes, really. I know, I
|
||||
// want to refactor this to use less `unsafe` as much as the next rustacean.
|
||||
unsafe fn initialize_memory(memory: *mut Node, num_nodes: usize, ident: &H256) {
|
||||
let dst = memory as *mut u8;
|
||||
|
||||
debug_assert_eq!(ident.len(), 32);
|
||||
keccak_512::unchecked(dst, NODE_BYTES, ident.as_ptr(), ident.len());
|
||||
|
||||
for i in 1..num_nodes {
|
||||
// We use raw pointers here, see above
|
||||
let dst = memory.offset(i as _) as *mut u8;
|
||||
let src = memory.offset(i as isize - 1) as *mut u8;
|
||||
|
||||
keccak_512::unchecked(dst, NODE_BYTES, src, NODE_BYTES);
|
||||
}
|
||||
|
||||
// Now this is initialized, we can treat it as a slice.
|
||||
let nodes: &mut [Node] = slice::from_raw_parts_mut(memory, num_nodes);
|
||||
|
||||
// For `unroll!`, see below. If the literal in `unroll!` is not the same as the RHS here then
|
||||
// these have got out of sync! Don't let this happen!
|
||||
debug_assert_eq!(NODE_DWORDS, 8);
|
||||
|
||||
// This _should_ get unrolled by the compiler, since it's not using the loop variable.
|
||||
for _ in 0..ETHASH_CACHE_ROUNDS {
|
||||
for i in 0..num_nodes {
|
||||
let data_idx = (num_nodes - 1 + i) % num_nodes;
|
||||
let idx = nodes.get_unchecked_mut(i).as_words()[0] as usize % num_nodes;
|
||||
|
||||
let data = {
|
||||
let mut data: Node = nodes.get_unchecked(data_idx).clone();
|
||||
let rhs: &Node = nodes.get_unchecked(idx);
|
||||
|
||||
unroll! {
|
||||
for w in 0..8 {
|
||||
*data.as_dwords_mut().get_unchecked_mut(w) ^=
|
||||
*rhs.as_dwords().get_unchecked(w);
|
||||
}
|
||||
}
|
||||
|
||||
data
|
||||
};
|
||||
|
||||
keccak_512::write(&data.bytes, &mut nodes.get_unchecked_mut(i).bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,30 +19,16 @@
|
||||
|
||||
// TODO: fix endianess for big endian
|
||||
|
||||
use primal::is_prime;
|
||||
use std::cell::Cell;
|
||||
use keccak::{keccak_512, keccak_256, H256};
|
||||
use cache::{NodeCache, NodeCacheBuilder};
|
||||
use seed_compute::SeedHashCompute;
|
||||
use shared::*;
|
||||
use std::io;
|
||||
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use hash;
|
||||
use std::slice;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::fs::{self, File};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub const ETHASH_EPOCH_LENGTH: u64 = 30000;
|
||||
pub const ETHASH_CACHE_ROUNDS: usize = 3;
|
||||
pub const ETHASH_MIX_BYTES: usize = 128;
|
||||
pub const ETHASH_ACCESSES: usize = 64;
|
||||
pub const ETHASH_DATASET_PARENTS: u32 = 256;
|
||||
|
||||
const DATASET_BYTES_INIT: u64 = 1 << 30;
|
||||
const DATASET_BYTES_GROWTH: u64 = 1 << 23;
|
||||
const CACHE_BYTES_INIT: u64 = 1 << 24;
|
||||
const CACHE_BYTES_GROWTH: u64 = 1 << 17;
|
||||
const NODE_WORDS: usize = 64 / 4;
|
||||
const NODE_BYTES: usize = 64;
|
||||
const MIX_WORDS: usize = ETHASH_MIX_BYTES / 4;
|
||||
const MIX_NODES: usize = MIX_WORDS / NODE_WORDS;
|
||||
const FNV_PRIME: u32 = 0x01000193;
|
||||
@@ -55,48 +41,24 @@ pub struct ProofOfWork {
|
||||
pub mix_hash: H256,
|
||||
}
|
||||
|
||||
struct Node {
|
||||
bytes: [u8; NODE_BYTES],
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Node { bytes: [0u8; NODE_BYTES] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Node {
|
||||
fn clone(&self) -> Self {
|
||||
Node { bytes: *&self.bytes }
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
#[inline]
|
||||
fn as_words(&self) -> &[u32; NODE_WORDS] {
|
||||
unsafe { mem::transmute(&self.bytes) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_words_mut(&mut self) -> &mut [u32; NODE_WORDS] {
|
||||
unsafe { mem::transmute(&mut self.bytes) }
|
||||
}
|
||||
}
|
||||
|
||||
pub type H256 = [u8; 32];
|
||||
|
||||
pub struct Light {
|
||||
cache_dir: PathBuf,
|
||||
block_number: u64,
|
||||
cache: Vec<Node>,
|
||||
seed_compute: Mutex<SeedHashCompute>,
|
||||
cache: NodeCache,
|
||||
}
|
||||
|
||||
/// Light cache structure
|
||||
impl Light {
|
||||
/// Create a new light cache for a given block number
|
||||
pub fn new<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> Light {
|
||||
light_new(cache_dir, block_number)
|
||||
pub fn new_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
) -> Self {
|
||||
let cache = builder.new_cache(cache_dir.to_path_buf(), block_number);
|
||||
|
||||
Light {
|
||||
block_number: block_number,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the light boundary data
|
||||
@@ -106,107 +68,25 @@ impl Light {
|
||||
light_compute(self, header_hash, nonce)
|
||||
}
|
||||
|
||||
pub fn file_path<T: AsRef<Path>>(cache_dir: T, seed_hash: H256) -> PathBuf {
|
||||
let mut cache_dir = cache_dir.as_ref().to_path_buf();
|
||||
cache_dir.push(to_hex(&seed_hash));
|
||||
cache_dir
|
||||
}
|
||||
|
||||
pub fn from_file<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> io::Result<Light> {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let path = Light::file_path(&cache_dir, seed_compute.get_seedhash(block_number));
|
||||
let mut file = File::open(path)?;
|
||||
|
||||
let cache_size = get_cache_size(block_number);
|
||||
if file.metadata()?.len() != cache_size as u64 {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Cache file size mismatch"));
|
||||
}
|
||||
let num_nodes = cache_size / NODE_BYTES;
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(num_nodes);
|
||||
|
||||
unsafe { nodes.set_len(num_nodes) };
|
||||
|
||||
let buf = unsafe { slice::from_raw_parts_mut(nodes.as_mut_ptr() as *mut u8, cache_size) };
|
||||
file.read_exact(buf)?;
|
||||
pub fn from_file_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
) -> io::Result<Self> {
|
||||
let cache = builder.from_file(cache_dir.to_path_buf(), block_number)?;
|
||||
Ok(Light {
|
||||
block_number,
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache: nodes,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
block_number: block_number,
|
||||
cache: cache,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_file(&self) -> io::Result<PathBuf> {
|
||||
let seed_compute = self.seed_compute.lock();
|
||||
let path = Light::file_path(&self.cache_dir, seed_compute.get_seedhash(self.block_number));
|
||||
|
||||
if self.block_number >= ETHASH_EPOCH_LENGTH * 2 {
|
||||
let deprecated = Light::file_path(
|
||||
&self.cache_dir,
|
||||
seed_compute.get_seedhash(self.block_number - ETHASH_EPOCH_LENGTH * 2)
|
||||
);
|
||||
|
||||
if deprecated.exists() {
|
||||
debug!(target: "ethash", "removing: {:?}", &deprecated);
|
||||
fs::remove_file(deprecated)?;
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
let mut file = File::create(&path)?;
|
||||
|
||||
let cache_size = self.cache.len() * NODE_BYTES;
|
||||
let buf = unsafe { slice::from_raw_parts(self.cache.as_ptr() as *const u8, cache_size) };
|
||||
file.write(buf)?;
|
||||
Ok(path)
|
||||
pub fn to_file(&mut self) -> io::Result<&Path> {
|
||||
self.cache.flush()?;
|
||||
Ok(self.cache.cache_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SeedHashCompute {
|
||||
prev_epoch: Cell<u64>,
|
||||
prev_seedhash: Cell<H256>,
|
||||
}
|
||||
|
||||
impl SeedHashCompute {
|
||||
#[inline]
|
||||
pub fn new() -> SeedHashCompute {
|
||||
SeedHashCompute {
|
||||
prev_epoch: Cell::new(0),
|
||||
prev_seedhash: Cell::new([0u8; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_cache(&self) {
|
||||
self.prev_epoch.set(0);
|
||||
self.prev_seedhash.set([0u8; 32]);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_seedhash(&self, block_number: u64) -> H256 {
|
||||
let epoch = block_number / ETHASH_EPOCH_LENGTH;
|
||||
if epoch < self.prev_epoch.get() {
|
||||
// can't build on previous hash if requesting an older block
|
||||
self.reset_cache();
|
||||
}
|
||||
if epoch > self.prev_epoch.get() {
|
||||
let seed_hash = SeedHashCompute::resume_compute_seedhash(self.prev_seedhash.get(), self.prev_epoch.get(), epoch);
|
||||
self.prev_seedhash.set(seed_hash);
|
||||
self.prev_epoch.set(epoch);
|
||||
}
|
||||
self.prev_seedhash.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resume_compute_seedhash(mut hash: H256, start_epoch: u64, end_epoch: u64) -> H256 {
|
||||
for _ in start_epoch..end_epoch {
|
||||
unsafe { hash::keccak_256(hash[..].as_mut_ptr(), 32, hash[..].as_ptr(), 32) };
|
||||
}
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slow_get_seedhash(block_number: u64) -> H256 {
|
||||
pub fn slow_hash_block_number(block_number: u64) -> H256 {
|
||||
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
|
||||
}
|
||||
|
||||
@@ -214,34 +94,6 @@ fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
return x.wrapping_mul(FNV_PRIME) ^ y;
|
||||
}
|
||||
|
||||
fn keccak_512(input: &[u8], output: &mut [u8]) {
|
||||
unsafe { hash::keccak_512(output.as_mut_ptr(), output.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
|
||||
fn keccak_512_inplace(input: &mut [u8]) {
|
||||
// This is safe since `keccak_*` uses an internal buffer and copies the result to the output. This
|
||||
// means that we can reuse the input buffer for both input and output.
|
||||
unsafe { hash::keccak_512(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
|
||||
fn get_cache_size(block_number: u64) -> usize {
|
||||
let mut sz: u64 = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - NODE_BYTES as u64;
|
||||
while !is_prime(sz / NODE_BYTES as u64) {
|
||||
sz = sz - 2 * NODE_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
fn get_data_size(block_number: u64) -> usize {
|
||||
let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - ETHASH_MIX_BYTES as u64;
|
||||
while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
|
||||
sz = sz - 2 * ETHASH_MIX_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
/// Difficulty quick check for POW preverification
|
||||
///
|
||||
/// `header_hash` The hash of the header
|
||||
@@ -261,12 +113,12 @@ pub fn quick_get_difficulty(header_hash: &H256, nonce: u64, mix_hash: &H256) ->
|
||||
ptr::copy_nonoverlapping(header_hash.as_ptr(), buf.as_mut_ptr(), 32);
|
||||
ptr::copy_nonoverlapping(mem::transmute(&nonce), buf[32..].as_mut_ptr(), 8);
|
||||
|
||||
hash::keccak_512(buf.as_mut_ptr(), 64, buf.as_ptr(), 40);
|
||||
keccak_512::unchecked(buf.as_mut_ptr(), 64, buf.as_ptr(), 40);
|
||||
ptr::copy_nonoverlapping(mix_hash.as_ptr(), buf[64..].as_mut_ptr(), 32);
|
||||
|
||||
// This is initialized in `keccak_256`
|
||||
let mut hash: [u8; 32] = mem::uninitialized();
|
||||
hash::keccak_256(hash.as_mut_ptr(), hash.len(), buf.as_ptr(), buf.len());
|
||||
keccak_256::unchecked(hash.as_mut_ptr(), hash.len(), buf.as_ptr(), buf.len());
|
||||
|
||||
hash
|
||||
}
|
||||
@@ -324,11 +176,7 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
// leaving it fully initialized.
|
||||
let mut out: [u8; NODE_BYTES] = mem::uninitialized();
|
||||
|
||||
ptr::copy_nonoverlapping(
|
||||
header_hash.as_ptr(),
|
||||
out.as_mut_ptr(),
|
||||
header_hash.len(),
|
||||
);
|
||||
ptr::copy_nonoverlapping(header_hash.as_ptr(), out.as_mut_ptr(), header_hash.len());
|
||||
ptr::copy_nonoverlapping(
|
||||
mem::transmute(&nonce),
|
||||
out[header_hash.len()..].as_mut_ptr(),
|
||||
@@ -336,11 +184,11 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
);
|
||||
|
||||
// compute keccak-512 hash and replicate across mix
|
||||
hash::keccak_512(
|
||||
keccak_512::unchecked(
|
||||
out.as_mut_ptr(),
|
||||
NODE_BYTES,
|
||||
out.as_ptr(),
|
||||
header_hash.len() + mem::size_of::<u64>()
|
||||
header_hash.len() + mem::size_of::<u64>(),
|
||||
);
|
||||
|
||||
Node { bytes: out }
|
||||
@@ -354,7 +202,7 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
let page_size = 4 * MIX_WORDS;
|
||||
let num_full_pages = (full_size / page_size) as u32;
|
||||
// deref once for better performance
|
||||
let cache: &[Node] = &light.cache;
|
||||
let cache: &[Node] = light.cache.as_ref();
|
||||
let first_val = buf.half_mix.as_words()[0];
|
||||
|
||||
debug_assert_eq!(MIX_NODES, 2);
|
||||
@@ -364,14 +212,10 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
let index = {
|
||||
// This is trivially safe, but does not work on big-endian. The safety of this is
|
||||
// asserted in debug builds (see the definition of `make_const_array!`).
|
||||
let mix_words: &mut [u32; MIX_WORDS] = unsafe {
|
||||
make_const_array!(MIX_WORDS, &mut mix)
|
||||
};
|
||||
let mix_words: &mut [u32; MIX_WORDS] =
|
||||
unsafe { make_const_array!(MIX_WORDS, &mut mix) };
|
||||
|
||||
fnv_hash(
|
||||
first_val ^ i,
|
||||
mix_words[i as usize % MIX_WORDS]
|
||||
) % num_full_pages
|
||||
fnv_hash(first_val ^ i, mix_words[i as usize % MIX_WORDS]) % num_full_pages
|
||||
};
|
||||
|
||||
unroll! {
|
||||
@@ -403,9 +247,8 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
// times and set each index, leaving the array fully initialized. THIS ONLY WORKS ON LITTLE-
|
||||
// ENDIAN MACHINES. See a future PR to make this and the rest of the code work correctly on
|
||||
// big-endian arches like mips.
|
||||
let mut compress: &mut [u32; MIX_WORDS / 4] = unsafe {
|
||||
make_const_array!(MIX_WORDS / 4, &mut buf.compress_bytes)
|
||||
};
|
||||
let compress: &mut [u32; MIX_WORDS / 4] =
|
||||
unsafe { make_const_array!(MIX_WORDS / 4, &mut buf.compress_bytes) };
|
||||
|
||||
// Compress mix
|
||||
debug_assert_eq!(MIX_WORDS / 4, 8);
|
||||
@@ -430,7 +273,7 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
// We overwrite the second half since `keccak_256` has an internal buffer and so allows
|
||||
// overlapping arrays as input.
|
||||
let write_ptr: *mut u8 = mem::transmute(&mut buf.compress_bytes);
|
||||
hash::keccak_256(
|
||||
keccak_256::unchecked(
|
||||
write_ptr,
|
||||
buf.compress_bytes.len(),
|
||||
read_ptr,
|
||||
@@ -439,25 +282,21 @@ fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64)
|
||||
buf.compress_bytes
|
||||
};
|
||||
|
||||
ProofOfWork {
|
||||
mix_hash: mix_hash,
|
||||
value: value,
|
||||
}
|
||||
ProofOfWork { mix_hash: mix_hash, value: value }
|
||||
}
|
||||
|
||||
// TODO: Use the `simd` crate
|
||||
fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
let num_parent_nodes = cache.len();
|
||||
let mut ret = cache[node_index as usize % num_parent_nodes].clone();
|
||||
ret.as_words_mut()[0] ^= node_index;
|
||||
|
||||
keccak_512_inplace(&mut ret.bytes);
|
||||
keccak_512::inplace(ret.as_bytes_mut());
|
||||
|
||||
debug_assert_eq!(NODE_WORDS, 16);
|
||||
for i in 0..ETHASH_DATASET_PARENTS as u32 {
|
||||
let parent_index = fnv_hash(
|
||||
node_index ^ i,
|
||||
ret.as_words()[i as usize % NODE_WORDS],
|
||||
) % num_parent_nodes as u32;
|
||||
let parent_index = fnv_hash(node_index ^ i, ret.as_words()[i as usize % NODE_WORDS]) %
|
||||
num_parent_nodes as u32;
|
||||
let parent = &cache[parent_index as usize];
|
||||
|
||||
unroll! {
|
||||
@@ -467,159 +306,107 @@ fn calculate_dag_item(node_index: u32, cache: &[Node]) -> Node {
|
||||
}
|
||||
}
|
||||
|
||||
keccak_512_inplace(&mut ret.bytes);
|
||||
keccak_512::inplace(ret.as_bytes_mut());
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn light_new<T: AsRef<Path>>(cache_dir: T, block_number: u64) -> Light {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let seedhash = seed_compute.get_seedhash(block_number);
|
||||
let cache_size = get_cache_size(block_number);
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
assert!(cache_size % NODE_BYTES == 0, "Unaligned cache size");
|
||||
let num_nodes = cache_size / NODE_BYTES;
|
||||
|
||||
let mut nodes: Vec<Node> = Vec::with_capacity(num_nodes);
|
||||
unsafe {
|
||||
// Use uninit instead of unnecessarily writing `size_of::<Node>() * num_nodes` 0s
|
||||
nodes.set_len(num_nodes);
|
||||
|
||||
keccak_512(&seedhash[0..32], &mut nodes.get_unchecked_mut(0).bytes);
|
||||
for i in 1..num_nodes {
|
||||
hash::keccak_512(nodes.get_unchecked_mut(i).bytes.as_mut_ptr(), NODE_BYTES, nodes.get_unchecked(i - 1).bytes.as_ptr(), NODE_BYTES);
|
||||
}
|
||||
|
||||
debug_assert_eq!(NODE_WORDS, 16);
|
||||
|
||||
// This _should_ get unrolled by the compiler, since it's not using the loop variable.
|
||||
for _ in 0..ETHASH_CACHE_ROUNDS {
|
||||
for i in 0..num_nodes {
|
||||
let idx = *nodes.get_unchecked_mut(i).as_words().get_unchecked(0) as usize % num_nodes;
|
||||
let mut data = nodes.get_unchecked((num_nodes - 1 + i) % num_nodes).clone();
|
||||
|
||||
unroll! {
|
||||
for w in 0..16 {
|
||||
*data.as_words_mut().get_unchecked_mut(w) ^= *nodes.get_unchecked(idx).as_words().get_unchecked(w);
|
||||
}
|
||||
}
|
||||
|
||||
keccak_512(&data.bytes, &mut nodes.get_unchecked_mut(i).bytes);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_get_cache_size() {
|
||||
// https://github.com/ethereum/wiki/wiki/Ethash/ef6b93f9596746a088ea95d01ca2778be43ae68f#data-sizes
|
||||
assert_eq!(16776896usize, get_cache_size(0));
|
||||
assert_eq!(16776896usize, get_cache_size(1));
|
||||
assert_eq!(16776896usize, get_cache_size(ETHASH_EPOCH_LENGTH - 1));
|
||||
assert_eq!(16907456usize, get_cache_size(ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(16907456usize, get_cache_size(ETHASH_EPOCH_LENGTH + 1));
|
||||
assert_eq!(284950208usize, get_cache_size(2046 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2047 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2048 * ETHASH_EPOCH_LENGTH - 1));
|
||||
}
|
||||
|
||||
Light {
|
||||
block_number,
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache: nodes,
|
||||
seed_compute: Mutex::new(seed_compute),
|
||||
}
|
||||
}
|
||||
|
||||
static CHARS: &'static [u8] = b"0123456789abcdef";
|
||||
fn to_hex(bytes: &[u8]) -> String {
|
||||
let mut v = Vec::with_capacity(bytes.len() * 2);
|
||||
for &byte in bytes.iter() {
|
||||
v.push(CHARS[(byte >> 4) as usize]);
|
||||
v.push(CHARS[(byte & 0xf) as usize]);
|
||||
#[test]
|
||||
fn test_get_data_size() {
|
||||
// https://github.com/ethereum/wiki/wiki/Ethash/ef6b93f9596746a088ea95d01ca2778be43ae68f#data-sizes
|
||||
assert_eq!(1073739904usize, get_data_size(0));
|
||||
assert_eq!(1073739904usize, get_data_size(1));
|
||||
assert_eq!(1073739904usize, get_data_size(ETHASH_EPOCH_LENGTH - 1));
|
||||
assert_eq!(1082130304usize, get_data_size(ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(1082130304usize, get_data_size(ETHASH_EPOCH_LENGTH + 1));
|
||||
assert_eq!(18236833408usize, get_data_size(2046 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(18245220736usize, get_data_size(2047 * ETHASH_EPOCH_LENGTH));
|
||||
}
|
||||
|
||||
unsafe { String::from_utf8_unchecked(v) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cache_size() {
|
||||
// https://github.com/ethereum/wiki/wiki/Ethash/ef6b93f9596746a088ea95d01ca2778be43ae68f#data-sizes
|
||||
assert_eq!(16776896usize, get_cache_size(0));
|
||||
assert_eq!(16776896usize, get_cache_size(1));
|
||||
assert_eq!(16776896usize, get_cache_size(ETHASH_EPOCH_LENGTH - 1));
|
||||
assert_eq!(16907456usize, get_cache_size(ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(16907456usize, get_cache_size(ETHASH_EPOCH_LENGTH + 1));
|
||||
assert_eq!(284950208usize, get_cache_size(2046 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2047 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(285081536usize, get_cache_size(2048 * ETHASH_EPOCH_LENGTH - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_data_size() {
|
||||
// https://github.com/ethereum/wiki/wiki/Ethash/ef6b93f9596746a088ea95d01ca2778be43ae68f#data-sizes
|
||||
assert_eq!(1073739904usize, get_data_size(0));
|
||||
assert_eq!(1073739904usize, get_data_size(1));
|
||||
assert_eq!(1073739904usize, get_data_size(ETHASH_EPOCH_LENGTH - 1));
|
||||
assert_eq!(1082130304usize, get_data_size(ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(1082130304usize, get_data_size(ETHASH_EPOCH_LENGTH + 1));
|
||||
assert_eq!(18236833408usize, get_data_size(2046 * ETHASH_EPOCH_LENGTH));
|
||||
assert_eq!(18245220736usize, get_data_size(2047 * ETHASH_EPOCH_LENGTH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_difficulty_test() {
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
let boundary_good = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
assert_eq!(quick_get_difficulty(&hash, nonce, &mix_hash)[..], boundary_good[..]);
|
||||
let boundary_bad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3a, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
assert!(quick_get_difficulty(&hash, nonce, &mix_hash)[..] != boundary_bad[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_light_compute() {
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let mix_hash = [0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce, 0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a, 0x64, 0x31, 0xab, 0x6d];
|
||||
let boundary = [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2, 0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a, 0xe9, 0x7e, 0x53, 0x84];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
let light = Light::new(&::std::env::temp_dir(), 486382);
|
||||
let result = light_compute(&light, &hash, nonce);
|
||||
assert_eq!(result.mix_hash[..], mix_hash[..]);
|
||||
assert_eq!(result.value[..], boundary[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_once() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_zero() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
assert_eq!(seed_compute.get_seedhash(0), [0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_older() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an older value first shouldn't affect the result
|
||||
let _ = seed_compute.get_seedhash(50000);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_newer() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an newer value first shouldn't affect the result
|
||||
let _ = seed_compute.get_seedhash(972764);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.get_seedhash(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let path = ::std::env::temp_dir();
|
||||
let first = Light::new(&path, 0).to_file().unwrap();
|
||||
|
||||
let second = Light::new(&path, ETHASH_EPOCH_LENGTH).to_file().unwrap();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = Light::new(&path, ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
#[test]
|
||||
fn test_difficulty_test() {
|
||||
let hash = [
|
||||
0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3,
|
||||
0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94,
|
||||
0x05, 0x52, 0x7d, 0x72,
|
||||
];
|
||||
let mix_hash = [
|
||||
0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce,
|
||||
0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a,
|
||||
0x64, 0x31, 0xab, 0x6d,
|
||||
];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
let boundary_good = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2,
|
||||
0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a,
|
||||
0xe9, 0x7e, 0x53, 0x84,
|
||||
];
|
||||
assert_eq!(quick_get_difficulty(&hash, nonce, &mix_hash)[..], boundary_good[..]);
|
||||
let boundary_bad = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3a, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2,
|
||||
0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a,
|
||||
0xe9, 0x7e, 0x53, 0x84,
|
||||
];
|
||||
assert!(quick_get_difficulty(&hash, nonce, &mix_hash)[..] != boundary_bad[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_light_compute() {
|
||||
let hash = [
|
||||
0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3,
|
||||
0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94,
|
||||
0x05, 0x52, 0x7d, 0x72,
|
||||
];
|
||||
let mix_hash = [
|
||||
0x1f, 0xff, 0x04, 0xce, 0xc9, 0x41, 0x73, 0xfd, 0x59, 0x1e, 0x3d, 0x89, 0x60, 0xce,
|
||||
0x6b, 0xdf, 0x8b, 0x19, 0x71, 0x04, 0x8c, 0x71, 0xff, 0x93, 0x7b, 0xb2, 0xd3, 0x2a,
|
||||
0x64, 0x31, 0xab, 0x6d,
|
||||
];
|
||||
let boundary = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3e, 0x9b, 0x6c, 0x69, 0xbc, 0x2c, 0xe2, 0xa2,
|
||||
0x4a, 0x8e, 0x95, 0x69, 0xef, 0xc7, 0xd7, 0x1b, 0x33, 0x35, 0xdf, 0x36, 0x8c, 0x9a,
|
||||
0xe9, 0x7e, 0x53, 0x84,
|
||||
];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
let light = NodeCacheBuilder::new(None).light(&::std::env::temp_dir(), 486382);
|
||||
let result = light_compute(&light, &hash, nonce);
|
||||
assert_eq!(result.mix_hash[..], mix_hash[..]);
|
||||
assert_eq!(result.value[..], boundary[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let path = ::std::env::temp_dir();
|
||||
let builder = NodeCacheBuilder::new(None);
|
||||
let first = builder.light(&path, 0).to_file().unwrap().to_owned();
|
||||
|
||||
let second = builder.light(&path, ETHASH_EPOCH_LENGTH).to_file().unwrap().to_owned();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = builder.light(&path, ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = builder.light(&path, ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
52
ethash/src/keccak.rs
Normal file
52
ethash/src/keccak.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate hash;
|
||||
|
||||
pub type H256 = [u8; 32];
|
||||
|
||||
pub mod keccak_512 {
|
||||
use super::hash;
|
||||
|
||||
pub use self::hash::keccak_512 as unchecked;
|
||||
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
unsafe { hash::keccak_512(output.as_mut_ptr(), output.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
|
||||
pub fn inplace(input: &mut [u8]) {
|
||||
// This is safe since `sha3_*` uses an internal buffer and copies the result to the output. This
|
||||
// means that we can reuse the input buffer for both input and output.
|
||||
unsafe { hash::keccak_512(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
}
|
||||
|
||||
pub mod keccak_256 {
|
||||
use super::hash;
|
||||
|
||||
pub use self::hash::keccak_256 as unchecked;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
unsafe { hash::keccak_256(output.as_mut_ptr(), output.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
|
||||
pub fn inplace(input: &mut [u8]) {
|
||||
// This is safe since `sha3_*` uses an internal buffer and copies the result to the output. This
|
||||
// means that we can reuse the input buffer for both input and output.
|
||||
unsafe { hash::keccak_256(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len()) };
|
||||
}
|
||||
}
|
||||
@@ -14,28 +14,35 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethash implementation
|
||||
//! See https://github.com/ethereum/wiki/wiki/Ethash
|
||||
|
||||
#![cfg_attr(feature = "benches", feature(test))]
|
||||
|
||||
extern crate primal;
|
||||
extern crate hash;
|
||||
extern crate parking_lot;
|
||||
extern crate either;
|
||||
extern crate memmap;
|
||||
|
||||
#[macro_use]
|
||||
extern crate crunchy;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
mod compute;
|
||||
|
||||
mod compute;
|
||||
mod seed_compute;
|
||||
mod cache;
|
||||
mod keccak;
|
||||
mod shared;
|
||||
|
||||
pub use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
pub use compute::{ProofOfWork, quick_get_difficulty, slow_hash_block_number};
|
||||
use compute::Light;
|
||||
use keccak::H256;
|
||||
use parking_lot::Mutex;
|
||||
pub use seed_compute::SeedHashCompute;
|
||||
pub use shared::ETHASH_EPOCH_LENGTH;
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
use compute::Light;
|
||||
pub use compute::{ETHASH_EPOCH_LENGTH, H256, ProofOfWork, SeedHashCompute, quick_get_difficulty, slow_get_seedhash};
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
struct LightCache {
|
||||
recent_epoch: Option<u64>,
|
||||
@@ -46,15 +53,17 @@ struct LightCache {
|
||||
|
||||
/// Light/Full cache manager.
|
||||
pub struct EthashManager {
|
||||
nodecache_builder: NodeCacheBuilder,
|
||||
cache: Mutex<LightCache>,
|
||||
cache_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl EthashManager {
|
||||
/// Create a new new instance of ethash manager
|
||||
pub fn new<T: AsRef<Path>>(cache_dir: T) -> EthashManager {
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(cache_dir: &Path, optimize_for: T) -> EthashManager {
|
||||
EthashManager {
|
||||
cache_dir: cache_dir.as_ref().to_path_buf(),
|
||||
cache_dir: cache_dir.to_path_buf(),
|
||||
nodecache_builder: NodeCacheBuilder::new(optimize_for.into().unwrap_or_default()),
|
||||
cache: Mutex::new(LightCache {
|
||||
recent_epoch: None,
|
||||
recent: None,
|
||||
@@ -96,11 +105,19 @@ impl EthashManager {
|
||||
};
|
||||
match light {
|
||||
None => {
|
||||
let light = match Light::from_file(&self.cache_dir, block_number) {
|
||||
let light = match Light::from_file_with_builder(
|
||||
&self.nodecache_builder,
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
) {
|
||||
Ok(light) => Arc::new(light),
|
||||
Err(e) => {
|
||||
debug!("Light cache file not found for {}:{}", block_number, e);
|
||||
let light = Light::new(&self.cache_dir, block_number);
|
||||
let mut light = Light::new_with_builder(
|
||||
&self.nodecache_builder,
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
);
|
||||
if let Err(e) = light.to_file() {
|
||||
warn!("Light cache file write error: {}", e);
|
||||
}
|
||||
@@ -120,7 +137,7 @@ impl EthashManager {
|
||||
|
||||
#[test]
|
||||
fn test_lru() {
|
||||
let ethash = EthashManager::new(&::std::env::temp_dir());
|
||||
let ethash = EthashManager::new(&::std::env::temp_dir(), None);
|
||||
let hash = [0u8; 32];
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
ethash.compute_light(50000, &hash, 1);
|
||||
@@ -138,24 +155,89 @@ fn test_lru() {
|
||||
mod benchmarks {
|
||||
extern crate test;
|
||||
|
||||
use compute::{Light, light_compute, SeedHashCompute};
|
||||
use self::test::Bencher;
|
||||
use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
use compute::{Light, light_compute};
|
||||
|
||||
const HASH: [u8; 32] = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe,
|
||||
0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f,
|
||||
0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
const NONCE: u64 = 0xd7b3ac70a301a249;
|
||||
|
||||
#[bench]
|
||||
fn bench_light_compute(b: &mut Bencher) {
|
||||
use ::std::env;
|
||||
fn bench_light_compute_memmap(b: &mut Bencher) {
|
||||
use std::env;
|
||||
|
||||
let hash = [0xf5, 0x7e, 0x6f, 0x3a, 0xcf, 0xc0, 0xdd, 0x4b, 0x5b, 0xf2, 0xbe, 0xe4, 0x0a, 0xb3, 0x35, 0x8a, 0xa6, 0x87, 0x73, 0xa8, 0xd0, 0x9f, 0x5e, 0x59, 0x5e, 0xab, 0x55, 0x94, 0x05, 0x52, 0x7d, 0x72];
|
||||
let nonce = 0xd7b3ac70a301a249;
|
||||
let light = Light::new(env::temp_dir(), 486382);
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory);
|
||||
let light = Light::new_with_builder(&builder, &env::temp_dir(), 486382);
|
||||
|
||||
b.iter(|| light_compute(&light, &hash, nonce));
|
||||
b.iter(|| light_compute(&light, &HASH, NONCE));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_seedhash(b: &mut Bencher) {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
fn bench_light_compute_memory(b: &mut Bencher) {
|
||||
use std::env;
|
||||
|
||||
b.iter(|| seed_compute.get_seedhash(486382));
|
||||
let light = Light::new(&env::temp_dir(), 486382);
|
||||
|
||||
b.iter(|| light_compute(&light, &HASH, NONCE));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_light_new_round_trip_memmap(b: &mut Bencher) {
|
||||
use std::env;
|
||||
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory);
|
||||
let light = Light::new_with_builder(&builder, &env::temp_dir(), 486382);
|
||||
light_compute(&light, &HASH, NONCE);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_light_new_round_trip_memory(b: &mut Bencher) {
|
||||
use std::env;
|
||||
b.iter(|| {
|
||||
let light = Light::new(&env::temp_dir(), 486382);
|
||||
light_compute(&light, &HASH, NONCE);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_light_from_file_round_trip_memory(b: &mut Bencher) {
|
||||
use std::env;
|
||||
|
||||
let dir = env::temp_dir();
|
||||
let height = 486382;
|
||||
{
|
||||
let mut dummy = Light::new(&dir, height);
|
||||
dummy.to_file().unwrap();
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let light = Light::from_file(&dir, 486382).unwrap();
|
||||
light_compute(&light, &HASH, NONCE);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_light_from_file_round_trip_memmap(b: &mut Bencher) {
|
||||
use std::env;
|
||||
|
||||
let dir = env::temp_dir();
|
||||
let height = 486382;
|
||||
{
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory);
|
||||
let mut dummy = Light::new_with_builder(&builder, &dir, height);
|
||||
dummy.to_file().unwrap();
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory);
|
||||
let light = Light::from_file_with_builder(&builder, &dir, 486382).unwrap();
|
||||
light_compute(&light, &HASH, NONCE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
109
ethash/src/seed_compute.rs
Normal file
109
ethash/src/seed_compute.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use shared;
|
||||
use keccak::{keccak_256, H256};
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
pub struct SeedHashCompute {
|
||||
prev_epoch: Cell<u64>,
|
||||
prev_seedhash: Cell<H256>,
|
||||
}
|
||||
|
||||
impl SeedHashCompute {
|
||||
#[inline]
|
||||
pub fn new() -> SeedHashCompute {
|
||||
SeedHashCompute {
|
||||
prev_epoch: Cell::new(0),
|
||||
prev_seedhash: Cell::new([0u8; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_cache(&self) {
|
||||
self.prev_epoch.set(0);
|
||||
self.prev_seedhash.set([0u8; 32]);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash_block_number(&self, block_number: u64) -> H256 {
|
||||
self.hash_epoch(shared::epoch(block_number))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash_epoch(&self, epoch: u64) -> H256 {
|
||||
if epoch < self.prev_epoch.get() {
|
||||
// can't build on previous hash if requesting an older block
|
||||
self.reset_cache();
|
||||
}
|
||||
if epoch > self.prev_epoch.get() {
|
||||
let seed_hash = SeedHashCompute::resume_compute_seedhash(
|
||||
self.prev_seedhash.get(),
|
||||
self.prev_epoch.get(),
|
||||
epoch,
|
||||
);
|
||||
self.prev_seedhash.set(seed_hash);
|
||||
self.prev_epoch.set(epoch);
|
||||
}
|
||||
self.prev_seedhash.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resume_compute_seedhash(mut hash: H256, start_epoch: u64, end_epoch: u64) -> H256 {
|
||||
for _ in start_epoch..end_epoch {
|
||||
keccak_256::inplace(&mut hash);
|
||||
}
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SeedHashCompute;
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_once() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_zero() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
assert_eq!(seed_compute.hash_block_number(0), [0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_older() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an older value first shouldn't affect the result
|
||||
let _ = seed_compute.hash_block_number(50000);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_newer() {
|
||||
let seed_compute = SeedHashCompute::new();
|
||||
// calculating an newer value first shouldn't affect the result
|
||||
let _ = seed_compute.hash_block_number(972764);
|
||||
let hash = [241, 175, 44, 134, 39, 121, 245, 239, 228, 236, 43, 160, 195, 152, 46, 7, 199, 5, 253, 147, 241, 206, 98, 43, 3, 104, 17, 40, 192, 79, 106, 162];
|
||||
assert_eq!(seed_compute.hash_block_number(486382), hash);
|
||||
}
|
||||
|
||||
}
|
||||
149
ethash/src/shared.rs
Normal file
149
ethash/src/shared.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use primal::is_prime;
|
||||
|
||||
pub const DATASET_BYTES_INIT: u64 = 1 << 30;
|
||||
pub const DATASET_BYTES_GROWTH: u64 = 1 << 23;
|
||||
pub const CACHE_BYTES_INIT: u64 = 1 << 24;
|
||||
pub const CACHE_BYTES_GROWTH: u64 = 1 << 17;
|
||||
|
||||
pub const ETHASH_EPOCH_LENGTH: u64 = 30000;
|
||||
pub const ETHASH_CACHE_ROUNDS: usize = 3;
|
||||
pub const ETHASH_MIX_BYTES: usize = 128;
|
||||
pub const ETHASH_ACCESSES: usize = 64;
|
||||
pub const ETHASH_DATASET_PARENTS: u32 = 256;
|
||||
pub const NODE_DWORDS: usize = NODE_WORDS / 2;
|
||||
pub const NODE_WORDS: usize = NODE_BYTES / 4;
|
||||
pub const NODE_BYTES: usize = 64;
|
||||
|
||||
pub fn epoch(block_number: u64) -> u64 {
|
||||
block_number / ETHASH_EPOCH_LENGTH
|
||||
}
|
||||
|
||||
static CHARS: &'static [u8] = b"0123456789abcdef";
|
||||
pub fn to_hex(bytes: &[u8]) -> String {
|
||||
let mut v = Vec::with_capacity(bytes.len() * 2);
|
||||
for &byte in bytes.iter() {
|
||||
v.push(CHARS[(byte >> 4) as usize]);
|
||||
v.push(CHARS[(byte & 0xf) as usize]);
|
||||
}
|
||||
|
||||
unsafe { String::from_utf8_unchecked(v) }
|
||||
}
|
||||
|
||||
pub fn get_cache_size(block_number: u64) -> usize {
|
||||
// TODO: Memoise
|
||||
let mut sz: u64 = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - NODE_BYTES as u64;
|
||||
while !is_prime(sz / NODE_BYTES as u64) {
|
||||
sz = sz - 2 * NODE_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
pub fn get_data_size(block_number: u64) -> usize {
|
||||
// TODO: Memoise
|
||||
let mut sz: u64 = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number / ETHASH_EPOCH_LENGTH);
|
||||
sz = sz - ETHASH_MIX_BYTES as u64;
|
||||
while !is_prime(sz / ETHASH_MIX_BYTES as u64) {
|
||||
sz = sz - 2 * ETHASH_MIX_BYTES as u64;
|
||||
}
|
||||
sz as usize
|
||||
}
|
||||
|
||||
pub type NodeBytes = [u8; NODE_BYTES];
|
||||
pub type NodeWords = [u32; NODE_WORDS];
|
||||
pub type NodeDwords = [u64; NODE_DWORDS];
|
||||
|
||||
macro_rules! static_assert_size_eq {
|
||||
(@inner $a:ty, $b:ty, $($rest:ty),*) => {
|
||||
fn first() {
|
||||
static_assert_size_eq!($a, $b);
|
||||
}
|
||||
|
||||
fn second() {
|
||||
static_assert_size_eq!($b, $($rest),*);
|
||||
}
|
||||
};
|
||||
(@inner $a:ty, $b:ty) => {
|
||||
unsafe {
|
||||
let val: $b = ::std::mem::uninitialized();
|
||||
let _: $a = ::std::mem::transmute(val);
|
||||
}
|
||||
};
|
||||
($($rest:ty),*) => {
|
||||
static_assert_size_eq!(size_eq: $($rest),*);
|
||||
};
|
||||
($name:ident : $($rest:ty),*) => {
|
||||
#[allow(dead_code)]
|
||||
fn $name() {
|
||||
static_assert_size_eq!(@inner $($rest),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static_assert_size_eq!(Node, NodeBytes, NodeWords, NodeDwords);
|
||||
|
||||
#[repr(C)]
|
||||
pub union Node {
|
||||
pub dwords: NodeDwords,
|
||||
pub words: NodeWords,
|
||||
pub bytes: NodeBytes,
|
||||
}
|
||||
|
||||
impl Clone for Node {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { Node { bytes: *&self.bytes } }
|
||||
}
|
||||
}
|
||||
|
||||
// We use `inline(always)` because I was experiencing an 100% slowdown and `perf` showed that these
|
||||
// calls were taking up ~30% of the runtime. Adding these annotations fixes the issue. Remove at
|
||||
// your peril, if and only if you have benchmarks to prove that this doesn't reintroduce the
|
||||
// performance regression. It's not caused by the `debug_assert_eq!` either, your guess is as good
|
||||
// as mine.
|
||||
impl Node {
|
||||
#[inline(always)]
|
||||
pub fn as_bytes(&self) -> &NodeBytes {
|
||||
unsafe { &self.bytes }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_bytes_mut(&mut self) -> &mut NodeBytes {
|
||||
unsafe { &mut self.bytes }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_words(&self) -> &NodeWords {
|
||||
unsafe { &self.words }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_words_mut(&mut self) -> &mut NodeWords {
|
||||
unsafe { &mut self.words }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_dwords(&self) -> &NodeDwords {
|
||||
unsafe { &self.dwords }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_dwords_mut(&mut self) -> &mut NodeDwords {
|
||||
unsafe { &mut self.dwords }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user