Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: ProgPoW v0.9.2 support #111

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
8 changes: 4 additions & 4 deletions ethash.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ func freeCache(cache *cache) {
cache.ptr = nil
}

func (cache *cache) compute(dagSize uint64, hash common.Hash, nonce uint64) (ok bool, mixDigest, result common.Hash) {
ret := C.ethash_light_compute_internal(cache.ptr, C.uint64_t(dagSize), hashToH256(hash), C.uint64_t(nonce))
func (cache *cache) compute(dagSize uint64, hash common.Hash, nonce uint64, block_number uint64) (ok bool, mixDigest, result common.Hash) {
ret := C.progpow_light_compute_internal(cache.ptr, C.uint64_t(dagSize), hashToH256(hash), C.uint64_t(nonce), C.uint64_t(block_number))
// Make sure cache is live until after the C call.
// This is important because a GC might happen and execute
// the finalizer before the call completes.
Expand All @@ -130,7 +130,7 @@ func (l *Light) Verify(block Block) bool {
// to prevent DOS attacks.
blockNum := block.NumberU64()
if blockNum >= epochLength*2048 {
log.Debug(fmt.Sprintf("block number %d too high, limit is %d", epochLength*2048))
log.Debug(fmt.Sprintf("block number %d too high, limit is %d", blockNum, epochLength*2048))
return false
}

Expand All @@ -151,7 +151,7 @@ func (l *Light) Verify(block Block) bool {
dagSize = dagSizeForTesting
}
// Recompute the hash using the cache.
ok, mixDigest, result := cache.compute(uint64(dagSize), block.HashNoNonce(), block.Nonce())
ok, mixDigest, result := cache.compute(uint64(dagSize), block.HashNoNonce(), block.Nonce(), blockNum)
if !ok {
return false
}
Expand Down
1 change: 1 addition & 0 deletions ethashc.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ package ethash
#cgo LDFLAGS: -lm

#include "src/libethash/internal.c"
#include "src/libethash/progpow-internal.c"
#include "src/libethash/sha3.c"
#include "src/libethash/io.c"

Expand Down
1 change: 1 addition & 0 deletions src/libethash/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ endif()
set(FILES util.h
io.c
internal.c
progpow-internal.c
ethash.h
endian.h
compiler.h
Expand Down
40 changes: 40 additions & 0 deletions src/libethash/ethash.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,17 @@
#define ETHASH_DAG_MAGIC_NUM_SIZE 8
#define ETHASH_DAG_MAGIC_NUM 0xFEE1DEADBADDCAFE

#define PROGPOW_MIX_BYTES 256

#ifdef __cplusplus
extern "C" {
#endif

typedef struct
{
uint32_t uint32s[32 / sizeof(uint32_t)];
} hash32_t;

/// Type of a seedhash/blockhash e.t.c.
typedef struct ethash_h256 { uint8_t b[32]; } ethash_h256_t;

Expand Down Expand Up @@ -128,6 +135,39 @@ ethash_return_value_t ethash_full_compute(
ethash_h256_t const header_hash,
uint64_t nonce
);

/**
* Calculate the light client data of the ProgPow
*
* @param light The light client handler
* @param header_hash The header hash to pack into the mix
* @param nonce The nonce to pack into the mix
* @param block_number The block_number
* @return an object of ethash_return_value_t holding the return values
*/
ethash_return_value_t progpow_light_compute(
ethash_light_t light,
ethash_h256_t const header_hash,
uint64_t nonce,
uint64_t block_number
);

/**
* Calculate the full client data of the ProgPoW
*
* @param full The full client handler
* @param header_hash The header hash to pack into the mix
* @param nonce The nonce to pack into the mix
* @param block_number The current block_number
* @return An object of ethash_return_value to hold the return value
*/
ethash_return_value_t progpow_full_compute(
ethash_full_t full,
ethash_h256_t const header_hash,
uint64_t nonce,
uint64_t block_number
);

/**
* Get a pointer to the full DAG data
*/
Expand Down
96 changes: 81 additions & 15 deletions src/libethash/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ uint64_t ethash_get_cachesize(uint64_t const block_number)
// Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014)
// https://bitslog.files.wordpress.com/2013/12/memohash-v0-3.pdf
// SeqMemoHash(s, R, N)
bool static ethash_compute_cache_nodes(
static bool ethash_compute_cache_nodes(
node* const nodes,
uint64_t cache_size,
ethash_h256_t const* seed
Expand Down Expand Up @@ -108,6 +108,9 @@ void ethash_calculate_dag_item(
__m128i xmm1 = ret->xmm[1];
__m128i xmm2 = ret->xmm[2];
__m128i xmm3 = ret->xmm[3];
#elif defined(__MIC__)
__m512i const fnv_prime = _mm512_set1_epi32(FNV_PRIME);
__m512i zmm0 = ret->zmm[0];
#endif

for (uint32_t i = 0; i != ETHASH_DATASET_PARENTS; ++i) {
Expand All @@ -131,6 +134,14 @@ void ethash_calculate_dag_item(
ret->xmm[2] = xmm2;
ret->xmm[3] = xmm3;
}
#elif defined(__MIC__)
{
zmm0 = _mm512_mullo_epi32(zmm0, fnv_prime);

// have to write to ret as values are used to compute index
zmm0 = _mm512_xor_si512(zmm0, parent->zmm[0]);
ret->zmm[0] = zmm0;
}
#else
{
for (unsigned w = 0; w != NODE_WORDS; ++w) {
Expand Down Expand Up @@ -207,10 +218,10 @@ static bool ethash_hash(

for (unsigned n = 0; n != MIX_NODES; ++n) {
node const* dag_node;
node tmp_node;
if (full_nodes) {
dag_node = &full_nodes[MIX_NODES * index + n];
} else {
node tmp_node;
ethash_calculate_dag_item(&tmp_node, index * MIX_NODES + n, light);
dag_node = &tmp_node;
}
Expand All @@ -227,6 +238,14 @@ static bool ethash_hash(
mix[n].xmm[2] = _mm_xor_si128(xmm2, dag_node->xmm[2]);
mix[n].xmm[3] = _mm_xor_si128(xmm3, dag_node->xmm[3]);
}
#elif defined(__MIC__)
{
// __m512i implementation via union
// Each vector register (zmm) can store sixteen 32-bit integer numbers
__m512i fnv_prime = _mm512_set1_epi32(FNV_PRIME);
__m512i zmm0 = _mm512_mullo_epi32(fnv_prime, mix[n].zmm[0]);
mix[n].zmm[0] = _mm512_xor_si512(zmm0, dag_node->zmm[0]);
}
#else
{
for (unsigned w = 0; w != NODE_WORDS; ++w) {
Expand All @@ -238,6 +257,22 @@ static bool ethash_hash(

}

// Workaround for a GCC regression which causes a bogus -Warray-bounds warning.
// The regression was introduced in GCC 4.8.4, fixed in GCC 5.0.0 and backported to GCC 4.9.3 but
// never to the GCC 4.8.x line.
//
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273
//
// This regression is affecting Debian Jesse (8.5) builds of cpp-ethereum (GCC 4.9.2) and also
// manifests in the doublethinkco armel v5 cross-builds, which use crosstool-ng and resulting
// in the use of GCC 4.8.4. The Tizen runtime wants an even older GLIBC version - the one from
// GCC 4.6.0!

#if defined(__GNUC__) && (__GNUC__ < 5)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif // define (__GNUC__)

// compress mix
for (uint32_t w = 0; w != MIX_WORDS; w += 4) {
uint32_t reduction = mix->words[w + 0];
Expand All @@ -247,6 +282,10 @@ static bool ethash_hash(
mix->words[w / 4] = reduction;
}

#if defined(__GNUC__) && (__GNUC__ < 5)
#pragma GCC diagnostic pop
#endif // define (__GNUC__)

fix_endian_arr32(mix->words, MIX_WORDS / 4);
memcpy(&ret->mix_hash, mix->bytes, 32);
// final Keccak hash
Expand Down Expand Up @@ -300,7 +339,11 @@ ethash_light_t ethash_light_new_internal(uint64_t cache_size, ethash_h256_t cons
if (!ret) {
return NULL;
}
#if defined(__MIC__)
ret->cache = _mm_malloc((size_t)cache_size, 64);
#else
ret->cache = malloc((size_t)cache_size);
#endif
if (!ret->cache) {
goto fail_free_light;
}
Expand All @@ -312,7 +355,11 @@ ethash_light_t ethash_light_new_internal(uint64_t cache_size, ethash_h256_t cons
return ret;

fail_free_cache_mem:
#if defined(__MIC__)
_mm_free(ret->cache);
#else
free(ret->cache);
#endif
fail_free_light:
free(ret);
return NULL;
Expand Down Expand Up @@ -399,31 +446,47 @@ ethash_full_t ethash_full_new_internal(
return NULL;
}
ret->file_size = (size_t)full_size;
switch (ethash_io_prepare(dirname, seed_hash, &f, (size_t)full_size, false)) {
case ETHASH_IO_FAIL:
// ethash_io_prepare will do all ETHASH_CRITICAL() logging in fail case

enum ethash_io_rc err = ethash_io_prepare(dirname, seed_hash, &f, (size_t)full_size, false);
if (err == ETHASH_IO_FAIL)
goto fail_free_full;
case ETHASH_IO_MEMO_MATCH:
if (!ethash_mmap(ret, f)) {
ETHASH_CRITICAL("mmap failure()");
goto fail_close_file;
}
return ret;
case ETHASH_IO_MEMO_SIZE_MISMATCH:

if (err == ETHASH_IO_MEMO_SIZE_MISMATCH) {
// if a DAG of same filename but unexpected size is found, silently force new file creation
if (ethash_io_prepare(dirname, seed_hash, &f, (size_t)full_size, true) != ETHASH_IO_MEMO_MISMATCH) {
ETHASH_CRITICAL("Could not recreate DAG file after finding existing DAG with unexpected size.");
goto fail_free_full;
}
// fallthrough to the mismatch case here, DO NOT go through match
case ETHASH_IO_MEMO_MISMATCH:
// we now need to go through the mismatch case, NOT the match case
err = ETHASH_IO_MEMO_MISMATCH;
}

if (err == ETHASH_IO_MEMO_MISMATCH || err == ETHASH_IO_MEMO_MATCH) {
if (!ethash_mmap(ret, f)) {
ETHASH_CRITICAL("mmap failure()");
goto fail_close_file;
}
break;

if (err == ETHASH_IO_MEMO_MATCH) {
#if defined(__MIC__)
node* tmp_nodes = _mm_malloc((size_t)full_size, 64);
//copy all nodes from ret->data
//mmapped_nodes are not aligned properly
uint32_t const countnodes = (uint32_t) ((size_t)ret->file_size / sizeof(node));
//fprintf(stderr,"ethash_full_new_internal:countnodes:%d",countnodes);
for (uint32_t i = 1; i != countnodes; ++i) {
tmp_nodes[i] = ret->data[i];
}
ret->data = tmp_nodes;
#endif
return ret;
}
}


#if defined(__MIC__)
ret->data = _mm_malloc((size_t)full_size, 64);
#endif
if (!ethash_compute_full_data(ret->data, full_size, light, callback)) {
ETHASH_CRITICAL("Failure at computing DAG data.");
goto fail_free_full_data;
Expand All @@ -448,6 +511,9 @@ ethash_full_t ethash_full_new_internal(
fail_free_full_data:
// could check that munmap(..) == 0 but even if it did not can't really do anything here
munmap(ret->data, (size_t)full_size);
#if defined(__MIC__)
_mm_free(ret->data);
#endif
fail_close_file:
fclose(ret->file);
fail_free_full:
Expand Down
28 changes: 28 additions & 0 deletions src/libethash/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#if defined(_M_X64) && ENABLE_SSE
#include <smmintrin.h>
#elif defined(__MIC__)
#include <immintrin.h>
#endif

#ifdef __cplusplus
Expand All @@ -27,6 +29,8 @@ typedef union node {

#if defined(_M_X64) && ENABLE_SSE
__m128i xmm[NODE_WORDS/4];
#elif defined(__MIC__)
__m512i zmm[NODE_WORDS/16];
#endif

} node;
Expand Down Expand Up @@ -102,6 +106,7 @@ ethash_light_t ethash_light_new_internal(uint64_t cache_size, ethash_h256_t cons
* @param full_size The size of the full data in bytes.
* @param header_hash The header hash to pack into the mix
* @param nonce The nonce to pack into the mix
* @param block_number The block_number
* @return The resulting hash.
*/
ethash_return_value_t ethash_light_compute_internal(
Expand All @@ -111,6 +116,29 @@ ethash_return_value_t ethash_light_compute_internal(
uint64_t nonce
);

void keccak_f800_round(uint32_t st[25], const int r);
hash32_t keccak_f800_progpow(hash32_t header, uint64_t seed, hash32_t digest);
uint32_t progpowMath(uint32_t a, uint32_t b, uint32_t r);
void merge(uint32_t *a, uint32_t b, uint32_t r);

/**
* Calculate the light client data of the ProgPow. Internal version.
*
* @param light The light client handler
* @param full_size The size of the full data in bytes.
* @param header_hash The header hash to pack into the mix
* @param nonce The nonce to pack into the mix
* @param block_number The block_number
* @return The resulting hash.
*/
ethash_return_value_t progpow_light_compute_internal(
ethash_light_t light,
uint64_t full_size,
ethash_h256_t const header_hash,
uint64_t nonce,
uint64_t block_number
);

struct ethash_full {
FILE* file;
uint64_t file_size;
Expand Down
2 changes: 1 addition & 1 deletion src/libethash/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ enum ethash_io_rc ethash_io_prepare(
goto free_memo;
}
// make sure it's of the proper size
if (fseek(f, (long int)(file_size + ETHASH_DAG_MAGIC_NUM_SIZE - 1), SEEK_SET) != 0) {
if (ethash_fseek(f, file_size + ETHASH_DAG_MAGIC_NUM_SIZE - 1, SEEK_SET) != 0) {
fclose(f);
ETHASH_CRITICAL("Could not seek to the end of DAG file: \"%s\". Insufficient space?", tmpfile);
goto free_memo;
Expand Down
10 changes: 10 additions & 0 deletions src/libethash/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ enum ethash_io_rc ethash_io_prepare(
*/
FILE* ethash_fopen(char const* file_name, char const* mode);

/**
* An fseek wrapper for crossplatform 64-bit seek.
*
* @param f The file stream whose fd to get
* @param offset Number of bytes from @a origin
* @param origin Initial position
* @return Current offset or -1 to indicate an error
*/
int ethash_fseek(FILE* f, size_t offset, int origin);

/**
* An strncat wrapper for no-warnings crossplatform strncat.
*
Expand Down
7 changes: 7 additions & 0 deletions src/libethash/io_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ FILE* ethash_fopen(char const* file_name, char const* mode)
return fopen(file_name, mode);
}

int ethash_fseek(FILE* f, size_t offset, int origin)
{
return fseeko(f, offset, origin);
}

char* ethash_strncat(char* dest, size_t dest_size, char const* src, size_t count)
{
return strlen(dest) + count + 1 <= dest_size ? strncat(dest, src, count) : NULL;
Expand Down Expand Up @@ -96,6 +101,8 @@ bool ethash_get_default_dirname(char* strbuf, size_t buffsize)
struct passwd* pwd = getpwuid(getuid());
if (pwd)
home_dir = pwd->pw_dir;
if (!home_dir)
return false;
}

size_t len = strlen(home_dir);
Expand Down
Loading