Gav Wood
10 years ago
71 changed files with 6883 additions and 1499 deletions
@ -0,0 +1,47 @@ |
|||
cmake_minimum_required(VERSION 2.8) |
|||
|
|||
set(LIBRARY ethash-cl) |
|||
#set(CMAKE_BUILD_TYPE Release) |
|||
|
|||
include(bin2h.cmake) |
|||
bin2h(SOURCE_FILE ethash_cl_miner_kernel.cl |
|||
VARIABLE_NAME ethash_cl_miner_kernel |
|||
HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/ethash_cl_miner_kernel.h) |
|||
|
|||
if (NOT MSVC) |
|||
# Initialize CXXFLAGS for c++11 |
|||
set(CMAKE_CXX_FLAGS "-Wall -std=c++11") |
|||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") |
|||
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") |
|||
set(CMAKE_CXX_FLAGS_RELEASE "-O4 -DNDEBUG") |
|||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") |
|||
|
|||
# Compiler-specific C++11 activation. |
|||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") |
|||
execute_process( |
|||
COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) |
|||
if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) |
|||
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.7 or greater.") |
|||
endif () |
|||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") |
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") |
|||
else () |
|||
message(FATAL_ERROR "Your C++ compiler does not support C++11.") |
|||
endif () |
|||
endif() |
|||
|
|||
set(OpenCL_FOUND TRUE) |
|||
set(OpenCL_INCLUDE_DIRS /usr/include/CL) |
|||
set(OpenCL_LIBRARIES -lOpenCL) |
|||
|
|||
if (NOT OpenCL_FOUND) |
|||
find_package(OpenCL) |
|||
endif() |
|||
|
|||
if (OpenCL_FOUND) |
|||
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wno-unknown-pragmas -Wextra -Werror -pedantic -fPIC ${CMAKE_CXX_FLAGS}") |
|||
include_directories(${OpenCL_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) |
|||
include_directories(..) |
|||
add_library(${LIBRARY} ethash_cl_miner.cpp ethash_cl_miner.h cl.hpp) |
|||
TARGET_LINK_LIBRARIES(${LIBRARY} ${OpenCL_LIBRARIES} ethash) |
|||
endif() |
@ -0,0 +1,86 @@ |
|||
# https://gist.github.com/sivachandran/3a0de157dccef822a230 |
|||
include(CMakeParseArguments) |
|||
|
|||
# Function to wrap a given string into multiple lines at the given column position. |
|||
# Parameters: |
|||
# VARIABLE - The name of the CMake variable holding the string. |
|||
# AT_COLUMN - The column position at which string will be wrapped. |
|||
function(WRAP_STRING) |
|||
set(oneValueArgs VARIABLE AT_COLUMN) |
|||
cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN}) |
|||
|
|||
string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength) |
|||
math(EXPR offset "0") |
|||
|
|||
while(stringLength GREATER 0) |
|||
|
|||
if(stringLength GREATER ${WRAP_STRING_AT_COLUMN}) |
|||
math(EXPR length "${WRAP_STRING_AT_COLUMN}") |
|||
else() |
|||
math(EXPR length "${stringLength}") |
|||
endif() |
|||
|
|||
string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line) |
|||
set(lines "${lines}\n${line}") |
|||
|
|||
math(EXPR stringLength "${stringLength} - ${length}") |
|||
math(EXPR offset "${offset} + ${length}") |
|||
endwhile() |
|||
|
|||
set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE) |
|||
endfunction() |
|||
|
|||
# Function to embed contents of a file as byte array in C/C++ header file(.h). The header file |
|||
# will contain a byte array and integer variable holding the size of the array. |
|||
# Parameters |
|||
# SOURCE_FILE - The path of source file whose contents will be embedded in the header file. |
|||
# VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append |
|||
# to this name and will be used a variable name for size variable. |
|||
# HEADER_FILE - The path of header file. |
|||
# APPEND - If specified appends to the header file instead of overwriting it |
|||
# NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be |
|||
# useful if the source file is a text file and we want to use the file contents |
|||
# as string. But the size variable holds size of the byte array without this |
|||
# null byte. |
|||
# Usage: |
|||
# bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG") |
|||
function(BIN2H) |
|||
set(options APPEND NULL_TERMINATE) |
|||
set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE) |
|||
cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN}) |
|||
|
|||
# reads source file contents as hex string |
|||
file(READ ${BIN2H_SOURCE_FILE} hexString HEX) |
|||
string(LENGTH ${hexString} hexStringLength) |
|||
|
|||
# appends null byte if asked |
|||
if(BIN2H_NULL_TERMINATE) |
|||
set(hexString "${hexString}00") |
|||
endif() |
|||
|
|||
# wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line) |
|||
wrap_string(VARIABLE hexString AT_COLUMN 32) |
|||
math(EXPR arraySize "${hexStringLength} / 2") |
|||
|
|||
# adds '0x' prefix and comma suffix before and after every byte respectively |
|||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString}) |
|||
# removes trailing comma |
|||
string(REGEX REPLACE ", $" "" arrayValues ${arrayValues}) |
|||
|
|||
# converts the variable name into proper C identifier |
|||
IF (${CMAKE_VERSION} GREATER 2.8.10) # fix for legacy cmake |
|||
string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) |
|||
ENDIF() |
|||
string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) |
|||
|
|||
# declares byte array and the length variables |
|||
set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };") |
|||
set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};") |
|||
|
|||
set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n") |
|||
if(BIN2H_APPEND) |
|||
file(APPEND ${BIN2H_HEADER_FILE} "${declarations}") |
|||
else() |
|||
file(WRITE ${BIN2H_HEADER_FILE} "${declarations}") |
|||
endif() |
|||
endfunction() |
File diff suppressed because it is too large
@ -0,0 +1,334 @@ |
|||
/*
|
|||
This file is part of c-ethash. |
|||
|
|||
c-ethash is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
c-ethash is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ethash_cl_miner.cpp
|
|||
* @author Tim Hughes <tim@twistedfury.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#define _CRT_SECURE_NO_WARNINGS |
|||
|
|||
#include <cstdio> |
|||
#include <cstdlib> |
|||
#include <assert.h> |
|||
#include <queue> |
|||
#include <vector> |
|||
#include <libethash/util.h> |
|||
#include <libethash/ethash.h> |
|||
#include "ethash_cl_miner.h" |
|||
#include "ethash_cl_miner_kernel.h" |
|||
|
|||
#define ETHASH_BYTES 32 |
|||
|
|||
// workaround lame platforms
|
|||
#if !CL_VERSION_1_2 |
|||
#define CL_MAP_WRITE_INVALIDATE_REGION CL_MAP_WRITE |
|||
#define CL_MEM_HOST_READ_ONLY 0 |
|||
#endif |
|||
|
|||
#undef min |
|||
#undef max |
|||
|
|||
static void add_definition(std::string& source, char const* id, unsigned value) |
|||
{ |
|||
char buf[256]; |
|||
sprintf(buf, "#define %s %uu\n", id, value); |
|||
source.insert(source.begin(), buf, buf + strlen(buf)); |
|||
} |
|||
|
|||
ethash_cl_miner::search_hook::~search_hook() {} |
|||
|
|||
ethash_cl_miner::ethash_cl_miner() |
|||
: m_opencl_1_1() |
|||
{ |
|||
} |
|||
|
|||
void ethash_cl_miner::finish() |
|||
{ |
|||
if (m_queue()) |
|||
{ |
|||
m_queue.finish(); |
|||
} |
|||
} |
|||
|
|||
bool ethash_cl_miner::init(ethash_params const& params, std::function<void(void*)> _fillDAG, unsigned workgroup_size) |
|||
{ |
|||
// store params
|
|||
m_params = params; |
|||
|
|||
// get all platforms
|
|||
std::vector<cl::Platform> platforms; |
|||
cl::Platform::get(&platforms); |
|||
if (platforms.empty()) |
|||
{ |
|||
debugf("No OpenCL platforms found.\n"); |
|||
return false; |
|||
} |
|||
|
|||
// use default platform
|
|||
fprintf(stderr, "Using platform: %s\n", platforms[0].getInfo<CL_PLATFORM_NAME>().c_str()); |
|||
|
|||
// get GPU device of the default platform
|
|||
std::vector<cl::Device> devices; |
|||
platforms[0].getDevices(CL_DEVICE_TYPE_ALL, &devices); |
|||
if (devices.empty()) |
|||
{ |
|||
debugf("No OpenCL devices found.\n"); |
|||
return false; |
|||
} |
|||
|
|||
// use default device
|
|||
unsigned device_num = 0; |
|||
cl::Device& device = devices[device_num]; |
|||
std::string device_version = device.getInfo<CL_DEVICE_VERSION>(); |
|||
fprintf(stderr, "Using device: %s (%s)\n", device.getInfo<CL_DEVICE_NAME>().c_str(),device_version.c_str()); |
|||
|
|||
if (strncmp("OpenCL 1.0", device_version.c_str(), 10) == 0) |
|||
{ |
|||
debugf("OpenCL 1.0 is not supported.\n"); |
|||
return false; |
|||
} |
|||
if (strncmp("OpenCL 1.1", device_version.c_str(), 10) == 0) |
|||
{ |
|||
m_opencl_1_1 = true; |
|||
} |
|||
|
|||
// create context
|
|||
m_context = cl::Context(std::vector<cl::Device>(&device, &device + 1)); |
|||
m_queue = cl::CommandQueue(m_context, device); |
|||
|
|||
// use requested workgroup size, but we require multiple of 8
|
|||
m_workgroup_size = ((workgroup_size + 7) / 8) * 8; |
|||
|
|||
// patch source code
|
|||
std::string code(ETHASH_CL_MINER_KERNEL, ETHASH_CL_MINER_KERNEL + ETHASH_CL_MINER_KERNEL_SIZE); |
|||
add_definition(code, "GROUP_SIZE", m_workgroup_size); |
|||
add_definition(code, "DAG_SIZE", (unsigned)(params.full_size / ETHASH_MIX_BYTES)); |
|||
add_definition(code, "ACCESSES", ETHASH_ACCESSES); |
|||
add_definition(code, "MAX_OUTPUTS", c_max_search_results); |
|||
//debugf("%s", code.c_str());
|
|||
|
|||
// create miner OpenCL program
|
|||
cl::Program::Sources sources; |
|||
sources.push_back({code.c_str(), code.size()}); |
|||
|
|||
cl::Program program(m_context, sources); |
|||
try |
|||
{ |
|||
program.build({device}); |
|||
} |
|||
catch (cl::Error err) |
|||
{ |
|||
debugf("%s\n", program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(device).c_str()); |
|||
return false; |
|||
} |
|||
m_hash_kernel = cl::Kernel(program, "ethash_hash"); |
|||
m_search_kernel = cl::Kernel(program, "ethash_search"); |
|||
|
|||
// create buffer for dag
|
|||
m_dag = cl::Buffer(m_context, CL_MEM_READ_ONLY, params.full_size); |
|||
|
|||
// create buffer for header
|
|||
m_header = cl::Buffer(m_context, CL_MEM_READ_ONLY, 32); |
|||
|
|||
// compute dag on CPU
|
|||
{ |
|||
// if this throws then it's because we probably need to subdivide the dag uploads for compatibility
|
|||
void* dag_ptr = m_queue.enqueueMapBuffer(m_dag, true, m_opencl_1_1 ? CL_MAP_WRITE : CL_MAP_WRITE_INVALIDATE_REGION, 0, params.full_size); |
|||
// memcpying 1GB: horrible... really. horrible. but necessary since we can't mmap *and* gpumap.
|
|||
_fillDAG(dag_ptr); |
|||
m_queue.enqueueUnmapMemObject(m_dag, dag_ptr); |
|||
} |
|||
|
|||
// create mining buffers
|
|||
for (unsigned i = 0; i != c_num_buffers; ++i) |
|||
{ |
|||
m_hash_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY | (!m_opencl_1_1 ? CL_MEM_HOST_READ_ONLY : 0), 32*c_hash_batch_size); |
|||
m_search_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY, (c_max_search_results + 1) * sizeof(uint32_t)); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void ethash_cl_miner::hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count) |
|||
{ |
|||
struct pending_batch |
|||
{ |
|||
unsigned base; |
|||
unsigned count; |
|||
unsigned buf; |
|||
}; |
|||
std::queue<pending_batch> pending; |
|||
|
|||
// update header constant buffer
|
|||
m_queue.enqueueWriteBuffer(m_header, true, 0, 32, header); |
|||
|
|||
/*
|
|||
__kernel void ethash_combined_hash( |
|||
__global hash32_t* g_hashes, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong start_nonce, |
|||
uint isolate |
|||
) |
|||
*/ |
|||
m_hash_kernel.setArg(1, m_header); |
|||
m_hash_kernel.setArg(2, m_dag); |
|||
m_hash_kernel.setArg(3, nonce); |
|||
m_hash_kernel.setArg(4, ~0u); // have to pass this to stop the compile unrolling the loop
|
|||
|
|||
unsigned buf = 0; |
|||
for (unsigned i = 0; i < count || !pending.empty(); ) |
|||
{ |
|||
// how many this batch
|
|||
if (i < count) |
|||
{ |
|||
unsigned const this_count = std::min<unsigned>(count - i, c_hash_batch_size); |
|||
unsigned const batch_count = std::max<unsigned>(this_count, m_workgroup_size); |
|||
|
|||
// supply output hash buffer to kernel
|
|||
m_hash_kernel.setArg(0, m_hash_buf[buf]); |
|||
|
|||
// execute it!
|
|||
m_queue.enqueueNDRangeKernel( |
|||
m_hash_kernel, |
|||
cl::NullRange, |
|||
cl::NDRange(batch_count), |
|||
cl::NDRange(m_workgroup_size) |
|||
); |
|||
m_queue.flush(); |
|||
|
|||
pending.push({i, this_count, buf}); |
|||
i += this_count; |
|||
buf = (buf + 1) % c_num_buffers; |
|||
} |
|||
|
|||
// read results
|
|||
if (i == count || pending.size() == c_num_buffers) |
|||
{ |
|||
pending_batch const& batch = pending.front(); |
|||
|
|||
// could use pinned host pointer instead, but this path isn't that important.
|
|||
uint8_t* hashes = (uint8_t*)m_queue.enqueueMapBuffer(m_hash_buf[batch.buf], true, CL_MAP_READ, 0, batch.count * ETHASH_BYTES); |
|||
memcpy(ret + batch.base*ETHASH_BYTES, hashes, batch.count*ETHASH_BYTES); |
|||
m_queue.enqueueUnmapMemObject(m_hash_buf[batch.buf], hashes); |
|||
|
|||
pending.pop(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook& hook) |
|||
{ |
|||
struct pending_batch |
|||
{ |
|||
uint64_t start_nonce; |
|||
unsigned buf; |
|||
}; |
|||
std::queue<pending_batch> pending; |
|||
|
|||
static uint32_t const c_zero = 0; |
|||
|
|||
// update header constant buffer
|
|||
m_queue.enqueueWriteBuffer(m_header, false, 0, 32, header); |
|||
for (unsigned i = 0; i != c_num_buffers; ++i) |
|||
{ |
|||
m_queue.enqueueWriteBuffer(m_search_buf[i], false, 0, 4, &c_zero); |
|||
} |
|||
|
|||
#if CL_VERSION_1_2 && 0 |
|||
cl::Event pre_return_event; |
|||
if (!m_opencl_1_1) |
|||
{ |
|||
m_queue.enqueueBarrierWithWaitList(NULL, &pre_return_event); |
|||
} |
|||
else |
|||
#endif |
|||
{ |
|||
m_queue.finish(); |
|||
} |
|||
|
|||
/*
|
|||
__kernel void ethash_combined_search( |
|||
__global hash32_t* g_hashes, // 0
|
|||
__constant hash32_t const* g_header, // 1
|
|||
__global hash128_t const* g_dag, // 2
|
|||
ulong start_nonce, // 3
|
|||
ulong target, // 4
|
|||
uint isolate // 5
|
|||
) |
|||
*/ |
|||
m_search_kernel.setArg(1, m_header); |
|||
m_search_kernel.setArg(2, m_dag); |
|||
|
|||
// pass these to stop the compiler unrolling the loops
|
|||
m_search_kernel.setArg(4, target); |
|||
m_search_kernel.setArg(5, ~0u); |
|||
|
|||
|
|||
unsigned buf = 0; |
|||
for (uint64_t start_nonce = 0; ; start_nonce += c_search_batch_size) |
|||
{ |
|||
// supply output buffer to kernel
|
|||
m_search_kernel.setArg(0, m_search_buf[buf]); |
|||
m_search_kernel.setArg(3, start_nonce); |
|||
|
|||
// execute it!
|
|||
m_queue.enqueueNDRangeKernel(m_search_kernel, cl::NullRange, c_search_batch_size, m_workgroup_size); |
|||
|
|||
pending.push({start_nonce, buf}); |
|||
buf = (buf + 1) % c_num_buffers; |
|||
|
|||
// read results
|
|||
if (pending.size() == c_num_buffers) |
|||
{ |
|||
pending_batch const& batch = pending.front(); |
|||
|
|||
// could use pinned host pointer instead
|
|||
uint32_t* results = (uint32_t*)m_queue.enqueueMapBuffer(m_search_buf[batch.buf], true, CL_MAP_READ, 0, (1+c_max_search_results) * sizeof(uint32_t)); |
|||
unsigned num_found = std::min<unsigned>(results[0], c_max_search_results); |
|||
|
|||
uint64_t nonces[c_max_search_results]; |
|||
for (unsigned i = 0; i != num_found; ++i) |
|||
{ |
|||
nonces[i] = batch.start_nonce + results[i+1]; |
|||
} |
|||
|
|||
m_queue.enqueueUnmapMemObject(m_search_buf[batch.buf], results); |
|||
|
|||
bool exit = num_found && hook.found(nonces, num_found); |
|||
exit |= hook.searched(batch.start_nonce, c_search_batch_size); // always report searched before exit
|
|||
if (exit) |
|||
break; |
|||
|
|||
// reset search buffer if we're still going
|
|||
if (num_found) |
|||
m_queue.enqueueWriteBuffer(m_search_buf[batch.buf], true, 0, 4, &c_zero); |
|||
|
|||
pending.pop(); |
|||
} |
|||
} |
|||
|
|||
// not safe to return until this is ready
|
|||
#if CL_VERSION_1_2 && 0 |
|||
if (!m_opencl_1_1) |
|||
{ |
|||
pre_return_event.wait(); |
|||
} |
|||
#endif |
|||
} |
|||
|
@ -0,0 +1,45 @@ |
|||
#pragma once |
|||
|
|||
#define __CL_ENABLE_EXCEPTIONS |
|||
#define CL_USE_DEPRECATED_OPENCL_2_0_APIS |
|||
#include "cl.hpp" |
|||
#include <time.h> |
|||
#include <functional> |
|||
#include <libethash/ethash.h> |
|||
|
|||
class ethash_cl_miner |
|||
{ |
|||
public: |
|||
struct search_hook |
|||
{ |
|||
virtual ~search_hook(); // always a virtual destructor for a class with virtuals.
|
|||
|
|||
// reports progress, return true to abort
|
|||
virtual bool found(uint64_t const* nonces, uint32_t count) = 0; |
|||
virtual bool searched(uint64_t start_nonce, uint32_t count) = 0; |
|||
}; |
|||
|
|||
public: |
|||
ethash_cl_miner(); |
|||
|
|||
bool init(ethash_params const& params, std::function<void(void*)> _fillDAG, unsigned workgroup_size = 64); |
|||
|
|||
void finish(); |
|||
void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); |
|||
void search(uint8_t const* header, uint64_t target, search_hook& hook); |
|||
|
|||
private: |
|||
enum { c_max_search_results = 63, c_num_buffers = 2, c_hash_batch_size = 1024, c_search_batch_size = 1024*256 }; |
|||
|
|||
ethash_params m_params; |
|||
cl::Context m_context; |
|||
cl::CommandQueue m_queue; |
|||
cl::Kernel m_hash_kernel; |
|||
cl::Kernel m_search_kernel; |
|||
cl::Buffer m_dag; |
|||
cl::Buffer m_header; |
|||
cl::Buffer m_hash_buf[c_num_buffers]; |
|||
cl::Buffer m_search_buf[c_num_buffers]; |
|||
unsigned m_workgroup_size; |
|||
bool m_opencl_1_1; |
|||
}; |
@ -0,0 +1,460 @@ |
|||
// author Tim Hughes <tim@twistedfury.com> |
|||
// Tested on Radeon HD 7850 |
|||
// Hashrate: 15940347 hashes/s |
|||
// Bandwidth: 124533 MB/s |
|||
// search kernel should fit in <= 84 VGPRS (3 wavefronts) |
|||
|
|||
#define THREADS_PER_HASH (128 / 16) |
|||
#define HASHES_PER_LOOP (GROUP_SIZE / THREADS_PER_HASH) |
|||
|
|||
#define FNV_PRIME 0x01000193 |
|||
|
|||
__constant uint2 const Keccak_f1600_RC[24] = { |
|||
(uint2)(0x00000001, 0x00000000), |
|||
(uint2)(0x00008082, 0x00000000), |
|||
(uint2)(0x0000808a, 0x80000000), |
|||
(uint2)(0x80008000, 0x80000000), |
|||
(uint2)(0x0000808b, 0x00000000), |
|||
(uint2)(0x80000001, 0x00000000), |
|||
(uint2)(0x80008081, 0x80000000), |
|||
(uint2)(0x00008009, 0x80000000), |
|||
(uint2)(0x0000008a, 0x00000000), |
|||
(uint2)(0x00000088, 0x00000000), |
|||
(uint2)(0x80008009, 0x00000000), |
|||
(uint2)(0x8000000a, 0x00000000), |
|||
(uint2)(0x8000808b, 0x00000000), |
|||
(uint2)(0x0000008b, 0x80000000), |
|||
(uint2)(0x00008089, 0x80000000), |
|||
(uint2)(0x00008003, 0x80000000), |
|||
(uint2)(0x00008002, 0x80000000), |
|||
(uint2)(0x00000080, 0x80000000), |
|||
(uint2)(0x0000800a, 0x00000000), |
|||
(uint2)(0x8000000a, 0x80000000), |
|||
(uint2)(0x80008081, 0x80000000), |
|||
(uint2)(0x00008080, 0x80000000), |
|||
(uint2)(0x80000001, 0x00000000), |
|||
(uint2)(0x80008008, 0x80000000), |
|||
}; |
|||
|
|||
void keccak_f1600_round(uint2* a, uint r, uint out_size) |
|||
{ |
|||
#if !__ENDIAN_LITTLE__ |
|||
for (uint i = 0; i != 25; ++i) |
|||
a[i] = a[i].yx; |
|||
#endif |
|||
|
|||
uint2 b[25]; |
|||
uint2 t; |
|||
|
|||
// Theta |
|||
b[0] = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]; |
|||
b[1] = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]; |
|||
b[2] = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]; |
|||
b[3] = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]; |
|||
b[4] = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]; |
|||
t = b[4] ^ (uint2)(b[1].x << 1 | b[1].y >> 31, b[1].y << 1 | b[1].x >> 31); |
|||
a[0] ^= t; |
|||
a[5] ^= t; |
|||
a[10] ^= t; |
|||
a[15] ^= t; |
|||
a[20] ^= t; |
|||
t = b[0] ^ (uint2)(b[2].x << 1 | b[2].y >> 31, b[2].y << 1 | b[2].x >> 31); |
|||
a[1] ^= t; |
|||
a[6] ^= t; |
|||
a[11] ^= t; |
|||
a[16] ^= t; |
|||
a[21] ^= t; |
|||
t = b[1] ^ (uint2)(b[3].x << 1 | b[3].y >> 31, b[3].y << 1 | b[3].x >> 31); |
|||
a[2] ^= t; |
|||
a[7] ^= t; |
|||
a[12] ^= t; |
|||
a[17] ^= t; |
|||
a[22] ^= t; |
|||
t = b[2] ^ (uint2)(b[4].x << 1 | b[4].y >> 31, b[4].y << 1 | b[4].x >> 31); |
|||
a[3] ^= t; |
|||
a[8] ^= t; |
|||
a[13] ^= t; |
|||
a[18] ^= t; |
|||
a[23] ^= t; |
|||
t = b[3] ^ (uint2)(b[0].x << 1 | b[0].y >> 31, b[0].y << 1 | b[0].x >> 31); |
|||
a[4] ^= t; |
|||
a[9] ^= t; |
|||
a[14] ^= t; |
|||
a[19] ^= t; |
|||
a[24] ^= t; |
|||
|
|||
// Rho Pi |
|||
b[0] = a[0]; |
|||
b[10] = (uint2)(a[1].x << 1 | a[1].y >> 31, a[1].y << 1 | a[1].x >> 31); |
|||
b[7] = (uint2)(a[10].x << 3 | a[10].y >> 29, a[10].y << 3 | a[10].x >> 29); |
|||
b[11] = (uint2)(a[7].x << 6 | a[7].y >> 26, a[7].y << 6 | a[7].x >> 26); |
|||
b[17] = (uint2)(a[11].x << 10 | a[11].y >> 22, a[11].y << 10 | a[11].x >> 22); |
|||
b[18] = (uint2)(a[17].x << 15 | a[17].y >> 17, a[17].y << 15 | a[17].x >> 17); |
|||
b[3] = (uint2)(a[18].x << 21 | a[18].y >> 11, a[18].y << 21 | a[18].x >> 11); |
|||
b[5] = (uint2)(a[3].x << 28 | a[3].y >> 4, a[3].y << 28 | a[3].x >> 4); |
|||
b[16] = (uint2)(a[5].y << 4 | a[5].x >> 28, a[5].x << 4 | a[5].y >> 28); |
|||
b[8] = (uint2)(a[16].y << 13 | a[16].x >> 19, a[16].x << 13 | a[16].y >> 19); |
|||
b[21] = (uint2)(a[8].y << 23 | a[8].x >> 9, a[8].x << 23 | a[8].y >> 9); |
|||
b[24] = (uint2)(a[21].x << 2 | a[21].y >> 30, a[21].y << 2 | a[21].x >> 30); |
|||
b[4] = (uint2)(a[24].x << 14 | a[24].y >> 18, a[24].y << 14 | a[24].x >> 18); |
|||
b[15] = (uint2)(a[4].x << 27 | a[4].y >> 5, a[4].y << 27 | a[4].x >> 5); |
|||
b[23] = (uint2)(a[15].y << 9 | a[15].x >> 23, a[15].x << 9 | a[15].y >> 23); |
|||
b[19] = (uint2)(a[23].y << 24 | a[23].x >> 8, a[23].x << 24 | a[23].y >> 8); |
|||
b[13] = (uint2)(a[19].x << 8 | a[19].y >> 24, a[19].y << 8 | a[19].x >> 24); |
|||
b[12] = (uint2)(a[13].x << 25 | a[13].y >> 7, a[13].y << 25 | a[13].x >> 7); |
|||
b[2] = (uint2)(a[12].y << 11 | a[12].x >> 21, a[12].x << 11 | a[12].y >> 21); |
|||
b[20] = (uint2)(a[2].y << 30 | a[2].x >> 2, a[2].x << 30 | a[2].y >> 2); |
|||
b[14] = (uint2)(a[20].x << 18 | a[20].y >> 14, a[20].y << 18 | a[20].x >> 14); |
|||
b[22] = (uint2)(a[14].y << 7 | a[14].x >> 25, a[14].x << 7 | a[14].y >> 25); |
|||
b[9] = (uint2)(a[22].y << 29 | a[22].x >> 3, a[22].x << 29 | a[22].y >> 3); |
|||
b[6] = (uint2)(a[9].x << 20 | a[9].y >> 12, a[9].y << 20 | a[9].x >> 12); |
|||
b[1] = (uint2)(a[6].y << 12 | a[6].x >> 20, a[6].x << 12 | a[6].y >> 20); |
|||
|
|||
// Chi |
|||
a[0] = bitselect(b[0] ^ b[2], b[0], b[1]); |
|||
a[1] = bitselect(b[1] ^ b[3], b[1], b[2]); |
|||
a[2] = bitselect(b[2] ^ b[4], b[2], b[3]); |
|||
a[3] = bitselect(b[3] ^ b[0], b[3], b[4]); |
|||
if (out_size >= 4) |
|||
{ |
|||
a[4] = bitselect(b[4] ^ b[1], b[4], b[0]); |
|||
a[5] = bitselect(b[5] ^ b[7], b[5], b[6]); |
|||
a[6] = bitselect(b[6] ^ b[8], b[6], b[7]); |
|||
a[7] = bitselect(b[7] ^ b[9], b[7], b[8]); |
|||
a[8] = bitselect(b[8] ^ b[5], b[8], b[9]); |
|||
if (out_size >= 8) |
|||
{ |
|||
a[9] = bitselect(b[9] ^ b[6], b[9], b[5]); |
|||
a[10] = bitselect(b[10] ^ b[12], b[10], b[11]); |
|||
a[11] = bitselect(b[11] ^ b[13], b[11], b[12]); |
|||
a[12] = bitselect(b[12] ^ b[14], b[12], b[13]); |
|||
a[13] = bitselect(b[13] ^ b[10], b[13], b[14]); |
|||
a[14] = bitselect(b[14] ^ b[11], b[14], b[10]); |
|||
a[15] = bitselect(b[15] ^ b[17], b[15], b[16]); |
|||
a[16] = bitselect(b[16] ^ b[18], b[16], b[17]); |
|||
a[17] = bitselect(b[17] ^ b[19], b[17], b[18]); |
|||
a[18] = bitselect(b[18] ^ b[15], b[18], b[19]); |
|||
a[19] = bitselect(b[19] ^ b[16], b[19], b[15]); |
|||
a[20] = bitselect(b[20] ^ b[22], b[20], b[21]); |
|||
a[21] = bitselect(b[21] ^ b[23], b[21], b[22]); |
|||
a[22] = bitselect(b[22] ^ b[24], b[22], b[23]); |
|||
a[23] = bitselect(b[23] ^ b[20], b[23], b[24]); |
|||
a[24] = bitselect(b[24] ^ b[21], b[24], b[20]); |
|||
} |
|||
} |
|||
|
|||
// Iota |
|||
a[0] ^= Keccak_f1600_RC[r]; |
|||
|
|||
#if !__ENDIAN_LITTLE__ |
|||
for (uint i = 0; i != 25; ++i) |
|||
a[i] = a[i].yx; |
|||
#endif |
|||
} |
|||
|
|||
void keccak_f1600_no_absorb(ulong* a, uint in_size, uint out_size, uint isolate) |
|||
{ |
|||
for (uint i = in_size; i != 25; ++i) |
|||
{ |
|||
a[i] = 0; |
|||
} |
|||
#if __ENDIAN_LITTLE__ |
|||
a[in_size] ^= 0x0000000000000001; |
|||
a[24-out_size*2] ^= 0x8000000000000000; |
|||
#else |
|||
a[in_size] ^= 0x0100000000000000; |
|||
a[24-out_size*2] ^= 0x0000000000000080; |
|||
#endif |
|||
|
|||
// Originally I unrolled the first and last rounds to interface |
|||
// better with surrounding code, however I haven't done this |
|||
// without causing the AMD compiler to blow up the VGPR usage. |
|||
uint r = 0; |
|||
do |
|||
{ |
|||
// This dynamic branch stops the AMD compiler unrolling the loop |
|||
// and additionally saves about 33% of the VGPRs, enough to gain another |
|||
// wavefront. Ideally we'd get 4 in flight, but 3 is the best I can |
|||
// massage out of the compiler. It doesn't really seem to matter how |
|||
// much we try and help the compiler save VGPRs because it seems to throw |
|||
// that information away, hence the implementation of keccak here |
|||
// doesn't bother. |
|||
if (isolate) |
|||
{ |
|||
keccak_f1600_round((uint2*)a, r++, 25); |
|||
} |
|||
} |
|||
while (r < 23); |
|||
|
|||
// final round optimised for digest size |
|||
keccak_f1600_round((uint2*)a, r++, out_size); |
|||
} |
|||
|
|||
#define copy(dst, src, count) for (uint i = 0; i != count; ++i) { (dst)[i] = (src)[i]; } |
|||
|
|||
#define countof(x) (sizeof(x) / sizeof(x[0])) |
|||
|
|||
uint fnv(uint x, uint y) |
|||
{ |
|||
return x * FNV_PRIME ^ y; |
|||
} |
|||
|
|||
uint4 fnv4(uint4 x, uint4 y) |
|||
{ |
|||
return x * FNV_PRIME ^ y; |
|||
} |
|||
|
|||
uint fnv_reduce(uint4 v) |
|||
{ |
|||
return fnv(fnv(fnv(v.x, v.y), v.z), v.w); |
|||
} |
|||
|
|||
typedef union |
|||
{ |
|||
ulong ulongs[32 / sizeof(ulong)]; |
|||
uint uints[32 / sizeof(uint)]; |
|||
} hash32_t; |
|||
|
|||
typedef union |
|||
{ |
|||
ulong ulongs[64 / sizeof(ulong)]; |
|||
uint4 uint4s[64 / sizeof(uint4)]; |
|||
} hash64_t; |
|||
|
|||
typedef union |
|||
{ |
|||
uint uints[128 / sizeof(uint)]; |
|||
uint4 uint4s[128 / sizeof(uint4)]; |
|||
} hash128_t; |
|||
|
|||
hash64_t init_hash(__constant hash32_t const* header, ulong nonce, uint isolate) |
|||
{ |
|||
hash64_t init; |
|||
uint const init_size = countof(init.ulongs); |
|||
uint const hash_size = countof(header->ulongs); |
|||
|
|||
// sha3_512(header .. nonce) |
|||
ulong state[25]; |
|||
copy(state, header->ulongs, hash_size); |
|||
state[hash_size] = nonce; |
|||
keccak_f1600_no_absorb(state, hash_size + 1, init_size, isolate); |
|||
|
|||
copy(init.ulongs, state, init_size); |
|||
return init; |
|||
} |
|||
|
|||
uint inner_loop(uint4 init, uint thread_id, __local uint* share, __global hash128_t const* g_dag, uint isolate) |
|||
{ |
|||
uint4 mix = init; |
|||
|
|||
// share init0 |
|||
if (thread_id == 0) |
|||
*share = mix.x; |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
uint init0 = *share; |
|||
|
|||
uint a = 0; |
|||
do |
|||
{ |
|||
bool update_share = thread_id == (a/4) % THREADS_PER_HASH; |
|||
|
|||
#pragma unroll |
|||
for (uint i = 0; i != 4; ++i) |
|||
{ |
|||
if (update_share) |
|||
{ |
|||
uint m[4] = { mix.x, mix.y, mix.z, mix.w }; |
|||
*share = fnv(init0 ^ (a+i), m[i]) % DAG_SIZE; |
|||
} |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
|
|||
mix = fnv4(mix, g_dag[*share].uint4s[thread_id]); |
|||
} |
|||
} |
|||
while ((a += 4) != (ACCESSES & isolate)); |
|||
|
|||
return fnv_reduce(mix); |
|||
} |
|||
|
|||
hash32_t final_hash(hash64_t const* init, hash32_t const* mix, uint isolate) |
|||
{ |
|||
ulong state[25]; |
|||
|
|||
hash32_t hash; |
|||
uint const hash_size = countof(hash.ulongs); |
|||
uint const init_size = countof(init->ulongs); |
|||
uint const mix_size = countof(mix->ulongs); |
|||
|
|||
// keccak_256(keccak_512(header..nonce) .. mix); |
|||
copy(state, init->ulongs, init_size); |
|||
copy(state + init_size, mix->ulongs, mix_size); |
|||
keccak_f1600_no_absorb(state, init_size+mix_size, hash_size, isolate); |
|||
|
|||
// copy out |
|||
copy(hash.ulongs, state, hash_size); |
|||
return hash; |
|||
} |
|||
|
|||
hash32_t compute_hash_simple( |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong nonce, |
|||
uint isolate |
|||
) |
|||
{ |
|||
hash64_t init = init_hash(g_header, nonce, isolate); |
|||
|
|||
hash128_t mix; |
|||
for (uint i = 0; i != countof(mix.uint4s); ++i) |
|||
{ |
|||
mix.uint4s[i] = init.uint4s[i % countof(init.uint4s)]; |
|||
} |
|||
|
|||
uint mix_val = mix.uints[0]; |
|||
uint init0 = mix.uints[0]; |
|||
uint a = 0; |
|||
do |
|||
{ |
|||
uint pi = fnv(init0 ^ a, mix_val) % DAG_SIZE; |
|||
uint n = (a+1) % countof(mix.uints); |
|||
|
|||
#pragma unroll |
|||
for (uint i = 0; i != countof(mix.uints); ++i) |
|||
{ |
|||
mix.uints[i] = fnv(mix.uints[i], g_dag[pi].uints[i]); |
|||
mix_val = i == n ? mix.uints[i] : mix_val; |
|||
} |
|||
} |
|||
while (++a != (ACCESSES & isolate)); |
|||
|
|||
// reduce to output |
|||
hash32_t fnv_mix; |
|||
for (uint i = 0; i != countof(fnv_mix.uints); ++i) |
|||
{ |
|||
fnv_mix.uints[i] = fnv_reduce(mix.uint4s[i]); |
|||
} |
|||
|
|||
return final_hash(&init, &fnv_mix, isolate); |
|||
} |
|||
|
|||
typedef union |
|||
{ |
|||
struct |
|||
{ |
|||
hash64_t init; |
|||
uint pad; // avoid lds bank conflicts |
|||
}; |
|||
hash32_t mix; |
|||
} compute_hash_share; |
|||
|
|||
hash32_t compute_hash( |
|||
__local compute_hash_share* share, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong nonce, |
|||
uint isolate |
|||
) |
|||
{ |
|||
uint const gid = get_global_id(0); |
|||
|
|||
// Compute one init hash per work item. |
|||
hash64_t init = init_hash(g_header, nonce, isolate); |
|||
|
|||
// Threads work together in this phase in groups of 8. |
|||
uint const thread_id = gid % THREADS_PER_HASH; |
|||
uint const hash_id = (gid % GROUP_SIZE) / THREADS_PER_HASH; |
|||
|
|||
hash32_t mix; |
|||
uint i = 0; |
|||
do |
|||
{ |
|||
// share init with other threads |
|||
if (i == thread_id) |
|||
share[hash_id].init = init; |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
|
|||
uint4 thread_init = share[hash_id].init.uint4s[thread_id % (64 / sizeof(uint4))]; |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
|
|||
uint thread_mix = inner_loop(thread_init, thread_id, share[hash_id].mix.uints, g_dag, isolate); |
|||
|
|||
share[hash_id].mix.uints[thread_id] = thread_mix; |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
|
|||
if (i == thread_id) |
|||
mix = share[hash_id].mix; |
|||
barrier(CLK_LOCAL_MEM_FENCE); |
|||
} |
|||
while (++i != (THREADS_PER_HASH & isolate)); |
|||
|
|||
return final_hash(&init, &mix, isolate); |
|||
} |
|||
|
|||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
|||
__kernel void ethash_hash_simple( |
|||
__global hash32_t* g_hashes, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong start_nonce, |
|||
uint isolate |
|||
) |
|||
{ |
|||
uint const gid = get_global_id(0); |
|||
g_hashes[gid] = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
|||
} |
|||
|
|||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
|||
__kernel void ethash_search_simple( |
|||
__global volatile uint* restrict g_output, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong start_nonce, |
|||
ulong target, |
|||
uint isolate |
|||
) |
|||
{ |
|||
uint const gid = get_global_id(0); |
|||
hash32_t hash = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
|||
if (as_ulong(as_uchar8(hash.ulongs[0]).s76543210) < target) |
|||
{ |
|||
uint slot = min(MAX_OUTPUTS, atomic_inc(&g_output[0]) + 1); |
|||
g_output[slot] = gid; |
|||
} |
|||
} |
|||
|
|||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
|||
__kernel void ethash_hash( |
|||
__global hash32_t* g_hashes, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong start_nonce, |
|||
uint isolate |
|||
) |
|||
{ |
|||
__local compute_hash_share share[HASHES_PER_LOOP]; |
|||
|
|||
uint const gid = get_global_id(0); |
|||
g_hashes[gid] = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
|||
} |
|||
|
|||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
|||
__kernel void ethash_search( |
|||
__global volatile uint* restrict g_output, |
|||
__constant hash32_t const* g_header, |
|||
__global hash128_t const* g_dag, |
|||
ulong start_nonce, |
|||
ulong target, |
|||
uint isolate |
|||
) |
|||
{ |
|||
__local compute_hash_share share[HASHES_PER_LOOP]; |
|||
|
|||
uint const gid = get_global_id(0); |
|||
hash32_t hash = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
|||
|
|||
if (as_ulong(as_uchar8(hash.ulongs[0]).s76543210) < target) |
|||
{ |
|||
uint slot = min(MAX_OUTPUTS, atomic_inc(&g_output[0]) + 1); |
|||
g_output[slot] = gid; |
|||
} |
|||
} |
@ -0,0 +1,264 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Ethash.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "Ethash.h" |
|||
|
|||
#include <boost/detail/endian.hpp> |
|||
#include <boost/filesystem.hpp> |
|||
#include <chrono> |
|||
#include <array> |
|||
#include <thread> |
|||
#include <random> |
|||
#include <thread> |
|||
#include <libdevcore/Guards.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/FileSystem.h> |
|||
#include <libethash/ethash.h> |
|||
#if ETH_ETHASHCL || !ETH_TRUE |
|||
#include <libethash-cl/ethash_cl_miner.h> |
|||
#endif |
|||
#include "BlockInfo.h" |
|||
#include "EthashAux.h" |
|||
using namespace std; |
|||
using namespace std::chrono; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
const Ethash::WorkPackage Ethash::NullWorkPackage = Ethash::WorkPackage(); |
|||
|
|||
std::string Ethash::name() |
|||
{ |
|||
return "Ethash"; |
|||
} |
|||
|
|||
unsigned Ethash::revision() |
|||
{ |
|||
return ETHASH_REVISION; |
|||
} |
|||
|
|||
Ethash::WorkPackage Ethash::package(BlockInfo const& _bi) |
|||
{ |
|||
WorkPackage ret; |
|||
ret.boundary = _bi.boundary(); |
|||
ret.headerHash = _bi.headerHash(WithoutNonce); |
|||
ret.seedHash = _bi.seedHash(); |
|||
return ret; |
|||
} |
|||
|
|||
void Ethash::prep(BlockInfo const& _header) |
|||
{ |
|||
EthashAux::full(_header); |
|||
} |
|||
|
|||
bool Ethash::preVerify(BlockInfo const& _header) |
|||
{ |
|||
if (_header.number >= ETHASH_EPOCH_LENGTH * 2048) |
|||
return false; |
|||
|
|||
h256 boundary = u256((bigint(1) << 256) / _header.difficulty); |
|||
|
|||
return !!ethash_quick_check_difficulty( |
|||
_header.headerHash(WithoutNonce).data(), |
|||
(uint64_t)(u64)_header.nonce, |
|||
_header.mixHash.data(), |
|||
boundary.data()); |
|||
} |
|||
|
|||
bool Ethash::verify(BlockInfo const& _header) |
|||
{ |
|||
bool pre = preVerify(_header); |
|||
#if !ETH_DEBUG |
|||
if (!pre) |
|||
return false; |
|||
#endif |
|||
|
|||
auto result = EthashAux::eval(_header); |
|||
bool slow = result.value <= _header.boundary() && result.mixHash == _header.mixHash; |
|||
|
|||
#if ETH_DEBUG || !ETH_TRUE |
|||
if (!pre && slow) |
|||
{ |
|||
cwarn << "WARNING: evaluated result gives true whereas ethash_quick_check_difficulty gives false."; |
|||
cwarn << "headerHash:" << _header.headerHash(WithoutNonce); |
|||
cwarn << "nonce:" << _header.nonce; |
|||
cwarn << "mixHash:" << _header.mixHash; |
|||
cwarn << "difficulty:" << _header.difficulty; |
|||
cwarn << "boundary:" << _header.boundary(); |
|||
cwarn << "result.value:" << result.value; |
|||
cwarn << "result.mixHash:" << result.mixHash; |
|||
} |
|||
#endif |
|||
|
|||
return slow; |
|||
} |
|||
|
|||
void Ethash::CPUMiner::workLoop() |
|||
{ |
|||
auto tid = std::this_thread::get_id(); |
|||
static std::mt19937_64 s_eng((time(0) + std::hash<decltype(tid)>()(tid))); |
|||
|
|||
uint64_t tryNonce = (uint64_t)(u64)Nonce::random(s_eng); |
|||
ethash_return_value ethashReturn; |
|||
|
|||
WorkPackage w = work(); |
|||
|
|||
auto p = EthashAux::params(w.seedHash); |
|||
void const* dagPointer = EthashAux::full(w.seedHash).data(); |
|||
uint8_t const* headerHashPointer = w.headerHash.data(); |
|||
h256 boundary = w.boundary; |
|||
unsigned hashCount = 1; |
|||
for (; !shouldStop(); tryNonce++, hashCount++) |
|||
{ |
|||
ethash_compute_full(ðashReturn, dagPointer, &p, headerHashPointer, tryNonce); |
|||
h256 value = h256(ethashReturn.result, h256::ConstructFromPointer); |
|||
if (value <= boundary && submitProof(Solution{(Nonce)(u64)tryNonce, h256(ethashReturn.mix_hash, h256::ConstructFromPointer)})) |
|||
break; |
|||
if (!(hashCount % 1000)) |
|||
accumulateHashes(1000); |
|||
} |
|||
} |
|||
|
|||
#if ETH_ETHASHCL || !ETH_TRUE |
|||
|
|||
class EthashCLHook: public ethash_cl_miner::search_hook |
|||
{ |
|||
public: |
|||
EthashCLHook(Ethash::GPUMiner* _owner): m_owner(_owner) {} |
|||
|
|||
void abort() |
|||
{ |
|||
Guard l(x_all); |
|||
if (m_aborted) |
|||
return; |
|||
// cdebug << "Attempting to abort";
|
|||
m_abort = true; |
|||
for (unsigned timeout = 0; timeout < 100 && !m_aborted; ++timeout) |
|||
std::this_thread::sleep_for(chrono::milliseconds(30)); |
|||
// if (!m_aborted)
|
|||
// cwarn << "Couldn't abort. Abandoning OpenCL process.";
|
|||
} |
|||
|
|||
void reset() |
|||
{ |
|||
m_aborted = m_abort = false; |
|||
} |
|||
|
|||
protected: |
|||
virtual bool found(uint64_t const* _nonces, uint32_t _count) override |
|||
{ |
|||
// dev::operator <<(std::cerr << "Found nonces: ", vector<uint64_t>(_nonces, _nonces + _count)) << std::endl;
|
|||
for (uint32_t i = 0; i < _count; ++i) |
|||
{ |
|||
if (m_owner->report(_nonces[i])) |
|||
{ |
|||
m_aborted = true; |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
virtual bool searched(uint64_t _startNonce, uint32_t _count) override |
|||
{ |
|||
Guard l(x_all); |
|||
// std::cerr << "Searched " << _count << " from " << _startNonce << std::endl;
|
|||
m_owner->accumulateHashes(_count); |
|||
m_last = _startNonce + _count; |
|||
if (m_abort) |
|||
{ |
|||
m_aborted = true; |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private: |
|||
Mutex x_all; |
|||
uint64_t m_last; |
|||
bool m_abort = false; |
|||
bool m_aborted = true; |
|||
Ethash::GPUMiner* m_owner = nullptr; |
|||
}; |
|||
|
|||
Ethash::GPUMiner::GPUMiner(ConstructionInfo const& _ci): |
|||
Miner(_ci), |
|||
m_hook(new EthashCLHook(this)) |
|||
{ |
|||
} |
|||
|
|||
Ethash::GPUMiner::~GPUMiner() |
|||
{ |
|||
pause(); |
|||
delete m_miner; |
|||
delete m_hook; |
|||
} |
|||
|
|||
bool Ethash::GPUMiner::report(uint64_t _nonce) |
|||
{ |
|||
Nonce n = (Nonce)(u64)_nonce; |
|||
Result r = EthashAux::eval(work().seedHash, work().headerHash, n); |
|||
if (r.value < work().boundary) |
|||
return submitProof(Solution{n, r.mixHash}); |
|||
return false; |
|||
} |
|||
|
|||
void Ethash::GPUMiner::kickOff() |
|||
{ |
|||
m_hook->reset(); |
|||
startWorking(); |
|||
} |
|||
|
|||
void Ethash::GPUMiner::workLoop() |
|||
{ |
|||
// take local copy of work since it may end up being overwritten by kickOff/pause.
|
|||
WorkPackage w = work(); |
|||
if (!m_miner || m_minerSeed != w.seedHash) |
|||
{ |
|||
m_minerSeed = w.seedHash; |
|||
|
|||
delete m_miner; |
|||
m_miner = new ethash_cl_miner; |
|||
|
|||
auto p = EthashAux::params(m_minerSeed); |
|||
auto cb = [&](void* d) { EthashAux::full(m_minerSeed, bytesRef((byte*)d, p.full_size)); }; |
|||
m_miner->init(p, cb, 32); |
|||
} |
|||
|
|||
uint64_t upper64OfBoundary = (uint64_t)(u64)((u256)w.boundary >> 192); |
|||
m_miner->search(w.headerHash.data(), upper64OfBoundary, *m_hook); |
|||
} |
|||
|
|||
void Ethash::GPUMiner::pause() |
|||
{ |
|||
m_hook->abort(); |
|||
stopWorking(); |
|||
} |
|||
|
|||
#endif |
|||
|
|||
} |
|||
} |
@ -0,0 +1,134 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Ethash.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
* |
|||
* A proof of work algorithm. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include <thread> |
|||
#include <cstdint> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include "Common.h" |
|||
#include "BlockInfo.h" |
|||
#include "Miner.h" |
|||
|
|||
class ethash_cl_miner; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class EthashCLHook; |
|||
|
|||
class Ethash |
|||
{ |
|||
public: |
|||
using Miner = GenericMiner<Ethash>; |
|||
|
|||
struct Solution |
|||
{ |
|||
Nonce nonce; |
|||
h256 mixHash; |
|||
}; |
|||
|
|||
struct Result |
|||
{ |
|||
h256 value; |
|||
h256 mixHash; |
|||
}; |
|||
|
|||
struct WorkPackage |
|||
{ |
|||
WorkPackage() = default; |
|||
|
|||
void reset() { headerHash = h256(); } |
|||
operator bool() const { return headerHash != h256(); } |
|||
|
|||
h256 boundary; |
|||
h256 headerHash; ///< When h256() means "pause until notified a new work package is available".
|
|||
h256 seedHash; |
|||
}; |
|||
|
|||
static const WorkPackage NullWorkPackage; |
|||
|
|||
static std::string name(); |
|||
static unsigned revision(); |
|||
static void prep(BlockInfo const& _header); |
|||
static bool verify(BlockInfo const& _header); |
|||
static bool preVerify(BlockInfo const& _header); |
|||
static WorkPackage package(BlockInfo const& _header); |
|||
static void assignResult(Solution const& _r, BlockInfo& _header) { _header.nonce = _r.nonce; _header.mixHash = _r.mixHash; } |
|||
|
|||
class CPUMiner: public Miner, Worker |
|||
{ |
|||
public: |
|||
CPUMiner(ConstructionInfo const& _ci): Miner(_ci), Worker("miner" + toString(index())) {} |
|||
|
|||
static unsigned instances() { return std::thread::hardware_concurrency(); } |
|||
|
|||
protected: |
|||
void kickOff() override |
|||
{ |
|||
stopWorking(); |
|||
startWorking(); |
|||
} |
|||
|
|||
void pause() override { stopWorking(); } |
|||
|
|||
private: |
|||
void workLoop() override; |
|||
}; |
|||
|
|||
#if ETH_ETHASHCL || !ETH_TRUE |
|||
class GPUMiner: public Miner, Worker |
|||
{ |
|||
friend class dev::eth::EthashCLHook; |
|||
|
|||
public: |
|||
GPUMiner(ConstructionInfo const& _ci); |
|||
~GPUMiner(); |
|||
|
|||
static unsigned instances() { return 1; } |
|||
|
|||
protected: |
|||
void kickOff() override; |
|||
void pause() override; |
|||
|
|||
private: |
|||
void workLoop() override; |
|||
bool report(uint64_t _nonce); |
|||
|
|||
using Miner::accumulateHashes; |
|||
|
|||
EthashCLHook* m_hook = nullptr; |
|||
ethash_cl_miner* m_miner = nullptr; |
|||
|
|||
h256 m_minerSeed; ///< Last seed in m_miner
|
|||
}; |
|||
#else |
|||
using GPUMiner = CPUMiner; |
|||
#endif |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,214 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file EthashAux.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "EthashAux.h" |
|||
|
|||
#include <boost/detail/endian.hpp> |
|||
#include <boost/filesystem.hpp> |
|||
#include <chrono> |
|||
#include <array> |
|||
#include <random> |
|||
#include <thread> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Guards.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libdevcrypto/FileSystem.h> |
|||
#include <libethcore/Params.h> |
|||
#include "BlockInfo.h" |
|||
using namespace std; |
|||
using namespace chrono; |
|||
using namespace dev; |
|||
using namespace eth; |
|||
|
|||
#define ETH_IGNORE_EXCEPTIONS(X) try { X; } catch (...) {} |
|||
|
|||
EthashAux* dev::eth::EthashAux::s_this = nullptr; |
|||
|
|||
EthashAux::~EthashAux() |
|||
{ |
|||
while (!m_lights.empty()) |
|||
killCache(m_lights.begin()->first); |
|||
} |
|||
|
|||
ethash_params EthashAux::params(BlockInfo const& _header) |
|||
{ |
|||
return params((unsigned)_header.number); |
|||
} |
|||
|
|||
ethash_params EthashAux::params(unsigned _n) |
|||
{ |
|||
ethash_params p; |
|||
p.cache_size = ethash_get_cachesize(_n); |
|||
p.full_size = ethash_get_datasize(_n); |
|||
return p; |
|||
} |
|||
|
|||
h256 EthashAux::seedHash(unsigned _number) |
|||
{ |
|||
unsigned epoch = _number / ETHASH_EPOCH_LENGTH; |
|||
RecursiveGuard l(get()->x_this); |
|||
if (epoch >= get()->m_seedHashes.size()) |
|||
{ |
|||
h256 ret; |
|||
unsigned n = 0; |
|||
if (!get()->m_seedHashes.empty()) |
|||
{ |
|||
ret = get()->m_seedHashes.back(); |
|||
n = get()->m_seedHashes.size() - 1; |
|||
} |
|||
get()->m_seedHashes.resize(epoch + 1); |
|||
cdebug << "Searching for seedHash of epoch " << epoch; |
|||
for (; n <= epoch; ++n, ret = sha3(ret)) |
|||
{ |
|||
get()->m_seedHashes[n] = ret; |
|||
cdebug << "Epoch" << n << "is" << ret.abridged(); |
|||
} |
|||
} |
|||
return get()->m_seedHashes[epoch]; |
|||
} |
|||
|
|||
ethash_params EthashAux::params(h256 const& _seedHash) |
|||
{ |
|||
RecursiveGuard l(get()->x_this); |
|||
unsigned epoch = 0; |
|||
try |
|||
{ |
|||
epoch = get()->m_epochs.at(_seedHash); |
|||
} |
|||
catch (...) |
|||
{ |
|||
cdebug << "Searching for seedHash " << _seedHash.abridged(); |
|||
for (h256 h; h != _seedHash && epoch < 2048; ++epoch, h = sha3(h), get()->m_epochs[h] = epoch) {} |
|||
if (epoch == 2048) |
|||
{ |
|||
std::ostringstream error; |
|||
error << "apparent block number for " << _seedHash.abridged() << " is too high; max is " << (ETHASH_EPOCH_LENGTH * 2048); |
|||
throw std::invalid_argument(error.str()); |
|||
} |
|||
} |
|||
return params(epoch * ETHASH_EPOCH_LENGTH); |
|||
} |
|||
|
|||
void EthashAux::killCache(h256 const& _s) |
|||
{ |
|||
RecursiveGuard l(x_this); |
|||
if (m_lights.count(_s)) |
|||
{ |
|||
ethash_delete_light(m_lights.at(_s)); |
|||
m_lights.erase(_s); |
|||
} |
|||
} |
|||
|
|||
void const* EthashAux::light(BlockInfo const& _header) |
|||
{ |
|||
return light(_header.seedHash()); |
|||
} |
|||
|
|||
void const* EthashAux::light(h256 const& _seedHash) |
|||
{ |
|||
RecursiveGuard l(get()->x_this); |
|||
if (!get()->m_lights.count(_seedHash)) |
|||
{ |
|||
ethash_params p = params(_seedHash); |
|||
get()->m_lights[_seedHash] = ethash_new_light(&p, _seedHash.data()); |
|||
} |
|||
return get()->m_lights[_seedHash]; |
|||
} |
|||
|
|||
bytesConstRef EthashAux::full(BlockInfo const& _header, bytesRef _dest) |
|||
{ |
|||
return full(_header.seedHash(), _dest); |
|||
} |
|||
|
|||
bytesConstRef EthashAux::full(h256 const& _seedHash, bytesRef _dest) |
|||
{ |
|||
RecursiveGuard l(get()->x_this); |
|||
if (get()->m_fulls.count(_seedHash) && _dest) |
|||
{ |
|||
assert(get()->m_fulls.size() <= _dest.size()); |
|||
get()->m_fulls.at(_seedHash).copyTo(_dest); |
|||
return _dest; |
|||
} |
|||
if (!get()->m_fulls.count(_seedHash)) |
|||
{ |
|||
// @memoryleak @bug place it on a pile for deletion - perhaps use shared_ptr.
|
|||
/* if (!m_fulls.empty())
|
|||
{ |
|||
delete [] m_fulls.begin()->second.data(); |
|||
m_fulls.erase(m_fulls.begin()); |
|||
}*/ |
|||
|
|||
try { |
|||
boost::filesystem::create_directories(getDataDir("ethash")); |
|||
} catch (...) {} |
|||
|
|||
auto info = rlpList(Ethash::revision(), _seedHash); |
|||
std::string oldMemoFile = getDataDir("ethash") + "/full"; |
|||
std::string memoFile = getDataDir("ethash") + "/full-R" + toString(ETHASH_REVISION) + "-" + toHex(_seedHash.ref().cropped(0, 8)); |
|||
if (boost::filesystem::exists(oldMemoFile) && contents(oldMemoFile + ".info") == info) |
|||
{ |
|||
// memofile valid - rename.
|
|||
boost::filesystem::rename(oldMemoFile, memoFile); |
|||
} |
|||
|
|||
ETH_IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile)); |
|||
ETH_IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile + ".info")); |
|||
|
|||
ethash_params p = params(_seedHash); |
|||
assert(!_dest || _dest.size() >= p.full_size); // must be big enough.
|
|||
|
|||
bytesRef r = contentsNew(memoFile, _dest); |
|||
if (!r) |
|||
{ |
|||
// file didn't exist.
|
|||
if (_dest) |
|||
// buffer was passed in - no insertion into cache nor need to allocate
|
|||
r = _dest; |
|||
else |
|||
r = bytesRef(new byte[p.full_size], p.full_size); |
|||
ethash_prep_full(r.data(), &p, light(_seedHash)); |
|||
writeFile(memoFile, r); |
|||
} |
|||
if (_dest) |
|||
return _dest; |
|||
get()->m_fulls[_seedHash] = r; |
|||
} |
|||
return get()->m_fulls[_seedHash]; |
|||
} |
|||
|
|||
Ethash::Result EthashAux::eval(BlockInfo const& _header, Nonce const& _nonce) |
|||
{ |
|||
return eval(_header.seedHash(), _header.headerHash(WithoutNonce), _nonce); |
|||
} |
|||
|
|||
Ethash::Result EthashAux::eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce) |
|||
{ |
|||
auto p = EthashAux::params(_seedHash); |
|||
ethash_return_value r; |
|||
if (EthashAux::get()->m_fulls.count(_seedHash)) |
|||
ethash_compute_full(&r, EthashAux::get()->full(_seedHash).data(), &p, _headerHash.data(), (uint64_t)(u64)_nonce); |
|||
else |
|||
ethash_compute_light(&r, EthashAux::get()->light(_seedHash), &p, _headerHash.data(), (uint64_t)(u64)_nonce); |
|||
// cdebug << "EthashAux::eval sha3(cache):" << sha3(EthashAux::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer);
|
|||
return Ethash::Result{h256(r.result, h256::ConstructFromPointer), h256(r.mix_hash, h256::ConstructFromPointer)}; |
|||
} |
@ -0,0 +1,67 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file EthashAux.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include <libethash/ethash.h> |
|||
#include "Ethash.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth{ |
|||
|
|||
class EthashAux |
|||
{ |
|||
public: |
|||
~EthashAux(); |
|||
|
|||
static EthashAux* get() { if (!s_this) s_this = new EthashAux(); return s_this; } |
|||
|
|||
using LightType = void const*; |
|||
using FullType = void const*; |
|||
|
|||
static h256 seedHash(unsigned _number); |
|||
static ethash_params params(BlockInfo const& _header); |
|||
static ethash_params params(h256 const& _seedHash); |
|||
static ethash_params params(unsigned _n); |
|||
static LightType light(BlockInfo const& _header); |
|||
static LightType light(h256 const& _seedHash); |
|||
static bytesConstRef full(BlockInfo const& _header, bytesRef _dest = bytesRef()); |
|||
static bytesConstRef full(h256 const& _header, bytesRef _dest = bytesRef()); |
|||
|
|||
static Ethash::Result eval(BlockInfo const& _header) { return eval(_header, _header.nonce); } |
|||
static Ethash::Result eval(BlockInfo const& _header, Nonce const& _nonce); |
|||
static Ethash::Result eval(h256 const& _seedHash, h256 const& _headerHash, Nonce const& _nonce); |
|||
|
|||
private: |
|||
EthashAux() {} |
|||
|
|||
void killCache(h256 const& _s); |
|||
|
|||
static EthashAux* s_this; |
|||
RecursiveMutex x_this; |
|||
|
|||
std::map<h256, LightType> m_lights; |
|||
std::map<h256, bytesRef> m_fulls; |
|||
std::map<h256, unsigned> m_epochs; |
|||
h256s m_seedHashes; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,220 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Ethasher.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include <boost/detail/endian.hpp> |
|||
#include <boost/filesystem.hpp> |
|||
#include <chrono> |
|||
#include <array> |
|||
#include <random> |
|||
#include <thread> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Guards.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libdevcrypto/FileSystem.h> |
|||
#include <libethcore/Params.h> |
|||
#include "BlockInfo.h" |
|||
#include "Ethasher.h" |
|||
using namespace std; |
|||
using namespace chrono; |
|||
using namespace dev; |
|||
using namespace eth; |
|||
|
|||
Ethasher* dev::eth::Ethasher::s_this = nullptr; |
|||
|
|||
Ethasher::~Ethasher() |
|||
{ |
|||
while (!m_lights.empty()) |
|||
killCache(m_lights.begin()->first); |
|||
} |
|||
|
|||
void Ethasher::killCache(h256 const& _s) |
|||
{ |
|||
RecursiveGuard l(x_this); |
|||
if (m_lights.count(_s)) |
|||
{ |
|||
ethash_delete_light(m_lights.at(_s)); |
|||
m_lights.erase(_s); |
|||
} |
|||
} |
|||
|
|||
void const* Ethasher::light(BlockInfo const& _header) |
|||
{ |
|||
RecursiveGuard l(x_this); |
|||
if (_header.number > c_ethashEpochLength * 2048) |
|||
{ |
|||
std::ostringstream error; |
|||
error << "block number is too high; max is " << c_ethashEpochLength * 2048 << "(was " << _header.number << ")"; |
|||
throw std::invalid_argument( error.str() ); |
|||
} |
|||
|
|||
if (!m_lights.count(_header.seedHash())) |
|||
{ |
|||
ethash_params p = params((unsigned)_header.number); |
|||
m_lights[_header.seedHash()] = ethash_new_light(&p, _header.seedHash().data()); |
|||
} |
|||
return m_lights[_header.seedHash()]; |
|||
} |
|||
|
|||
#define IGNORE_EXCEPTIONS(X) try { X; } catch (...) {} |
|||
|
|||
bytesConstRef Ethasher::full(BlockInfo const& _header) |
|||
{ |
|||
RecursiveGuard l(x_this); |
|||
if (!m_fulls.count(_header.seedHash())) |
|||
{ |
|||
// @memoryleak @bug place it on a pile for deletion - perhaps use shared_ptr.
|
|||
/* if (!m_fulls.empty())
|
|||
{ |
|||
delete [] m_fulls.begin()->second.data(); |
|||
m_fulls.erase(m_fulls.begin()); |
|||
}*/ |
|||
|
|||
try { |
|||
boost::filesystem::create_directories(getDataDir("ethash")); |
|||
} catch (...) {} |
|||
|
|||
auto info = rlpList(c_ethashRevision, _header.seedHash()); |
|||
std::string oldMemoFile = getDataDir("ethash") + "/full"; |
|||
std::string memoFile = getDataDir("ethash") + "/full-R" + toString(c_ethashRevision) + "-" + toHex(_header.seedHash().ref().cropped(0, 8)); |
|||
if (boost::filesystem::exists(oldMemoFile) && contents(oldMemoFile + ".info") == info) |
|||
{ |
|||
// memofile valid - rename.
|
|||
boost::filesystem::rename(oldMemoFile, memoFile); |
|||
} |
|||
|
|||
IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile)); |
|||
IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile + ".info")); |
|||
|
|||
m_fulls[_header.seedHash()] = contentsNew(memoFile); |
|||
if (!m_fulls[_header.seedHash()]) |
|||
{ |
|||
ethash_params p = params((unsigned)_header.number); |
|||
m_fulls[_header.seedHash()] = bytesRef(new byte[p.full_size], p.full_size); |
|||
auto c = light(_header); |
|||
ethash_prep_full(m_fulls[_header.seedHash()].data(), &p, c); |
|||
writeFile(memoFile, m_fulls[_header.seedHash()]); |
|||
} |
|||
} |
|||
return m_fulls[_header.seedHash()]; |
|||
} |
|||
|
|||
ethash_params Ethasher::params(BlockInfo const& _header) |
|||
{ |
|||
return params((unsigned)_header.number); |
|||
} |
|||
|
|||
void Ethasher::readFull(BlockInfo const& _header, void* _dest) |
|||
{ |
|||
if (!m_fulls.count(_header.seedHash())) |
|||
{ |
|||
// @memoryleak @bug place it on a pile for deletion - perhaps use shared_ptr.
|
|||
/* if (!m_fulls.empty())
|
|||
{ |
|||
delete [] m_fulls.begin()->second.data(); |
|||
m_fulls.erase(m_fulls.begin()); |
|||
}*/ |
|||
|
|||
try { |
|||
boost::filesystem::create_directories(getDataDir("ethash")); |
|||
} catch (...) {} |
|||
|
|||
auto info = rlpList(c_ethashRevision, _header.seedHash()); |
|||
std::string oldMemoFile = getDataDir("ethash") + "/full"; |
|||
std::string memoFile = getDataDir("ethash") + "/full-R" + toString(c_ethashRevision) + "-" + toHex(_header.seedHash().ref().cropped(0, 8)); |
|||
if (boost::filesystem::exists(oldMemoFile) && contents(oldMemoFile + ".info") == info) |
|||
{ |
|||
// memofile valid - rename.
|
|||
boost::filesystem::rename(oldMemoFile, memoFile); |
|||
} |
|||
|
|||
IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile)); |
|||
IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile + ".info")); |
|||
|
|||
ethash_params p = params((unsigned)_header.number); |
|||
bytesRef r = contentsNew(memoFile, bytesRef((byte*)_dest, p.full_size)); |
|||
if (!r) |
|||
{ |
|||
auto c = light(_header); |
|||
ethash_prep_full(_dest, &p, c); |
|||
writeFile(memoFile, bytesConstRef((byte*)_dest, p.full_size)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ethash_params Ethasher::params(unsigned _n) |
|||
{ |
|||
ethash_params p; |
|||
p.cache_size = ethash_get_cachesize(_n); |
|||
p.full_size = ethash_get_datasize(_n); |
|||
return p; |
|||
} |
|||
|
|||
bool Ethasher::verify(BlockInfo const& _header) |
|||
{ |
|||
if (_header.number >= c_ethashEpochLength * 2048) |
|||
return false; |
|||
|
|||
h256 boundary = u256((bigint(1) << 256) / _header.difficulty); |
|||
|
|||
bool quick = ethash_quick_check_difficulty( |
|||
_header.headerHash(WithoutNonce).data(), |
|||
(uint64_t)(u64)_header.nonce, |
|||
_header.mixHash.data(), |
|||
boundary.data()); |
|||
|
|||
#if !ETH_DEBUG |
|||
if (!quick) |
|||
return false; |
|||
#endif |
|||
|
|||
auto result = eval(_header); |
|||
bool slow = result.value <= boundary && result.mixHash == _header.mixHash; |
|||
|
|||
#if ETH_DEBUG |
|||
if (!quick && slow) |
|||
{ |
|||
cwarn << "WARNING: evaluated result gives true whereas ethash_quick_check_difficulty gives false."; |
|||
cwarn << "headerHash:" << _header.headerHash(WithoutNonce); |
|||
cwarn << "nonce:" << _header.nonce; |
|||
cwarn << "mixHash:" << _header.mixHash; |
|||
cwarn << "difficulty:" << _header.difficulty; |
|||
cwarn << "boundary:" << boundary; |
|||
cwarn << "result.value:" << result.value; |
|||
cwarn << "result.mixHash:" << result.mixHash; |
|||
} |
|||
#endif |
|||
|
|||
return slow; |
|||
} |
|||
|
|||
Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce) |
|||
{ |
|||
auto p = Ethasher::params(_header); |
|||
ethash_return_value r; |
|||
if (Ethasher::get()->m_fulls.count(_header.seedHash())) |
|||
ethash_compute_full(&r, Ethasher::get()->full(_header).data(), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); |
|||
else |
|||
ethash_compute_light(&r, Ethasher::get()->light(_header), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); |
|||
// cdebug << "Ethasher::eval sha3(cache):" << sha3(Ethasher::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer);
|
|||
return Result{h256(r.result, h256::ConstructFromPointer), h256(r.mix_hash, h256::ConstructFromPointer)}; |
|||
} |
@ -1,109 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Ethasher.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
* |
|||
* ProofOfWork algorithm. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include <thread> |
|||
#include <cstdint> |
|||
#include <libdevcore/Guards.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethash/ethash.h> // TODO: REMOVE once everything merged into this class and an opaque API can be provided. |
|||
static const unsigned c_ethashRevision = ETHASH_REVISION; |
|||
static const unsigned c_ethashEpochLength = ETHASH_EPOCH_LENGTH; |
|||
#include "Common.h" |
|||
#include "BlockInfo.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class Ethasher |
|||
{ |
|||
public: |
|||
Ethasher() {} |
|||
~Ethasher(); |
|||
|
|||
static Ethasher* get() { if (!s_this) s_this = new Ethasher(); return s_this; } |
|||
|
|||
using LightType = void const*; |
|||
using FullType = void const*; |
|||
|
|||
LightType light(BlockInfo const& _header); |
|||
bytesConstRef full(BlockInfo const& _header); |
|||
static ethash_params params(BlockInfo const& _header); |
|||
static ethash_params params(unsigned _n); |
|||
|
|||
void readFull(BlockInfo const& _header, void* _dest); |
|||
|
|||
struct Result |
|||
{ |
|||
h256 value; |
|||
h256 mixHash; |
|||
}; |
|||
|
|||
static Result eval(BlockInfo const& _header) { return eval(_header, _header.nonce); } |
|||
static Result eval(BlockInfo const& _header, Nonce const& _nonce); |
|||
static bool verify(BlockInfo const& _header); |
|||
|
|||
class Miner |
|||
{ |
|||
public: |
|||
Miner(BlockInfo const& _header): |
|||
m_headerHash(_header.headerHash(WithoutNonce)), |
|||
m_params(Ethasher::params(_header)), |
|||
m_datasetPointer(Ethasher::get()->full(_header).data()) |
|||
{} |
|||
|
|||
inline h256 mine(uint64_t _nonce) |
|||
{ |
|||
ethash_compute_full(&m_ethashReturn, m_datasetPointer, &m_params, m_headerHash.data(), _nonce); |
|||
// cdebug << "Ethasher::mine hh:" << m_headerHash << "nonce:" << (Nonce)(u64)_nonce << " => " << h256(m_ethashReturn.result, h256::ConstructFromPointer);
|
|||
return h256(m_ethashReturn.result, h256::ConstructFromPointer); |
|||
} |
|||
|
|||
inline h256 lastMixHash() const |
|||
{ |
|||
return h256(m_ethashReturn.mix_hash, h256::ConstructFromPointer); |
|||
} |
|||
|
|||
private: |
|||
ethash_return_value m_ethashReturn; |
|||
h256 m_headerHash; |
|||
ethash_params m_params; |
|||
void const* m_datasetPointer; |
|||
}; |
|||
|
|||
private: |
|||
void killCache(h256 const& _s); |
|||
|
|||
static Ethasher* s_this; |
|||
RecursiveMutex x_this; |
|||
std::map<h256, LightType> m_lights; |
|||
std::map<h256, bytesRef> m_fulls; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,169 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Miner.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <thread> |
|||
#include <list> |
|||
#include <atomic> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Worker.h> |
|||
#include <libethcore/Common.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Describes the progress of a mining operation. |
|||
*/ |
|||
struct MiningProgress |
|||
{ |
|||
// MiningProgress& operator+=(MiningProgress const& _mp) { hashes += _mp.hashes; ms = std::max(ms, _mp.ms); return *this; }
|
|||
uint64_t hashes = 0; ///< Total number of hashes computed.
|
|||
uint64_t ms = 0; ///< Total number of milliseconds of mining thus far.
|
|||
}; |
|||
|
|||
struct MineInfo: public MiningProgress {}; |
|||
|
|||
inline std::ostream& operator<<(std::ostream& _out, MiningProgress _p) |
|||
{ |
|||
_out << (_p.hashes * 1000 / _p.ms) << "H/s = " << _p.hashes << " hashes / " << (double(_p.ms) / 1000) << "s"; |
|||
return _out; |
|||
} |
|||
|
|||
template <class PoW> class GenericMiner; |
|||
|
|||
/**
|
|||
* @brief Class for hosting one or more Miners. |
|||
* @warning Must be implemented in a threadsafe manner since it will be called from multiple |
|||
* miner threads. |
|||
*/ |
|||
template <class PoW> class GenericFarmFace |
|||
{ |
|||
public: |
|||
using WorkPackage = typename PoW::WorkPackage; |
|||
using Solution = typename PoW::Solution; |
|||
using Miner = GenericMiner<PoW>; |
|||
|
|||
/**
|
|||
* @brief Called from a Miner to note a WorkPackage has a solution. |
|||
* @param _p The solution. |
|||
* @param _wp The WorkPackage that the Solution is for; this will be reset if the work is accepted. |
|||
* @param _finder The miner that found it. |
|||
* @return true iff the solution was good (implying that mining should be . |
|||
*/ |
|||
virtual bool submitProof(Solution const& _p, Miner* _finder) = 0; |
|||
}; |
|||
|
|||
/**
|
|||
* @brief A miner - a member and adoptee of the Farm. |
|||
* @warning Not threadsafe. It is assumed Farm will synchronise calls to/from this class. |
|||
*/ |
|||
template <class PoW> class GenericMiner |
|||
{ |
|||
public: |
|||
using WorkPackage = typename PoW::WorkPackage; |
|||
using Solution = typename PoW::Solution; |
|||
using FarmFace = GenericFarmFace<PoW>; |
|||
using ConstructionInfo = std::pair<FarmFace*, unsigned>; |
|||
|
|||
GenericMiner(ConstructionInfo const& _ci): |
|||
m_farm(_ci.first), |
|||
m_index(_ci.second) |
|||
{} |
|||
|
|||
// API FOR THE FARM TO CALL IN WITH
|
|||
|
|||
void setWork(WorkPackage const& _work = WorkPackage()) |
|||
{ |
|||
auto old = m_work; |
|||
{ |
|||
Guard l(x_work); |
|||
m_work = _work; |
|||
} |
|||
if (!!_work) |
|||
{ |
|||
pause(); |
|||
kickOff(); |
|||
} |
|||
else if (!_work && !!old) |
|||
pause(); |
|||
m_hashCount = 0; |
|||
} |
|||
|
|||
uint64_t hashCount() { return m_hashCount; } |
|||
|
|||
unsigned index() const { return m_index; } |
|||
|
|||
protected: |
|||
|
|||
// REQUIRED TO BE REIMPLEMENTED BY A SUBCLASS:
|
|||
|
|||
/**
|
|||
* @brief Begin working on a given work package, discarding any previous work. |
|||
* @param _work The package for which to find a solution. |
|||
*/ |
|||
virtual void kickOff() = 0; |
|||
|
|||
/**
|
|||
* @brief No work left to be done. Pause until told to kickOff(). |
|||
*/ |
|||
virtual void pause() = 0; |
|||
|
|||
// AVAILABLE FOR A SUBCLASS TO CALL:
|
|||
|
|||
/**
|
|||
* @brief Notes that the Miner found a solution. |
|||
* @param _s The solution. |
|||
* @return true if the solution was correct and that the miner should pause. |
|||
*/ |
|||
bool submitProof(Solution const& _s) |
|||
{ |
|||
if (!m_farm) |
|||
return true; |
|||
if (m_farm->submitProof(_s, this)) |
|||
{ |
|||
Guard l(x_work); |
|||
m_work.reset(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
WorkPackage const& work() const { Guard l(x_work); return m_work; } |
|||
|
|||
void accumulateHashes(unsigned _n) { m_hashCount += _n; } |
|||
|
|||
private: |
|||
FarmFace* m_farm = nullptr; |
|||
unsigned m_index; |
|||
|
|||
uint64_t m_hashCount = 0; |
|||
|
|||
WorkPackage m_work; |
|||
mutable Mutex x_work; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,194 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Farm.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <thread> |
|||
#include <list> |
|||
#include <atomic> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Worker.h> |
|||
#include <libethcore/Common.h> |
|||
#include <libethcore/Miner.h> |
|||
#include <libethcore/BlockInfo.h> |
|||
#include <libethcore/ProofOfWork.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
/**
|
|||
* @brief A collective of Miners. |
|||
* Miners ask for work, then submit proofs |
|||
* @threadsafe |
|||
*/ |
|||
template <class PoW> |
|||
class GenericFarm: public GenericFarmFace<PoW> |
|||
{ |
|||
public: |
|||
using WorkPackage = typename PoW::WorkPackage; |
|||
using Solution = typename PoW::Solution; |
|||
using Miner = GenericMiner<PoW>; |
|||
|
|||
~GenericFarm() |
|||
{ |
|||
stop(); |
|||
} |
|||
|
|||
/**
|
|||
* @brief Sets the current mining mission. |
|||
* @param _bi The block (header) we wish to be mining. |
|||
*/ |
|||
void setWork(BlockInfo const& _bi) |
|||
{ |
|||
WriteGuard l(x_minerWork); |
|||
m_header = _bi; |
|||
auto p = PoW::package(m_header); |
|||
if (p.headerHash == m_work.headerHash) |
|||
return; |
|||
m_work = p; |
|||
for (auto const& m: m_miners) |
|||
m->setWork(m_work); |
|||
resetTimer(); |
|||
} |
|||
|
|||
/**
|
|||
* @brief (Re)start miners for CPU only. |
|||
* @returns true if started properly. |
|||
*/ |
|||
bool startCPU() { return start<typename PoW::CPUMiner>(); } |
|||
|
|||
/**
|
|||
* @brief (Re)start miners for GPU only. |
|||
* @returns true if started properly. |
|||
*/ |
|||
bool startGPU() { return start<typename PoW::GPUMiner>(); } |
|||
|
|||
/**
|
|||
* @brief Stop all mining activities. |
|||
*/ |
|||
void stop() |
|||
{ |
|||
WriteGuard l(x_minerWork); |
|||
m_miners.clear(); |
|||
m_work.reset(); |
|||
m_isMining = false; |
|||
} |
|||
|
|||
bool isMining() const |
|||
{ |
|||
return m_isMining; |
|||
} |
|||
|
|||
/**
|
|||
* @brief Get information on the progress of mining this work package. |
|||
* @return The progress with mining so far. |
|||
*/ |
|||
MiningProgress const& miningProgress() const |
|||
{ |
|||
MiningProgress p; |
|||
p.ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_lastStart).count(); |
|||
{ |
|||
ReadGuard l2(x_minerWork); |
|||
for (auto const& i: m_miners) |
|||
p.hashes += i->hashCount(); |
|||
} |
|||
ReadGuard l(x_progress); |
|||
m_progress = p; |
|||
return m_progress; |
|||
} |
|||
|
|||
using SolutionFound = std::function<bool(Solution const&)>; |
|||
|
|||
/**
|
|||
* @brief Provides a valid header based upon that received previously with setWork(). |
|||
* @param _bi The now-valid header. |
|||
* @return true if the header was good and that the Farm should pause until more work is submitted. |
|||
*/ |
|||
void onSolutionFound(SolutionFound const& _handler) { m_onSolutionFound = _handler; } |
|||
|
|||
WorkPackage work() const { ReadGuard l(x_minerWork); return m_work; } |
|||
|
|||
private: |
|||
/**
|
|||
* @brief Called from a Miner to note a WorkPackage has a solution. |
|||
* @param _p The solution. |
|||
* @param _wp The WorkPackage that the Solution is for. |
|||
* @return true iff the solution was good (implying that mining should be . |
|||
*/ |
|||
bool submitProof(Solution const& _s, Miner* _m) override |
|||
{ |
|||
if (m_onSolutionFound && m_onSolutionFound(_s)) |
|||
{ |
|||
WriteGuard ul(x_minerWork); |
|||
for (std::shared_ptr<Miner> const& m: m_miners) |
|||
if (m.get() != _m) |
|||
m->setWork(); |
|||
m_work.reset(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/**
|
|||
* @brief Start a number of miners. |
|||
*/ |
|||
template <class MinerType> |
|||
bool start() |
|||
{ |
|||
WriteGuard l(x_minerWork); |
|||
if (!m_miners.empty() && !!std::dynamic_pointer_cast<MinerType>(m_miners[0])) |
|||
return true; |
|||
m_miners.clear(); |
|||
m_miners.reserve(MinerType::instances()); |
|||
for (unsigned i = 0; i < MinerType::instances(); ++i) |
|||
{ |
|||
m_miners.push_back(std::shared_ptr<Miner>(new MinerType(std::make_pair(this, i)))); |
|||
m_miners.back()->setWork(m_work); |
|||
} |
|||
m_isMining = true; |
|||
resetTimer(); |
|||
return true; |
|||
} |
|||
|
|||
void resetTimer() |
|||
{ |
|||
m_lastStart = std::chrono::steady_clock::now(); |
|||
} |
|||
|
|||
mutable SharedMutex x_minerWork; |
|||
std::vector<std::shared_ptr<Miner>> m_miners; |
|||
WorkPackage m_work; |
|||
BlockInfo m_header; |
|||
|
|||
std::atomic<bool> m_isMining = {false}; |
|||
|
|||
mutable SharedMutex x_progress; |
|||
mutable MiningProgress m_progress; |
|||
std::chrono::steady_clock::time_point m_lastStart; |
|||
|
|||
SolutionFound m_onSolutionFound; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -1,96 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Miner.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @author Giacomo Tazzari |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "Miner.h" |
|||
|
|||
#include <libdevcore/CommonIO.h> |
|||
#include "State.h" |
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
Miner::~Miner() {} |
|||
|
|||
LocalMiner::LocalMiner(MinerHost* _host, unsigned _id): |
|||
AsyncMiner(_host, _id), |
|||
Worker("miner-" + toString(_id)) |
|||
{ |
|||
m_pow.reset(_host->turbo() ? new Ethash : (Ethash*)new EthashCPU); |
|||
} |
|||
|
|||
void LocalMiner::setup(MinerHost* _host, unsigned _id) |
|||
{ |
|||
AsyncMiner::setup(_host, _id); |
|||
setName("miner-" + toString(m_id)); |
|||
m_pow.reset(_host->turbo() ? new Ethash : (Ethash*)new EthashCPU); |
|||
} |
|||
|
|||
void LocalMiner::doWork() |
|||
{ |
|||
// Do some mining.
|
|||
if (m_miningStatus != Waiting && m_miningStatus != Mined) |
|||
{ |
|||
if (m_miningStatus == Preparing) |
|||
{ |
|||
m_host->setupState(m_mineState); |
|||
if (m_host->force() || m_mineState.pending().size()) |
|||
m_miningStatus = Mining; |
|||
else |
|||
m_miningStatus = Waiting; |
|||
|
|||
{ |
|||
Guard l(x_mineInfo); |
|||
m_mineProgress.best = (double)-1; |
|||
m_mineProgress.hashes = 0; |
|||
m_mineProgress.ms = 0; |
|||
} |
|||
} |
|||
|
|||
if (m_miningStatus == Mining) |
|||
{ |
|||
// Mine for a while.
|
|||
MineInfo mineInfo = m_mineState.mine(m_pow.get()); |
|||
|
|||
{ |
|||
Guard l(x_mineInfo); |
|||
m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); |
|||
m_mineProgress.current = mineInfo.best; |
|||
m_mineProgress.requirement = mineInfo.requirement; |
|||
m_mineProgress.ms += 100; |
|||
m_mineProgress.hashes += mineInfo.hashes; |
|||
m_mineHistory.push_back(mineInfo); |
|||
} |
|||
if (mineInfo.completed) |
|||
{ |
|||
m_mineState.completeMine(); |
|||
m_host->onComplete(); |
|||
m_miningStatus = Mined; |
|||
} |
|||
else |
|||
m_host->onProgressed(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this_thread::sleep_for(chrono::milliseconds(100)); |
|||
} |
|||
} |
@ -1,178 +0,0 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Miner.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <thread> |
|||
#include <list> |
|||
#include <atomic> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Worker.h> |
|||
#include <libethcore/Common.h> |
|||
#include "State.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace eth |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Describes the progress of a mining operation. |
|||
*/ |
|||
struct MineProgress |
|||
{ |
|||
void combine(MineProgress const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); current = std::max(current, _m.current); hashes += _m.hashes; ms = std::max(ms, _m.ms); } |
|||
double requirement = 0; ///< The PoW requirement - as the second logarithm of the minimum acceptable hash.
|
|||
double best = 1e99; ///< The PoW achievement - as the second logarithm of the minimum found hash.
|
|||
double current = 0; ///< The most recent PoW achievement - as the second logarithm of the presently found hash.
|
|||
unsigned hashes = 0; ///< Total number of hashes computed.
|
|||
unsigned ms = 0; ///< Total number of milliseconds of mining thus far.
|
|||
}; |
|||
|
|||
/**
|
|||
* @brief Class for hosting one or more Miners. |
|||
* @warning Must be implemented in a threadsafe manner since it will be called from multiple |
|||
* miner threads. |
|||
*/ |
|||
class MinerHost |
|||
{ |
|||
public: |
|||
virtual void setupState(State& _s) = 0; ///< Reset the given State object to the one that should be being mined.
|
|||
virtual void onProgressed() {} ///< Called once some progress has been made.
|
|||
virtual void onComplete() {} ///< Called once a block is found.
|
|||
virtual bool force() const = 0; ///< @returns true iff the Miner should mine regardless of the number of transactions.
|
|||
virtual bool turbo() const = 0; ///< @returns true iff the Miner should use GPU if possible.
|
|||
}; |
|||
|
|||
class Miner |
|||
{ |
|||
public: |
|||
virtual ~Miner(); |
|||
|
|||
virtual void noteStateChange() = 0; |
|||
virtual bool isComplete() const = 0; |
|||
virtual bytes const& blockData() const = 0; |
|||
}; |
|||
|
|||
class AsyncMiner: public Miner |
|||
{ |
|||
public: |
|||
/// Null constructor.
|
|||
AsyncMiner(): m_host(nullptr) {} |
|||
|
|||
/// Constructor.
|
|||
AsyncMiner(MinerHost* _host, unsigned _id = 0): m_host(_host), m_id(_id) {} |
|||
|
|||
/// Setup its basics.
|
|||
void setup(MinerHost* _host, unsigned _id = 0) { m_host = _host; m_id = _id; } |
|||
|
|||
/// Start mining.
|
|||
virtual void start() {} |
|||
|
|||
/// Stop mining.
|
|||
virtual void stop() {} |
|||
|
|||
/// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force().
|
|||
virtual bool isRunning() const { return false; } |
|||
|
|||
protected: |
|||
MinerHost* m_host = nullptr; ///< Our host.
|
|||
unsigned m_id = 0; ///< Our unique id.
|
|||
}; |
|||
|
|||
/**
|
|||
* @brief Implements Miner. |
|||
* To begin mining, use start() & stop(). noteStateChange() can be used to reset the mining and set up the |
|||
* State object according to the host. Use isRunning() to determine if the miner has been start()ed. |
|||
* Use isComplete() to determine if the miner has finished mining. |
|||
* |
|||
* blockData() can be used to retrieve the complete block, ready for insertion into the BlockChain. |
|||
* |
|||
* Information on the mining can be queried through miningProgress() and miningHistory(). |
|||
* @threadsafe |
|||
* @todo Signal Miner to restart once with condition variables. |
|||
*/ |
|||
class LocalMiner: public AsyncMiner, Worker |
|||
{ |
|||
public: |
|||
/// Null constructor.
|
|||
LocalMiner() {} |
|||
|
|||
/// Constructor.
|
|||
LocalMiner(MinerHost* _host, unsigned _id = 0); |
|||
|
|||
/// Move-constructor.
|
|||
LocalMiner(LocalMiner&& _m): Worker((Worker&&)_m) { std::swap(m_host, _m.m_host); std::swap(m_pow, _m.m_pow); } |
|||
|
|||
/// Move-assignment.
|
|||
LocalMiner& operator=(LocalMiner&& _m) { Worker::operator=((Worker&&)_m); std::swap(m_host, _m.m_host); std::swap(m_pow, _m.m_pow); return *this; } |
|||
|
|||
/// Destructor. Stops miner.
|
|||
~LocalMiner() { stop(); } |
|||
|
|||
/// Setup its basics.
|
|||
void setup(MinerHost* _host, unsigned _id = 0); |
|||
|
|||
/// Start mining.
|
|||
void start() { startWorking(); } |
|||
|
|||
/// Stop mining.
|
|||
void stop() { stopWorking(); } |
|||
|
|||
/// Call to notify Miner of a state change.
|
|||
virtual void noteStateChange() override { m_miningStatus = Preparing; } |
|||
|
|||
/// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force().
|
|||
bool isRunning() const override { return isWorking(); } |
|||
|
|||
/// @returns true if mining is complete.
|
|||
virtual bool isComplete() const override { return m_miningStatus == Mined; } |
|||
|
|||
/// @returns the internal State object.
|
|||
virtual bytes const& blockData() const override { return m_mineState.blockData(); } |
|||
|
|||
/// Check the progress of the mining.
|
|||
MineProgress miningProgress() const { Guard l(x_mineInfo); return m_mineProgress; } |
|||
|
|||
/// Get and clear the mining history.
|
|||
std::list<MineInfo> miningHistory() { Guard l(x_mineInfo); auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } |
|||
|
|||
/// @returns the state on which we mined.
|
|||
State const& state() const { return m_mineState; } |
|||
|
|||
private: |
|||
/// Do some work on the mining.
|
|||
virtual void doWork(); |
|||
|
|||
enum MiningStatus { Waiting, Preparing, Mining, Mined, Stopping, Stopped }; |
|||
MiningStatus m_miningStatus = Waiting; ///< TODO: consider mutex/atomic variable.
|
|||
State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine.
|
|||
std::unique_ptr<EthashPoW> m_pow; ///< Our miner.
|
|||
|
|||
mutable Mutex x_mineInfo; ///< Lock for the mining progress & history.
|
|||
MineProgress m_mineProgress; ///< What's our progress?
|
|||
std::list<MineInfo> m_mineHistory; ///< What the history of our mining?
|
|||
}; |
|||
|
|||
} |
|||
} |
Loading…
Reference in new issue