スマートコントラクトで、helloworld
を実行します。ここでは、スマートコントラクトの革新的なところである、ある処理(この場合、helloworld
)をした際に、トランザクションが発生し、コストがかかると言うことを体現することが出来ます。
環境構築がまだの場合は、
Ethereum入門(1) - スマートコントラクトの開発環境を構築する から、環境構築を済ませてください。
スマートコントラクトを実施するための、ディレクトリの作成から入ります。このhelloworldディレクトリ配下に作成していきます。
$ mkdir -p /Users/test/applications/blockchain/helloworld
$ cd helloworld
nodejs用のNPM 管理パッケージのインストール
ここで、管理を簡単にするために、npm
を導入します。npm
は、node.js
で使われるパッケージ管理ツールです。npm
のリポジトリに、ツールが追加されていきます。スマートコントラクトを扱う際は、node.js
を使うため、パッケージの管理に npm
を使用していきます。管理したいディレクトリ(パッケージディレクトリ)で npm init
コマンドを打ち込むことでパッケージを初期化します。初期化中に色々と聞かれますが、デフォルトでエンターキーを叩いて大丈夫です。
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (helloworld)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/takeo/Applications/blockchain/helloworld/package.json:
{
"name": "helloworld",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this ok? (yes) yes
tree
で作成されたファイルを見てみます。npm
パッケージが package.json
を生成し、このファイルを使用して、プロジェクトが管理されます。
$ tree
.
└── package.json
0 directories, 1 file
npmのチートシートとして、分かりやすかったエントリがあったので、リンクを記載しておきます。ありがとうございます!
npmコマンドの使い方
Helloworld アプリに必要なオブジェクトのインストール
helloword
アプリケーションに必要な、依存関係のあるパッケージをインストールしていきます。
イーサリアムノードのRPC
エンドポイントのラッパーで、スマートコントラクトと連携する web3
と スマートコントラクトを記述するsolidity
の コンパイラである solc
をインストールします。npm install web3@0.20.0 solc
コマンドでインストールすることになります。web3
はまだ開発中とのことらしいのですが、比較的安定している0.20 バージョンを使用していきます。
$ npm install web3@0.20.0 solc
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN helloworld@1.0.0 No description
npm WARN helloworld@1.0.0 No repository field.
+ web3@0.20.0
+ solc@0.4.18
added 71 packages in 10.616s
ここで、package.json
ファイルの中身を見てみましょう。npm init
したものに加えて、dependencies
に、web3
とsolc
が追加されているのが確認出来ます。このようにNPMは、パッケージを管理します。後にディレクトリごと別の環境に持っていった際に、npm install とすることで、インストール作業を簡単にしてくれます。
$ cat package.json
{
"name": "helloworld",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"solc": "^0.4.18",
"web3": "^0.20.0"
}
}
tree を打ち込んでみると、インストールされた多くのパッケージが確認できたと思います。これは、web3 solc に依存する全てのパッケージがインストールされたからです。このように、インストールされる依存関係も拾って、一括でインストールしてくれます。
Helloworld スマートコントラクトの実装
簡単なスマートコントラクトを記述します。getHelloworld()関数で、メッセージを表示し、setHelloworld()関数では、出力するメッセージがセット出来るようにっています。setHelloworld()関数を実行することで、トランザクションが実行され、ブロックチェーン上に記載されることになり、またコストが発生することになります。
olidity のコンパイラーが、0.4.18 となっていることに気をつけてください。コントラクターの名前は、helloworld です。function helloworld() は、コンストラクターで、ブロックチェーン上に配置される際に、一度だけ呼ばれます。新たに配置する際は、別のインスタンスを作成することになるので、新しいアドレスが割当てられます。
pragma solidity ^0.4.18;
contract helloworld {
string message;
function helloworld() public {
message = "Hello World!";
}
function setHelloworld(string _message) public {
message = _message;
}
function getHelloworld() constant returns (string) {
return message;
}
}
イーサリアムノードへの配置
では、イーサリアムノードに配置していきます。配置すると入っても、実際のイーサリアムノードではなく、TestRPCに配置します。TestRPCは、イーサリアムのエミュレーターでした。
テスト用エミュレーターの起動
実際のメインネットでは試すことが出来ないので、testRPCを立ち上げて、プライベートノードのエミュレーター上でスマートコントラクトをテストします。
$ testrpc
-bash: testrpc: command not found
testrpc と打ち込んで、上記のようなメッセージが出た場合、testrpc インストールされていないか、パスが通っていない可能性があります。
$ sudo npm install -g ethereumjs-testrpc
Password:
/usr/local/Cellar/node/8.9.1/bin/testrpc -> /usr/local/Cellar/node/8.9.1/lib/node_modules/ethereumjs-testrpc/build/cli.node.js
> fsevents@1.1.3 install /usr/local/Cellar/node/8.9.1/lib/node_modules/ethereumjs-testrpc/node_modules/fsevents
> node install
[fsevents] Success: "/usr/local/Cellar/node/8.9.1/lib/node_modules/ethereumjs-testrpc/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile
+ ethereumjs-testrpc@6.0.1
例えば、上記のように打ち込むと、インストール先がわかります。私の環境では、/usr/local/Cellar/node/8.9.1/bin/
にtestRPC
がインストールされていました。
● MacでPATHを通す際は、以下のエントリが参考になります。
MacでPATHを通す
自分は、.bash_profile
にパスを追記したくなかったので、少し面倒ですが、毎回打ち込むことにします。
$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ testrpc
EthereumJS TestRPC v6.0.1 (ganache-core: 2.0.0)
Available Accounts
==================
(0) 0x691fd5c90f2b79102d0262310db33ecbd22df62b
(1) 0xc3433b538dbce2fe9a5c2d0672fc0d2377b6b5d3
(2) 0x23b7e1e4c3f378154bd494908ef42dd0d6dab3b7
(3) 0x011d8641cd48371ec13bd00e5bd5f7e540c2afa6
(4) 0xd808a917657d6d5285f215b3bdb9a5eb282c4370
(5) 0x67adac0c3d9fe6db78bb326eab1aabd4e6138d46
(6) 0x06ca468857e64672b76ba1d4538e3ebf75e3c0b1
(7) 0x017fd6ca50e6eb86ab727580909dc64767346d65
(8) 0xe2f2254fe5fbca79a16eab3ef57b1de0aa0b841e
(9) 0x2c890ad8fbdcb0b24dad404596a7d566db4f7845
Private Keys
==================
(0) 3883239aa6bc3ce77c62be94a5c2bd371c8fc3cddb38a882bc92c0e8c716ecc3
(1) 3baa7861144e63a2e6e11d7f329995e5cfa3f98d3f386ff59a825b13a2188e7d
(2) d58fb5b2722633a94aa72121a37a14f08dee9db9e4e5f4312fe901b68aa37a24
(3) f8ba7e37429eb3c9090c66441d123e3f569c4a77f8ce07f2707c208628a50bf7
(4) c3bcac430d9355fdf34890793dd090b8eed3532ce621b58f6ed06c57d45d31c2
(5) d93d6ae585a42efb5062e0bfdf2f0d619e3a258f7c1dee5ef0c86a5a781a355d
(6) b5f6a46abae1449814ba2ef817959af3aa19b1d2a5459b128e1d6e470aa0b243
(7) 7650a62f412395051c58246c653ef0161b1e913e799336e59fff90d873daf77c
(8) a4fbcfa06bd7926a085027ebab7a79796caa3744a0d0df82be3da40fb4002e7b
(9) 2eeb0e27d283e830fa907d6e4319828200107f257c4e83a4c07ae8ab5cec9792
HD Wallet
==================
Mnemonic: summer glad second two coin mouse glad hero accuse manage wild have
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
このtestrpc コマンドは、イーサリウムのエミュレート環境を提供します。また、新規に10のアカウントを自動的に追記してくれます。
そしてそれぞれのアカウントに100ETH割り当ててくれます。また、イーサリアムのノードに wsで、localhost:8545 からアクセスすることが出来ます。
nodejsを使ってのコントラクト配置の準備とコンパイル
て、これでノードがスタートし、ノードに対して読み書きができるようになりました。先程作ったhelloworld
コントラクトをnodejs
を使ってコンパイルし、配置していきます。まず別のコンソールを立上げ、プロジェクトフォルダに移動します。node
コマンドを使って、nodejs
コンソールに移動します。
$ node
>
オブジェクトをインポートします。
> Web3 = require('web3')
{ [Function: Web3]
providers:
{ HttpProvider: [Function: HttpProvider],
IpcProvider: [Function: IpcProvider] } }
Web3オブジェクトのインスタンスを生成します。
> web3_instance = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
実際に作成されたかを確認してみましょう。
> web3_instance.eth.accounts
[ '0x691fd5c90f2b79102d0262310db33ecbd22df62b',
'0xc3433b538dbce2fe9a5c2d0672fc0d2377b6b5d3',
'0x23b7e1e4c3f378154bd494908ef42dd0d6dab3b7',
'0x011d8641cd48371ec13bd00e5bd5f7e540c2afa6',
'0xd808a917657d6d5285f215b3bdb9a5eb282c4370',
'0x67adac0c3d9fe6db78bb326eab1aabd4e6138d46',
'0x06ca468857e64672b76ba1d4538e3ebf75e3c0b1',
'0x017fd6ca50e6eb86ab727580909dc64767346d65',
'0xe2f2254fe5fbca79a16eab3ef57b1de0aa0b841e',
'0x2c890ad8fbdcb0b24dad404596a7d566db4f7845' ]
solidity のコンパイル
インスタンスが確認できたところで、solidityをコンパイルしていきます。
> solc = require('solc')
ソースコードを確認します。fs は、デフォルトで読み込まれているライブラリです。
sourceCode = fs.readFileSync('helloworld.sol').toString()
コンパイルします。
> compiledCode = solc.compile(sourceCode)
このコマンドを実行すると、bytecode
で示されるバイナリデータが含まれていますが、大きくは、json
ファイルであり、一見するとエラーのようですが、コンパイルは正常に通っています。このjson
データを使って、helloworld
コントラクトをデプロイしていきます。
まずは、ABI
を取得します。ABI
は、helloword
コントラクトとやり取りするためのAPI
のようなものですが、コントラクトでやり取りするので、API
とは分けて、ABI
というのが一般的なようです。
> contractABI = JSON.parse(compiledCode.contracts[':helloworld'].interface)
これで、オブジェクトを操作するためのABIが取得できました。次にデプロイする為のバイトコードを取得します。
> byteCode = compiledCode.contracts[':helloworld'].bytecode
これで、実際に動作する対象であるバイトコードと、バイトコードを操作するためのABIを取得できました。
次にデプロイするための、インスタンスを取得します。
> helloworldContract = web3_instance.eth.contract(contractABI)
Helloworldスマートコントラクトのデプロイ
前回のコマンドで、インスタンスが取得できたので、これをnew
でデプロイします。from:
で記載しているのは、accounts[0]からデプロイし、gas
のコストを支払うという意味のようです。(ここ少し曖昧です) これで、ブロックチェーン上のトランザクションにHelloworldコントラクトがデプロイされました。
> helloworldDeploy = helloworldContract.new({data: byteCode, from: web3_instance.eth.accounts[0], gas: 4700000})
testrpcを見てみると、このコマンドによる結果を見ることが出来ます。
HD Wallet
==================
Mnemonic: summer glad second two coin mouse glad hero accuse manage wild have
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
eth_accounts
eth_accounts
eth_sendTransaction
Transaction: 0x280ba1f31bd1aaaa4efd0b55e89a663d6bdfadfd05fa5ed2472782cba282ef1b
Contract created: 0xa3adb5859aa22874438aae3c4a3a1092f362d3fc
Gas usage: 284092
Block Number: 1
Block Time: Sat Nov 11 2017 21:02:59 GMT+0900 (JST)
eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
Contract created を見るとアドレスが記載されています。デプロイされたアドレスを確認したい場合は、以下のコマンドを打ち込みます。
> helloworldDeploy.address
次のこのコントラクトのアドレスから、コントラクトのインスタンスを取得してみます。このようにアドレスがわかればインスタンスを取得できます。
> helloworld_Instance = helloworldContract.at(helloworldDeploy.address)
Helloworldコントラクトに記載した関数を使ってみます。
> helloworld_Instance.getHelloworld()
'Hello world!'
トランザクションの発生
setHelloworld()
関数を使い、メッセージを変えてみます。ここでトランザクションが起動することになります。スマートコントラクトの核心的であり革新的な部分でもあります。関数を呼び出すことで、トランザクションフィーを発生させることが出来るのです。accounts[0] は、トランザクションフィーを支払って、setHelloworld
()関数を呼び出すこととになります。
> helloworld_Instance.setHelloworld("The world is changed", {from:web3_instance.eth.accounts[0]})
'0xb3b64dcb3aee87cc68d1c3de787e8934c3498a3f067e6ea719b423516ab29772'
トランザクションを特定するハッシュキーが発行されました。
testrpcを見てみると、トランザクションが追加されているのがわかります。
Transaction: 0xb3b64dcb3aee87cc68d1c3de787e8934c3498a3f067e6ea719b423516ab29772
Gas usage: 33352
Block Number: 2
Block Time: Sat Nov 11 2017 21:16:33 GMT+0900 (JST)
getHelloworld()関数を実行するとメッセージが変わっていることがわかります。
> helloworld_Instance.getHelloworld()
'The world is changed'