ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

Ethereumの勉強: Hello, World! Contract by Solidity (macOS)

https://www.ethereum.org/images/home-title.png

Hello, world!してみようと思います。

以下のリンクを参考にしました。

動機

geth が v1.6.0 から solidity のコンパイルをサポートしなくなったけれど、あまりそれについて触れているドキュメントがなかったので色々苦労したため、一応この時点ではこれでいけたよ!ということを残しておきます;

  • 環境: macOS High Sierra 10.13
  • 書いた日付: 2018/02/13
  • geth: 1.7.3-stable
  • solc(solidity): 0.4.19+commit.c4cbbb05.Darwin.appleclang

用語

  • geth: go 言語で書かれた Ethereum クライアント
  • コントラクト: Ethereum 上で動作するコード
  • solidity: Ethereum 上でコントラクトを記述するための言語またはコンパイラ (solidity で書いたプログラムを実行環境である Ethereum Virtual Machine で解釈できるようにコンパイルする)

Ethereum について

ここで言う Ethereum はブロックチェーン上でチューリング完全なプログラムを動かせるプラットフォームのことらしいです。一般的に知られている仮想通貨の方は ether と読んで区別しています。ブロックチェーン上にコードが追加できて、そこに向けて GAS (コードが使う資源に応じた実行に必要な ether )や引数などを投げるとコードが実行される感じです。

プログラミングする観点から言うと、コードの書き方によって使用される GAS が異なる(コードを実行するための GAS を少なくする最適化スキルが重宝される)ことが重要な気がします。

Install geth

$ brew tap ethereum/ethereum
$ brew install geth
$ geth version

無事インストールできていることを確認できました。

Private チェーンの作成

新しく実験用のディレクトリを作って、そこで geth を起動します。 --dev オプションによって、本来の Ethereum のチェーンではなく自分専用の開発用のチェーンで起動します。

$ mkdir path/to/somewhere
$ cd path/to/somewhere
$ geth --dev --datadir .

INFO がつらつらと出てきたら起動成功のようです。 WARN が出るかもしれないですが Block sealing failed などは --dev のせいなので気にしなくていいようです。

注意: 今回はコントラクトを作成して実行するだけが目的なのでひとまず気にしなくていいですが、--dev で起動した場合、マイニングが行われるのはトランザクションが生成された時のみのようです # 。初めから大量の ether を持ったアカウントが一人登録されており、このアカウントが miner (マイニングを行うアカウント)の役割をふられています。パスワードは空です。

別コンソールから (JavaScript) 対話環境にログインします。基本的にこのインタフェースで作業していくようです。

$ ls
… geth.ipc …
$ geth attach geth.ipc

別コンソールで先ほど実行した geth --dev --datadir . によって geth.ipc と言うファイルが作成されていることを確認できました。geth attach geth.ipc を実行することで対話環境に入れます。

> eth.accounts
["0x75eece28f8ce7b99af2af2324dbb17f5c1aab56e"]

とりあえず今のところやることはないですが、ethminerweb3などをいじって遊んでみるといいと思います。

Install solidity (solc コマンド)

コントラクトを書くために solidity をインストールします。

$ brew install solidity
$ brew link solidity
$ solc --version

結構時間がかかりました。適当なコマンドを打ってみて、ちゃんとインストールされていることが確認できました。

コントラクトの作成

Hello コントラクトを作成していきます。Hello, world! までの流れとしては以下の 3steps です;

  1. ソースをコンパイルして abi (Application Binary Interface), bin (Binary)を取得
  2. 対話環境で eth.contractabi を食わせてコントラクトの雛形を作成、コントラクトの雛形にコンストラクタ引数として bin と 送信者 と gas を与えてインスタンス化する
  3. インスタンスに対してメソッドの呼び出し(今回はcall)を行う

step 1

solidity のコードサンプルがいくつかネットに落ちているのでそれを参考に Hello, world! プログラムを書きました。拡張子は sol が一般的なようです。ファイル名の命名規則などは後々でいいかのとか思いました。

hello.sol

pragma solidity ^0.4.0;

contract Hello {
  function say() public pure returns (string) {
    return 'Hello, world!';
  }
}

コンパイルします。使いやすく出力フォーマット変えたりできないのかなとか思いますが、ターミナルで加工します。「コンパイル後のコードを json の形で吐き出した後、compilerOutput 変数に代入する」と言う js ファイルを作成します。

$ echo "var compilerOutput = `solc --optimize --combined-json abi,bin hello.sol`" > hello.js
$ less hello.js
var compilerOutput = {"contracts":{"hello.sol:Hello":{"abi":"[{\"constant\":true,\"inputs\":[],\"name\":\"say\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}]","bin":"6060604052341561000f57600080fd5b61014e8061001e6000396000f3006060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663954ab4b28114610045575b600080fd5b341561005057600080fd5b6100586100cf565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561009457808201518382015260200161007c565b50505050905090810190601f1680156100c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100d7610110565b60408051908101604052600d81527f48656c6c6f2c20776f726c6421000000000000000000000000000000000000006020820152905090565b602060405190810160405260008152905600a165627a7a723058206803a6ff4f3b05c9ebd7f5f75edb032cfd89ce2b8177d8653269f91a932178720029"}},"version":"0.4.19+commit.c4cbbb05.Darwin.appleclang"}

solc のオプション abi, bin は 出力される json に含める内容です。

対話環境でコードをロードします。ここまでくればコンパイルは OK と見ていいのだろうか?

> loadScript('./hello.js')
true
> compilerOutput
{
  contracts: {
    hello.sol:Hello: {
      abi: "[{\"constant\":true,\"inputs\":[],\"name\":\"say\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}]",
      bin: "6060604052341561000f57600080fd5b61014e8061001e6000396000f3006060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663954ab4b28114610045575b600080fd5b341561005057600080fd5b6100586100cf565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561009457808201518382015260200161007c565b50505050905090810190601f1680156100c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100d7610110565b60408051908101604052600d81527f48656c6c6f2c20776f726c6421000000000000000000000000000000000000006020820152905090565b602060405190810160405260008152905600a165627a7a723058206803a6ff4f3b05c9ebd7f5f75edb032cfd89ce2b8177d8653269f91a932178720029"
    }
  },
  version: "0.4.19+commit.c4cbbb05.Darwin.appleclang"
}

abi, bin を含んだ感じのものが表示されました。

step 2

コントラクトを作成して実行してみます。abi は JSON.parse, bin は頭に '0x' がついてないので代入時につけてあげます。

> var abi = JSON.parse(compilerOutput.contracts['hello.sol:Hello'].abi)
> var bin = '0x' + compilerOutput.contracts['hello.sol:Hello'].bin
> var contract = eth.contract(abi)
> Hello = contract.new({ from: eth.coinbase, data: bin, gas: 100000 })
> Hello
{
  abi: [{
      constant: true,
      inputs: [],
      name: "say",
      outputs: [{...}],
      payable: false,
      stateMutability: "pure",
      type: "function"
  }],
  address: "0x34f2deef0f113982608a8a943c85dc74293daa7a",
  transactionHash: "0xc81c82a8a3825a5cfef35122f06036ad220601a53f38e029a1a00ff132d3187b",
  allEvents: function(),
  say: function()
}

addressが振られているのでOKみたいです。

注意: 実は何回か試行錯誤していて、アカウントのパスワード聞かれたり、Hello インスタンスに address が振られないという現象がありました。 eth.coinebase のアカウントはパスワードが空で、 address 振られないときは miner.stop() & miner.start() とマイニングを再起動させたりしたら動作するようになりました。(なぜだろう…)

step 3

Hello.say に対して call を実行します。

> Hello.say.call()
"Hello, world!"

ようやく出ました!

まとめ

感想

  • geth は一番使われているという割にはドキュメント散在していたり、動作不安定だったりしていて、そのあたりの仕事はあるのではないかと思いました。
  • geth --dev だと miner がよくわからないルールで動いているようなので、genesis.json を使ってプライベートネットワークを立てた方がいいかもしれないです。
  • solidity に関しては solc でコンパイルするより Remix という IDE 使うと良さそうな感じらしいです。