Reformat the source code
This commit is contained in:
@@ -16,291 +16,298 @@
|
||||
|
||||
use compute::Light;
|
||||
use either::Either;
|
||||
use keccak::{H256, keccak_512};
|
||||
use keccak::{keccak_512, H256};
|
||||
use memmap::MmapMut;
|
||||
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 shared::{epoch, get_cache_size, to_hex, Node, ETHASH_CACHE_ROUNDS, NODE_BYTES, NODE_DWORDS};
|
||||
|
||||
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;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
slice,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
type Cache = Either<Vec<Node>, MmapMut>;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum OptimizeFor {
|
||||
Cpu,
|
||||
Memory,
|
||||
Cpu,
|
||||
Memory,
|
||||
}
|
||||
|
||||
impl Default for OptimizeFor {
|
||||
fn default() -> Self {
|
||||
OptimizeFor::Cpu
|
||||
}
|
||||
fn default() -> Self {
|
||||
OptimizeFor::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_size(cache: &Cache) -> usize {
|
||||
use self::Either::{Left, Right};
|
||||
use self::Either::{Left, Right};
|
||||
|
||||
match *cache {
|
||||
Left(ref vec) => vec.len() * NODE_BYTES,
|
||||
Right(ref mmap) => mmap.len(),
|
||||
}
|
||||
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(),
|
||||
};
|
||||
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))
|
||||
})
|
||||
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,
|
||||
progpow_transition: u64,
|
||||
// TODO: Remove this locking and just use an `Rc`?
|
||||
seedhash: Arc<Mutex<SeedHashCompute>>,
|
||||
optimize_for: OptimizeFor,
|
||||
progpow_transition: u64,
|
||||
}
|
||||
|
||||
// TODO: Abstract the "optimize for" logic
|
||||
pub struct NodeCache {
|
||||
builder: NodeCacheBuilder,
|
||||
cache_dir: Cow<'static, Path>,
|
||||
cache_path: PathBuf,
|
||||
epoch: u64,
|
||||
cache: Cache,
|
||||
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, self.progpow_transition)
|
||||
}
|
||||
pub fn light(&self, cache_dir: &Path, block_number: u64) -> Light {
|
||||
Light::new_with_builder(self, cache_dir, block_number, self.progpow_transition)
|
||||
}
|
||||
|
||||
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, self.progpow_transition)
|
||||
}
|
||||
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, self.progpow_transition)
|
||||
}
|
||||
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(optimize_for: T, progpow_transition: u64) -> Self {
|
||||
NodeCacheBuilder {
|
||||
seedhash: Arc::new(Mutex::new(SeedHashCompute::default())),
|
||||
optimize_for: optimize_for.into().unwrap_or_default(),
|
||||
progpow_transition
|
||||
}
|
||||
}
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(optimize_for: T, progpow_transition: u64) -> Self {
|
||||
NodeCacheBuilder {
|
||||
seedhash: Arc::new(Mutex::new(SeedHashCompute::default())),
|
||||
optimize_for: optimize_for.into().unwrap_or_default(),
|
||||
progpow_transition,
|
||||
}
|
||||
}
|
||||
|
||||
fn block_number_to_ident(&self, block_number: u64) -> H256 {
|
||||
self.seedhash.lock().hash_block_number(block_number)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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);
|
||||
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 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);
|
||||
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",
|
||||
))
|
||||
}
|
||||
}
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
// 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);
|
||||
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,
|
||||
}
|
||||
}
|
||||
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 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),
|
||||
});
|
||||
}
|
||||
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)
|
||||
}
|
||||
consume_cache(&mut self.cache, &self.cache_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_memmapped_cache(path: &Path, num_nodes: usize, ident: &H256) -> io::Result<MmapMut> {
|
||||
use std::fs::OpenOptions;
|
||||
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 file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
file.set_len((num_nodes * NODE_BYTES) as _)?;
|
||||
|
||||
let mut memmap = unsafe { MmapMut::map_mut(&file)? };
|
||||
let mut memmap = unsafe { MmapMut::map_mut(&file)? };
|
||||
|
||||
unsafe { initialize_memory(memmap.as_mut_ptr() as *mut Node, num_nodes, ident) };
|
||||
unsafe { initialize_memory(memmap.as_mut_ptr() as *mut Node, num_nodes, ident) };
|
||||
|
||||
Ok(memmap)
|
||||
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);
|
||||
}
|
||||
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
|
||||
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
|
||||
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;
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
match *cache {
|
||||
Either::Left(ref mut vec) => {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
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)
|
||||
};
|
||||
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()
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
let file = fs::OpenOptions::new().read(true).write(true).create(true).open(path)?;
|
||||
unsafe { MmapMut::map_mut(&file).ok() }
|
||||
},
|
||||
};
|
||||
let memmap = match optimize_for {
|
||||
OptimizeFor::Cpu => None,
|
||||
OptimizeFor::Memory => {
|
||||
let file = fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)?;
|
||||
unsafe { MmapMut::map_mut(&file).ok() }
|
||||
}
|
||||
};
|
||||
|
||||
memmap.map(Either::Right).ok_or(()).or_else(|_| {
|
||||
read_from_path(path).map(Either::Left)
|
||||
})
|
||||
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;
|
||||
use std::{fs::File, mem};
|
||||
|
||||
let mut file = File::open(path)?;
|
||||
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)?;
|
||||
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();
|
||||
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",
|
||||
));
|
||||
}
|
||||
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,
|
||||
)
|
||||
};
|
||||
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);
|
||||
mem::forget(nodes);
|
||||
|
||||
Ok(out)
|
||||
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.as_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)
|
||||
},
|
||||
}
|
||||
}
|
||||
fn as_ref(&self) -> &[Node] {
|
||||
match self.cache {
|
||||
Either::Left(ref vec) => vec,
|
||||
Either::Right(ref mmap) => unsafe {
|
||||
let bytes = mmap.as_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
|
||||
@@ -311,47 +318,47 @@ impl AsRef<[Node]> for NodeCache {
|
||||
// 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;
|
||||
let dst = memory as *mut u8;
|
||||
|
||||
debug_assert_eq!(ident.len(), 32);
|
||||
keccak_512::unchecked(dst, NODE_BYTES, ident.as_ptr(), ident.len());
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
// 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);
|
||||
// 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;
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
unroll! {
|
||||
for w in 0..8 {
|
||||
*data.as_dwords_mut().get_unchecked_mut(w) ^=
|
||||
*rhs.as_dwords().get_unchecked(w);
|
||||
}
|
||||
}
|
||||
|
||||
data
|
||||
};
|
||||
data
|
||||
};
|
||||
|
||||
keccak_512::write(&data.bytes, &mut nodes.get_unchecked_mut(i).bytes);
|
||||
}
|
||||
}
|
||||
keccak_512::write(&data.bytes, &mut nodes.get_unchecked_mut(i).bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,14 @@
|
||||
|
||||
// TODO: fix endianess for big endian
|
||||
|
||||
use keccak::{keccak_512, keccak_256, H256};
|
||||
use cache::{NodeCache, NodeCacheBuilder};
|
||||
use progpow::{CDag, generate_cdag, progpow, keccak_f800_short, keccak_f800_long};
|
||||
use keccak::{keccak_256, keccak_512, H256};
|
||||
use progpow::{generate_cdag, keccak_f800_long, keccak_f800_short, progpow, CDag};
|
||||
use seed_compute::SeedHashCompute;
|
||||
use shared::*;
|
||||
use std::io;
|
||||
|
||||
use std::{mem, ptr};
|
||||
use std::path::Path;
|
||||
use std::{mem, path::Path, ptr};
|
||||
|
||||
const MIX_WORDS: usize = ETHASH_MIX_BYTES / 4;
|
||||
const MIX_NODES: usize = MIX_WORDS / NODE_WORDS;
|
||||
@@ -35,92 +34,99 @@ pub const FNV_PRIME: u32 = 0x01000193;
|
||||
|
||||
/// Computation result
|
||||
pub struct ProofOfWork {
|
||||
/// Difficulty boundary
|
||||
pub value: H256,
|
||||
/// Mix
|
||||
pub mix_hash: H256,
|
||||
/// Difficulty boundary
|
||||
pub value: H256,
|
||||
/// Mix
|
||||
pub mix_hash: H256,
|
||||
}
|
||||
|
||||
enum Algorithm {
|
||||
Hashimoto,
|
||||
Progpow(Box<CDag>),
|
||||
Hashimoto,
|
||||
Progpow(Box<CDag>),
|
||||
}
|
||||
|
||||
pub struct Light {
|
||||
block_number: u64,
|
||||
cache: NodeCache,
|
||||
algorithm: Algorithm,
|
||||
block_number: u64,
|
||||
cache: NodeCache,
|
||||
algorithm: Algorithm,
|
||||
}
|
||||
|
||||
/// Light cache structure
|
||||
impl Light {
|
||||
pub fn new_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
progpow_transition: u64,
|
||||
) -> Self {
|
||||
let cache = builder.new_cache(cache_dir.to_path_buf(), block_number);
|
||||
pub fn new_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
progpow_transition: u64,
|
||||
) -> Self {
|
||||
let cache = builder.new_cache(cache_dir.to_path_buf(), block_number);
|
||||
|
||||
let algorithm = if block_number >= progpow_transition {
|
||||
Algorithm::Progpow(Box::new(generate_cdag(cache.as_ref())))
|
||||
} else {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
let algorithm = if block_number >= progpow_transition {
|
||||
Algorithm::Progpow(Box::new(generate_cdag(cache.as_ref())))
|
||||
} else {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
|
||||
Light { block_number, cache, algorithm }
|
||||
}
|
||||
Light {
|
||||
block_number,
|
||||
cache,
|
||||
algorithm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the light boundary data
|
||||
/// `header_hash` - The header hash to pack into the mix
|
||||
/// `nonce` - The nonce to pack into the mix
|
||||
pub fn compute(&self, header_hash: &H256, nonce: u64, block_number: u64) -> ProofOfWork {
|
||||
match self.algorithm {
|
||||
Algorithm::Progpow(ref c_dag) => {
|
||||
let (value, mix_hash) = progpow(
|
||||
*header_hash,
|
||||
nonce,
|
||||
block_number,
|
||||
self.cache.as_ref(),
|
||||
c_dag,
|
||||
);
|
||||
/// Calculate the light boundary data
|
||||
/// `header_hash` - The header hash to pack into the mix
|
||||
/// `nonce` - The nonce to pack into the mix
|
||||
pub fn compute(&self, header_hash: &H256, nonce: u64, block_number: u64) -> ProofOfWork {
|
||||
match self.algorithm {
|
||||
Algorithm::Progpow(ref c_dag) => {
|
||||
let (value, mix_hash) = progpow(
|
||||
*header_hash,
|
||||
nonce,
|
||||
block_number,
|
||||
self.cache.as_ref(),
|
||||
c_dag,
|
||||
);
|
||||
|
||||
ProofOfWork { value, mix_hash }
|
||||
},
|
||||
Algorithm::Hashimoto => light_compute(self, header_hash, nonce),
|
||||
}
|
||||
ProofOfWork { value, mix_hash }
|
||||
}
|
||||
Algorithm::Hashimoto => light_compute(self, header_hash, nonce),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
pub fn from_file_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
progpow_transition: u64,
|
||||
) -> io::Result<Self> {
|
||||
let cache = builder.from_file(cache_dir.to_path_buf(), block_number)?;
|
||||
|
||||
pub fn from_file_with_builder(
|
||||
builder: &NodeCacheBuilder,
|
||||
cache_dir: &Path,
|
||||
block_number: u64,
|
||||
progpow_transition: u64,
|
||||
) -> io::Result<Self> {
|
||||
let cache = builder.from_file(cache_dir.to_path_buf(), block_number)?;
|
||||
let algorithm = if block_number >= progpow_transition {
|
||||
Algorithm::Progpow(Box::new(generate_cdag(cache.as_ref())))
|
||||
} else {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
|
||||
let algorithm = if block_number >= progpow_transition {
|
||||
Algorithm::Progpow(Box::new(generate_cdag(cache.as_ref())))
|
||||
} else {
|
||||
Algorithm::Hashimoto
|
||||
};
|
||||
Ok(Light {
|
||||
block_number,
|
||||
cache,
|
||||
algorithm,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Light { block_number, cache, algorithm })
|
||||
}
|
||||
|
||||
pub fn to_file(&mut self) -> io::Result<&Path> {
|
||||
self.cache.flush()?;
|
||||
Ok(self.cache.cache_path())
|
||||
}
|
||||
pub fn to_file(&mut self) -> io::Result<&Path> {
|
||||
self.cache.flush()?;
|
||||
Ok(self.cache.cache_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slow_hash_block_number(block_number: u64) -> H256 {
|
||||
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
|
||||
SeedHashCompute::resume_compute_seedhash([0u8; 32], 0, block_number / ETHASH_EPOCH_LENGTH)
|
||||
}
|
||||
|
||||
fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
return x.wrapping_mul(FNV_PRIME) ^ y;
|
||||
return x.wrapping_mul(FNV_PRIME) ^ y;
|
||||
}
|
||||
|
||||
/// Difficulty quick check for POW preverification
|
||||
@@ -129,33 +135,38 @@ fn fnv_hash(x: u32, y: u32) -> u32 {
|
||||
/// `nonce` The block's nonce
|
||||
/// `mix_hash` The mix digest hash
|
||||
/// Boundary recovered from mix hash
|
||||
pub fn quick_get_difficulty(header_hash: &H256, nonce: u64, mix_hash: &H256, progpow: bool) -> H256 {
|
||||
unsafe {
|
||||
if progpow {
|
||||
let seed = keccak_f800_short(*header_hash, nonce, [0u32; 8]);
|
||||
keccak_f800_long(*header_hash, seed, mem::transmute(*mix_hash))
|
||||
} else {
|
||||
// This is safe - the `keccak_512` call below reads the first 40 bytes (which we explicitly set
|
||||
// with two `copy_nonoverlapping` calls) but writes the first 64, and then we explicitly write
|
||||
// the next 32 bytes before we read the whole thing with `keccak_256`.
|
||||
//
|
||||
// This cannot be elided by the compiler as it doesn't know the implementation of
|
||||
// `keccak_512`.
|
||||
let mut buf: [u8; 64 + 32] = mem::uninitialized();
|
||||
pub fn quick_get_difficulty(
|
||||
header_hash: &H256,
|
||||
nonce: u64,
|
||||
mix_hash: &H256,
|
||||
progpow: bool,
|
||||
) -> H256 {
|
||||
unsafe {
|
||||
if progpow {
|
||||
let seed = keccak_f800_short(*header_hash, nonce, [0u32; 8]);
|
||||
keccak_f800_long(*header_hash, seed, mem::transmute(*mix_hash))
|
||||
} else {
|
||||
// This is safe - the `keccak_512` call below reads the first 40 bytes (which we explicitly set
|
||||
// with two `copy_nonoverlapping` calls) but writes the first 64, and then we explicitly write
|
||||
// the next 32 bytes before we read the whole thing with `keccak_256`.
|
||||
//
|
||||
// This cannot be elided by the compiler as it doesn't know the implementation of
|
||||
// `keccak_512`.
|
||||
let mut buf: [u8; 64 + 32] = mem::uninitialized();
|
||||
|
||||
ptr::copy_nonoverlapping(header_hash.as_ptr(), buf.as_mut_ptr(), 32);
|
||||
ptr::copy_nonoverlapping(&nonce as *const u64 as *const u8, buf[32..].as_mut_ptr(), 8);
|
||||
ptr::copy_nonoverlapping(header_hash.as_ptr(), buf.as_mut_ptr(), 32);
|
||||
ptr::copy_nonoverlapping(&nonce as *const u64 as *const u8, buf[32..].as_mut_ptr(), 8);
|
||||
|
||||
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);
|
||||
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();
|
||||
keccak_256::unchecked(hash.as_mut_ptr(), hash.len(), buf.as_ptr(), buf.len());
|
||||
// This is initialized in `keccak_256`
|
||||
let mut hash: [u8; 32] = mem::uninitialized();
|
||||
keccak_256::unchecked(hash.as_mut_ptr(), hash.len(), buf.as_ptr(), buf.len());
|
||||
|
||||
hash
|
||||
}
|
||||
}
|
||||
hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the light client data
|
||||
@@ -163,289 +174,310 @@ pub fn quick_get_difficulty(header_hash: &H256, nonce: u64, mix_hash: &H256, pro
|
||||
/// `header_hash` - The header hash to pack into the mix
|
||||
/// `nonce` - The nonce to pack into the mix
|
||||
pub fn light_compute(light: &Light, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
let full_size = get_data_size(light.block_number);
|
||||
hash_compute(light, full_size, header_hash, nonce)
|
||||
let full_size = get_data_size(light.block_number);
|
||||
hash_compute(light, full_size, header_hash, nonce)
|
||||
}
|
||||
|
||||
fn hash_compute(light: &Light, full_size: usize, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
macro_rules! make_const_array {
|
||||
($n:expr, $value:expr) => {{
|
||||
// We use explicit lifetimes to ensure that val's borrow is invalidated until the
|
||||
// transmuted val dies.
|
||||
unsafe fn make_const_array<T, U>(val: &mut [T]) -> &mut [U; $n] {
|
||||
use ::std::mem;
|
||||
macro_rules! make_const_array {
|
||||
($n:expr, $value:expr) => {{
|
||||
// We use explicit lifetimes to ensure that val's borrow is invalidated until the
|
||||
// transmuted val dies.
|
||||
unsafe fn make_const_array<T, U>(val: &mut [T]) -> &mut [U; $n] {
|
||||
use std::mem;
|
||||
|
||||
debug_assert_eq!(val.len() * mem::size_of::<T>(), $n * mem::size_of::<U>());
|
||||
&mut *(val.as_mut_ptr() as *mut [U; $n])
|
||||
}
|
||||
debug_assert_eq!(val.len() * mem::size_of::<T>(), $n * mem::size_of::<U>());
|
||||
&mut *(val.as_mut_ptr() as *mut [U; $n])
|
||||
}
|
||||
|
||||
make_const_array($value)
|
||||
}}
|
||||
}
|
||||
make_const_array($value)
|
||||
}};
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct MixBuf {
|
||||
half_mix: Node,
|
||||
compress_bytes: [u8; MIX_WORDS],
|
||||
};
|
||||
#[repr(C)]
|
||||
struct MixBuf {
|
||||
half_mix: Node,
|
||||
compress_bytes: [u8; MIX_WORDS],
|
||||
};
|
||||
|
||||
if full_size % MIX_WORDS != 0 {
|
||||
panic!("Unaligned full size");
|
||||
}
|
||||
if full_size % MIX_WORDS != 0 {
|
||||
panic!("Unaligned full size");
|
||||
}
|
||||
|
||||
// You may be asking yourself: what in the name of Crypto Jesus is going on here? So: we need
|
||||
// `half_mix` and `compress_bytes` in a single array later down in the code (we hash them
|
||||
// together to create `value`) so that we can hash the full array. However, we do a bunch of
|
||||
// reading and writing to these variables first. We originally allocated two arrays and then
|
||||
// stuck them together with `ptr::copy_nonoverlapping` at the end, but this method is
|
||||
// _significantly_ faster - by my benchmarks, a consistent 3-5%. This is the most ridiculous
|
||||
// optimization I have ever done and I am so sorry. I can only chalk it up to cache locality
|
||||
// improvements, since I can't imagine that 3-5% of our runtime is taken up by catting two
|
||||
// arrays together.
|
||||
let mut buf: MixBuf = MixBuf {
|
||||
half_mix: unsafe {
|
||||
// Pack `header_hash` and `nonce` together
|
||||
// We explicitly write the first 40 bytes, leaving the last 24 as uninitialized. Then
|
||||
// `keccak_512` reads the first 40 bytes (4th parameter) and overwrites the entire array,
|
||||
// leaving it fully initialized.
|
||||
let mut out: [u8; NODE_BYTES] = mem::uninitialized();
|
||||
// You may be asking yourself: what in the name of Crypto Jesus is going on here? So: we need
|
||||
// `half_mix` and `compress_bytes` in a single array later down in the code (we hash them
|
||||
// together to create `value`) so that we can hash the full array. However, we do a bunch of
|
||||
// reading and writing to these variables first. We originally allocated two arrays and then
|
||||
// stuck them together with `ptr::copy_nonoverlapping` at the end, but this method is
|
||||
// _significantly_ faster - by my benchmarks, a consistent 3-5%. This is the most ridiculous
|
||||
// optimization I have ever done and I am so sorry. I can only chalk it up to cache locality
|
||||
// improvements, since I can't imagine that 3-5% of our runtime is taken up by catting two
|
||||
// arrays together.
|
||||
let mut buf: MixBuf = MixBuf {
|
||||
half_mix: unsafe {
|
||||
// Pack `header_hash` and `nonce` together
|
||||
// We explicitly write the first 40 bytes, leaving the last 24 as uninitialized. Then
|
||||
// `keccak_512` reads the first 40 bytes (4th parameter) and overwrites the entire array,
|
||||
// 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(
|
||||
&nonce as *const u64 as *const u8,
|
||||
out[header_hash.len()..].as_mut_ptr(),
|
||||
mem::size_of::<u64>(),
|
||||
);
|
||||
ptr::copy_nonoverlapping(header_hash.as_ptr(), out.as_mut_ptr(), header_hash.len());
|
||||
ptr::copy_nonoverlapping(
|
||||
&nonce as *const u64 as *const u8,
|
||||
out[header_hash.len()..].as_mut_ptr(),
|
||||
mem::size_of::<u64>(),
|
||||
);
|
||||
|
||||
// compute keccak-512 hash and replicate across mix
|
||||
keccak_512::unchecked(
|
||||
out.as_mut_ptr(),
|
||||
NODE_BYTES,
|
||||
out.as_ptr(),
|
||||
header_hash.len() + mem::size_of::<u64>(),
|
||||
);
|
||||
// compute keccak-512 hash and replicate across mix
|
||||
keccak_512::unchecked(
|
||||
out.as_mut_ptr(),
|
||||
NODE_BYTES,
|
||||
out.as_ptr(),
|
||||
header_hash.len() + mem::size_of::<u64>(),
|
||||
);
|
||||
|
||||
Node { bytes: out }
|
||||
},
|
||||
// This is fully initialized before being read, see `let mut compress = ...` below
|
||||
compress_bytes: unsafe { mem::uninitialized() },
|
||||
};
|
||||
Node { bytes: out }
|
||||
},
|
||||
// This is fully initialized before being read, see `let mut compress = ...` below
|
||||
compress_bytes: unsafe { mem::uninitialized() },
|
||||
};
|
||||
|
||||
let mut mix: [_; MIX_NODES] = [buf.half_mix.clone(), buf.half_mix.clone()];
|
||||
let mut mix: [_; MIX_NODES] = [buf.half_mix.clone(), buf.half_mix.clone()];
|
||||
|
||||
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.as_ref();
|
||||
let first_val = buf.half_mix.as_words()[0];
|
||||
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.as_ref();
|
||||
let first_val = buf.half_mix.as_words()[0];
|
||||
|
||||
debug_assert_eq!(MIX_NODES, 2);
|
||||
debug_assert_eq!(NODE_WORDS, 16);
|
||||
debug_assert_eq!(MIX_NODES, 2);
|
||||
debug_assert_eq!(NODE_WORDS, 16);
|
||||
|
||||
for i in 0..ETHASH_ACCESSES as u32 {
|
||||
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) };
|
||||
for i in 0..ETHASH_ACCESSES as u32 {
|
||||
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) };
|
||||
|
||||
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! {
|
||||
// MIX_NODES
|
||||
for n in 0..2 {
|
||||
let tmp_node = calculate_dag_item(
|
||||
index * MIX_NODES as u32 + n as u32,
|
||||
cache,
|
||||
);
|
||||
unroll! {
|
||||
// MIX_NODES
|
||||
for n in 0..2 {
|
||||
let tmp_node = calculate_dag_item(
|
||||
index * MIX_NODES as u32 + n as u32,
|
||||
cache,
|
||||
);
|
||||
|
||||
unroll! {
|
||||
// NODE_WORDS
|
||||
for w in 0..16 {
|
||||
mix[n].as_words_mut()[w] =
|
||||
fnv_hash(
|
||||
mix[n].as_words()[w],
|
||||
tmp_node.as_words()[w],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unroll! {
|
||||
// NODE_WORDS
|
||||
for w in 0..16 {
|
||||
mix[n].as_words_mut()[w] =
|
||||
fnv_hash(
|
||||
mix[n].as_words()[w],
|
||||
tmp_node.as_words()[w],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mix_words: [u32; MIX_WORDS] = unsafe { mem::transmute(mix) };
|
||||
let mix_words: [u32; MIX_WORDS] = unsafe { mem::transmute(mix) };
|
||||
|
||||
{
|
||||
// This is an uninitialized buffer to begin with, but we iterate precisely `compress.len()`
|
||||
// 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 compress: &mut [u32; MIX_WORDS / 4] =
|
||||
unsafe { make_const_array!(MIX_WORDS / 4, &mut buf.compress_bytes) };
|
||||
{
|
||||
// This is an uninitialized buffer to begin with, but we iterate precisely `compress.len()`
|
||||
// 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 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);
|
||||
unroll! {
|
||||
for i in 0..8 {
|
||||
let w = i * 4;
|
||||
// Compress mix
|
||||
debug_assert_eq!(MIX_WORDS / 4, 8);
|
||||
unroll! {
|
||||
for i in 0..8 {
|
||||
let w = i * 4;
|
||||
|
||||
let mut reduction = mix_words[w + 0];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 1];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 2];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 3];
|
||||
compress[i] = reduction;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut reduction = mix_words[w + 0];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 1];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 2];
|
||||
reduction = reduction.wrapping_mul(FNV_PRIME) ^ mix_words[w + 3];
|
||||
compress[i] = reduction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mix_hash = buf.compress_bytes;
|
||||
let mix_hash = buf.compress_bytes;
|
||||
|
||||
let value: H256 = {
|
||||
// We can interpret the buffer as an array of `u8`s, since it's `repr(C)`.
|
||||
let read_ptr: *const u8 = &buf as *const MixBuf as *const u8;
|
||||
// We overwrite the second half since `keccak_256` has an internal buffer and so allows
|
||||
// overlapping arrays as input.
|
||||
let write_ptr: *mut u8 = &mut buf.compress_bytes as *mut [u8; 32] as *mut u8;
|
||||
unsafe {
|
||||
keccak_256::unchecked(
|
||||
write_ptr,
|
||||
buf.compress_bytes.len(),
|
||||
read_ptr,
|
||||
buf.half_mix.bytes.len() + buf.compress_bytes.len(),
|
||||
);
|
||||
}
|
||||
buf.compress_bytes
|
||||
};
|
||||
let value: H256 = {
|
||||
// We can interpret the buffer as an array of `u8`s, since it's `repr(C)`.
|
||||
let read_ptr: *const u8 = &buf as *const MixBuf as *const u8;
|
||||
// We overwrite the second half since `keccak_256` has an internal buffer and so allows
|
||||
// overlapping arrays as input.
|
||||
let write_ptr: *mut u8 = &mut buf.compress_bytes as *mut [u8; 32] as *mut u8;
|
||||
unsafe {
|
||||
keccak_256::unchecked(
|
||||
write_ptr,
|
||||
buf.compress_bytes.len(),
|
||||
read_ptr,
|
||||
buf.half_mix.bytes.len() + buf.compress_bytes.len(),
|
||||
);
|
||||
}
|
||||
buf.compress_bytes
|
||||
};
|
||||
|
||||
ProofOfWork { mix_hash: mix_hash, value: value }
|
||||
ProofOfWork {
|
||||
mix_hash: mix_hash,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the `simd` crate
|
||||
pub 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;
|
||||
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(ret.as_bytes_mut());
|
||||
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 = &cache[parent_index as usize];
|
||||
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 = &cache[parent_index as usize];
|
||||
|
||||
unroll! {
|
||||
for w in 0..16 {
|
||||
ret.as_words_mut()[w] = fnv_hash(ret.as_words()[w], parent.as_words()[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
unroll! {
|
||||
for w in 0..16 {
|
||||
ret.as_words_mut()[w] = fnv_hash(ret.as_words()[w], parent.as_words()[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keccak_512::inplace(ret.as_bytes_mut());
|
||||
keccak_512::inplace(ret.as_bytes_mut());
|
||||
|
||||
ret
|
||||
ret
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempdir::TempDir;
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[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_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_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, false)[..], 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, false)[..] != boundary_bad[..]);
|
||||
}
|
||||
#[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, false)[..],
|
||||
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, false)[..] != 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;
|
||||
#[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;
|
||||
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
let light = NodeCacheBuilder::new(None, u64::max_value()).light(tempdir.path(), 486382);
|
||||
let result = light_compute(&light, &hash, nonce);
|
||||
assert_eq!(result.mix_hash[..], mix_hash[..]);
|
||||
assert_eq!(result.value[..], boundary[..]);
|
||||
}
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
// difficulty = 0x085657254bd9u64;
|
||||
let light = NodeCacheBuilder::new(None, u64::max_value()).light(tempdir.path(), 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 tempdir = TempDir::new("").unwrap();
|
||||
let builder = NodeCacheBuilder::new(None, u64::max_value());
|
||||
let first = builder.light(tempdir.path(), 0).to_file().unwrap().to_owned();
|
||||
#[test]
|
||||
fn test_drop_old_data() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let builder = NodeCacheBuilder::new(None, u64::max_value());
|
||||
let first = builder
|
||||
.light(tempdir.path(), 0)
|
||||
.to_file()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
let second = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH).to_file().unwrap().to_owned();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
let second = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH)
|
||||
.to_file()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
assert!(fs::metadata(&first).is_ok());
|
||||
|
||||
let _ = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 2).to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
let _ = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 2)
|
||||
.to_file();
|
||||
assert!(fs::metadata(&first).is_err());
|
||||
assert!(fs::metadata(&second).is_ok());
|
||||
|
||||
let _ = builder.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 3).to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
let _ = builder
|
||||
.light(tempdir.path(), ETHASH_EPOCH_LENGTH * 3)
|
||||
.to_file();
|
||||
assert!(fs::metadata(&second).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,38 +19,48 @@ extern crate keccak_hash as hash;
|
||||
pub type H256 = [u8; 32];
|
||||
|
||||
pub mod keccak_512 {
|
||||
use super::hash;
|
||||
use super::hash;
|
||||
|
||||
pub use self::hash::keccak_512_unchecked as unchecked;
|
||||
pub use self::hash::keccak_512_unchecked as unchecked;
|
||||
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
hash::keccak_512(input, output);
|
||||
}
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
hash::keccak_512(input, output);
|
||||
}
|
||||
|
||||
pub fn 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_unchecked(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len());
|
||||
}
|
||||
}
|
||||
pub fn 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_unchecked(
|
||||
input.as_mut_ptr(),
|
||||
input.len(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod keccak_256 {
|
||||
use super::hash;
|
||||
use super::hash;
|
||||
|
||||
pub use self::hash::keccak_256_unchecked as unchecked;
|
||||
pub use self::hash::keccak_256_unchecked as unchecked;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
hash::keccak_256(input, output);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn write(input: &[u8], output: &mut [u8]) {
|
||||
hash::keccak_256(input, output);
|
||||
}
|
||||
|
||||
pub fn 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_256_unchecked(input.as_mut_ptr(), input.len(), input.as_ptr(), input.len());
|
||||
}
|
||||
}
|
||||
pub fn 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_256_unchecked(
|
||||
input.as_mut_ptr(),
|
||||
input.len(),
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ pub mod compute;
|
||||
#[cfg(not(feature = "bench"))]
|
||||
mod compute;
|
||||
|
||||
mod seed_compute;
|
||||
mod cache;
|
||||
mod keccak;
|
||||
mod seed_compute;
|
||||
mod shared;
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
@@ -50,190 +50,221 @@ pub mod progpow;
|
||||
mod progpow;
|
||||
|
||||
pub use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
pub use compute::{ProofOfWork, quick_get_difficulty, slow_hash_block_number};
|
||||
use compute::Light;
|
||||
pub use compute::{quick_get_difficulty, slow_hash_block_number, ProofOfWork};
|
||||
use ethereum_types::{U256, U512};
|
||||
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 std::{
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
struct LightCache {
|
||||
recent_epoch: Option<u64>,
|
||||
recent: Option<Arc<Light>>,
|
||||
prev_epoch: Option<u64>,
|
||||
prev: Option<Arc<Light>>,
|
||||
recent_epoch: Option<u64>,
|
||||
recent: Option<Arc<Light>>,
|
||||
prev_epoch: Option<u64>,
|
||||
prev: Option<Arc<Light>>,
|
||||
}
|
||||
|
||||
/// Light/Full cache manager.
|
||||
pub struct EthashManager {
|
||||
nodecache_builder: NodeCacheBuilder,
|
||||
cache: Mutex<LightCache>,
|
||||
cache_dir: PathBuf,
|
||||
progpow_transition: u64,
|
||||
nodecache_builder: NodeCacheBuilder,
|
||||
cache: Mutex<LightCache>,
|
||||
cache_dir: PathBuf,
|
||||
progpow_transition: u64,
|
||||
}
|
||||
|
||||
impl EthashManager {
|
||||
/// Create a new new instance of ethash manager
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(cache_dir: &Path, optimize_for: T, progpow_transition: u64) -> EthashManager {
|
||||
EthashManager {
|
||||
cache_dir: cache_dir.to_path_buf(),
|
||||
nodecache_builder: NodeCacheBuilder::new(optimize_for.into().unwrap_or_default(), progpow_transition),
|
||||
progpow_transition: progpow_transition,
|
||||
cache: Mutex::new(LightCache {
|
||||
recent_epoch: None,
|
||||
recent: None,
|
||||
prev_epoch: None,
|
||||
prev: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
/// Create a new new instance of ethash manager
|
||||
pub fn new<T: Into<Option<OptimizeFor>>>(
|
||||
cache_dir: &Path,
|
||||
optimize_for: T,
|
||||
progpow_transition: u64,
|
||||
) -> EthashManager {
|
||||
EthashManager {
|
||||
cache_dir: cache_dir.to_path_buf(),
|
||||
nodecache_builder: NodeCacheBuilder::new(
|
||||
optimize_for.into().unwrap_or_default(),
|
||||
progpow_transition,
|
||||
),
|
||||
progpow_transition: progpow_transition,
|
||||
cache: Mutex::new(LightCache {
|
||||
recent_epoch: None,
|
||||
recent: None,
|
||||
prev_epoch: None,
|
||||
prev: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the light client data
|
||||
/// `block_number` - Block number to check
|
||||
/// `light` - The light client handler
|
||||
/// `header_hash` - The header hash to pack into the mix
|
||||
/// `nonce` - The nonce to pack into the mix
|
||||
pub fn compute_light(&self, block_number: u64, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
let epoch = block_number / ETHASH_EPOCH_LENGTH;
|
||||
let light = {
|
||||
let mut lights = self.cache.lock();
|
||||
let light = if block_number == self.progpow_transition {
|
||||
// we need to regenerate the cache to trigger algorithm change to progpow inside `Light`
|
||||
None
|
||||
} else {
|
||||
match lights.recent_epoch.clone() {
|
||||
Some(ref e) if *e == epoch => lights.recent.clone(),
|
||||
_ => match lights.prev_epoch.clone() {
|
||||
Some(e) if e == epoch => {
|
||||
// don't swap if recent is newer.
|
||||
if lights.recent_epoch > lights.prev_epoch {
|
||||
None
|
||||
} else {
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
};
|
||||
/// Calculate the light client data
|
||||
/// `block_number` - Block number to check
|
||||
/// `light` - The light client handler
|
||||
/// `header_hash` - The header hash to pack into the mix
|
||||
/// `nonce` - The nonce to pack into the mix
|
||||
pub fn compute_light(&self, block_number: u64, header_hash: &H256, nonce: u64) -> ProofOfWork {
|
||||
let epoch = block_number / ETHASH_EPOCH_LENGTH;
|
||||
let light = {
|
||||
let mut lights = self.cache.lock();
|
||||
let light = if block_number == self.progpow_transition {
|
||||
// we need to regenerate the cache to trigger algorithm change to progpow inside `Light`
|
||||
None
|
||||
} else {
|
||||
match lights.recent_epoch.clone() {
|
||||
Some(ref e) if *e == epoch => lights.recent.clone(),
|
||||
_ => match lights.prev_epoch.clone() {
|
||||
Some(e) if e == epoch => {
|
||||
// don't swap if recent is newer.
|
||||
if lights.recent_epoch > lights.prev_epoch {
|
||||
None
|
||||
} else {
|
||||
// swap
|
||||
let t = lights.prev_epoch;
|
||||
lights.prev_epoch = lights.recent_epoch;
|
||||
lights.recent_epoch = t;
|
||||
let t = lights.prev.clone();
|
||||
lights.prev = lights.recent.clone();
|
||||
lights.recent = t;
|
||||
lights.recent.clone()
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match light {
|
||||
None => {
|
||||
let light = match self.nodecache_builder.light_from_file(
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
) {
|
||||
Ok(light) => Arc::new(light),
|
||||
Err(e) => {
|
||||
debug!("Light cache file not found for {}:{}", block_number, e);
|
||||
let mut light = self.nodecache_builder.light(
|
||||
&self.cache_dir,
|
||||
block_number,
|
||||
);
|
||||
if let Err(e) = light.to_file() {
|
||||
warn!("Light cache file write error: {}", e);
|
||||
}
|
||||
Arc::new(light)
|
||||
}
|
||||
};
|
||||
lights.prev_epoch = mem::replace(&mut lights.recent_epoch, Some(epoch));
|
||||
lights.prev = mem::replace(&mut lights.recent, Some(light.clone()));
|
||||
light
|
||||
}
|
||||
Some(light) => light,
|
||||
}
|
||||
};
|
||||
light.compute(header_hash, nonce, block_number)
|
||||
}
|
||||
match light {
|
||||
None => {
|
||||
let light = match self
|
||||
.nodecache_builder
|
||||
.light_from_file(&self.cache_dir, block_number)
|
||||
{
|
||||
Ok(light) => Arc::new(light),
|
||||
Err(e) => {
|
||||
debug!("Light cache file not found for {}:{}", block_number, e);
|
||||
let mut light =
|
||||
self.nodecache_builder.light(&self.cache_dir, block_number);
|
||||
if let Err(e) = light.to_file() {
|
||||
warn!("Light cache file write error: {}", e);
|
||||
}
|
||||
Arc::new(light)
|
||||
}
|
||||
};
|
||||
lights.prev_epoch = mem::replace(&mut lights.recent_epoch, Some(epoch));
|
||||
lights.prev = mem::replace(&mut lights.recent, Some(light.clone()));
|
||||
light
|
||||
}
|
||||
Some(light) => light,
|
||||
}
|
||||
};
|
||||
light.compute(header_hash, nonce, block_number)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an Ethash boundary to its original difficulty. Basically just `f(x) = 2^256 / x`.
|
||||
pub fn boundary_to_difficulty(boundary: ðereum_types::H256) -> U256 {
|
||||
difficulty_to_boundary_aux(&**boundary)
|
||||
difficulty_to_boundary_aux(&**boundary)
|
||||
}
|
||||
|
||||
/// Convert an Ethash difficulty to the target boundary. Basically just `f(x) = 2^256 / x`.
|
||||
pub fn difficulty_to_boundary(difficulty: &U256) -> ethereum_types::H256 {
|
||||
difficulty_to_boundary_aux(difficulty).into()
|
||||
difficulty_to_boundary_aux(difficulty).into()
|
||||
}
|
||||
|
||||
fn difficulty_to_boundary_aux<T: Into<U512>>(difficulty: T) -> ethereum_types::U256 {
|
||||
let difficulty = difficulty.into();
|
||||
let difficulty = difficulty.into();
|
||||
|
||||
assert!(!difficulty.is_zero());
|
||||
assert!(!difficulty.is_zero());
|
||||
|
||||
if difficulty == U512::one() {
|
||||
U256::max_value()
|
||||
} else {
|
||||
// difficulty > 1, so result should never overflow 256 bits
|
||||
U256::from((U512::one() << 256) / difficulty)
|
||||
}
|
||||
if difficulty == U512::one() {
|
||||
U256::max_value()
|
||||
} else {
|
||||
// difficulty > 1, so result should never overflow 256 bits
|
||||
U256::from((U512::one() << 256) / difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lru() {
|
||||
use tempdir::TempDir;
|
||||
use tempdir::TempDir;
|
||||
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let ethash = EthashManager::new(tempdir.path(), None, u64::max_value());
|
||||
let hash = [0u8; 32];
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
ethash.compute_light(50000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 1);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 1);
|
||||
ethash.compute_light(70000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 2);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let ethash = EthashManager::new(tempdir.path(), None, u64::max_value());
|
||||
let hash = [0u8; 32];
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
ethash.compute_light(50000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 1);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
ethash.compute_light(1, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 0);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 1);
|
||||
ethash.compute_light(70000, &hash, 1);
|
||||
assert_eq!(ethash.cache.lock().recent_epoch.unwrap(), 2);
|
||||
assert_eq!(ethash.cache.lock().prev_epoch.unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_difficulty_to_boundary() {
|
||||
use ethereum_types::H256;
|
||||
use std::str::FromStr;
|
||||
use ethereum_types::H256;
|
||||
use std::str::FromStr;
|
||||
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(1)), H256::from(U256::max_value()));
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(2)), H256::from_str("8000000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(4)), H256::from_str("4000000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(difficulty_to_boundary(&U256::from(32)), H256::from_str("0800000000000000000000000000000000000000000000000000000000000000").unwrap());
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(1)),
|
||||
H256::from(U256::max_value())
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(2)),
|
||||
H256::from_str("8000000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(4)),
|
||||
H256::from_str("4000000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
difficulty_to_boundary(&U256::from(32)),
|
||||
H256::from_str("0800000000000000000000000000000000000000000000000000000000000000").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_difficulty_to_boundary_regression() {
|
||||
use ethereum_types::H256;
|
||||
use ethereum_types::H256;
|
||||
|
||||
// the last bit was originally being truncated when performing the conversion
|
||||
// https://github.com/paritytech/parity-ethereum/issues/8397
|
||||
for difficulty in 1..9 {
|
||||
assert_eq!(U256::from(difficulty), boundary_to_difficulty(&difficulty_to_boundary(&difficulty.into())));
|
||||
assert_eq!(H256::from(difficulty), difficulty_to_boundary(&boundary_to_difficulty(&difficulty.into())));
|
||||
assert_eq!(U256::from(difficulty), boundary_to_difficulty(&boundary_to_difficulty(&difficulty.into()).into()));
|
||||
assert_eq!(H256::from(difficulty), difficulty_to_boundary(&difficulty_to_boundary(&difficulty.into()).into()));
|
||||
}
|
||||
// the last bit was originally being truncated when performing the conversion
|
||||
// https://github.com/paritytech/parity-ethereum/issues/8397
|
||||
for difficulty in 1..9 {
|
||||
assert_eq!(
|
||||
U256::from(difficulty),
|
||||
boundary_to_difficulty(&difficulty_to_boundary(&difficulty.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
H256::from(difficulty),
|
||||
difficulty_to_boundary(&boundary_to_difficulty(&difficulty.into()))
|
||||
);
|
||||
assert_eq!(
|
||||
U256::from(difficulty),
|
||||
boundary_to_difficulty(&boundary_to_difficulty(&difficulty.into()).into())
|
||||
);
|
||||
assert_eq!(
|
||||
H256::from(difficulty),
|
||||
difficulty_to_boundary(&difficulty_to_boundary(&difficulty.into()).into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_difficulty_to_boundary_panics_on_zero() {
|
||||
difficulty_to_boundary(&U256::from(0));
|
||||
difficulty_to_boundary(&U256::from(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_boundary_to_difficulty_panics_on_zero() {
|
||||
boundary_to_difficulty(ðereum_types::H256::from(0));
|
||||
boundary_to_difficulty(ðereum_types::H256::from(0));
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
// 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::{FNV_PRIME, calculate_dag_item};
|
||||
use compute::{calculate_dag_item, FNV_PRIME};
|
||||
use keccak::H256;
|
||||
use shared::{ETHASH_ACCESSES, ETHASH_MIX_BYTES, Node, get_data_size};
|
||||
use shared::{get_data_size, Node, ETHASH_ACCESSES, ETHASH_MIX_BYTES};
|
||||
|
||||
const PROGPOW_CACHE_BYTES: usize = 16 * 1024;
|
||||
const PROGPOW_CACHE_WORDS: usize = PROGPOW_CACHE_BYTES / 4;
|
||||
@@ -32,564 +32,551 @@ const PROGPOW_REGS: usize = 32;
|
||||
const FNV_HASH: u32 = 0x811c9dc5;
|
||||
|
||||
const KECCAKF_RNDC: [u32; 24] = [
|
||||
0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001,
|
||||
0x80008081, 0x00008009, 0x0000008a, 0x00000088, 0x80008009, 0x8000000a,
|
||||
0x8000808b, 0x0000008b, 0x00008089, 0x00008003, 0x00008002, 0x00000080,
|
||||
0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008
|
||||
0x00000001, 0x00008082, 0x0000808a, 0x80008000, 0x0000808b, 0x80000001, 0x80008081, 0x00008009,
|
||||
0x0000008a, 0x00000088, 0x80008009, 0x8000000a, 0x8000808b, 0x0000008b, 0x00008089, 0x00008003,
|
||||
0x00008002, 0x00000080, 0x0000800a, 0x8000000a, 0x80008081, 0x00008080, 0x80000001, 0x80008008,
|
||||
];
|
||||
|
||||
const KECCAKF_ROTC: [u32; 24] = [
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
|
||||
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44
|
||||
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
|
||||
];
|
||||
|
||||
const KECCAKF_PILN: [usize; 24] = [
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
|
||||
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1
|
||||
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
|
||||
];
|
||||
|
||||
fn keccak_f800_round(st: &mut [u32; 25], r: usize) {
|
||||
// Theta
|
||||
let mut bc = [0u32; 5];
|
||||
for i in 0..bc.len() {
|
||||
bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20];
|
||||
}
|
||||
// Theta
|
||||
let mut bc = [0u32; 5];
|
||||
for i in 0..bc.len() {
|
||||
bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20];
|
||||
}
|
||||
|
||||
for i in 0..bc.len() {
|
||||
let t = bc[(i + 4) % 5] ^ bc[(i + 1) % 5].rotate_left(1);
|
||||
for j in (0..st.len()).step_by(5) {
|
||||
st[j + i] ^= t;
|
||||
}
|
||||
}
|
||||
for i in 0..bc.len() {
|
||||
let t = bc[(i + 4) % 5] ^ bc[(i + 1) % 5].rotate_left(1);
|
||||
for j in (0..st.len()).step_by(5) {
|
||||
st[j + i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
// Rho Pi
|
||||
let mut t = st[1];
|
||||
// Rho Pi
|
||||
let mut t = st[1];
|
||||
|
||||
debug_assert_eq!(KECCAKF_ROTC.len(), 24);
|
||||
for i in 0..24 {
|
||||
let j = KECCAKF_PILN[i];
|
||||
bc[0] = st[j];
|
||||
st[j] = t.rotate_left(KECCAKF_ROTC[i]);
|
||||
t = bc[0];
|
||||
}
|
||||
debug_assert_eq!(KECCAKF_ROTC.len(), 24);
|
||||
for i in 0..24 {
|
||||
let j = KECCAKF_PILN[i];
|
||||
bc[0] = st[j];
|
||||
st[j] = t.rotate_left(KECCAKF_ROTC[i]);
|
||||
t = bc[0];
|
||||
}
|
||||
|
||||
// Chi
|
||||
for j in (0..st.len()).step_by(5) {
|
||||
for i in 0..bc.len() {
|
||||
bc[i] = st[j + i];
|
||||
}
|
||||
for i in 0..bc.len() {
|
||||
st[j + i] ^= (!bc[(i + 1) % 5]) & bc[(i + 2) % 5];
|
||||
}
|
||||
}
|
||||
// Chi
|
||||
for j in (0..st.len()).step_by(5) {
|
||||
for i in 0..bc.len() {
|
||||
bc[i] = st[j + i];
|
||||
}
|
||||
for i in 0..bc.len() {
|
||||
st[j + i] ^= (!bc[(i + 1) % 5]) & bc[(i + 2) % 5];
|
||||
}
|
||||
}
|
||||
|
||||
// Iota
|
||||
debug_assert!(r < KECCAKF_RNDC.len());
|
||||
st[0] ^= KECCAKF_RNDC[r];
|
||||
// Iota
|
||||
debug_assert!(r < KECCAKF_RNDC.len());
|
||||
st[0] ^= KECCAKF_RNDC[r];
|
||||
}
|
||||
|
||||
fn keccak_f800(header_hash: H256, nonce: u64, result: [u32; 8], st: &mut [u32; 25]) {
|
||||
for i in 0..8 {
|
||||
st[i] = (header_hash[4 * i] as u32) +
|
||||
((header_hash[4 * i + 1] as u32) << 8) +
|
||||
((header_hash[4 * i + 2] as u32) << 16) +
|
||||
((header_hash[4 * i + 3] as u32) << 24);
|
||||
}
|
||||
for i in 0..8 {
|
||||
st[i] = (header_hash[4 * i] as u32)
|
||||
+ ((header_hash[4 * i + 1] as u32) << 8)
|
||||
+ ((header_hash[4 * i + 2] as u32) << 16)
|
||||
+ ((header_hash[4 * i + 3] as u32) << 24);
|
||||
}
|
||||
|
||||
st[8] = nonce as u32;
|
||||
st[9] = (nonce >> 32) as u32;
|
||||
st[8] = nonce as u32;
|
||||
st[9] = (nonce >> 32) as u32;
|
||||
|
||||
for i in 0..8 {
|
||||
st[10 + i] = result[i];
|
||||
}
|
||||
for i in 0..8 {
|
||||
st[10 + i] = result[i];
|
||||
}
|
||||
|
||||
for r in 0..22 {
|
||||
keccak_f800_round(st, r);
|
||||
}
|
||||
for r in 0..22 {
|
||||
keccak_f800_round(st, r);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keccak_f800_short(header_hash: H256, nonce: u64, result: [u32; 8]) -> u64 {
|
||||
let mut st = [0u32; 25];
|
||||
keccak_f800(header_hash, nonce, result, &mut st);
|
||||
(st[0].swap_bytes() as u64) << 32 | st[1].swap_bytes() as u64
|
||||
let mut st = [0u32; 25];
|
||||
keccak_f800(header_hash, nonce, result, &mut st);
|
||||
(st[0].swap_bytes() as u64) << 32 | st[1].swap_bytes() as u64
|
||||
}
|
||||
|
||||
pub fn keccak_f800_long(header_hash: H256, nonce: u64, result: [u32; 8]) -> H256 {
|
||||
let mut st = [0u32; 25];
|
||||
keccak_f800(header_hash, nonce, result, &mut st);
|
||||
let mut st = [0u32; 25];
|
||||
keccak_f800(header_hash, nonce, result, &mut st);
|
||||
|
||||
// NOTE: transmute from `[u32; 8]` to `[u8; 32]`
|
||||
unsafe {
|
||||
std::mem::transmute(
|
||||
[st[0], st[1], st[2], st[3], st[4], st[5], st[6], st[7]]
|
||||
)
|
||||
}
|
||||
// NOTE: transmute from `[u32; 8]` to `[u8; 32]`
|
||||
unsafe { std::mem::transmute([st[0], st[1], st[2], st[3], st[4], st[5], st[6], st[7]]) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fnv1a_hash(h: u32, d: u32) -> u32 {
|
||||
(h ^ d).wrapping_mul(FNV_PRIME)
|
||||
(h ^ d).wrapping_mul(FNV_PRIME)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Kiss99 {
|
||||
z: u32,
|
||||
w: u32,
|
||||
jsr: u32,
|
||||
jcong: u32,
|
||||
z: u32,
|
||||
w: u32,
|
||||
jsr: u32,
|
||||
jcong: u32,
|
||||
}
|
||||
|
||||
impl Kiss99 {
|
||||
fn new(z: u32, w: u32, jsr: u32, jcong: u32) -> Kiss99 {
|
||||
Kiss99 { z, w, jsr, jcong }
|
||||
}
|
||||
fn new(z: u32, w: u32, jsr: u32, jcong: u32) -> Kiss99 {
|
||||
Kiss99 { z, w, jsr, jcong }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.z = 36969u32.wrapping_mul(self.z & 65535).wrapping_add(self.z >> 16);
|
||||
self.w = 18000u32.wrapping_mul(self.w & 65535).wrapping_add(self.w >> 16);
|
||||
let mwc = (self.z << 16).wrapping_add(self.w);
|
||||
self.jsr ^= self.jsr << 17;
|
||||
self.jsr ^= self.jsr >> 13;
|
||||
self.jsr ^= self.jsr << 5;
|
||||
self.jcong = 69069u32.wrapping_mul(self.jcong).wrapping_add(1234567);
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.z = 36969u32
|
||||
.wrapping_mul(self.z & 65535)
|
||||
.wrapping_add(self.z >> 16);
|
||||
self.w = 18000u32
|
||||
.wrapping_mul(self.w & 65535)
|
||||
.wrapping_add(self.w >> 16);
|
||||
let mwc = (self.z << 16).wrapping_add(self.w);
|
||||
self.jsr ^= self.jsr << 17;
|
||||
self.jsr ^= self.jsr >> 13;
|
||||
self.jsr ^= self.jsr << 5;
|
||||
self.jcong = 69069u32.wrapping_mul(self.jcong).wrapping_add(1234567);
|
||||
|
||||
(mwc ^ self.jcong).wrapping_add(self.jsr)
|
||||
}
|
||||
(mwc ^ self.jcong).wrapping_add(self.jsr)
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_mix(seed: u64, lane_id: u32) -> [u32; PROGPOW_REGS] {
|
||||
// Use FNV to expand the per-warp seed to per-lane
|
||||
// Use KISS to expand the per-lane seed to fill mix
|
||||
let z = fnv1a_hash(FNV_HASH, seed as u32);
|
||||
let w = fnv1a_hash(z, (seed >> 32) as u32);
|
||||
let jsr = fnv1a_hash(w, lane_id);
|
||||
let jcong = fnv1a_hash(jsr, lane_id);
|
||||
// Use FNV to expand the per-warp seed to per-lane
|
||||
// Use KISS to expand the per-lane seed to fill mix
|
||||
let z = fnv1a_hash(FNV_HASH, seed as u32);
|
||||
let w = fnv1a_hash(z, (seed >> 32) as u32);
|
||||
let jsr = fnv1a_hash(w, lane_id);
|
||||
let jcong = fnv1a_hash(jsr, lane_id);
|
||||
|
||||
let mut rnd = Kiss99::new(z, w, jsr, jcong);
|
||||
let mut rnd = Kiss99::new(z, w, jsr, jcong);
|
||||
|
||||
let mut mix = [0; PROGPOW_REGS];
|
||||
let mut mix = [0; PROGPOW_REGS];
|
||||
|
||||
debug_assert_eq!(PROGPOW_REGS, 32);
|
||||
for i in 0..32 {
|
||||
mix[i] = rnd.next_u32();
|
||||
}
|
||||
debug_assert_eq!(PROGPOW_REGS, 32);
|
||||
for i in 0..32 {
|
||||
mix[i] = rnd.next_u32();
|
||||
}
|
||||
|
||||
mix
|
||||
mix
|
||||
}
|
||||
|
||||
// Merge new data from b into the value in a. Assuming A has high entropy only
|
||||
// do ops that retain entropy even if B is low entropy (IE don't do A&B)
|
||||
fn merge(a: u32, b: u32, r: u32) -> u32 {
|
||||
match r % 4 {
|
||||
0 => a.wrapping_mul(33).wrapping_add(b),
|
||||
1 => (a ^ b).wrapping_mul(33),
|
||||
2 => a.rotate_left(((r >> 16) % 31) + 1) ^ b,
|
||||
_ => a.rotate_right(((r >> 16) % 31) + 1) ^ b,
|
||||
}
|
||||
match r % 4 {
|
||||
0 => a.wrapping_mul(33).wrapping_add(b),
|
||||
1 => (a ^ b).wrapping_mul(33),
|
||||
2 => a.rotate_left(((r >> 16) % 31) + 1) ^ b,
|
||||
_ => a.rotate_right(((r >> 16) % 31) + 1) ^ b,
|
||||
}
|
||||
}
|
||||
|
||||
fn math(a: u32, b: u32, r: u32) -> u32 {
|
||||
match r % 11 {
|
||||
0 => a.wrapping_add(b),
|
||||
1 => a.wrapping_mul(b),
|
||||
2 => ((a as u64).wrapping_mul(b as u64) >> 32) as u32,
|
||||
3 => a.min(b),
|
||||
4 => a.rotate_left(b),
|
||||
5 => a.rotate_right(b),
|
||||
6 => a & b,
|
||||
7 => a | b,
|
||||
8 => a ^ b,
|
||||
9 => a.leading_zeros() + b.leading_zeros(),
|
||||
_ => a.count_ones() + b.count_ones(),
|
||||
}
|
||||
match r % 11 {
|
||||
0 => a.wrapping_add(b),
|
||||
1 => a.wrapping_mul(b),
|
||||
2 => ((a as u64).wrapping_mul(b as u64) >> 32) as u32,
|
||||
3 => a.min(b),
|
||||
4 => a.rotate_left(b),
|
||||
5 => a.rotate_right(b),
|
||||
6 => a & b,
|
||||
7 => a | b,
|
||||
8 => a ^ b,
|
||||
9 => a.leading_zeros() + b.leading_zeros(),
|
||||
_ => a.count_ones() + b.count_ones(),
|
||||
}
|
||||
}
|
||||
|
||||
fn progpow_init(seed: u64) -> (Kiss99, [u32; PROGPOW_REGS], [u32; PROGPOW_REGS]) {
|
||||
let z = fnv1a_hash(FNV_HASH, seed as u32);
|
||||
let w = fnv1a_hash(z, (seed >> 32) as u32);
|
||||
let jsr = fnv1a_hash(w, seed as u32);
|
||||
let jcong = fnv1a_hash(jsr, (seed >> 32) as u32);
|
||||
let z = fnv1a_hash(FNV_HASH, seed as u32);
|
||||
let w = fnv1a_hash(z, (seed >> 32) as u32);
|
||||
let jsr = fnv1a_hash(w, seed as u32);
|
||||
let jcong = fnv1a_hash(jsr, (seed >> 32) as u32);
|
||||
|
||||
let mut rnd = Kiss99::new(z, w, jsr, jcong);
|
||||
let mut rnd = Kiss99::new(z, w, jsr, jcong);
|
||||
|
||||
// Create a random sequence of mix destinations for merge() and mix sources
|
||||
// for cache reads guarantees every destination merged once and guarantees
|
||||
// no duplicate cache reads, which could be optimized away. Uses
|
||||
// Fisher-Yates shuffle.
|
||||
let mut mix_seq_dst = [0u32; PROGPOW_REGS];
|
||||
let mut mix_seq_cache = [0u32; PROGPOW_REGS];
|
||||
for i in 0..mix_seq_dst.len() {
|
||||
mix_seq_dst[i] = i as u32;
|
||||
mix_seq_cache[i] = i as u32;
|
||||
}
|
||||
// Create a random sequence of mix destinations for merge() and mix sources
|
||||
// for cache reads guarantees every destination merged once and guarantees
|
||||
// no duplicate cache reads, which could be optimized away. Uses
|
||||
// Fisher-Yates shuffle.
|
||||
let mut mix_seq_dst = [0u32; PROGPOW_REGS];
|
||||
let mut mix_seq_cache = [0u32; PROGPOW_REGS];
|
||||
for i in 0..mix_seq_dst.len() {
|
||||
mix_seq_dst[i] = i as u32;
|
||||
mix_seq_cache[i] = i as u32;
|
||||
}
|
||||
|
||||
for i in (1..mix_seq_dst.len()).rev() {
|
||||
let j = rnd.next_u32() as usize % (i + 1);
|
||||
mix_seq_dst.swap(i, j);
|
||||
for i in (1..mix_seq_dst.len()).rev() {
|
||||
let j = rnd.next_u32() as usize % (i + 1);
|
||||
mix_seq_dst.swap(i, j);
|
||||
|
||||
let j = rnd.next_u32() as usize % (i + 1);
|
||||
mix_seq_cache.swap(i, j);
|
||||
}
|
||||
let j = rnd.next_u32() as usize % (i + 1);
|
||||
mix_seq_cache.swap(i, j);
|
||||
}
|
||||
|
||||
(rnd, mix_seq_dst, mix_seq_cache)
|
||||
(rnd, mix_seq_dst, mix_seq_cache)
|
||||
}
|
||||
|
||||
pub type CDag = [u32; PROGPOW_CACHE_WORDS];
|
||||
|
||||
fn progpow_loop(
|
||||
seed: u64,
|
||||
loop_: usize,
|
||||
mix: &mut [[u32; PROGPOW_REGS]; PROGPOW_LANES],
|
||||
cache: &[Node],
|
||||
c_dag: &CDag,
|
||||
data_size: usize,
|
||||
seed: u64,
|
||||
loop_: usize,
|
||||
mix: &mut [[u32; PROGPOW_REGS]; PROGPOW_LANES],
|
||||
cache: &[Node],
|
||||
c_dag: &CDag,
|
||||
data_size: usize,
|
||||
) {
|
||||
// All lanes share a base address for the global load. Global offset uses
|
||||
// mix[0] to guarantee it depends on the load result.
|
||||
let g_offset = mix[loop_ % PROGPOW_LANES][0] as usize %
|
||||
(64 * data_size / (PROGPOW_LANES * PROGPOW_DAG_LOADS));
|
||||
// All lanes share a base address for the global load. Global offset uses
|
||||
// mix[0] to guarantee it depends on the load result.
|
||||
let g_offset = mix[loop_ % PROGPOW_LANES][0] as usize
|
||||
% (64 * data_size / (PROGPOW_LANES * PROGPOW_DAG_LOADS));
|
||||
|
||||
// 256 bytes of dag data
|
||||
let mut dag_item = [0u32; 64];
|
||||
// 256 bytes of dag data
|
||||
let mut dag_item = [0u32; 64];
|
||||
|
||||
// Fetch DAG nodes (64 bytes each)
|
||||
for l in 0..PROGPOW_DAG_LOADS {
|
||||
let index = g_offset * PROGPOW_LANES * PROGPOW_DAG_LOADS + l * 16;
|
||||
let node = calculate_dag_item(index as u32 / 16, cache);
|
||||
dag_item[l * 16..(l + 1) * 16].clone_from_slice(node.as_words());
|
||||
}
|
||||
// Fetch DAG nodes (64 bytes each)
|
||||
for l in 0..PROGPOW_DAG_LOADS {
|
||||
let index = g_offset * PROGPOW_LANES * PROGPOW_DAG_LOADS + l * 16;
|
||||
let node = calculate_dag_item(index as u32 / 16, cache);
|
||||
dag_item[l * 16..(l + 1) * 16].clone_from_slice(node.as_words());
|
||||
}
|
||||
|
||||
let (rnd, mix_seq_dst, mix_seq_cache) = progpow_init(seed);
|
||||
let (rnd, mix_seq_dst, mix_seq_cache) = progpow_init(seed);
|
||||
|
||||
// Lanes can execute in parallel and will be convergent
|
||||
for l in 0..mix.len() {
|
||||
let mut rnd = rnd.clone();
|
||||
// Lanes can execute in parallel and will be convergent
|
||||
for l in 0..mix.len() {
|
||||
let mut rnd = rnd.clone();
|
||||
|
||||
// Initialize the seed and mix destination sequence
|
||||
let mut mix_seq_dst_cnt = 0;
|
||||
let mut mix_seq_cache_cnt = 0;
|
||||
// Initialize the seed and mix destination sequence
|
||||
let mut mix_seq_dst_cnt = 0;
|
||||
let mut mix_seq_cache_cnt = 0;
|
||||
|
||||
let mut mix_dst = || {
|
||||
let res = mix_seq_dst[mix_seq_dst_cnt % PROGPOW_REGS] as usize;
|
||||
mix_seq_dst_cnt += 1;
|
||||
res
|
||||
};
|
||||
let mut mix_cache = || {
|
||||
let res = mix_seq_cache[mix_seq_cache_cnt % PROGPOW_REGS] as usize;
|
||||
mix_seq_cache_cnt += 1;
|
||||
res
|
||||
};
|
||||
let mut mix_dst = || {
|
||||
let res = mix_seq_dst[mix_seq_dst_cnt % PROGPOW_REGS] as usize;
|
||||
mix_seq_dst_cnt += 1;
|
||||
res
|
||||
};
|
||||
let mut mix_cache = || {
|
||||
let res = mix_seq_cache[mix_seq_cache_cnt % PROGPOW_REGS] as usize;
|
||||
mix_seq_cache_cnt += 1;
|
||||
res
|
||||
};
|
||||
|
||||
for i in 0..PROGPOW_CNT_CACHE.max(PROGPOW_CNT_MATH) {
|
||||
if i < PROGPOW_CNT_CACHE {
|
||||
// Cached memory access, lanes access random 32-bit locations
|
||||
// within the first portion of the DAG
|
||||
let offset = mix[l][mix_cache()] as usize % PROGPOW_CACHE_WORDS;
|
||||
let data = c_dag[offset];
|
||||
let dst = mix_dst();
|
||||
for i in 0..PROGPOW_CNT_CACHE.max(PROGPOW_CNT_MATH) {
|
||||
if i < PROGPOW_CNT_CACHE {
|
||||
// Cached memory access, lanes access random 32-bit locations
|
||||
// within the first portion of the DAG
|
||||
let offset = mix[l][mix_cache()] as usize % PROGPOW_CACHE_WORDS;
|
||||
let data = c_dag[offset];
|
||||
let dst = mix_dst();
|
||||
|
||||
mix[l][dst] = merge(mix[l][dst], data, rnd.next_u32());
|
||||
}
|
||||
mix[l][dst] = merge(mix[l][dst], data, rnd.next_u32());
|
||||
}
|
||||
|
||||
if i < PROGPOW_CNT_MATH {
|
||||
// Random math
|
||||
// Generate 2 unique sources
|
||||
let src_rnd = rnd.next_u32() % (PROGPOW_REGS * (PROGPOW_REGS - 1)) as u32;
|
||||
let src1 = src_rnd % PROGPOW_REGS as u32; // 0 <= src1 < PROGPOW_REGS
|
||||
let mut src2 = src_rnd / PROGPOW_REGS as u32; // 0 <= src2 < PROGPOW_REGS - 1
|
||||
if src2 >= src1 {
|
||||
src2 += 1; // src2 is now any reg other than src1
|
||||
}
|
||||
if i < PROGPOW_CNT_MATH {
|
||||
// Random math
|
||||
// Generate 2 unique sources
|
||||
let src_rnd = rnd.next_u32() % (PROGPOW_REGS * (PROGPOW_REGS - 1)) as u32;
|
||||
let src1 = src_rnd % PROGPOW_REGS as u32; // 0 <= src1 < PROGPOW_REGS
|
||||
let mut src2 = src_rnd / PROGPOW_REGS as u32; // 0 <= src2 < PROGPOW_REGS - 1
|
||||
if src2 >= src1 {
|
||||
src2 += 1; // src2 is now any reg other than src1
|
||||
}
|
||||
|
||||
let data = math(mix[l][src1 as usize], mix[l][src2 as usize], rnd.next_u32());
|
||||
let dst = mix_dst();
|
||||
let data = math(mix[l][src1 as usize], mix[l][src2 as usize], rnd.next_u32());
|
||||
let dst = mix_dst();
|
||||
|
||||
mix[l][dst] = merge(mix[l][dst], data, rnd.next_u32());
|
||||
}
|
||||
}
|
||||
mix[l][dst] = merge(mix[l][dst], data, rnd.next_u32());
|
||||
}
|
||||
}
|
||||
|
||||
// Global load to sequential locations
|
||||
let mut data_g = [0u32; PROGPOW_DAG_LOADS];
|
||||
let index = ((l ^ loop_) % PROGPOW_LANES) * PROGPOW_DAG_LOADS;
|
||||
for i in 0..PROGPOW_DAG_LOADS {
|
||||
data_g[i] = dag_item[index + i];
|
||||
}
|
||||
// Global load to sequential locations
|
||||
let mut data_g = [0u32; PROGPOW_DAG_LOADS];
|
||||
let index = ((l ^ loop_) % PROGPOW_LANES) * PROGPOW_DAG_LOADS;
|
||||
for i in 0..PROGPOW_DAG_LOADS {
|
||||
data_g[i] = dag_item[index + i];
|
||||
}
|
||||
|
||||
// Consume the global load data at the very end of the loop to allow
|
||||
// full latency hiding. Always merge into `mix[0]` to feed the offset
|
||||
// calculation.
|
||||
mix[l][0] = merge(mix[l][0], data_g[0], rnd.next_u32());
|
||||
for i in 1..PROGPOW_DAG_LOADS {
|
||||
let dst = mix_dst();
|
||||
mix[l][dst] = merge(mix[l][dst], data_g[i], rnd.next_u32());
|
||||
}
|
||||
}
|
||||
// Consume the global load data at the very end of the loop to allow
|
||||
// full latency hiding. Always merge into `mix[0]` to feed the offset
|
||||
// calculation.
|
||||
mix[l][0] = merge(mix[l][0], data_g[0], rnd.next_u32());
|
||||
for i in 1..PROGPOW_DAG_LOADS {
|
||||
let dst = mix_dst();
|
||||
mix[l][dst] = merge(mix[l][dst], data_g[i], rnd.next_u32());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn progpow(
|
||||
header_hash: H256,
|
||||
nonce: u64,
|
||||
block_number: u64,
|
||||
cache: &[Node],
|
||||
c_dag: &CDag,
|
||||
header_hash: H256,
|
||||
nonce: u64,
|
||||
block_number: u64,
|
||||
cache: &[Node],
|
||||
c_dag: &CDag,
|
||||
) -> (H256, H256) {
|
||||
let mut mix = [[0u32; PROGPOW_REGS]; PROGPOW_LANES];
|
||||
let mut lane_results = [0u32; PROGPOW_LANES];
|
||||
let mut result = [0u32; 8];
|
||||
let mut mix = [[0u32; PROGPOW_REGS]; PROGPOW_LANES];
|
||||
let mut lane_results = [0u32; PROGPOW_LANES];
|
||||
let mut result = [0u32; 8];
|
||||
|
||||
let data_size = get_data_size(block_number) / PROGPOW_MIX_BYTES;
|
||||
let data_size = get_data_size(block_number) / PROGPOW_MIX_BYTES;
|
||||
|
||||
// NOTE: This assert is required to aid the optimizer elide the non-zero
|
||||
// remainder check in `progpow_loop`.
|
||||
assert!(data_size > 0);
|
||||
// NOTE: This assert is required to aid the optimizer elide the non-zero
|
||||
// remainder check in `progpow_loop`.
|
||||
assert!(data_size > 0);
|
||||
|
||||
// Initialize mix for all lanes
|
||||
let seed = keccak_f800_short(header_hash, nonce, result);
|
||||
// Initialize mix for all lanes
|
||||
let seed = keccak_f800_short(header_hash, nonce, result);
|
||||
|
||||
for l in 0..mix.len() {
|
||||
mix[l] = fill_mix(seed, l as u32);
|
||||
}
|
||||
for l in 0..mix.len() {
|
||||
mix[l] = fill_mix(seed, l as u32);
|
||||
}
|
||||
|
||||
// Execute the randomly generated inner loop
|
||||
let period = block_number / PROGPOW_PERIOD_LENGTH as u64;
|
||||
for i in 0..PROGPOW_CNT_DAG {
|
||||
progpow_loop(
|
||||
period,
|
||||
i,
|
||||
&mut mix,
|
||||
cache,
|
||||
c_dag,
|
||||
data_size,
|
||||
);
|
||||
}
|
||||
// Execute the randomly generated inner loop
|
||||
let period = block_number / PROGPOW_PERIOD_LENGTH as u64;
|
||||
for i in 0..PROGPOW_CNT_DAG {
|
||||
progpow_loop(period, i, &mut mix, cache, c_dag, data_size);
|
||||
}
|
||||
|
||||
// Reduce mix data to a single per-lane result
|
||||
for l in 0..lane_results.len() {
|
||||
lane_results[l] = FNV_HASH;
|
||||
for i in 0..PROGPOW_REGS {
|
||||
lane_results[l] = fnv1a_hash(lane_results[l], mix[l][i]);
|
||||
}
|
||||
}
|
||||
// Reduce mix data to a single per-lane result
|
||||
for l in 0..lane_results.len() {
|
||||
lane_results[l] = FNV_HASH;
|
||||
for i in 0..PROGPOW_REGS {
|
||||
lane_results[l] = fnv1a_hash(lane_results[l], mix[l][i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce all lanes to a single 128-bit result
|
||||
result = [FNV_HASH; 8];
|
||||
for l in 0..PROGPOW_LANES {
|
||||
result[l % 8] = fnv1a_hash(result[l % 8], lane_results[l]);
|
||||
}
|
||||
// Reduce all lanes to a single 128-bit result
|
||||
result = [FNV_HASH; 8];
|
||||
for l in 0..PROGPOW_LANES {
|
||||
result[l % 8] = fnv1a_hash(result[l % 8], lane_results[l]);
|
||||
}
|
||||
|
||||
let digest = keccak_f800_long(header_hash, seed, result);
|
||||
let digest = keccak_f800_long(header_hash, seed, result);
|
||||
|
||||
// NOTE: transmute from `[u32; 8]` to `[u8; 32]`
|
||||
let result = unsafe { ::std::mem::transmute(result) };
|
||||
// NOTE: transmute from `[u32; 8]` to `[u8; 32]`
|
||||
let result = unsafe { ::std::mem::transmute(result) };
|
||||
|
||||
(digest, result)
|
||||
(digest, result)
|
||||
}
|
||||
|
||||
pub fn generate_cdag(cache: &[Node]) -> CDag {
|
||||
let mut c_dag = [0u32; PROGPOW_CACHE_WORDS];
|
||||
let mut c_dag = [0u32; PROGPOW_CACHE_WORDS];
|
||||
|
||||
for i in 0..PROGPOW_CACHE_WORDS / 16 {
|
||||
let node = calculate_dag_item(i as u32, cache);
|
||||
for j in 0..16 {
|
||||
c_dag[i * 16 + j] = node.as_words()[j];
|
||||
}
|
||||
}
|
||||
for i in 0..PROGPOW_CACHE_WORDS / 16 {
|
||||
let node = calculate_dag_item(i as u32, cache);
|
||||
for j in 0..16 {
|
||||
c_dag[i * 16 + j] = node.as_words()[j];
|
||||
}
|
||||
}
|
||||
|
||||
c_dag
|
||||
c_dag
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use tempdir::TempDir;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
use keccak::H256;
|
||||
use rustc_hex::FromHex;
|
||||
use serde_json::{self, Value};
|
||||
use std::collections::VecDeque;
|
||||
use super::*;
|
||||
use super::*;
|
||||
use cache::{NodeCacheBuilder, OptimizeFor};
|
||||
use keccak::H256;
|
||||
use rustc_hex::FromHex;
|
||||
use serde_json::{self, Value};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
fn h256(hex: &str) -> H256 {
|
||||
let bytes = FromHex::from_hex(hex).unwrap();
|
||||
let mut res = [0; 32];
|
||||
res.copy_from_slice(&bytes);
|
||||
res
|
||||
}
|
||||
fn h256(hex: &str) -> H256 {
|
||||
let bytes = FromHex::from_hex(hex).unwrap();
|
||||
let mut res = [0; 32];
|
||||
res.copy_from_slice(&bytes);
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cdag() {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
#[test]
|
||||
fn test_cdag() {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
|
||||
let expected = vec![
|
||||
690150178u32, 1181503948, 2248155602, 2118233073, 2193871115,
|
||||
1791778428, 1067701239, 724807309, 530799275, 3480325829, 3899029234,
|
||||
1998124059, 2541974622, 1100859971, 1297211151, 3268320000, 2217813733,
|
||||
2690422980, 3172863319, 2651064309
|
||||
];
|
||||
let expected = vec![
|
||||
690150178u32,
|
||||
1181503948,
|
||||
2248155602,
|
||||
2118233073,
|
||||
2193871115,
|
||||
1791778428,
|
||||
1067701239,
|
||||
724807309,
|
||||
530799275,
|
||||
3480325829,
|
||||
3899029234,
|
||||
1998124059,
|
||||
2541974622,
|
||||
1100859971,
|
||||
1297211151,
|
||||
3268320000,
|
||||
2217813733,
|
||||
2690422980,
|
||||
3172863319,
|
||||
2651064309,
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
c_dag.iter().take(20).cloned().collect::<Vec<_>>(),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
assert_eq!(c_dag.iter().take(20).cloned().collect::<Vec<_>>(), expected,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_merge() {
|
||||
let tests = [
|
||||
(1000000u32, 101u32, 33000101u32),
|
||||
(2000000, 102, 66003366),
|
||||
(3000000, 103, 6000103),
|
||||
(4000000, 104, 2000104),
|
||||
(1000000, 0, 33000000),
|
||||
(2000000, 0, 66000000),
|
||||
(3000000, 0, 6000000),
|
||||
(4000000, 0, 2000000),
|
||||
];
|
||||
#[test]
|
||||
fn test_random_merge() {
|
||||
let tests = [
|
||||
(1000000u32, 101u32, 33000101u32),
|
||||
(2000000, 102, 66003366),
|
||||
(3000000, 103, 6000103),
|
||||
(4000000, 104, 2000104),
|
||||
(1000000, 0, 33000000),
|
||||
(2000000, 0, 66000000),
|
||||
(3000000, 0, 6000000),
|
||||
(4000000, 0, 2000000),
|
||||
];
|
||||
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(
|
||||
merge(a, b, i as u32),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(merge(a, b, i as u32), expected,);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_math() {
|
||||
let tests = [
|
||||
(20u32, 22u32, 42u32),
|
||||
(70000, 80000, 1305032704),
|
||||
(70000, 80000, 1),
|
||||
(1, 2, 1),
|
||||
(3, 10000, 196608),
|
||||
(3, 0, 3),
|
||||
(3, 6, 2),
|
||||
(3, 6, 7),
|
||||
(3, 6, 5),
|
||||
(0, 0xffffffff, 32),
|
||||
(3 << 13, 1 << 5, 3),
|
||||
(22, 20, 42),
|
||||
(80000, 70000, 1305032704),
|
||||
(80000, 70000, 1),
|
||||
(2, 1, 1),
|
||||
(10000, 3, 80000),
|
||||
(0, 3, 0),
|
||||
(6, 3, 2),
|
||||
(6, 3, 7),
|
||||
(6, 3, 5),
|
||||
(0, 0xffffffff, 32),
|
||||
(3 << 13, 1 << 5, 3),
|
||||
];
|
||||
#[test]
|
||||
fn test_random_math() {
|
||||
let tests = [
|
||||
(20u32, 22u32, 42u32),
|
||||
(70000, 80000, 1305032704),
|
||||
(70000, 80000, 1),
|
||||
(1, 2, 1),
|
||||
(3, 10000, 196608),
|
||||
(3, 0, 3),
|
||||
(3, 6, 2),
|
||||
(3, 6, 7),
|
||||
(3, 6, 5),
|
||||
(0, 0xffffffff, 32),
|
||||
(3 << 13, 1 << 5, 3),
|
||||
(22, 20, 42),
|
||||
(80000, 70000, 1305032704),
|
||||
(80000, 70000, 1),
|
||||
(2, 1, 1),
|
||||
(10000, 3, 80000),
|
||||
(0, 3, 0),
|
||||
(6, 3, 2),
|
||||
(6, 3, 7),
|
||||
(6, 3, 5),
|
||||
(0, 0xffffffff, 32),
|
||||
(3 << 13, 1 << 5, 3),
|
||||
];
|
||||
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(
|
||||
math(a, b, i as u32),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (i, &(a, b, expected)) in tests.iter().enumerate() {
|
||||
assert_eq!(math(a, b, i as u32), expected,);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keccak_256() {
|
||||
let expected = "5dd431e5fbc604f499bfa0232f45f8f142d0ff5178f539e5a7800bf0643697af";
|
||||
assert_eq!(
|
||||
keccak_f800_long([0; 32], 0, [0; 8]),
|
||||
h256(expected),
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_keccak_256() {
|
||||
let expected = "5dd431e5fbc604f499bfa0232f45f8f142d0ff5178f539e5a7800bf0643697af";
|
||||
assert_eq!(keccak_f800_long([0; 32], 0, [0; 8]), h256(expected),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keccak_64() {
|
||||
let expected: u64 = 0x5dd431e5fbc604f4;
|
||||
assert_eq!(
|
||||
keccak_f800_short([0; 32], 0, [0; 8]),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_keccak_64() {
|
||||
let expected: u64 = 0x5dd431e5fbc604f4;
|
||||
assert_eq!(keccak_f800_short([0; 32], 0, [0; 8]), expected,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_progpow_hash() {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
#[test]
|
||||
fn test_progpow_hash() {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.into_path(), 0);
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
|
||||
let header_hash = [0; 32];
|
||||
let header_hash = [0; 32];
|
||||
|
||||
let (digest, result) = progpow(
|
||||
header_hash,
|
||||
0,
|
||||
0,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
let (digest, result) = progpow(header_hash, 0, 0, cache.as_ref(), &c_dag);
|
||||
|
||||
let expected_digest = FromHex::from_hex("63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12b").unwrap();
|
||||
let expected_result = FromHex::from_hex("faeb1be51075b03a4ff44b335067951ead07a3b078539ace76fd56fc410557a3").unwrap();
|
||||
let expected_digest =
|
||||
FromHex::from_hex("63155f732f2bf556967f906155b510c917e48e99685ead76ea83f4eca03ab12b")
|
||||
.unwrap();
|
||||
let expected_result =
|
||||
FromHex::from_hex("faeb1be51075b03a4ff44b335067951ead07a3b078539ace76fd56fc410557a3")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
digest.to_vec(),
|
||||
expected_digest,
|
||||
);
|
||||
assert_eq!(digest.to_vec(), expected_digest,);
|
||||
|
||||
assert_eq!(
|
||||
result.to_vec(),
|
||||
expected_result,
|
||||
);
|
||||
}
|
||||
assert_eq!(result.to_vec(), expected_result,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_progpow_testvectors() {
|
||||
struct ProgpowTest {
|
||||
block_number: u64,
|
||||
header_hash: H256,
|
||||
nonce: u64,
|
||||
mix_hash: H256,
|
||||
final_hash: H256,
|
||||
}
|
||||
#[test]
|
||||
fn test_progpow_testvectors() {
|
||||
struct ProgpowTest {
|
||||
block_number: u64,
|
||||
header_hash: H256,
|
||||
nonce: u64,
|
||||
mix_hash: H256,
|
||||
final_hash: H256,
|
||||
}
|
||||
|
||||
let tests: Vec<VecDeque<Value>> =
|
||||
serde_json::from_slice(include_bytes!("../res/progpow_testvectors.json")).unwrap();
|
||||
let tests: Vec<VecDeque<Value>> =
|
||||
serde_json::from_slice(include_bytes!("../res/progpow_testvectors.json")).unwrap();
|
||||
|
||||
let tests: Vec<ProgpowTest> = tests.into_iter().map(|mut test: VecDeque<Value>| {
|
||||
assert!(test.len() == 5);
|
||||
let tests: Vec<ProgpowTest> = tests
|
||||
.into_iter()
|
||||
.map(|mut test: VecDeque<Value>| {
|
||||
assert!(test.len() == 5);
|
||||
|
||||
let block_number: u64 = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let header_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let nonce: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let mix_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let final_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let block_number: u64 = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let header_hash: String =
|
||||
serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let nonce: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let mix_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
let final_hash: String = serde_json::from_value(test.pop_front().unwrap()).unwrap();
|
||||
|
||||
ProgpowTest {
|
||||
block_number,
|
||||
header_hash: h256(&header_hash),
|
||||
nonce: u64::from_str_radix(&nonce, 16).unwrap(),
|
||||
mix_hash: h256(&mix_hash),
|
||||
final_hash: h256(&final_hash),
|
||||
}
|
||||
}).collect();
|
||||
ProgpowTest {
|
||||
block_number,
|
||||
header_hash: h256(&header_hash),
|
||||
nonce: u64::from_str_radix(&nonce, 16).unwrap(),
|
||||
mix_hash: h256(&mix_hash),
|
||||
final_hash: h256(&final_hash),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for test in tests {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.path().to_owned(), test.block_number);
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
for test in tests {
|
||||
let builder = NodeCacheBuilder::new(OptimizeFor::Memory, u64::max_value());
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let cache = builder.new_cache(tempdir.path().to_owned(), test.block_number);
|
||||
let c_dag = generate_cdag(cache.as_ref());
|
||||
|
||||
let (digest, result) = progpow(
|
||||
test.header_hash,
|
||||
test.nonce,
|
||||
test.block_number,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
let (digest, result) = progpow(
|
||||
test.header_hash,
|
||||
test.nonce,
|
||||
test.block_number,
|
||||
cache.as_ref(),
|
||||
&c_dag,
|
||||
);
|
||||
|
||||
assert_eq!(digest, test.final_hash);
|
||||
assert_eq!(result, test.mix_hash);
|
||||
}
|
||||
}
|
||||
assert_eq!(digest, test.final_hash);
|
||||
assert_eq!(result, test.mix_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,89 +14,97 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use shared;
|
||||
use keccak::{keccak_256, H256};
|
||||
use shared;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SeedHashCompute {
|
||||
prev_epoch: Cell<u64>,
|
||||
prev_seedhash: Cell<H256>,
|
||||
prev_epoch: Cell<u64>,
|
||||
prev_seedhash: Cell<H256>,
|
||||
}
|
||||
|
||||
impl SeedHashCompute {
|
||||
#[inline]
|
||||
fn reset_cache(&self) {
|
||||
self.prev_epoch.set(0);
|
||||
self.prev_seedhash.set([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_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 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
|
||||
}
|
||||
#[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;
|
||||
use super::SeedHashCompute;
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_once() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
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_once() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
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::default();
|
||||
assert_eq!(seed_compute.hash_block_number(0), [0u8; 32]);
|
||||
}
|
||||
#[test]
|
||||
fn test_seed_compute_zero() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
assert_eq!(seed_compute.hash_block_number(0), [0u8; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seed_compute_after_older() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
// 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::default();
|
||||
// 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);
|
||||
}
|
||||
#[test]
|
||||
fn test_seed_compute_after_older() {
|
||||
let seed_compute = SeedHashCompute::default();
|
||||
// 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::default();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,38 +31,39 @@ 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
|
||||
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]);
|
||||
}
|
||||
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) }
|
||||
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
|
||||
// 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
|
||||
// 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];
|
||||
@@ -100,15 +101,19 @@ static_assert_size_eq!(Node, NodeBytes, NodeWords, NodeDwords);
|
||||
|
||||
#[repr(C)]
|
||||
pub union Node {
|
||||
pub dwords: NodeDwords,
|
||||
pub words: NodeWords,
|
||||
pub bytes: NodeBytes,
|
||||
pub dwords: NodeDwords,
|
||||
pub words: NodeWords,
|
||||
pub bytes: NodeBytes,
|
||||
}
|
||||
|
||||
impl Clone for Node {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { Node { bytes: *&self.bytes } }
|
||||
}
|
||||
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
|
||||
@@ -117,33 +122,33 @@ impl Clone for Node {
|
||||
// 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(&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_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(&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_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(&self) -> &NodeDwords {
|
||||
unsafe { &self.dwords }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_dwords_mut(&mut self) -> &mut NodeDwords {
|
||||
unsafe { &mut self.dwords }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn as_dwords_mut(&mut self) -> &mut NodeDwords {
|
||||
unsafe { &mut self.dwords }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user