diff --git a/bindings/python/ethash/__init__.py b/bindings/python/ethash/__init__.py index 14b653b9..3cb8b1f6 100644 --- a/bindings/python/ethash/__init__.py +++ b/bindings/python/ethash/__init__.py @@ -44,4 +44,5 @@ def verify(epoch_number, header_hash, mix_hash, nonce, boundary): c_boundary = ffi.new('union ethash_hash256*') c_boundary[0].str = boundary - return lib.ethash_verify(ctx, c_header_hash, c_mix_hash, nonce, c_boundary) + ec = lib.ethash_verify(ctx, c_header_hash, c_mix_hash, nonce, c_boundary) + return ec == 0 diff --git a/include/ethash/ethash.h b/include/ethash/ethash.h index 10f559f1..96ecc149 100644 --- a/include/ethash/ethash.h +++ b/include/ethash/ethash.h @@ -31,6 +31,15 @@ extern "C" { #define ETHASH_FULL_DATASET_ITEM_SIZE 128 #define ETHASH_NUM_DATASET_ACCESSES 64 +/** Ethash error codes. */ +enum ethash_errc +{ + ETHASH_SUCCESS = 0, + ETHASH_INVALID_FINAL_HASH = 1, + ETHASH_INVALID_MIX_HASH = 2 +}; +typedef enum ethash_errc ethash_errc; + struct ethash_epoch_context { @@ -106,11 +115,24 @@ void ethash_destroy_epoch_context_full(struct ethash_epoch_context_full* context struct ethash_result ethash_hash(const struct ethash_epoch_context* context, const union ethash_hash256* header_hash, uint64_t nonce) NOEXCEPT; -bool ethash_verify(const struct ethash_epoch_context* context, +/** + * Verify Ethash validity of a header hash. + * + * @return Error code: ::ETHASH_SUCCESS if valid, ::ETHASH_INVALID_FINAL_HASH if the final hash is + * not within provided boundary, ::ETHASH_INVALID_MIX_HASH if the provided mix hash + * mismatches the computed one. + */ +ethash_errc ethash_verify(const struct ethash_epoch_context* context, const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce, const union ethash_hash256* boundary) NOEXCEPT; -bool ethash_verify_final_hash(const union ethash_hash256* header_hash, +/** + * Verify only the final hash. This can be performed quickly without accessing Ethash context. + * + * @return Error code: ::ETHASH_SUCCESS if valid, ::ETHASH_INVALID_FINAL_HASH if the final hash is + * not within provided boundary. + */ +ethash_errc ethash_verify_final_hash(const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce, const union ethash_hash256* boundary) NOEXCEPT; diff --git a/include/ethash/ethash.hpp b/include/ethash/ethash.hpp index 9a5b908a..9cc22291 100644 --- a/include/ethash/ethash.hpp +++ b/include/ethash/ethash.hpp @@ -20,6 +20,18 @@ #include #include #include +#include + +namespace std +{ +/// Template specialization of std::is_error_code_enum for ethash_errc. +/// This enabled implicit conversions from evmc::hex_errc to std::error_code. +template <> +struct is_error_code_enum : true_type +{ +}; +} // namespace std + namespace ethash { @@ -128,13 +140,13 @@ inline result hash( result hash(const epoch_context_full& context, const hash256& header_hash, uint64_t nonce) noexcept; -inline bool verify_final_hash(const hash256& header_hash, const hash256& mix_hash, uint64_t nonce, - const hash256& boundary) noexcept +inline std::error_code verify_final_hash(const hash256& header_hash, const hash256& mix_hash, + uint64_t nonce, const hash256& boundary) noexcept { return ethash_verify_final_hash(&header_hash, &mix_hash, nonce, &boundary); } -inline bool verify(const epoch_context& context, const hash256& header_hash, +inline std::error_code verify(const epoch_context& context, const hash256& header_hash, const hash256& mix_hash, uint64_t nonce, const hash256& boundary) noexcept { return ethash_verify(&context, &header_hash, &mix_hash, nonce, &boundary); @@ -157,4 +169,40 @@ search_result search(const epoch_context_full& context, const hash256& header_ha /// @return The epoch number or -1 if not found. int find_epoch_number(const hash256& seed) noexcept; + +/// Obtains a reference to the static error category object for ethash errors. +inline const std::error_category& ethash_category() noexcept +{ + struct ethash_category_impl : std::error_category + { + const char* name() const noexcept final { return "ethash"; } + + std::string message(int ev) const final + { + switch (ev) + { + case ETHASH_SUCCESS: + return ""; + case ETHASH_INVALID_FINAL_HASH: + return "invalid final hash"; + case ETHASH_INVALID_MIX_HASH: + return "invalid mix hash"; + default: + return "unknown error"; + } + } + }; + + static ethash_category_impl category_instance; + return category_instance; +} } // namespace ethash + + +/// Creates error_code object out of an Ethash error code value. +/// This is used by std::error_code to implement implicit conversion ethash_errc -> std::error_code, +/// therefore the definition is in the global namespace to match the definition of ethash_errc. +inline std::error_code make_error_code(ethash_errc errc) noexcept +{ + return {errc, ethash::ethash_category()}; +} diff --git a/lib/ethash/ethash.cpp b/lib/ethash/ethash.cpp index c561fbf3..b30fefe9 100644 --- a/lib/ethash/ethash.cpp +++ b/lib/ethash/ethash.cpp @@ -415,22 +415,23 @@ ethash_result ethash_hash( return {hash_final(seed, mix_hash), mix_hash}; } -bool ethash_verify_final_hash(const hash256* header_hash, const hash256* mix_hash, uint64_t nonce, - const hash256* boundary) noexcept +ethash_errc ethash_verify_final_hash(const hash256* header_hash, const hash256* mix_hash, + uint64_t nonce, const hash256* boundary) noexcept { const hash512 seed = hash_seed(*header_hash, nonce); - return less_equal(hash_final(seed, *mix_hash), *boundary); + return less_equal(hash_final(seed, *mix_hash), *boundary) ? ETHASH_SUCCESS : + ETHASH_INVALID_FINAL_HASH; } -bool ethash_verify(const epoch_context* context, const hash256* header_hash, +ethash_errc ethash_verify(const epoch_context* context, const hash256* header_hash, const hash256* mix_hash, uint64_t nonce, const hash256* boundary) noexcept { const hash512 seed = hash_seed(*header_hash, nonce); if (!less_equal(hash_final(seed, *mix_hash), *boundary)) - return false; + return ETHASH_INVALID_FINAL_HASH; const hash256 expected_mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); - return equal(expected_mix_hash, *mix_hash); + return equal(expected_mix_hash, *mix_hash) ? ETHASH_SUCCESS : ETHASH_INVALID_MIX_HASH; } } // extern "C" diff --git a/test/unittests/test_ethash.cpp b/test/unittests/test_ethash.cpp index 3585f6d6..ee79a53a 100644 --- a/test/unittests/test_ethash.cpp +++ b/test/unittests/test_ethash.cpp @@ -72,6 +72,35 @@ TEST(ethash, revision) EXPECT_EQ(ethash::revision, (std::string{"23"})); } +TEST(ethash, error_code) +{ + std::ostringstream os; + std::error_code ec = ETHASH_SUCCESS; + EXPECT_FALSE(ec); + EXPECT_EQ(ec.message(), ""); + + ec = ETHASH_INVALID_FINAL_HASH; + EXPECT_TRUE(ec); + EXPECT_EQ(ec.message(), "invalid final hash"); + os.str({}); + os << ec; + EXPECT_EQ(os.str(), "ethash:1"); + + ec = ETHASH_INVALID_MIX_HASH; + EXPECT_TRUE(ec); + EXPECT_EQ(ec.message(), "invalid mix hash"); + os.str({}); + os << ec; + EXPECT_EQ(os.str(), "ethash:2"); + + ec = static_cast(3); + EXPECT_TRUE(ec); + EXPECT_EQ(ec.message(), "unknown error"); + os.str({}); + os << ec; + EXPECT_EQ(os.str(), "ethash:3"); +} + TEST(hash, hash256_from_bytes) { const uint8_t bytes[32] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, @@ -545,20 +574,27 @@ TEST(ethash, verify_hash_light) EXPECT_EQ(to_hex(r.final_hash), t.final_hash_hex); EXPECT_EQ(to_hex(r.mix_hash), t.mix_hash_hex); - bool v = verify_final_hash(header_hash, mix_hash, nonce, boundary); - EXPECT_TRUE(v); - v = verify(*context, header_hash, mix_hash, nonce, boundary); - EXPECT_TRUE(v); + auto ec = verify_final_hash(header_hash, mix_hash, nonce, boundary); + EXPECT_EQ(ec, ETHASH_SUCCESS); + EXPECT_FALSE(ec); + EXPECT_EQ(ec.category(), ethash_category()); + ec = verify(*context, header_hash, mix_hash, nonce, boundary); + EXPECT_EQ(ec, ETHASH_SUCCESS); const bool within_significant_boundary = r.final_hash.bytes[0] == 0; if (within_significant_boundary) { - v = verify_final_hash(header_hash, mix_hash, nonce + 1, boundary); - EXPECT_FALSE(v) << t.final_hash_hex; - } + ec = verify_final_hash(header_hash, mix_hash, nonce + 1, boundary); + EXPECT_EQ(ec, ETHASH_INVALID_FINAL_HASH) << t.final_hash_hex; - v = verify(*context, header_hash, mix_hash, nonce + 1, boundary); - EXPECT_FALSE(v); + ec = verify(*context, header_hash, mix_hash, nonce + 1, boundary); + EXPECT_EQ(ec, ETHASH_INVALID_FINAL_HASH); + } + else + { + ec = verify(*context, header_hash, mix_hash, nonce + 1, boundary); + EXPECT_EQ(ec, ETHASH_INVALID_MIX_HASH); + } } } @@ -604,8 +640,8 @@ TEST(ethash, verify_final_hash_only) const hash256 boundary = to_hash256("000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - EXPECT_TRUE(verify_final_hash(header_hash, mix_hash, nonce, boundary)); - EXPECT_FALSE(verify(context, header_hash, mix_hash, nonce, boundary)); + EXPECT_EQ(verify_final_hash(header_hash, mix_hash, nonce, boundary), ETHASH_SUCCESS); + EXPECT_EQ(verify(context, header_hash, mix_hash, nonce, boundary), ETHASH_INVALID_MIX_HASH); } TEST(ethash, verify_boundary) @@ -631,9 +667,10 @@ TEST(ethash, verify_boundary) EXPECT_EQ(r.final_hash, boundary_eq); EXPECT_EQ(to_hex(r.final_hash), to_hex(boundary_eq)); - EXPECT_TRUE(verify(context, example_header_hash, r.mix_hash, nonce, boundary_eq)); - EXPECT_TRUE(verify(context, example_header_hash, r.mix_hash, nonce, boundary_gt)); - EXPECT_FALSE(verify(context, example_header_hash, r.mix_hash, nonce, boundary_lt)); + EXPECT_EQ(verify(context, example_header_hash, r.mix_hash, nonce, boundary_eq), ETHASH_SUCCESS); + EXPECT_EQ(verify(context, example_header_hash, r.mix_hash, nonce, boundary_gt), ETHASH_SUCCESS); + EXPECT_EQ(verify(context, example_header_hash, r.mix_hash, nonce, boundary_lt), + ETHASH_INVALID_FINAL_HASH); } TEST(ethash_multithreaded, small_dataset) diff --git a/test/unittests/test_global_context.cpp b/test/unittests/test_global_context.cpp index ab19c926..56057019 100644 --- a/test/unittests/test_global_context.cpp +++ b/test/unittests/test_global_context.cpp @@ -77,8 +77,8 @@ TEST(managed_multithreaded, verify_all) const hash256 boundary = final_hash; const int epoch_number = get_epoch_number(t.block_number); auto& context = get_global_epoch_context(epoch_number); - const bool valid = verify(context, header_hash, mix_hash, nonce, boundary); - EXPECT_TRUE(valid); + const auto ec = verify(context, header_hash, mix_hash, nonce, boundary); + EXPECT_EQ(ec, ETHASH_SUCCESS); } }); } @@ -88,7 +88,7 @@ TEST(managed_multithreaded, verify_all) TEST(managed_multithreaded, verify_parallel) { - std::vector> futures; + std::vector> futures; for (const auto& t : hash_test_cases) { @@ -105,7 +105,7 @@ TEST(managed_multithreaded, verify_parallel) } for (auto& f : futures) - EXPECT_TRUE(f.get()); + EXPECT_EQ(f.get(), ETHASH_SUCCESS); } TEST(managed_multithreaded, get_epoch_context_random)