diff --git a/Makefile b/Makefile index b872b2e..25a7179 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ REBAR = ./rebar3 -.PHONY: all dialyzer test clean console +.PHONY: all dialyzer ct eunit clean distclean console all: $(REBAR) compile @@ -12,7 +12,10 @@ dialyzer: $(REBAR) dialyzer ct: all - $(REBAR) ct test/aecuckoo_SUITE + $(REBAR) ct --suite=test/aecuckoo_SUITE + +eunit: + $(REBAR) eunit --module=aeminer_pow_tests,aeminer_pow_cuckoo_tests clean: $(REBAR) clean @@ -23,3 +26,4 @@ distclean: clean console: $(REBAR) shell + diff --git a/include/aeminer.hrl b/include/aeminer.hrl index 1eb5f6f..3091916 100644 --- a/include/aeminer.hrl +++ b/include/aeminer.hrl @@ -1,5 +1,11 @@ + -define(HIGHEST_TARGET_SCI, 16#2100ffff). + -define(HIGHEST_TARGET_INT, 16#ffff000000000000000000000000000000000000000000000000000000000000). --define(NONCE_BITS, 64). --define(MAX_NONCE, 16#ffffffffffffffff). + -define(DIFFICULTY_INTEGER_FACTOR, 16#1000000). + +-define(MAX_NONCE, 16#ffffffffffffffff). + +-define(SOLUTION_SIZE, 42). + diff --git a/rebar.config b/rebar.config index 0c90767..0dbdacf 100644 --- a/rebar.config +++ b/rebar.config @@ -1,10 +1,20 @@ -{deps, [{aecuckooprebuilt, + +{erl_opts, [{parse_transform, lager_transform}]}. + +{deps, [ + {lager, {git, "https://github.com/erlang-lager/lager.git", + {ref, "69b4ada"}}}, % tag: 3.6.7 + + %% Cuckoo prebuilt CUDA binaries. + {aecuckooprebuilt, {aecuckooprebuilt_app_with_priv_from_git, {git, "https://github.com/aeternity/cuckoo-prebuilt.git", {ref, "90afb699dc9cc41d033a7c8551179d32b3bd569d"}}}}, - {aecuckoo, - {git, "https://github.com/aeternity/aecuckoo.git", {branch, "master"}}}, + %% Cuckoo CPU miners (not prebuilt). + {aecuckoo, {git, "https://github.com/aeternity/aecuckoo.git", + {ref, "fa3d13e"}}}, + %% This is used just in one place, just to get blake2b_256 hash. {enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "26180f4"}}} ]}. @@ -13,3 +23,10 @@ {ref, "2b2f3b3cf969ee91ba41d8351f3808530a8bf28e"}}} ]}. +{profiles, [{test, [{deps, [{meck, "0.8.12"}]}]}]}. + +{dialyzer, [{warnings, [unknown]}, + {plt_apps, all_deps}, + {base_plt_apps, [erts, kernel, stdlib, crypto]} + ]}. + diff --git a/rebar.lock b/rebar.lock index b1efa1e..db8521c 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,3 +1,4 @@ +{"1.1.0", [{<<"aecuckoo">>, {git,"https://github.com/aeternity/aecuckoo.git", {ref,"fa3d13e8c7003589153223f634c851d389b61b93"}}, @@ -10,4 +11,13 @@ {<<"enacl">>, {git,"https://github.com/aeternity/enacl.git", {ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}}, - 0}]. + 0}, + {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, + {<<"lager">>, + {git,"https://github.com/erlang-lager/lager.git", + {ref,"69b4ada2341b8ab2cf5c8e464ac936e5e4a9f62b"}}, + 0}]}. +[ +{pkg_hash,[ + {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}]} +]. diff --git a/src/aeminer.app.src b/src/aeminer.app.src index 99c22e7..9a979e4 100644 --- a/src/aeminer.app.src +++ b/src/aeminer.app.src @@ -1,10 +1,14 @@ {application, aeminer, - [{description, "Aeternity cuckoo miner"}, + [{description, "Aeternity miner"}, {vsn, "1.0.0"}, {registered, []}, {applications, [kernel, - stdlib + stdlib, + lager, + enacl, + aecuckoo, + aecuckooprebuilt ]}, {env,[]}, {modules, []}, diff --git a/src/aeminer_blake2b_256.erl b/src/aeminer_blake2b_256.erl index f31cb21..9b60f60 100644 --- a/src/aeminer_blake2b_256.erl +++ b/src/aeminer_blake2b_256.erl @@ -2,6 +2,15 @@ -export([hash/1]). +-export_type([hashable/0, + hash/0 + ]). + +-type hashable() :: binary(). + +-type hash() :: binary(). + +-spec hash(hashable()) -> hash(). hash(Bin) -> {ok, Hash} = enacl:generichash(32, Bin), Hash. diff --git a/src/aeminer_pow.erl b/src/aeminer_pow.erl index 693b995..f114899 100644 --- a/src/aeminer_pow.erl +++ b/src/aeminer_pow.erl @@ -14,19 +14,34 @@ test_target/2, trim_nonce/2]). --ifdef(TEST). --compile([export_all, nowarn_export_all]). --endif. +-export_type([nonce/0, + int_target/0, + sci_target/0, + bin_target/0, + difficulty/0, + instance/0 + ]). --include_lib("aeminer/include/aeminer.hrl"). +-include("aeminer.hrl"). + +-type nonce() :: 0..?MAX_NONCE. + +-type int_target() :: integer(). + +-type sci_target() :: integer(). + +-type bin_target() :: <<_:256>>. + +%% Difficulty: max threshold (0x00000000FFFF0000000000000000000000000000000000000000000000000000) +%% over the actual one. Always positive. +-type difficulty() :: integer(). + +-type instance() :: non_neg_integer(). + +-type config() :: aeminer_pow_cuckoo:config(). %% 10^24, approx. 2^80 -define(NONCE_RANGE, 1000000000000000000000000). --define(POW_MODULE, aeminer_pow_cuckoo). - -%% 0..?MAX_NONCE --type nonce() :: 0..16#ffffffffffffffff. --export_type([nonce/0]). %%------------------------------------------------------------------------------ %% Target threshold and difficulty @@ -64,46 +79,12 @@ %% significand (i.e., the int value is 0. * 8^). %% https://en.bitcoin.it/wiki/Difficulty#How_is_difficulty_stored_in_blocks.3F) %%------------------------------------------------------------------------------ --type sci_int() :: integer(). - -%% Optional evidence for PoW verification --type pow_evidence() :: 'no_value' | term(). --type pow_result() :: {'ok', {Nonce :: nonce(), Solution :: pow_evidence()}} | - {error, no_solution | {runtime, term()}}. -%% Difficulty: max threshold (0x00000000FFFF0000000000000000000000000000000000000000000000000000) -%% over the actual one. Always positive. --type difficulty() :: integer(). - --type miner_config() :: aeminer_pow_cuckoo:miner_config(). --type miner_instance() :: non_neg_integer(). - --export_type([sci_int/0, - difficulty/0, - pow_evidence/0, - pow_result/0, - miner_instance/0, - miner_config/0]). - -%%%============================================================================= -%%% Behaviour -%%%============================================================================= - --type hashable() :: binary(). - --callback generate(Data :: hashable(), Target :: aeminer_pow:sci_int(), - Nonce :: aeminer_pow:nonce(), MinerConfig :: aeminer_pow:miner_config(), - MinerInstance :: aeminer_pow:miner_instance()) -> - aeminer_pow:pow_result(). - --callback verify(Data :: hashable(), Nonce :: aeminer_pow:nonce(), - Evd :: aeminer_pow:pow_evidence(), Target :: aeminer_pow:sci_int()) -> - boolean(). %%%============================================================================= %%% API %%%============================================================================= --spec scientific_to_integer(sci_int()) -> integer(). +-spec scientific_to_integer(sci_target()) -> int_target(). scientific_to_integer(S) -> {Exp, Significand} = break_up_scientific(S), E3 = Exp - 3, @@ -114,7 +95,7 @@ scientific_to_integer(S) -> Significand bsr (-8 * E3) end. --spec integer_to_scientific(integer()) -> sci_int(). +-spec integer_to_scientific(int_target()) -> sci_target(). integer_to_scientific(I) -> %% Find exponent and significand {Exp, Significand} = integer_to_scientific(I, 3), @@ -129,22 +110,22 @@ integer_to_scientific(I) -> %% We want difficulty to be an integer, to still have enough precision using %% integer division we multiply by K (1 bsl 24). --spec target_to_difficulty(sci_int()) -> integer(). +-spec target_to_difficulty(sci_target()) -> difficulty(). target_to_difficulty(SciTgt) -> (?DIFFICULTY_INTEGER_FACTOR * ?HIGHEST_TARGET_INT) div scientific_to_integer(SciTgt). --spec next_nonce(aeminer_pow:nonce(), aeminer_pow:miner_config()) -> aeminer_pow:nonce(). +-spec next_nonce(nonce(), config()) -> nonce(). next_nonce(Nonce, Cfg) -> - Nonce + aeminer_pow_cuckoo:get_repeats(Cfg). + Nonce + aeminer_pow_cuckoo:repeats(Cfg). --spec pick_nonce() -> aeminer_pow:nonce(). +-spec pick_nonce() -> nonce(). pick_nonce() -> rand:uniform(?NONCE_RANGE) band ?MAX_NONCE. --spec trim_nonce(aeminer_pow:nonce(), aeminer_pow:miner_config()) -> aeminer_pow:nonce(). +-spec trim_nonce(nonce(), config()) -> nonce(). trim_nonce(Nonce, Cfg) -> - case Nonce + aeminer_pow_cuckoo:get_repeats(Cfg) < ?MAX_NONCE of + case Nonce + aeminer_pow_cuckoo:repeats(Cfg) < ?MAX_NONCE of true -> Nonce; false -> 0 end. @@ -152,12 +133,18 @@ trim_nonce(Nonce, Cfg) -> %%------------------------------------------------------------------------------ %% Test if binary is under the target threshold %%------------------------------------------------------------------------------ --spec test_target(binary(), sci_int()) -> boolean(). +-spec test_target(bin_target(), sci_target()) -> boolean(). test_target(Bin, Target) -> Threshold = scientific_to_integer(Target), <> = Bin, Val < Threshold. +%% TODO: get target +%Bin = solution_to_binary(lists:sort(Soln), NodeSize * 8, <<>>), +%%Hash = aec_hash:hash(pow, Bin), +%%<> = Hash, +%%Val + %%%============================================================================= %%% Internal functions %%%============================================================================= @@ -170,7 +157,7 @@ integer_to_scientific(I, Exp) -> %% Add the number of bytes in the significand {Exp, I}. -%% Return the exponent and significand of a sci_int(). +%% Return the exponent and significand of a sci_target(). break_up_scientific(S) -> SigMask = (1 bsl 24) - 1, Exp = ((S bxor SigMask) bsr 24), @@ -183,8 +170,3 @@ break_up_scientific(S) -> {-Exp, Significand - 16#800000} end. -%% ------------ GET TARGET ------------ -%%Bin = solution_to_binary(lists:sort(Soln), NodeSize * 8, <<>>), -%%Hash = aec_hash:hash(pow, Bin), -%%<> = Hash, -%%Val diff --git a/src/aeminer_pow_cuckoo.erl b/src/aeminer_pow_cuckoo.erl index ecd510f..570ba8c 100644 --- a/src/aeminer_pow_cuckoo.erl +++ b/src/aeminer_pow_cuckoo.erl @@ -15,82 +15,138 @@ %%%------------------------------------------------------------------- -module(aeminer_pow_cuckoo). --behaviour(aeminer_pow). +-export([config/7, + addressed_instances/1, + repeats/1, + exec/1, + extra_args/1, + hex_enc_header/1, + get_node_size/1 + ]). +-export([generate/5, + verify/5 + ]). --export([check_env/0, - generate/5, - get_addressed_instances/1, - get_miner_configs/0, - get_repeats/1, - verify/4]). - +-export_type([hashable/0, + exec/0, + exec_group/0, + extra_args/0, + hex_enc_header/0, + repeats/0, + edge_bits/0, + solution/0, + config/0 + ]). -ifdef(TEST). --compile([export_all, nowarn_export_all]). --include_lib("eunit/include/eunit.hrl"). +-export([verify_proof_/3, + solution_to_binary/2 + ]). -endif. + -include("aeminer.hrl"). --define(debug(F, A), epoch_pow_cuckoo:debug(F, A)). --define(info(F, A), epoch_pow_cuckoo:info(F, A)). --define(warning(F, A), epoch_pow_cuckoo:warning(F, A)). --define(error(F, A), epoch_pow_cuckoo:error(F, A)). +-type hashable() :: aeminer_blake2b_256:hashable(). --type os_pid() :: integer() | undefined. --type pow_cuckoo_solution() :: [integer()]. +-type nonce() :: aeminer_pow:nonce(). --record(state, {os_pid :: os_pid(), - port :: port() | undefined, - buffer = [] :: string(), - target :: aeminer_pow:sci_int() | undefined, - parser :: output_parser_fun()}). +-type sci_target() :: aeminer_pow:sci_target(). --type output_parser_fun() :: fun((list(string()), #state{}) -> - {'ok', term(), term()} | {'error', term()}). +-type instance() :: aeminer_pow:instance() + | undefined. --define(DEFAULT_EXECUTABLE_GROUP , <<"aemineruckoo">>). --define(DEFAULT_EXTRA_ARGS , <<>>). --define(DEFAULT_HEX_ENCODED_HEADER , false). --define(DEFAULT_REPEATS , 1). --define(DEFAULT_EDGE_BITS , 29). --define(DEFAULT_CUCKOO_ENV, - {?DEFAULT_EDGE_BITS, - [{<<"mean29-generic">>, ?DEFAULT_EXTRA_ARGS, ?DEFAULT_HEX_ENCODED_HEADER, - ?DEFAULT_REPEATS, undefined, ?DEFAULT_EXECUTABLE_GROUP}]}). +-type exec() :: string(). --record(miner_config, - {executable :: list(), - executable_group :: binary(), - extra_args :: list(), - hex_encoded_header :: boolean(), - repeats :: non_neg_integer(), - instances :: list(aeminer_pow:miner_instance()) | 'undefined'}). --type miner_config() :: #miner_config{}. --export_type([miner_config/0]). +-type exec_group() :: binary(). + +-type extra_args() :: string(). + +-type hex_enc_header() :: boolean(). + +-type repeats() :: non_neg_integer(). + +-type edge_bits() :: pos_integer(). + +-type instances() :: [aeminer_pow:instance()] + | undefined. + +-type solution() :: [integer()]. + +-type output_parser_fun() :: fun(([string()], state()) -> + {ok, term(), term()} | {error, term()}). + +-record(config, { + exec :: exec(), + exec_group :: exec_group(), + extra_args :: extra_args(), + hex_enc_header :: hex_enc_header(), + repeats :: repeats(), + edge_bits :: edge_bits(), + instances :: instances() + }). + +-opaque config() :: #config{}. + +-record(state, { + os_pid :: integer() | undefined, + port :: port() | undefined, + buffer = [] :: string(), + target :: sci_target() | undefined, + edge_bits :: edge_bits(), + parser :: output_parser_fun() + }). + +-type state() :: #state{}. + +-define(IS_CONFIG(Exec, ExecGroup, ExtraArgs, HexEncHdr, Repeats, EdgeBits, Instances), + is_binary(Exec) and is_binary(ExecGroup) and + is_binary(ExtraArgs) and is_boolean(HexEncHdr) and + (is_integer(Repeats) and (Repeats > 0)) and + (is_integer(EdgeBits) and (EdgeBits > 0)) and + (is_list(Instances) or (Instances =:= undefined))). + +-define(LOG_MODULE, application:get_env(aeminer, log_module)). + +-define(debug(F, A), lager:debug(F, A)). +-define(info(F, A), lager:info(F, A)). +-define(warning(F, A), lager:warning(F, A)). +-define(error(F, A), lager:error(F, A)). %%%============================================================================= %%% API %%%============================================================================= -%%------------------------------------------------------------------------------ -%% Assert that configuration options 'mining > cuckoo > miners' and -%% 'mining > cuckoo > edge_bits' are not used together with deprecated -%% configuration property 'mining > cuckoo > miner'. -%%------------------------------------------------------------------------------ -check_env() -> - case {aeu_env:user_map([<<"mining">>, <<"cuckoo">>, <<"miners">>]), - aeu_env:user_config([<<"mining">>, <<"cuckoo">>, <<"edge_bits">>])} of - {undefined, undefined} -> ok; - {_, _} -> - case aeu_env:user_config([<<"mining">>, <<"cuckoo">>, <<"miner">>]) of - undefined -> ok; - _ -> - lager:error("Config error: deprecated property 'mining > cuckoo > miner' cannot be used " - "together with 'mining > cuckoo > miners' or 'mining > cuckoo > edge_bits'"), - exit(cuckoo_config_validation_failed) - end - end. +config(Exec, ExecGroup, ExtraArgs, HexEncHdr, Repeats, EdgeBits, Instances) when + ?IS_CONFIG(Exec, ExecGroup, ExtraArgs, HexEncHdr, Repeats, EdgeBits, Instances) -> + #config{ + exec = binary_to_list(Exec), + exec_group = ExecGroup, + extra_args = binary_to_list(ExtraArgs), + hex_enc_header = HexEncHdr, + repeats = Repeats, + edge_bits = EdgeBits, + instances = Instances}. + +-spec addressed_instances(config()) -> instances(). +addressed_instances(#config{instances = Instances}) -> + Instances. + +-spec repeats(config()) -> repeats(). +repeats(#config{repeats = Repeats}) -> + Repeats. + +-spec exec(config()) -> exec(). +exec(#config{exec = Exec}) -> + Exec. + +-spec extra_args(config()) -> extra_args(). +extra_args(#config{extra_args = ExtraArgs}) -> + ExtraArgs. + +-spec hex_enc_header(config()) -> hex_enc_header(). +hex_enc_header(#config{hex_enc_header = HexEncHdr}) -> + HexEncHdr. %%------------------------------------------------------------------------------ %% Proof of Work generation with default settings @@ -109,204 +165,61 @@ check_env() -> %% %% Very slow below 3 threads, not improving significantly above 5, let us take 5. %%------------------------------------------------------------------------------ - --type hashable() :: binary(). - --spec generate(Data :: hashable(), Target :: aeminer_pow:sci_int(), - Nonce :: aeminer_pow:nonce(), MinerConfig :: aeminer_pow:miner_config(), - MinerInstance :: aeminer_pow:miner_instance() | 'undefined') -> aeminer_pow:pow_result(). -generate(Data, Target, Nonce, MinerConfig, MinerInstance) when Nonce >= 0, - Nonce =< ?MAX_NONCE -> +-spec generate(hashable(), sci_target(), nonce(), config(), instance()) -> + {ok, {nonce(), solution()}} | {error, no_solution} | {error, {runtime, term()}}. +generate(Data, Target, Nonce, Config, Instance) when + Nonce >= 0, Nonce =< ?MAX_NONCE -> %% Hash Data and convert the resulting binary to a base64 string for Cuckoo %% Since this hash is purely internal, we don't use api encoding Hash = aeminer_blake2b_256:hash(Data), Hash64 = base64:encode_to_string(Hash), ?debug("Generating solution for data hash ~p and nonce ~p with target ~p.", [Hash, Nonce, Target]), - case generate_int(Hash64, Nonce, Target, MinerConfig, MinerInstance) of + case generate_int(Hash64, Nonce, Target, Config, Instance) of {ok, Nonce1, Soln} -> {ok, {Nonce1, Soln}}; {error, no_value} -> ?debug("No cuckoo solution found", []), {error, no_solution}; - {error, Reason} -> - %% Executable failed (segfault, not found, etc.): let miner decide - {error, {runtime, Reason}} + {error, Rsn} -> + %% Exec failed (segfault, not found, etc.): let miner decide + {error, {runtime, Rsn}} end. %%------------------------------------------------------------------------------ %% Proof of Work verification (with difficulty check) %%------------------------------------------------------------------------------ --spec verify(Data :: hashable(), Nonce :: aeminer_pow:nonce(), - Evd :: aeminer_pow:pow_evidence(), Target :: aeminer_pow:sci_int()) -> - boolean(). -verify(Data, Nonce, Evd, Target) when is_list(Evd), - Nonce >= 0, Nonce =< ?MAX_NONCE -> +-spec verify(hashable(), nonce(), solution(), sci_target(), edge_bits()) -> + boolean(). +verify(Data, Nonce, Soln, Target, EdgeBits) when + is_list(Soln), Nonce >= 0, Nonce =< ?MAX_NONCE -> Hash = aeminer_blake2b_256:hash(Data), - case test_target(Evd, Target) of + case test_target(Soln, Target, EdgeBits) of true -> - verify_proof(Hash, Nonce, Evd); + verify_proof(Hash, Nonce, Soln, EdgeBits); false -> false end. -%%------------------------------------------------------------------------------ -%% Read and parse miner configs. -%% -%% Miners defined in epoch.{json,yaml} user config file take precedence. -%% If there are no miners defined in the user config, sys.config cuckoo -%% miners are read. If there are neither user config nor sys.config miners -%% ?DEFAULT_CUCKOO_ENV is used as the last resort option (i.e. mean29-generic -%% without any extra args). -%%------------------------------------------------------------------------------ --spec get_miner_configs() -> list(miner_config()). -get_miner_configs() -> - case get_miners_from_user_config() of - {ok, MinerConfigs} -> MinerConfigs; - undefined -> - case get_miners_from_deprecated_user_config() of - {ok, MinerConfigs} -> MinerConfigs; - undefined -> get_miners_from_sys_config() - end - end. +%% Internal functions. --spec get_addressed_instances(miner_config()) -> list(non_neg_integer()) | undefined. -get_addressed_instances(#miner_config{instances = Instances}) -> - Instances. +generate_int(Hash, Nonce, Target, + #config{exec = Exec, extra_args = ExtraArgs0, + hex_enc_header = HexEncHdr} = Config, Instance) -> + ExtraArgs = case is_miner_instance_addressation_enabled(Config) of + true -> ExtraArgs0 ++ " -d " ++ integer_to_list(Instance); + false -> ExtraArgs0 + end, + EncodedHash = case HexEncHdr of + true -> hex_string(Hash); + false -> Hash + end, + ExecBinDir = exec_bin_dir(Config), + generate_int(EncodedHash, Nonce, Target, ExecBinDir, Exec, ExtraArgs, Config). --spec get_repeats(miner_config()) -> non_neg_integer(). -get_repeats(#miner_config{repeats = Repeats}) -> - Repeats. - -%%%============================================================================= -%%% Internal functions -%%%============================================================================= - -%%------------------------------------------------------------------------------ -%% Config handling -%%------------------------------------------------------------------------------ - -get_options() -> - {_, _} = aeu_env:get_env(aeminerore, aeminer_pow_cuckoo, ?DEFAULT_CUCKOO_ENV). - -get_miners_from_user_config() -> - case aeu_env:user_map([<<"mining">>, <<"cuckoo">>, <<"miners">>]) of - {ok, MinerConfigMaps} -> - MinerConfigs = - lists:foldl( - fun(ConfigMap, Configs) -> - [build_miner_config(ConfigMap) | Configs] - end, [], MinerConfigMaps), - {ok, MinerConfigs}; - undefined -> undefined - end. - -get_miners_from_deprecated_user_config() -> - case aeu_env:user_map([<<"mining">>, <<"cuckoo">>, <<"miner">>]) of - {ok, MinerConfigMap} -> - %% In the deprecated config 'mining > cuckoo > miner' - %% 'instances' is the property indicating the number of instances to be addressed. - %% Addressed instances list has to be generated accordingly (indexed from 0). - case maps:get(<<"instances">>, MinerConfigMap, undefined) of - undefined -> - MinerConfigs = [build_miner_config(MinerConfigMap)], - {ok, MinerConfigs}; - InstancesCount -> - AddressedInstances = lists:seq(0, InstancesCount - 1), - MinerConfigMap1 = MinerConfigMap#{<<"addressed_instances">> => AddressedInstances}, - MinerConfigs = [build_miner_config(MinerConfigMap1)], - {ok, MinerConfigs} - end; - undefined -> undefined - end. - -get_miners_from_sys_config() -> - {_, MinerConfigLists} = get_options(), - lists:foldl( - fun({_, _, _, _, _, _} = Config, Configs) -> - [build_miner_config(Config) | Configs] - end, [], MinerConfigLists). - -build_miner_config(Config) when is_map(Config) -> - Executable = maps:get(<<"executable">> , Config), - ExecutableGroup = maps:get(<<"executable_group">> , Config, ?DEFAULT_EXECUTABLE_GROUP), - ExtraArgs = maps:get(<<"extra_args">> , Config, ?DEFAULT_EXTRA_ARGS), - HexEncodedHdr = maps:get(<<"hex_encoded_header">> , Config, ?DEFAULT_HEX_ENCODED_HEADER), - Repeats = maps:get(<<"repeats">> , Config, ?DEFAULT_REPEATS), - Instances = maps:get(<<"addressed_instances">>, Config, undefined), - #miner_config{ - executable = binary_to_list(Executable), - executable_group = ExecutableGroup, - extra_args = binary_to_list(ExtraArgs), - hex_encoded_header = HexEncodedHdr, - repeats = Repeats, - instances = Instances}; -build_miner_config({Executable, ExtraArgs, HexEncodedHeader, Repeats, Instances, ExecutableGroup}) -> - #miner_config{ - executable = binary_to_list(Executable), - executable_group = ExecutableGroup, - extra_args = binary_to_list(ExtraArgs), - hex_encoded_header = HexEncodedHeader, - repeats = Repeats, - instances = Instances}. - -get_edge_bits() -> - case aeu_env:user_config([<<"mining">>, <<"cuckoo">>, <<"edge_bits">>]) of - {ok, EdgeBits} -> EdgeBits; - undefined -> - %% Deprecated property 'mining' > 'cuckoo' > 'miner' > 'edge_bits' - case aeu_env:user_config([<<"mining">>, <<"cuckoo">>, <<"miner">>, <<"edge_bits">>]) of - {ok, EdgeBits} -> EdgeBits; - undefined -> - {EdgeBits, _} = get_options(), - EdgeBits - end - end. - -get_executable(#miner_config{executable = Executable}) -> - Executable. - -get_extra_args(#miner_config{extra_args = ExtraArgs}) -> - ExtraArgs. - -is_hex_encoded_header(#miner_config{hex_encoded_header = HexEncodedHeader}) -> - HexEncodedHeader. - -is_miner_instance_addressation_enabled(#miner_config{instances = Instances}) -> - case Instances of - undefined -> false; - I when is_list(I) -> true - end. - -miner_bin_dir(#miner_config{executable_group = ExecutableGroup}) -> - case ExecutableGroup of - <<"aemineruckoo">> -> aemineruckoo:bin_dir(); - <<"aemineruckooprebuilt">> -> code:priv_dir(aemineruckooprebuilt) - end. - -%%------------------------------------------------------------------------------ -%% Proof of Work generation: use the hash provided -%%------------------------------------------------------------------------------ --spec generate_int(Hash :: string(), Nonce :: aeminer_pow:nonce(), - Target :: aeminer_pow:sci_int(), aeminer_pow:miner_config(), Instance :: non_neg_integer()) -> - {'ok', Nonce2 :: aeminer_pow:nonce(), Solution :: pow_cuckoo_solution()} | - {'error', term()}. -generate_int(Hash, Nonce, Target, #miner_config{} = Config, Instance) -> - MinerBin = get_executable(Config), - MinerExtraArgs0 = get_extra_args(Config), - MinerExtraArgs = case is_miner_instance_addressation_enabled(Config) of - true -> MinerExtraArgs0 ++ " -d " ++ integer_to_list(Instance); - false -> MinerExtraArgs0 - end, - EncodedHash = case is_hex_encoded_header(Config) of - true -> hex_string(Hash); - false -> Hash - end, - MinerBinDir = miner_bin_dir(Config), - generate_int(EncodedHash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, Config). - -generate_int(Hash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, Config) -> - Repeats = integer_to_list(get_repeats(Config)), +generate_int(Hash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, + #config{repeats = Repeats0, edge_bits = EdgeBits}) -> + Repeats = integer_to_list(Repeats0), Args = ["-h", Hash, "-n", integer_to_list(Nonce), "-r", Repeats | string:tokens(MinerExtraArgs, " ")], ?info("Executing cmd '~s ~s'", [MinerBin, lists:concat(lists:join(" ", Args))]), Old = process_flag(trap_exit, true), @@ -315,10 +228,11 @@ generate_int(Hash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, Config) wait_for_result(#state{os_pid = OsPid, port = Port, buffer = [], - parser = fun parse_generation_result/2, - target = Target}); - {error, _} = E -> - E + target = Target, + edge_bits = EdgeBits, + parser = fun parse_generation_result/2}); + {error, _Rsn} = Err -> + Err catch C:E -> {error, {unknown, {C, E}}} @@ -330,29 +244,35 @@ generate_int(Hash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, Config) end end. --spec hex_string(string()) -> string(). hex_string(S) -> Bin = list_to_binary(S), lists:flatten([io_lib:format("~2.16.0B", [B]) || <> <= Bin]). --define(POW_OK, ok). +is_miner_instance_addressation_enabled(#config{instances = Instances}) -> + case Instances of + I when is_list(I) -> true; + undefined -> false + end. + +exec_bin_dir(#config{exec_group = ExecGroup}) -> + case ExecGroup of + <<"aecuckoo">> -> aecuckoo:bin_dir(); + <<"aecuckooprebuilt">> -> code:priv_dir(aecuckooprebuilt) + end. + -define(POW_TOO_BIG(Nonce), {error, {nonce_too_big, Nonce}}). -define(POW_TOO_SMALL(Nonce, PrevNonce), {error, {nonces_not_ascending, Nonce, PrevNonce}}). -define(POW_NON_MATCHING, {error, endpoints_do_not_match_up}). -define(POW_BRANCH, {error, branch_in_cycle}). -define(POW_DEAD_END, {error, cycle_dead_ends}). -define(POW_SHORT_CYCLE, {error, cycle_too_short}). --define(PROOFSIZE, 42). + %%------------------------------------------------------------------------------ %% @doc %% Proof of Work verification (difficulty check should be done before calling %% this function) %% @end %%------------------------------------------------------------------------------ --spec verify_proof(Hash :: binary(), Nonce :: aeminer_pow:nonce(), - Solution :: aeminer_pow:pow_evidence()) -> boolean(). -verify_proof(Hash, Nonce, Solution) -> - verify_proof(Hash, Nonce, Solution, get_edge_bits()). verify_proof(Hash, Nonce, Solution, EdgeBits) -> %% Cuckoo has an 80 byte header, we have to use that as well @@ -392,8 +312,8 @@ verify_proof_(Header, Solution, EdgeBits) -> throw(?POW_NON_MATCHING) end catch - throw:{error, Reason} -> - ?info("Proof verification failed for ~p: ~p", [Solution, Reason]), + throw:{error, Rsn} -> + ?info("Proof verification failed for ~p: ~p", [Solution, Rsn]), false end. @@ -409,8 +329,8 @@ check_cycle(Nodes0) -> UOdds = lists:usort(Odds), %% Check that all nodes appear exactly twice (i.e. each node has %% exactly two edges). - case length(UEvens) == (?PROOFSIZE div 2) andalso - length(UOdds) == (?PROOFSIZE div 2) andalso + case length(UEvens) == (?SOLUTION_SIZE div 2) andalso + length(UOdds) == (?SOLUTION_SIZE div 2) andalso UOdds == Odds -- UOdds andalso UEvens == Evens -- UEvens of false -> {error, ?POW_BRANCH}; @@ -484,11 +404,7 @@ pack_header_and_nonce(Hash, Nonce) when byte_size(Hash) == 32 -> %% for the last line fragment w/o NL. %% @end %%------------------------------------------------------------------------------ --spec wait_for_result(#state{}) -> - {'ok', aeminer_pow:nonce(), pow_cuckoo_solution()} | {'error', term()}. -wait_for_result(#state{os_pid = OsPid, - port = Port, - buffer = Buffer} = State) -> +wait_for_result(#state{os_pid = OsPid, port = Port, buffer = Buffer} = State) -> receive {Port, {data, Msg}} -> Str = binary_to_list(Msg), @@ -514,7 +430,6 @@ wait_for_result(#state{os_pid = OsPid, %% end of Str. %% @end %%------------------------------------------------------------------------------ --spec handle_fragmented_lines(string(), string()) -> {list(string()), string()}. handle_fragmented_lines(Str, Buffer) -> Lines = string:tokens(Str, "\n"), @@ -542,16 +457,13 @@ handle_fragmented_lines(Str, Buffer) -> %% Parse miner output %% @end %%------------------------------------------------------------------------------ --spec parse_generation_result(list(string()), #state{}) -> - {'ok', Nonce :: aeminer_pow:nonce(), Solution :: pow_cuckoo_solution()} | - {'error', term()}. parse_generation_result([], State) -> wait_for_result(State); -parse_generation_result(["Solution" ++ NonceValuesStr | Rest], #state{os_pid = OsPid, - target = Target} = State) -> +parse_generation_result(["Solution" ++ NonceValuesStr | Rest], + #state{os_pid = OsPid, edge_bits = EdgeBits, target = Target} = State) -> [NonceStr | SolStrs] = string:tokens(NonceValuesStr, " "), Soln = [list_to_integer(string:trim(V, both, [$\r]), 16) || V <- SolStrs], - case {length(Soln), test_target(Soln, Target)} of + case {length(Soln), test_target(Soln, Target, EdgeBits)} of {42, true} -> stop_execution(OsPid), case parse_nonce_str(NonceStr) of @@ -563,8 +475,8 @@ parse_generation_result(["Solution" ++ NonceValuesStr | Rest], #state{os_pid = O Err end; {N, _} when N /= 42 -> - %% No nonce in solution, old miner executable? ?debug("Solution has wrong length (~p) should be 42", [N]), + %% No nonce in solution, old miner exec? stop_execution(OsPid), {error, bad_miner}; {_, false} -> @@ -585,7 +497,6 @@ parse_nonce_str(S) -> %% Stop the OS process %% @end %%------------------------------------------------------------------------------ --spec stop_execution(os_pid()) -> ok. stop_execution(OsPid) -> exec_kill(OsPid), ?debug("Mining OS process ~p stopped", [OsPid]), @@ -597,24 +508,16 @@ stop_execution(OsPid) -> %% greater than 33 (when it needs u64 to store). Hash result for difficulty %% control accordingly. %% @end -%%------------------------------------------------------------------------------ --spec get_node_size() -> non_neg_integer(). -get_node_size() -> - node_size(get_edge_bits()). - %% Refs: %% * https://github.com/tromp/cuckoo/blob/488c03f5dbbfdac6d2d3a7e1d0746c9a7dafc48f/src/Makefile#L214-L215 %% * https://github.com/tromp/cuckoo/blob/488c03f5dbbfdac6d2d3a7e1d0746c9a7dafc48f/src/cuckoo.h#L26-L30 --spec node_size(non_neg_integer()) -> non_neg_integer(). -node_size(EdgeBits) when is_integer(EdgeBits), EdgeBits > 31 -> 8; -node_size(EdgeBits) when is_integer(EdgeBits), EdgeBits > 0 -> 4. +%%------------------------------------------------------------------------------ +-spec get_node_size(pos_integer()) -> non_neg_integer(). +get_node_size(EdgeBits) when is_integer(EdgeBits), EdgeBits > 31 -> 8; +get_node_size(EdgeBits) when is_integer(EdgeBits), EdgeBits > 0 -> 4. --spec exec_run(string(), string(), list(string())) -> - {ok, Port :: port(), OsPid :: os_pid()} | - {error, {port_error, {term(), term()}}}. exec_run(Cmd, Dir, Args) -> - PortSettings = [ - binary, + PortSettings = [binary, exit_status, hide, in, @@ -639,7 +542,6 @@ exec_run(Cmd, Dir, Args) -> {error, {port_error, {C, E}}} end. --spec exec_kill(os_pid()) -> ok. exec_kill(undefined) -> ok; exec_kill(OsPid) -> @@ -652,7 +554,6 @@ exec_kill(OsPid) -> ok end. --spec is_unix() -> boolean(). is_unix() -> case erlang:system_info(system_architecture) of "win32" -> @@ -666,13 +567,11 @@ is_unix() -> %% hash-based target is suggested: the sha256 hash of the cycle nonces %% is restricted to be under the target value (0 < target < 2^256). %%------------------------------------------------------------------------------ --spec test_target(Soln :: pow_cuckoo_solution(), Target :: aeminer_pow:sci_int()) -> - boolean(). -test_target(Soln, Target) -> - test_target(Soln, Target, get_node_size()). +test_target(Soln, Target, EdgeBits) -> + test_target1(Soln, Target, get_node_size(EdgeBits)). -test_target(Soln, Target, NodeSize) -> - Bin = solution_to_binary(lists:sort(Soln), NodeSize * 8, <<>>), +test_target1(Soln, Target, NodeSize) -> + Bin = solution_to_binary(lists:sort(Soln), NodeSize * 8), Hash = aeminer_blake2b_256:hash(Bin), aeminer_pow:test_target(Hash, Target). @@ -680,8 +579,9 @@ test_target(Soln, Target, NodeSize) -> %% Convert solution (a list of 42 numbers) to a binary %% in a languauge-independent way %%------------------------------------------------------------------------------ --spec solution_to_binary(Soln :: pow_cuckoo_solution(), Bits :: integer(), - Acc :: binary()) -> binary(). +solution_to_binary(Soln, Bits) -> + solution_to_binary(Soln, Bits, <<>>). + solution_to_binary([], _Bits, Acc) -> Acc; solution_to_binary([H | T], Bits, Acc) -> diff --git a/src/aeminer_siphash24.erl b/src/aeminer_siphash24.erl index ed02fcc..33246ad 100644 --- a/src/aeminer_siphash24.erl +++ b/src/aeminer_siphash24.erl @@ -28,10 +28,10 @@ %%%============================================================================= -spec create_keys(binary()) -> - {aeu_siphash24:siphash_key(), - aeu_siphash24:siphash_key(), - aeu_siphash24:siphash_key(), - aeu_siphash24:siphash_key()}. + {aeminer_siphash24:siphash_key(), + aeminer_siphash24:siphash_key(), + aeminer_siphash24:siphash_key(), + aeminer_siphash24:siphash_key()}. create_keys(Header) -> AuxHash = <<_:32/binary>> = aeminer_blake2b_256:hash(Header), <>). +-define(TEST_HIGH_NONCE, 38). %% Nonce with solution with high target. +-define(EDGE_BITS_15, 15). +-define(EDGE_BITS_29, 29). + +pow_test_() -> + [{"Generate with a winning nonce and high target threshold, verify it", + {timeout, 60, + fun() -> + Target = ?HIGHEST_TARGET_SCI, + Nonce = ?TEST_HIGH_NONCE, + Config = fast_and_deterministic_cuckoo_pow(), + Res = spawn_worker(fun() -> ?TEST_MODULE:generate(?TEST_BIN, Target, Nonce, Config, undefined) end), + {ok, {Nonce, Soln}} = Res, + ?assertMatch(L when length(L) == 42, Soln), + + %% verify the nonce and the solution + Res2 = ?TEST_MODULE:verify(?TEST_BIN, Nonce, Soln, Target, ?EDGE_BITS_15), + ?assert(Res2) + end} + }, + {"Generate with a winning nonce but low target threshold, shall fail", + {timeout, 90, + fun() -> + Target = 16#01010000, + Nonce = ?TEST_HIGH_NONCE, + Config = fast_and_deterministic_cuckoo_pow(), + Res1 = spawn_worker(fun() -> + ?TEST_MODULE:generate(?TEST_BIN, Target, Nonce, Config, undefined) + end), + ?assertEqual({error, no_solution}, Res1), + + %% Any attempts to verify such nonce with a solution + %% found with high target threshold shall fail. + %% + %% Obtain solution with high target threshold ... + HighTarget = ?HIGHEST_TARGET_SCI, + Res2 = spawn_worker(fun() -> + ?TEST_MODULE:generate(?TEST_BIN, HighTarget, Nonce, Config, undefined) + end), + {ok, {Nonce, Soln2}} = Res2, + ?assertMatch(L when length(L) == 42, Soln2), + %% ... then attempt to verify such solution (and + %% nonce) with the low target threshold (shall fail). + ?assertNot(?TEST_MODULE:verify(?TEST_BIN, Nonce, Soln2, Target, ?EDGE_BITS_15)) + end} + }, + {"Attempt to verify wrong solution for nonce that has a solution shall fail", + fun() -> + Target = ?HIGHEST_TARGET_SCI, + Nonce = ?TEST_HIGH_NONCE, + Config = fast_and_deterministic_cuckoo_pow(), + Res = spawn_worker(fun() -> ?TEST_MODULE:generate(?TEST_BIN, Target, Nonce, Config, undefined) end), + {ok, {Nonce, Soln}} = Res, + ?assertMatch(L when length(L) == 42, Soln), + + WrongSoln = lists:seq(0, 41), + ?assertMatch(L when length(L) == 42, WrongSoln), + ?assertNotEqual(Soln, WrongSoln), + ?assertNot(?TEST_MODULE:verify(?TEST_BIN, Nonce, WrongSoln, Target, ?EDGE_BITS_15)) + end}, + {"Attempt to verify nonce that does not have a solution (providing a dummy solution) shall fail", + fun() -> + Target = ?HIGHEST_TARGET_SCI, + Nonce = 1, + Config = fast_and_deterministic_cuckoo_pow(), + ?assertMatch({error, no_solution}, + spawn_worker(fun() -> ?TEST_MODULE:generate(?TEST_BIN, Target, Nonce, Config, undefined) end)), + + DummySoln = lists:seq(0, 41), + ?assertMatch(L when length(L) == 42, DummySoln), + ?assertNot(?TEST_MODULE:verify(?TEST_BIN, Nonce, DummySoln, Target, ?EDGE_BITS_15)) + end}, + {"Attempt to verify nonce that is too big shall fail gracefully", + fun() -> + % this is a premined working solution for size 27 + Hash = <<83,237,15,231,60,2,35,26,173,64,55,84,59,100,88,146,91, + 124,171,211,193,86,167,83,17,153,168,99,84,72,33,186>>, + Pow = [2253069,4506519,4850569,8551070,9391218,15176443,22052028, + 24045664,29484700,31332105,38588547,39046239,43427572, + 53979472,58387992,60256309,62282050,67357873,68186886, + 69815968,71809484,73494956,74992447,76953489,82132560, + 84075861,84934950,85804033,87920415,96539757,96818481, + 98049225,98464641,98907580,110711166,115480621,117062778, + 117537386,120015599,125293300,125684682,129332159], + Nonce = 17654096256755765485, + Target = 536940240, + ?assertNot(?TEST_MODULE:verify(Hash, Nonce, Pow, Target, ?EDGE_BITS_15)) + end} + ]. + +misc_test_() -> + [{"Conversion of a solution to binary", + fun() -> + Soln = [5936046,6000450,9980569,10770186,11256679,11557293, + 12330374,16556162,25308926,27241299,29693321,31019885, + 38091840,44351975,46970870,55597976,57712943,76763622, + 78513115,78670397,82776188,82841920,84299614,86421603, + 87878232,87913313,92453652,93430969,94032236,94428148, + 97119256,102408900,104747553,108943266,112048126, + 112561693,118817859,118965199,121744219,122178237, + 132944539,133889045], + NodeSize = ?TEST_MODULE:get_node_size(?EDGE_BITS_15), + ?assertEqual(42*NodeSize, size(?TEST_MODULE:solution_to_binary( + lists:sort(Soln), NodeSize * 8))) + end} + ]. + +kill_ospid_miner_test_() -> + [ {"Run miner in OS and kill it by killing parent", + fun() -> + Config = default_cuckoo_pow(), + Self = self(), + Pid = spawn(fun() -> + Self ! {?TEST_MODULE:generate(?TEST_BIN, 12837272, 128253, Config, undefined), self()} + end), + timer:sleep(200), %% give some time to start the miner OS pid + %% We did create a new one. + ?assertNotMatch([], os:cmd("ps -e | grep mean29- | grep -v grep")), + exit(Pid, shutdown), + timer:sleep(1000), %% give it some time to kill the miner OS pid + ?assertMatch([], os:cmd("ps -e | grep mean29- | grep -v grep")) + end} + ]. + +% This code is partially from aec_conductor + +spawn_worker(Fun) -> + Wrapper = wrap_worker_fun(Fun), + {Pid, _Ref} = spawn_monitor(Wrapper), + receive + {worker_reply, Pid, Res} -> + Res + end. + +prebuilt_miner_test_() -> + [{"Err if absent prebuilt miner", + fun() -> + Target = ?HIGHEST_TARGET_SCI, + Nonce = 1, + Config = prebuilt_cuckoo_pow(), + Res = spawn_worker(fun() -> ?TEST_MODULE:generate(?TEST_BIN, Target, Nonce, Config, undefined) end), + ?assertMatch({error,{runtime,{port_error,{error,enoent}}}}, Res) + end} + ]. + +wrap_worker_fun(Fun) -> + Server = self(), + fun() -> + Server ! {worker_reply, self(), Fun()} + end. + +fast_and_deterministic_cuckoo_pow() -> + ?TEST_MODULE:config(<<"mean15-generic">>, <<"aecuckoo">>, <<>>, false, 10, + ?EDGE_BITS_15, undefined). + +prebuilt_cuckoo_pow() -> + ?TEST_MODULE:config(<<"nonexistingminer">>, <<"aecuckooprebuilt">>, <<>>, + false, 1, ?EDGE_BITS_15, undefined). + +default_cuckoo_pow() -> + ?TEST_MODULE:config(<<"mean29-generic">>, <<"aecuckoo">>, <<>>, false, 1, + ?EDGE_BITS_29, undefined). + diff --git a/test/aeminer_pow_tests.erl b/test/aeminer_pow_tests.erl new file mode 100644 index 0000000..54097bb --- /dev/null +++ b/test/aeminer_pow_tests.erl @@ -0,0 +1,162 @@ +%%%============================================================================= +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Unit tests for the aeminer_pow module +%%% @end +%%%============================================================================= +-module(aeminer_pow_tests). + +-include_lib("eunit/include/eunit.hrl"). + +-define(TEST_MODULE, aeminer_pow). + +-include("aeminer.hrl"). + +conversion_test_() -> + {setup, + fun setup/0, + fun teardown/1, + [{"Integer to scientific conversion", + fun() -> + %% 02: shifted up 2 bytes to reach the [0x7fffff, 0x008000] range, + %% 8: sign as shifted up, 10000: significand + ?assertEqual(16#01010000, ?TEST_MODULE:integer_to_scientific(1)), + %% 01: shifted up 1 byte, 8: shifted up, 0ff00: significand + ?assertEqual(16#0200ff00, ?TEST_MODULE:integer_to_scientific(255)), + ?assertEqual(16#02010000, ?TEST_MODULE:integer_to_scientific(256)), + ?assertEqual(16#02010100, ?TEST_MODULE:integer_to_scientific(257)), + %% iput: 1 more than the largest possible significand: + %% shifted up 1 byte, the smallest possible significand + ?assertEqual(16#04008000, ?TEST_MODULE:integer_to_scientific(16#800000)), + %% same result: underflow + ?assertEqual(16#04008000, ?TEST_MODULE:integer_to_scientific(16#800001)), + %% example from BitCoin Wiki: + %% https://en.bitcoin.it/wiki/Difficulty#How_is_difficulty_calculated.3F_What_is_the_difference_between_bdiff_and_pdiff.3F: (256-bit hash: 64 hex digits) + ?assertEqual(16#1b0404cb, + ?TEST_MODULE:integer_to_scientific( + 16#00000000000404CB000000000000000000000000000000000000000000000000)), + %% highest possible target in bitcoin + ?assertEqual(16#1d00ffff, + ?TEST_MODULE:integer_to_scientific(16#00000000FFFF0000000000000000000000000000000000000000000000000000)), + %% highest expressible number + ?assertEqual(?HIGHEST_TARGET_SCI, + ?TEST_MODULE:integer_to_scientific(?HIGHEST_TARGET_INT)) + end}, + {"Scientific to integer conversion", + fun() -> ?assertEqual(1, ?TEST_MODULE:scientific_to_integer(16#01010000)), + ?assertEqual(255, ?TEST_MODULE:scientific_to_integer(16#0200ff00)), + ?assertEqual(16#800000, ?TEST_MODULE:scientific_to_integer(16#04008000)), + ?assertEqual(?HIGHEST_TARGET_INT, + aeminer_pow:scientific_to_integer(?HIGHEST_TARGET_SCI)) + end}, + {"Integer to scientific and back", + fun() -> + %% can be converted w/o losing accuracy + ?assertEqual(1, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(1))), + ?assertEqual(255, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(255))), + ?assertEqual(256, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(256))), + %% losing accuracy (last digit: 257 = 1 0000 0001_2) + ?assertEqual(257, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(257))), + %% can be converted w/o losing accuracy + ?assertEqual(16#800000, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(16#800000))), + %% can be converted w/o losing accuracy + ?assertEqual(16#800000, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(16#800001))), + %% can be converted w/o losing accuracy + Num1 = 16#00000000000404CB000000000000000000000000000000000000000000000000, + ?assertEqual(Num1, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(Num1))), + Num2 = 16#00000000FFFF0000000000000000000000000000000000000000000000000000, + %% 0x1230000 = 1 0010 0011 0000 0000 0000_2, we lose the last 1 in conversion + ?assertEqual(Num2, ?TEST_MODULE:scientific_to_integer( + ?TEST_MODULE:integer_to_scientific(Num2))) + end}, + {"Testing difficulty", + fun() -> + %%---------------------------------------------------------------------- + %% More than 3 nonzero bytes + %%---------------------------------------------------------------------- + + ?assertEqual(true, ?TEST_MODULE:test_target( + <<0,0,0,0,0,16#04,16#04,16#ca, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>, + 16#1b0404cb)), + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,0,0,0,16#04,16#04,16#cc, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>, + 16#1b0404cb)), + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,1,0,0,16#04,16#04,16#ca, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>, + 16#1b0404cb)), + %%---------------------------------------------------------------------- + %% Less than 3 nonzero bytes + %%---------------------------------------------------------------------- + + %% 0403 < 0404 + ?assertEqual(true, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,16#04,16#03>>, + 16#02040400)), + %% 0404 < 0404 fails + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,16#04,16#04>>, + 16#02040400)), + %% 0405 < 0404 fails + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,16#04,16#05>>, + 16#02040400)), + %% hide a 1 among zeros + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,16#04,16#03>>, + 16#020404cb)), + %% 03 < 04 + ?assertEqual(true, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16#03>>, + 16#01040000)), + %% 04 < 04 fails + ?assertEqual(false, ?TEST_MODULE:test_target( + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,16#04>>, + 16#01040000)), + + %%---------------------------------------------------------------------- + %% Exp > size of binary + %%---------------------------------------------------------------------- + + %% fffe < ffff + ?assertEqual(true, ?TEST_MODULE:test_target( + <<16#ff,16#fe,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0>>, + 16#2100ffff)), + %% fffffe < ffff00 fails + ?assertEqual(false, ?TEST_MODULE:test_target( + <<16#ff,16#ff,16#fe,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0>>, + 16#2100ffff)) + + end}, + {"Threshold to difficulty", + fun() -> + %% More than 3 nonzero bytes + Diff = ?TEST_MODULE:target_to_difficulty(16#1b0404cb), + ?assert(Diff == 1175073517793766964014) + end} + ] + }. + +setup() -> + application:start(crypto). + +teardown(_) -> + application:stop(crypto). +