Blockchain
Ethereum
geth

「Ethereum入門」を Geth 1.6.7 で進めてみた(インストールからetherの送金まで)

はじめに

Ethereum入門 というEthereumのチュートリアルに取り組みました。

このチュートリアルではEthereumのクライアントとして Geth (go-ethereum, Go製のクライアント) が使われているのですが,Gethのバージョニングは セマンティック・バージョニング に従っておらず,1.5.x と 1.6.x の間にいくつか破壊的変更も含まれています。つまり 1.5.x 向けに書かれた資料が 1.6.x では動かないことがあります。そのため,1.6.7 でチュートリアルを進める上でそのままでは動作しなかったところを中心に知見をまとめました。

筆者の環境

$ geth version
Geth
Version: 1.6.7-stable
Git Commit: ab5646c532292b51e319f290afccf6a44f874372
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.8.3
Operating System: darwin
GOPATH=/Users/arosh/go
GOROOT=/usr/local/Cellar/go/1.8.3/libexec

Geth 1.6.7のコードネームは AYTABTU (All Your Transaction Are Belong To Us) といいます。

Gethをインストールする

https://install-geth.ethereum.org からインストールする方法は廃止されました。最新のインストール方法はgo-ethereumのGitHub Wikiに書いてあります。

Building Ethereum · ethereum/go-ethereum Wiki

私はmacOSなのでHomebrewでインストールしました。

brew tap ethereum/ethereum
brew install ethereum

プライベート・ネットを作成する

本番のEthereumネットワークを使うには送金やコントラクトのデプロイ,実行などに手数料が発生してしまうので,テスト用途ではプライベート・ネットを作るのが便利です。プライベート・ネットを作成する方法には,自分で genesis.json を用意する方法と,--dev オプションを使う方法がありますが,複数台のノードでネットワークを構築しないのであれば --dev オプションを使う方法のほうが簡単だと思います。

自分で genesis.json を用意する場合

genesis.json のパラメータの意味については以下の投稿を参照してください。

Geth 1.6からgenesis.jsonconfig節を追加する必要があります。GethのREADMEをコピペするのが良いのではないでしょうか。

https://github.com/ethereum/go-ethereum#defining-the-private-genesis-state

genesis.json
{
  "config": {
        "chainId": 10,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
  "alloc"      : {},
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x20000",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000042",
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00"
}

注意しなければならないのは config.chainID の値と,Gethを起動する時に使う --networkid の値を一致させる必要があることです。これを正しく設定していないとマイニングを行ったときに "invalid sender" というエラーが出ます(参考:最小構成のEthereum Tokenのprivatenetへの登録)。

プライベート・ネットを作成するには以下のコマンドを実行します。今回は他のノードとネットワークを構築させないため --nodiscover --maxpeers 0 を指定しています。

geth --datadir . genesis.json
geth --datadir . --networkid 10 --nodiscover --maxpeers 0

--dev オプションを使う場合

便利なオプションですが,ヘルプには "Developer mode: pre-configured private network with several debugging flags" としか書いておらず,結局ソースコードを読んで挙動を推測しました。さらに悪い事に,古いバージョンのGethとは挙動が変わっているようなので,古い記事を読む際には注意が必要です(参考:Ethereum go-ethereum/gethでのプライベート環境構築方法について)。

  1. geth init genesis.json で作成したGenesisブロックは無視される。代わりに特殊な設定のGenesisブロックが利用される genesis.go#L139, genesis.go#L345
  2. P2Pネットワークを利用しない設定になる flags.go#L797
  3. --datadir を設定しなかった場合,/tmp/ethereum_dev_mode が使用される flags.go#L819
  4. --gasprice を設定しなかった場合 --gasprice 0 になる flags.go#L992

開発環境と本番環境で挙動が大きく変わってしまうため, --gasprice 0 の設定は変更したほうがよいかもしれません。

プライベート・ネットを作成するには以下のコマンドを実行します。

geth --datadir . --dev --gasprice 18000000000

etherを採掘して送金する

ここからしばらくの間は Ethereum入門 の内容がそのまま動作するので説明は省略します。

$ geth attach ipc:geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/v1.6.7-stable-ab5646c5/darwin-amd64/go1.8.3
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0

# アカウントを作成する
> personal.newAccount("password1")
"0x21baabbd50c4ece4423be3588993bd97e92ef637"

# あと2つ作成する
> personal.newAccount("password2")
"0xd02f002f7f1cf12ca80d99b922db6377dbd60384"
> personal.newAccount("password3")
"0xfd30e3096902bc1f0f98e22e1e72440625c49bbc"

# アカウントの確認
> eth.accounts
["0x21baabbd50c4ece4423be3588993bd97e92ef637", "0xd02f002f7f1cf12ca80d99b922db6377dbd60384", "0xfd30e3096902bc1f0f98e22e1e72440625c49bbc"]

# マイニング報酬を振り込むアカウントの確認
> eth.coinbase
"0x21baabbd50c4ece4423be3588993bd97e92ef637"

# マイニングの開始(実行してからしばらく待つ)
> miner.start(1)
null

# マイニングされたブロックの数
> eth.blockNumber
147

# ブロックの情報の表示
> eth.getBlock(100)
{
  difficulty: 136718,
  extraData: "0xd883010607846765746887676f312e382e338664617277696e",
  gasLimit: 4712388,
  gasUsed: 0,
  hash: "0x1ab0effc87f0fd16bfe23a77b9f1b15577429cbbf81887d6404e8c7c41eac4b6",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x21baabbd50c4ece4423be3588993bd97e92ef637",
  mixHash: "0xd33adcca8106b4c1bea4bc13ed9a8ba1dd4dd2da49e35c8d1d3a016607b83ef7",
  nonce: "0x2b50413110c99b50",
  number: 100,
  parentHash: "0xdd927202ee0464cf30462f42b442370a57bf6385124d92945e76ffbbf6aa21ea",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 536,
  stateRoot: "0xeb1b9b3c9015d485a59acda530829b838dfc2770836289013aa8b718c6a5d497",
  timestamp: 1504169999,
  totalDifficulty: 13511140,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

> eth.getBlock(101)
{
  difficulty: 136784,
  extraData: "0xd883010607846765746887676f312e382e338664617277696e",
  gasLimit: 4712388,
  gasUsed: 0,
  hash: "0xdf4e303da71e8d94df6eb046f70f05966595022fc4dab259ebcec48cd8d81a46",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x21baabbd50c4ece4423be3588993bd97e92ef637",
  mixHash: "0xb01a8c3c0ba8e0abe2bec6d7cf6e9db0cd7d4bee278c0cda8df644dd72f9bcc6",
  nonce: "0x12b956a969d89734",
  number: 101,
  parentHash: "0x1ab0effc87f0fd16bfe23a77b9f1b15577429cbbf81887d6404e8c7c41eac4b6",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 536,
  stateRoot: "0xdaaeadd42f5a310b728cb82e3b0464061ee69bbcdbf4defcc2763361f960c5f1",
  timestamp: 1504170000,
  totalDifficulty: 13647924,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

# マイニング報酬をether単位で表示
> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
780

# 1番目のアカウント (coinbase) から2番目のアカウントに1 ether送金する
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei("1", "ether")})
Error: authentication needed: password or unlock
    at web3.js:3104:20
    at web3.js:6191:15
    at web3.js:5004:36
    at <anonymous>:1:1

# アカウントをアンロックするのを忘れていた
> personal.unlockAccount(eth.accounts[0], "password1")
true

# 送金をやり直す
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei("1", "ether")})
"0x50f84b46497a9b4eba2cb0eb50bf0d52b3aecbe1407efdd283e362f156c60dde"

# 2番目のアカウントの残高を確認する
> web3.fromWei(eth.getBalance(eth.accounts[1]))
1

# 2番目のアカウントから3番目のアカウントに0.5 ether送金する
> personal.unlockAccount(eth.accounts[1], "password2")
true
> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[2], value: web3.toWei("0.5", "ether")})
"0xd38d12bff2e17101219374c324213f9f5cfcbb3c5676fca231de641e4d8fc8ec"

# 2番目と3番目のアカウントの残高を確認する
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
0.499622
> web3.fromWei(eth.getBalance(eth.accounts[2]), "ether")
0.5

# 2番目のアカウントの残高が 1 - 0.5 = 0.5 ether よりも少ないのは,送金手数料が発生したため
> eth.getTransaction("0xd38d12bff2e17101219374c324213f9f5cfcbb3c5676fca231de641e4d8fc8ec")
{
  blockHash: "0x66ecfaf846224ff205d641ffcab38104f5acfa0871d902dc53dac5d036ddca35",
  blockNumber: 391,
  from: "0xd02f002f7f1cf12ca80d99b922db6377dbd60384",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0xd38d12bff2e17101219374c324213f9f5cfcbb3c5676fca231de641e4d8fc8ec",
  input: "0x",
  nonce: 0,
  r: "0x703bb4b15e87c3c0e2078ce2653a8895a121df290127dd091c6e7e68b7afd317",
  s: "0x6429a6a7bb5045457cdaa20257af2eb19d6d06d8230b3c4438f2f61156d693b9",
  to: "0xfd30e3096902bc1f0f98e22e1e72440625c49bbc",
  transactionIndex: 0,
  v: "0xa95",
  value: 500000000000000000
}

> eth.getBlock("0x66ecfaf846224ff205d641ffcab38104f5acfa0871d902dc53dac5d036ddca35")
{
  difficulty: 150262,
  extraData: "0xd883010607846765746887676f312e382e338664617277696e",
  gasLimit: 4712388,
  gasUsed: 21000,
  hash: "0x66ecfaf846224ff205d641ffcab38104f5acfa0871d902dc53dac5d036ddca35",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x21baabbd50c4ece4423be3588993bd97e92ef637",
  mixHash: "0x0153b82617b97cc391f66607e1a2b48fe1c305558ecebe6e7266777556ad58c5",
  nonce: "0x06a71e24164c1e67",
  number: 391,
  parentHash: "0xee60ab2e3c682e149c537b30e725f5bd852a239cf02ee2ccab8ecbde9ce2c375",
  receiptsRoot: "0xf37332ef7bba33f2050ea36c1ce837faf22df96685615941fb271db79330c5c2",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 654,
  stateRoot: "0x0a15295f8240dbd2ae57be581749e4ee720bb6397cadf1dbfa6ad3ade5b68be0",
  timestamp: 1504171620,
  totalDifficulty: 54678353,
  transactions: ["0xd38d12bff2e17101219374c324213f9f5cfcbb3c5676fca231de641e4d8fc8ec"],
  transactionsRoot: "0xb26180dce26dd5de1a6a32da3c0565f02da6ce3ed71403464a4c1a5453327fd7",
  uncles: []
}

# gas price が 18000000000 wei/gas で,送金に 21000 gas 必要なので,それだけの手数料が引かれている
> eth.gasPrice
18000000000
> web3.toWei("0.5", "ether") - 21000 * eth.gasPrice
499622000000000000
> eth.getBalance(eth.accounts[1])
499622000000000000

おまけ

Geth以外のクライアント

少し古いですが,Ethereumのクライアントのリストが以下のページにあります。

Choosing a client — Ethereum Homestead 0.1 documentation

現在でもメンテナンスされていると思われているクライアントは以下のとおりです。brew install ethereum でインストールできることからも分かるように,Go製のクライアントが最も人気があります。

Repository Language GitHub Star
https://github.com/ethereum/go-ethereum Go 6958
https://github.com/paritytech/parity Rust 1951
https://github.com/ethereum/cpp-ethereum C++ 1585
https://github.com/ethereum/ethereumj Java 695
https://github.com/ethereum/pyethapp Python 630

プライベート・ネット以外のネットワーク

ライブ・ネット

Ethereumの本番のネットワークです。開発の進行度によってFrontier, Homestead, Metropolis, Serenityという4段階の名前が付けられており,Frontierは2015年7月,Homesteadは2016年3月にリリースされました。次期のバージョンであるMetropolisは2017年中にはリリースされそうです。Serenityではコンセンサス・アルゴリズムがProof of WorkからProof of Stakeに変更される予定だそうですが,どうなるか分かりません。

イーサリアムには4つの開発段階がある - ビットコインラボ

接続するには --networkid 1 を指定します。

$ geth --help
...
--networkid value                          Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby) (default: 1)
...

テスト・ネット

テスト・ネットはテスト用に運用されている開かれたネットワークです。--networkid のヘルプにも載っているように,Ethereumのテスト・ネットは複数あります。

Ethereum Testnet Selection

現在のEthereumはコンセンサス・アルゴリズムとしてProof of Workが使用されています。etherに金銭的価値が生じないテスト・ネットで熱心にマイニングをする人はあまりいないため,簡単に乗っ取ることができるという問題があり,最初のテスト・ネットであるMordenを継続することが困難になったため新たにRopstenの運用が開始されました。

しかしながら,これでも根本的な解決にはなっていないため,Proof of Authorityというコンセンサス・アルゴリズムを使用しているRinkebyも運用されています。Proof of Authorityでは選ばれたコミュニティメンバーのみがブロックを承認することができるという中央集権的な仕組みにより運用されています。Rinkeby用のetherは https://faucet.rinkeby.io/ から入手することができます。

マイニングのCPU負荷を下げる

プライベート・ネットで送金やコントラクトのデプロイ,実行を行うためには,いずれかのノードでマイニングを行う必要があります。ラップトップで開発をしている場合には熱くなって嫌なので,CPUの負荷を下げる方法を調べました。

macOSの場合には cputhrottle というツールがあり,Homebrewでインストールすることができます。新しいターミナルを開いて以下のコマンドを実行すればCPU負荷を10%に減らすことができるのですが,sudoで実行しないといけないので正直あまりやりたくないですね…

sudo cputhrottle $PID 10

macos - Can I manually limit the %CPU used by a process? - Ask Different