[SUPPORT] æternity Stratum Implementation

If you have any questions regarding æternity’s Stratum implementation, share them here and our devs will provide you with answers :slight_smile:

Hi Vlad,

I’m trying to implement stratum protocol and share validation on our servers. The only protocol documentation I have is from https:__github.com/aeternity/protocol/blob/stratum/STRATUM.md, but this one is from stratum branch and it is not on master branch an it differs a bit from the GMiner protocol trace so I use GMiner protocol trace as the reference stratum protocol implementation.

Trace {"id":1,"method":"mining.subscribe","params":["GMiner/1.34",null,"ae.f2pool.com" - Pastebin.com

But I have big problems problems validating this submit myself even though it was confirmed by f2pool. As a reference for the validation I took erlang code from https:__github.com/aeternity/aeternity/tree/dev-stratum2 on stratum2 branch as it does not exist on master branch. In the file aec_pow_cuckoo.erl there is section that describes procedure

%%------------------------------------------------------------------------------
%% @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(), aec_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.

So I’m doing it like this // Cuckoo Cycle, a memory-hard proof-of-work// Copyright (c) 2013-2016 John Tr - Pastebin.com

It just doesn’t work. What am I missing. It is even more confusing for me as methods verify_proof and generate in the aec_pow_cuckoo.erl sim to assemble input for the siphash keys generation a bit differently.

I always get result like

‘AAD9nukZNiOpwzH0LsT1MF0WzdMeweiU5VkcefavhRU=AAD8fgAAAPQ=’
Verifying size 42 proof for cuckoo30(“”,244)
FAILED due to endpoints don’t match up

Am I missing something. Do I misinterpret strings and values from the attached stratum protocol trace?

1 Like

I will take a look at your code today/tomorrow. In the mean time you can take a look at our implementation work-in-progress

https://github.com/aeternity/aeternity/blob/dev-stratum/apps/aestratum/src/aestratum_session.erl#L281

I point you to the code that verifies the solution. The point is - you can judge by that, how heavily it depends on internal server assumptions regarding targets/timeouts etc.

I will also update tickets re stratum today, so you are welcome to contribute.

1 Like

I checked the part of code you pointed me to which lead me to this part of the code

https://github.com/aeternity/aeternity/blob/dev-stratum/apps/aestratum/src/aestratum_session.erl#L647

but there is no ‘aestratum_miner:verify_proof’ code in the project. Cannot find ‘aestratum_miner’ file.

Next thing I tried is setting up aeternity node and start mining. From aeternity_pow_cuckoo.log I can see you are calling external miner like that

2019-04-18 14:59:43.378 [debug] <0.19673.0>@aeminer_pow_cuckoo:generate:180 Generating solution for data hash <<247,185,22,43,190,186,14,199,74,250,240,47,120,59,88,187,232,99,148,228,109,69,42,61,174,252,128,206,14,100,12,187>> and nonce 5773483257493617322 with target 520133365.
2019-04-18 14:59:43.378 [info] <0.19673.0>@aeminer_pow_cuckoo:generate_int:252 Executing cmd 'mean29-generic -h 97kWK766DsdK+vAveDtYu+hjlORtRSo9rvyAzg5kDLs= -n 5773483257493617322 -r 1 -t 1'

where did you get mean29-generic from? In the GitHub - aeternity/cuckoo: Please open Aeternity-specific issues in https://github.com/aeternity/aeternity/issues . For Cuckoo-specific issues and pull requests, the most effective way is opening those in upstream repository https://github.com/tromp/cuckoo repository there are cuckoo, cuckaroo, cuckatoo implementations. I guess you use cuckoo implementation as other two are for GRIN. In the makefile there is no mean29-generic. There are other mean variants from which I am using mean29x4 that I changed a bit so it can handle 64 bit nonce.

My input data from GMiner stratum protocol trace, which shows confirmed submit

{"id":1,"result":[[["mining.set_difficulty","mining.set_difficulty"],["mining.notify","mining.notify"]],"0000fc7e",4],"error":null}
{"id":null,"method":"mining.notify","params":["2f070fb7bf0b0ce2562","8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e",66424,"1e056bca",false]}
{"id":5,"method":"mining.submit","params":["ak_v4cBSQhjh8gc49XMmrt1ELXJDA8U7sDZVKhLJiAzjPymVFgFQ","2f070fb7bf0b0ce2562","000001a8",["003b5d47","00a70508","00d0aa4a","0238a16a","038653bf","03e91d96","03f4baa8","062ef17e","065d7b41","066fbb1e","079af861","08bd2cf2","0956b89d","0b56fb7f","0c098553","0c6d2c27","0d8c0fd9","0ddcbb1d","0e3eccde","0e464bef","0fb09bef","1267ebb1","129ef8e6","138432b5","144d428b","1484e6b6","14efcfba","158d5352","159f3551","15a07563","160a3efd","17c9b61e","184499bc","1844f434","1919053a","197a9095","1aa04947","1bc3f6e5","1d8b4029","1e6a1fe0","1e7e4380","1f5a2a50"]]}

header hash 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e
xn1 0000fc7e
xn2 000001a8
nonce = xn1 + xn2 → 0000fc7e000001a8

I hex decode header 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e and base64 encode to iCUIX4ge548MnNlMqKcq6TDgJf1Dkx5HYOMgpPAx2I4=
I hex decode nonce 0000fc7e000001a8 to 277618096079272

I try to validate already confirmed submit so there must be solution

./mean29x4_custom -h 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e -n 0000fc7e000001a8
Looking for 42-cycle on cuckoo30("iCUIX4ge548MnNlMqKcq6TDgJf1Dkx5HYOMgpPAx2I4=",277618096079272) with 50% edges
Using 2144MB bucket memory at 105a53000,
1x21MB thread memory at 103762000,
4-way siphash, and 128 buckets.
nonce 424 k0 k1 k2 k3 6654e422413d38c6 6124bcbdaebc5b84 13e8f4520304e4bc ee2e40c49a90f945
genUnodes round  0 size 537046788 rdtsc: 11007392081
genVnodes round  1 size 339371791 rdtsc: 13361543717
trimedges id 0 round  3 size 94103114 rdtsc: 2289746194
trimedges id 0 round  7 size 26454091 rdtsc: 469774500
trimrename id 0 round 14 size 8153637 rdtsc: 288011926 maxnnid 31606
trimrename id 0 round 15 size 7213287 rdtsc: 260470456 maxnnid 28075
trimedges1 id 0 round 31 size 1904458 rdtsc: 21753953
trimedges1 id 0 round 63 size 487092 rdtsc: 13914094
trimrename1 id 0 round 66 size 444992 rdtsc: 20470566 maxnnid 1814
trimrename1 id 0 round 67 size 432158 rdtsc: 19548076 maxnnid 1807
90-cycle found
40-cycle found
240-cycle found
findcycles rdtsc: 129568331
Time: 13037 ms
0 total solutions

No solution found!

What is strange to me is also in the dev-stratum2 branch there is aec_pow_cuckoo.erl file in which there are two methods

generate_int(Hash, Nonce, Target, MinerBinDir, MinerBin, MinerExtraArgs, Config) ->
    Repeats = integer_to_list(get_repeats(Config)),
    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 = [],
                                parser = fun parse_generation_result/2,
                                target = Target});
        {error, _} = E ->
            E
    catch
        C:E ->
            {error, {unknown, {C, E}}}
    after
        process_flag(trap_exit, Old),
        receive
            {'EXIT',_From, shutdown} -> exit(shutdown)
        after 0 -> ok
        end
    end.

and

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} = aeu_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, Reason} ->
            ?info("Proof verification failed for ~p: ~p", [Solution, Reason]),
            false
    end.

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.

Why do you pack hashed_header/nonce differently for mining and for verification?

In the case of the generation the external miner mean29-generic is called

mean29-generic -h 97kWK766DsdK+vAveDtYu+hjlORtRSo9rvyAzg5kDLs= -n 5773483257493617322

and this one I know copies -h part to the beginning of the 80 byte buffer and -n part to the end of the 80 byte buffer and use that as the input for the siphash keys generation.

In the case of verification you base64 encode also nonce part and concatenate it with base64 encoded header and use this as the input for the siphash keys generation.

Why mining and verification is different. Shouldn’t they be the same?

So I also tried this approach

header hash 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e
xn1 0000fc7e
xn2 000001a8
nonce = xn1 + xn2 → 0000fc7e000001a8

I hex decode header 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e and base64 encode to iCUIX4ge548MnNlMqKcq6TDgJf1Dkx5HYOMgpPAx2I4=
I hex decode nonce 0000fc7e000001a8 to 277618096079272 and base64 encode to qAEAAH78AAA=

I concatenated both base64 encoded header and nonce and use this as the entry for the siphash keys generation.

./mean29x4_custom -h 8825085f881ee78f0c9cd94ca8a72ae930e025fd43931e4760e320a4f031d88e -n 0000fc7e000001a8
Looking for 42-cycle on cuckoo30("iCUIX4ge548MnNlMqKcq6TDgJf1Dkx5HYOMgpPAx2I4=qAEAAH78AAA=",0) with 50% edges
Using 2144MB bucket memory at 111349000,
1x21MB thread memory at 101af9000,
4-way siphash, and 128 buckets.
nonce 0 k0 k1 k2 k3 3ca0cf6fa6941e2f 1437253be2620727 48247eae3bcaa355 5e3cfffa3def7796
genUnodes round  0 size 537046129 rdtsc: 12798187105
genVnodes round  1 size 339361015 rdtsc: 13320595345
trimedges id 0 round  3 size 94098207 rdtsc: 2242147049
trimedges id 0 round  7 size 26448057 rdtsc: 477716357
trimrename id 0 round 14 size 8150503 rdtsc: 316602406 maxnnid 31606
trimrename id 0 round 15 size 7212233 rdtsc: 286713499 maxnnid 28108
trimedges1 id 0 round 31 size 1914359 rdtsc: 22830708
trimedges1 id 0 round 63 size 496267 rdtsc: 9412965
trimrename1 id 0 round 66 size 453814 rdtsc: 24614443 maxnnid 1885
trimrename1 id 0 round 67 size 440869 rdtsc: 20049506 maxnnid 1815
24-cycle found
30-cycle found
findcycles rdtsc: 127745385
Time: 13734 ms
0 total solutions

No solutions found!

What am I doing wrong?

1 Like

@kukovec

OK, lets start from the top:

  1. Missing aestratum_miner - please build the whole project - it is in one of the dependencies. I see it here: aestratum_lib/aestratum_miner.erl at master · aeternity/aestratum_lib · GitHub

It re-uses all logic of full node, so it uses one of cuckoo builds (or pre-builds GitHub - aeternity/cuckoo-prebuilt)

mean29 is one of implementations of cuckoo cycle. Cuckaroo or cuckatoo are different algorithms targeted to be more anti asic and asic friendly respectively.

Mean29 must be defualt one to build. It uses more memory and uses CPU.

I need to digest the details about proof of work (or will ask someone for help)

Side note: our general rule is to keep verification intact. Even though there was series of changes to pow client, if we keep verification the same and our tests pass it means that they are in sync.

Btw. before I can help - did you check our tests? They show usually full cycle of life of given feature.

1 Like

Hi @michalzee,

I found out what was the problem. There was difference in how siphash keys were generated. I had the latest master branch of cuckoo cycle from John Tromp which have changed the siphash key generation. Aeternity project uses some older branch. This has happened in this commit working cuckaroo mean cpu miner; osbsolete siphash.h · tromp/cuckoo@1931de4 · GitHub where siphash.h changed to siphash.hpp and it’s implementation. Just for note if somebody else will have this issue.

Br, Marko

1 Like