アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

Eventにつけるindexedの役割

ContractのEventの仕組み - アルゴリズムとかオーダーとかに引き続きContractのEvent関連のお話です。
Eventを定義する時にparameterにindexedというmodifierを付与する事ができます。今回はこのindexedの仕組みについてまとめてみました。

indexedの役割

indexedをEventのparameterに付与するとそのparameterでもEventを絞り込む事ができるようになります。

以下にContractとTruffle Testでの具体例を載せます。

pragma solidity ^0.4.18;

contract IndexedReceipt {
  event Deposit(address indexed _from, bytes32 indexed _id, uint _value);

  function deposit(bytes32 _id) public payable {
    // Any call to this function (even deeply nested) can
    // be detected from the JavaScript API by filtering
    // for `Deposit` to be called.
    Deposit(msg.sender, _id, msg.value);
  }
}

const util = require("ethereumjs-util")
const to32Hex = (num) => util.bufferToHex(util.setLengthLeft(num, 32))
const IndexedReceipt = artifacts.require('IndexedReceipt.sol')

contract('IndexedReceiptTest', accounts => {
  const mainAccount = accounts[0]
  const otherAccount = accounts[1]
  it("find by mainAccount events", async () => {
    const instance = await IndexedReceipt.new()
    const event = instance.Deposit({_from: mainAccount}, {fromBlock: 0, toBlock: 'latest'})
    await instance.deposit(to32Hex(1), {from: mainAccount})
    await instance.deposit(to32Hex(2), {from: otherAccount})
    await instance.deposit(to32Hex(3), {from: mainAccount})

    assert.equal(2, event.get().length)
  })
});

このように、Depositに対して、送り主のaddressでの絞り込み検索ができるようになります。
#TruffleContractからevent取得する時の最初の引数いつも{}指定してたけどこれ何か気になってた。indexed付与した時の追加の検索条件指定する時に使うのね


なぜindexedのついたparamで検索ができるようになるのか

答えはだいたい想像がつくと思いますが、indexedを付与すると、そのパラメータの値はTransactionReceipt内のlogsのtopicsパートに記録されるようになります。

{
  "transactionHash": "0xd909c1dc6fef3a673a195a9c775126cf61fc237c26915290c957e2cc617dd69b",
  "transactionIndex": 0,
  "blockHash": "0x82116e460de5a3eab43e5ea060224efbb260499e4a36acf8c68b4da15a8f6695",
  "blockNumber": 4,
  "gasUsed": 23451,
  "cumulativeGasUsed": 23451,
  "contractAddress": null,
  "logs": [
    {
      "logIndex": 0,
      "transactionIndex": 0,
      "transactionHash": "0xd909c1dc6fef3a673a195a9c775126cf61fc237c26915290c957e2cc617dd69b",
      "blockHash": "0x82116e460de5a3eab43e5ea060224efbb260499e4a36acf8c68b4da15a8f6695",
      "blockNumber": 4,
      "address": "0x345ca3e014aaf5dca488057592ee47305d9b3e10",
      "data": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "topics": [
        "0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f",
        "0x000000000000000000000000627306090abab3a6e1400e9345bc60c78a8bef57",
        "0x0000000000000000000000000000000000000000000000000000000000000001"
      ],
      "type": "mined"
    }
  ]
}

青字のdata部分にはindexedがついていないparameterが記載されます。今回の例では言えば_valueの値がここに記録されます。
赤字の部分が今回の本題となるtopicsパートです。ここには

  • Deposit Eventのsignature
  • _addressの値
  • _idの値

の順で記録されています。

topicsパートにindexedを付与したparameterの値が追加されるようになるため、filterから検索ができるようになります。


indexed parameterを検索条件に指定した時のfilterの状態

topicsindexedを付与したparameterの値が追加されている事がわかったので、次は今回のサンプルコードで生成したfilterがどのような状態になっているのか確認してみます。

instance.Deposit({_from: mainAccount}, {fromBlock: 0, toBlock: 'latest'})で生成されたfilterの状態はこのようになっていました。

Filter {
  requestManager: 
   RequestManager {
     provider: Provider { provider: [Object] },
     polls: {},
     timeout: null },
  options: 
   { topics: 
      [ '0x19dacbf83c5de6658e14cbf7bcae5c15eca2eedecf1c66fbca928e4d351bea0f',
        '0x000000000000000000000000627306090abab3a6e1400e9345bc60c78a8bef57',
        null ],
     from: undefined,
     to: undefined,
     address: '0x345ca3e014aaf5dca488057592ee47305d9b3e10',
     fromBlock: '0x0',
     toBlock: 'latest' },
  implementation: 
   { newFilter: { [Function: send] request: [Function: bound ], call: [Function: newFilterCall] },
     uninstallFilter: { [Function: send] request: [Function: bound ], call: 'eth_uninstallFilter' },
     getLogs: { [Function: send] request: [Function: bound ], call: 'eth_getFilterLogs' },
     poll: { [Function: send] request: [Function: bound ], call: 'eth_getFilterChanges' } },
  filterId: '0x03',
  callbacks: ,
  getLogsCallbacks: ,
  pollFilters: [],
  formatter: [Function: bound ] }

topicsにはTransactionReceiptのlogsと同じように

  • Deposit Eventのsignature
  • _addressの検索条件
  • _idの検索条件

の順に設定されています。今回は_idの検索条件を設定していないので該当箇所がnullになってますね。

ちなみに、全然関係のないparameterもしくはindexedのついていないparameterを検索条件に指定しても無視されます。例えば、

instance.Deposit({_from: mainAccount, _value: 10}, {fromBlock: 0, toBlock: 'latest'})

なんて書いても、生成されるfilterには_value: 10はfilterのtopicsには追加されません。(この判定はweb3.jsさんがabiを見てやってくれてるみたいです。)


まとめ

  • indexedをつけるとさらにそのparameterの値で絞り込み検索ができるようになる
  • indexedをつけるとそのparameterの値がTransactionReceiptのlog.topicsに記録されるようになる
  • TransactionReceiptのlog.topicsに値が追加されるため、filterでさらに条件として追加できるようになる

という感じですね。
余談ですが、自前でfilterを設定すれば、signature部分をnullにすることで複数のイベントに対して共通の条件で検索とかもできそうですね。ただその場合は各Eventのparameterの位置を調整する必要がありますが。。。