NTTドコモ 新事業開発部の大月魁と申します。私は NTTドコモ の Software Engineer として、技術支援やアプリケーションの開発に携わっています。 その中で、分散システムに関わる研究活動を過去に行っていたこともあり、ブロックチェーン周りの技術支援にも携わっています。 本稿ではそのブロックチェーンについて、「Who owns the token?」というタイトルで執筆しました。1つ疑問文を導入に、本稿を始めたいと思います。
みなさんブロックチェーンと聞くと何を想像するでしょうか?
多くの人にとって、その回答は Ethereum であったり、その Ethereum 上で管理される NFT などのトークンになるのではないかと想像します。今回はそのトークンの所有者履歴の追跡を題材にして、Ethereum Virtual Machine(EVM)など少し深い Ethereum の話をしていければと思います。
トークンとERC1155
ブロックチェーンという分散管理されているデータベース上に記録されている電子的・暗号的な資産をトークンと呼びます。このトークンの有名な例の一つが NFT と呼ばれるものです。これは代替不可な、この世に二つとない何かをブロックチェーンの台帳上で管理しようという思想から生まれたトークンです。このトークンですが、実態はスマートコントラクトと呼ばれる EVM 上で実行できるバイトコードとして実装されます。そのため、NFT という概念をこのバイトコードという現実に落とし込むにはある種の繋ぎが必要です。この繋ぎの役割を果たすために、提案されている標準規格というのが”ERC”と呼ばれるトークン規格です。ERC はオブジェクト指向プログラミングにおけるインターフェースに相当する概念と考えてもらうといいでしょう。例えば、NFT という概念が持つべき振る舞いを定義するインターフェースが ERC721 と呼ばれる規格です。このほかにも、株式のような互いに交換可能な資産を表す Fungible Token(FT)のインターフェースとして提案された ERC20 など多くの規格が存在します。そのような数多くの規格の中で、近年よく耳にする規格が ERC1155 と呼ばれる規格です。これは NFT、FT のいずれとしても役割を果たすことができるスイスアーミーナイフのような規格です。その万能性から NFT マーケットプレイスなど多くの Dapps で利用されています。しかしながら、この ERC1155 も完全無欠というわけではなく、その万能性にはある種の不都合が伴います。ERC1155 トークンの所有者追跡は難しいのです。
なぜERC1155トークンの所有者を追うのは難しい?
平たく言うと、ERC1155 は"Multi-Token Standard”と呼ばれ一つのスマートコントラクトで複数のタイプのトークンを管理するために生み出された規格だからです(詳しくはこちら)。有名な標準規格である ERC20 や ERC721 は一つのスマートコントラクトが一種類のトークンを表します。対して ERC1155 における所有の概念は ERC1155 トークン内のある id について誰が何個保有しているのか、そのトークンは FT なのか NFT なのか、というように、複雑になるわけです(図 1)。そのため、ERC1155 は ERC721 と異なり、このトークンの所有権はこれですという関数(OwnerOf)などを定義することができず、所有者追跡のためには EVM のログの仕組みを理解しつつ送信イベント(TransferSingle や TransferBatch)を追跡・解読していく必要があると言う点で、ERC1155トークンの所有者追跡は困難と言えます。
EVM ログは、スマートコントラクト内のイベントとデータを記録するログです。世の他のアプリケーションと同様にいつ何がどこで起こったなどを捉えるために存在します。例えば、このログは開発者が自身で書いたスマートコントラクトのデバッグを行う、あるスマートコントラクトの処理の流れを理解するために利用されます。本稿では、表題の「Who Owns the Token?」という質問に答えるために、愚直に生の EVM ログ(以下ログ)を解析することで ERC1155 トークンの所有者履歴を追っていきみましょう。
EVM ログを理解する
まずは EVM ログを理解していきましょう。ログとは図 2 のようなハッシュ列が実態です(後でこちらのログを解析することで、トークンの譲渡履歴を解読していきます)。さてこのログですが、いったい何物で、どのような役割を果たすのでしょうか?ログとはスマートコントラクト内で起こった、イベント(スマートコントラクト実行中に起こったなにがしかの記録すべき内容)を分散的に出来事を記録する機構、もしくはその記録自体を指します。典型的なログの例としては、トークンの保有権の譲渡などのイベントが挙げられます。このログというのは、以下のようなステップで記録されます(コード 1 を参照)。
記録すべき何かしらのイベントを event [イベント定義]の形式で定義する。
このイベントを残したい場所で emit event の形式でログを残すことができる。このスマートコントラクトの該当の行が実行されるたびにイベントログが分散システム上の各実行ノードに保存される1。
上記のようなステップで発行されたログは ブロックチェーンの分散データベース 内に保存されます。ここで保存されたログはどのように使われるかというと
- 開発者が自身のコードのデバッグを行う、スマートコントラクトの実行手順を理解する、実行状態などを確認する
- 分析者が保有履歴などなにがしかの有用な情報を獲得する、Dapps 挙動について分析を行う
といったことが基本的に想定されます。
とはいえ、ログというのは、図 1 のような無機質なハッシュ列になっており、そのまま人が読むことができるという情報ではありません。次のチャプターではこちらを解析していくことで、本題である譲渡履歴を取り出すという目標を達成していきましょう。
コード 1: イベントを吐き出すコードの例
abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); ..... function _setOwner(address newOwner) private { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
EVM イベントログを解析してみよう
本節は下記の構成です。
- ログの集積
- ゴールの再確認
- ログの解析
- 結果
ログの集積
下記の 3 つのアプローチが一般的でしょう。
- ブロックチェーンノードから収集
- Alchemy, QuickNode などのノードプロバイダーを利用してアーカイブノード2を立てる。そのノードに対して、RPC のリクエストを行いログを取得する。自前でノードを立てることも可能だが、メンテナンスコストを考えると基本的にはノードプロバイダーに利がある。
- ブロックチェーンエクスプローラの活用
- Google BigQuery (BigQuery Public Datasets): 今回はこれを使う
- Google Cloud は、BigQuery を介してアクセス可能な公開データセットを提供している。この中には Ethereum を含むブロックチェーンのデータが含まれており、 SQL ライクなクエリを実行して、そのデータに対して詳細な分析を行うこともできる。
1, 2 もリアルタイム性やカスタム性を考えると非常に魅力的ですが、今回は簡易に分析するだけですので、利用に際して特にセットアップが必要ない 3 を採用します。
ゴールの再確認
今回のゴールはERC1155 準拠トークンの譲渡ログの解析です。
図 2 のような生のログからトークン譲渡の情報(図 3 参照)を取り出すことができれば完了です。
ちなみに、今回サンプルとして使用する図 2 のログですが、コード 2 のようなクエリを BigQuery 上で叩くことで取得することができます。
コード 2: 今回使うログの取得方法
SELECT log_index, transaction_hash, transaction_index, address, data, topics, block_number, block_hash FROM `bigquery-public-data.crypto_ethereum.logs` WHERE transaction_hash="0x35bab2d97bf1926f84842c50eb4ea2278e75b0cae4ef6285a1a97236d02e49cf"
ログの解析
ログは下記のような形式です。
- log_index
- 各ログエントリーに与えられる一意な識別番号。あるトランザクションの実行において発生したログのリストのインデックスである。
- transaction_hash
- そのログが発生したトランザクションのハッシュ値。トランザクションの識別に使用する。
- transaction_index
- ブロック内のトランザクションリストのインデックス。
- block_hash
- そのログが発生したトランザクションが含まれるブロックのハッシュ値。ブロックの識別に使用する。
- block_number
- 同ブロックの高さ。チェーン内の何番目のブロックであるかを指す。
- address
- そのログを出力したスマートコントラクトのアドレス。このフィールドにより、どのスマートコントラクトを実行している最中にそのログが発生したのか特定することができる。
- topics
- イベントに関する主要情報。典型的にはイベント識別情報とそのイベントに関する主要な変数の情報が含まれる。
- data
- data には topics に含まれていない、イベント情報(今回で言うと、uint256 _id, uint256 _value の二つ)が含まれる。これは topics に含めることのできる情報は 4 個までという制限があるためである。
今回の説明では topics が重要なのでこちらに焦点を置いて説明を進めていきましょう。
topics は最大で4 * 32 bytes のデータを含むことができるログの主要な要素です。topics は基本的にkeccak256によってハッシュ化されたイベント識別情報(例えば、TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value)
をハッシュ化した 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62 など)を第一要素に、第二要素以降にそのイベントや関数の実行に伴う引数の値が記録されます。引数のうちどの値が topics に含まれるのかは、そのスマートコントラクトが EVM 上で実行できるようにコンパイルされる際に、自動で生成される ABI encoding を参照することで知ることができます。ABI encoding は自動で生成されるものの、イベント定義に indexed の修飾子をつけることで開発者側が指定することができます。
したがって、今回の譲渡履歴のケースで言うと、TransferSingle
の ABI encoding を見るまでもなく定義を見ることで_operator, _from, _to の三つが topics に含まれていることがわかります。operator は今回のトランザクション発行者(すなわち今回の文脈においてはスマートコントラクトの実行起動者)、from は元の所有者、to は移動後の所有者を表すため、これらを解読できれば、operator 起動した操作の結果、 from から to のアドレスに ERC1155 トークンが移動するイベントが発生したことがわかります。ちなみに indexed されていない残りの情報(id, value)は data に含まれるのでこれを追跡することでどの id のトークンが value 個移動したということもわかります。
あとは、愚直にこれを解析して、譲渡履歴を取り出すという目的を達成しましょう。
今回のケース(図 2)では topics は次のようなハッシュ文字列を要素に持つ配列になっています。
1: 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62 2: 0x000000000000000000000000688b57bffcc894f214593b90a1b6947065868240 3: 0x000000000000000000000000688b57bffcc894f214593b90a1b6947065868240 4: 0x000000000000000000000000cbd8db297a6e54b692e1e74d755e970083cd1fbe
配列の要素を一つずつ解読していきましょう。
第一要素は先ほどお話ししたように、keccak256によってハッシュ化されたイベント識別情報です。0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62
は、event 定義から変数名や修飾子を除いた、TransferSingle(address,address,address,uint256,uint256)
をハッシュ化した値と一致することが確認できるため、このイベントが ERC1155 トークンの送信イベントであることが解読できました。蛇足ですが、実際に一致を確かめたい場合は、例えばこちらのサイトで先の文字列をハッシュ化することで確認できます。
第二要素です。こちらはオペレータで、今回はさして重要な情報ではないですが、解読しましょう。Ethereum ではアドレスは 20 bytes の英数字列になります。そのため、32 bytes を超えるデータではないため、topics の一要素のメモリ領域の最大値である 32 bytes を下回るため、ハッシュ化されずにそのまま格納されます。そして不足する 12bytes 領域はゼロ埋めされて格納されます。したがって、先頭 12 bytes 分の 0 を取り除くことで、688b57bffcc894f214593b90a1b6947065868240
というアドレスを取り出すことができます。
第三要素、第四要素も第二要素と同様の手順で解読することで、それぞれ688b57bffcc894f214593b90a1b6947065868240
とcbd8db297a6e54b692e1e74d755e970083cd1fbe
というアドレスを取り出すことができます。688b57bffcc894f214593b90a1b6947065868240
は送信者を、cbd8db297a6e54b692e1e74d755e970083cd1fbe
は受信者を指すわけです。
最後に「どのトークンが移譲対象か」ですが、これは愚直に address フィールドを見ることで知ることができます。今回のケースでは、0x8e30738d721f9c10623e2d9f102f21e32f7fad61
が対象のトークンということがわかります。
したがって、解読結果をまとめると688b57bffcc894f214593b90a1b6947065868240
からcbd8db297a6e54b692e1e74d755e970083cd1fbe
に0x8e30738d721f9c10623e2d9f102f21e32f7fad61
のトークンが譲渡されたことが解読できました。topics 解読と類似の手順で data フィールド解析すると具体的にどの id のアセットが value 個移動されたかということも解読できます。また、この情報にブロック高など時間情報を加えることでさらに譲渡履歴が有用な情報になるでしょう。
結果
結果としてログから ERC1155 準拠のトークンの譲渡履歴を一つ取り出すことができました。topics は EVM 上でログをフィルタリングするために使うフィールドなので、このフィールドをもとにして、同じ識別子を持つログという形で、ある特定のイベントのみを抽出することができます。具体的には、本稿の文脈で言うと、0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62
でフィルタリングをすると ERC1155 系のトークンの送信イベントだけを抽出することができます。これらの抽出ログについて本稿の手順を施すことによって EVM 系のブロックチェーンの全ERC1155トークンの譲渡履歴すなわち保有者履歴を抽出し、タイトルである「Who owns the token?」という質問に回答を与えることができるでしょう。
まとめ
本稿では、ERC1155 トークンの所有者を追跡する方法を入り口として、EVM ログをはじめとする Ethereum の裏側の世界についてお話ししました。ERC1155 は、複数のタイプのトークンを管理するための規格であり、その複雑な構造ゆえに所有者の追跡が通常困難です。しかし、EVM ログを活用することで、トークンの譲渡履歴を解析し、所有者を特定することが可能となります。また、 EVM ログなど普段ブロックチェーンを触わる上で、見ることのない裏側を知ることは、より立体的に Ethereum 等の EVM 系のブロックチェーンを理解する上で有効な手段になるかと思います。合意形成アルゴリズム、通信プロトコル、EVM といった Ethereum の裏側の世界も奥深く面白い世界なので、本稿で興味を持っていただけましたら幸いです。