16 Commits

Author SHA1 Message Date
Ulf Wiger 07b658d509 Configurable progress reporting 2025-09-23 16:44:46 +02:00
uwiger 8a68244c90 Merge pull request 'Update gmhive_worker and gmcuckoo deps, miner returns in debug' (#12) from uw-miner-returns into master
Reviewed-on: #12
2025-09-23 21:46:32 +09:00
Ulf Wiger e31a8dd2f1 Update gmhive_worker dep 2025-08-27 16:31:23 +02:00
Ulf Wiger 2ea84ee4b2 Add app.src.script for version mgmt 2025-08-27 12:49:16 +02:00
Ulf Wiger 8d99f55377 Update gmhive_worker and gmcuckoo deps, miner returns in debug 2025-08-27 12:48:33 +02:00
uwiger 6939ae2fd1 Merge pull request 'Add missing demonitor() calls' (#11) from uw-demonitor into master
Reviewed-on: #11
Reviewed-by: Craig Everett <craigeverett@qpq.swiss>
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-08-27 19:28:49 +09:00
Ulf Wiger 196a2d9949 Reset worker errors, retry failed workers 2025-08-21 22:46:54 +02:00
Ulf Wiger d61f103945 update gmhive_worker dep, bump patch vsn 2025-08-21 16:46:11 +02:00
Ulf Wiger 0a76bada43 bump zx patch vsn 2025-08-20 20:44:55 +02:00
Ulf Wiger 36a11575d2 Add missing demonitor() calls 2025-08-20 20:43:22 +02:00
uwiger 5bc0fc5ff8 Merge pull request 'uw-handle-empty-nonces' (#8) from uw-handle-empty-nonces into master
Reviewed-on: #8
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-08-21 03:42:37 +09:00
Ulf Wiger c61ce6df1a Handle empty nonces (zx vsn 0.4.5) 2025-06-16 22:29:00 +02:00
Ulf Wiger 672f2f75c9 reset worker on report error 2025-06-12 15:46:54 +02:00
uwiger e34207144e Merge pull request 'Improved README.md' (#7) from uw-documentation into master
Reviewed-on: #7
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-05-29 16:26:46 +09:00
Jarvis Carroll 51eca9c6f6 Some README edits.
A typo, an expansion for (interested) laypeople, and a `\n` escape code.
2025-05-29 16:13:28 +10:00
Ulf Wiger 25e5a11669 Improved README.md 2025-05-28 19:17:05 +02:00
20 changed files with 396 additions and 45 deletions
+208 -6
View File
@@ -1,11 +1,213 @@
# Gajumaru Hive Client
When running tests locally:
This application is primarily meant to be used by the [GajuMine](https://git.qpq.swiss/QPQ-AG/gajumine)
application, but can also be run as a headless worker. It can be built either using `rebar3` or
using [`zx`](https://zxq9.com/projects/zomp/) (recomended).
`rebar3 shell`
## Operation
The `gmhive_client` needs at least a worker public key to operate.
It first queries the [GajuMining website](https://gajumining.com) to find the address of
the Gajumaru Hive server, which it then connects to. GajuMining won't provide the address
unless the public key is a registered worker.
If connecting to the `testnet` hive:
`GMMP_CLIENT_CONFIG=gmhc_config-testnet.eterm rebar3 shell`
## Configuration
If connecting to a `testnet` hive running locally:
`GMHC_CONFIG=gmhc_config-testnet-local.eterm rebar3 shell`
The Hive Client uses a configuration schema ([see below](#json-schema)).
At a minimum, a value for `"pubkey"` must be provided. All other configuration
items have usable defaults.
Examples:
**.json**:
```json
{ "pubkey" : "ak_2NQoybA6uyvhu3cdzWFtZnxCZpZvKE5TxjYB3hB7kMWEquwLVY",
"extra_pubkeys" : [ "ak_2Dfwyb7ZFhcAELoxgWyMCkVnpZUhwUm6aftKcv6dakagePWyx9" ]
}
```
**.eterm**:
```erlang
#{ <<"pubkey">> => <<"ak_2NQoybA6uyvhu3cdzWFtZnxCZpZvKE5TxjYB3hB7kMWEquwLVY">>,
<<"extra_pubkeys">> : [ <<"ak_2Dfwyb7ZFhcAELoxgWyMCkVnpZUhwUm6aftKcv6dakagePWyx9">> ]
}.
```
Configuration can be provided in a file, where the filename extension defines
the format: `".json"` for JSON format, `".eterm"` for Erlang term format.
In the case of Erlang term format, the data should be on "internal JSON form",
i.e. nested maps, where all strings are of type `binary()`.
By default, the Hive client looks for a configuration file named
`"gmhive_client_config.[json|eterm]"` in the current working directory.
A specific configuration file can be identified using the OS environment
variable `GMHIVE_CLIENT_CONFIG`.
On invocation, individual configuration items can be set in one of two ways:
### OS environment variable overrides
By creating an OS environment variable with the name `GMHC__<variable_name>`,
an individual variable can be redefined. The name is composed from the
configuration key name, converted to uppercase, and with `__` as a delimiter
for a nested key. The value is coerced into the expected type using the schema.
Complex values are given in JSON format - be sure to quote it.
Example:
```
GMHC__PUBKEY="ak_2Dfwyb7ZFhcAELoxgWyMCkVnpZUhwUm6aftKcv6dakagePWyx9" zx run uwiger-gmhive_client
```
```
GMHC__WORKERS='[{"executable": "mean29-avx2"}]' zx run ...
```
### Command-line arguments
Arguments on the form `-gmhc Key Value` can be added to the command line.
The key name is derived from the schema, all lowercase and with `__` as delimiter.
Example:
```
zx run uwiger-gmhive_client -gmhc pubkey ak_2Dfwyb...
```
### Considerations
The values for `pool_admin` and `pool` should be left alone. The `"network"`
configuration item is special. Its default value is `"mainnet"`, which
indicates that the client should connect to
[gajumining.com](https://gajumining.com) to find the hive server, but by
changing its value to `"testnet"`, (or to other networks in the future,) not
only does this inform the client of the network for which the client should
mine, but it also changes what URL the client will use to find the hive
server. (For `"testnet"` this will be `"test.gajumining.com"`.)
## JSON Schema
```json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"additionalProperties": false,
"properties": {
"extra_pubkeys": {
"default": [],
"description": "Additional worker pubkeys, sharing rewards",
"items": {
"pattern": "^ak_[1-9A-HJ-NP-Za-km-z]*$",
"type": "string"
},
"type": "array"
},
"network": {
"default": "mainnet",
"type": "string"
},
"pool": {
"additionalProperties": false,
"properties": {
"host": {
"default": "127.0.0.1",
"description": "Hostname of hive server",
"example": "0.0.0.0",
"type": "string"
},
"id": {
"description": "Pool contract id",
"pattern": "^ct_[1-9A-HJ-NP-Za-km-z]*$",
"type": "string"
},
"port": {
"default": 17888,
"description": "Hive server listen port",
"minimum": 1,
"type": "integer"
}
},
"type": "object"
},
"pool_admin": {
"additionalProperties": false,
"properties": {
"default_per_network": {
"additionalProperties": false,
"properties": {
"mainnet": {
"default": "https://gajumining.com/api/workers/{CLIENT_ID}",
"type": "string"
},
"testnet": {
"default": "https://test.gajumining.com/api/workers/{CLIENT_ID}",
"type": "string"
}
},
"type": "object"
},
"url": {
"default": "https://test.gajumining.com/api/workers/{CLIENT_ID}",
"description": "URL of Eureka worker api",
"type": "string"
}
},
"type": "object"
},
"pubkey": {
"description": "Primary client pubkey",
"pattern": "^ak_[1-9A-HJ-NP-Za-km-z]*$",
"type": "string"
},
"type": {
"default": "worker",
"description": "monitor mode can be used to see if a pool is alive",
"enum": [
"worker",
"monitor"
],
"type": "string"
},
"workers": {
"default": [
{ "executable": "mean29-generic" }
],
"description": "Definitions of workers' configurations. If no worker are configured one worker is used as default, i.e. 'mean29-generic' executable without any extra args.",
"items": {
"additionalProperties": false,
"properties": {
"executable": {
"default": "mean29-generic",
"description": "Executable binary of the worker. Can be a fully qualified path, but the software may apply default logic to locate a plain basename.",
"type": "string"
},
"extra_args": {
"default": "",
"description": "Extra arguments to pass to the worker executable binary. The safest choice is specifying no arguments i.e. empty string.",
"type": "string"
},
"hex_encoded_header": {
"default": false,
"description": "Hexadecimal encode the header argument that is send to the worker executable. CUDA executables expect hex encoded header.",
"type": "boolean"
},
"instances": {
"description": "Instances used by the worker in case of Multi-GPU mining. Numbers on the configuration list represent GPU devices that are to be addressed by the worker.",
"example": [0,1,2,3],
"items": { "type": "integer" },
"minItems": 1,
"type": "array"
},
"repeats": {
"default": 1,
"description": "Number of tries to do in each worker context - WARNING: it should be set so the worker process runs for 3-5s or else the node risk missing out on new micro blocks.",
"type": "integer"
}
},
"required": [
"executable"
],
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
```
+1 -1
View File
@@ -1,6 +1,6 @@
{application,gmhive_client,
[{description,"Gajumaru Hive Client"},
{vsn,"0.4.3"},
{vsn,"0.6.0"},
{registered,[]},
{applications,[kernel,stdlib,sasl,gproc,inets,ssl,enoise,
gmconfig,gmhive_protocol,gmhive_worker]},
+1 -1
View File
@@ -10,7 +10,7 @@
{gmhive_protocol,
{git, "https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git",
{ref, "818ce33"}}},
{gmhive_worker, {git, "https://git.qpq.swiss/QPQ-AG/gmhive_worker", {ref, "255ef59"}}},
{gmhive_worker, {git, "https://git.qpq.swiss/QPQ-AG/gmhive_worker", {ref, "cabd104114"}}},
{gmconfig, {git, "https://git.qpq.swiss/QPQ-AG/gmconfig.git",
{ref, "38620ff9e2"}}},
{gproc, "1.0.0"},
+2 -2
View File
@@ -21,7 +21,7 @@
0},
{<<"gmcuckoo">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmcuckoo.git",
{ref,"106e1cd2e4ff81286f6bc7d7c85f83bc20e14b82"}},
{ref,"256e14e7c88043132245be902ff9756070d285b4"}},
1},
{<<"gmhive_protocol">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git",
@@ -29,7 +29,7 @@
0},
{<<"gmhive_worker">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmhive_worker",
{ref,"255ef59ccd7f795d2d25f2d0ebcf24e3251b6f36"}},
{ref,"cabd104114691edb925aacd7e04d431d47bac420"}},
0},
{<<"gmserialization">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
+10
View File
@@ -1,5 +1,6 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
-module(gmhc_app).
-vsn("0.6.0").
-behaviour(application).
@@ -48,4 +49,13 @@ set_things_up() ->
gmhc_config:load_config(),
logger:set_module_level([gmhw_pow_cuckoo], notice),
?LOG_DEBUG("Config: ~p", [gmconfig:user_config()]),
case gmhc_config:get_config([<<"report">>]) of
<<"debug">> ->
?LOG_NOTICE("Starting debug reporter", []),
gmhc_events:debug();
<<"progress">> ->
?LOG_NOTICE("Starting progress reporter", []),
gmhc_events:progress();
_ -> ok
end,
ok.
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_config).
-vsn("0.6.0").
-export([ load_config/0
, get_config/1
+4
View File
@@ -1,4 +1,5 @@
-module(gmhc_config_schema).
-vsn("0.6.0").
-export([ schema/0
, to_json/0 ]).
@@ -38,6 +39,9 @@ schema() ->
, pool => pool()
, pool_admin => pool_admin()
, workers => workers()
, report => str(#{ enum => [<<"debug">>, <<"progress">>, <<"silent">>]
, default => <<"silent">>
, description => <<"Progress reporting">> })
}).
pool() ->
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_connector).
-vsn("0.6.0").
-behaviour(gen_server).
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_connectors_sup).
-vsn("0.6.0").
-behavior(supervisor).
-export([ start_link/0
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_counters).
-vsn("0.6.0").
-export([ initialize/0 ]).
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_eureka).
-vsn("0.6.0").
-export([get_pool_address/0]).
+104 -15
View File
@@ -1,4 +1,5 @@
-module(gmhc_events).
-vsn("0.6.0").
-export([subscribe/1,
ensure_subscribed/1,
@@ -6,7 +7,17 @@
ensure_unsubscribed/1,
publish/2]).
-export([debug/0]).
-export([debug/0,
progress/0,
stop/0]).
-export([rpt_debug/2,
rpt_progress/2]).
%% internal
-export([init_reporter/2]).
-include_lib("kernel/include/logger.hrl").
-export_type([event/0]).
@@ -59,22 +70,100 @@ ensure_unsubscribed(Event) ->
debug() ->
ok = application:ensure_started(gproc),
spawn(fun() ->
subscribe(pool_notification),
subscribe({pool_notification, new_generation}),
subscribe(connected),
subscribe(puzzle),
subscribe(result),
subscribe(error),
subscribe(disconnected),
loop()
end).
spawn_reporter(fun() ->
sub(),
gmhive_worker:subscribe_returns(),
loop(fun rpt_debug/2, false)
end).
loop() ->
progress() ->
ok = application:ensure_started(gproc),
spawn_reporter(fun() ->
sub(),
loop(fun rpt_progress/2, true)
end).
spawn_reporter(F) ->
Parent = self(),
proc_lib:start_link(?MODULE, init_reporter, [F, Parent]).
init_reporter(F, Parent) ->
try_register_reporter(),
proc_lib:init_ack(Parent, self()),
F().
stop() ->
case whereis(gmhc_reporter) of
undefined ->
not_running;
Pid ->
exit(Pid, kill)
end.
sub() ->
subscribe(pool_notification),
subscribe({pool_notification, new_generation}),
subscribe(connected),
subscribe(puzzle),
subscribe(result),
subscribe(error),
subscribe(disconnected).
loop(F, Ts) ->
receive
stop -> ok;
{gproc_ps_event, E, Data} ->
io:fwrite("EVENT ~p: ~p~n", [E, Data]),
loop()
maybe_print(F(E, Data), Ts),
loop(F, Ts)
end.
try_register_reporter() ->
try register(gmhc_reporter, self())
catch
error:_ ->
?LOG_ERROR("Reporter already running. Try gmhc_events:stop().", []),
error(already_running)
end.
maybe_print([], _) ->
ok;
maybe_print(String, Ts) when is_boolean(Ts) ->
TSstr = [[ts(), " "] || Ts],
io:put_chars([TSstr, String, "\n"]).
ts() ->
calendar:system_time_to_rfc3339(erlang:system_time(millisecond),
[{unit, millisecond}, {offset, "Z"}]).
rpt_debug(E, Data) ->
io_lib:fwrite("EVENT ~p: ~p", [E, Data]).
rpt_progress(puzzle, #{info := {_Data, _Target, Nonce, _Config}}) ->
w("Trying nonce: ~p", [Nonce]);
rpt_progress(result, #{info := Info}) ->
case Info of
{error, no_solution} ->
[];
{ok, Cycles} ->
w("Found! Reporting ~w cycles to leader.", [length(Cycles)]);
Other ->
w("Unexpected 'result': ~tp", [Other])
end;
rpt_progress(pool_notification, #{info := #{msg := Msg}}) ->
case Msg of
#{solution_accepted := #{seq := Seq}} ->
w("The hive has produced a solution! Sequence: ~w", [Seq]);
#{new_generation := _} -> [];
#{candidate := _} -> [];
Other ->
w("Unexpected 'pool_notification': ~tp", [Other])
end;
rpt_progress(connected, _) ->
w("Connected!", []);
rpt_progress(disconnected, _) ->
w("Disconnected!", []);
rpt_progress(_, _) ->
[].
w(Fmt, Args) ->
io_lib:fwrite(Fmt, Args).
+4
View File
@@ -1,4 +1,5 @@
-module(gmhc_handler).
-vsn("0.6.0").
-behavior(gen_server).
-export([ start_link/0
@@ -129,12 +130,15 @@ call_connector(Req0) ->
gmhc_connector:send(ViaId, #{call => Req#{ id => Id }}),
receive
{from_pool, #{reply := #{ id := Id, result := Result }}} ->
erlang:demonitor(MRef),
Result;
{from_pool, #{error := #{ id := Id } = Error}} ->
erlang:demonitor(MRef),
{error, maps:remove(id, Error)};
{'DOWN', MRef, _, _, _} ->
{error, no_connection}
after 5000 ->
erlang:demonitor(MRef),
{error, {timeout, process_info(self(), messages)}}
end
end.
+36 -16
View File
@@ -1,4 +1,5 @@
-module(gmhc_server).
-vsn("0.6.0").
-behaviour(gen_server).
@@ -44,7 +45,7 @@
-define(CONNECTED(S), map_size(S#st.connected) > 0).
-define(MAX_ERRORS, 5).
-define(MAX_ERRORS, 50).
connected(Id, Type) ->
gen_server:call(?MODULE, {connected, Id, Type}).
@@ -107,17 +108,20 @@ handle_cast({from_pool, #{via := Connector,
%% We could check whether we have already received the candidate ...
%% For now, stop all workers, restart with new candidate
try
% Most of the time we don't want to stop the worker. If we do, though, then
% we need to do it more carefully than this, or memory usage will triple.
% Workers1 = stop_workers(Workers),
{Workers2, Cand1} = assign_nonces(Workers, Cand),
#st{candidate = Cand2} = S1 = maybe_request_nonces(S#st{candidate = Cand1}),
NewWorkers = [spawn_worker(W, Cand2) || W <- Workers2],
{noreply, S1#st{workers = NewWorkers}}
%% Most of the time we don't want to stop the worker. If we do, though, then
%% we need to do it more carefully than this, or memory usage will triple.
%% Workers1 = stop_workers(Workers),
%%
%% Nonces may be [], in which case we need to request new nonces first.
#st{candidate = Cand1} = S1 = maybe_request_nonces(S#st{candidate = Cand}),
{Workers2, Cand2} = assign_nonces(Workers, Cand1),
#st{candidate = Cand3} = S2 = maybe_request_nonces(S1#st{candidate = Cand2}),
NewWorkers = [spawn_worker(W, Cand3) || W <- Workers2],
{noreply, S2#st{workers = NewWorkers}}
catch
Cat:Err:St ->
?LOG_ERROR("CAUGHT ~p:~p / ~p", [Cat, Err, St]),
{noreply, S}
{noreply, S}
end;
handle_cast({disconnected, Id}, #st{connected = Conn} = S) ->
?LOG_DEBUG("disconnected: ~p", [Id]),
@@ -151,11 +155,23 @@ handle_info({'EXIT', Pid, Reason}, #st{ workers = Workers
gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error,
data => Reason})),
Ws1 = incr_worker_error(W, Workers),
erlang:start_timer(100, self(), check_workers),
{noreply, S#st{workers = Ws1}};
false ->
%% ?LOG_DEBUG("EXIT apparently not from worker?? (~p)", [Pid]),
{noreply, S}
end;
handle_info({timeout, _, check_workers}, #st{workers = Workers} = S) ->
case [W || #worker{cand = undefined} = W <- Workers] of
[] ->
{noreply, S};
Idle ->
S1 = maybe_request_nonces(S),
S2 = lists:foldl(fun(W, Sx) ->
maybe_restart_worker(W, Sx)
end, S1, Idle),
{noreply, S2}
end;
handle_info(Msg, St) ->
?LOG_DEBUG("Unknown msg: ~p", [Msg]),
{noreply, St}.
@@ -223,14 +239,14 @@ handle_worker_result({worker_result, Result}, W, S) ->
case Result of
{solutions, Solutions} ->
{Cont, S1} = report_solutions_(Solutions, W, S),
maybe_continue(Cont, W, S1);
maybe_continue(Cont, reset_errors(W), S1);
{solution, Nonce, Solution} ->
%% report_solution(Nonce, Solution, W, S),
{Cont, S1} = report_solutions_([{Nonce, Solution}], W, S),
maybe_continue(Cont, W, S1);
maybe_continue(Cont, reset_errors(W), S1);
{no_solution, Nonce} ->
report_no_solution(Nonce, W, S),
maybe_restart_worker(W, S);
maybe_restart_worker(reset_errors(W), S);
{error, S} ->
?LOG_DEBUG("Worker ~p reported error as normal", [W#worker.index]),
gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error,
@@ -258,6 +274,9 @@ report_solutions_(Solutions, W, S) ->
{error, S}
end.
reset_errors(#worker{} = W) ->
W#worker{errors = 0}.
reset_worker(#worker{index = I} = W, Ws) ->
W1 = reset_worker_(W),
lists:keyreplace(I, #worker.index, Ws, W1).
@@ -272,13 +291,14 @@ incr_worker_error(#worker{errors = Es, index = I} = W, Ws) ->
W1 = reset_worker_(W#worker{errors = Es+1}),
lists:keyreplace(I, #worker.index, Ws, W1).
maybe_continue(stopped, _, S) ->
S;
%% maybe_continue(stopped, _, S) ->
%% S;
maybe_continue(continue, W, S) ->
maybe_restart_worker(W, S);
maybe_continue(error, W, S) ->
?LOG_INFO("Won't restart worker ~p due to error", [W#worker.index]),
S.
Ws = reset_worker(W, S#st.workers),
S#st{workers = Ws}.
maybe_restart_worker(#worker{index = I} = W, #st{candidate = C} = S) ->
case maps:get(nonces, C) of
@@ -314,7 +334,7 @@ stop_workers_for_seq(Seq, Workers) ->
stop_worker(#worker{pid = Pid} = W) when is_pid(Pid) ->
MRef = erlang:monitor(process, Pid),
?LOG_DEBUG("Will stop worker ~p (MRef = ~p)", [Pid, MRef]),
exit(Pid, kill),
exit(Pid, shutdown),
receive
{'EXIT', Pid, _} -> ok;
{'DOWN', MRef, process, Pid, _} -> ok
+1
View File
@@ -1,5 +1,6 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
-module(gmhc_sup).
-vsn("0.6.0").
-behaviour(supervisor).
+1
View File
@@ -8,6 +8,7 @@
%%%-------------------------------------------------------------------
-module(gmhc_workers).
-vsn("0.6.0").
-export([
get_worker_configs/0
+1 -1
View File
@@ -1,7 +1,7 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
{application, gmhive_client,
[{description, "Gajumaru Hive Client"},
{vsn, "0.1.0"},
{vsn, "zomp"},
{registered, []},
{applications,
[
+14
View File
@@ -0,0 +1,14 @@
%% -*- erlang-mode; erlang-indent-level: 4; indent-tabs-mode: nil -*-
[{application, Name, Opts}] = CONFIG.
case lists:keyfind(vsn, 1, Opts) of
{vsn, "zomp"} ->
ZompMetaF = filename:join(filename:dirname(filename:dirname(SCRIPT)), "zomp.meta"),
{ok, ZMeta} = file:consult(ZompMetaF),
{_, {_, _, {Vmaj,Vmin,Vpatch}}} = lists:keyfind(package_id, 1, ZMeta),
VsnStr = unicode:characters_to_list(io_lib:fwrite("~w.~w.~w", [Vmaj, Vmin, Vpatch])),
Opts1 = lists:keyreplace(vsn, 1, Opts, {vsn, VsnStr}),
[{application, Name, Opts1}];
_ ->
CONFIG
end.
+1
View File
@@ -1,4 +1,5 @@
-module(gmhive_client).
-vsn("0.6.0").
-export([ connect/1
, disconnect/1
+3 -3
View File
@@ -4,9 +4,9 @@
{prefix,"gmhc"}.
{author,"Ulf Wiger, QPQ AG"}.
{desc,"Gajumaru Hive Client"}.
{package_id,{"uwiger","gmhive_client",{0,4,3}}}.
{deps,[{"uwiger","gmcuckoo",{1,2,3}},
{"uwiger","gmhive_worker",{0,3,0}},
{package_id,{"uwiger","gmhive_client",{0,6,0}}}.
{deps,[{"uwiger","gmhive_worker",{0,5,1}},
{"uwiger","gmcuckoo",{1,2,4}},
{"otpr","eblake2",{1,0,1}},
{"otpr","base58",{0,1,1}},
{"otpr","gmserialization",{0,1,3}},