First commit
This commit is contained in:
commit
3a061f057c
13
rebar.config
Normal file
13
rebar.config
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
{erl_opts, [debug_info]}.
|
||||||
|
{plugins, [rebar3_hex]}.
|
||||||
|
{deps, [
|
||||||
|
{ranch, "2.2.0"},
|
||||||
|
{enoise, {git, "https://git.qpq.swiss/QPQ-AG/enoise.git", {ref, "8acbce9"}}}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{xref_checks, [undefined_function_calls, undefined_functions,
|
||||||
|
locals_not_used,
|
||||||
|
deprecated_function_calls, deprecated_functions]}.
|
||||||
|
|
||||||
|
{dialyzer, [{warnings, [unknown]}]}.
|
17
src/gm_mining_pool_protocol.app.src
Normal file
17
src/gm_mining_pool_protocol.app.src
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
{application, gm_mining_pool_protocol,
|
||||||
|
[{description, "Gajumaru Mining Pool Protocol (server- + client-side)"},
|
||||||
|
{vsn, "0.1.0"},
|
||||||
|
{registered, []},
|
||||||
|
{application,
|
||||||
|
[
|
||||||
|
kernel
|
||||||
|
, stdlib
|
||||||
|
, gmserialization
|
||||||
|
]},
|
||||||
|
{env, []},
|
||||||
|
{modules, []},
|
||||||
|
{maintainers, ["QPQ IaaS AG"]},
|
||||||
|
{licensens, ["ISC"]},
|
||||||
|
{links, [{"gitea", "https://git.qpq.swiss/gm_mining_pool_protocol"}]}
|
||||||
|
]}.
|
230
src/gmmpp_msgs.erl
Normal file
230
src/gmmpp_msgs.erl
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2025, QPQ AG
|
||||||
|
%%% @doc Gajumaru mining pool protocol messages
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(gmmpp_msgs).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
validate/2
|
||||||
|
, decode/3
|
||||||
|
, encode_request/4
|
||||||
|
, encode_reply/4
|
||||||
|
, encode_msg/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([ encode_connect/2 %% (Params, Id)
|
||||||
|
, decode_connect/1 %% (MsgBin)
|
||||||
|
, encode_connect_ack/2 %% (Params, Id)
|
||||||
|
, decode_connect_ack/1 %% (MsgBin)
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([ versions/0
|
||||||
|
, protocols/1
|
||||||
|
, latest_version/0
|
||||||
|
, connect_version/0
|
||||||
|
, connect_protocol/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-type protocol() :: binary().
|
||||||
|
-type version() :: binary().
|
||||||
|
|
||||||
|
-export_type([ protocol/0
|
||||||
|
, version/0 ]).
|
||||||
|
|
||||||
|
-define(VSN0, <<"0.1">>).
|
||||||
|
-define(VSN, ?VSN0).
|
||||||
|
-define(PROTOCOL_JSON, pool_ws_json).
|
||||||
|
-define(PROTOCOL, ?PROTOCOL_JSON).
|
||||||
|
|
||||||
|
-spec latest_version() -> version().
|
||||||
|
latest_version() ->
|
||||||
|
?VSN.
|
||||||
|
|
||||||
|
connect_version() -> ?VSN0.
|
||||||
|
connect_protocol() -> ?PROTOCOL_JSON.
|
||||||
|
|
||||||
|
-spec versions() -> [version()].
|
||||||
|
%% List sorted highest priority first
|
||||||
|
versions() -> [?VSN0].
|
||||||
|
|
||||||
|
-spec protocols(version()) -> [protocol()].
|
||||||
|
%% List sorted highest priority first
|
||||||
|
protocols(_Vsn) -> [?PROTOCOL].
|
||||||
|
|
||||||
|
validate(#{ connect := #{ pubkey := PK }} = Msg, _Vsn) ->
|
||||||
|
valid(pubkey, PK),
|
||||||
|
Msg;
|
||||||
|
validate(#{ connect_ack := #{ pubkey := PK
|
||||||
|
, edge_bits := EB }} = Msg, _Vsn) ->
|
||||||
|
valid(pubkey, PK),
|
||||||
|
valid(edgebits, EB),
|
||||||
|
Msg;
|
||||||
|
validate(#{ nonces := #{ seq := Seq
|
||||||
|
, n := N } } = Msg, _Vsn) ->
|
||||||
|
valid(seq, Seq),
|
||||||
|
valid(pos_int, N),
|
||||||
|
Msg;
|
||||||
|
validate(#{ candidate := #{ candidate := C
|
||||||
|
, nonces := Nonces
|
||||||
|
, seq := Seq } } = Msg, _Vsn) ->
|
||||||
|
valid(candidate, C),
|
||||||
|
valid(nonces, Nonces),
|
||||||
|
valid(seq, Seq),
|
||||||
|
Msg;
|
||||||
|
validate(#{ solution := #{ seq := Seq
|
||||||
|
, nonce := Nonce
|
||||||
|
, evidence := Evidence } } = Msg, _Vsn) ->
|
||||||
|
valid(seq, Seq),
|
||||||
|
valid(nonce, Nonce),
|
||||||
|
valid(evidence, Evidence),
|
||||||
|
Msg;
|
||||||
|
validate(#{ solution_ack := #{ seq := Seq } } = Msg, _Vsn) ->
|
||||||
|
valid(seq, Seq),
|
||||||
|
Msg;
|
||||||
|
validate(#{ no_solution := #{ seq := Seq
|
||||||
|
, nonce := Nonce } } = Msg, _Vsn) ->
|
||||||
|
valid(seq, Seq),
|
||||||
|
valid(nonce, Nonce),
|
||||||
|
Msg;
|
||||||
|
validate(#{ stop_mining := #{} } = Msg, _Vsn) ->
|
||||||
|
Msg.
|
||||||
|
|
||||||
|
encode_connect(#{ protocols := _Protocols
|
||||||
|
, versions := _Versions
|
||||||
|
, pool_id := _PoolId
|
||||||
|
, pubkey := _Pubkey
|
||||||
|
, signature := _Sig } = Params, Id) ->
|
||||||
|
encode_request(#{connect => Params}, Id, connect_protocol(), connect_version()).
|
||||||
|
|
||||||
|
decode_connect(MsgBin) ->
|
||||||
|
decode(MsgBin, connect_protocol(), connect_version()).
|
||||||
|
|
||||||
|
encode_connect_ack(#{ protocol := _
|
||||||
|
, version := _ } = Params, Id) ->
|
||||||
|
encode_reply(#{ connect_ack => Params }, Id, connect_protocol(), connect_version()).
|
||||||
|
|
||||||
|
decode_connect_ack(MsgBin) ->
|
||||||
|
decode(MsgBin, connect_protocol(), connect_version()).
|
||||||
|
|
||||||
|
decode(MsgBin, ?PROTOCOL_JSON, Vsn) ->
|
||||||
|
case json:decode(MsgBin) of
|
||||||
|
#{ <<"jsonrpc">> := <<"2.0">> } = Msg ->
|
||||||
|
case Msg of
|
||||||
|
#{ <<"method">> := Method
|
||||||
|
, <<"params">> := Params
|
||||||
|
, <<"id">> := Id } ->
|
||||||
|
%% JSON-RPC call request
|
||||||
|
#{ call => #{ id => Id
|
||||||
|
, req => validate(decode_msg_(Method, Params), Vsn) }};
|
||||||
|
#{ <<"method">> := Method
|
||||||
|
, <<"params">> := Params } ->
|
||||||
|
%% JSON-RPC notification
|
||||||
|
#{ notification => #{ msg => validate(decode_msg_(Method, Params), Vsn) }};
|
||||||
|
#{ <<"id">> := Id
|
||||||
|
, <<"result">> := Result } ->
|
||||||
|
#{ reply => #{ id => Id
|
||||||
|
, result => validate(decode_result(Result), Vsn) }};
|
||||||
|
#{ <<"id">> := Id
|
||||||
|
, <<"error">> := #{ <<"code">> := Code
|
||||||
|
, <<"message">> := Message }} ->
|
||||||
|
#{ error => #{ id => Id
|
||||||
|
, code => Code
|
||||||
|
, message => Message }}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
encode_msg(Msg0, ?PROTOCOL_JSON, Vsn) ->
|
||||||
|
Msg = validate(Msg0, Vsn),
|
||||||
|
[{Method, Args}] = maps:to_list(Msg),
|
||||||
|
json:encode(#{ <<"jsonrpc">> => <<"2.0">>
|
||||||
|
, <<"method">> => Method
|
||||||
|
, <<"args">> => Args }).
|
||||||
|
|
||||||
|
encode_request(Req0, Id, ?PROTOCOL_JSON, Vsn) ->
|
||||||
|
Req = validate(Req0, Vsn),
|
||||||
|
[{Method, Args}] = maps:to_list(Req),
|
||||||
|
json:encode(#{ <<"jsonrpc">> => <<"2.0">>
|
||||||
|
, <<"id">> => Id
|
||||||
|
, <<"method">> => Method
|
||||||
|
, <<"args">> => Args }).
|
||||||
|
|
||||||
|
encode_reply(Reply0, Id, ?PROTOCOL_JSON, Vsn) ->
|
||||||
|
Reply = validate(Reply0, Vsn),
|
||||||
|
Msg = case Reply of
|
||||||
|
{error, Reason} ->
|
||||||
|
#{ <<"jsonrpc">> => <<"2.0">>
|
||||||
|
, <<"id">> => Id
|
||||||
|
, <<"error">> => #{ <<"code">> => error_code(Reason)
|
||||||
|
, <<"message">> => Reason }};
|
||||||
|
Result ->
|
||||||
|
#{ <<"jsonrpc">> => <<"2.0">>
|
||||||
|
, <<"id">> => Id
|
||||||
|
, <<"result">> => Result }
|
||||||
|
end,
|
||||||
|
json:encode(Msg).
|
||||||
|
|
||||||
|
error_code(mining_disabled ) -> -32000;
|
||||||
|
error_code(pool_exists ) -> -32001;
|
||||||
|
error_code(pool_not_found ) -> -32002;
|
||||||
|
error_code(unknown_contract ) -> -32003;
|
||||||
|
error_code(invalid_prefix ) -> -32004;
|
||||||
|
error_code(invalid_encoding ) -> -32005;
|
||||||
|
error_code(outdated ) -> -32006;
|
||||||
|
error_code(solution_mismatch) -> -32007;
|
||||||
|
error_code(unknown_method ) -> -32601;
|
||||||
|
error_code(_ ) -> -32603. % internal error
|
||||||
|
|
||||||
|
decode_result(<<"ok">>) -> ok;
|
||||||
|
decode_result(#{<<"nonces">> := Nonces}) -> #{nonces => Nonces}.
|
||||||
|
|
||||||
|
%% Mapping types
|
||||||
|
decode_msg_(<<"connect">>, #{ <<"pubkey">> := PK }) ->
|
||||||
|
#{connect => #{pubkey => PK}};
|
||||||
|
decode_msg_(<<"nonces">>, #{ <<"seq">> := Seq
|
||||||
|
, <<"n">> := N }) ->
|
||||||
|
#{nonces => #{seq => Seq, n => N}};
|
||||||
|
decode_msg_(<<"candidate">>, #{ <<"candidate">> := C
|
||||||
|
, <<"nonces">> := Nonces
|
||||||
|
, <<"seq">> := Seq }) ->
|
||||||
|
#{candidate => #{ candidate => C
|
||||||
|
, nonces => Nonces
|
||||||
|
, seq => Seq }};
|
||||||
|
decode_msg_(<<"solution">>, #{ <<"seq">> := Seq
|
||||||
|
, <<"nonces">> := Nonces
|
||||||
|
, <<"evidence">> := Evidence }) ->
|
||||||
|
#{solution => #{ seq => Seq
|
||||||
|
, nonce => Nonces
|
||||||
|
, seq => Seq
|
||||||
|
, evidence => Evidence}}.
|
||||||
|
|
||||||
|
valid(Type, Val) ->
|
||||||
|
try valid_(Type, Val)
|
||||||
|
catch
|
||||||
|
error:_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
valid_(pubkey, PK) -> ok_tuple(aeser_api:safe_decode(account_pubkey, PK));
|
||||||
|
valid_(seq, Seq) -> pos_integer(Seq);
|
||||||
|
valid_(edgebits, E) -> pos_integer(E);
|
||||||
|
valid_(pos_int, I) -> pos_integer(I);
|
||||||
|
valid_(nonces, Ns) ->
|
||||||
|
case Ns of
|
||||||
|
[N] -> pos_integer(N);
|
||||||
|
[A,B] -> pos_integer(A) andalso pos_integer(B);
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
valid_(candidate, C) -> ok_tuple(aeser_api:safe_decode(bytearray, C)).
|
||||||
|
|
||||||
|
ok_tuple(V) ->
|
||||||
|
case V of
|
||||||
|
{ok, _} -> true;
|
||||||
|
_ -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
pos_integer(I) ->
|
||||||
|
is_integer(I) andalso I >= 0.
|
Loading…
x
Reference in New Issue
Block a user