try catch and ...release
ページ
ホーム
Chromeアプリ開発Tips
公開アプリ
Ubuntu
Linuxコマンド
#!/bin/bash
ブロックチェーンと暗号通貨
2016年12月15日木曜日
Ethereum で Dapp を HelloWorld
[前回構築した Private Testnet](http://trycatchand.blogspot.jp/2016/12/create-ethereum-private-testnet-send-ether.html)を利用して Ethereum の Dapp を HelloWorld してみたいと思います。
## 目次 (index) + [Dappとは](#about) + [環境](#environment) + [Web3 とは](#web3) + [Web3を使うための Geth 起動オプション](#run-geth-for-web3) + [Web3を使う](#using-web3) - [残高取得](#get-balance-using-web3) - [アドレス作成](#create-account-using-web3) - [送金](#send-ether-using-web3) - [マイナーに支払われる手数料Gas](#gas) + [コントラクトの作成](#create-contract) - [SolCをインストール](#install-solc) - [コントラクトのサンプルコード](#contract-sample) - [コントラクトのコンパイルとデプロイ](#compile-deploy-contract) - [コントラクトの実行](#execute-contract) - [コントラクトを利用してもらう](#contract-used-by-others) - [コントラクトの削除](#delete-contract) + [Dappを作成](#create-dapp) + [次回](#next) [⤒](#index) ### Dappとは (about) `Decentralized Application` (非中央集権型アプリケーション)の略。 ざっくり言うと、Ethereumのブロックチェーンをバックエンドとして利用するクライアントアプリケーションのこと。 EthereumのDappでは、フロントエンド作成にHTMLやJSなどのWeb標準技術を使うことができます。一方、バックエンド(`Contract`)作成には専用のコントラクト指向言語を使用します。コントラクト指向言語にはいくつか種類がありますが(`Solidity`, `Serpent`, `LLL`)、今回はJSっぽい構文を持つ`Solidity`を使いたいと思います。 [⤒](#index) ## 環境 (environment) - Ethereumネットワーク: [前回作成した Private Testnet](http://trycatchand.blogspot.jp/2016/12/create-ethereum-private-testnet-send-ether.html)を利用 - データ保存先フォルダ`datadir`: *~/.ethereum_private_testnet* - 使用言語(フロントエンド): *HTML5*, *JavaScript*(*ES2015*), *CSS3* - 使用言語(バックエンド): コントラクト指向言語 *Solidity* [⤒](#index) ## Web3 とは (web3) 今回は `web3.js` というライブラリを使って Dapp を作りたいと思います。web3とは Ethereum JavaScript API です。web3を使うことで、Ethereumクライアントの Geth Console で実行できることがJavaScript(たとえばブラウザ)からも実行できるようになります。 [⤒](#index) ## Web3を使うための Geth 起動オプション (run-geth-for-web3) そのためにはまず **HTTP-RPCを有効** にして Geth を起動する必要があります。 以下のオプションで Geth を起動: - `--rpc`: **HTTP-RPCサーバ有効** - `--datadir`: ブロックチェーンなどの保存先フォルダ=**~/.ethereum_private_testnet/** - `--dev`: **開発モード有効** - `--mine`: **マイニング有効** - `--minerthreads=1`: マイナースレッド数=**1** - `--nodiscover`: 他Nodeから見つからない設定 - `--etherbase`: マイニング報酬受け取りアドレス=**0x4a1c11eaec40197beb6e8607ee61953e7d6d8731** ``` $ cd ~/.ethereum_private_testnet $ geth --rpc --datadir=./ --dev --mine --minerthreads=1 --nodiscover --etherbase=0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 ``` [⤒](#index) ## Web3を使う (using-web3) それでは、`web3.js` を使って Dapp コーディングをしてみましょう。 まずはテキトーな空プロジェクトを作成し、npmモジュールとして `web3` をインストールします: ``` $ mkdir web3example && cd web3example $ npm init $ npm install --save web3 ``` index.js を作成して web3 を読み込めるかどうかを確認: ``` $ touch index.js ``` index.js: ``` var Web3 = require('web3'); var web3 = new Web3(); web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); console.log("web3:", web3); // web3の中に何らかのObjectが入っていれば読み込み成功 ``` 実行: ``` $ node index.js web3: Web3 { _requestManager: { provider: { host: 'http://localhost:8545' }, polls: {}, timeout: null }, currentProvider: { host: 'http://localhost:8545' }, eth: Eth { ... ``` [⤒](#index) ### 残高取得 (get-balance-using-web3) web3を使ってcoinbaseアドレスの残高を取得してみます。 index.js: ``` var Web3 = require('web3'); var web3 = new Web3(); web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); // // coinbaseアドレスの残高を確認: // var coinbase = web3.eth.coinbase; var balance = web3.eth.getBalance(coinbase); console.log("balance:", balance); ``` 実行: ``` $ node index.js balance: { [String: '814000000000000000000'] s: 1, e: 20, c: [ 8140000 ] } ``` web3を使ってcoinbaseアドレスの残高が取得できました。 [⤒](#index) ### アドレス作成 (create-account-using-web3) その前に、アドレス作成に必要なAPI`personal`を有効にして Geth を再起動しておきます: ``` $ cd ~/.ethereum_private_testnet $ geth --rpc --rpcapi "eth,net,web3,personal" --datadir=./ --dev --mine --minerthreads=1 --nodiscover --etherbase=0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 ``` では、web3を使ってアドレスを作成してみます。 index.js: ``` var Web3 = require('web3'); var web3 = new Web3(); web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); // // アドレスを新規作成: // var address = web3.personal.newAccount("<パスフレーズ>"); console.log("address:", address); ``` index.jsを実行: ``` $ node index.js address: 0x585542d64e07e856c3233042bc2d20a6711bbd75 ### ↑新たに作成されたアドレス ``` #### 作成したTest用アドレス: (test-accounts) [前回作った2つのアドレス](http://trycatchand.blogspot.jp/2016/12/create-ethereum-private-testnet-send-ether.html#create-account)に加え、今回はアドレスをもうひとつ作成しておきました。 - (A) 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 - (B) 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 - (C) **0x585542d64e07e856c3233042bc2d20a6711bbd75** <= new [⤒](#index) ### 送金 (send-ether-using-web3) web3を使って、[テスト用アドレス](#test-accounts)の (B) から (C) へ 1000000000000 wei 送金してみます: index.js: ``` var Web3 = require('web3'); var web3 = new Web3(); web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); // // アドレスB(0x5855a19b64e1068a1edc39bb817647e16a1c57e7)から // アドレスC(0x585542d64e07e856c3233042bc2d20a6711bbd75)へ 1000000000000 wei 送金: // var addressB = "0x5855a19b64e1068a1edc39bb817647e16a1c57e7"; var addressC = "0x585542d64e07e856c3233042bc2d20a6711bbd75"; var amount = 1000000000000; // wei // 送金前にBとCのそれぞれの残高を確認: console.log("送金前のそれぞれの残高:") console.log("送金元の残高:", web3.eth.getBalance(addressB)); console.log("送金先の残高:", web3.eth.getBalance(addressC)); // 送金元のアドレスを600秒間アンロック: console.log("送金前に送金元アドレスBをアンロック:") web3.personal.unlockAccount(addressB, "<パスフレーズ>", 600); // 送金実行: console.log("送金を実行します:") var txId = web3.eth.sendTransaction({ from: addressB, to: addressC, value: amount }); console.log("txId:", txId); // 30秒後に送金後のBとCのそれぞれの残高を確認(30秒間待つのはトランザクション採掘完了を待つため): setTimeout(function() { console.log("送信後のそれぞれの残高:") console.log("送金元の残高:", web3.eth.getBalance(addressB)); console.log("送金先の残高:", web3.eth.getBalance(addressC)); }, 30000); ``` **Note:** 送金トランザクション送信後、採掘が完了するまで`setTimeout(function(){...}, 30000)`で時間を稼いでいます。 index.jsを実行: ``` $ node index.js 送金前のそれぞれの残高: 送金元の残高: { [String: '998739999999970000'] s: 1, e: 17, c: [ 9987, 39999999970000 ] } 送金先の残高: { [String: '30000'] s: 1, e: 4, c: [ 30000 ] } 送金前に送金元アドレスBをアンロック: 送金を実行します: txId: 0xabc8d1687a49108c1cb68c1268e6af077f1c1cd8a17be534084667629b677f71 送信後のそれぞれの残高: 送金元の残高: { [String: '998318999999970000'] s: 1, e: 17, c: [ 9983, 18999999970000 ] } 送金先の残高: { [String: '1000000030000'] s: 1, e: 12, c: [ 1000000030000 ] } ``` 送金先のアドレスCへ 1000000000000 wei送金した結果、アドレスCの残高は 30000 -> 1000000030000 と送金額通りに増加しました。 しかし一方で、送信元のアドレスBの残高は、 ``` 998739999999970000 (送金前) -998318999999970000 (送金後) ---------------------------------- -421000000000000 (減少) 1000000000000 (減少額のうち送金額) ``` 見ての通り 421000000000000 も減っています。これは、送金額の 1000000000000 wei の他に、マイナーに対して手数料(gas fee)が 420000000000000 wei 支払われたからです。 [⤒](#index) ### マイナーに支払われる手数料Gas (gas) 先ほどの手数料が本当に正しいのか、上記の送金トランザクション詳細から調べてみましょう: ``` > eth.getTransaction("0xabc8d1687a49108c1cb68c1268e6af077f1c1cd8a17be534084667629b677f71") { blockHash: "0x805c27889c5a1f6efe5d26d3d6e3aecdc06396faec231c28769bfe6f5a7faccd", blockNumber: 614, from: "0x5855a19b64e1068a1edc39bb817647e16a1c57e7", gas: 90000, gasPrice: 20000000000, hash: "0xabc8d1687a49108c1cb68c1268e6af077f1c1cd8a17be534084667629b677f71", input: "0x", nonce: 3, r: "0x29987455907a45d940614ae51311b3979ef27512814d685e0742cc99302d61be", s: "0x5186f575832fafead589b92b52ce2f5c755cab8d68851c9a8f6cbab373bd98b6", to: "0x585542d64e07e856c3233042bc2d20a6711bbd75", transactionIndex: 0, v: "0x1c", value: 1000000000000 } ``` まずこの時点で`gasPrice`(Gas価格)が 20000000000 という情報が得られました。 次に、上記の`blockNumber: 614` を元に、このトランザクションを含む採掘済みブロックの詳細を確認します: ``` > eth.getBlock("614") { difficulty: 143641, extraData: "0xd783010504846765746887676f312e372e33856c696e7578", gasLimit: 4712388, gasUsed: 21000, hash: "0x805c27889c5a1f6efe5d26d3d6e3aecdc06396faec231c28769bfe6f5a7faccd", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x4a1c11eaec40197beb6e8607ee61953e7d6d8731", mixHash: "0xb5c7d9881abf09c219963975581f2f82470dbd4ec03967efc35f422f1afbdd51", nonce: "0x6c51b267e3176246", number: 614, parentHash: "0xc1f7d428a16faa8af1b8cd4e49b63a73a24bd1f05aab0fc3242bb3b759b83333", receiptsRoot: "0xb460d4d87a0a6fa4655382334caf2fdcf6c83b33b7b483424287e9c1a5a1f9b6", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 648, stateRoot: "0x4d241389d476f9881dd798c68628ae237a26de8df3d8637952e10a98e698a114", timestamp: 1481535406, totalDifficulty: 84861675, transactions: ["0xabc8d1687a49108c1cb68c1268e6af077f1c1cd8a17be534084667629b677f71"], transactionsRoot: "0xd6273aa928ba5dbc13d58dfcf98fea9add20571902d4afe1ef47b601877c13db", uncles: [] } ``` `gasUsed`(実際に使われたGas)が 21000 ということが分かりました。 ということは、実際に支払ったGas手数料は、 **gasPrice * gasUsed = 420000000000000** ということになりますので、手数料は正しく徴収されているということになります。 [⤒](#index) ## コントラクトの作成 (create-contract) コントラクトとは、ブロックチェーン上で動き続けるプログラムのようなもので、Dappのバックエンドになります。 コントラクトを実行するには、コントラクトのソースコード(今回はコントラクト指向言語`Solidity`を使います)を作成し、それをコンパイルする必要があります。 [⤒](#index) ### SolCをインストール (install-solc) まずはコントラクト・コンパイラ`SolC`をインストールします: ``` $ sudo add-apt-repository ppa:ethereum/ethereum $ sudo apt-get update $ sudo apt-get install solc $ which solc /usr/bin/solc # Private Testnet が起動しているという前提で # Geth Console を起動: $ cd ~/.ethereum_private_testnet/ $ geth attach ./geth.ipc # SolCコンパイラとしてリンク: > admin.setSolc("/usr/bin/solc") # コンパイラを確認: > eth.getCompilers() ["Solidity"] ``` **Tips:** インストール型Solidityコンパイラ以外にも、[browser-solidity](https://ethereum.github.io/browser-solidity/)というWebサービス型のSolidityコンパイラもあります。 [⤒](#index) ### コントラクトのサンプルコード (contract-sample) それではいよいよコントラクトを書いてみましょう。 今回作るのは、ただ挨拶するだけのシンプルなコントラクトです。 greeter.sol: ``` contract mortal { /* Define variable owner of the type address*/ address owner; /* this function is executed at initialization and sets the owner of the contract */ function mortal() { owner = msg.sender; } /* Function to recover the funds on the contract */ function kill() { if (msg.sender == owner) suicide(owner); } } contract greeter is mortal { /* define variable greeting of the type string */ string greeting; /* this runs when the contract is executed */ function greeter(string _greeting) public { greeting = _greeting; } /* main function */ function greet() constant returns (string) { return greeting; } } ``` [⤒](#index) ### コントラクトのコンパイルとデプロイ (compile-deploy-contract) 先程のコントラクトのサンプルコードをコンパイル&デプロイしてみましょう。 Geth Console から以下を実行します: ``` // まずは改行をなくす: > var greeterSource = 'contract mortal { address owner; function mortal() { owner = msg.sender; } function kill() { if (msg.sender == owner) suicide(owner); } } contract greeter is mortal { string greeting; function greeter(string _greeting) public { greeting = _greeting; } function greet() constant returns (string) { return greeting; } }' // コンパイルする: > var greeterCompiled = web3.eth.compile.solidity(greeterSource) // 引数のパラメータを設定してみる: > var _greeting = "Hello World!" // コントラクトのクラス的なものを取得: > var greeterContract = web3.eth.contract(greeterCompiled.greeter.info.abiDefinition); // コントラクトを送信する前に送信元のアドレスをアンロック: > web3.personal.unlockAccount(web3.eth.accounts[0], "<パスフレーズ>", 600); // コントラクトのインスタンスを生成&デプロイ(ブロックチェーンへの組み込み): > var greeter = greeterContract.new(_greeting,{from:web3.eth.accounts[0], data: greeterCompiled.greeter.code, gas: 1000000}, function(e, contract){ if(!e) { if(!contract.address) { console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined..."); } else { console.log("Contract mined! Address: " + contract.address); console.log(contract); } } }) Contract transaction send: TransactionHash: 0xb05f36d348ce8c6a49ea6ca6ea67e355c943ed1ed69ba2346be8e7aa741805d9 waiting to be mined... ``` #### コントラクトのアドレス (contract-address) だいたい30秒以内で採掘が完了しコントラクトがブロックチェーンに組み込まれ、以下の結果が出力されます: ``` > Contract mined! Address: 0x5aa6d32cdfc37480efeb1d0d22aefa1a647d2051 [object Object] ``` **注意:** コントラクトのデプロイにもGas(マイナーへの手数料)が必要となります。 [⤒](#index) ### コントラクトの実行 (execute-contract) 採掘が完了しブロックチェーンに登録されたので、greeterコントラクトを使うことができます: ``` # コントラクトのアドレスの確認: > greeter.address "0x5aa6d32cdfc37480efeb1d0d22aefa1a647d2051" # greet関数を呼び出してみる: > greeter.greet(); 'Hello World!' ``` [⤒](#index) ### コントラクトを利用してもらう (contract-used-by-others) 作成したコントラクトを他の人にも使ってもらうためには、コントラクトの**ABI**と**アドレス**の2つの要素を公開する必要があります。*ABI*はコントラクトのインターフェースで、*アドレス*は[上でも確認できた](#execute-contract) *0x5aa6d32cdfc37480efeb1d0d22aefa1a647d2051* のような文字列です。 コントラクトの`ABI`と`アドレス`はそれぞれ以下のようにして取得することができます: ``` # greeterコントラクトのABI: > greeterCompiled.greeter.info.abiDefinition; [{constant:false,inputs:[],name:'kill',outputs:[],type:'function'},{constant:true,inputs:[],name:'greet',outputs:[{name:'',type:'string'}],type:'function'},{inputs:[{name:'_greeting',type:'string'}],type:'constructor'}] # greeterコントラクトのアドレス: > greeter.address; 0x5aa6d32cdfc37480efeb1d0d22aefa1a647d2051 ``` そして、コントラクトの利用者は以下のコードを呼ぶことで、あなたが作ったコントラクトを呼び出すことができます: ``` > var greeter = eth.contract(
).at(<アドレス>); > greeter.greet(); 'Hello World!' ``` [⤒](#index) ### コントラクトの削除 (delete-contract) 先ほど作成したgreeterコントラクトは作成者の手で削除することができます。それはgreeterコントラクトが自らを削除するための処理([mortal#kill()](#contract-sample))を実装しているからです。 コントラクトを削除するには以下のようにします(**が、この後、このコントラクトを使ってDappを作成する予定なので実際にはまだ削除しないでください**): ``` # トランザクションを送信する前に送信元のアドレスをアンロック: > web3.personal.unlockAccount(web3.eth.accounts[0], "<パスフレーズ>", 600); # greeterコントラクトの削除: > greeter.kill.sendTransaction({from:eth.accounts[0]}) # 削除されたか確認: > eth.getCode(greeter.address) "0x" <= 削除されたので"0x"が返ってくる ``` [⤒](#index) ## Dappを作成 (create-dapp) さて、ついにDappをコーディングする時が来ました。先ほど作成したgreeterコントラクトを使って簡単なDappを作ってみましょう。 index.html: ```html
Greeter Dapp
Click Me!
``` Dappのコードはたったのコレだけです。 **Note:** scriptタグで読み込まれている`web3.min.js`(web3モジュール)は、[Web3を使う](#using-web3)で`npm install`したときに生成された `/node_modules/web3/dist/web3.min.js` をコピーしてきました。 **注意:** これをブラウザから実行すためには、GethのRPC設定を許可する必要があります。以下のオプションをつけることで、ブラウザアプリのDappがEthereumネットワークにアクセスできるようになります: - `--rpc`: **RPCを有効に** - `--rpccorsdomain "*"`: **corsを全て許可** ``` $ cd ~/.ethereum_private_testnet $ geth --rpc --rpccorsdomain "*" --datadir=./ --dev --mine --minerthreads=1 --nodiscover --etherbase=0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 ``` **Dappを実行** ブラウザで先ほど作成した index.html を開いてみると、`Click Me!`というボタンだけのシンプルな画面が現れます。そのボタンをクリックすると、内部ではweb3を使ってgreeterコントラクトのインスタンスが生成され、greet関数が実行されます。すると、Ethereumネットワークから返り値(Hello World!)が返却されてボタンのinnerTextに表示されます。このようにDappのバックエンドのように振る舞うのがコントラクトなのです。 [⤒](#index) ## 次回 (next) 次回は[もうちょっと複雑で面白みのあるコントラクト](https://trycatchand.blogspot.jp/2016/12/issue-your-coin-on-ethereum.html)を作ってみたいと思います。 [⤒](#index)
0 件のコメント:
コメントを投稿
次の投稿
前の投稿
ホーム
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿