PT-163387907 Move mining related code from ae node #18

Merged
zxq9 merged 2 commits from initial into master 2019-02-12 22:57:49 +09:00
14 changed files with 1400 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
_build
c_src
priv
ebin

29
Makefile Normal file
View File

@ -0,0 +1,29 @@
REBAR = ./rebar3
.PHONY: all dialyzer ct eunit clean distclean console
all:
$(REBAR) compile
doc:
$(REBAR) doc
dialyzer:
$(REBAR) dialyzer
ct: all
$(REBAR) ct --suite=test/aecuckoo_SUITE
eunit:
$(REBAR) eunit --module=aeminer_pow_tests,aeminer_pow_cuckoo_tests
clean:
$(REBAR) clean
rm -rf doc/*
distclean: clean
rm -rf _build
console:
$(REBAR) shell

11
include/aeminer.hrl Normal file
View File

@ -0,0 +1,11 @@
-define(HIGHEST_TARGET_SCI, 16#2100ffff).
-define(HIGHEST_TARGET_INT, 16#ffff000000000000000000000000000000000000000000000000000000000000).
-define(DIFFICULTY_INTEGER_FACTOR, 16#1000000).
-define(MAX_NONCE, 16#ffffffffffffffff).
-define(SOLUTION_SIZE, 42).

32
rebar.config Normal file
View File

@ -0,0 +1,32 @@
{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"}}}},
%% 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"}}}
]}.
{plugins, [{rebar_aecuckooprebuilt_dep, {git, "https://github.com/aeternity/rebar3-cuckoo-prebuilt-plugin.git",
{ref, "2b2f3b3cf969ee91ba41d8351f3808530a8bf28e"}}}
]}.
{profiles, [{test, [{deps, [{meck, "0.8.12"}]}]}]}.
{dialyzer, [{warnings, [unknown]},
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto]}
]}.

23
rebar.lock Normal file
View File

@ -0,0 +1,23 @@
{"1.1.0",
[{<<"aecuckoo">>,
{git,"https://github.com/aeternity/aecuckoo.git",
{ref,"fa3d13e8c7003589153223f634c851d389b61b93"}},
0},
{<<"aecuckooprebuilt">>,
{aecuckooprebuilt_app_with_priv_from_git,
{git,"https://github.com/aeternity/cuckoo-prebuilt.git",
{ref,"90afb699dc9cc41d033a7c8551179d32b3bd569d"}}},
0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
0},
{<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1},
{<<"lager">>,
{git,"https://github.com/erlang-lager/lager.git",
{ref,"69b4ada2341b8ab2cf5c8e464ac936e5e4a9f62b"}},
0}]}.
[
{pkg_hash,[
{<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}]}
].

BIN
rebar3 Executable file

Binary file not shown.

18
src/aeminer.app.src Normal file
View File

@ -0,0 +1,18 @@
{application, aeminer,
[{description, "Aeternity miner"},
{vsn, "1.0.0"},
{registered, []},
{applications,
[kernel,
stdlib,
lager,
enacl,
aecuckoo,
aecuckooprebuilt
]},
{env,[]},
{modules, []},
{maintainers, []},
{licenses, []},
{links, []}
]}.

View File

@ -0,0 +1,17 @@
-module(aeminer_blake2b_256).
-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.

172
src/aeminer_pow.erl Normal file
View File

@ -0,0 +1,172 @@
%%%=============================================================================
%%% @copyright 2019, Aeternity Anstalt
%%% @doc
%%% Common functions and behaviour for Proof of Work
%%% @end
%%%=============================================================================
-module(aeminer_pow).
-export([integer_to_scientific/1,
next_nonce/2,
pick_nonce/0,
scientific_to_integer/1,
target_to_difficulty/1,
test_target/2,
trim_nonce/2]).
-export_type([nonce/0,
int_target/0,
sci_target/0,
bin_target/0,
difficulty/0,
instance/0
]).
-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).
%%------------------------------------------------------------------------------
%% Target threshold and difficulty
%%
%% The mining rate is controlled by setting a target threshold. The PoW nonce
%% is accepted if a hash value (the hash of the header for SHA-256, the hash of
%% the solution graph for Cuckoo Cycleas, converted to an integer) is below
%% this target threshold.
%%
%% A lower target represents a harder task (requiers the hash to start with a
%% number of zeros).
%%
%% The target thershold relates to another value: the diifculty. This is
%% proportional to the hardness of the PoW task:
%%
%% Difficulty = <Target of difficulty 1> / Target,
%%
%% a floating point value.
%% Bitcoin uses 0x00000000FFFF0000000000000000000000000000000000000000000000000000
%% as Difficulty 1 target (0x1d00ffff in scientific notation, see below). For
%% Cuckoo Cycle we need a lighter filtering of solutions than for SHA-256 as the
%% basic algorithm is much slower than a simple hash generation, so we use the
%% largest possible value:
%% 0xFFFF000000000000000000000000000000000000000000000000000000000000 (0x2100ffff
%% in scientific notation) as difficulty 1.
%%
%% We store the current target threshold in the block header in scientific notation.
%% Difficulty is used to select the winning fork of new blocks: the difficulty of a
%% chain of blocks is the sum of the diffculty of each block.
%%
%% Integers represented in scientific notation:
%% 2^24 * <base-2 exponent + 3> + the first 3 most significant bytes (i.e.,
%% the significand, see https://en.wikipedia.org/wiki/Significand).
%% The + 3 corresponds to the length of the
%% significand (i.e., the int value is 0.<significand> * 8^<exponent>).
%% https://en.bitcoin.it/wiki/Difficulty#How_is_difficulty_stored_in_blocks.3F)
%%------------------------------------------------------------------------------
%%%=============================================================================
%%% API
%%%=============================================================================
-spec scientific_to_integer(sci_target()) -> int_target().
scientific_to_integer(S) ->
{Exp, Significand} = break_up_scientific(S),
E3 = Exp - 3,
case Exp >= 0 of
true ->
Significand bsl (8 * E3);
false ->
Significand bsr (-8 * E3)
end.
-spec integer_to_scientific(int_target()) -> sci_target().
integer_to_scientific(I) ->
%% Find exponent and significand
{Exp, Significand} = integer_to_scientific(I, 3),
case Exp >= 0 of
true ->
%% 1st byte: exponent, next 3 bytes: significand
(Exp bsl 24) + Significand;
false ->
%% flip sign bit in significand
((-Exp) bsl 24) + 16#800000 + Significand
end.
%% 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_target()) -> difficulty().
target_to_difficulty(SciTgt) ->
(?DIFFICULTY_INTEGER_FACTOR * ?HIGHEST_TARGET_INT)
div scientific_to_integer(SciTgt).
-spec next_nonce(nonce(), config()) -> nonce().
next_nonce(Nonce, Cfg) ->
Nonce + aeminer_pow_cuckoo:repeats(Cfg).
-spec pick_nonce() -> nonce().
pick_nonce() ->
rand:uniform(?NONCE_RANGE) band ?MAX_NONCE.
-spec trim_nonce(nonce(), config()) -> nonce().
trim_nonce(Nonce, Cfg) ->
case Nonce + aeminer_pow_cuckoo:repeats(Cfg) < ?MAX_NONCE of
true -> Nonce;
false -> 0
end.
%%------------------------------------------------------------------------------
%% Test if binary is under the target threshold
%%------------------------------------------------------------------------------
-spec test_target(bin_target(), sci_target()) -> boolean().
test_target(Bin, Target) ->
Threshold = scientific_to_integer(Target),
<<Val:32/big-unsigned-integer-unit:8>> = Bin,
Val < Threshold.
%% TODO: get target
%Bin = solution_to_binary(lists:sort(Soln), NodeSize * 8, <<>>),
%%Hash = aec_hash:hash(pow, Bin),
%%<<Val:32/big-unsigned-integer-unit:8>> = Hash,
%%Val
%%%=============================================================================
%%% Internal functions
%%%=============================================================================
integer_to_scientific(I, Exp) when I > 16#7fffff ->
integer_to_scientific(I bsr 8, Exp + 1);
integer_to_scientific(I, Exp) when I < 16#008000, I > 16#000000 ->
integer_to_scientific(I bsl 8, Exp - 1);
integer_to_scientific(I, Exp) ->
%% Add the number of bytes in the significand
{Exp, I}.
%% Return the exponent and significand of a sci_target().
break_up_scientific(S) ->
SigMask = (1 bsl 24) - 1,
Exp = ((S bxor SigMask) bsr 24),
Significand = S band SigMask,
%% Remove the sign bit, apply to exponent
case 16#800000 band Significand of
0 ->
{Exp, Significand};
_ ->
{-Exp, Significand - 16#800000}
end.

589
src/aeminer_pow_cuckoo.erl Normal file
View File

@ -0,0 +1,589 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% A library providing Cuckoo Cycle PoW generation and verification.
%%% Using (as an independent OS process) the C/C++ Cuckoo Cycle implementation of
%%% John Tromp: https://github.com/tromp/cuckoo
%%% White paper: https://github.com/tromp/cuckoo/blob/master/doc/cuckoo.pdf?raw=true
%%%
%%% We use erlang:open_port/2 to start an OS process that runs this C code.
%%% The reasons for using this over os:cmd and erlexec are:
%%% - no additional C-based dependency which is Unix-focused and therefore hard to port to Windows
%%% - widely tested and multiplatform-enabled solution
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeminer_pow_cuckoo).
-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_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).
-export([verify_proof_/3,
solution_to_binary/2
]).
-endif.
-include("aeminer.hrl").
-type hashable() :: aeminer_blake2b_256:hashable().
-type nonce() :: aeminer_pow:nonce().
-type sci_target() :: aeminer_pow:sci_target().
-type instance() :: aeminer_pow:instance()
| undefined.
-type exec() :: string().
-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
%%%=============================================================================
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
%%
%% According to my experiments, increasing the number of trims from the default
%% 7 in John Tromp's code does not really affect speed, reducing it causes failure.
%%
%% Measured execution times (seconds) for 7 trims for threads:
%% 1: 44.61 46.92 46.41
%% 2: 15.17 15.75 19.19
%% 3: 9.35 10.92 9.73
%% 4: 10.66 7.83 10.02
%% 5: 7.41 7.47 7.32
%% 10: 7.27 6.70 6.38
%% 20: 6.25 6.74 6.41
%%
%% Very slow below 3 threads, not improving significantly above 5, let us take 5.
%%------------------------------------------------------------------------------
-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, Config, Instance) of
{ok, Nonce1, Soln} ->
{ok, {Nonce1, Soln}};
{error, no_value} ->
?debug("No cuckoo solution found", []),
{error, no_solution};
{error, Rsn} ->
%% Exec failed (segfault, not found, etc.): let miner decide
{error, {runtime, Rsn}}
end.
%%------------------------------------------------------------------------------
%% Proof of Work verification (with difficulty check)
%%------------------------------------------------------------------------------
-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(Soln, Target, EdgeBits) of
true ->
verify_proof(Hash, Nonce, Soln, EdgeBits);
false ->
false
end.
%% Internal functions.
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).
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),
try exec_run(MinerBin, MinerBinDir, Args) of
{ok, Port, OsPid} ->
wait_for_result(#state{os_pid = OsPid,
port = Port,
buffer = [],
target = Target,
edge_bits = EdgeBits,
parser = fun parse_generation_result/2});
{error, _Rsn} = Err ->
Err
catch
C:E ->
{error, {unknown, {C, E}}}
after
process_flag(trap_exit, Old),
receive
{'EXIT',_From, shutdown} -> exit(shutdown)
after 0 -> ok
end
end.
hex_string(S) ->
Bin = list_to_binary(S),
lists:flatten([io_lib:format("~2.16.0B", [B]) || <<B:8>> <= Bin]).
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}).
%%------------------------------------------------------------------------------
%% @doc
%% Proof of Work verification (difficulty check should be done before calling
%% this function)
%% @end
%%------------------------------------------------------------------------------
verify_proof(Hash, Nonce, Solution, EdgeBits) ->
%% Cuckoo has an 80 byte header, we have to use that as well
%% packed Hash + Nonce = 56 bytes, add 24 bytes of 0:s
Header0 = pack_header_and_nonce(Hash, Nonce),
Header = <<(list_to_binary(Header0))/binary, 0:(8*24)>>,
verify_proof_(Header, Solution, EdgeBits).
verify_proof_(Header, Solution, EdgeBits) ->
{K0, K1, K2, K3} = aeminer_siphash24:create_keys(Header),
EdgeMask = (1 bsl EdgeBits) - 1,
try
%% Generate Uv pairs representing endpoints by hashing the proof
%% XOR points together: for a closed cycle they must match somewhere
%% making one of the XORs zero.
{Xor0, Xor1, _, Uvs} =
lists:foldl(
fun(N, _) when N > EdgeMask ->
throw(?POW_TOO_BIG(N));
(N, {_Xor0, _Xor1, PrevN, _Uvs}) when N =< PrevN ->
throw(?POW_TOO_SMALL(N, PrevN));
(N, {Xor0C, Xor1C, _PrevN, UvsC}) ->
Uv0 = sipnode(K0, K1, K2, K3, N, 0, EdgeMask),
Uv1 = sipnode(K0, K1, K2, K3, N, 1, EdgeMask),
{Xor0C bxor Uv0, Xor1C bxor Uv1, N, [{Uv0, Uv1} | UvsC]}
end, {16#0, 16#0, -1, []}, Solution),
case Xor0 bor Xor1 of
0 ->
%% check cycle
case check_cycle(Uvs) of
ok -> true;
{error, E} -> throw(E)
end;
_ ->
%% matching endpoints imply zero xors
throw(?POW_NON_MATCHING)
end
catch
throw:{error, Rsn} ->
?info("Proof verification failed for ~p: ~p", [Solution, Rsn]),
false
end.
sipnode(K0, K1, K2, K3, Proof, UOrV, EdgeMask) ->
SipHash = aeminer_siphash24:hash(K0, K1, K2, K3, 2*Proof + UOrV) band EdgeMask,
(SipHash bsl 1) bor UOrV.
check_cycle(Nodes0) ->
Nodes = lists:keysort(2, Nodes0),
{Evens0, Odds} = lists:unzip(Nodes),
Evens = lists:sort(Evens0), %% Odd nodes are already sorted...
UEvens = lists:usort(Evens),
UOdds = lists:usort(Odds),
%% Check that all nodes appear exactly twice (i.e. each node has
%% exactly two edges).
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};
true ->
[{X0, Y0}, {X1, Y0} | Nodes1] = Nodes,
check_cycle(X0, X1, Nodes1)
end.
%% If we reach the end in the last step everything is fine
check_cycle(X, X, []) ->
ok;
%% If we reach the end too early the cycle is too short
check_cycle(X, X, _) ->
{error, ?POW_SHORT_CYCLE};
check_cycle(XEnd, XNext, Nodes) ->
%% Find the outbound edge for XNext and follow that edge
%% to an odd node and back again to NewXNext
case find_node(XNext, Nodes, []) of
Err = {error, _} -> Err;
{XNext, NewXNext, NewNodes} -> check_cycle(XEnd, NewXNext, NewNodes)
end.
find_node(_, [], _Acc) ->
{error, ?POW_DEAD_END};
find_node(X, [{X, Y}, {X1, Y} | Nodes], Acc) ->
{X, X1, Nodes ++ Acc};
find_node(X, [{X1, Y}, {X, Y} | Nodes], Acc) ->
{X, X1, Nodes ++ Acc};
find_node(X, [{X, _Y} | _], _Acc) ->
{error, ?POW_DEAD_END};
find_node(X, [N1, N2 | Nodes], Acc) ->
find_node(X, Nodes, [N1, N2 | Acc]).
%%------------------------------------------------------------------------------
%% @doc
%% Creates the Cuckoo buffer (hex encoded) from a base64-encoded hash and a
%% uint64 nonce.
%% Since this hash is purely internal, we don't use api encoding.
%% @end
%%------------------------------------------------------------------------------
-spec pack_header_and_nonce(binary(), aeminer_pow:nonce()) -> string().
pack_header_and_nonce(Hash, Nonce) when byte_size(Hash) == 32 ->
%% Cuckoo originally uses 32-bit nonces inserted at the end of its 80-byte buffer.
%% This buffer is hashed into the keys used by the main algorithm.
%%
%% We insert our 64-bit Nonce right after the hash of the block header We
%% base64-encode both the hash of the block header and the nonce and pass
%% the resulting command-line friendly string with the -h option to Cuckoo.
%%
%% The SHA256 hash is 32 bytes (44 chars base64-encoded), the nonce is 8 bytes
%% (12 chars base64-encoded). That leaves plenty of room (80 - 56 = 24
%% bytes) for cuckoo to put its nonce (which will be 0 in our case) in.
%%
%% (Base64 encoding: see RFC 3548, Section 3:
%% https://tools.ietf.org/html/rfc3548#page-4
%% converts every triplet of bytes to 4 characters: from N bytes to 4*ceil(N/3)
%% bytes.)
%%
%% Like Cuckoo, we use little-endian for the nonce here.
NonceStr = base64:encode_to_string(<<Nonce:64/little-unsigned-integer>>),
HashStr = base64:encode_to_string(Hash),
%% Cuckoo will automatically fill bytes not given with -h option to 0, thus
%% we need only return the two base64 encoded strings concatenated.
%% 44 + 12 = 56 bytes
HashStr ++ NonceStr.
%%------------------------------------------------------------------------------
%% @doc
%% Receive and process notifications about the fate of the process and its
%% output. The receieved stdout tends to be in large chunks, we keep a buffer
%% for the last line fragment w/o NL.
%% @end
%%------------------------------------------------------------------------------
wait_for_result(#state{os_pid = OsPid, port = Port, buffer = Buffer} = State) ->
receive
{Port, {data, Msg}} ->
Str = binary_to_list(Msg),
{Lines, NewBuffer} = handle_fragmented_lines(Str, Buffer),
(State#state.parser)(Lines, State#state{buffer = NewBuffer});
{Port, {exit_status, 0}} ->
wait_for_result(State);
{'EXIT',_From, shutdown} ->
%% Someone is telling us to stop
stop_execution(OsPid),
exit(shutdown);
{'EXIT', Port, normal} ->
%% Process ended but no value found
{error, no_value};
_Other ->
wait_for_result(State)
end.
%%------------------------------------------------------------------------------
%% @doc
%% Prepend the first new incoming line with the last line fragment stored
%% in Buffer and replace Buffer with the possible new line fragment at the
%% end of Str.
%% @end
%%------------------------------------------------------------------------------
handle_fragmented_lines(Str, Buffer) ->
Lines = string:tokens(Str, "\n"),
%% Add previous truncated line if present to first line
Lines2 =
case Buffer of
[] ->
Lines;
_ ->
[Line1 | More] = Lines,
[Buffer ++ Line1 | More]
end,
%% Keep last fraction (w/o NL) in buffer
case lists:last(Str) of
$\n ->
{Lines2, ""};
_ ->
{L3, [Bf]} = lists:split(length(Lines) - 1, Lines2),
{L3, Bf}
end.
%%------------------------------------------------------------------------------
%% @doc
%% Parse miner output
%% @end
%%------------------------------------------------------------------------------
parse_generation_result([], State) ->
wait_for_result(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, EdgeBits)} of
{42, true} ->
stop_execution(OsPid),
case parse_nonce_str(NonceStr) of
{ok, Nonce} ->
?debug("Solution found: ~p", [Soln]),
{ok, Nonce, Soln};
Err = {error, _} ->
?debug("Bad nonce: ~p", [Err]),
Err
end;
{N, _} when N /= 42 ->
?debug("Solution has wrong length (~p) should be 42", [N]),
%% No nonce in solution, old miner exec?
stop_execution(OsPid),
{error, bad_miner};
{_, false} ->
%% failed to meet target: go on, we may find another solution
?debug("Failed to meet target (~p)", [Target]),
parse_generation_result(Rest, State)
end;
parse_generation_result([_Msg | T], State) ->
parse_generation_result(T, State).
parse_nonce_str(S) ->
try {ok, list_to_integer(string:trim(S, both, "()"), 16)}
catch _:_ -> {error, bad_nonce} end.
%%------------------------------------------------------------------------------
%% @doc
%% Stop the OS process
%% @end
%%------------------------------------------------------------------------------
stop_execution(OsPid) ->
exec_kill(OsPid),
?debug("Mining OS process ~p stopped", [OsPid]),
ok.
%%------------------------------------------------------------------------------
%% @doc
%% The Cuckoo solution is a list of uint32 integers unless the graph size is
%% greater than 33 (when it needs u64 to store). Hash result for difficulty
%% control accordingly.
%% @end
%% 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 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.
exec_run(Cmd, Dir, Args) ->
PortSettings = [binary,
exit_status,
hide,
in,
overlapped_io,
stderr_to_stdout,
{args, Args},
{cd, Dir}
],
PortName = {spawn_executable, os:find_executable(Cmd, Dir)},
try
Port = erlang:open_port(PortName, PortSettings),
case erlang:port_info(Port, os_pid) of
{os_pid, OsPid} ->
?debug("External mining process started with OS pid ~p", [OsPid]),
{ok, Port, OsPid};
undefined ->
?warning("External mining process finished before ~p could acquire the OS pid", [?MODULE]),
{ok, Port, undefined}
end
catch
C:E ->
{error, {port_error, {C, E}}}
end.
exec_kill(undefined) ->
ok;
exec_kill(OsPid) ->
case is_unix() of
true ->
os:cmd(io_lib:format("kill -9 ~p", [OsPid])),
ok;
false ->
os:cmd(io_lib:format("taskkill /PID ~p /T /F", [OsPid])),
ok
end.
is_unix() ->
case erlang:system_info(system_architecture) of
"win32" ->
false;
_ ->
true
end.
%%------------------------------------------------------------------------------
%% White paper, section 9: rather than adjusting the nodes/edges ratio, a
%% hash-based target is suggested: the sha256 hash of the cycle nonces
%% is restricted to be under the target value (0 < target < 2^256).
%%------------------------------------------------------------------------------
test_target(Soln, Target, EdgeBits) ->
test_target1(Soln, Target, get_node_size(EdgeBits)).
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).
%%------------------------------------------------------------------------------
%% Convert solution (a list of 42 numbers) to a binary
%% in a languauge-independent way
%%------------------------------------------------------------------------------
solution_to_binary(Soln, Bits) ->
solution_to_binary(Soln, Bits, <<>>).
solution_to_binary([], _Bits, Acc) ->
Acc;
solution_to_binary([H | T], Bits, Acc) ->
solution_to_binary(T, Bits, <<Acc/binary, H:Bits>>).

100
src/aeminer_siphash24.erl Normal file
View File

@ -0,0 +1,100 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% SipHash-2-4 without standard IV xor and specialized to precomputed key and 8 byte nonces
%%% @end
%%%=============================================================================
-module(aeminer_siphash24).
-export([create_keys/1,
hash/5]).
-ifdef(TEST).
-compile([export_all, nowarn_export_all]).
-endif.
-define(MAX64, 16#ffffffffffffffff).
-type hashable() :: integer().
-type siphash_key() :: integer().
-type sip_quadruple() :: {integer(), integer(), integer(), integer()}.%% in fact, uint64
-export_type([hashable/0,
siphash_key/0]).
%%%=============================================================================
%%% API
%%%=============================================================================
-spec create_keys(binary()) ->
{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),
<<K0:64/little-unsigned,
K1:64/little-unsigned,
K2:64/little-unsigned,
K3:64/little-unsigned>> = AuxHash,
{K0, K1, K2, K3}.
-spec hash(siphash_key(), siphash_key(), siphash_key(), siphash_key(), hashable()) -> hashable().
hash(K0, K1, K2, K3, Nonce) ->
V0 = K0,
V1 = K1,
V2 = K2,
V3 = K3 bxor Nonce,
{V01, V11, V21, V31} =
sip_round(sip_round(sip_round(sip_round(sip_change(Nonce, sip_round(sip_round({V0, V1, V2, V3}))))))),
rotl64(((V01 bxor V11) bxor (V21 bxor V31)), 17) band 16#ffffffffffffffff.
%%%=============================================================================
%%% Internal functions
%%%=============================================================================
%% 1:
%% v0 += v1; v2 += v3; v1 = ROTL(v1,13); \
%% v3 = ROTL(v3,16);
%% 2:
%% v1 ^= v0; v3 ^= v2; \
%% v0 = ROTL(v0,32);
%% 3:
%% v2 += v1; v0 += v3; \
%% v1 = ROTL(v1,17); v3 = ROTL(v3,21); \
%% 4:
%% v1 ^= v2; v3 ^= v0; v2 = ROTL(v2,32); \
-spec sip_round(sip_quadruple()) -> sip_quadruple().
sip_round({_V0, _V1, _V2, _V3} = Vs) ->
sip_round4(sip_round3(sip_round2(sip_round1(Vs)))).
-spec sip_round1(sip_quadruple()) -> sip_quadruple().
sip_round1({V0, V1, V2, V3}) ->
{(V0 + V1) band ?MAX64, rotl64(V1, 13), (V2 + V3) band ?MAX64, rotl64(V3, 16)}.
-spec sip_round2(sip_quadruple()) -> sip_quadruple().
sip_round2({V0, V1, V2, V3}) ->
{rotl64(V0, 32), V1 bxor V0, V2, V3 bxor V2}.
-spec sip_round3(sip_quadruple()) -> sip_quadruple().
sip_round3({V0, V1, V2, V3}) ->
{(V0 + V3) band ?MAX64, rotl64(V1, 17), (V2 + V1) band ?MAX64, rotl64(V3, 21)}.
-spec sip_round4(sip_quadruple()) -> sip_quadruple().
sip_round4({V0, V1, V2, V3}) ->
{V0, V1 bxor V2, rotl64(V2, 32), V3 bxor V0}.
-spec sip_change(integer(), sip_quadruple()) -> sip_quadruple().
sip_change(Nonce, {V0, V1, V2, V3}) ->
{V0 bxor Nonce, V1, V2 bxor 16#ff, V3}.
-spec rotl64(integer(), integer()) -> integer().
rotl64(X, B) ->
((X bsl B) bor (X bsr (64 - B))) band 16#ffffffffffffffff.

65
test/aecuckoo_SUITE.erl Normal file
View File

@ -0,0 +1,65 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc Basic sanity checks and examples on Cuckoo cycle PoW executables.
%%% @end
%%%-------------------------------------------------------------------
-module(aecuckoo_SUITE).
%% common_test exports
-export(
[
all/0, groups/0,
init_per_group/2, end_per_group/2
]).
%% test case exports
-export([smoke_test/1]).
-include_lib("common_test/include/ct.hrl").
-define(TEST_MODULE, aecuckoo).
all() ->
[
{group, smoke_tests_15}
].
groups() ->
[
{smoke_tests_15, [{group, mean15},
{group, lean15}]},
{mean15, [smoke_test]},
{lean15, [smoke_test]}
].
init_per_group(smoke_tests_15, Config) ->
[{nonce, 91} | Config];
init_per_group(mean15, Config) ->
[{miner, 'mean15-generic'} | Config];
init_per_group(lean15, Config) ->
[{miner, 'lean15-generic'} | Config].
end_per_group(_Group, _Config) ->
ok.
smoke_test(Config) ->
Nonce = ?config(nonce, Config),
Miner = ?config(miner, Config),
MinBin = ?TEST_MODULE:bin(atom_to_list(Miner)),
Cmd = io_lib:format("'~s' -n ~B | grep '^Solution'", [MinBin, Nonce]),
ct:log("Command: ~s~n", [Cmd]),
CmdRes = nonl(os:cmd(Cmd)),
ct:log("Command result: ~s~n", [CmdRes]),
Solution = lists:map(fun(X) -> list_to_integer(X, 16) end, tl(string:tokens(CmdRes, " "))),
HeaderEquivalent = <<0:(44*8), (base64:encode(<<Nonce:64/little-unsigned-integer>>))/binary, 0:(24*8)>>,
42 = length(Solution),
true = aeminer_pow_cuckoo:verify_proof_(HeaderEquivalent, Solution, 15),
ok.
nonl([$\n]) -> [];
nonl([]) -> [];
nonl([H|T]) -> [H|nonl(T)].

View File

@ -0,0 +1,178 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Unit tests for the aeminer_pow_cuckoo module
%%% @end
%%%=============================================================================
-module(aeminer_pow_cuckoo_tests).
-include_lib("eunit/include/eunit.hrl").
-include("aeminer.hrl").
-define(TEST_MODULE, aeminer_pow_cuckoo).
-define(TEST_BIN, <<"wsffgujnjkqhduihsahswgdf">>).
-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).

162
test/aeminer_pow_tests.erl Normal file
View File

@ -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).