Ethereumとは
Ethereum とは Ethereum Virtual Machine (EVM) 上でスマートコントラクトを実行するためのプラットフォームである。ビットコインのスクリプトよりも表現力が高く、チューリング完全であると言われている。また、あらゆるスマートコントラクトを実行することができる World Computer である、とも言われている。EVM上で実行されるすべての処理はEthereumのブロックチェーンに参加している全ノードで同時に実行されるが、その実行のコストとして gas が消費される。このgasこそがコインチェックやbitFlyerで扱われている ETH である。
EVM上で実行されるのは PUSH1 0 CALLDATALOAD SLOAD NOT PUSH1 9 JUMPI STOP JUMPDEST PUSH1 32 CALLDATALOAD PUSH1 0 CALLDATALOAD SSTORE
のようなopcodeである。さすがにこれを人が実装するのは大変なので、高級言語として Solidity などの言語が開発されている。ここでは一番人気のあるSolidityを利用する。
Ethereumには本番のブロックチェーンネットワーク mainnet や ropsten や kovan 、 rinkeby といった開発用のtestnetが存在する。そして、開発や利用するにはそのネットワークに繋がるためのクライアントが必要になる。golangで実装されている geth や先日バグで問題が発覚して話題になった parity などがある。個人的にはparityはわかりやすくて好き。
Truffleを使って開発してみる
Truffle は ConsenSys が開発しているSolidityのフレームワークである。これを利用すると簡単にオリジナルのトークンも作ることができる。2017年11月に発表されたTruffleV4を使うとtestrpc(ローカルの開発用のネットワークのイメージ)も組み込まれているのでテストも簡単に実行できる。ここではテストは割愛。
セットアップする
$ npm install -g truffle $ mkdir solidity-sample $ cd solidity-sample $ truffle init
これでプロジェクトの雛形が作成される。これをもとにスマートコントラクトを実装していく。
スマートコントラクトを実装する
$ truffle create contract ProofOfExistence
とするとファイルが contracts
以下に自動生成される。これを以下のように実装してみる。
pragma solidity ^0.4.17; contract ProofOfExistence { mapping (bytes32 => bool) private proofs; function storeProof(bytes32 proof) { proofs[proof] = true; } function notarize(string document) { var proof = proofFor(document); storeProof(proof); } function proofFor(string document) constant returns (bytes32) { return sha256(document); } function checkDocument(string document) constant returns (bool) { var proof = proofFor(document); return hasProof(proof); } function hasProof(bytes32 proof) constant returns(bool) { return proofs[proof]; } }
testrpcで確認してみる。
次に、このスマートコントラクトを確認するために migrations/2_deploy_contracts.js
を作成する。
var ProofOfExistence = artifacts.require("./ProofOfExistence.sol"); module.exports = function(deployer, network, accounts) { deployer.deploy(ProofOfExistence); };
そして、ターミナルを開いてTruffleのtestrpcを開く。
$ truffle develop ... truffle(default)> migrate --reset ... truffle(default)> var p = ProofOfExistence.at(ProofOfExistence.address) truffle(default)> p.checkDocument('Hello world'); false truffle(default)> p.notarize('Hello world'); truffle(default)> p.checkDocument('Hello world'); true
このようにスマートコントラクトの処理が確認できる。
testnet kovanにデプロイする
parityでkovanを利用してみる。parityがインストールされてなければインストールする。この作業はせずにいきなり web3
で動作確認しても問題ない。
truffle.js
で利用するネットワークを書いておく。
module.exports = { networks: { development: { host: 'localhost', port: 8545, network_id: '42' } } };
parity --chain kovan --rpccorsdomain "*"
を呼んで動かしている状態で、parityを開く。必要であればアカウントの登録を済ます。kovan上でのETHがなければfaucetでアドレスをポストすればすぐもらえる。syncに結構時間がかかるので注意。
次に、 truffle migrate
を実行してkovanにデプロイする。
parityで動作確認する
画像のようにSETTINGSからCONTRACTSタブを追加しておく。
CONTRACTSタブを選択し、 + WATCH
からさきほどデプロイしたスマートコントラクトを登録する。デプロイした時の、デプロイ先のアドレスを貼り付ける。また、migrateコマンドを実行した時に生成された build/contracts/ProofOfExistence.json
の中の abi
の部分(配列ごと)もコピーして貼り付ける。
これで登録完了なので、実際にfunctionが機能しているか確認してみる。
hello
とだけ入力して checkDocument
をクエリしても何も起こらない。
次にCONTARACTSタブのすぐ下にある EXECUTE
を押して、 notarize
を実行して hello
を記録する。
もう一度さきほどと同様に checkDocument
に hello
を渡して呼んでみる。すると、 true
が返ってくる。すごく微妙だが動作は確認できた。
mainnetはまたsyncに時間がかかりそうなので一旦後回し...。
ついでにweb3でたたいてみる
parityやgethはそれぞれのノードがブロックチェーンを保持して常に同期されている。さすがにモバイルアプリや普通のブラウザでそのままEthereumを利用するにはリソースを食い過ぎてしまう。そこで web3
というEthereumとのインターフェースになるライブラリを使って、さきほど作成したスマートコントラクトを普通のブラウザから利用してみる。
app/index.html
を新規作成して以下のように編集してみる。abiとコントラクトアドレスはさきほど利用したものと同じものである。 notarize
を実行する時に認証があるが、その認証を毎回聞かれないように、ここでは ps.txt
というファイルを作成して、そこにパスワードを書いて保存し、 parity --chain kovan --rpccorsdomain "*" --unlock 0x005041C1a70B270DB90adaEbb109f4C9501d2C6B --password pw.txt
でparityを起動する。pw.txtはもれないように、 .gitignore
に書いておくなりしておく。 --unlock
の後には自分のkovanでのアドレスを入れておく。
parity --chain kovan --rpccorsdomain "*"
でparityを起動すると、 http://localhost:8545
でweb3がEthereum(kovan)にアクセスできる。
<!DOCTYPE html> <html lang="ja"> <head> <title>Ethereum web3 Sample</title> <script src="https://cdn.rawgit.com/ethereum/web3.js/0.19.0/dist/web3.min.js"></script> <script> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); abi = [ { "constant": true, "inputs": [ { "name": "document", "type": "string" } ], "name": "checkDocument", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "document", "type": "string" } ], "name": "notarize", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "name": "proof", "type": "bytes32" } ], "name": "storeProof", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "proof", "type": "bytes32" } ], "name": "hasProof", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "document", "type": "string" } ], "name": "proofFor", "outputs": [ { "name": "", "type": "bytes32" } ], "payable": false, "stateMutability": "view", "type": "function" } ]; address = '0x6B3Ac171153526C4B6d61500c21335B893E22548'; contract = web3.eth.contract(abi).at(address); function getInput() { let get = {}; let query = window.location.search.substring(1).split("&"); for (let i in query) { if (query === "") continue; const param = query[i].split("="); get[decodeURIComponent(param[0])] = decodeURIComponent(param[1] || ""); } return get.word; } function checkDocument() { const word = getInput(); const res = contract.checkDocument(word); document.write(`The result of ProofOfExistence.checkDocument( ${word} ) is ${res}`); } function notarize(word) { console.log(word); const res = contract.notarize(word).call() } </script> </head> <body> <h1>Ethereum Smart Contract Sample</h1> <p>Latest block: <script>document.write(web3.eth.blockNumber + "<br><br>")</script></p> <form onSubmit="return notarize()"> Notarize a word <input type=text size=50 name=word><input type="submit"> </form> <form> CheckDocument a word <input type=text size=50 name=word><input type="submit"> </form> <br> <p><script>checkDocument()</script></p> </body> </html>
app/index.html
を保存したら、 python -m SimpleHTTPServer
などのコマンドでサーバーを起動して http://localhost:8000
でアクセスしてみる。一度notarizeで登録した単語をcheckDocumentで確認するとtrueになるはず。
GitHubにあげています。