diff --git a/CMakeLists.txt b/CMakeLists.txt index 4875dcf77..a65621c3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ set(FILES util.h data_sizes.h) if (MSVC) - list(APPEND FILES util.c io_win32.c mmap_win32.c) + list(APPEND FILES util_win32.c io_win32.c mmap_win32.c) else() list(APPEND FILES io_posix.c) endif() diff --git a/ethash.h b/ethash.h index 2acda0e31..0c6a1f9e9 100644 --- a/ethash.h +++ b/ethash.h @@ -37,35 +37,15 @@ #define ETHASH_DATASET_PARENTS 256 #define ETHASH_CACHE_ROUNDS 3 #define ETHASH_ACCESSES 64 +#define ETHASH_DAG_MAGIC_NUM_SIZE 8 +#define ETHASH_DAG_MAGIC_NUM 0xFEE1DEADBADDCAFE #ifdef __cplusplus extern "C" { #endif -// LTODO: for consistency's sake maybe use ethash_params_t? -typedef struct ethash_params { - /// Size of full data set (in bytes, multiple of mix size (128)). - uint64_t full_size; - /// Size of compute cache (in bytes, multiple of node size (64)). - uint64_t cache_size; -} ethash_params; - /// Type of a seedhash/blockhash e.t.c. typedef struct ethash_h256 { uint8_t b[32]; } ethash_h256_t; -static inline uint8_t ethash_h256_get(ethash_h256_t const* hash, unsigned int i) -{ - return hash->b[i]; -} - -static inline void ethash_h256_set(ethash_h256_t* hash, unsigned int i, uint8_t v) -{ - hash->b[i] = v; -} - -static inline void ethash_h256_reset(ethash_h256_t* hash) -{ - memset(hash, 0, 32); -} // convenience macro to statically initialize an h256_t // usage: @@ -81,55 +61,20 @@ struct ethash_full; typedef struct ethash_full* ethash_full_t; typedef int(*ethash_callback_t)(unsigned); -// LTODO: for consistency's sake maybe use ethash_return_value_t? typedef struct ethash_return_value { ethash_h256_t result; ethash_h256_t mix_hash; -} ethash_return_value; - -uint64_t ethash_get_datasize(uint32_t const block_number); -uint64_t ethash_get_cachesize(uint32_t const block_number); - -// initialize the parameters -static inline void ethash_params_init(ethash_params* params, uint32_t const block_number) -{ - params->full_size = ethash_get_datasize(block_number); - params->cache_size = ethash_get_cachesize(block_number); -} - -// LTODO: for consistency's sake maybe use ethash_cache_t? -typedef struct ethash_cache { - void* mem; -} ethash_cache; - -/** - * Allocate and initialize a new ethash_cache object - * - * @param params The parameters to initialize it with. We are interested in - * the cache_size from here - * @param seed Block seedhash to be used during the computation of the - * cache nodes - * @return Newly allocated ethash_cache on success or NULL in case of - * ERRNOMEM or invalid parameters used for @ref ethash_compute_cache_nodes() - */ -ethash_cache* ethash_cache_new(ethash_params const* params, ethash_h256_t const* seed); -/** - * Frees a previously allocated ethash_cache - * @param c The object to free - */ -void ethash_cache_delete(ethash_cache* c); + bool success; +} ethash_return_value_t; /** * Allocate and initialize a new ethash_light handler * - * @param params The parameters to initialize it with. We are interested in - * the cache_size from here - * @param seed Block seedhash to be used during the computation of the - * cache nodes - * @return Newly allocated ethash_light handler or NULL in case of - * ERRNOMEM or invalid parameters used for @ref ethash_compute_cache_nodes() + * @param block_number The block number for which to create the handler + * @return Newly allocated ethash_light handler or NULL in case of + * ERRNOMEM or invalid parameters used for @ref ethash_compute_cache_nodes() */ -ethash_light_t ethash_light_new(ethash_params const* params, ethash_h256_t const* seed); +ethash_light_t ethash_light_new(uint64_t block_number); /** * Frees a previously allocated ethash_light handler * @param light The light handler to free @@ -138,63 +83,33 @@ void ethash_light_delete(ethash_light_t light); /** * Calculate the light client data * - * @param ret An object of ethash_return_value to hold the return value * @param light The light client handler - * @param params The parameters to use * @param header_hash The header hash to pack into the mix * @param nonce The nonce to pack into the mix - * @return true if all went well and false if there were invalid - * parameters given. + * @return an object of ethash_return_value_t holding the return values */ -bool ethash_light_compute( - ethash_return_value* ret, +ethash_return_value_t ethash_light_compute( ethash_light_t light, - ethash_params const* params, - const ethash_h256_t* header_hash, - uint64_t const nonce + ethash_h256_t const header_hash, + uint64_t nonce ); -/** - * Get a pointer to the cache object held by the light client - * - * @param light The light client whose cache to request - * @return A pointer to the cache held by the light client or NULL if - * there was no cache in the first place - */ -ethash_cache* ethash_light_get_cache(ethash_light_t light); -/** - * Move the memory ownership of the cache somewhere else - * - * @param light The light client whose cache's memory ownership to acquire. - * After this function concludes it will no longer have a cache. - * @return A pointer to the moved cache or NULL if there was no cache in the first place - */ -ethash_cache* ethash_light_acquire_cache(ethash_light_t light); /** * Allocate and initialize a new ethash_full handler * - * @param dirname The directory in which to put the DAG file. - * @param seedhash The seed hash of the block. Used in the DAG file naming. - * @param params The parameters to initialize it with. We are interested in - * the full_size from here - * @param cache A cache object to use that was allocated with @ref ethash_cache_new(). - * Iff this function succeeds the ethash_full_t will take memory - * ownership of the cache and free it at deletion. If not then the user - * still has to handle freeing of the cache himself. - * @param callback A callback function with signature of @ref ethash_callback_t - * It accepts an unsigned with which a progress of DAG calculation - * can be displayed. If all goes well the callback should return 0. - * If a non-zero value is returned then DAG generation will stop. - * @return Newly allocated ethash_full handler or NULL in case of - * ERRNOMEM or invalid parameters used for @ref ethash_compute_full_data() + * @param light The light handler containing the cache. + * @param callback A callback function with signature of @ref ethash_callback_t + * It accepts an unsigned with which a progress of DAG calculation + * can be displayed. If all goes well the callback should return 0. + * If a non-zero value is returned then DAG generation will stop. + * Be advised. A progress value of 100 means that DAG creation is + * almost complete and that this function will soon return succesfully. + * It does not mean that the function has already had a succesfull return. + * @return Newly allocated ethash_full handler or NULL in case of + * ERRNOMEM or invalid parameters used for @ref ethash_compute_full_data() */ -ethash_full_t ethash_full_new( - char const* dirname, - ethash_h256_t const* seed_hash, - ethash_params const* params, - ethash_cache const* cache, - ethash_callback_t callback -); +ethash_full_t ethash_full_new(ethash_light_t light, ethash_callback_t callback); + /** * Frees a previously allocated ethash_full handler * @param full The light handler to free @@ -203,97 +118,29 @@ void ethash_full_delete(ethash_full_t full); /** * Calculate the full client data * - * @param ret An object of ethash_return_value to hold the return value * @param full The full client handler - * @param params The parameters to use * @param header_hash The header hash to pack into the mix * @param nonce The nonce to pack into the mix - * @return true if all went well and false if there were invalid - * parameters given or if there was a callback given and - * at some point return a non-zero value + * @return An object of ethash_return_value to hold the return value */ -bool ethash_full_compute( - ethash_return_value* ret, +ethash_return_value_t ethash_full_compute( ethash_full_t full, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce + ethash_h256_t const header_hash, + uint64_t nonce ); /** - * Get a pointer to the cache object held by the full client - * - * @param full The full client whose cache to request - * @return A pointer to the cache held by the full client or NULL - * if there was no cache in the first place + * Get a pointer to the full DAG data */ -ethash_cache* ethash_full_get_cache(ethash_full_t full); +void const* ethash_full_dag(ethash_full_t full); /** - * Move the memory ownership of the cache somewhere else - * - * @param full The full client whose cache's memory ownership to acquire. - * After this function concludes it will no longer have a cache. - * @return A pointer to the moved cache or NULL if there was no cache in the first place + * Get the size of the DAG data */ -ethash_cache* ethash_full_acquire_cache(ethash_full_t full); - -void ethash_get_seedhash(ethash_h256_t *seedhash, const uint32_t block_number); - -// Returns if hash is less than or equal to difficulty -static inline int ethash_check_difficulty( - ethash_h256_t const* hash, - ethash_h256_t const* difficulty -) -{ - // Difficulty is big endian - for (int i = 0; i < 32; i++) { - if (ethash_h256_get(hash, i) == ethash_h256_get(difficulty, i)) { - continue; - } - return ethash_h256_get(hash, i) < ethash_h256_get(difficulty, i); - } - return 1; -} - -int ethash_quick_check_difficulty( - ethash_h256_t const* header_hash, - uint64_t const nonce, - ethash_h256_t const* mix_hash, - ethash_h256_t const* difficulty -); - +uint64_t ethash_full_dag_size(ethash_full_t full); /** - * ========================= - * = DEPRECATED API = - * ========================= - * - * Kept for backwards compatibility with whoever still uses it. Please consider - * switching to the new API (look above) - */ -void ethash_mkcache(ethash_cache* cache, ethash_params const* params, ethash_h256_t const* seed); -void ethash_full( - ethash_return_value* ret, - void const* full_mem, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce -); -void ethash_light( - ethash_return_value* ret, - ethash_cache const* cache, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce -); -/** - * Compute the memory data for a full node's memory - * - * @param mem A pointer to an ethash full's memory - * @param params The parameters to compute the data with - * @param cache A cache object to use in the calculation - * @return true if all went fine and false for invalid parameters + * Calculate the seedhash for a given block number */ -bool ethash_compute_full_data(void* mem, ethash_params const* params, ethash_cache const* cache); +ethash_h256_t ethash_get_seedhash(uint64_t block_number); #ifdef __cplusplus } diff --git a/internal.c b/internal.c index 02967913c..607e44138 100644 --- a/internal.c +++ b/internal.c @@ -41,13 +41,13 @@ #include "sha3.h" #endif // WITH_CRYPTOPP -uint64_t ethash_get_datasize(uint32_t const block_number) +uint64_t ethash_get_datasize(uint64_t const block_number) { assert(block_number / ETHASH_EPOCH_LENGTH < 2048); return dag_sizes[block_number / ETHASH_EPOCH_LENGTH]; } -uint64_t ethash_get_cachesize(uint32_t const block_number) +uint64_t ethash_get_cachesize(uint64_t const block_number) { assert(block_number / ETHASH_EPOCH_LENGTH < 2048); return cache_sizes[block_number / ETHASH_EPOCH_LENGTH]; @@ -58,27 +58,27 @@ uint64_t ethash_get_cachesize(uint32_t const block_number) // SeqMemoHash(s, R, N) bool static ethash_compute_cache_nodes( node* const nodes, - ethash_params const* params, + uint64_t cache_size, ethash_h256_t const* seed ) { - if (params->cache_size % sizeof(node) != 0) { + if (cache_size % sizeof(node) != 0) { return false; } - uint32_t const num_nodes = (uint32_t) (params->cache_size / sizeof(node)); + uint32_t const num_nodes = (uint32_t) (cache_size / sizeof(node)); SHA3_512(nodes[0].bytes, (uint8_t*)seed, 32); - for (unsigned i = 1; i != num_nodes; ++i) { + for (uint32_t i = 1; i != num_nodes; ++i) { SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64); } - for (unsigned j = 0; j != ETHASH_CACHE_ROUNDS; j++) { - for (unsigned i = 0; i != num_nodes; i++) { + for (uint32_t j = 0; j != ETHASH_CACHE_ROUNDS; j++) { + for (uint32_t i = 0; i != num_nodes; i++) { uint32_t const idx = nodes[i].words[0] % num_nodes; node data; data = nodes[(num_nodes - 1 + i) % num_nodes]; - for (unsigned w = 0; w != NODE_WORDS; ++w) { + for (uint32_t w = 0; w != NODE_WORDS; ++w) { data.words[w] ^= nodes[idx].words[w]; } SHA3_512(nodes[i].bytes, data.bytes, sizeof(data)); @@ -90,46 +90,14 @@ bool static ethash_compute_cache_nodes( return true; } -ethash_cache* ethash_cache_new(ethash_params const* params, ethash_h256_t const* seed) -{ - ethash_cache* ret; - ret = malloc(sizeof(*ret)); - if (!ret) { - return NULL; - } - ret->mem = malloc((size_t)params->cache_size); - if (!ret->mem) { - goto fail_free_cache; - } - - node* nodes = (node*)ret->mem; - if (!ethash_compute_cache_nodes(nodes, params, seed)) { - goto fail_free_cache_mem; - } - return ret; - -fail_free_cache_mem: - free(ret->mem); -fail_free_cache: - free(ret); - return NULL; -} - -void ethash_cache_delete(ethash_cache* c) -{ - free(c->mem); - free(c); -} - void ethash_calculate_dag_item( node* const ret, - const unsigned node_index, - const struct ethash_params *params, - const struct ethash_cache *cache + uint32_t node_index, + ethash_light_t const light ) { - uint32_t num_parent_nodes = (uint32_t) (params->cache_size / sizeof(node)); - node const* cache_nodes = (node const *) cache->mem; + uint32_t num_parent_nodes = (uint32_t) (light->cache_size / sizeof(node)); + node const* cache_nodes = (node const *) light->cache; node const* init = &cache_nodes[node_index % num_parent_nodes]; memcpy(ret, init, sizeof(node)); ret->words[0] ^= node_index; @@ -142,8 +110,8 @@ void ethash_calculate_dag_item( __m128i xmm3 = ret->xmm[3]; #endif - for (unsigned i = 0; i != ETHASH_DATASET_PARENTS; ++i) { - uint32_t parent_index = ((node_index ^ i) * FNV_PRIME ^ ret->words[i % NODE_WORDS]) % num_parent_nodes; + for (uint32_t i = 0; i != ETHASH_DATASET_PARENTS; ++i) { + uint32_t parent_index = fnv_hash(node_index ^ i, ret->words[i % NODE_WORDS]) % num_parent_nodes; node const *parent = &cache_nodes[parent_index]; #if defined(_M_X64) && ENABLE_SSE @@ -176,40 +144,50 @@ void ethash_calculate_dag_item( bool ethash_compute_full_data( void* mem, - ethash_params const* params, - ethash_cache const* cache + uint64_t full_size, + ethash_light_t const light, + ethash_callback_t callback ) { - if (params->full_size % (sizeof(uint32_t) * MIX_WORDS) != 0 || - (params->full_size % sizeof(node)) != 0) { + if (full_size % (sizeof(uint32_t) * MIX_WORDS) != 0 || + (full_size % sizeof(node)) != 0) { return false; } + uint32_t const max_n = (uint32_t)(full_size / sizeof(node)); node* full_nodes = mem; + double const progress_change = 1.0f / max_n; + double progress = 0.0f; // now compute full nodes - for (unsigned n = 0; n != (params->full_size / sizeof(node)); ++n) { - ethash_calculate_dag_item(&(full_nodes[n]), n, params, cache); + for (uint32_t n = 0; n != max_n; ++n) { + if (callback && + n % (max_n / 100) == 0 && + callback((unsigned int)(ceil(progress * 100.0f))) != 0) { + + return false; + } + progress += progress_change; + ethash_calculate_dag_item(&(full_nodes[n]), n, light); } return true; } static bool ethash_hash( - ethash_return_value* ret, + ethash_return_value_t* ret, node const* full_nodes, - ethash_cache const* cache, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce, - ethash_callback_t callback + ethash_light_t const light, + uint64_t full_size, + ethash_h256_t const header_hash, + uint64_t const nonce ) { - if (params->full_size % MIX_WORDS != 0) { + if (full_size % MIX_WORDS != 0) { return false; } // pack hash and nonce together into first 40 bytes of s_mix assert(sizeof(node) * 8 == 512); node s_mix[MIX_NODES + 1]; - memcpy(s_mix[0].bytes, header_hash, 32); + memcpy(s_mix[0].bytes, &header_hash, 32); fix_endian64(s_mix[0].double_words[4], nonce); // compute sha3-512 hash and replicate across mix @@ -217,30 +195,23 @@ static bool ethash_hash( fix_endian_arr32(s_mix[0].words, 16); node* const mix = s_mix + 1; - for (unsigned w = 0; w != MIX_WORDS; ++w) { + for (uint32_t w = 0; w != MIX_WORDS; ++w) { mix->words[w] = s_mix[0].words[w % NODE_WORDS]; } unsigned const page_size = sizeof(uint32_t) * MIX_WORDS; - unsigned const num_full_pages = (unsigned) (params->full_size / page_size); + unsigned const num_full_pages = (unsigned) (full_size / page_size); - double const progress_change = 1.0f / ETHASH_ACCESSES / MIX_NODES; - double progress = 0.0f; for (unsigned i = 0; i != ETHASH_ACCESSES; ++i) { - uint32_t const index = ((s_mix->words[0] ^ i) * FNV_PRIME ^ mix->words[i % MIX_WORDS]) % num_full_pages; + uint32_t const index = fnv_hash(s_mix->words[0] ^ i, mix->words[i % MIX_WORDS]) % num_full_pages; for (unsigned n = 0; n != MIX_NODES; ++n) { node const* dag_node; - if (callback && - callback((unsigned int)(ceil(progress * 100.0f))) != 0) { - return false; - } - progress += progress_change; 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, params, cache); + ethash_calculate_dag_item(&tmp_node, index * MIX_NODES + n, light); dag_node = &tmp_node; } @@ -268,7 +239,7 @@ static bool ethash_hash( } // compress mix - for (unsigned w = 0; w != MIX_WORDS; w += 4) { + for (uint32_t w = 0; w != MIX_WORDS; w += 4) { uint32_t reduction = mix->words[w + 0]; reduction = reduction * FNV_PRIME ^ mix->words[w + 1]; reduction = reduction * FNV_PRIME ^ mix->words[w + 2]; @@ -299,12 +270,14 @@ void ethash_quick_hash( SHA3_256(return_hash, buf, 64 + 32); } -void ethash_get_seedhash(ethash_h256_t* seedhash, const uint32_t block_number) +ethash_h256_t ethash_get_seedhash(uint64_t block_number) { - ethash_h256_reset(seedhash); - const uint32_t epochs = block_number / ETHASH_EPOCH_LENGTH; + ethash_h256_t ret; + ethash_h256_reset(&ret); + uint64_t const epochs = block_number / ETHASH_EPOCH_LENGTH; for (uint32_t i = 0; i < epochs; ++i) - SHA3_256(seedhash, (uint8_t*)seedhash, 32); + SHA3_256(&ret, (uint8_t*)&ret, 32); + return ret; } int ethash_quick_check_difficulty( @@ -320,111 +293,150 @@ int ethash_quick_check_difficulty( return ethash_check_difficulty(&return_hash, difficulty); } -ethash_light_t ethash_light_new(ethash_params const* params, ethash_h256_t const* seed) +ethash_light_t ethash_light_new_internal(uint64_t cache_size, ethash_h256_t const* seed) { struct ethash_light *ret; ret = calloc(sizeof(*ret), 1); if (!ret) { return NULL; } - ret->cache = ethash_cache_new(params, seed); + ret->cache = malloc((size_t)cache_size); if (!ret->cache) { goto fail_free_light; } + node* nodes = (node*)ret->cache; + if (!ethash_compute_cache_nodes(nodes, cache_size, seed)) { + goto fail_free_cache_mem; + } + ret->cache_size = cache_size; return ret; +fail_free_cache_mem: + free(ret->cache); fail_free_light: free(ret); return NULL; } +ethash_light_t ethash_light_new(uint64_t block_number) +{ + ethash_h256_t seedhash = ethash_get_seedhash(block_number); + ethash_light_t ret; + ret = ethash_light_new_internal(ethash_get_cachesize(block_number), &seedhash); + ret->block_number = block_number; + return ret; +} + void ethash_light_delete(ethash_light_t light) { if (light->cache) { - ethash_cache_delete(light->cache); + free(light->cache); } free(light); } -bool ethash_light_compute( - ethash_return_value* ret, +ethash_return_value_t ethash_light_compute_internal( ethash_light_t light, - ethash_params const* params, - const ethash_h256_t* header_hash, - uint64_t const nonce + uint64_t full_size, + ethash_h256_t const header_hash, + uint64_t nonce ) { - return ethash_hash(ret, NULL, light->cache, params, header_hash, nonce, NULL); + ethash_return_value_t ret; + ret.success = true; + if (!ethash_hash(&ret, NULL, light, full_size, header_hash, nonce)) { + ret.success = false; + } + return ret; } -ethash_cache *ethash_light_get_cache(ethash_light_t light) +ethash_return_value_t ethash_light_compute( + ethash_light_t light, + ethash_h256_t const header_hash, + uint64_t nonce +) { - return light->cache; + uint64_t full_size = ethash_get_datasize(light->block_number); + return ethash_light_compute_internal(light, full_size, header_hash, nonce); } -ethash_cache *ethash_light_acquire_cache(ethash_light_t light) +static bool ethash_mmap(struct ethash_full* ret, FILE* f) { - ethash_cache* ret = light->cache; - light->cache = 0; - return ret; + int fd; + char* mmapped_data; + ret->file = f; + if ((fd = ethash_fileno(ret->file)) == -1) { + return false; + } + mmapped_data= mmap( + NULL, + (size_t)ret->file_size + ETHASH_DAG_MAGIC_NUM_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + 0 + ); + if (mmapped_data == MAP_FAILED) { + return false; + } + ret->data = (node*)(mmapped_data + ETHASH_DAG_MAGIC_NUM_SIZE); + return true; } -ethash_full_t ethash_full_new( +ethash_full_t ethash_full_new_internal( char const* dirname, - ethash_h256_t const* seed_hash, - ethash_params const* params, - ethash_cache const* cache, + ethash_h256_t const seed_hash, + uint64_t full_size, + ethash_light_t const light, ethash_callback_t callback ) { struct ethash_full* ret; - int fd; FILE *f = NULL; - bool match = false; ret = calloc(sizeof(*ret), 1); if (!ret) { return NULL; } - - ret->cache = (ethash_cache*)cache; - ret->file_size = (size_t)params->full_size; - switch (ethash_io_prepare(dirname, *seed_hash, &f, (size_t)params->full_size)) { + ret->file_size = (size_t)full_size; + switch (ethash_io_prepare(dirname, seed_hash, &f, (size_t)full_size, false)) { case ETHASH_IO_FAIL: - case ETHASH_IO_MEMO_SIZE_MISMATCH: goto fail_free_full; case ETHASH_IO_MEMO_MATCH: - match = true; - case ETHASH_IO_MEMO_MISMATCH: - ret->file = f; - if ((fd = ethash_fileno(ret->file)) == -1) { + if (!ethash_mmap(ret, f)) { + goto fail_close_file; + } + return ret; + case 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) { goto fail_free_full; } - ret->data = mmap( - NULL, - (size_t)params->full_size, - PROT_READ | PROT_WRITE, - MAP_SHARED, - fd, - 0 - ); - if (ret->data == MAP_FAILED) { + // fallthrough to the mismatch case here, DO NOT go through match + case ETHASH_IO_MEMO_MISMATCH: + if (!ethash_mmap(ret, f)) { goto fail_close_file; } - if (match) { - return ret; - } break; } - if (!ethash_compute_full_data(ret->data, params, cache)) { + if (!ethash_compute_full_data(ret->data, full_size, light, callback)) { + goto fail_free_full_data; + } + + // after the DAG has been filled then we finalize it by writting the magic number at the beginning + if (fseek(f, 0, SEEK_SET) != 0) { goto fail_free_full_data; } - ret->callback = callback; + uint64_t const magic_num = ETHASH_DAG_MAGIC_NUM; + if (fwrite(&magic_num, ETHASH_DAG_MAGIC_NUM_SIZE, 1, f) != 1) { + goto fail_free_full_data; + } + fflush(f); // make sure the magic number IS there return ret; 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)params->full_size); + munmap(ret->data, (size_t)full_size); fail_close_file: fclose(ret->file); fail_free_full: @@ -432,82 +444,53 @@ fail_free_full: return NULL; } -void ethash_full_delete(ethash_full_t full) +ethash_full_t ethash_full_new(ethash_light_t light, ethash_callback_t callback) { - if (full->cache) { - ethash_cache_delete(full->cache); + char strbuf[256]; + if (!ethash_get_default_dirname(strbuf, 256)) { + return NULL; } + uint64_t full_size = ethash_get_datasize(light->block_number); + ethash_h256_t seedhash = ethash_get_seedhash(light->block_number); + return ethash_full_new_internal(strbuf, seedhash, full_size, light, callback); +} + +void ethash_full_delete(ethash_full_t full) +{ // could check that munmap(..) == 0 but even if it did not can't really do anything here - munmap(full->data, full->file_size); + munmap(full->data, (size_t)full->file_size); if (full->file) { fclose(full->file); } free(full); } -bool ethash_full_compute( - ethash_return_value* ret, +ethash_return_value_t ethash_full_compute( ethash_full_t full, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce + ethash_h256_t const header_hash, + uint64_t nonce ) { - return ethash_hash(ret, - (node const*)full->data, - NULL, - params, - header_hash, - nonce, - full->callback); -} - -ethash_cache* ethash_full_get_cache(ethash_full_t full) -{ - return full->cache; -} - -ethash_cache* ethash_full_acquire_cache(ethash_full_t full) -{ - ethash_cache* ret = full->cache; - full->cache = 0; + ethash_return_value_t ret; + ret.success = true; + if (!ethash_hash( + &ret, + (node const*)full->data, + NULL, + full->file_size, + header_hash, + nonce)) { + ret.success = false; + } return ret; } -/** - * ========================= - * = DEPRECATED API = - * ========================= - * - * Kept for backwards compatibility with whoever still uses it. Please consider - * switching to the new API (look above) - */ -void ethash_mkcache( - ethash_cache* cache, - ethash_params const* params, - ethash_h256_t const* seed -) +void const* ethash_full_dag(ethash_full_t full) { - node* nodes = (node*) cache->mem; - ethash_compute_cache_nodes(nodes, params, seed); + return full->data; } -void ethash_full( - ethash_return_value* ret, - void const* full_mem, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce -) -{ - ethash_hash(ret, (node const *) full_mem, NULL, params, header_hash, nonce, NULL); -} -void ethash_light( - ethash_return_value* ret, - ethash_cache const* cache, - ethash_params const* params, - ethash_h256_t const* header_hash, - uint64_t const nonce -) + +uint64_t ethash_full_dag_size(ethash_full_t full) { - ethash_hash(ret, NULL, cache, params, header_hash, nonce, NULL); + return full->file_size; } diff --git a/internal.h b/internal.h index b492ae983..221ff290a 100644 --- a/internal.h +++ b/internal.h @@ -31,23 +31,112 @@ typedef union node { } node; +static inline uint8_t ethash_h256_get(ethash_h256_t const* hash, unsigned int i) +{ + return hash->b[i]; +} + +static inline void ethash_h256_set(ethash_h256_t* hash, unsigned int i, uint8_t v) +{ + hash->b[i] = v; +} + +static inline void ethash_h256_reset(ethash_h256_t* hash) +{ + memset(hash, 0, 32); +} + +// Returns if hash is less than or equal to difficulty +static inline int ethash_check_difficulty( + ethash_h256_t const* hash, + ethash_h256_t const* difficulty +) +{ + // Difficulty is big endian + for (int i = 0; i < 32; i++) { + if (ethash_h256_get(hash, i) == ethash_h256_get(difficulty, i)) { + continue; + } + return ethash_h256_get(hash, i) < ethash_h256_get(difficulty, i); + } + return 1; +} + +int ethash_quick_check_difficulty( + ethash_h256_t const* header_hash, + uint64_t const nonce, + ethash_h256_t const* mix_hash, + ethash_h256_t const* difficulty +); + struct ethash_light { - ethash_cache* cache; + void* cache; + uint64_t cache_size; + uint64_t block_number; }; +/** + * Allocate and initialize a new ethash_light handler. Internal version + * + * @param cache_size The size of the cache in bytes + * @param seed Block seedhash to be used during the computation of the + * cache nodes + * @return Newly allocated ethash_light handler or NULL in case of + * ERRNOMEM or invalid parameters used for @ref ethash_compute_cache_nodes() + */ +ethash_light_t ethash_light_new_internal(uint64_t cache_size, ethash_h256_t const* seed); + +/** + * Calculate the light client data. 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 + * @return The resulting hash. + */ +ethash_return_value_t ethash_light_compute_internal( + ethash_light_t light, + uint64_t full_size, + ethash_h256_t const header_hash, + uint64_t nonce +); + struct ethash_full { FILE* file; - size_t file_size; - ethash_cache* cache; + uint64_t file_size; node* data; - ethash_callback_t callback; }; +/** + * Allocate and initialize a new ethash_full handler. Internal version. + * + * @param dirname The directory in which to put the DAG file. + * @param seedhash The seed hash of the block. Used in the DAG file naming. + * @param full_size The size of the full data in bytes. + * @param cache A cache object to use that was allocated with @ref ethash_cache_new(). + * Iff this function succeeds the ethash_full_t will take memory + * memory ownership of the cache and free it at deletion. If + * not then the user still has to handle freeing of the cache himself. + * @param callback A callback function with signature of @ref ethash_callback_t + * It accepts an unsigned with which a progress of DAG calculation + * can be displayed. If all goes well the callback should return 0. + * If a non-zero value is returned then DAG generation will stop. + * @return Newly allocated ethash_full handler or NULL in case of + * ERRNOMEM or invalid parameters used for @ref ethash_compute_full_data() + */ +ethash_full_t ethash_full_new_internal( + char const* dirname, + ethash_h256_t const seed_hash, + uint64_t full_size, + ethash_light_t const light, + ethash_callback_t callback +); + void ethash_calculate_dag_item( node* const ret, - const unsigned node_index, - ethash_params const* params, - ethash_cache const* cache + uint32_t node_index, + ethash_light_t const cache ); void ethash_quick_hash( @@ -57,6 +146,25 @@ void ethash_quick_hash( ethash_h256_t const* mix_hash ); +uint64_t ethash_get_datasize(uint64_t const block_number); +uint64_t ethash_get_cachesize(uint64_t const block_number); + +/** + * Compute the memory data for a full node's memory + * + * @param mem A pointer to an ethash full's memory + * @param full_size The size of the full data in bytes + * @param cache A cache object to use in the calculation + * @param callback The callback function. Check @ref ethash_full_new() for details. + * @return true if all went fine and false for invalid parameters + */ +bool ethash_compute_full_data( + void* mem, + uint64_t full_size, + ethash_light_t const light, + ethash_callback_t callback +); + #ifdef __cplusplus } #endif diff --git a/io.c b/io.c index c39494826..5b4e7da2b 100644 --- a/io.c +++ b/io.c @@ -26,7 +26,8 @@ enum ethash_io_rc ethash_io_prepare( char const* dirname, ethash_h256_t const seedhash, FILE** output_file, - size_t file_size + uint64_t file_size, + bool force_create ) { char mutable_name[DAG_MUTABLE_NAME_MAX_SIZE]; @@ -43,35 +44,53 @@ enum ethash_io_rc ethash_io_prepare( goto end; } - // try to open the file - FILE* f = ethash_fopen(tmpfile, "rb+"); - if (f) { - size_t found_size; - if (!ethash_file_size(f, &found_size)) { - fclose(f); - goto free_memo; + FILE *f; + if (!force_create) { + // try to open the file + f = ethash_fopen(tmpfile, "rb+"); + if (f) { + size_t found_size; + if (!ethash_file_size(f, &found_size)) { + fclose(f); + goto free_memo; + } + if (file_size != found_size - ETHASH_DAG_MAGIC_NUM_SIZE) { + fclose(f); + ret = ETHASH_IO_MEMO_SIZE_MISMATCH; + goto free_memo; + } + // compare the magic number, no need to care about endianess since it's local + uint64_t magic_num; + if (fread(&magic_num, ETHASH_DAG_MAGIC_NUM_SIZE, 1, f) != 1) { + // I/O error + fclose(f); + ret = ETHASH_IO_MEMO_SIZE_MISMATCH; + goto free_memo; + } + if (magic_num != ETHASH_DAG_MAGIC_NUM) { + fclose(f); + ret = ETHASH_IO_MEMO_SIZE_MISMATCH; + goto free_memo; + } + ret = ETHASH_IO_MEMO_MATCH; + goto set_file; } - if (file_size != found_size) { - fclose(f); - ret = ETHASH_IO_MEMO_SIZE_MISMATCH; - goto free_memo; - } - } else { - // file does not exist, will need to be created - f = ethash_fopen(tmpfile, "wb+"); - if (!f) { - goto free_memo; - } - // make sure it's of the proper size - if (fseek(f, file_size - 1, SEEK_SET) != 0) { - fclose(f); - goto free_memo; - } - fputc('\n', f); - fflush(f); - ret = ETHASH_IO_MEMO_MISMATCH; - goto set_file; } + + // file does not exist, will need to be created + f = ethash_fopen(tmpfile, "wb+"); + if (!f) { + 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) { + fclose(f); + goto free_memo; + } + fputc('\n', f); + fflush(f); + ret = ETHASH_IO_MEMO_MISMATCH; + goto set_file; ret = ETHASH_IO_MEMO_MATCH; set_file: diff --git a/io.h b/io.h index 609c2769e..26c82f111 100644 --- a/io.h +++ b/io.h @@ -69,13 +69,16 @@ enum ethash_io_rc { * mode, while on the case of mismatch a new file is created * on write mode * @param[in] file_size The size that the DAG file should have on disk + * @param[out] force_create If true then there is no check to see if the file + * already exists * @return For possible return values @see enum ethash_io_rc */ enum ethash_io_rc ethash_io_prepare( char const* dirname, ethash_h256_t const seedhash, FILE** output_file, - size_t file_size + uint64_t file_size, + bool force_create ); /** @@ -112,7 +115,7 @@ char* ethash_strncat(char* dest, size_t dest_size, char const* src, size_t count /** * A cross-platform mkdir wrapper to create a directory or assert it's there - * + * * @param dirname The full path of the directory to create * @return true if the directory was created or if it already * existed @@ -130,12 +133,39 @@ bool ethash_file_size(FILE* f, size_t* ret_size); /** * Get a file descriptor number from a FILE stream - * + * * @param f The file stream whose fd to get * @return Platform specific fd handler */ int ethash_fileno(FILE* f); +/** + * Create the filename for the DAG. + * + * @param dirname The directory name in which the DAG file should reside + * If it does not end with a directory separator it is appended. + * @param filename The actual name of the file + * @param filename_length The length of the filename in bytes + * @return A char* containing the full name. User must deallocate. + */ +char* ethash_io_create_filename( + char const* dirname, + char const* filename, + size_t filename_length +); + +/** + * Gets the default directory name for the DAG depending on the system + * + * The spec defining this directory is here: https://github.com/ethereum/wiki/wiki/Ethash-DAG + * + * @param[out] strbuf A string buffer of sufficient size to keep the + * null termninated string of the directory name + * @param[in] buffsize Size of @a strbuf in bytes + * @return true for success and false otherwise + */ +bool ethash_get_default_dirname(char* strbuf, size_t buffsize); + static inline bool ethash_io_mutable_name( uint32_t revision, ethash_h256_t const* seed_hash, @@ -149,26 +179,6 @@ static inline bool ethash_io_mutable_name( return snprintf(output, DAG_MUTABLE_NAME_MAX_SIZE, "%u_%016" PRIx64, revision, hash) >= 0; } -static inline char* ethash_io_create_filename( - char const* dirname, - char const* filename, - size_t filename_length -) -{ - size_t dirlen = strlen(dirname); - // in C the cast is not needed, but a C++ compiler will complain for invalid conversion - char* name = (char*)malloc(dirlen + filename_length + 1); - if (!name) { - return NULL; - } - - name[0] = '\0'; - ethash_strncat(name, dirlen + filename_length + 1, dirname, dirlen); - ethash_strncat(name, dirlen + filename_length + 1, filename, filename_length); - return name; -} - - #ifdef __cplusplus } #endif diff --git a/io_posix.c b/io_posix.c index 5783ec272..7f03d5482 100644 --- a/io_posix.c +++ b/io_posix.c @@ -48,6 +48,31 @@ int ethash_fileno(FILE *f) return fileno(f); } +char* ethash_io_create_filename( + char const* dirname, + char const* filename, + size_t filename_length +) +{ + size_t dirlen = strlen(dirname); + size_t dest_size = dirlen + filename_length + 1; + if (dirname[dirlen] != '/') { + dest_size += 1; + } + char* name = malloc(dest_size); + if (!name) { + return NULL; + } + + name[0] = '\0'; + ethash_strncat(name, dest_size, dirname, dirlen); + if (dirname[dirlen] != '/') { + ethash_strncat(name, dest_size, "/", 1); + } + ethash_strncat(name, dest_size, filename, filename_length); + return name; +} + bool ethash_file_size(FILE* f, size_t* ret_size) { struct stat st; @@ -58,3 +83,20 @@ bool ethash_file_size(FILE* f, size_t* ret_size) *ret_size = st.st_size; return true; } + +bool ethash_get_default_dirname(char* strbuf, size_t buffsize) +{ + static const char dir_suffix[] = ".ethash/"; + strbuf[0] = '\0'; + char* home_dir = getenv("HOME"); + size_t len = strlen(home_dir); + if (!ethash_strncat(strbuf, buffsize, home_dir, len)) { + return false; + } + if (home_dir[len] != '/') { + if (!ethash_strncat(strbuf, buffsize, "/", 1)) { + return false; + } + } + return ethash_strncat(strbuf, buffsize, dir_suffix, sizeof(dir_suffix)); +} diff --git a/io_win32.c b/io_win32.c index 505f11e2b..d9c54d141 100644 --- a/io_win32.c +++ b/io_win32.c @@ -25,6 +25,7 @@ #include #include #include +#include FILE* ethash_fopen(char const* file_name, char const* mode) { @@ -48,6 +49,31 @@ int ethash_fileno(FILE* f) return _fileno(f); } +char* ethash_io_create_filename( + char const* dirname, + char const* filename, + size_t filename_length +) +{ + size_t dirlen = strlen(dirname); + size_t dest_size = dirlen + filename_length + 1; + if (dirname[dirlen] != '\\' || dirname[dirlen] != '/') { + dest_size += 1; + } + char* name = malloc(dest_size); + if (!name) { + return NULL; + } + + name[0] = '\0'; + ethash_strncat(name, dest_size, dirname, dirlen); + if (dirname[dirlen] != '\\' || dirname[dirlen] != '/') { + ethash_strncat(name, dest_size, "\\", 1); + } + ethash_strncat(name, dest_size, filename, filename_length); + return name; +} + bool ethash_file_size(FILE* f, size_t* ret_size) { struct _stat st; @@ -58,3 +84,17 @@ bool ethash_file_size(FILE* f, size_t* ret_size) *ret_size = st.st_size; return true; } + +bool ethash_get_default_dirname(char* strbuf, size_t buffsize) +{ + static const char dir_suffix[] = "Appdata\\Ethash\\"; + strbuf[0] = '\0'; + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, (WCHAR*)strbuf))) { + return false; + } + if (!ethash_strncat(strbuf, buffsize, "\\", 1)) { + return false; + } + + return ethash_strncat(strbuf, buffsize, dir_suffix, sizeof(dir_suffix)); +} diff --git a/util.c b/util_win32.c similarity index 100% rename from util.c rename to util_win32.c