Geth(Go Ethereum)を使って、イーサリアムのプライベートネットを構築し送金までやってみた

f:id:enomotodev:20180218181951p:plain

最近毎日のようにテレビや新聞に取り上げられている仮想通貨ですが、ちょっと試してみたいなぁと思っても実際に投資するのは怖いし・・・って感じだったので、Geth(Go Ethereum)を使って、イーサリアムのプライベートネットを構築し、送金を行ってみました。

環境構築

あまりローカル環境を汚したくなかったので、Docker で環境を構築しました

まずは、最新の golang のイメージでコンテナを立ち上げ、コンテナの中に入ります。

$ docker run -it --name geth golang:1.9 /bin/bash

ここからは Docker のコンテナ内での作業となります。

Geth(Go Ethereum)を git clone し、ソースからビルドします。

ビルドしたら、Geth のバイナリファイルが置かれる場所にパスを通しておきます。

# git clone -b release/1.8 https://github.com/ethereum/go-ethereum ~/go-ethereum
# cd ~/go-ethereum/
# make geth
# export PATH=/root/go-ethereum/build/bin:$PATH

パスを通したら、ちゃんとインストールできているか、バージョンを表示してみます。

# geth version
Geth
Version: 1.8.1-unstable
Git Commit: 9fd76e33af367752160ab0e33d1097e1e9aff6e4
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.10
Operating System: linux
GOPATH=/go
GOROOT=/usr/local/go

バージョンが表示され、Geth が問題なくインストールされたのが確認できました!

Ethereum のプライベートネットを構築する

genesis.json の作成

Geth のインストールが完了したので、いよいよプライベートネットを構築したいと思います。

まずは作業用のディレクトリを作成します。

# mkdir ~/geth

次に作業用のディレクトリ直下に genesis.json を作成します。

genesis.jsonGenesis ブロックと呼ばれる、最初のブロックを作成するためのファイルです。

# cat << EOS > ~/geth/genesis.json
{
  "config": {
    "chainId": 12345,
    "homesteadBlock": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "timestamp": "0x0",
  "gasLimit": "0x8000000",
  "difficulty": "0x400",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x3333333333333333333333333333333333333333",
  "alloc": {}
}
EOS

プライベートネットの初期化処理

genesis.json を作成したら、そのファイルを元に、プライベートネットの初期化を行います。

このとき、データ格納用のディレクトリを指定する必要があるので、作業用に作成したディレクトリを指定しておきます。

# geth --datadir ~/geth/ init ~/geth/genesis.json
INFO [02-18|04:48:44] Maximum peer count                       ETH=25 LES=0 total=25
INFO [02-18|04:48:44] Allocated cache and file handles         database=/root/geth/geth/chaindata cache=16 handles=16
INFO [02-18|04:48:44] Writing custom genesis block
INFO [02-18|04:48:44] Persisted trie from memory database      nodes=0 size=0.00B time=8.896µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [02-18|04:48:44] Successfully wrote genesis state         database=chaindata                 hash=7c0bc2…bb4ff6
INFO [02-18|04:48:44] Allocated cache and file handles         database=/root/geth/geth/lightchaindata cache=16 handles=16
INFO [02-18|04:48:44] Writing custom genesis block
INFO [02-18|04:48:44] Persisted trie from memory database      nodes=0 size=0.00B time=11.494µs gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
INFO [02-18|04:48:44] Successfully wrote genesis state         database=lightchaindata                 hash=7c0bc2…bb4ff6

Successfully wrote genesis state という出力が表示されたら初期化は完了です。

プライベートネットの起動

プライベートネットの初期化が完了したので、プライベートネットを起動し、コンソールを立ち上げます。

# geth --networkid 10 --datadir ~/geth/ console 2>> ~/geth/error.log
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.1-unstable-9fd76e33/linux-amd64/go1.10
 modules: admin: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

>

これでプライベートネットが起動し、コンソール上からコマンドを実行できる状態になりました。

Genesis ブロックの確認

まずはプライベートネットの初期化処理の時に指定した genesis.json によって作成された Genesis ブロックを確認してみます。

ブロックの確認には eth.getBlock() コマンドを使用します。

第一引数にはブロック番号を指定し、Genesis ブロックは最初のブロックなので 0 を指定することによって、Genesis ブロックを確認することができます。

> eth.getBlock(0)
{
  difficulty: 1024,
  extraData: "0x",
  gasLimit: 134217728,
  gasUsed: 0,
  hash: "0x7c0bc26e9b9b9135eb6dd41b9f0bc8edac47998cd2e30720b3f1497b12bb4ff6",
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  miner: "0x3333333333333333333333333333333333333333",
  mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  nonce: "0x0000000000000000",
  number: 0,
  parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 507,
  stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  timestamp: 0,
  totalDifficulty: 1024,
  transactions: [],
  transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  uncles: []
}

まだマイニングを開始していないので新しいブロックは作られておらず、引数に 1 を指定すると null が返ってきます。

> eth.getBlock(1)
null

アカウントの作成

まだプライベートネットを起動したばかりの状態で、アカウントが存在しないので、まずはアカウントを作成してみましょう。

personal.newAccount() コマンドの第一引数にパスワードを文字列で指定することによって、新しくアカウントが作成されます。

> personal.newAccount("password")
"0x27963c263a5c42f84b169a1aff9d4411c2d666fa"

これでアカウントが1つ作成されました。

この後に送金を試していくので、これをあと4回繰り返して、合計5つのアカウントを作成しておきましょう。

アカウントの確認

作成されたアカウントを eth.accounts コマンドで確認してみます。

> eth.accounts
["0x27963c263a5c42f84b169a1aff9d4411c2d666fa", "0x81bfeed39ebdd70b99a82e34a5d78a4388221ad2", "0xd6e5d2876988a5d3570254dd0c6e6aac87784df6", "0xaacf137f7d307ae43ca183693f51311166f22802", "0x2c102c2e294d131a698c37e54183b9c0ccaaee03"]

合計5つのアカウントが作成され、配列形式で表示されているのが確認できるかと思います。

配列形式なので、インデックスを指定することによって、それぞれのアカウントにアクセスすることもできます。

> eth.accounts[0]
"0x27963c263a5c42f84b169a1aff9d4411c2d666fa"

コインベースアカウントの確認・変更

コインベースアカウント(マイニングをした時に報酬が支払われるアカウント)はデフォルトでは accounts[0] に設定されています。

> eth.coinbase
"0x27963c263a5c42f84b169a1aff9d4411c2d666fa"

ここではコインベースアカウントを accounts[0] から accounts[1] に変更します。

> miner.setEtherbase(eth.accounts[1])
true

> eth.coinbase
"0x81bfeed39ebdd70b99a82e34a5d78a4388221ad2"

マイニングの開始

コインベースアカウントを accounts[1] に変更したので、マイニンングを開始すると accounts[1] に報酬が支払われていきます。

> miner.start(1)
null

これでマイニングが開始されました。

今マイニングが開始されているかどうかは eth.mining コマンドで確認できます。

> eth.mining
true

少し時間を置いて、ある程度マイニングが進んだらマイニングの停止してみましょう。

> miner.stop()
true

マイニングを停止した状態の場合、eth.mining コマンドを実行すると false が返ってきます。

> eth.mining
false

コインベースアカウントの残高確認

ある程度マイニングが進み、コインベースアカウントは報酬をもらっているはずなので、残高を確認してみます。

> eth.getBalance(eth.accounts[1])
210000000000000000000

残高が表示されましたが、桁数が多すぎてかなり見づらいです。

これは残高の単位が wei で表示されているからなので、単位を wei から ether に変換して表示してみます。

> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
210

コインベースアカウントの残高が 210 ether あるのが確認できました。

ロックの解除

報酬を得たので、他のアカウントに送金をしてみたいところなのですが、そのまま送金をしようとするとエラーになってしまいます。

これは誤操作によって間違った送金を防ぐためのようで、送金する際にはアカウントを作成したときのパスワードでロックを解除してあげなくてはいけません。

> personal.unlockAccount(eth.accounts[1])
Unlock account 0x81bfeed39ebdd70b99a82e34a5d78a4388221ad2
Passphrase:
true

送金

アカウントのロックが解除できたので、accounts[1] から eth.accounts[0] への送金を試してみたいと思います。

まずは送金先の eth.accounts[0] の残高が 0 であることを確認します。

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
0

次に、実際に accounts[1] から accounts[0] に 10 ether 送金してみます。

> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(10, "ether")})
"0x72baef0e9c1f12ae7f625fe4838bdd0e9cca01010f7311a304ea994efb883731"

送金する単位は wei なので 10 ether を wei に変換しています。

ロックをかける

送金が終了したら、アカウントのロックを掛け直しておきます。

> personal.lockAccount(eth.accounts[1])
true

トランザクションを確認

eth.sendTransaction() コメンドを実行して送金した際に返り値として、トランザクションハッシュ値が返ってくるので、トランザクションを確認してみます。

> eth.getTransaction("0x72baef0e9c1f12ae7f625fe4838bdd0e9cca01010f7311a304ea994efb883731")
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0x81bfeed39ebdd70b99a82e34a5d78a4388221ad2",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x72baef0e9c1f12ae7f625fe4838bdd0e9cca01010f7311a304ea994efb883731",
  input: "0x",
  nonce: 0,
  r: "0xb8ea73f8b8057b75ace72b26ee4356d2d405d610e30df9d8975d921bdd49c37f",
  s: "0x1c66eeb82edd12aa2b494b8370e1d0b97d9d821e3a549f1add26cbee9f951a54",
  to: "0x27963c263a5c42f84b169a1aff9d4411c2d666fa",
  transactionIndex: 0,
  v: "0x6095",
  value: 10000000000000000000
}

マイニングを停止しているので、このトランザクションはまだブロックに取り込まれておらず、送金はまだ完了していないです。

マイニングを再開して、トランザクションがブロックに取り込まれると、blockNumber の値が null から、取り込まれたブロックの番号に変更されます。

トランザクションをブロックに取り込む

それでは、マイニングを再開して、先ほどのトランザクションをブロックに取り込んでみましょう。

> miner.start(1)

少し時間を置いてからトランザクションを確認してみます。

> eth.getTransaction("0x72baef0e9c1f12ae7f625fe4838bdd0e9cca01010f7311a304ea994efb883731")
{
  blockHash: "0xea90ca8cd6934ee9fd950547c8008b93a0894690e7e2d30363ae9186614c3fd3",
  blockNumber: 53,
  from: "0x81bfeed39ebdd70b99a82e34a5d78a4388221ad2",
  gas: 90000,
  gasPrice: 18000000000,
  hash: "0x72baef0e9c1f12ae7f625fe4838bdd0e9cca01010f7311a304ea994efb883731",
  input: "0x",
  nonce: 0,
  r: "0xb8ea73f8b8057b75ace72b26ee4356d2d405d610e30df9d8975d921bdd49c37f",
  s: "0x1c66eeb82edd12aa2b494b8370e1d0b97d9d821e3a549f1add26cbee9f951a54",
  to: "0x27963c263a5c42f84b169a1aff9d4411c2d666fa",
  transactionIndex: 0,
  v: "0x6095",
  value: 10000000000000000000
}

blockNumbernull から 53 に変更されました!

トランザクションがブロックに取り込まれ、送金が完了したので、accounts[0] の残高を見ると、10 ether になっているのが確認できます。

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
10

実は今まで残高を確認してきた eth.getBalance() コマンドの第二引数に integer を渡すと、そのブロックの時点での残高を見ることもできます。

> web3.fromWei(eth.getBalance(eth.accounts[0], 52), "ether")
0
> web3.fromWei(eth.getBalance(eth.accounts[0], 53), "ether")
10

コンソールを終了する

コンソールの終了は exit コマンドで出来ます。

> exit

まとめ

Docker のコンテナの中にイーサリアムのプライベートネットを構築し、実際に送金まで行ってみました。

環境の構築自体も Docker を使わなくても、インストーラーが準備されていたり、Mac の方は Homebrew でも環境を構築することができるようです。

ちょっと試してみたいけど、実際に投資するのはイヤだなぁと思っている方は是非プライベートネットを構築し色々と試してみてください。

追記

  • 2018/3/1:Docker を立ち上げる時と、git clone する時のバージョンを固定しました