1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Explorer(XDCScan)は何を表示しているの?

Last updated at Posted at 2024-11-02

ブロックチェーンのトランザクションを確認するのに欠かせないExplorer。
XDCネットワークだとXDCScanになるけど、正直、何書いてあるかよくわからない。

ブロックチェーンを理解する上でも、ブロックチェーンに何が記録されているのか、実際に記録されたデータはどのように格納されて、どうやって情報を得られるのか、Explorerと同じ値が自分で取れたら分かるんじゃないの?という軽いノリでやってみます。

前提

  • 所詮素人。間違いはいっぱいあると思う。
  • 同じブロックチェーン上のアドレスでも、ウォレット(EOA)とコントラクトは似て非なるもの(個人的感想)で、コントラクトの方が扱いやすい(と思っている)ので、今回の題材はXDCネットワークのネイティブオラクルであるPluginのデータフィード用のOracle Contract Address(OCA)をベースとする。
  • サンプルとしてOCAのトランザクションを表示させるので、Plugiin Nodeをしていない人には同じ作業はしんどいかも。
  • やったことは結果論であって、最適解かどうかはわからない。

ゴール

XDCScanと同じ情報を自力で取ってくる!

XDCScan

Overview

スクリーンショット 2024-11-02 19.11.11.png

Log

スクリーンショット 2024-11-02 19.11.38.png

これらの値が自分で取ってこれるようになると、ブロックチェーンに記録された情報がきっと今より読めるようになると思う。

環境準備

Nodejsを使っていくので、まずは環境構築。

Nodejs環境セットアップ

nvmインストール
cd ~
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
環境設定反映
source ~/.nvm/nvm.sh
Nodejsインストール(このバージョンはPlugin Nodeに合わせている)
nvm install 18.12.0
nvm use 18.12.0
node --version

作業ディレクトリ作成

ディレクトリ名はなんでもOK
mkdir explorer_test
cd explorer_test

必要なNodejsパッケージインストール

npm install xdc3 readline-sync abi-decoder

xdc3 : ブロックチェーンと対話するためのライブラリ。web3.jsのV1からフォークされている。web3.jsでも問題ないが、最新のものは少しロジックの書き方が違うので注意。
readline-sync : ターミナルからの入力を受け取るライブラリ。プログラムを対話式で動かせる。
abi-decoder : Consensys(Metamaskの開発元)が提供しているブロックチェーンにハッシュ化されて記録されているデータを復元化してくれるライブラリ(超便利)

まずはOverview

まず、何をもとにデータを拾ってくるか・・・
一番手っ取り早いのはTransaction Hash(TxHash/TxID)。
TxHashはブロックチェーン上のトランザクションを識別する一意の番号。
特定の一意のデータを取得できるため、検証にはもってこい。

OCAの履歴からTransaction Hashを1つ決める

スクリーンショット 2024-10-31 19.32.48.png

Transaction情報を見てみる

まずはプログラムを作る

nano explorer_1.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_1.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    //explorer_1 表示してみる
    console.log(tx);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる

node explorer_1.js

答え合わせ1
スクリーンショット 2024-10-31 19.58.52.png

スクリーンショット 2024-10-31 20.13.54.png
7つの情報を得ることができた。

答え合わせ2
スクリーンショット 2024-10-31 21.30.36.png
スクリーンショット 2024-10-31 21.36.44.png
5つの情報を得ることができた。

(まとめ)Transactionの詳細を見ると何がわかるか

これを使えば詳細情報を取得できる
eth.getTransaction(TxHash)

Transaction Hashをキーとして、トランザクションの詳細情報を確認できる。

  • Transaction Hash:トランザクションハッシュ。トランザクションごとに一意に振られるID
  • Method:実行されたトランザクションメソッドのシグネチャ(署名)←Input Dataの頭10文字(0x + 8バイト)
  • Block:トランザクションが格納されているブロック番号
  • from:トランザクションの送信アドレス(今回だとOCAにフィード値を送り込んだNode Address)
  • to:トランザクションの受信アドレス(今回だとOCA)
  • value:送金されたネイティブトークン(XDC)
  • Gas Price:ガスプライス(XDC2.0になったので、12.5Gweiに設定されている)
  • Gas Limit:トランザクションガスリミット。Contractを実行する際、消費する最大ガス量。Contract実行時にこれ以上のガスを消費する場合、エラーになる。※これとは別にブロックガスリミットもある。これはBlockに格納されるTransaction全体の最大ガス量。ブロックガスリミットを超えない分だけのTransactionが1つのBlockに収まる
  • Txn Type:トランザクションタイプ。ややこしそうなので割愛。EIP1559で検索すると良いかも
  • Nonce:そのアドレスで実行されたトランザクションの総数
  • Position:(=transactionIndex)同じブロック内に格納されたトランザクションの位置(何番目のトランザクションか)
  • Input Data:トランザクションで送信されたデータ

ここまででまだ見つけられていない情報

スクリーンショット 2024-11-01 22.40.38.png

  • Timestamp:トランザクションが生成された日時
  • Transaction Action:トランザクションの処理(関数名)
  • Usage by Txn:トランザクションで使用されたガス量

Block情報を見てみる

BlockはTransactionを束ねたもの。複数のTransactionをBlockの単位でブロックチェーンに記録する。

まずはプログラムを作る

nano explorer_2.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_2.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //explorer_2 表示してみる
    console.log(block);

    //timestampの答え合わせ
    const blockDate = new Date(block.timestamp * 1000);
    console.log('timestamp: ' + blockDate);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる

node explorer_2.js

答え合わせ
スクリーンショット 2024-10-31 23.08.39.png
スクリーンショット 2024-10-31 23.17.50.png
UTCの違いはあるが、同じ時間を取得できた。
XDCScanに表示されているのは、ブロックが生成された時間。

Transaction Receipt情報を見てみる

トランザクションレシートはブロックチェーンにおけるトランザクションの実行結果を記録したもの。

まずはプログラムを作る

nano explorer_3.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_3.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_3 表示してみる
    console.log(txrec);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる

node explorer_3.js

答え合わせ
スクリーンショット 2024-11-01 21.58.36.png
スクリーンショット 2024-11-01 21.59.57.png

スクリーンショット 2024-11-01 22.19.30.png

念の為確認
①Status
トランザクションエラーでの確認
スクリーンショット 2024-11-01 22.28.42.png
スクリーンショット 2024-11-01 22.03.18.png
正解。
Status : true はXDCScanではSuccess
Status : false はXDCScanではFailed

②Usage by Txn
違うトランザクションハッシュを確認。
スクリーンショット 2024-11-01 22.17.10.png
スクリーンショット 2024-11-01 22.16.55.png
正解。
XDCScanに表示されるのはgasUsed
cumulativeGasUsedではない。

cumulativeGasUsedこのトランザクションに関連する処理全体で使われたGasの量だそうです。ものによってはInternal Transactionなど関連するトランザクションも含めた総量らしいです。もう少し勉強が必要なようです。

ここまでで、Overviewのほとんどの情報が取得できました。
まだ得られていない情報はあと1つ
Transaction Action:トランザクションの処理(関数名)
スクリーンショット 2024-11-01 22.46.04.png
いったんこれは後で、Logsの方へ移ります。

次にLogs

実はgetTransactionReceiptではLogsも取得できています。

スクリーンショット 2024-11-01 22.53.49.png

スクリーンショット 2024-11-01 22.56.10.png

次に少し編集して見やすくしてみます。

まずはプログラムを作る

nano explorer_4.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_4.js
const Xdc3 = require("xdc3");

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_4 logだけ表示してみる
    txrec.logs.forEach((log) => {
        console.log(`Index: ${log.logIndex}`);
        console.log(`  Address: ${log.address}`);
        console.log(`  Topics:`);
        log.topics.forEach((topic, index) => {
            console.log(`    [${index}] ${topic}`);
        });
        console.log(`  Data(Hex): ${log.data}`);
    });
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる

node explorer_4.js

スクリーンショット 2024-11-02 20.01.07.png
スクリーンショット 2024-11-02 20.01.31.png

かなり見やすくなったと思います。
でも、ここでもやっぱりName(関数名っぽい)が取得できません。

さて、関数名はどうやって取得するか

ここで使うのが、abi-decoderです。
実は、関数名はすでに取得した情報の中に含まれていました。

やっぱりまずはプログラムを作ってみます。

まずはプログラムを作る(Overview)

今回は事前準備があります。
abi-decoderを使うためには、ContractのABI(Application Binary Interface)が必要になります。
ABIについてはググってくださいm(_ _)m
ABIはどこで手に入れるか。
今回の検証ではPluginのOCAをベースにしています。
RemixでOCA(Operator.sol)をコンパイルするところでGetできます。
スクリーンショット 2024-11-02 0.59.31.png
これをクリックしてコピーしたものを使います。

mkdir ABIs
nano ABIs/Operator.json

RemixでコピーしたABIを貼り付けてください。

同じもの
ABIs/Operator.json
[
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "pli",
				"type": "address"
			},
			{
				"internalType": "address",
				"name": "owner",
				"type": "address"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "changedBy",
				"type": "address"
			}
		],
		"name": "AuthorizedSendersChanged",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"name": "CancelOracleRequest",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "requester",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "callbackAddr",
				"type": "address"
			},
			{
				"indexed": false,
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "cancelExpiration",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"indexed": false,
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "OracleRequest",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"name": "OracleResponse",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "acceptedContract",
				"type": "address"
			}
		],
		"name": "OwnableContractAccepted",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			},
			{
				"indexed": false,
				"internalType": "address",
				"name": "changedBy",
				"type": "address"
			}
		],
		"name": "TargetsUpdatedAuthorizedSenders",
		"type": "event"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "acceptAuthorizedReceivers",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "ownable",
				"type": "address[]"
			}
		],
		"name": "acceptOwnableContracts",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "acceptOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunc",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			}
		],
		"name": "cancelOracleRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunc",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			}
		],
		"name": "cancelOracleRequestByRequester",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address payable[]",
				"name": "receivers",
				"type": "address[]"
			},
			{
				"internalType": "uint256[]",
				"name": "amounts",
				"type": "uint256[]"
			}
		],
		"name": "distributeFunds",
		"outputs": [],
		"stateMutability": "payable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "data",
				"type": "bytes32"
			}
		],
		"name": "fulfillOracleRequest",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "expiration",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "fulfillOracleRequest2",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getAuthorizedSenders",
		"outputs": [
			{
				"internalType": "address[]",
				"name": "",
				"type": "address[]"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getExpiryTime",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "getPluginToken",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			}
		],
		"name": "isAuthorizedSender",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "amount",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "onTokenTransfer",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "operatorRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "sender",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "payment",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "specId",
				"type": "bytes32"
			},
			{
				"internalType": "address",
				"name": "callbackAddress",
				"type": "address"
			},
			{
				"internalType": "bytes4",
				"name": "callbackFunctionId",
				"type": "bytes4"
			},
			{
				"internalType": "uint256",
				"name": "nonce",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "dataVersion",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "oracleRequest",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "ownerForward",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "value",
				"type": "uint256"
			},
			{
				"internalType": "bytes",
				"name": "data",
				"type": "bytes"
			}
		],
		"name": "ownerTransferAndCall",
		"outputs": [
			{
				"internalType": "bool",
				"name": "success",
				"type": "bool"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "setAuthorizedSenders",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "targets",
				"type": "address[]"
			},
			{
				"internalType": "address[]",
				"name": "senders",
				"type": "address[]"
			}
		],
		"name": "setAuthorizedSendersOn",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address[]",
				"name": "ownable",
				"type": "address[]"
			},
			{
				"internalType": "address",
				"name": "newOwner",
				"type": "address"
			}
		],
		"name": "transferOwnableContracts",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "transferOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "typeAndVersion",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "pure",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "recipient",
				"type": "address"
			},
			{
				"internalType": "uint256",
				"name": "amount",
				"type": "uint256"
			}
		],
		"name": "withdraw",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "withdrawable",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
]
nano explorer_5.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_5.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);

    //explorer_5 inputだけ表示してみる
    console.log('[Input]');
    console.log(tx.input);

    console.log();

    //Inputをデコード(復号化)
    const decodeInput = abiDecoder.decodeMethod(tx.input);
    console.log('[Decoded Input]');
    console.log(decodeInput);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる(Overview)

node explorer_5.js

スクリーンショット 2024-11-02 1.20.02.png
スクリーンショット 2024-11-02 1.25.18.png

不思議ですが、16進数のデータをabi-decoderを通すと、全く別のものが出てきました。
これでOverviewの全ての値を取得することができました。

次はLogsです。

まずはプログラムを作る(Logs)

nano explorer_6.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_6.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_6 Logsだけ表示してみる
    console.log('[Logs]');
    console.log(txrec.logs);

    console.log();

    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    console.log(decodeLogs);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる(Logs)

node explorer_6.js

スクリーンショット 2024-11-02 8.13.44.png
スクリーンショット 2024-11-02 8.13.59.png

スクリーンショット 2024-11-02 8.16.57.png

3つのLogのうち1つ目しか取得できませんでした。

それぞれのLogのAddressを見てみると・・・
1つ目のAddressは今まで追いかけてきたOCAのものです。
2つ目、3つ目は・・・Oracleにリクエストを出してきたAPIConsumerのコントラクトアドレスでした。
つまり、APIConsumerABIがあればいけそう?

プログラムを作る(リベンジ)

じゃあ、サクッとABI取ってきちゃいます。

スクリーンショット 2024-11-02 8.43.56.png

nano ABIs/APIConsumer.json

RemixでコピーしたABIを貼り付けてください。

同じもの
ABIs/APIConsumer.json
[
	{
		"inputs": [],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkCancelled",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkFulfilled",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "id",
				"type": "bytes32"
			}
		],
		"name": "ChainlinkRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferRequested",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "address",
				"name": "from",
				"type": "address"
			},
			{
				"indexed": true,
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "OwnershipTransferred",
		"type": "event"
	},
	{
		"anonymous": false,
		"inputs": [
			{
				"indexed": true,
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			},
			{
				"indexed": false,
				"internalType": "uint256",
				"name": "volume",
				"type": "uint256"
			}
		],
		"name": "RequestVolume",
		"type": "event"
	},
	{
		"inputs": [],
		"name": "acceptOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "_requestId",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "_volume",
				"type": "uint256"
			}
		],
		"name": "fulfill",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"internalType": "address",
				"name": "",
				"type": "address"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "requestVolumeData",
		"outputs": [
			{
				"internalType": "bytes32",
				"name": "requestId",
				"type": "bytes32"
			}
		],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "address",
				"name": "to",
				"type": "address"
			}
		],
		"name": "transferOwnership",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "volume",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "withdrawPli",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	}
]
nano explorer_6-2.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_6-2.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット(追加)
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //explorer_6 Logsだけ表示してみる
    console.log('[Logs]');
    console.log(txrec.logs);

    console.log();

    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    console.log(decodeLogs);
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる(リベンジ)

修正はAPIConsumerのABIを読み込ませただけですが、どうでしょうか。

node explorer_6-2.js

スクリーンショット 2024-11-02 9.00.40.png

もう少し整形してみる(自己満)

nano explorer_6-3.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_6-3.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット(追加)
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //整形
    console.log('[Decoded Logs]');
    txrec.logs.forEach(log => {
        console.log(`[${log.logIndex}]`);
        console.log(`  Address: ${log.address}`);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }
        });
        functionName = functionName + ')';
        console.log(`  Name   : ${functionName}`);
        console.log(`  Topics : `);
        log.topics.forEach((topic, index) => {
            console.log(`      [${index}] ${topic}`);
        });
        console.log(`  Data   : `);
        console.log(`    [Hex] ${log.data}`);
    });
}

main();

保存は Ctrl+xの後、yキー押下

実行してみる

node explorer_6-3.js

スクリーンショット 2024-11-02 17.34.01.png
スクリーンショット 2024-11-02 17.34.40.png

キレイに出力できました。
でも、Dataはハッシュ化された値です。

色々と触ったけど、デコード(Dec)された値を表示するには、方法は他にもあるのだろうけど、最後に紹介します。(たぶん邪道)

最終形

nano explorer_fin.js

プログラムの中身。
※TODOの部分を書き換えてください。

explorer_fin.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //編集
    //timestamp
    const blockDate = new Date(block.timestamp * 1000);
    const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
    const decodeInput = abiDecoder.decodeMethod(tx.input);

    //出力
    //Overview
    console.log(RED + '[Overview]' + RESET);
    console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
    console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
    console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
    console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
    console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
    console.log(YELLOW + '  From:                     ' + RESET + tx.from);
    console.log(YELLOW + '  To:                       ' + RESET + tx.to);
    console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
    console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
    console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
    console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
    console.log(YELLOW + '  Other Attributes:         ' + RESET);
    console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
    console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
    console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
    console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

    //Logs
    console.log(RED + '[Logs]' + RESET);
    txrec.logs.forEach(log => {
        console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
        console.log(YELLOW + '    Address: ' + RESET + log.address);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }
        });
        functionName = functionName + ')';
        console.log(YELLOW + '    Name   : ' + RESET + functionName);
        console.log(YELLOW + '    Topics : ' + RESET);
        log.topics.forEach((topic, index) => {
            console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
        });
        console.log(YELLOW + '    Data   : ' + RESET);
        console.log(YELLOW + '      [Hex] ' + RESET + log.data);
    });

}
main();

保存は Ctrl+xの後、yキー押下

動かしてみる

node explorer_fin.js

スクリーンショット 2024-11-02 19.01.36.png
スクリーンショット 2024-11-02 19.09.41.png
スクリーンショット 2024-11-02 19.11.11.png
スクリーンショット 2024-11-02 19.11.38.png

満足!!

もうちょっと・・・

やっぱりログの値もデコードしたものを表示したい。

色々と調べていると、

  • Topicsイベントに関連付けられたインデックス付きパラメータである
  • DataイベントのABIエンコードされた非インデックスパラメータを指す

そもそも、スマートコントラクトはsolidityという言語でプログラムされ、ログはそのプログラムの中で出力されるように設計されている。
(ここらへん、もっと勉強しないとちゃんと説明できない。)

Remix
スクリーンショット 2024-11-02 20.24.10.png
スクリーンショット 2024-11-02 20.30.30.png

つまり、emitされることにより、↓このログが残る
スクリーンショット 2024-11-02 20.45.35.png

少し脱線したけど、Dataに格納されるのは非インデックスパラメータが重要。
デコードしたログをもう少し見てみる。

nano explorer_6-4.js

explorer_6-2.jsを少し編集したもの

explorer_6-4.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット(追加)
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);

    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);
    console.log('[Decoded Logs]');
    decodeLogs.forEach(log => {
        console.log(log);
    });
}

main();

スクリーンショット 2024-11-02 21.34.30.png

邪道な方法で直していきます。

エンコードされたDataを表示する

nano -l node_modules/abi-decoder/index.js

161行目に下記を追加します。

161行目
          indexed: param.indexed,

スクリーンショット 2024-11-02 21.41.44.png

nano explorer_fin_mod.js

プログラムの中身。
※TODOの部分を書き換えてください。

nano explorer_fin_mod.js
const Xdc3 = require("xdc3");
//abi-decoderをインポート
const abiDecoder = require('abi-decoder');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";
const TXHASH = "YOUR_TRANSACTION_HASH" //TODO ご自身で取得したTxHashを入力する

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {

    // 接続先のノードを設定
    const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
    // TxHashからトランザクションを取得
    const tx = await xdc3.eth.getTransaction(TXHASH);
    // TxHashからトランザクションレシートを取得
    const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
    // TxHashが格納されているブロックの情報を取得
    const block = await xdc3.eth.getBlock(tx.blockNumber);

    //Logsをデコード(復号化)
    const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

    //編集
    //timestamp
    const blockDate = new Date(block.timestamp * 1000);
    const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
    const decodeInput = abiDecoder.decodeMethod(tx.input);

    //出力
    //Overview
    console.log(RED + '[Overview]' + RESET);
    console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
    console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
    console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
    console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
    console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
    console.log(YELLOW + '  From:                     ' + RESET + tx.from);
    console.log(YELLOW + '  To:                       ' + RESET + tx.to);
    console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
    console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
    console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
    console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
    console.log(YELLOW + '  Other Attributes:         ' + RESET);
    console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
    console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
    console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
    console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

    //Logs
    console.log(RED + '[Logs]' + RESET);
    txrec.logs.forEach(log => {
        console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
        console.log(YELLOW + '    Address: ' + RESET + log.address);
        let functionName = decodeLogs[log.logIndex].name;
        let nonIndexedData = '';
        decodeLogs[log.logIndex].events.forEach((param, index) => {
            if(index == 0) {
                functionName = functionName + ' (' + param.type + ' ' + param.name;
            }else {
                functionName = functionName + ', ' + param.type + ' ' + param.name;
            }

            //非インデックスパラメータ判定
            if(!param.indexed) {
                if(nonIndexedData == '') {
                    nonIndexedData = `${param.name}:${param.value}`;
                }else {
                    nonIndexedData = nonIndexedData + ', ' + `${param.name}:${param.value}`;
                }
            }
        });
        functionName = functionName + ')';
        console.log(YELLOW + '    Name   : ' + RESET + functionName);
        console.log(YELLOW + '    Topics : ' + RESET);
        log.topics.forEach((topic, index) => {
            console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
        });
        console.log(YELLOW + '    Data   : ' + RESET);
        console.log(YELLOW + '      [Hex] ' + RESET + log.data);
        //非インデックスをDataとして表示
        console.log(YELLOW + '      [Dec] ' + RESET + nonIndexedData);
    });

}
main();

保存は Ctrl+xの後、yキー押下

スクリーンショット 2024-11-02 21.54.19.png
スクリーンショット 2024-11-02 21.55.32.png

ほぼ完コピ?

最後の最後

入力されたトランザクションハッシュからトランザクション情報を出力できるようにして終わり!!

nano explorer_completed.js

プログラムの中身。
※例外処理入れてないからねっ!!

explorer_completed.js
const Xdc3 = require("xdc3");
const abiDecoder = require('abi-decoder');
const readlineSync = require('readline-sync');

//Letter Color
const RED     = '\u001b[31m';
const YELLOW  = '\u001b[33m';
const BLUE    = '\u001b[34m';
const RESET   = '\u001b[0m';

const RPC = "https://erpc.xinfin.network";

//OparatorのABIをインポート
const operatorABI = require("./ABIs/Operator.json");
//APIConsumerのABIをインポート
const apiConsumerABI = require("./ABIs/APIConsumer.json");

//Operator ABIをデコーダーにセット
abiDecoder.addABI(operatorABI);
//APIConsumer ABIをデコーダーにセット
abiDecoder.addABI(apiConsumerABI);

async function main() {
    try {
        console.log(BLUE);
        const TXHASH = readlineSync.question('Input Transaction Hash(Plugin OCA was executed): ');
        console.log(RESET);

        if(TXHASH == '') {
            console.log(RED + 'Transaction Hash is a required field.' + RESET);
            return;
        }

        // 接続先のノードを設定
        const xdc3 = new Xdc3(new Xdc3.providers.HttpProvider(RPC));
        // TxHashからトランザクションを取得
        const tx = await xdc3.eth.getTransaction(TXHASH);

        if(tx == null) {
            console.log(RED + 'Invalid Transaction Hash.' + RESET);
            return;
        }

        // TxHashからトランザクションレシートを取得
        const txrec = await xdc3.eth.getTransactionReceipt(TXHASH);
        // TxHashが格納されているブロックの情報を取得
        const block = await xdc3.eth.getBlock(tx.blockNumber);

        //Logsをデコード(復号化)
        const decodeLogs = abiDecoder.decodeLogs(txrec.logs);

        //編集
        //timestamp
        const blockDate = new Date(block.timestamp * 1000);
        const status = txrec.status ? `${BLUE}Success${RESET}` : `${RED}Failed${RESET}`;
        const decodeInput = abiDecoder.decodeMethod(tx.input);

        if(decodeInput == undefined) {
            console.log(RED + 'This Transaction Hash does not contain any Plugin OCA transactions.' + RESET);
            return;
        }

        //出力
        //Overview
        console.log(RED + '[Overview]' + RESET);
        console.log(YELLOW + '  TransactionHash           ' + RESET + tx.hash);
        console.log(YELLOW + '  Status and method:        ' + RESET + status + ' | ' + tx.input.slice(0,10));
        console.log(YELLOW + '  Block:                    ' + RESET + tx.blockNumber);
        console.log(YELLOW + '  Timestamp:                ' + RESET + blockDate);
        console.log(YELLOW + '  Transaction Action:       ' + RESET + decodeInput.name);
        console.log(YELLOW + '  From:                     ' + RESET + tx.from);
        console.log(YELLOW + '  To:                       ' + RESET + tx.to);
        console.log(YELLOW + '  Value:                    ' + RESET + tx.value);
        console.log(YELLOW + '  Actual Tx Cost/Fee:       ' + RESET +
                    xdc3.utils.fromWei(xdc3.utils.toBN(tx.gasPrice * txrec.gasUsed), 'ether') + ' XDC');
        console.log(YELLOW + '  Gas Price:                ' + RESET + tx.gasPrice);
        console.log(YELLOW + '  Gas Limit & Usage by Txn: ' + RESET + tx.gas + ' | ' + txrec.gasUsed);
        console.log(YELLOW + '  Other Attributes:         ' + RESET);
        console.log(YELLOW + '    Txn Type: ' + RESET + tx.type);
        console.log(YELLOW + '    Nonce:    ' + RESET + tx.nonce);
        console.log(YELLOW + '    Position: ' + RESET + tx.transactionIndex);
        console.log(YELLOW + '  Input Data:               ' + RESET + tx.input);

        //Logs
        console.log(RED + '[Logs]' + RESET);
        txrec.logs.forEach(log => {
            console.log(YELLOW + '  [' + log.logIndex + ']' + RESET);
            console.log(YELLOW + '    Address: ' + RESET + log.address);
            let functionName = decodeLogs[log.logIndex].name;
            let nonIndexedData = '';
            decodeLogs[log.logIndex].events.forEach((param, index) => {
                if(index == 0) {
                    functionName = functionName + ' (' + param.type + ' ' + param.name;
                }else {
                    functionName = functionName + ', ' + param.type + ' ' + param.name;
                }

                //非インデックスパラメータ判定
                if(!param.indexed) {
                    if(nonIndexedData == '') {
                        nonIndexedData = `${param.name}:${param.value}`;
                    }else {
                        nonIndexedData = nonIndexedData + ', ' + `${param.name}:${param.value}`;
                    }
                }

            });
            functionName = functionName + ')';
            console.log(YELLOW + '    Name   : ' + RESET + functionName);
            console.log(YELLOW + '    Topics : ' + RESET);
            log.topics.forEach((topic, index) => {
                console.log(YELLOW + '      [' + index + '] ' + RESET + topic);
            });
            console.log(YELLOW + '    Data   : ' + RESET);
            console.log(YELLOW + '      [Hex] ' + RESET + log.data);
            //非インデックスをDataとして表示
            console.log(YELLOW + '      [Dec] ' + RESET + nonIndexedData);
        });

    }catch(e) {
        console.log("["+e.name+"]"+e.message);
    }
}

main();

保存は Ctrl+xの後、yキー押下

付録

今回、関数名を取るところが(知らない状態だと)まず1つ目の難関でしたが、ABIを使ってデコードするようにしました。
調査の過程で、関数名を取るだけであれば、ABIがなくてももっと簡単に取得できる方法がありました。

スクリーンショット 2024-11-02 22.54.57.png

Overview
スクリーンショット 2024-11-02 23.00.21.png
スクリーンショット 2024-11-02 23.01.59.png

Logs
0x9e9bc7616d42c2835d05ae617e508454e63b30b934be8aa932ebc125e0e58a64

OracleResponse
スクリーンショット 2024-11-02 23.04.27.png
スクリーンショット 2024-11-02 23.05.11.png

0x673b38182859a514d734bdfcb49d3a05c4a641dec9311d9894c9717bcb79af76

RequestVolume
スクリーンショット 2024-11-02 23.08.19.png
スクリーンショット 2024-11-02 23.09.24.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?