こちらは、Ethereum上(テストネットワーク)で気軽にサクッとスマートコントラクトを試すための記事になります。
前提知識としては、ブロックチェーン、Ethereum、スマートコントラクトになります。
この記事では、ブロックチェーンやスマートコントラクトが既存のシステムよりどこが優れていて、何を解決するか等は説明しません。あくまで、動かすことに重点を置いています。
開発環境構築
FROM ubuntu:16.04
ENV ETH_ROOT /eth_data
RUN apt-get update && apt-get install -y git && \
git clone -b release/1.3.6 https://github.com/ethereum/go-ethereum.git && \
apt-get install -y build-essential libgmp3-dev golang && \
make -C go-ethereum geth && \
cp go-ethereum/build/bin/geth /usr/bin/ && \
apt-get install -y software-properties-common python-software-properties && \
add-apt-repository ppa:ethereum/ethereum && \
apt-get update && apt-get install -y solc
EXPOSE 8545
RUN mkdir $ETH_ROOT
WORKDIR $ETH_ROOT
ENTRYPOINT /bin/bash
サクッと試せるように、Dockerで環境構築します。
Dockerfileの説明
- gethのinstall
- solcのinstall
端的にやっていることは、上記の2つのみです。
gethは、Ethereumをコンソールからコマンド操作で操作するためのGolangで実装されたEthereumクライアントです。
このgethを使って、マイニングやスマートコントラクトの実行をしていきます。
solcは、Ethereum上で動作するスマートコントラクトを記述する専用言語Solidityのコンパイラです。
記事作成時のgethのバージョンは1.3.6
、solcのバージョンは0.4.19
となっています。
では、早速イメージをビルドして、コンテナを起動しましょう。
$ docker build . -t ethereum:1.0.0
$ docker run -it ethereum:1.0.0
アカウント作成
コンテナを起動すると、シェルログインした状態になっています。
テストネットワーク上にアカウントを作成するためにgethを起動します。
$ geth --networkid "123" --rpc --datadir "eth_data" --olympic console
networkid: 作成するネットワークの識別番号
rpc: 後ほどRPCで別コンソールからネットワークにアタッチするために必要なオプション
datadir: ブロックチェーン情報を格納するディレクトリ
olympic: テストネットワークを作成するオプション
console: コンソールの起動
実行すると、テストネットワークが作成され、gethのコンソールが立ち上がります。
立ち上がったら、アカウントを作成していきます。
> personal.newAccount("test1")
0xf5e5c5046e94fea557155efb30b38845e24ed63e
上記で、アカウント作成出来ます。newAccountの引数は、送金する時などに使用するパスフレーズになります。
下記のコマンドで、アドレスが格納されていれば作成完了です。
> eth.accounts
["0xf5e5c5046e94fea557155efb30b38845e24ed63e"]
後ほどのトランザクション発行時に、アカウントがロックされていて発行できない事象が発生するので、今のうちにアカウントをアンロックしておきます。
> personal.unlockAccount(eth.accounts[0])
マイニング
では、アカウントを作成したので、マイニングします。
マイニングは以下のコマンドで実行可能です。
> miner.start()
終了は以下です。
> miner.stop()
これで、スマートコントラクトを実行するための環境が整いました。
スマートコントラクト実行
任意の場所に、コントラクトファイルを作成しましょう。
今回は、簡単なカウンターを作成します。
pragma solidity ^0.4.19;
contract Counter {
uint32 private count = 0;
function incrementCounter() public {
count += 1;
}
function decrementCounter() public {
if(count <= 0) return;
count -= 1;
}
function resetCounter() public {
count = 0;
}
function getCount() public constant returns (uint32) {
return count;
}
}
見て分かるように、カウンターを上げ下げしたり、リセットしたり、現在のカウントを取得するコントラクトです。
作成したら、コンパイルします。
$ solc --bin --abi counter.sol
======= counter.sol:Counter =======
Binary:
606060405260008060006101000a81548163ffffffff021916908363ffffffff160217905550341561003057600080fd5b6101da8061003f6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635b34b96614610067578063a87d942c1461007c578063dbdf7fce146100b1578063f5c5ad83146100c6575b600080fd5b341561007257600080fd5b61007a6100db565b005b341561008757600080fd5b61008f610113565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b34156100bc57600080fd5b6100c461012c565b005b34156100d157600080fd5b6100d961014f565b005b60016000808282829054906101000a900463ffffffff160192506101000a81548163ffffffff021916908363ffffffff160217905550565b60008060009054906101000a900463ffffffff16905090565b60008060006101000a81548163ffffffff021916908363ffffffff160217905550565b60008060009054906101000a900463ffffffff1663ffffffff16111515610175576101ac565b60016000808282829054906101000a900463ffffffff160392506101000a81548163ffffffff021916908363ffffffff1602179055505b5600a165627a7a72305820bed0fd30c65dac6417e92e6c82d00506a15ef0990d90be1864f90d01564785ce0029
Contract JSON ABI
[{"constant":false,"inputs":[],"name":"incrementCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getCount","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resetCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"decrementCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]
コンパイルすると、バイトコードとABIと呼ばれるインターフェイスが出力されます。
これらをもとにコントラクトをデプロイします。
まずは、バイトコード、ABIを変数に格納します。
> bytecode = "606060405260008060006101000a81548163ffffffff021916908363ffffffff160217905550341561003057600080fd5b6101da8061003f6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635b34b96614610067578063a87d942c1461007c578063dbdf7fce146100b1578063f5c5ad83146100c6575b600080fd5b341561007257600080fd5b61007a6100db565b005b341561008757600080fd5b61008f610113565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b34156100bc57600080fd5b6100c461012c565b005b34156100d157600080fd5b6100d961014f565b005b60016000808282829054906101000a900463ffffffff160192506101000a81548163ffffffff021916908363ffffffff160217905550565b60008060009054906101000a900463ffffffff16905090565b60008060006101000a81548163ffffffff021916908363ffffffff160217905550565b60008060009054906101000a900463ffffffff1663ffffffff16111515610175576101ac565b60016000808282829054906101000a900463ffffffff160392506101000a81548163ffffffff021916908363ffffffff1602179055505b5600a165627a7a72305820bed0fd30c65dac6417e92e6c82d00506a15ef0990d90be1864f90d01564785ce0029"
> abi = [{"constant":false,"inputs":[],"name":"incrementCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getCount","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resetCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"decrementCounter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]
次に、このコントラクトのデプロイに必要なGASを取得します。
> gas_estimate = eth.estimateGas({data: bytecode})
152641
そして、コントラクトオブジェクトを作成します。
> contract = eth.contract(abi)
{
abi: [{
constant: false,
inputs: [],
name: "incrementCounter",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: true,
inputs: [],
name: "getCount",
outputs: [{...}],
payable: false,
stateMutability: "view",
type: "function"
}, {
constant: false,
inputs: [],
name: "resetCounter",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}, {
constant: false,
inputs: [],
name: "decrementCounter",
outputs: [],
payable: false,
stateMutability: "nonpayable",
type: "function"
}],
at: function(address, callback),
new: function()
}
以上の値をもとにデプロイします。
> counter = contract.new({ from: eth.accounts[0], data: bytecode, gas: gas_estimate })
{
address: undefined,
transactionHash: "0x34c068d461617bb6ad4485c495e7ae87aeb4b8426c0f096c882506d6f84b829c"
}
上記では、マイニングが実行されていないのでアドレスがundefined
になっています。
ここで、miner.start()
をして暫く待つと、addressが割り当てられデプロイ完了となります。
> counter
{
address: "0xa79983bc0497090af844c4447b7f293bbfe9cfa1",
transactionHash: "0x34c068d461617bb6ad4485c495e7ae87aeb4b8426c0f096c882506d6f84b829c",
allEvents: function(),
decrementCounter: function(),
getCount: function(),
incrementCounter: function(),
resetCounter: function()
}
デプロイしたコントラクトにアクセスしたい場合は、以下のコマンドでアクセスできます。
> counter_contract = eth.contract(abi).at("0xa79983bc0497090af844c4447b7f293bbfe9cfa1")
{
address: "0xa79983bc0497090af844c4447b7f293bbfe9cfa1",
allEvents: function(),
decrementCounter: function(),
getCount: function(),
incrementCounter: function(),
resetCounter: function()
}
at()
の引数は、コントラクトのアドレスです。
動作確認
スマートコントラクトの動作確認では、トランザクションを発行して、それがマイニングにより承認されれば、実行結果が反映される動きになっているため、テスト環境ではminer.start()
を裏で走らせておくと確認が楽です。
では、先程デプロイしたカウンターコントラクトを実行していきましょう。
まずは、現在のカウントを確認します。
> counter.getCount.call()
call()
は、読み取り専用の関数について使用します。状態を変更させない(トランザクションを発行しない)のでGASの消費もありません。
反対に、以下のようにGASを消費して、状態を変えることをしたい場合には、sendTransaction()
を使います。
カウントアップします
> counter.incrementCounter.sendTransaction({ from: eth.accounts[0] })
"0xfff921c2bb0358453bc6f7d3ef346e26471c11b59d8c29e83cb09db0f656ab38"
しばらくマイニングを待って、確認すると1になっています。
> counter.getCount.call()
1
以上により正常にコントラクトが実行されていることを確認できました。
これで、スマートコントラクトをサクッと動かすことに成功しました。
おわりに
ブロックチェーンとは、スマートコントラクトとはと理解を深める手立てとして簡単に動かせるということは重要だと思ったのでこのような記事を書いた所存です。