22 Commits

Author SHA1 Message Date
Ulf Wiger 968f9d92f2 Update README.md with latest json schema (automated) 2025-10-08 15:39:48 +02:00
uwiger 34f3c93aaa Merge pull request 'Start tty logger if headless, check config for duplicate ids' (#15) from uw-check-for-dups into master
Reviewed-on: #15
2025-10-01 18:33:30 +09:00
Ulf Wiger c37ee1c3af Start tty logger if headless, check config for duplicate ids 2025-09-30 19:55:12 +02:00
uwiger 408bd9fc18 Merge pull request 'Report no_solution for all relevant nonces with repeats' (#14) from uw-repeats-reporting into master
Reviewed-on: #14
2025-09-27 23:27:56 +09:00
Ulf Wiger 2a33d06bd6 Report no_solution for all relevant nonces with repeats 2025-09-25 23:25:00 +02:00
uwiger 8cb2c76614 Merge pull request 'Configurable progress reporting' (#13) from uw-progress-reporting into master
Reviewed-on: #13
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
Reviewed-by: Craig Everett <craigeverett@qpq.swiss>
2025-09-24 14:44:09 +09:00
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
23 changed files with 567 additions and 68 deletions
+10
View File
@@ -0,0 +1,10 @@
BUILD="_build/default/lib/gmhive_client"
SCHEMA_OUT=$(BUILD)/priv/gmhc_schema.json
schema:
ERL_LIBS=_build/default/lib \
erl -run gmhc_config_schema export $(SCHEMA_OUT) -run init stop
scripts/update_readme_json.sh README.md $(SCHEMA_OUT)
+218 -6
View File
@@ -1,11 +1,223 @@
# Gajumaru Hive Client # 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: ## Configuration
`GMMP_CLIENT_CONFIG=gmhc_config-testnet.eterm rebar3 shell`
If connecting to a `testnet` hive running locally: The Hive Client uses a configuration schema ([see below](#json-schema)).
`GMHC_CONFIG=gmhc_config-testnet-local.eterm rebar3 shell` 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"
},
"report": {
"default": "silent",
"description": "Progress reporting",
"enum": [
"debug",
"progress",
"silent"
],
"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, \nbut 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, {application,gmhive_client,
[{description,"Gajumaru Hive Client"}, [{description,"Gajumaru Hive Client"},
{vsn,"0.4.3"}, {vsn,"0.6.3"},
{registered,[]}, {registered,[]},
{applications,[kernel,stdlib,sasl,gproc,inets,ssl,enoise, {applications,[kernel,stdlib,sasl,gproc,inets,ssl,enoise,
gmconfig,gmhive_protocol,gmhive_worker]}, gmconfig,gmhive_protocol,gmhive_worker]},
+3 -1
View File
@@ -5,12 +5,14 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{plugins, [rebar3_hex]}. {plugins, [rebar3_hex]}.
{post_hooks, [{compile, "make schema"}]}.
{deps, [ {deps, [
{enoise, {git, "https://git.qpq.swiss/QPQ-AG/enoise.git", {ref, "029292817e"}}}, {enoise, {git, "https://git.qpq.swiss/QPQ-AG/enoise.git", {ref, "029292817e"}}},
{gmhive_protocol, {gmhive_protocol,
{git, "https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git", {git, "https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git",
{ref, "818ce33"}}}, {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", {gmconfig, {git, "https://git.qpq.swiss/QPQ-AG/gmconfig.git",
{ref, "38620ff9e2"}}}, {ref, "38620ff9e2"}}},
{gproc, "1.0.0"}, {gproc, "1.0.0"},
+2 -2
View File
@@ -21,7 +21,7 @@
0}, 0},
{<<"gmcuckoo">>, {<<"gmcuckoo">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmcuckoo.git", {git,"https://git.qpq.swiss/QPQ-AG/gmcuckoo.git",
{ref,"106e1cd2e4ff81286f6bc7d7c85f83bc20e14b82"}}, {ref,"256e14e7c88043132245be902ff9756070d285b4"}},
1}, 1},
{<<"gmhive_protocol">>, {<<"gmhive_protocol">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git", {git,"https://git.qpq.swiss/QPQ-AG/gmhive_protocol.git",
@@ -29,7 +29,7 @@
0}, 0},
{<<"gmhive_worker">>, {<<"gmhive_worker">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmhive_worker", {git,"https://git.qpq.swiss/QPQ-AG/gmhive_worker",
{ref,"255ef59ccd7f795d2d25f2d0ebcf24e3251b6f36"}}, {ref,"cabd104114691edb925aacd7e04d431d47bac420"}},
0}, 0},
{<<"gmserialization">>, {<<"gmserialization">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git", {git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
README="$1"
SCHEMA="$2"
if [[ ! -f "$README" || ! -f "$SCHEMA" ]]; then
echo "Usage: $0 README.md schema.json"
exit 1
fi
tmpfile=$(mktemp)
awk -v schema_file="$SCHEMA" '
BEGIN {
in_json_block = 0
in_schema_section = 0
}
/^##[[:space:]]+JSON[[:space:]]+Schema/ {
in_schema_section = 1
print
next
}
/^##[[:space:]]+/ && in_schema_section {
# Another section starts, end the schema section
in_schema_section = 0
}
in_schema_section && /^```json/ {
print "```json"
while ((getline line < schema_file) > 0) print line
close(schema_file)
in_json_block = 1
next
}
in_json_block && /^```$/ {
print "```"
in_json_block = 0
next
}
!(in_schema_section && in_json_block) {
print
}
' "$README" > "$tmpfile"
mv "$tmpfile" "$README"
echo "✅ Updated JSON schema block in $README"
+24
View File
@@ -1,5 +1,6 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*- %% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
-module(gmhc_app). -module(gmhc_app).
-vsn("0.6.1").
-behaviour(application). -behaviour(application).
@@ -44,8 +45,31 @@ stop(_State) ->
ok. ok.
set_things_up() -> set_things_up() ->
maybe_add_logger_handler(),
gmhc_counters:initialize(), gmhc_counters:initialize(),
gmhc_config:load_config(), gmhc_config:load_config(),
logger:set_module_level([gmhw_pow_cuckoo], notice), logger:set_module_level([gmhw_pow_cuckoo], notice),
?LOG_DEBUG("Config: ~p", [gmconfig:user_config()]), ?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. ok.
maybe_add_logger_handler() ->
case is_headless() orelse application:get_env(gmhive_client, tty_logger, false) of
true ->
Level = application:get_env(gmhive_client, tty_logger_level, error),
io:fwrite("Adding logger handler: ~p~n", [Level]),
logger:add_handler(gmhc_tty, logger_std_h, #{level => Level});
false ->
ok
end.
is_headless() ->
undefined == application:get_key(gajumine, vsn).
+13
View File
@@ -1,4 +1,5 @@
-module(gmhc_config). -module(gmhc_config).
-vsn("0.6.1").
-export([ load_config/0 -export([ load_config/0
, get_config/1 , get_config/1
@@ -13,8 +14,20 @@ load_config() ->
gmconfig:apply_os_env(), gmconfig:apply_os_env(),
gmconfig:process_plain_args(), gmconfig:process_plain_args(),
check_application_env(), check_application_env(),
check_final_config(),
ok. ok.
check_final_config() ->
ExtraPubkeys = get_config([<<"extra_pubkeys">>]),
AllKeys = [get_config([<<"pubkey">>]) | ExtraPubkeys],
case AllKeys -- lists:usort(AllKeys) of
[] ->
ok;
Duplicates ->
?LOG_ERROR("Duplicate account ids found: ~p", [Duplicates]),
error({duplicate_account_ids, Duplicates})
end.
instrument_gmconfig() -> instrument_gmconfig() ->
gmconfig:set_gmconfig_env(gmconfig_env()). gmconfig:set_gmconfig_env(gmconfig_env()).
+17 -1
View File
@@ -1,7 +1,9 @@
-module(gmhc_config_schema). -module(gmhc_config_schema).
-vsn("0.6.1").
-export([ schema/0 -export([ schema/0
, to_json/0 ]). , to_json/0
, export/1 ]).
-import(gmconfig_schema_helpers, -import(gmconfig_schema_helpers,
[ str/1 [ str/1
@@ -21,6 +23,17 @@
to_json() -> to_json() ->
json:encode(schema()). json:encode(schema()).
export(ToFile) ->
case file:open(ToFile, [write]) of
{ok, Fd} ->
try ok = io:put_chars(Fd, json:format(schema(), #{indent => 4}))
after
file:close(Fd)
end;
{error, _} = Error ->
Error
end.
schema() -> schema() ->
obj(schema_init(), obj(schema_init(),
#{ #{
@@ -38,6 +51,9 @@ schema() ->
, pool => pool() , pool => pool()
, pool_admin => pool_admin() , pool_admin => pool_admin()
, workers => workers() , workers => workers()
, report => str(#{ enum => [<<"debug">>, <<"progress">>, <<"silent">>]
, default => <<"silent">>
, description => <<"Progress reporting">> })
}). }).
pool() -> pool() ->
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_connector). -module(gmhc_connector).
-vsn("0.6.1").
-behaviour(gen_server). -behaviour(gen_server).
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_connectors_sup). -module(gmhc_connectors_sup).
-vsn("0.6.1").
-behavior(supervisor). -behavior(supervisor).
-export([ start_link/0 -export([ start_link/0
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_counters). -module(gmhc_counters).
-vsn("0.6.1").
-export([ initialize/0 ]). -export([ initialize/0 ]).
+1
View File
@@ -1,4 +1,5 @@
-module(gmhc_eureka). -module(gmhc_eureka).
-vsn("0.6.1").
-export([get_pool_address/0]). -export([get_pool_address/0]).
+97 -8
View File
@@ -1,4 +1,5 @@
-module(gmhc_events). -module(gmhc_events).
-vsn("0.6.1").
-export([subscribe/1, -export([subscribe/1,
ensure_subscribed/1, ensure_subscribed/1,
@@ -6,7 +7,17 @@
ensure_unsubscribed/1, ensure_unsubscribed/1,
publish/2]). 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]). -export_type([event/0]).
@@ -59,22 +70,100 @@ ensure_unsubscribed(Event) ->
debug() -> debug() ->
ok = application:ensure_started(gproc), ok = application:ensure_started(gproc),
spawn(fun() -> spawn_reporter(fun() ->
sub(),
gmhive_worker:subscribe_returns(),
loop(fun rpt_debug/2, false)
end).
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),
subscribe({pool_notification, new_generation}), subscribe({pool_notification, new_generation}),
subscribe(connected), subscribe(connected),
subscribe(puzzle), subscribe(puzzle),
subscribe(result), subscribe(result),
subscribe(error), subscribe(error),
subscribe(disconnected), subscribe(disconnected).
loop()
end).
loop() -> loop(F, Ts) ->
receive receive
stop -> ok; stop -> ok;
{gproc_ps_event, E, Data} -> {gproc_ps_event, E, Data} ->
io:fwrite("EVENT ~p: ~p~n", [E, Data]), maybe_print(F(E, Data), Ts),
loop() loop(F, Ts)
end. 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).
+29 -3
View File
@@ -1,4 +1,5 @@
-module(gmhc_handler). -module(gmhc_handler).
-vsn("0.6.1").
-behavior(gen_server). -behavior(gen_server).
-export([ start_link/0 -export([ start_link/0
@@ -11,6 +12,7 @@
]). ]).
-export([ call/1 -export([ call/1
, async_call/1
, notify/1 , notify/1
, pool_connected/2 , pool_connected/2
, from_pool/1 ]). , from_pool/1 ]).
@@ -37,6 +39,13 @@ call(Req) ->
{error, Reason} {error, Reason}
end. end.
async_call(Req) ->
try gen_server:call(?MODULE, {async_call, Req}, ?CALL_TIMEOUT)
catch
exit:Reason ->
{error, Reason}
end.
notify(Msg) -> notify(Msg) ->
gen_server:cast(?MODULE, {notify, Msg}). gen_server:cast(?MODULE, {notify, Msg}).
@@ -55,6 +64,8 @@ init([]) ->
handle_call({call, Req}, _From, #st{} = S) -> handle_call({call, Req}, _From, #st{} = S) ->
{reply, call_connector(Req), S}; {reply, call_connector(Req), S};
handle_call({async_call, Req}, _From, #st{} = S) ->
{reply, call_connector(Req, false), S};
handle_call(_Req, _From, S) -> handle_call(_Req, _From, S) ->
{reply, {error, unknown_method}, S}. {reply, {error, unknown_method}, S}.
@@ -118,24 +129,39 @@ maybe_publish(_) ->
maybe_via(#{via := Via}, Info) -> maybe_via(#{via := Via}, Info) ->
Info#{via => Via}. Info#{via => Via}.
call_connector(Req0) -> call_connector(Req) ->
call_connector(Req, true).
call_connector(Req0, Wait) ->
{ViaId, Req} = maps:take(via, Req0), {ViaId, Req} = maps:take(via, Req0),
case gmhc_connector:whereis_id(ViaId) of case gmhc_connector:whereis_id(ViaId) of
undefined -> undefined ->
{error, no_connection}; {error, no_connection};
Pid when is_pid(Pid) -> Pid when is_pid(Pid) ->
Id = erlang:unique_integer(), Id = erlang:unique_integer(),
MRef = erlang:monitor(process, Pid), MRef = case Wait of
true -> erlang:monitor(process, Pid);
false -> none
end,
gmhc_connector:send(ViaId, #{call => Req#{ id => Id }}), gmhc_connector:send(ViaId, #{call => Req#{ id => Id }}),
case Wait of
true ->
receive receive
{from_pool, #{reply := #{ id := Id, result := Result }}} -> {from_pool, #{reply := #{ id := Id
, result := Result }}} ->
erlang:demonitor(MRef),
Result; Result;
{from_pool, #{error := #{ id := Id } = Error}} -> {from_pool, #{error := #{ id := Id } = Error}} ->
erlang:demonitor(MRef),
{error, maps:remove(id, Error)}; {error, maps:remove(id, Error)};
{'DOWN', MRef, _, _, _} -> {'DOWN', MRef, _, _, _} ->
{error, no_connection} {error, no_connection}
after 5000 -> after 5000 ->
erlang:demonitor(MRef),
{error, {timeout, process_info(self(), messages)}} {error, {timeout, process_info(self(), messages)}}
end;
false ->
ok
end end
end. end.
+51 -23
View File
@@ -1,4 +1,5 @@
-module(gmhc_server). -module(gmhc_server).
-vsn("0.6.1").
-behaviour(gen_server). -behaviour(gen_server).
@@ -44,7 +45,7 @@
-define(CONNECTED(S), map_size(S#st.connected) > 0). -define(CONNECTED(S), map_size(S#st.connected) > 0).
-define(MAX_ERRORS, 5). -define(MAX_ERRORS, 50).
connected(Id, Type) -> connected(Id, Type) ->
gen_server:call(?MODULE, {connected, Id, Type}). gen_server:call(?MODULE, {connected, Id, Type}).
@@ -107,13 +108,16 @@ handle_cast({from_pool, #{via := Connector,
%% We could check whether we have already received the candidate ... %% We could check whether we have already received the candidate ...
%% For now, stop all workers, restart with new candidate %% For now, stop all workers, restart with new candidate
try try
% Most of the time we don't want to stop the worker. If we do, though, then %% 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. %% we need to do it more carefully than this, or memory usage will triple.
% Workers1 = stop_workers(Workers), %% Workers1 = stop_workers(Workers),
{Workers2, Cand1} = assign_nonces(Workers, Cand), %%
#st{candidate = Cand2} = S1 = maybe_request_nonces(S#st{candidate = Cand1}), %% Nonces may be [], in which case we need to request new nonces first.
NewWorkers = [spawn_worker(W, Cand2) || W <- Workers2], #st{candidate = Cand1} = S1 = maybe_request_nonces(S#st{candidate = Cand}),
{noreply, S1#st{workers = NewWorkers}} {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 catch
Cat:Err:St -> Cat:Err:St ->
?LOG_ERROR("CAUGHT ~p:~p / ~p", [Cat, Err, St]), ?LOG_ERROR("CAUGHT ~p:~p / ~p", [Cat, Err, St]),
@@ -151,11 +155,23 @@ handle_info({'EXIT', Pid, Reason}, #st{ workers = Workers
gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error, gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error,
data => Reason})), data => Reason})),
Ws1 = incr_worker_error(W, Workers), Ws1 = incr_worker_error(W, Workers),
erlang:start_timer(100, self(), check_workers),
{noreply, S#st{workers = Ws1}}; {noreply, S#st{workers = Ws1}};
false -> false ->
%% ?LOG_DEBUG("EXIT apparently not from worker?? (~p)", [Pid]), %% ?LOG_DEBUG("EXIT apparently not from worker?? (~p)", [Pid]),
{noreply, S} {noreply, S}
end; 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) -> handle_info(Msg, St) ->
?LOG_DEBUG("Unknown msg: ~p", [Msg]), ?LOG_DEBUG("Unknown msg: ~p", [Msg]),
{noreply, St}. {noreply, St}.
@@ -168,6 +184,9 @@ code_change(_FromVsn, S, _Extra) ->
report_solutions(Solutions, W, #st{} = S) when ?CONNECTED(S) -> report_solutions(Solutions, W, #st{} = S) when ?CONNECTED(S) ->
#{via := Via, seq := Seq} = W#worker.cand, #{via := Via, seq := Seq} = W#worker.cand,
Nonces = all_nonces(W),
[report_no_solution_(Via, Seq, N)
|| N <- Nonces, not lists:keymember(N, 1, Solutions)],
gmhc_handler:call( gmhc_handler:call(
#{via => Via, #{via => Via,
solutions => #{ seq => Seq solutions => #{ seq => Seq
@@ -175,19 +194,24 @@ report_solutions(Solutions, W, #st{} = S) when ?CONNECTED(S) ->
, evidence => Evd } , evidence => Evd }
|| {Nonce, Evd} <- Solutions] }}). || {Nonce, Evd} <- Solutions] }}).
%% report_solution(Nonce, Solution, W, #st{connected = true}) -> report_no_solution(_Nonce, W, #st{} = S) when ?CONNECTED(S) ->
%% #{seq := Seq} = W#worker.cand,
%% gmhc_handler:call(#{solution => #{ seq => Seq
%% , nonce => Nonce
%% , evidence => Solution }}).
report_no_solution(Nonce, W, #st{} = S) when ?CONNECTED(S) ->
#{via := Via, seq := Seq} = W#worker.cand, #{via := Via, seq := Seq} = W#worker.cand,
Nonces = all_nonces(W),
%% ?LOG_DEBUG("report no_solution Seq = ~p, Nonce = ~p", [Seq, Nonce]), %% ?LOG_DEBUG("report no_solution Seq = ~p, Nonce = ~p", [Seq, Nonce]),
gmhc_handler:call(#{via => Via, [report_no_solution_(Via, Seq, Nonce1) || Nonce1 <- Nonces],
ok.
report_no_solution_(Via, Seq, Nonce) ->
gmhc_handler:async_call(#{via => Via,
no_solution => #{ seq => Seq no_solution => #{ seq => Seq
, nonce => Nonce}}). , nonce => Nonce}}).
all_nonces(#worker{nonce = Nonce, config = Config}) ->
case gmhw_pow_cuckoo:repeats(Config) of
1 -> [Nonce];
Rs -> lists:seq(Nonce, Nonce + Rs - 1)
end.
maybe_request_nonces(#st{ candidate = #{via := Via, seq := Seq, nonces := Nonces} maybe_request_nonces(#st{ candidate = #{via := Via, seq := Seq, nonces := Nonces}
, nonces = N} = S) when ?CONNECTED(S) -> , nonces = N} = S) when ?CONNECTED(S) ->
case Nonces == [] of case Nonces == [] of
@@ -223,14 +247,14 @@ handle_worker_result({worker_result, Result}, W, S) ->
case Result of case Result of
{solutions, Solutions} -> {solutions, Solutions} ->
{Cont, S1} = report_solutions_(Solutions, W, S), {Cont, S1} = report_solutions_(Solutions, W, S),
maybe_continue(Cont, W, S1); maybe_continue(Cont, reset_errors(W), S1);
{solution, Nonce, Solution} -> {solution, Nonce, Solution} ->
%% report_solution(Nonce, Solution, W, S), %% report_solution(Nonce, Solution, W, S),
{Cont, S1} = report_solutions_([{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} -> {no_solution, Nonce} ->
report_no_solution(Nonce, W, S), report_no_solution(Nonce, W, S),
maybe_restart_worker(W, S); maybe_restart_worker(reset_errors(W), S);
{error, S} -> {error, S} ->
?LOG_DEBUG("Worker ~p reported error as normal", [W#worker.index]), ?LOG_DEBUG("Worker ~p reported error as normal", [W#worker.index]),
gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error, gmhc_events:publish(error, ?ERR_EVT(#{error => worker_error,
@@ -258,6 +282,9 @@ report_solutions_(Solutions, W, S) ->
{error, S} {error, S}
end. end.
reset_errors(#worker{} = W) ->
W#worker{errors = 0}.
reset_worker(#worker{index = I} = W, Ws) -> reset_worker(#worker{index = I} = W, Ws) ->
W1 = reset_worker_(W), W1 = reset_worker_(W),
lists:keyreplace(I, #worker.index, Ws, W1). lists:keyreplace(I, #worker.index, Ws, W1).
@@ -272,13 +299,14 @@ incr_worker_error(#worker{errors = Es, index = I} = W, Ws) ->
W1 = reset_worker_(W#worker{errors = Es+1}), W1 = reset_worker_(W#worker{errors = Es+1}),
lists:keyreplace(I, #worker.index, Ws, W1). lists:keyreplace(I, #worker.index, Ws, W1).
maybe_continue(stopped, _, S) -> %% maybe_continue(stopped, _, S) ->
S; %% S;
maybe_continue(continue, W, S) -> maybe_continue(continue, W, S) ->
maybe_restart_worker(W, S); maybe_restart_worker(W, S);
maybe_continue(error, W, S) -> maybe_continue(error, W, S) ->
?LOG_INFO("Won't restart worker ~p due to error", [W#worker.index]), ?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) -> maybe_restart_worker(#worker{index = I} = W, #st{candidate = C} = S) ->
case maps:get(nonces, C) of case maps:get(nonces, C) of
@@ -314,7 +342,7 @@ stop_workers_for_seq(Seq, Workers) ->
stop_worker(#worker{pid = Pid} = W) when is_pid(Pid) -> stop_worker(#worker{pid = Pid} = W) when is_pid(Pid) ->
MRef = erlang:monitor(process, Pid), MRef = erlang:monitor(process, Pid),
?LOG_DEBUG("Will stop worker ~p (MRef = ~p)", [Pid, MRef]), ?LOG_DEBUG("Will stop worker ~p (MRef = ~p)", [Pid, MRef]),
exit(Pid, kill), exit(Pid, shutdown),
receive receive
{'EXIT', Pid, _} -> ok; {'EXIT', Pid, _} -> ok;
{'DOWN', MRef, process, Pid, _} -> ok {'DOWN', MRef, process, Pid, _} -> ok
+1
View File
@@ -1,5 +1,6 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*- %% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
-module(gmhc_sup). -module(gmhc_sup).
-vsn("0.6.1").
-behaviour(supervisor). -behaviour(supervisor).
+1
View File
@@ -8,6 +8,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(gmhc_workers). -module(gmhc_workers).
-vsn("0.6.1").
-export([ -export([
get_worker_configs/0 get_worker_configs/0
+1 -1
View File
@@ -1,7 +1,7 @@
%% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*- %% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*-
{application, gmhive_client, {application, gmhive_client,
[{description, "Gajumaru Hive Client"}, [{description, "Gajumaru Hive Client"},
{vsn, "0.1.0"}, {vsn, "zomp"},
{registered, []}, {registered, []},
{applications, {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). -module(gmhive_client).
-vsn("0.6.1").
-export([ connect/1 -export([ connect/1
, disconnect/1 , disconnect/1
+4 -4
View File
@@ -2,11 +2,11 @@
{type,app}. {type,app}.
{modules,[]}. {modules,[]}.
{prefix,"gmhc"}. {prefix,"gmhc"}.
{author,"Ulf Wiger, QPQ AG"}.
{desc,"Gajumaru Hive Client"}. {desc,"Gajumaru Hive Client"}.
{package_id,{"uwiger","gmhive_client",{0,4,3}}}. {author,"Ulf Wiger, QPQ AG"}.
{deps,[{"uwiger","gmcuckoo",{1,2,3}}, {package_id,{"uwiger","gmhive_client",{0,6,3}}}.
{"uwiger","gmhive_worker",{0,3,0}}, {deps,[{"uwiger","gmhive_worker",{0,5,1}},
{"uwiger","gmcuckoo",{1,2,4}},
{"otpr","eblake2",{1,0,1}}, {"otpr","eblake2",{1,0,1}},
{"otpr","base58",{0,1,1}}, {"otpr","base58",{0,1,1}},
{"otpr","gmserialization",{0,1,3}}, {"otpr","gmserialization",{0,1,3}},
+7
View File
@@ -38,5 +38,12 @@ rm "$IGNORE_TEMP"
cp "$PWD/zomp.meta" "$DST/" cp "$PWD/zomp.meta" "$DST/"
cp "$PWD/Emakefile" "$DST/" cp "$PWD/Emakefile" "$DST/"
# copy generated schema
SCHEMA="$SRC/priv/gmhc_schema.json"
if [ -e "$SCHEMA" ]; then
mkdir -p "$DST/priv"
cp -a "$SCHEMA" "$DST/priv/$(basename "$SCHEMA")"
fi
# Clean up beam files just in case # Clean up beam files just in case
[ -d "$DST/ebin" ] && find "$DST/ebin" -name '*.beam' -exec rm -f {} + || true [ -d "$DST/ebin" ] && find "$DST/ebin" -name '*.beam' -exec rm -f {} + || true