公開を楽しみにしている暗号資産(仮想通貨)があるのですが、あまりにもマイニング素人すぎて不安なので既存のコインで練習することにしました。

練習台としたのは、性質の近い(CPUマイニングが可能)Monero(モネロ)のテストネットです。ノード構築からマイニング、報酬の受け取りまでを一通り予行演習してみました。

テストネットなので、掘ったコインには当然ながら価値はありません。収益は関係なく、費用ゼロ・追加ハードなしで純粋にマイニングの一連の流れを練習しよう、という意図です。

なぜMoneroで練習するのか

公開を楽しみにしている暗号資産というのはCodablecashのことなのですが、こちらのPoW(マイニングの計算)は、CPU向けの設計になっています。そして、既存の暗号資産でこの思想の代表格がMoneroで、RandomXというこれまたCPU向けのアルゴリズムを使っているそうです。

  • CPUだけで掘れる(うちのGPUなしミニPCでも参加できる)
  • テストネットが公開されている

というわけで、練習場としては申し分ありません。マシンはいつもの中古ミニPC(Core i3-10105T、4コア8スレッド、GPUなし)です。

環境づくり: Podmanに閉じ込める

Podmanというルートレスコンテナを使用し、こちらの隔離環境で練習を行います。

まずやることは、公式サイトのダウンロードページからMoneroのバイナリ(v0.18.5.1)のダウンロード・展開です。つまり、ソースからのビルド等は不要です。

そしてこの手順を、Containerfileというファイルに書いておきます。Containerfileとは、DockerでいうDockerfileと同じもので、ファイル名のContainerfileは、Podmanが探しに来る決まりの名前です。Podmanまわりの流れを整理すると、こうなります。

  1. Containerfileを用意(「コンテナの中身を作るレシピ」。自分でテキストエディタで作成
  2. podman build -t monero-practice .と打つと、Podmanがこのレシピを上から順に実行して、イメージ(環境の完成品の雛形)を作る。monero-practiceはそのイメージに自分で付けた名前
  3. podman run ... monero-practiceで、そのイメージからコンテナ(実際に動く環境)を起動する

今回書いたContainerfileの中身はこちらです。ubuntu:24.04を土台に、上のダウンロードと展開を行っています。

FROM docker.io/library/ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
        bzip2 \
    && rm -rf /var/lib/apt/lists/*

# バージョン更新時は https://www.getmonero.org/downloads/hashes.txt で
# バージョンとハッシュを取り直すこと
ARG MONERO_VERSION=v0.18.5.1
ARG MONERO_SHA256=22a7dda7b0cb699fdd6b7674c3b4a4465b337cc98a54983523b759e1e7cc9958

RUN curl -fsSL -o /tmp/monero.tar.bz2 \
        "https://downloads.getmonero.org/cli/monero-linux-x64-${MONERO_VERSION}.tar.bz2" \
    && echo "${MONERO_SHA256}  /tmp/monero.tar.bz2" | sha256sum -c - \
    && mkdir -p /opt/monero \
    && tar xjf /tmp/monero.tar.bz2 -C /opt/monero --strip-components=1 \
    && rm /tmp/monero.tar.bz2

# ubuntu:24.04に最初からいるubuntuユーザーを、非rootのminerユーザーに置き換える
RUN userdel -r ubuntu 2>/dev/null || true; \
    groupadd -g 1000 miner \
    && useradd -m -u 1000 -g 1000 -s /bin/bash miner

USER miner
WORKDIR /data
ENV PATH="/opt/monero:${PATH}"

CMD ["/bin/bash"]

こちらのコードの中で、展開する前にSHA256ハッシュを公式サイト掲載の値と照合しています(sha256sum -cの行)。本物のコインを扱うソフトは偽物をつかまされる事故が怖いので、練習のうちから手癖にしておきます。コンテナの中で動かすユーザーも非rootのminerにしました。

書けたら、ビルドしてイメージを作ります。

$ podman build -t monero-practice .

続いて起動です。起動コマンドの骨格はこうです。

$ podman run -d --rm --name monero-testnet \
    --userns=keep-id --cap-drop=all --security-opt=no-new-privileges \
    -v "$PWD/data:/data" monero-practice \
    monerod --testnet --data-dir=/data --prune-blockchain --no-igd --non-interactive

monerodがMoneroのノード本体(常駐プログラム)です。オプションの意味は以下のような感じです。

オプション 役割
--testnet テストネットに参加
--data-dir=/data チェーンデータ・ピア情報の保存先。コンテナ外に永続化
--prune-blockchain 古い取引の詳細を間引いてディスク節約(検証能力は維持)
--no-igd ルーターへのポート開放要求(UPnP)を無効化
--non-interactive 対話プロンプトなしで常駐(コンテナ運用向け)

また、ポート公開(-p)を付けていません。今回は外向きに接続しに行くだけの非公開ノードになり、外からは誰もこちらに到達できない形で練習します。

ノードを起動して、同期を待つ

先ほどのpodman runコマンドは長いので、run-monerod.shという起動スクリプトに保存してあります。起動はこれを実行するだけです。

$ ./run-monerod.sh

こちらのコマンドでノードを起動すると、ノードはまずチェーンの先頭に追いつくまでブロックをダウンロードして検証し続けます。テストネット全同期(高さ約305.5万ブロック)で、夜に開始して、翌朝には終わっていた、というペース感でした。

同期中のCPU使用率はというと、ダウンロード待ちでヒマかと思いきや約3.7コア分をずっと使っていました(CPU温度63〜66°C)。同期とは全ブロックの署名やPoWを自分で検証し直す作業で、これ自体がけっこうなCPU仕事なんですね。

状態確認は、常駐しているノードに外から質問(コマンド)を送る、以下のような形です。podman execは「コンテナの中でコマンドを実行する」という意味です。

$ podman exec monero-testnet monerod --testnet status

これで同期率・自分のハッシュレート・ネットワーク全体のハッシュレート・接続ピア数が1行で返ってきます。ピアの一覧(相手のIPと状態)を見たいときはprint_cnです。

ウォレット作成

次に、マイニング報酬の受け取り先が要るので、ウォレットを作ります。

$ podman exec -it monero-testnet monero-wallet-cli --testnet --generate-new-wallet /data/my-wallet

対話形式で2、3やりとりがあり、それにより鍵とシード(復元用の合言葉)が生成されます。2回目以降は--wallet-file /data/my-walletで開き、ウォレット内のプロンプトでbalance(残高)やshow_transfers(入出金履歴)を打つ、という操作体系です。

[wallet 9xxxxx]: balance
[wallet 9xxxxx]: show_transfers

ウォレットに入るとプロンプトが[wallet 9xxxxx]:のような表示に変わり(自分のアドレスの先頭数文字が入ります)、そこにこうしたコマンドを打っていく形です。

ここで、個人的に覚えておきたい点をメモ。

  • ノードに渡すのはウォレットアドレスのみ。後述のマイニング開始コマンドに渡すのもアドレス(公開の宛名)だけで、ノードは「掘れたら報酬をこの宛名に払う」ことしか知らない状態
  • ウォレットは鍵ファイルの管理者+ノードへの質問クライアント。開くたびにRefreshing...と表示が流れ、ノードに「私宛の取引は増えてないか」を問い合わせて追いついている様子でした。ノードとウォレットの間に恒久的な紐付けや登録は存在しないようです。
  • アドレスは秘密鍵から計算された公開の名前。他人に知られても「送金される」ことしかできず、資産は動かせない

ちなみに、掘れたブロックの中身はブロック高を指定して見ることができます。

$ podman exec monero-testnet monerod --testnet print_block <ブロック高>

ブロックには、採掘報酬を受取アドレスに払うための取引(コインベーストランザクション)が入っています。そしてMoneroはプライバシーコインというだけあって、出力先は毎回使い捨ての一時鍵(ステルスアドレス)に変換されるため、ブロックを調べても受取アドレスそのものは読めません

ただしこれはMoneroがその仕様なのであって、Bitcoinをはじめとする透明型のチェーンでは、受取アドレスは掘ったブロック全部に刻まれて半公開になります。「受取用と保管用でアドレスを分けるべき」という話は知識として知っていましたが、なるほどやはり分けるべきですね。

いよいよマイニング

同期が終わったら、ノードにマイニング開始の指示を送ります。

$ podman exec monero-testnet monerod --testnet start_mining <受取アドレス> 3

最後の数字は使うスレッド数です。start_miningで(ノード本体の中のスレッドで)マイニングがスタートし、止めるときはstop_miningという指示を送ります。

$ podman exec monero-testnet monerod --testnet stop_mining

スレッドとは、ざっくり言うと「同時に走らせる採掘係の人数」です。うちのCPUは4コア8スレッドなので最大8まで指定できますが、増やすほどCPUを占有して熱とファン音も増えます。まずは控えめに3人でスタートしました(この人数がちょうど良かった理由は、後の実測で判明します)。

ネットワークが小さすぎて自分が主役に

開始直後にstatusで確認すると、テストネット全体のハッシュレートは654H/s

ハッシュレートとは、マイニングの「くじ引き」(ハッシュ計算)を1秒間に何回試せるかという数値で、単位はH/sです。654H/sは、ネットワークの参加者全員をあわせて毎秒654回しか引いていないという意味になります。次の実測で見るように、うちのミニPCは1台で1kH/s(1,000H/s)以上出ます。

つまり自分の参加でネットワーク全体の数字が動く規模です。

実際、掘り始めるとネットワーク全体のハッシュレートが654H/sから2.2kH/sへ上がっていくのが観測できました。マイナーの計算力に応じて難易度が調整された結果、このように数値が変わったようです。こんな様子が見られるのは、閑散としたテストネットならではだと思います。

スレッド数を変えて実測

スレッド数 自分のハッシュレート Package温度 ファン回転数
3 1.08kH/s 66°C 2594RPM(最大3230)
7 1.24kH/s 70°C 3054RPM

温度とファンはホスト側でsensorsコマンドを眺めて記録しました。見ての通り、**スレッドを2.3倍にしてもハッシュレートは+15%**しか伸びません。

調べたところ、RandomXは1スレッドあたりL3キャッシュ(CPU内蔵の小さな超高速メモリ)を2MB要求する設計で、L3が6MBしかないうちのCPUでは3スレッドで満杯。4本目以降はメインメモリに溢れて渋滞するのだそうです。つまりこのマシンのMonero最適解は3スレッドでした。

温度は7スレッド全力でも70°C(警告80°C・危険100°C)で余裕あり。むしろ律速は熱ではなくファンの音で、3054RPMは最大回転数の約95%、実質「常に最大音」です。マイニングは24時間回すものなので、この音の基準点が取れたのは良い収穫でした。

初ブロック、そして報酬のロック

マイニング開始から約5分後、ブロックを掘り当てました。自分がブロックを当てても特に通知は来ないのでログをチェックします。このコマンドで抽出できます。

$ podman logs monero-testnet | grep -i "found block"

以後、ネットワーク内のシェア約47%で数時間ほど回して**51ブロック(30.6XMR相当)**掘れました。Moneroの報酬は現在1ブロック0.6XMR固定です(2022年に減衰が終わり、「テール・エミッション」と呼ばれる永久固定発行に移行済み)。

ただし掘った報酬はすぐには使えません。コインベース報酬(採掘報酬)は60ブロック熟成するまでロックされる仕組みで、ウォレットのbalanceにはこう表示されます。

unlocked balance: 0 (49 block(s) to unlock)

総残高とは別に「いますぐ使える残高(unlocked balance)」が表示され、括弧内は解除までの残りブロック数です。この例だと「最初の報酬が使えるまであと49ブロック」で、Moneroのブロック間隔は約2分なので時間にして100分ほど。掘り当てるたびに新しいロックが積まれるので、回している間はこの数字がずっと動き続けます。

なぜこんなロックがあるかというと、チェーンの先端付近はまれに分岐で覆る(ブロックがなかったことになる)ことがあり、その場合はブロックと一緒に採掘報酬も消えてしまうからです。消えるかもしれないお金をうっかり使わせないよう、深く埋まって覆らなくなるまで待たせる安全装置、というわけですね。とても面白い仕組みです。

IPとIDの話

非公開ノードにしたから完全に匿名で参加できている、と思いきや、そうでもありませんでした。

  • 非公開ノード(ポート開放なし)でも、外向きに接続した相手には自分のIPが見えます。IPを知られてもできることはほぼないとは思いますが
  • Moneroのノード識別子(ピアID)はランダムな64bitの使い捨て名札で、データディレクトリ内のtestnet/p2pstate.binに保存されます。このファイルを消して再起動すれば別人として再登場でき、本人が見る必要すらないので表示コマンドも存在しません

つまり、IPだけを気にするのではなく、ノードIDにも気をつける必要があるということです。IP単体ではなく**「IPとIDの紐付け」**により、一意のユーザーが特定されやすくなります。といって先述のようにMoneroはIDが使い捨てなので、いつでも破棄して新しい名札を取得すればいいということになります。

後片付け

練習を終えたチェーンデータは6GBほどになったので、記事を書き終えた時点で削除しました。一応、削除コマンドはこちらです。

$ podman stop monero-testnet   # ノードを停止(--rm付きで起動したのでコンテナも自動で消える)
$ rm -rf ./data            # チェーン+ウォレット
$ podman rmi monero-practice   # コンテナイメージ

ウォレットの鍵ごと消えるので、その後も回して増えていた最終残高の34.2XMRも消滅しますが、テストネットなので問題ありません。また立ち上げたくなったら、Containerfileと起動スクリプトさえ残しておけば、「ビルド→起動→同期一晩」でいつでも環境を丸ごと再現できます。

まとめ

  • Moneroテストネットで、ノード構築→同期→ウォレット作成→ソロマイニング→報酬受け取りまでを費用ゼロで一巡できた
  • うちのミニPC(L3 6MB)のRandomX最適解は3スレッド
  • 非公開ノードでもIPは接続相手に見える。守るべきは「IPとIDの紐付け」