ど素人がEthreumのプライベートチェーンを作ってスマコンを実装・実行する


Ethereum スマートコントラクト入門:geth のインストールから Hello World まで - Qiita
を大いに参照しています。多謝

概要

ブロックチェーンをさらっと知ってるだけの人が、
- Ethreumのプライベートチェーンを立ち上げ
- スマートコントラクトの実装、実行

を思いの外かんたんにやり遂げる記事

Ethereumプライベートネットワークで遊ぶ

環境構築

macOSにGethをいれます。

geth は Go言語で実装された Ethereumクライアントです。アカウントの作成からマイニングまで、Ethereum に関わる多くの機能を担います。

とのこと。
インストールはHomebrewでかんたん

$ brew tap ethereum/ethereum
$ brew install ethereum

データ入れるための適当なディレクトリを切って入っておく

$ mkdir private_eth
$ cd private_eth

クライアントを起動してコンソールに入る

$ geth --dev --datadir .

--dev : 開発用途のプライベートチェーン設定で起動する
--datadir . : カレントディレクトリをデータディレクトリに指定する
というオプションらしい

別のターミナルでgeth のコンソールを開く

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

なるほど、JSで書けるのか

アカウントの作成

> personal.newAccount()
Passphrase: 
Repeat passphrase: 
"0xddde6753508fc51465dd6c4c41abd2ef5092c083"

4行目がアカウントのアドレス
このアカウントの秘密鍵は ~/my_eth_chain/keystoreに入っている

> eth.accounts
["0x773dbc35e6923007a3b5a58161674079e3ec4a72", "0xddde6753508fc51465dd6c4c41abd2ef5092c083"]

最初のアドレス(eth.accounts[0])は、Coinbase (Etherbase) 即ちマイニング報酬を受取るアカウントとなる

Ether の取得(マイニング)

残高を確認

> eth.getBalance(eth.accounts[0])
1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77

あれ… なんかめっちゃあるんだけど… とおもったらwei換算らしい。
1wei = 10^18 ether

にしてもあるな。初期設定が悪かったっぽい
(きっと勝手にマイニング始まってた)

残高を送金

accounts[1]から、あらたに作ったaccounts[2]に送金する

送金ロックを解除しておく

> personal.unlockAccount(eth.accounts[0])
Passphrase: 
true

送金を実行する

> eth.sendTransaction({ from: eth.accounts[1], to: eth.accounts[2], value: 100 }"0xe218fadc726d5e3ac0b8ccc65f809fe26963e9e928390157aaa5c8e72d7d0850"
> eth.getBalance(eth.accounts[2])

0

送れてないじゃん。
というのは当たり前で、この取引を取り込んだブロックが採掘されて、このトランザクションがブロックチェーンに書き込まれる必要がある。

ついにマイニング

> miner.start()

これでOK。実際のブロックチェーンではセキリュティの観点から難易度設定とハッシュパワー(≒マイナー量)の関係により、時間が掛かるが、これはオレオレチェーンなので即刻マイニングされる。

INFO [12-29|13:29:54] 🔨 mined potential block                  number=1 hash=96b7af…3c0930

クライアント側に目を移すとこんなログが。

> eth.getBalance(eth.accounts[2])
100

残高が反映された

スマートコントラクトの記述

環境構築

ETHのスマコン記述言語Solidityをつかう。
そのコンパイラであるsolcをHomebrewでinstallする。

$ brew install solidity

solc でコンパイルされたバイトコードは、Ethereum の EVM (Etherum Virtual Machine) という仮想マシンで実行されるとのこと。Java のバイトコードが JVM で実行されるのと一緒。

スマートコントラクトを書く

以下の Solidity コードを書いて、HelloWorld.sol という名前で作成。
コンパイルされたコードをコピペして使うので、場所はどこでもよい。
スマコンってもっといかつい文法だと思ってたんだけど、思いの外普通。
pragma solidity ^0.4.18;は、ご自身が入れたバージョンに合わせるべきっぽい

HelloWorld.sol
pragma solidity ^0.4.18;

contract HelloWorld {
    string message;

    function setMessage(string _message) {
        message = _message;
    }

    function sayHello() returns (string) {
        return message;
    }
}

sayHello() メソッドを呼んだら、setMessage() で設定された文字列 (message) を返すだけの単純なコントラクトです。
contract Hoge は通常のプログラミング言語におけるクラスのようなものだが、contract の状態変数として宣言されたデータ(ここでは string message)は、ブロックチェーン上に永続的に記録されることになります。

コントラクトの骨子と、その変数がブロックチェーンに記録されるので、各種契約行為をブロックチェーン上で行える。
って理解だけどあってるのかな?

Solidity コードのコンパイル

新たなコンソールで、コンパイル

$ solc --bin --abi HelloWorld.sol

すると、Binary と Contract JSON ABI という2つの項目の結果が表示されます。
Binary が EVM のバイトコード
ABI (Application Binary Interface) は、コントラクトのインタフェース情報になります。

======= HelloWorld.sol:HelloWorld =======
Binary: 
6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ef5fb05b146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a723058206e31dc5265cbd20710726cd50237c79eef965c2947bf4d312ae1ffba794e00660029
Contract JSON ABI 
[{"constant":false,"inputs":[{"name":"_message","type":"string"}],"name":"setMessage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"sayHello","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

チェーンへのデプロイ

コンパイルされたコントラクトを、チェーンに刻む。
即ち、上で得られた Binary と ABI を使って、ブロックチェーン上に HelloWorld コントラクトのデプロイを行う。

起動中のgethのコンソールに戻って、以下のコマンドを実行。
bin の方には、コンパイル結果の Binary の先頭に 0x を付けた上で文字列にして代入します。

> bin = "0x6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ef5fb05b146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a723058206e31dc5265cbd20710726cd50237c79eef965c2947bf4d312ae1ffba794e00660029"
> abi = [{"constant":false,"inputs":[{"name":"_message","type":"string"}],"name":"setMessage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"sayHello","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

ABIインタフェースからHelloWorldのコントラクトオブジェクトを作成します。

> contract = eth.contract(abi)

そして、コントラクトをチェーン上に登録するトランザクションを送信します。
コンパイルされたコントラクトと、マイナーが受け取る手数料であるGasを指定しておく。
ちなみにGas手数料が馬鹿にならないという問題があるそうで 。
GAS使用量問題 · ブロックチェーンサービス(Ethereum)利用ガイド

> HelloWorld = contract.new({ from: eth.accounts[0], data: bin, gas: 1000000 })

マイニングが成功されればチェーン上でHelloWorldにアドレスが割り振られる。

> HelloWorld
{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "setMessage",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [],
      name: "sayHello",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: "0x6bac00860803fef8b67760761edf5fca98b2a44c",
  transactionHash: "0x1f564da9cad3f66a08b46273f6af35c5c79a0d3d17d701ac1e13680bbd194ac1",
  allEvents: function(),
  sayHello: function(),
  setMessage: function()
}

スマートコントラクトの呼び出し

スマートコントラクトのメソッドの実行方法には下記の2種類がある
- sendTransaction()
- call()

※ 以下、本章は引用元からまるっと引用

sendTransaction()

sendTransaction() は、ブロックチェーン上に値を書き込むメソッドを呼び出すときに使用します。今回の例で言えば、setMessage() メソッドは、状態変数 message の値を更新するので sendTransaction を使う必要があります。
sendTransaction() は、マイナーに採掘される必要があり、そのための費用(手数料)がかかります。手数料は Gas という単位で支払われます。

call()

call() は、ブロックチェーンの状態を変化させない “Read Only” のメソッドの場合に使用します。今回の例で言えば、sayHello() メソッドは状態変数の更新を伴わないので、call() で呼び出せます。
マイナーによる新たなブロック生成は必要ないので、call() には手数料 (Gas) は発生しません。

Hello, world!する

では実際にデプロイしたコントラクトを呼び出してみましょう。呼び出し方は、

コントラクトオブジェクト.メソッド名.call(引数)
コントラクトオブジェクト.メソッド名.sendTransaction(引数, { from: 送信元アドレス })

最初に sayHello() を実行すると、まだ message の値はセットされていないので空文字が返ってきます。

> HelloWorld.sayHello.call()

では、"Hello, world!" という文字列を message に代入するために、以下の sendTransaction を実行します。

> HelloWorld.setMessage.sendTransaction("Hello, world!", { from: eth.accounts[0] })
"0x10daf47f210ad8db5e75058188eee84160ce99173ab40568578a2b0cd96be7c4"

トランザクションIDが表示され、マイニングしていればすぐにブロックチェーンに反映されます。

そして、再度 HelloWorld.sayHello.call() を実行することで、めでたく "Hello, world!" と表示されました。

> HelloWorld.sayHello.call()
"Hello, world!"

最後に

スマートコントラクトと聞くと身構えてしまいますが、基本の仕組みや文法はかんたんでした。
ただ、堅牢で低コストなコントラクトの記載には職人技があるはずなので、いずれそこら辺の深みにもハマってみたいなあと思った今日このごろでした。
このくらいは積読しとこうかなあ。