About
Ethereumブロックチェーンのnodeと簡単にやり取りするためのJavaScriptライブラリ Web3.jsに触ってみます。
しかし、ただ触るだけでは面白くないので、Reactと組み合わせてEtherScanもどきを作ってみました。しょぼいUIだとテンションが下がるので、ReactのコンポネントライブラリであるMaterial-UIを使用しています。
完成したデモアプリのソースはこちら。
https://github.com/kecbigmt/demo-react-web3
今回は、Reactアプリのテンプレを用意して、必要なパッケージをインストールして、Web3.jsで遊んでみるところまでやっていきましょう。Reactの実装は次回行います。
筆者の環境
- macOS High Sierra 10.13.4
- npm 5.8.0
- React.js 16.4.0
必要となる知識
- JavaScriptをある程度書ける
- Ethereumやブロックチェーンについてなんとなく知ってる
※Reactの実装は次回行います。
手順
- Reactアプリのテンプレやパッケージを用意する(1min)
- InfuraのAPI Keyを取得する(3min)
- Web3.jsで最新のブロック番号を取得する(3min)
- ブロックやトランザクションの中身を取得する(3min)
1. Reactアプリのテンプレやパッケージを用意する
※Reactには興味がなく、Web3.jsに触れればいいという方は好きなフォルダでnpm install --save web3
だけして、2. InfuraのAPI Keyを取得する に進んでください
create-react-appを使って、Reactアプリのテンプレを準備します。Terminalで下記のコマンドを実行するとdemo-react-web3という名前のフォルダが作成され、そのなかにReactアプリを動かすのに必要なパッケージや各種ファイルがダウンロードされます。
$ npm install -g create-react-app
$ create-react-app demo-react-web3
$ cd demo-react-web3
$ ls # 何が入ってるか見てみる
node_modules package.json package-lock.json public README.md src
この状態で、npm run start
を実行すると、http://localhost:3000 あたりでReactアプリが起動します。
あとは、React以外に追加で必要なパッケージを一気にインストールしましょう。Material-UI、Web3.js、WebSocket-Nodeを追加しています(最後のwebsocketは、どれかのパッケージの依存パッケージ。自動でインストールされなかったので手動で入れた)。
$ npm install --save @material-ui/core @material-ui/icons web3 websocket
本来はここからエクスプローラアプリをガシガシ作っていくのですが、その前にWeb3.js単体に少し触ってみましょう。全体からすると実はWeb3.jsをいじるコードは少なく、大半がReactによるUIの実装なので、先にサラッとWeb3.jsの使い方を見ておきます。
本命のReactの実装は次回詳しく見ていきます。
2. InfuraのAPI Keyを取得する
続いて、Web3.jsでEthereumネットワークにアクセスするために、InfuraのAPI Keyを取得します。
InfuraはConsensys社が提供しているサービスで、Ethereumネットワークのnodeにアクセスするためのエンドポイントを用意してくれています。node(ノード)とは、Ethereumネットワークの参加者のことです。
このネットワークに参加するためには、過去のブロックチェーンの全取引記録を保持していなければなりません。Ethereumの設計上、全取引記録を保持していないと「誰がいくら持っているか」などのネットワークの状態(state)を知ることができないからです。
以下の図のように、ネットワークの状態は取引ごとに更新されていきますが、状態自体はブロックチェーンに保存されていないので、ある時点での状態を知りたければその時点までの全取引記録を順番に処理する必要があるのです。
出典:https://github.com/ethereum/wiki/wiki/White-Paper
しかし、この「全取引記録」というのが厄介で、すべて保存しようとすると数十GBのディスク領域が必要です。しかもこれからどんどん増え続けていきます。興味本位でEthereumに触っている個人が気安く手を出せるものとは言い難いでしょう。
(ここで言っているのは「full node」のことで、実際はSPVという仕組みを使った軽量クライアントも利用できますが、これだとEthereumの全状態を把握することはできません)
**そこで役に立つのがInfuraです。**これを使えばConsenSys社が運用しているfull nodeを経由してEthereumのネットワークにアクセスすることができます。ということで、登録しましょう。
初見だと真っ赤な配色に不安感を煽られますが、とりあえず「GETTING STARTED FOR FREE」をクリックしてください。入力しなければならない情報は名前とメールアドレスくらいです。
入力を終えて「Submit」を押すと、登録したメールアドレス宛にAPI Keyが書かれたメールが届きます。https://mainnet.infura.io/XXXXXXXXXXXX
のXの部分がAPI Keyです。これを使ってWeb3.jsをいじります。
今回は念のため環境変数に格納します。コードをアップロードする予定がない場合はコードに直書きでも構いません。
$ export INFURA_API_KEY=XXXXXXXXXXXX
3. Web3.jsで最新のブロック番号を取得する
コードエディタを起動して、適当なjsファイルに以下を記述してdemo-react-web3フォルダ直下に配置しましょう。ファイル名は何でもいいですが、ここではdemo-web3.jsとします。
最終的なソースはgistにアップしています。
https://gist.github.com/kecbigmt/78cac16ac2d1c36ae2040288743e8b07
const Web3 = require('web3');
const INFURA_API_KEY = process.env.INFURA_API_KEY;
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/"+INFURA_API_KEY));
これでInfuraのnodeを経由してEthereumネットワークにアクセスするWeb3インスタンスが利用できるようになりました。続けてこう書いてみましょう。
// 最新のブロック番号を取得
web3.eth.getBlockNumber().then(result => {
console.log("last block number: ", result);
});
Terminalでnode demo-web3.js
を実行すると、最新のブロック番号が取得できるかと思います。
$ node demo-web3.js
last block number: 5686293
EtherScanの数字と同じになっていれば正常です。
4. ブロックやトランザクションの中身を取得する
続いて、ブロックの中身も見てみましょう。web3.eth.getBlock("latest")
で最新のブロックを取得できます。
// 最新のブロックの中身を取得
web3.eth.getBlock("latest").then(result => {
console.log("last block: ", result);
});
↑をdemo-web3.jsに追記して実行すると、↓のように最新のブロックの中身を確認することができます。
$ node demo-web3.js
last block: { difficulty: '3182414898839113',
extraData: '0x65746865726d696e652d657539',
gasLimit: 8000029,
gasUsed: 7995522,
hash: '0xd5ac738f2c6ed9c0a246ad5f5651b09b01ff33b5032c527d143542208606544b',
logsBloom: '0x116408080040d218d4208060400024108000981448188840604a0e00423ac1f80800110444316a06604c80010210041006004000031001c10708ca08000005000148004d4b25005108554909008820c214802000a00892e02031010850000112002a002a6686220100c0498080582b00c002008008054000268881759310008034420900c8508808000050400000048080e089504000305c00880400c0c02841383d860020522000554000109642c0002c080c8980078000120a805e0461010090120c02880422000069180290302444244a0105512c1448440840042000324040830340a8c20232402400c04140a10460063410880310044002104480804492',
miner: '0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8',
mixHash: '0xa5c3d12f5b1d50256947846faf208822a21204d5f21ecceae3a7f5de20437683',
nonce: '0x359bebe000545a4b',
number: 5686293,
parentHash: '0xd5d7530942569de79cae880699227f843fc109cd53ad3f0e3d9e809e0574a4af',
receiptsRoot: '0xb114d2eb0aa59348d8e8523baada7c0b2a38205b64fbb25e5e6e43ef29353b7b',
sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',
size: 29801,
stateRoot: '0x9ae7e576cdc666cb559a2a7a34278ae65cff3a1211b3a3b2bb18768309e46e18',
timestamp: 1527434514,
totalDifficulty: '4430817103927605509103',
transactions:
[ '0x5aafd796a0262ec4d04488538299560e539198153117be27b6d99bc7c667094c',
'0x77eb99bdd225114c7a7fa8f224dc5f7b63ae92ae4fe22eb7b15de119fb4e99aa',
'0x753f88bf4f5beb30b1485562357d943cc69d830ba2bed2b327e72b08bc114744',
'0x943ac77cc1b2f9426cf3c6dd1ec6f5245468d1e04def1bf72bd2a650cb3f0629',
'0x0db1a12183089de5d974476453b9f69962a1546c614ac54b8e6422c87fe78c57',
... ],
transactionsRoot: '0xf7fd108580b35fe42e03ca1820d2401505a72028df702f4aeaaf0f03d66c120c',
uncles: [] }
例えば以下のようなフィールドが見られます。
-
hash
: そのブロックをハッシュ化した文字列 -
parentHash
: そのブロックの一つ前のブロックをハッシュ化した文字列 -
timestamp
: そのブロックが作られたUNIX時間 -
miner
: そのブロックを作ったマイナーのアドレス
各フィールドの詳しい定義は、公式ドキュメントを見てみましょう。
https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgetblock
次はトランザクションの中身を見てみます。web3.eth.getTransaction()
を使いますが、引数にトランザクションのハッシュが必要になります。上のweb3.eth.getBlock("latest")
で取得したtransactions
のうちの一番目を使ってみましょう。
// 最新のブロックの最初のトランザクションを取得
web3.eth.getBlock("latest").then(result => {
const tx = result.transactions[0]; // 最新のブロックに含まれている最初のトランザクションのハッシュを使う
web3.eth.getTransaction(tx).then(result => {
console.log("last transaction: ", result);
});
});
↑のスクリプトを実行すると以下のような結果が出てきます。
$ node demo-web3.js
last transaction: { blockHash: '0xd5ac738f2c6ed9c0a246ad5f5651b09b01ff33b5032c527d143542208606544b',
blockNumber: 5686293,
from: '0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8',
gas: 50000,
gasPrice: '1000000000',
hash: '0x5aafd796a0262ec4d04488538299560e539198153117be27b6d99bc7c667094c',
input: '0x',
nonce: 10463202,
to: '0x094b718C2a6EBCF9168805c1B0F2D05E0B4d6007',
transactionIndex: 0,
value: '50111181091601229',
v: '0x25',
r: '0xb4701b98804765d217fc94fb4c887abc078d7e8477343744501ae8f35d530961',
s: '0x5082710bd184c0ff5d646f3d1a866dbbd1bb075e47dce8552d9d068fbf1f75fe' }
ざっくり以下のようなフィールドがあるのがわかります。
-
blockNumber
: そのトランザクションが記録されているブロックの番号 -
from
: そのトランザクションの送金主のアドレス -
to
: そのトランザクションの送り先のアドレス -
value
: そのトランザクションでの送金額
詳細はこちらで調べることができます。
https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethgettransaction
終わり
今回はここまで。次回は、Web3.jsをReactに組み込んでみて、エクスプローラのUIを完成させます!
References
- Infura の JSON RPC API で Ethereum アドレスの残高を取得(@hm0429さん) : https://qiita.com/hm0429/items/7d0321a62355ad513e1b
- Web3.js : https://github.com/ethereum/web3.js
- JavaScript API(ethereum/wiki): https://github.com/ethereum/wiki/wiki/JavaScript-API
- Material-UI : https://material-ui.com/