cpuminerをGUIで管理するツールを作った for BitZeny

Bitzeny Miner Manager

タイトルが全てです。
cpuminer のパラメータ調節をGUIから手軽に行う為のツールを作りました。
Bitzeny用に特別なチューニングを行っているわけではありませんが、 cpuminer に対して-a yescryptを決め打ちで流しているため、現在はそうしています。
この記事には前半は使い方、後半では幾つか裏で使った技術についてを備忘録的に残しておきます。

使い方

  1. インストーラココからDL
  2. 実行しインストール
  3. スタートメニューから Bitzeny Miner Manager を起動
  4. CPUMiner実行ファイルへのパスを Miner に入力
  5. プールを選択
  6. ユーザ名/ワーカ名/パスワードを入力
  7. [Run] をクリック
  8. ([Save] でデータを保存)

スレッド数のデフォルト値はCPUのコア数を取得し代入しています。
特に変更しなければ、-tオプションを指定しなかった場合と同様に動作します。

保存したデータは次回起動時に自動的に読み込まれ、デフォルト値として利用されます。

裏側

構成

アプリケーションを手軽に、高速にリリースすることを念頭に置いていたので、プラットフォームにはElectronを採用しました。
HTML5/CSS3/Node.jsとフロントエンドの様々なライブラリが利用できる為、開発コストが低減されます。
配布する際にはElectronの実行環境を含める必要がある為サイズが大きくなる、などの弱点はありますが、動作環境のリソースがそれほど枯渇しているということは考えられない為、利点が勝ると考え採用しています。
(v1.0.1現在、インストーラはサイズ59.0MB、展開後159MBとなります。) この構成を採用したことで、アプリケーションのロジック部分は8時間で完成しています。

実装

マイナー制御

マイナー起動はchild_process.spawnで実現しています。

const {spawn} = require('child_process');
const command = $miner.val();
const args = ["-a", "yescrypt",
              "-o", (() => { return $pool.val() === "other" ? $pool_raw.val() : $pool.val() })(),
              "-u", $user.val() + "." + $worker.val(),
              "-p", $password.val(),
              "-t", $threads.val()];
child = spawn(command, args);

レスポンスは

child.stderr.on('data', data_bytes => {
    const data = data_bytes.toString();
    console.log(data);
});

で拾い上げられます。
child.stdout.on() も存在するようですが、stdoutでは拾えませんでした。今もなぜだかわかりません。
マイナーの停止はchild.kill('SIGINT')を呼ぶだけです。

ハッシュレートやValid/Invalidの表示は出力をregexでmatchすることで拾っています。

const regex = /\[(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\]\s(accepted:\s(\d+)\/(\d+) \((\d+\.\d+%)\), (\d+\.\d+\skhash\/s)\s\S*)/;
const match = data.match(regex);
if (match !== null) {
    $hashrate.text(match[6]);
    $valid.text(match[3]);
    $invalid.text(match[4] - match[3]);
}

面倒な正規表現Regexperで逐一確認しながら構築していくと手間が少なくなると思います。

設定保存/読み込み

設定の保存はelectron-json-storageを利用しています。
保存は

storage.set('config', data, error => {
    if (error) throw error;
});

読み込みは

storage.get('config', (error, data) => {
    if (error) reject(error);
});

ね、簡単でしょ?
ファイルの書き込み/読み込みは面倒なことが多い中で確実に動いてくれる印象です。
保存先はデフォルトの%AppData%\miner-manager\になっています。

アラート

設定を保存した際やStratumへの接続に失敗した際など、ウィンドウ左下にアラートが表示されます。 f:id:kalmare:20171214182632p:plain

Foundationのデザインにいい感じにマッチしていますが、Foundationではなくalertify.jsというnpmのパッケージを利用しています。

alertify.success("Configuration saved successfully!");
alertify.error("Stratum authentication failed");

1行でいい感じのアラートを出せるので中々に便利だと感じています。
npmには alertify というほぼ同名のライブラリがあるのでそれだけ注意が必要そうです。

アップデート確認

最新版がリリースされるとアプリ上部に最新版の通知が表示されます。
イケてるアプリケーションなんかだと NuGet なんかを使うのでしょうけど、エラー続きで心が折れてしまったので、Webサーバに最新バージョンを記載したjsonを配置し、app.getVersion()と突き合わせることでなんとか実現しています。
このあたり非常にイケてないので落ち着いたら直そうと思っています。

LiveReload

フロントエンド側のHTML/CSS/JSを編集すると、自動的に再読込が行われて編集後の形を確認できるようにしています。
これはGulpのgulp.watchelectron-connectで実現しています。
フロントエンド側JSに1行必要ですが、配布時には必要ない為gulp-replaceで置換消去しています。

require('electron-connect').client.create();
const replace = require('gulp-replace');
gulp.task('remove_debugger', ['compile'], () => {
    return gulp.src(build+'/index.js')
        .pipe(replace(/require\('electron-connect'\).client.create\(\);/g), '')
        .pipe(gulp.dest(build));
});

パッケージング

Gulpとelectron-packagerで自動化しています。

const packager = require('electron-packager');
gulp.task('package', ['remove_debugger'], () => {
    return packager({
        dir: './',
        out: 'release/',
        name: 'Bitzeny Miner Manager',
        arch: 'x64',
        platform: 'win32',
        overwrite: true
    });
});
インストーラ

electron-winstaller で自動化...の予定だったのですが、アプリケーション名にハイフンが入っていると失敗する、などいくつかの不具合があり、実現できませんでした。
結局HM NIS EditでNSISスクリプトを作り手作業で実行しています。
NSIS自体は非常によく見る普通のインストーラを生成でき素晴らしいのですが、なんせソフト自体が10年物なので古臭さは感じるでしょう。
f:id:kalmare:20171219030250p:plain

おわりに

ある程度のプロダクトとしてははじめてソースコードGithubで公開しています: kalmare/miner-manager
Twitterで冒頭の告知ツイートを見た方がコードに対して有益な意見を送ってくれたりして、個人的には成功だと思っています。
難しいことはしていませんが、それでもコードを公開状態に置くことにはそういう意義があるのかもしれません。 今後も機能追加/改善など行っていきたいと考えているので、何卒よろしくお願いします。