Blockchain
Ethereum
ブロックチェーン
geth

Ethereumのプライベートチェーンでトランザクションが発生した時のみ、マイニングを実施する方法

背景

  • 以前、PoA(Proof of Authority)のコンセンサスアルゴリズムを用いたプライベートチェーンの構築方法をこちらの記事に記載させていただきました。
  • 無事、複数ノードでのマイニングが実現でき、difficultyも定数のまま、1sに1回、低負荷でブロックを生成できるようになりましたが、今度は空のブロックが常に生成され続けるという悩みに直面しました。
  • そこで、トランザクションが発生した時のみ、マイニングを開始するというロジックを実装し、空ブロックの生成を抑えることに成功したので、その手順について記事に残すことにしました。
  • このテクニックを使うことにより、無駄なブロックの生成を抑えることができるため、マイニングによるCPU負荷を下げられるPoAと組み合わせると、更に安定性の高いプライベートチェーンの構築が可能になると思われます。

前提条件

手順

トランザクションの発生時のみ、マイニングを実行するJavaScriptの作成

  • 今回、トランザクションの発生時のみ、マイニングを実行させたいため、Pendingとなっているトランザクションがあったときのみ、実行するJavaScriptを用意します。
  • 具体的には以下の通りとなります。
function checkPending() {
  if (eth.getBlock("pending", true).transactions.length > 0) {
    if (eth.mining) return;
    miner.start(1);
  } else {
    miner.stop();
  }
}

eth.filter("latest", function(err, block) { checkPending(); });
eth.filter("pending", function(err, block) { checkPending(); });

checkPending();
  • こちらのファイルを、 mine_pending.js など適当なファイル名で保存します。

node1での上記JavaScriptの実行

  • node1で上記のJavaScriptを実行するため、コンソールモードで立ち上げを行います。
  • 詳しくは前提条件のセクションに記載した記事の内容を参考にしてください。(このコマンドは、あくまで例ですので、addressやbootnodesのURLなどは適宜置き換えてください。)
geth --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr 'localhost' --rpcport 8501 --rpcapi 'personal,db,eth,net,web3,txpool,miner' --bootnodes 'enode://abaeeba464158234f51971698a7f956b8ef66f42aa7c6f61daaea68ee6d60e5dc6d3a0fbdef2eb17c1a508ebf5314bc29b0363064e1ce130d65e427c8d89ce5b@127.0.0.1:30310' --networkid 1515 --gasprice '0' --unlock '0x92d366c53b5789c2208db13a86acb5e8400ef8f0' --password node1/password.txt console
  • するとコンソールが立ち上がるので、以下のコマンドを実行します。
  • このコマンドを実行すると、まさにトランザクションが発行されるのを待つ状態になります。
> loadScript('./mine_pending.js')
true

node2でのminingの開始 (node3以上の場合も同様)

  • PoAでは信頼ノードとして設定されたノード全員がMiningを実施している事が前提となり、例えminingを開始していたとしても、他のnodeがminingを実施していないときは、自身のノードもwait状態になります。
  • よって、node2については、通常通り、miningを開始する形でOKです。
  • もし、3つ以上のノードを立てる場合においても、node1以外はnode2と同様に、mining開始状態にしてOKです。
  • 具体的な起動コマンドはこんな感じになります。 (こちらも、あくまで例ですので、addressやbootnodesのURLなどは適宜置き換えてください。)
geth --datadir node2/ --syncmode 'full' --port 30312 --rpc --rpcaddr 'localhost' --rpcport 8502 --rpcapi 'personal,db,eth,net,web3,txpool,miner' --bootnodes 'enode://abaeeba464158234f51971698a7f956b8ef66f42aa7c6f61daaea68ee6d60e5dc6d3a0fbdef2eb17c1a508ebf5314bc29b0363064e1ce130d65e427c8d89ce5b@127.0.0.1:30310' --networkid 1515 --gasprice '0' --unlock '0x06cb591585e3d12c881da2b7d2edb0bb9268c706' --password node2/password.txt --mine
  • すると、以下の通り、他の参加nodeのminingを待つ形となります。 image.png

node1へのIPC接続と送金処理の実施

  • JavaScriptにより、transactionの発生を待っているnode1へIPC接続し、コンソールを立ち上げます。
geth attach node1/geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.3-stable/darwin-amd64/go1.10.1
coinbase: 0x92d366c53b5789c2208db13a86acb5e8400ef8f0
at block: 3762 (Mon, 16 Apr 2018 22:10:28 JST)
 datadir: /Users/atsushi-fukuda/devnet/node1
 modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> 
  • この状態で、送金処理を行ってみます。
eth.sendTransaction({'from':eth.coinbase, 'to':'0x06cb591585e3d12c881da2b7d2edb0bb9268c706', 'value':web3.toWei(3, 'ether')})
"0xa9992a211e31d29da137d836aa0d545d2e12bfa5d2ec0ba1b823401d88fb2592"
  • 以下のコマンドを打って、トランザクションが処理されたことを確認します。
> eth.getTransactionReceipt("0xa9992a211e31d29da137d836aa0d545d2e12bfa5d2ec0ba1b823401d88fb2592")
{
  blockHash: "0xcac9a9fda5c0ec7f51d5a94f5c7faa9c5d8c9998569fa71815f6a30e5c2d9136",
  blockNumber: 3763,
  contractAddress: null,
  cumulativeGasUsed: 21000,
  from: "0x92d366c53b5789c2208db13a86acb5e8400ef8f0",
  gasUsed: 21000,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0x06cb591585e3d12c881da2b7d2edb0bb9268c706",
  transactionHash: "0xa9992a211e31d29da137d836aa0d545d2e12bfa5d2ec0ba1b823401d88fb2592",
  transactionIndex: 0
}

処理終了後に、node1, node2のマイニングが一時停止していることの確認

  • 無事トランザクションが処理されましたが、これでマイニングが動き続けていたのでは意味がありません。
  • トランザクションの処理が終わったら、マイニングが正しく終了していることを確認します。

node1の状態

  • 正しくマイニングが実行され、一時停止状態になっています。

image.png

node2の状態

  • node2についても同様に、一時停止状態になっています。

image.png

  • 無事、node1もnode2もトランザクション終了後には一時停止状態になっていることを確認できました。

まとめ

  • この通り、IPC接続などにより、トランザクション実行を受け付けるnodeにおいて(この記事ではnode1のことを指します)、トランザクションの発生までマイニングを待つJavaScriptを実行させることにより、全ノードでのマイニングを停止させ、無駄なマイニングによる空ブロックの生成を抑えることができます。
  • PoAによって、CPUの負荷も下げられ、さらにこのテクニックにより、無駄な空ブロックの生成も抑えられるので、更に安定性の高いプライベートチェーンの構築が可能になると思われます。

謝辞