2023-05-23|閱讀時間 ‧ 約 8 分鐘

深入了解區塊鏈(四) - Internal Txns? 真的有筆交易紀錄存在?

前言

很高興能再次與您見面,再次感謝您一直以來對這系列文章的支持。今天我們會開始深入探討到EVM(以太坊虛擬機)上的一點點基礎知識。
若您是一個常常在etherscan.io上追蹤交易的朋友,一定對上面提到的各項資訊並不陌生。但我們觀察一筆交易時會發現,etherscan.io又額外提供了Internal Txns這項資訊。內部交易?這是什麼意思?它有被記錄在區塊鏈上嗎?今天的議題就會來看看它的真面目是什麼。
etherscan.io上的Internal Txns
etherscan.io上的Internal Txns

Internal Txns

首先,Internal Txns並不是一筆真正的交易紀錄,它並不會被記錄在區塊鏈上。所以您無法用一般的RPC方法去向節點詢問到這項資訊。
事實上Internal Txns指的是一筆交易在EVM當中執行時,合約之間互相呼叫的紀錄。例如這筆交易發送給了A合約,但A合約中使用了B合約中的function,B合約又使用了C合約中的function,這樣您在etherscan.io中應會看到類似於下面的這種紀錄存在:
call: from A → to B call: from B → to C
但這種紀錄不會被記錄在區塊鏈上,etherscan.io又要如何得知這種EVM的內部呼叫呢?

回放一筆交易 (Replay)

區塊鏈上記錄了所有的交易紀錄,因此我們也可以回放任何一筆交易來驗證其正確性。在以太坊節點的JSON-RPC標準中,有個名為debug_traceTransaction的API,它實現了交易回放與記錄的功能。除了回放交易外,它還會將EVM中執行的每一個步驟和運算結果都記錄下來,非常詳盡。目前Geth和Ganache都有實現該API的功能,因此我們也可以用Ganache來試看看。
  • Ganache的功能和啟動方式就不多贅述了
ganache --fork.network mainnet \
--chain.networkId 1 \
--accounts 10 \
--wallet.seed 1337 \
--port 8545
  • 另外我們也一樣透過Metamask使用Uniswap來交易1000 USDT,執行步驟和我們先前的文章相同
深入了解區塊鏈(一) - 如何使用Ganache來模擬一個真實的以太坊
  • 交易成功後,請將該筆交易的交易ID從Metamask記錄下來
選取交易紀錄中,剛剛兌換的那筆交易
可以點選複製交易ID來取得
  • 我們可以透過該ID向Ganache呼叫debug_traceTransaction方法,由於web3.py不支援該方法的呼叫,因此我們需要自行透過requests套件來呼叫
import requests

#將您剛剛複製的交易ID取代下面的tx_token字串
tx_token = '您的交易ID'
payload = {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "debug_traceTransaction",
    "params": [tx_token]
}
r = requests.post('http://127.0.0.1:8545', headers={'Content-Type': 'application/json'}, json=payload)

#可將內容存為文字檔來觀察,不建議直接把這內容印出到Notebook(內容很長)
with open('txn_trace.log', 'w') as fw:
    fw.write(r.text)
  • 若將txn_trace.log文字檔打開,並以JSON格式pretty-print後,可以看到類似的以下內容
Log中記錄了非常詳盡的EVM執行過程
這些內容記錄了這筆交易於EVM中所執行的每個指令(OP),以及各種儲存體(stack/memory/storage)的變化狀態,本篇暫先不詳盡說明這些細節,未來有機會再和各位介紹。
  • 這次我們想要關注的重點是合約間的互相呼叫(Internal Txns),因此我們需要關注的幾個OP為:
  1. CALL
  2. STATICCALL
  3. CALLCODE
  4. DELEGATECALL
  • 我們觀察一下這些OP相關的資料...
經過一個Call後的變化
可以發現到經過一個CALL呼叫後,depth變成了2,而且所有的儲存體狀態都消失了。這代表著現在已經跳到了另一個合約的作用域當中。細節就待未來的篇章再行說明。
另外可以發現到進行CALL這個呼叫時,Stack的第二個參數(由下往上數)就是跳轉的目標合約地址。CALL這個OP的參數意義可以參考這裡
  • 透過這些發現,我們可以寫一段簡單的Python Code來將所有的內部呼叫印出來
#列出合約地址間呼叫的關聯性
logs = r.json()['result']['structLogs']
history = [UNI_ROUTER_ADDR]
storage = {}
for idx in range(1, len(logs)):
    if logs[idx-1]['depth'] < logs[idx]['depth']:
        op = logs[idx-1]['op']
        if op in ['CALL', 'STATICCALL', 'CALLCODE', 'DELEGATECALL']:
            addr = logs[idx-1]['stack'][-2][-40:]
            history.append('({}) 0x{}'.format(op, addr))
            storage[addr] = logs[idx-1]['storage']
            print(' -> '.join(history))
    elif logs[idx-1]['depth'] > logs[idx]['depth']:
        history.pop()
印出來的結果如下:
各合約間的呼叫關係
  • 把他們畫成圖後,可以更了解這些合約間的關係
經由Uniswap Router交換Token時會呼叫到的相關合約
Uniswap Router會先到WETH合約將ETH封裝成ERC20標準的WETH,之後會呼叫Uniswap的USDT池中將USDT轉給您,並將您的WETH轉到USDT池中

後記

從本篇已開始從底層來認識這些交易,也稍微摸到了EVM的一點點基礎,未來若有機會再來和各位探討一下更為底層的技術。再次感謝您閱讀完這篇文章,期待下一次再見。

作者Steve目前任職於趨勢科技區塊鏈安全研究小組,專注於區塊鏈合約安全與與相關技術,如果喜歡我的文章,或是想獲得更多區塊鏈詐騙事件懶人包,歡迎關注我的帳號
另外,我已經加入由趨勢科技防詐達人所成立的方格子專題-《區塊鏈生存守則》,在那裡我會跟其他優質的創作者一起帶大家深入瞭解區塊鏈,並隨時向大家更新區塊鏈資安事件
> 追蹤《區塊鏈生存守則》學習如何在區塊鏈的世界保護自己
> 關注防詐達人獲得其他最新詐騙情報
分享至
成為作者繼續創作的動力吧!
從 Google News 追蹤更多 vocus 的最新精選內容從 Google News 追蹤更多 vocus 的最新精選內容

作者的相關文章

防詐達人的沙龍 的其他內容

你可能也想看

發表回應

成為會員 後即可發表留言
© 2024 vocus All rights reserved.