ESP32を使ってEthereumのSmart Contractを操作する


はじめに

blockchainやEthereum・スマートコントラクトは、PCやサーバなど比較的高性能なコンピュータがないと操作できないと思っていました。しかし、いくつか調べてみると、ArduinoやESP32など比較的低スペックなマイコンからでも一部の操作は可能であるということがわかりました。
そこで、ArduinoからEthereumやEthereum上のスマートコントラクトを操作するためにweb3-arduinoというArduinoライブラリを作成しました。この記事では、その使い方を説明します。

  • web3-arduinoでできること

    • Arduino(というかESP32)のライブラリとして使用することができます。
    • Ethereum blockchainにデプロイ済みのスマートコントラクトを操作できます。
    • トランザクションをローカル(マイコン内)で署名できます。
  • web3-arduinoでできないこと

    • 採掘はできません。
    • スマートコントラクトの発行はできません(コンパイル・デプロイ済みのスマートコントラクトを叩くことのみできます)
    • Ethereumクライアントにはなれません。(現在はINFURA等のインターネット上のEthereum clientにjsonrpcを投げてます)

なお、web3-arduinoはEthereumのjavascript APIであるweb3.jsのサブセットを移植したものです。関数名等はweb3.jsに似た形にしているので、web3.jsのAPI仕様も合わせてご参照ください。

全体の流れ

  • 前提必要なもの
    • ESP32開発環境
    • Ethereumのアドレス(この記事ではRinkeby testnetを使用)
    • INFURAへのアクセスURL (自分でgethクライアントを立てる場合は不要)
  • 手順1 Ethereum上にスマートコントラクトをデプロイする
  • 手順2 Arduino IDEにweb3-arduinoをインストールする
  • 手順3 ESP32のコードを書き、ESP32からスマートコントラクトを操作できることを確認する

Ethereum上にスマートコントラクトをデプロイする

この作業はPCで行います。
まずは、Ethereum blockchain上にスマートコントラクトをデプロイします。スマートコントラクトの開発のためのツールはいくつかありますが、僕はフレームワーク等は使わずbrowser-soliditymetamaskで開発を行っています。はじめてこれらのツールを使う場合は、以下の記事がわかりやすかったです(英語ですが)
Learning Solidity Part 1: Contract Dev with MetaMask

デプロイしたソースコードはこちらです。超シンプルですね。。

pragma solidity ^0.4.18;

contract Sample {
    uint data;

    function set(uint d) public{
        data = d;
    }

    function get() public constant returns (uint retVal) {
        return data;
    }
}

なお、mainnetではなくRinkebyへデプロイしています。
コントラクトアドレスは 0x868d93bf82b9047574af4297d4af0fbaa012bbcd になりました。

Arduino IDEにweb3-arduinoをインストールする

web3-arduinoは、Arduinoから簡単にEthereum blockchainやスマートコントラクトを操作するためのArduinoライブラリです。動作確認はESP32でしか行っていませんが、他のArduino-likeな環境でも動くと思います(RAM/ROMが足りれば)。

ライブラリはこちらにあります。
web3-arduino

このページのClone or downloadボタンからソースコードのzipをダウンロードします。このzipをArduinoライブラリとして登録します。登録の方法は以下の記事が分かりやすかったです。
GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )

ESP32のコードを書き、ESP32からスマートコントラクトを操作できることを確認する

ESP32のコードを書きます。サンプルコード全体はgithubのexamples以下にあります。

まず、web3インスタンスを作成します。

#define INFURA_HOST "rinkeby.infura.io"
#define INFURA_PATH "/<YOUR_INFURA_ID>"

// インスタンスの生成。これを使ってEthereum blockchainへアクセスする
Web3 web3(INFURA_HOST, INFURA_PATH);

なお、ここではrinkeby.infura.ioをホストとして使用していますが、ローカルやAWS上にEthereumのクライアントを立てている場合はそちらを指すことも可能です。

接続できるかを試すために、Web3ClientVersion()を呼んでみます。うまく接続できればホストで使われるEthereumクライアントのバージョンが取得できるはずです。

web3.Web3ClientVersion(result);
USE_SERIAL.println(result); // Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9

次に、先程作成したスマートコントラクトに対して、Transactionを投げます。

// Contractを作成します。第二引数に先程作成したContractのアドレスを指定します。
Contract contract(&web3, CONTRACT_ADDRESS);

// 自分の秘密鍵をContractにセットします。
// この秘密鍵は、Transactionを署名するために使用されます。署名はESP32内で行われるため、この秘密鍵が外部へ送信されるというわけではありません。
contract.SetPrivateKey((uint8_t*)PRIVATE_KEY);

// sendTransactionに必要なデータをセットします。
// nonceは web3.EthGetTransactionCount() APIを使って取得します。
uint32_t nonceVal = (uint32_t)web3.EthGetTransactionCount((char *)MY_ADDRESS);
uint32_t gasPriceVal = 141006540;
uint32_t  gasLimitVal = 3000000;
uint8_t toStr[] = CONTRACT_ADDRESS;
uint8_t valueStr[] = "0x00";
uint8_t dataStr[100];
memset(dataStr, 0, 100);

// Contractに対して投げるバイナリデータを生成します。この場合「"set(uint256)"という関数を
// 第一引数が123という値を設定し呼び出す」場合のデータを作成します。
contract.SetupContractData((char*)dataStr, "set(uint256)", 123);

// Transactionを行います。
contract.SendTransaction((uint8_t *) result,
                         nonceVal, gasPriceVal, gasLimitVal, toStr, valueStr, dataStr);

正常に動作すれば、Blockchain上でtransactionが走ります。結果はEtherscan等で確認できます。

https://rinkeby.etherscan.io/tx/0xad29c8d9292bdb4477fd1a995789328bc838fda2c9b4e8cac96cb18826698db8
によると、正しく動作していたようです。

まとめとポエム

  • ESP32からINFURA経由でスマートコントラクトの操作ができました。
  • まだまだgasが高かったり遅かったりする理由で、EthereumスマートコントラクトとIoTの相性は良いわけではないと思いますが、各IoT機器がblockchainのアドレスを持って、通信し合う日に備えてweb3-arduinoもバグを直したり諸々整理していこうと思います。。
  • 次は単なるset(uint256)みたいなコントラクトではなく、DAppsに使えそうなコントラクトを書きたいです。