web3.js v1.0.0でEthereumのコントラクトを監視する方法

DAppsでフロントに表示しているデータとコントラクトのデータを同期させるため、コントラクトの状態監視をできないか調査してみました。

一定時間ごとにデータを取得することで実現はできるのですが、フロントの負荷やガス代を考慮して効率的な方法を選択することにします。

event修飾子を利用すれば簡単にいけると思い実装してみましたが、思いの外苦戦したので一連の流れをまとめておきます。

検証用DApps

前回紹介したチュートリアルDAppsにSetValueイベントを追加します。

DApps開発ギルドでデプロイしたアプリを1から作ってみる


pragma solidity ^0.4.23;

contract SimpleStore {
    string value;

    event SetValue(address from, string value);

    function set(string _value) public {
        value = _value;

        emit SetValue(msg.sender, _value);
    }

    function get() public view returns (string) {
        return (value);
    }
}

SetValueイベントは送信者アドレスと送信された値を記録するシンプルなイベントです。
set関数内でSetValueイベントを呼び出すようにします。

※emitキーワードはfunction実行とevent発火を区別するため、Solidity0.4.21から導入されました。

コントラクトを更新

コントラクトをデプロイし更新します。


$ truffle migrate --reset
Compiling ./contracts/SimpleStore.sol...
Writing artifacts to ./build/contracts

Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0x3d6ab4afbdbcf1d62479ce1f556f9c232b3e13b76eccf8d45b116a4d0e78c09a
  Migrations: 0xdca7ffd3ee28b1f3266a8793137cb4d04b817e29
Saving successful migration to network...
  ... 0xd4bfe57e75456a48a1afcfdbf4e09945e8d086935bb1445f7941a21ae12569c1
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Replacing SimpleStore...
  ... 0xdb75462f6e52730dae12c2f01296d0b7acfd53eb914f7303a06e7a900fc8ae21
  SimpleStore: 0x1e01faaff41ebced1840750a647811f326767072
Saving successful migration to network...
  ... 0x66a82562cd2f4ad196c10cc16ad747a5f1699d67e0d38ea6230a8f29479f7faf
Saving artifacts...

続いてフロント側のアドレスとABIを更新します。

  • src/index.html

var contractAddress = "0xdc30da2490373416e7bf7467bb297bae624287b4";
  • src/js/contract_abi.js

/build/contracts/SimpleStore.jsonの”abi”の値をコピペします。


var contractABI = [
  {
    anonymous: false,
    inputs: [
      {
        indexed: false,
        name: 'from',
        type: 'address'
      },
      {
        indexed: false,
        name: 'value',
        type: 'string'
      }
    ],
    name: 'SetValue',
    type: 'event'
  },
  {
    constant: false,
    inputs: [
      {
        name: '_value',
        type: 'string'
      }
    ],
    name: 'set',
    outputs: [],
    payable: false,
    stateMutability: 'nonpayable',
    type: 'function'
  },
  {
    constant: true,
    inputs: [],
    name: 'get',
    outputs: [
      {
        name: '',
        type: 'string'
      }
    ],
    payable: false,
    stateMutability: 'view',
    type: 'function'
  }
];

Cryptozombiesの処理を流用する

フロント側のevent取得処理に移ります。

CryptoZombiesのLESSON6で紹介されたevent取得処理を参考に実装してみましょう。
cryptozombie-lessons/jp/6/09.md | github.com

startApp関数の最後に下記コードを追加します。


contract.events.SetValue()
    .on("data", function (event) {
        let data = event.returnValues;
        console.log('Get event');
        console.log(data);
    })
    .on("error", console.error);

動作確認してみましょう。

set関数内のログは出力されますが、eventは取得できてないようです。

MetaMaskはeventを取得できない

ググって調査したところweb3.jsのv1.0.0では、MetaMaskでeventを取得することがまだできないようです。
Solidity and Web3.js | medium.com

The syntax we just described above is from the latest 1.0 release of Web3.js, which uses WebSockets to subscribe to events.
However, MetaMask doesn’t yet support the latest events API (although they’re actively working on it — check this github issue for updates) (MetaMask version 4.5.1)
So for now we’ll have to use a separate Web3 provider that supports WebSockets specifically for the events. We can use Infura to instantiate a second copy as follows:


var web3Infura = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
var czEvents = new web3Infura.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);

テストネットに繋がないと確認できないみたいですね。

テストネット(ropsten)にデプロイする

Infuraを使う必要があるので、Infuraにプロジェクトを作成します。
API KEYをコピーし、infura_access_tokenというファイルを新規作成しAPI KEYを貼り付けます。

次にMetamaskのmnemonicを取得し、mnemonicというファイルを新規作成し貼り付けます。

mnemonicの取得方法がわからない場合はこちらへ

MetaMaskから秘密鍵(ニーモニック)を取り出す | マキオネア

必要なライブラリをインストールします。


$ npm install truffle-hdwallet-provider

truffle.jsにテストネット(ropsten)の接続情報を追記します。


var { readFileSync } = require('fs');
var HDWalletProvider = require('truffle-hdwallet-provider');

var mnemonic = readFileSync('./mnemonic', 'utf-8').replace(/\r?\n/g, '');
var accessToken = readFileSync('./infura_access_token', 'utf-8');

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
  networks: {
    development: {
      host: '127.0.0.1',
      port: 8545,
      network_id: '*'
    },
    ropsten: {
      provider: function() {
        return new HDWalletProvider(
          mnemonic,
          'https://ropsten.infura.io/v3/' + accessToken
        );
      },
      //Ropsten gas limit is 4700000 (Truffle default gas limit is 4712388).
      gas: 4700000,
      network_id: 1
    }
  }
};

準備が整ったのでデプロイします。


$ truffle migrate --network ropsten --reset
Using network 'ropsten'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0x82a6d8ed87baf27c24aadc97bd592c48c90f720fc988be4683e703126c31d676
  Migrations: 0x5bf5e971e8ed63bd2ac95f548d547506b0db8568
Saving successful migration to network...
  ... 0xbc22ccc08533def5b14460beee87eebcf4709f1d93d3ef05043ca7bb7c11f0d3
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying SimpleStore...
  ... 0xa2b341b77ba332333c1b73f3e6aed473d3d59cd78224aa5eedaefb6559c56c32
  SimpleStore: 0x7f0855af33e8d66e20545b88d9043d93ff5029ab
Saving artifacts...

ガス代が足りない場合はエラーとなるので、足りない場合はropstenのetherを取得しましょう。
Ethereum testnet (Ropsten)のアカウントを作成して1 ETH取得する手順 | Qiita

成功したらコントラクトを更新と同じようにフロント側のアドレスとABIも更新します。

startApp関数の最後に追記したイベント取得処理を修正します。


const web3Infura = new Web3(new Web3.providers.WebsocketProvider("wss://ropsten.infura.io/ws"));
const czEvents = new web3Infura.eth.Contract(contractABI, contractAddress);
czEvents.events.SetValue()
.on("data", function (event) {
    let data = event.returnValues;
    console.log('Get event');
    console.log(data);
})
.on("error", console.error);

 

ブラウザのコンソールを確認するとWebSocketの接続でエラーになります。。。

web3.min.js:1 WebSocket connection to ‘wss://ropsten.infura.io/ws’ failed: Error during WebSocket handshake: Sent non-empty ‘Sec-WebSocket-Protocol’ header but no response was received

web3.jsのバージョン

どうやらweb3.jsのバージョンがv1.0.0-beta.34(もしくは35)の場合はエラーになるようです。
web3 Websocket connection to infura | StackExchange

v1.0.0-beta.33をダウンロードし直して試してみます。
Releases · ethereum/web3.js | github.com

ダウンロードしたら/src/js/web3.min.jsと入れ替えます。

動作確認してみましょう。

「Get event」と出力されたのでeventの取得が確認できました!

まとめ

2018年8月現在、コントラクトeventを監視するには

  • テストネット(Infura)のコントラクトを取得する
  • web3.js v1.0.0-beta33を利用する

ローカルネットワークで確認できないのはちょっと辛いですね。
今後改善されると思いますので、動向を見守りたいと思います。

ではまた。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

ABOUTこの記事をかいた人

Yusuke Ito

■25歳時に異業種からWeb系企業に転職、現在はフリーランスとして活動 ■バックエンド(PHP、Ruby、Python)がメイン、フロントエンドも少々 ※ECサイトの開発が得意 ■筋トレ、ゴルフ、読書、ポケモンが趣味 ■エンジニアとしてのキャリア、アプリ開発、書評をメインに発信しています。