はじめに(Introduction)
Ethereumなど、EVM互換ブロックチェーンのネイティブトークンに対して、秘密鍵をもつアカウント(以下、EOA)のネイティブトークンに対する収入と支出を監視しようとした場合、案外複雑な手順が必要なので説明したいと思います。
輸入と支出トークン(In Out Token)
ネイティブトークンの収入と支出には以下の2つが考えられます。
- EOA ⇒ EOA
- EOA ⇒ コントラクトアカウント(CA) ⇒ EOA
トランザクションをノードに送付するには秘密鍵が必要な為、EOAからの送付となります。
支出トークン(Out Token)
ネイティブトークンの支出を見てみます。
手順として、まずはトランザクションデータを取得します。
トランザクションデータの取得には、単体であればeth_getTransactionByHash、eth_getTransactionByBlockNumberAndIndex、eth_getTransactionByBlockHashAndIndexなどから、複数であればeth_getBlockByHash、eth_getBlockByNumberの hydratedオプションを true にすることで取得できます。
以下がトランザクションデータのデータ構造となります。
※:トランザクションの形式により項目が増える場合もあります。
| field | type | description |
|---|---|---|
| blockHash | DATA 32バイト |
このトランザクションが組み込まれていたブロックのハッシュ 保留中の場合はnull |
| blockNumber | QUANTITY | このトランザクションが組み込まれていたブロックの番号 保留中の場合はnull |
| from | DATA 20バイト |
送信者のアドレス |
| gas | QUANTITY | 送信者が提供するガス |
| gasPrice | QUANTITY | 送信者が指定したガス価格(wei単位) |
| hash | DATA、32バイト | トランザクションのハッシュ |
| input | DATA | トランザクションと共に送信されるデータ |
| nonce | QUANTITY | 送信者がこのトランザクションより前に送信したトランザクションの数 |
| to | DATA 20バイト |
受信者のアドレス コントラクト作成時のトランザクションはnull |
| transactionIndex | QUANTITY | ブロック内のトランザクションのインデックスの位置(整数) 保留中の場合はnull |
| value | QUANTITY | 送金された価値(wei単位) |
| v | QUANTITY | ECDSAのリカバリID |
| r | QUANTITY | ECDSA署名 r
|
| s | QUANTITY | ECDSA署名 s
|
fromが対象のEOAであった場合、支出していることになります。
支出を計算する前に、このトランザクションレシートを取得します。
トランザクションレシートの取得方法は、eth_getTransactionReceipt を利用します。
以下がトランザクションレシートのデータ構造となります。
| field | type | description |
|---|---|---|
| transactionHash | DATA 32バイト |
トランザクションのハッシュ |
| transactionIndex | QUANTITY | ブロック内のトランザクションのインデックスの位置(整数) |
| blockHash | DATA 32バイト |
このトランザクションが組み込まれていたブロックのハッシュ |
| blockNumber | QUANTITY | このトランザクションが組み込まれていたブロックの番号 |
| from | DATA 20バイト |
送信者のアドレス |
| to | DATA 20バイト |
受信者のアドレス コントラクト作成時のトランザクションはnull |
| cumulativeGasUsed | QUANTITY | ブロック内でこのトランザクションの実行時に使用されたガスの総量 |
| effectiveGasPrice | QUANTITY | ガスユニットごとに支払われるベースフィーとチップの合計 |
| gasUsed | QUANTITY | この特定のトランザクションのみで使用されたガスの量 |
| contractAddress | DATA 20バイト |
コントラクト作成のトランザクションの場合は、作成されたコントラクト |
| logs | Array | このトランザクションが生成したログオブジェクトの配列 |
| logsBloom | DATA 256バイト |
関連ログを迅速に取得するためのライトクライアント用のブルームフィルター |
| type | QUANTITY | トランザクションタイプの整数、0x0でレガシートランザクション、 0x1でアクセスリストタイプ、0x2で動的手数料 |
| status | QUANTITY |
1(成功)、または 0(失敗) |
このデータから、statusを参照します。
1(成功) の場合
支出は、トランザクションデータから以下の式で求めることができます。
$ \mathrm{Out} = \mathrm{value} + \mathrm{gas} \times \mathrm{gasPrice} $
0(失敗) の場合
支出の値は、トランザクションデータから以下の式で求めることができます。
$ \mathrm{Out} = \mathrm{gas} \times \mathrm{gasPrice} $
収入トークン(In Token)
支出と同等に、トランザクションデータとトランザクションレシートを取得します。
以下の2つの場合でも収入の可能性はあります。
- トランザクションデータの
toが対象のEOAでない - トランザクションデータの
valueが0x0
トランザクションレシートの statusを参照します。
-
0(失敗) の場合
ネイティブトークンの収入はありません。 -
1(成功) の場合
ここからまた分岐します。
トランザクションデータのtoを参照します。-
toが対象のEOAだった場合
トランザクションデータのvalueが収入の値となります。 -
toが対象のEOAでない場合
インターナルトランザクションを調べる必要があります。
Gethの場合、debug_traceTransactionを利用してインターナルトランザクションを取得します。
パラメータは、トランザクションハッシュと callTracer({ tracer: 'callTracer' }) になります。-
各
call(サブcallが存在)を検索-
toが対象のEOAだった場合
callのvalueが収入の値となります。
-
-
各
-
以下がcallのデータ構造となります。
| field | type | description |
|---|---|---|
| type | string | CALL または CREATE |
| from | string | アドレス |
| to | string | アドレス |
| value | string | 16進数でエンコードされた価値転送量(value transfer) |
| gas | string |
call 用に提供された 16 進数でエンコードされたガス(gas) |
| gasUsed | string |
call 中に使用された 16 進数でエンコードされたガス |
| input | string |
call データ |
| output | string | リターンデータ |
| error | string | エラー(ある場合) |
| revertReason | string | Solidity の「revert」理由(ある場合) |
| calls | []callframe | サブcallのリスト |
インターナルトランザクション(Internal Transaction)
インターナルトランザクションの仕様を探したところ、イーサリアム仮想マシン(EVM)のドキュメントに記載されていました。
The calldata
calldata 領域は、スマート コントラクト トランザクションの一部としてトランザクションに送信されるデータです。
たとえば、コントラクトを作成する場合、calldata は新しいコントラクトのコンストラクター コードになります。
calldata は不変であり、CALLDATALOAD、CALLDATASIZE、および CALLDATACOPY 命令を使用して読み取ることができます。
コントラクトが xCALL 命令を実行すると、内部トランザクション(internal transaction)も作成されることに注意してください。
その結果、xCALL を実行すると、新しいコンテキストに calldata 領域が存在します。
まとめ(Conclusion)
EOAからの支出に関しては比較的簡単に見つけることが出来ます。
EOAへの収入に関しては、かなり複雑な手順が必要となります。
これは、コントラクトからEOAへの支出がEOAからの場合とコントラクトからの場合がある為です。
コントラクト内での呼び出しはインターナルトランザクションとして作成される為、これらをトレースする必要があります。
参考(References)
- トランザクションのデータ
https://ethereum.org/ja/developers/docs/apis/json-rpc/#eth_gettransactionbyhash - レシピのデータ
https://ethereum.org/ja/developers/docs/apis/json-rpc/#eth_gettransactionreceipt - インターナルトランザクション
https://www.evm.codes/about#calldata - debug_traceTransaction
https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction - callTracer
https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#call-tracer