shivaseの日記

SIer→Web→教師→SIer→ベンチャー(SRE)と異色な人生を歩んでいるエンジニアの日記

ブロックチェーンを体験する 第3回 Hello, Worldしてみる

ブロックチェーンを体験する 第2回 Ether(通貨)の送金の続きです。

スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかる

スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかる

今回は、Ethereumの大きな特徴であるコントラクトを実装して遊んでみます。 本に掲載されている足し算のコントラクトでもいいですが、折角なので違うのをやってみようと探していると、Create a Hello World Contract in ethereumという「Hello, World」チュートリアルがあったのでこちらを参考にやっていきます。

そもそもコントラクトとは何か

定義は以下の通りでチュートリアルに書いてあるのですが、

Smart contracts are account holding objects on the ethereum blockchain. They contain code functions and can interact with other contracts, make decisions, store data, and send ether to others. Contracts are defined by their creators, but their execution, and by extension the services they offer, is provided by the ethereum network itself. They will exist and be executable as long as the whole network exists, and will only disappear if they were programmed to self destruct. www.ethereum.org

Ethereumブロックチェーン上に存在するアカウントの一つで、

  • 他のコントラクトを呼び出す(作用させる)
  • 判断する
  • データを保存する
  • Etherを送る

機能を持っており、作成するとネットワーク全体に存在し実行できるプログラムになります。ビットコインでは通貨の移動しかできない単純な機能だけだったのが、このコントラクトにより非常に汎用的な処理ができるようになっています。

例えばどんなことできんの?って話ですが、そこらはこの本あたりを読むと良くわかる今回は割愛します。

ブロックチェーン 仕組みと理論  サンプルで学ぶFinTechのコア技術

ブロックチェーン 仕組みと理論 サンプルで学ぶFinTechのコア技術

  • 作者: 赤羽喜治編著,愛敬真生編著
  • 出版社/メーカー: リックテレコム
  • 発売日: 2016/10/15
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

目標

コントラクトの実装の流れを理解し、HelloWorldを画面に表示できる

作業の流れ

  • Hello, Worldコントラクトを作成しコンパイルする
  • ブロックチェーンネットワーク上に配布
  • コントラクトを実行する
  • 作ったコントラクトを廃棄する

前提

  • ユーザ作成が完了していること
  • 作成したユーザに実行に必要なetherの残高があること(適当にminer.start()してればおk)

Hello, Worldコントラクトを作成しコンパイルする

コントラクトの作業の流れは以下のような流れになっています。 非常にややこしいので自力でやるのはしんどく、本来はwebでよしなにやってくれるツール を使って実装することになる部分だと思われます。

  1. ソースコードを作成するsolidityコンパイラでコンパイルする(compiled)
  2. compiledされたものからcontractを作成する(contract)
  3. contractを元にインスタンスを作成する(instance)
  4. instanceをネットワークに配布する(miner.start)

f:id:kegamin:20170220132747p:plain

どこの作業をやっているのか理解しながらやらないとパンクしますね。。 本では全く図がなく、かつバージョンの差異でコマンドが違ってたりする部分で非常に苦労しました(´・ω:;.:...

ソースコード作成

以下の通りのプログラムを作成し、適当なファイル名(greeter.js)で保存して下さい。チュートリアルでは継承を使って少し複雑になっていますが、そこまで要求しても仕方が無いのでシンプルにしています。 本の内容をやる場合は同じようにaddtion.jsとして作成すればOKです。

contract greeter {
  address owner; // コントラクト
  string greeting; // 出力メッセージ

  //コンストラクタ
  function greeter(string _greeting) public {
    owner = msg.sender;
    greeting = _greeting;
  }

  // 実際に呼ぶメソッド
  function greet() constant returns (string) {
    return greeting;
  }

  // ブロックチェーンネットワークから削除するメソッド
  function kill() {
    if (msg.sender == owner) selfdestruct(owner);
  }
}

実際にこのプログラムをコピペして流すと、改行が邪魔をしてうまく実行してくれません。 なのでRemove Line Breaks Online Tool を使って今回は以下のように改行を消した部分をコピペしましょう。

f:id:kegamin:20170220123345p:plain

準備できたらさっそくコピーして実行させます。

> var source = "contract greeter { address owner; string greeting; function greeter(string _greeting) public { owner = msg.sender; greeting = _greeting; } function greet() constant returns (string) { return greeting; } function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
undefined

// うまくできていたら、sourceで内容が表示されるはずです
> source
"contract greeter { address owner; string greeting; function greeter(string _greeting) public { owner = msg.sender; greeting = _greeting; } function greet() constant returns (string) { return greeting; } function kill() { if (msg.sender == owner) selfdestruct(owner); } }"

コンパイル〜コントラクト作成

作成したコントラクトをコンパイルしていきますが、こちらも同じようにソースコードを別で作成し(compile.js)、改行を消して実行します。 ここがチュートリアルや本ではそのままでは動かない部分です。 compiledの内部jsonのフォーマットが変わっているのでcompiled.greeterとかcompiled.additionとか でデータを取ってこれなくなっているので注意して下さい。

var contract = web3.eth.contract(compiled["<stdin>:greeter"].info.abiDefinition);

var instance = contract.new("Hello World!!",
  {from: web3.eth.accounts[0],
    data: compiled["<stdin>:greeter"].code,
    gas: 300000}
  );

上のコンパイルコードが準備できたら、同じように改行を削除して実行します。

> var instance = web3.eth.contract(compiled["<stdin>:greeter"].info.abiDefinition); var instance = contract.new("Hello World!!", {from: web3.eth.accounts[0], data: compiled["<stdin>:greeter"].code, gas: 300000} );
Error: authentication needed: password or unlock
    at web3.js:3104:20
    at web3.js:6191:15
    at web3.js:5004:36
    at web3.js:2985:24
    at <anonymous>:1:98

実行時にeth.account[0]を使って配布しようとしています。 しかし利用料としてgasを消費するため、これも勝手に実行できないように権限エラーになりました。

> personal.unlockAccount(eth.accounts[0], "pass1")
true

// これで問題無く実行できました
> var instance = web3.eth.contract(compiled["<stdin>:greeter"].info.abiDefinition); var instance = contract.new("Hello World!!", {from: web3.eth.accounts[0], data: compiled["<stdin>:greeter"].code, gas: 300000} );
I0220 13:47:28.747527 internal/ethapi/api.go:1141] Tx(0x69c32b61cac18ab5895e990fe42d09da4f407a71c1c5eeb54c192a457f4843e0) created: 0x19300e99f2ebba20a53c7f715ed82797b725da9e
undefined

// ただしaddress部分はまだundefinedのまま
// 使えるようにするために採掘を最後にする必要があります
> instance
{
  abi: [{
      constant: false,
      inputs: [],
      name: "kill",
      outputs: [],
      payable: false,
      type: "function"
  }, {
  type: "constructor"
}],
address: undefined,
transactionHash: null
}

> miner.start()
> (中略)
> miner.stop()

// addressに値が入れば配布完了です
> instance
{
  abi: [{
      constant: false,
      inputs: [],
  }],
  address: "0x1c7b8887c6808860aceefaf917118d532d9fbad7",
  transactionHash: "0xe50c6b4eb80fcf20d483032bbe9d0fc8870b3d18d2964cb0dcb0ba8890670afb",
  allEvents: function(),
  greet: function(),
  kill: function()
   }

// 別のコマンドでアドレスを見ることもできます
> eth.getCode(instance.address)

"0x606060405263ffffffff60e060020a60003504166341c0e1b5811461002c578063cfae32171461003e575bfe5b341561003457fe5b61003c6100ce565b005b341561004657fe5b61004e610110565b604080516020808252835181830152835191928392908301918501908083838215610094575b80518252602083111561009457601f199092019160209182019101610074565b505050905090810190601f1680156100c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000543373ffffffffffffffffffffffffffffffffffffffff9081169116141561010d5760005473ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b6101186101a8565b60018054604080516020600284861615610100026000190190941693909304601f8101849004840282018401909252818152929183018282801561019d5780601f106101725761010080835404028352916020019161019d565b820191906000526020600020905b81548152906001019060200180831161018057829003601f168201915b505050505090505b90565b604080516020810190915260008152905600a165627a7a7230582047d26d1f8d0150c8005f573a1fa9367e77fb45737312a83d6ce9e67ac85ea39e0029"
>

コントラクトを実行する

最後にminer.start()を実施して配布して完了です

> instance.greet()
"Hello World!!"

これだけやってHello World...

作ったコントラクトを廃棄する

最後にkillというメソッドを作っていたので、それを使ってコンストラクトを削除して終わりましょう

> instance.kill.sendTransaction({from:eth.accounts[0]})
I0220 15:02:15.290844 internal/ethapi/api.go:1143] Tx(0x1131922a21482556ae4b6401b76a58177fdca600253efec6940e27f36fabeebf) to: 0x1c7b8887c6808860aceefaf917118d532d9fbad7
"0x1131922a21482556ae4b6401b76a58177fdca600253efec6940e27f36fabeebf"

// もちろんこのままだとネットワーク上には残ったままなので、採掘する
> miner.start()
> eth.getCode(instance.address)
"0x"
> instance.greet()
Error: new BigNumber() not a base 16 number:
... 

これで作ったコンストラクトは消せました

最後に

ここまでやってやっと「Hello world」です。 正直まじめにコマンドラインからやるのは勉強にはいいですが、やはりフレームワーク を利用してやっていきたいですね。次回からは、その辺のフレームワークとかを調べながら やってみたいと思います。

エラー

実行に必要な税金が足りないよ!

Error: Insufficient funds for gas * price + value
    at web3.js:3104:20
    at web3.js:6191:15
    at web3.js:5004:36
    at web3.js:2985:24
    at <anonymous>:1:16

かわいそうなユーザーにminer.start()を実行してあげてお金を増やしてあげましょう。

何かadditionAbiでエラーになるんだけど

> var additionAbi = additionCompiled.Addition.info.abiDefinition
TypeError: Cannot access member 'info' of undefined
    at <anonymous>:1:19

スマートコントラクト本格入門―FinTechとブロックチェーンが作り出す近未来がわかるの通りにやると出るエラーです。additionAbiの内部のJSON形式が変わっているため変更が必要です。

var additionAbi = additionCompiled["<stdin>Addition:"].info.abiDefinition

今後もまだまだ変わるかもしれないので、additionCompiledだけを実行して内部を見るようにした方がよいですね。

Enjoy!Ethereum!

Enjoy!!

【スポンサーリンク】