OracleによるSmart Contractと外部サーバーとの連携

Oracle(DMBSじゃないほうの、Etereumの)で今後テストを行うために、最も単純化されたSmart ContractとOracleサーバーの連携アプリケーションを作ります。やることは極めてシンプルで、イベントで文字列を送り、Oracleが起動したsqliteのインメモリのDBにその文字列を保存します。

前提環境は以下の通りです。

Truffle v5.0.40 (core: 5.0.40)
Solidity - 0.5.8 (solc-js)
Node v10.16.2
Web3.js v1.2.1

プロジェクト環境()を以下の手順でセットアップします。

$ mkdir Oracletest
$ truffle init
$ npm init
$ npm install web3
$ npm install fs
$ npm install http

できた環境は、GitHubにアップロードしています。

Oracleに発行するイベントを実装したSmart Contractをcontractディレクトリ配下で作成します。GitHub上ではEmitOracle.solとしてアップロードしています。Oracle用に特別なEventがあるわけでなく、通常のSolidityのEventをそのまま使用します。

pragma solidity >=0.5.0 <0.6.0;

contract EmitOracle {
    event evHelloOracle(string _message);

    function helloOracle()
        external
    {
        bytes memory b = new bytes(20);
        for (uint i = 0; i < 20; i++)
            b[i] = byte(uint8(uint(msg.sender) / (2**(8*(19 - i)))));

        emit evHelloOracle(string(b));
    }
}

テストスクリプトtruffle test

$ truffle develop
truffle(develop)> test

で実行すると、下記のイベントが発行されることが見て取れます。

  logs:
   [ { logIndex: 0,
       transactionIndex: 0,
       transactionHash:
        '0x2125913cd387fd47655f37bfca81dc8a0a820376823518802debab8b913a9d32',
       blockHash:
        '0xe4d61ee9b10ed37625ebf03c14da61a3785988fb5c01be4e167eeea69974e702',
       blockNumber: 21,
       address: '0x0dd9Bcf9f1E7bC10D0AB84d1AEf620225b3ddAEA',
       type: 'mined',
       id: 'log_a2491510',
       event: 'evHelloOracle',

疑似Oracleサーバーを以下のスクリプトserver.jsで実行します。

import EmitOracle from '../../build/contracts/EmitOracle.json';
import Config from './config.json';
import Web3 from 'web3';
import express from 'express';

let config = Config['localhost'];
let web3 = new Web3(new Web3.providers.WebsocketProvider(config.url.replace('http', 'ws')));
let contract = new web3.eth.Contract(EmitOracle.abi, config.appAddress);
console.log(contract);

contract.events.evHelloOracle(
  {fromBlock: 0},
  function (error, event) {
    console.log("evHelloOracle")
    if (error) {
      console.log("error")
    }
    if (event) {
      console.log("event")
    }
  }
);

const app = express();
app.get('/api', (req, res) => {
    res.send({
      message: 'An API if you want.'
    })
})

export default app;

起動コマンドはnpm start serverでローカルサーバーのポート3000で起動するように、package.jsonに定義しています。サーバーを起動させた上で、あらためてTruffleからテストを行うと、サーバー側のコンソールに以下の出力が得られます。

Contract {
  currentProvider: [Getter/Setter],
  _requestManager:
   RequestManager {
     provider:
      WebsocketProvider {
        responseCallbacks: {},
        notificationCallbacks: [Array],
        _customTimeout: undefined,
        connection: [W3CWebSocket],
        connected: [Getter] },
     providers:
      { WebsocketProvider: [Function: WebsocketProvider],
        HttpProvider: [Function: HttpProvider],
        IpcProvider: [Function: IpcProvider] },
     subscriptions: {} },
  givenProvider: null,
  providers:
   { WebsocketProvider: [Function: WebsocketProvider],
     HttpProvider: [Function: HttpProvider],
     IpcProvider: [Function: IpcProvider] },
  _provider:
   WebsocketProvider {
     responseCallbacks: {},
     notificationCallbacks:
      [ [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification],
        [Function: requestManagerNotification] ],
     _customTimeout: undefined,
     connection:
      W3CWebSocket {
        _listeners: {},
        addEventListener: [Function: _addEventListener],
        removeEventListener: [Function: _removeEventListener],
        dispatchEvent: [Function: _dispatchEvent],
        _url: 'ws://localhost:8545',
        _readyState: 0,
        _protocol: undefined,
        _extensions: '',
        _bufferedAmount: 0,
        _binaryType: 'arraybuffer',
        _connection: undefined,
        _client: [WebSocketClient],
        onerror: [Function],
        onclose: [Function],
        onmessage: [Function] },
     connected: [Getter] },
  setProvider: [Function],
  BatchRequest: [Function: bound Batch],
  extend:
   { [Function: ex]
     formatters:
      { inputDefaultBlockNumberFormatter: [Function: inputDefaultBlockNumberFormatter],
        inputBlockNumberFormatter: [Function: inputBlockNumberFormatter],
        inputCallFormatter: [Function: inputCallFormatter],
        inputTransactionFormatter: [Function: inputTransactionFormatter],
        inputAddressFormatter: [Function: inputAddressFormatter],
        inputPostFormatter: [Function: inputPostFormatter],
        inputLogFormatter: [Function: inputLogFormatter],
        inputSignFormatter: [Function: inputSignFormatter],
        outputBigNumberFormatter: [Function: outputBigNumberFormatter],
        outputTransactionFormatter: [Function: outputTransactionFormatter],
        outputTransactionReceiptFormatter: [Function: outputTransactionReceiptFormatter],
        outputBlockFormatter: [Function: outputBlockFormatter],
        outputLogFormatter: [Function: outputLogFormatter],
        outputPostFormatter: [Function: outputPostFormatter],
        outputSyncingFormatter: [Function: outputSyncingFormatter] },
     utils:
      { _fireError: [Function: _fireError],
        _jsonInterfaceMethodToString: [Function: _jsonInterfaceMethodToString],
        _flattenTypes: [Function: _flattenTypes],
        randomHex: [Function: randomHex],
        _: [Function],
        BN: [Function],
        isBN: [Function: isBN],
        isBigNumber: [Function: isBigNumber],
        isHex: [Function: isHex],
        isHexStrict: [Function: isHexStrict],
        sha3: [Function],
        keccak256: [Function],
        soliditySha3: [Function: soliditySha3],
        isAddress: [Function: isAddress],
        checkAddressChecksum: [Function: checkAddressChecksum],
        toChecksumAddress: [Function: toChecksumAddress],
        toHex: [Function: toHex],
        toBN: [Function: toBN],
        bytesToHex: [Function: bytesToHex],
        hexToBytes: [Function: hexToBytes],
        hexToNumberString: [Function: hexToNumberString],
        hexToNumber: [Function: hexToNumber],
        toDecimal: [Function: hexToNumber],
        numberToHex: [Function: numberToHex],
        fromDecimal: [Function: numberToHex],
        hexToUtf8: [Function: hexToUtf8],
        hexToString: [Function: hexToUtf8],
        toUtf8: [Function: hexToUtf8],
        utf8ToHex: [Function: utf8ToHex],
        stringToHex: [Function: utf8ToHex],
        fromUtf8: [Function: utf8ToHex],
        hexToAscii: [Function: hexToAscii],
        toAscii: [Function: hexToAscii],
        asciiToHex: [Function: asciiToHex],
        fromAscii: [Function: asciiToHex],
        unitMap: [Object],
        toWei: [Function: toWei],
        fromWei: [Function: fromWei],
        padLeft: [Function: leftPad],
        leftPad: [Function: leftPad],
        padRight: [Function: rightPad],
        rightPad: [Function: rightPad],
        toTwosComplement: [Function: toTwosComplement] },
     Method: [Function: Method] },
  clearSubscriptions: [Function],
  options: { address: [Getter/Setter], jsonInterface: [Getter/Setter] },
  transactionBlockTimeout: 50,
  transactionConfirmationBlocks: 24,
  transactionPollingTimeout: 750,
  defaultChain: undefined,
  defaultHardfork: undefined,
  defaultCommon: undefined,
  defaultAccount: [Getter/Setter],
  defaultBlock: [Getter/Setter],
  methods:
   { helloOracle: [Function: bound _createTxObject],
     '0x3257848c': [Function: bound _createTxObject],
     'helloOracle()': [Function: bound _createTxObject] },
  events:
   { evHelloOracle: [Function: bound ],
     '0xdf40fa980c10bb6809b9f1f2be22593794d164d671481b037a8f7c3e0d34d8a4': [Function: bound ],
     'evHelloOracle(string)': [Function: bound ],
     allEvents: [Function: bound ] },
  _address: '0x0dd9Bcf9f1E7bC10D0AB84d1AEf620225b3ddAEA',
  _jsonInterface:
   [ { anonymous: false,
       inputs: [Array],
       name: 'evHelloOracle',
       type: 'event',
       constant: undefined,
       payable: undefined,
       signature:
        '0xdf40fa980c10bb6809b9f1f2be22593794d164d671481b037a8f7c3e0d34d8a4' },
     { constant: false,
       inputs: [],
       name: 'helloOracle',
       outputs: [],
       payable: false,
       stateMutability: 'nonpayable',
       type: 'function',
       signature: '0x3257848c' } ] }
[HMR] Updated modules:
[HMR]  - ./src/server/config.json
[HMR]  - ./src/server/server.js
[HMR] Update applied.
evHelloOracle
event

非常に簡単ですが、動きました。

アプリケーションのすべてをSmart Contractで実装するのは、コスト面や機能面で現実的ではなく、かなりの機能は外部に依存することになるでしょう。その際の外部サービスとの連携手段としてイベントとコールバックをを利用するのがOracleの基本となりますが、連携には気をつけなければならない点がいろいろあると感じています。

そういった懸念材料をテストするためのたたき台として、この環境を作ってみました。

コメント

  1. […] コードは以前の投稿OracleによるSmart Contractと外部サーバーとの連携を拡張して作ります。 […]

タイトルとURLをコピーしました