Ethereum使ったアプリケーションをRubyで書けないかなーと思ってRubyのライブラリを調べてみた。
EthereumのRubyライブラリ
公式ドキュメントで紹介されているのは以下の3つ。
ruby-ethereum
Rubyで書かれたEVM(Ethereum Virtual Machine)の実装
ruby-serpent
EthereumのSerpent*1コンパイラへのRubyバインディングライブラリ
ethereum-ruby
EthereumのノードへのJSON-RPCをラップしたライブラリ
teth
またその他にtethというrubyで書かれたテスティングフレームワークがある。
Solidityのコントラクトのテストをgethもしくはruby EVMで実行することができるみたい。
ruby EVMは↑のruby-ethereum。
tethのセットアップ
コントラクトの記述もTDDでということで、minitestベースのテストが書けるtethを使ってみる。
libserpent.soのビルド
tethの依存ライブラリにserpentというgemがあり、libserpent.soが無いとそのgemのビルドに失敗するので、事前にserpentをビルドする。
$ git clone git@github.com:ethereum/serpent.git .. $ cd serpent $ make ... $ sudo make install cp serpent /usr/local/bin cp libserpent.a /usr/local/lib cp libserpent.so /usr/local/lib rm -rf /usr/local/include/libserpent mkdir -p /usr/local/include/libserpent cp bignum.h util.h keccak-tiny-wrapper.h tokenize.h lllparser.h parser.h opcodes.h functions.h optimize.h rewriteutils.h preprocess.h rewriter.h compiler.h funcs.h /usr/local/include/libserpent
ruby-bitcoin-secp256k1のセットアップ
tethはruby-bitcoin-secp256k1にも依存しており、ruby-bitcoin-secp256k1はlibsecp256k1に依存している。libsecp256k1はBitcoin Coreが独自実装した楕円曲線暗号のCライブラリの最適化実装。
そのためruby-bitcoin-secp256k1をクローンしてlibsecp256k1をビルドする。
$ git clone git@github.com:cryptape/ruby-bitcoin-secp256k1.git ... $ ruby-bitcoin-secp256k1 $ git submodule update --init --recursive ... $ ./install_lib.sh ... 途中でsudoのパスワード聞かれるので入力 ...
実行が終わると/usr/local/lib/libsecp256k1.so
ができてる。あとは生成されたライブラリをちゃんと読みこむようsudo ldconfig
しておく。
Bundleインストール
あとはGemfileに
gem 'teth'
と書いてbundleすれば良い。
expectのインストール
tethのスクリプトが内部でexpect
を使っているので、expect
がインストールされていない場合はインストールしておく。aptやyumでインストールできる。
tethの使い方
プロジェクトの作成
new
コマンドでSmart Contractアプリケーションを新規作成し、プロジェクトのテンプレートが作成される。
$ teth n sample Creating project tethsample... Resolving dependencies... Using ffi 1.9.10 Using little-plugger 1.1.4 Using multi_json 1.11.2 Using digest-sha3 1.1.0 Using ethash 0.2.0 Using fiddler-rb 0.1.2 Using lru_redux 1.1.0 Using minitest 5.8.4 Using rlp 0.7.3 Using serpent 0.3.0 Using bundler 1.11.2 Using bitcoin-secp256k1 0.4.0 Using logging 2.1.0 Using leveldb 0.1.9 Using block_logger 0.1.2 Using ruby-ethereum 0.9.6 Using teth 0.2.2 Bundle complete! 1 Gemfile dependency, 17 gems now installed. Use `bundle show [gemname]` to see where a bundled gem is installed. Done.
そうすると以下のようなディレクトリ/ファイルが生成される。
コントラクトの新規作成
generate
コマンドでコントラクトを新規作成する。
$ teth g token Creating Token contract file... Create Token.sol contract file... Creating token test files... Done.
コントラクトのファイルとしてcontracts/Token.sol
が、テストファイルとしてgtests/Token_test.js
とtests/token_test.rb
が生成される。
README.mdに記載されているサンプルのようにコントラクトとテストを実装する。
テストの実行
test
コマンドで対象のコントラクトのテストを実行する。この際のテストはrubyのEVMで実行される。
$ teth t token Test Token contract... Run options: --seed 44085 # Running: .... Finished in 1.966287s, 2.0343 runs/s, 3.5600 assertions/s. 4 runs, 7 assertions, 0 failures, 0 errors, 0 skips Done.
gethと連携したプライベートチェーンでのテスト
test
コマンドはrubyのEVMで実行したが、続いてgethを使ってテストを実行する。
プライベートチェーンの初期化
init
コマンドを使うと新しいgenesis block
を初期化し、プライベートチェーンの新しいデータディレクトリを作成する。
$ teth init Initialising a new genesis block... ***** Using geth at: geth I1012 16:55:00.860131 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to /home/azuchi/projects/ethereum/tethsample/data/chaindata I1012 16:55:00.870799 cmd/geth/main.go:353] successfully wrote genesis block and/or chain rule set: 611596e7979cd4e7ca1531260fa706093a5492ecbdf58f20a39545397e424d04
data/chaindata
ディレクトリが新規に作られる。
gethへキーのインポート
import_keysコマンドを実行するとprivate_keysディレクトリ内の*.keyファイルがプライベートチェーンにインポートされる。
$ teth ik Importing keys, this will take a while, please be patient... ***** Using geth at: geth ***** Import all pre-funded private keys Notice: No need to input your password. The default password is 123456 spawn geth --datadir data account import ./private_keys/9da26fc2e1d6ad9fdd46138906b0104ae68a65d8.key Your new account is locked with a password. Please give a password. Do not forget this password. Passphrase: Repeat passphrase: Address: {9da26fc2e1d6ad9fdd46138906b0104ae68a65d8} Notice: No need to input your password. The default password is 123456 spawn geth --datadir data account import ./private_keys/81063419f13cab5ac090cd8329d8fff9feead4a0.key Your new account is locked with a password. Please give a password. Do not forget this password. Passphrase: Repeat passphrase: Address: {81063419f13cab5ac090cd8329d8fff9feead4a0} Notice: No need to input your password. The default password is 123456 spawn geth --datadir data account import ./private_keys/3ae88fe370c39384fc16da2c9e768cf5d2495b48.key Your new account is locked with a password. Please give a password. Do not forget this password. Passphrase: Repeat passphrase: Address: {3ae88fe370c39384fc16da2c9e768cf5d2495b48} ***** Done.
コントラクトのビルド
build
コマンドでコントラクトをビルドする。(コントラクト名を省略した場合は全てのコントラクトがビルドされる)
$ teth build token Building contract Token ======= Token ======= Gas estimation: construction: 20201 + 73400 = 93601 external: issue(address,uint256): 21716 transfer(address,uint256): 42187 getBalance(address): 318 internal: ------------------------------------- Enter Gas: 400000 Enter Value To Be Transferred: Enter Input: Done.
gethの起動
server
コマンドでgeth serverを起動
$ teth server ***** Using geth at: geth Start geth server... I1012 17:32:18.222462 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata I1012 17:32:18.233830 ethdb/database.go:169] closed db:data/chaindata I1012 17:32:18.235163 ethdb/database.go:82] Alloted 128MB cache and 1024 file handles to data/chaindata I1012 17:32:18.277175 ethdb/database.go:82] Alloted 16MB cache and 16 file handles to data/dapp I1012 17:32:18.281127 eth/backend.go:170] Protocol Versions: [63 62 61], Network Id: 31415926 I1012 17:32:18.281287 eth/backend.go:199] Blockchain DB Version: 3 I1012 17:32:18.281863 core/blockchain.go:206] Last header: #0 [611596e7…] TD=131072 I1012 17:32:18.281887 core/blockchain.go:207] Last block: #0 [611596e7…] TD=131072 I1012 17:32:18.281899 core/blockchain.go:208] Fast block: #0 [611596e7…] TD=131072 I1012 17:32:18.282866 p2p/server.go:311] Starting Server I1012 17:32:18.283063 p2p/server.go:554] Listening on [::]:30303 I1012 17:32:18.284900 node/node.go:298] IPC endpoint opened: data/geth.ipc I1012 17:32:18.285096 node/node.go:368] HTTP endpoint opened: http://localhost:8545 I1012 17:32:19.556940 cmd/geth/accountcmd.go:187] Unlocked account 3ae88fe370c39384fc16da2c9e768cf5d2495b48
デプロイ
migrate
コマンドでコントラクトをgethにデプロイする。(コントラクト名を省略した場合は全てのコントラクトがデプロイされる)
$ teth m Migrating all contracts ***** Using geth at: geth null Contract transaction send: TransactionHash: 0xc8cba6ded9720d2db8c69551bf1ee1ddee1fe2c7b82a7adda1442da4d3362e1a waiting to be mined... Compiled Object : TokenCompiled Contract : TokenContract Contract Instance : Token true Contract mined! Address: 0x3ff58e2e4f9ab2d47f0cd84bead42dd0369846c9 Done.
アカウントがロックされている場合はエラーが発生するので、teth console
でgethのコンソールにアクセスしアカウントをアンロックする。(初期パスワードはimport.shに書いてあるが123456)
2つのファイルが生成され、1つはabiとアドレスを保持するtemp/db/Token.json
で、もう1つがtemp/migrations/Token.js
javascriptのテストを書く
gtests以下にgethの環境で実行するテストを記述する。↓はgtests/Token_test.js
loadScript('temp/migrations/Token.js'); var balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) console.log("balance is: ", balance); Token.issue.sendTransaction(web3.eth.accounts[0], 10000, { from: web3.eth.accounts[0] }, function(err, tx){ if(err){ console.log("issue error!"); } else { console.log("issue success. tx: ", tx); } }) miner.start();admin.sleepBlocks(2);miner.stop(); balance = Token.getBalance.call(web3.eth.accounts[0], { from: web3.eth.accounts[0] }) console.log("balance is: ", balance);
作成したテストはgtest
コマンドで実行する。
$ teth gt ***** Using geth at: geth Testing all contracts on geth... balance is: 0 issue success. tx: 0x4fb32aaf2c9272478dbb549374349654b3785eee3cef8d671603efb54ee62e95 balance is: 10000 true Done.
gethのコンソールアクセス
console
コマンドでgethのコンソールにアタッチできる。
$ teth console ***** Using geth at: geth Starting geth attach... instance: Geth/v1.5.0-unstable/linux/go1.5.1 coinbase: 0x9da26fc2e1d6ad9fdd46138906b0104ae68a65d8 at block: 13 (Wed, 12 Oct 2016 18:16:38 JST) datadir: data
rubyのEVMでサクッとテストが実行できるのは便利だなー。