やりたいこと
- 手元環境(macOS Sierra)にて、できるだけ簡単にコントラクト開発環境を整えたい。
- brewで簡単インストールしたgethは1.6.xで1.5.xから仕様が変わっているので動作および開発手順を確認したい。
Gethのインストールと環境構築
インストール
brewを使うと簡単にインストール可能です。が、最新版(2017年8月7日時点で1.6.7)がインストールされるためgeth内でsolcが利用出来ないなど、過去のgethの動作と仕様が違うため注意が必要です。
brew tap ethereum/ethereum
brew install ethereum
過去資産利用重視の人は1.5.xをソースでインストールした方がいいかもしれません。
初期化(Genesisブロック)
これはローカルでテストネットを利用するときにだけ必要な操作です。
作業フォルダと初期ファイルの作成
作業ディレクトリを作成して、そこにGenesisブロックの情報を記述したjsonファイルを作成します。
cd
mkdir eth_private
vi ./eth_private/myGenesis.json
ネットワークIDは被った場合の対応としてnonceも変更しておきます。
また、configにてhomesteadBlockを0に設定しておきます。そうしないとコントラクトがうまく動作しない場合があるようです。私はしばらくハマりました。
{
"nonce": "0x0000000000000099",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x400",
"alloc": {},
"coinbase": "0x3333333333333333333333333333333333333333",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x8000000",
"config": {
"homesteadBlock": 0
}
}
初期化
ファイルを作成したら初期化を実行します。
geth --datadir /Users/hoge/eth_private/ init /Users/hoge/eth_private/myGenesis.json
hogeのところは自分のアカウント名に置き換えます。
起動
動作(マイニング)を開始するには必ずetherbase address(coinbase)が必要となります。いろいろな方法で指定できますが、最初に作られたaccountが設定デフォルトで設定されるのでaccountを作っておきます。
コンソールの起動
geth --networkid "10" --nodiscover --datadir "/Users/hoge/eth_private" console 2>> /Users/hoge/eth_private/geth_err.log
networkidは既に予約されている0,1,2,3以外なら何でもOKです。
Accountの追加
> personal.newAccount("hoge1")
"0xcdf63ba3db701a5a5704cd5d9671b2e3076037c0"
>
> personal.newAccount("hoge2")
"0x120f06b2fa782301b8eea4ec27c146e37f075e84"
hoge1, hoge2はユーザー名ではなくパスフレーズとなります。
Coinbaseの確認
> eth.coinbase
"0xcdf63ba3db701a5a5704cd5d9671b2e3076037c0"
マイニングしてみる
> miner.start()
null
マイニングの状態を見ている
> miner.getHashrate()
0
この値が0以上ならマイニングが進んでいます。最初のカウントアップには10分くらいかかります。
マイニングを停止する
> miner.stop()
true
残高を確認してみる
> eth.getBalance(eth.accounts[0])
130000000000000000000
送金をしてみる
まずはアカウント(送信元)をunlockします。
personal.unlockAccount(eth.accounts[0])
そして送金。
eth.sendTransaction({from: "0xcdf63ba3db701a5a5704cd5d9671b2e3076037c0", to: "0x120f06b2fa782301b8eea4ec27c146e37f075e84", value: web3.toWei(3, "ether")})
送金を完了するにはマイニングをスタートさせておく必要があります。
コンソールを抜ける
> exit
ここまででコンソールを利用した操作はだいたいわかりました。
次に、コントラクトの開発や外部からの操作に必要なRPCに対応した状態でgethを起動する方法を見てみます。
マイニングやRPCをONの状態で起動(+コンソール)
geth --mine --minerthreads 2 --identity "sampleNode" --rpc --rpcport 8575 --rpcapi "web3,eth,net,personal" --rpccorsdomain "*" --rpcaddr "0.0.0.0" --datadir "/Users/hoge/eth_private" --nodiscover --networkid 10 console 2>> /Users/hoge/eth_private/geth.log
実際にコントラクトの開発を行う際は上記に--unlock 0,1 など、利用するアカウントを指定してunlockします(主導でも可)。
RPCでの接続確認
curl -X POST http://127.0.0.1:8575/ --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' -H "Content-Type: application/json"
{"jsonrpc":"2.0","id":1,"result":"Geth/sampleNode/v1.6.7-stable-ab5646c5/darwin-amd64/go1.8.3"}
無事にRPC経由で接続できるようです。接続を確認したら一旦exitしておきます。
1.7.xではapplication/jsonじゃないとダメ!と怒られます。-H "Content-Type: application/json" をつけてあげましょう。
スマートコントラクト開発
gethと連携してスマートコントラクトを開発する方法はいくつかありますが、ここでは一番オーソドックスなbrowser-solidity(Remix)を利用する方法を検証してみます。
なお、方法についてはこちらの記事が大変参考になりました。
Remixインストール
nodeのバージョンを選ぶようです。8.xはNGで、7.xにしました。また、あらかじめwgetのインストールが必要。
さらにはnpm install -D babel-preset-envを実行。
git clone https://github.com/ethereum/browser-solidity
cd browser-solidity
npm install
起動
npm start
起動したら http://localhost:8080 にアクセスします。
ローカルファイルの同期
Remixで編集しているファイルはWeb Storageに保存されるようです(多分)。
ファイル管理という視点では微妙なのでローカルフォルダとRemixを同期してローカルでファイルを管理できるようにします。
Remixdのインストール
どからでも利用できるように-gでインストールします。
npm install -g remixd
紐付け
インストールが完了したら、同期したいPathを指定して起動させます。
remixd -S /path/to/sync
remixdを起動したらRemix側(ブラウザ側)でリンクボタンをクリックします。
ダイアログが表示されるのでConnectを選択します。
リンクボタンが緑に変わり左ペインにlocalhost(指定さたPath)が表示されます。
RPC + unlockしてgethを起動
では、デプロイ先となるgeth(プライベートネット)を起動します。
基本的には先述のRPC対応での起動と同じですが、--unlock 0,1 というオプションが追加されています。
これは、geth.accounts[0]とgeth.accounts[1]で取得できるアカウントアンロックする指定です。
送金したりコントラクトを発行するためには、それを行うユーザーの「アンロック」が必要になります。
geth --mine --minerthreads 2 --identity "sampleNode" --rpc --rpcport 8575 --rpcapi "web3,eth,net,personal" --rpccorsdomain "*" --rpcaddr "0.0.0.0" --datadir "/Users/hoge/eth_private" --nodiscover --networkid 10 --unlock 0,1 console 2>> /Users/hoge/eth_private/geth.log
起動時にパスフレーズを聞いてくるので入力します。
上記の通り操作していれば、それぞれhoge1とhoge2となっています。
Remixからgethへの接続
Remixを起動した時点では、RemixはJavascript VMを実行環境として利用するようになっています。これを起動中のテストネットに変更してやります(もちろん、VMのままデバッグをしても問題ありません)。
左ペインでEnvironmentメニューからWeb3 Providerを選択します。
接続するか?と聞いてくるのでOKを選択します。
続いて表示されるダイアログで接続先のEndpoint(URL)を指定します。
なお、私の環境ではサーバ名がlocalhostではうまく接続できず、127.0.0.1と記入する必要がありました。
Accountにgethで作成したアカウントや残高が表示されれば接続できています。
HelloWorldコントラクトの実装
HelloWorldがコントラクトか?という議論はさておき、もっとも簡単なプログラムをデプロイしてみます。
下記コードをRemixのエディタ上にペーストします(私はhello.solというファイル名にしました)。
同期フォルダにファイルを追加するのはMac側のFinderでファイルを追加するしかないようです。
pragma solidity ^0.4.13;
contract HelloWorld{
//メソッド
function Hello() constant returns (string){
return "Hello World";
}
}
デプロイ
記述が終わったら右ペインにある[Create]をクリックします。
unlockを忘れるとcallback contain no result Error: authentication needed: password or unlockというエラーが出ます。
処理が開始されると、Waiting for transaction to be mined...というメッセージが表示されます。
マイニングが完了すると展開先のアドレスやその他の情報が表示されます。
動作確認
メソッドを実行するには[Hello]という青いボタンを押します。このコントラクトは静的なものなので何の変化もありませんが、動的なメソッドであれば内容が変更されていれば値が変化します。
geth(他の環境)からコントラクトを利用する
アクセスに必要な情報を取得
外部からコントラクトを利用するには
- コントラクトのアドレス
- ABI(I/Fの定義情報。まあWSDLみたいな意味合いのもの)
の2つが必要です。これは、左ペインの情報から取得できます。
Contract detailをクリックすれば詳細情報が表示されます。
アドレスは文字通りaddressの所で、ABIはInterfaceってところの情報です。
gethから利用してみる
gethのコンソールで以下のような操作をします。
- addressを登録
- abiを登録
- abiとaddressからcontract(へのアクセス)を生成
- contractの内容確認
- 内容確認(うまく生成できてないと0xとなっている)
- .call()にて実行
という流れになっています。
なお、冒頭のGenesisブロックの生成で、"homesteadBlock": 0の記述が無いとうまく動きません(getCodeで0xが返る)でした。
>
> var address = "0xe5578dde70d8ddf6ee7372fcdb604d1f7daeb513"
undefined
>
> var abi = [{"constant":true,"inputs":[],"name":"Hello","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}]
undefined
>
> var contract = eth.contract(abi).at(address)
undefined
>
> contract
{
abi: [{
constant: true,
inputs: [],
name: "Hello",
outputs: [{...}],
payable: false,
type: "function"
}],
address: "0xe5578dde70d8ddf6ee7372fcdb604d1f7daeb513",
transactionHash: null,
Hello: function(),
allEvents: function()
}
>
> eth.getCode(address)
"0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063bcdfe0d51461003e575b600080fd5b341561004957600080fd5b6100516100cd565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100925780820151818401525b602081019050610076565b50505050905090810190601f1680156100bf5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100d5610111565b6040805190810160405280600b81526020017f48656c6c6f20576f726c6400000000000000000000000000000000000000000081525090505b90565b6020604051908101604052806000815250905600a165627a7a723058200088f64d820dc93d3166441a68bf7c298c0ee25a88eda9cc16e31eb509a998cb0029"
>
>
> contract.Hello.call()
"Hello World"
>
その他
jsのコンソールアプリやWeb経由での利用はこちらをご覧ください。