可能你也注意到了,在跟智能合約交互(例如發送 token)時,你的事務會自動包含 input data(“輸入數據”)。在 MyCrypto 錢包界面,這些數據有個簡單的標籤:“Data(數據)” —— 它是做什麼的呢?
這篇文章就是從技術上解釋事務輸入數據是怎麼一回事,它實質是什麼,又是怎麼工作的。
– MyCrypto 錢包的高級事務設定 –
什麼是 Input Data?
我們先來看看這筆 token 轉賬交易。某個人發送了 0 ETH 到 0xd26114cd6ee289accf82350c8d8487fedb8a0c07(OmiseGo 合約地址),而且 Etherscan 網站呈現了這是一筆意圖發送 0.19 OMG token 到這個地址的事務。那麼,EVM (以太坊虛擬機)究竟是怎麼知道,這個人想要轉賬某個數額的 token 到另一地址的呢?
你再仔細看 Etherscan,就能看到這筆事務帶着 input data。input data 是發送者為這筆事務附加的額外數據,既可以是普通的文本,也可以是數字(以十六進制的形式編碼)。但在這筆交易中,發送者使用這部分數據來 “告訴” 合約,讓合約運行特定的函數。智能合約本身是由一系列函數組成的。舉例而言,一個 ERC-20 token 合約使用比如 “transfer” 來把 token 從 A 賬戶轉移到 B賬戶,使用 “balancerOf” 函數來獲得某個地址的餘額,等等。在我們研究的這筆交易中,你可以看到它調用了 transfer(address_to, uint256_value)
函數。
這筆事務的輸入數據為0xa9059cbb0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa26152000000000000000000000000000000000000000000000000002a34892d36d6c74
。你可以把這一長串的 十六進制 數據分解一下。開頭的 0x 表示這是一個十六進制數值,緊接着的 8 個字節(a9059cbb)是函數標識符,再然後就全部是以 32 字節(也就是 64 個 16 進制字符)為一組的函數參數。所以第一組是 0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520
而第二組是 000000000000000000000000000000000000000000000000002a34892d36d6c74
。
– Input Data 分解 –
如果你在 Etherscan 上查看這些數據,你會看到它以下文這個形式呈現:
Function: transfer(address _to, uint256 _value)
MethodID: 0xa9059cbb
[0]: 0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520
[1]: 00000000000000000000000000000000000000000000000002a34892d36d6c74
十六進制是啥?
十六進制是一種計數系統,就像十進制和二進制一樣;十六進制使用數字 0 到 9 和字母 A 到 F(不區分大小寫),來對應表示十進制的 0 到 15。下面這種圖展現的就是這樣的對應關係。十六進制常常用來更直觀地表示大數字。
– 十進制數字與對應的十六進制字符 –
單個十六進制字符所能表示的最大數值是 15,長度是 4 個比特(bit)。多個十六進制字符相連時,你要把每個字符的二進製表示前後拼接在一起,才能得到其十進制數值。舉個例子,0x5C,可以寫成 0101 (=5) 和 1100 (=C),前後拼接就是 01011100,這就是二進制形式的 92,所以十六進制數 0x5C 的數值就是 92。
大多數編程語言都使用前綴 0x 作為絕對標識符(arbitrary identifier),將十六進制數與其他的計數類型(比如普通的十進制、二進制等)區別開來。這個前綴本身沒有任何意義,只是為了清晰。我們這篇文章也會採取一樣的做法,十六進制數都用 0x 開頭。
講完這些,我們繼續。如果你還是沒能理解十六進制,也不用擔心 —— 對於理解 input data 來說不是必需的。
Input Data 與智能合約
Input Data 的首要用途就是與智能合約交互。大部分智能合約都使用 合約 ABI 規範,使得 Etherscan 這樣的網站能自動解碼 input data 並顯示事務所調用的具體操作。在我們上面那個例子中,這是一筆有關代幣合約的事務,而且代幣合約遵循 ERC-20 標準。這也就意味着,我們都知曉所有可能調用的函數,以及它們的 簽名。舉例,用於 ERC-20 合約的 transfer(轉賬)函數的完整簽名總是 transfer(address, uint256)
,意味着這個函數需要兩個參數,所傳入的第一個參數會被解讀為一個地址,第二個參數會被解讀為一個未簽名的 256 位的數字(大小上限為 2256-1)。
Solidity 語言有多種參數類型。如果你有興趣學習 Solidity 語言和智能合約,你可以在Solidity 文檔頁面了解更多。
函數簽名
如你所見,transfer 函數的簽名是 transfer(address, uint256)
,這個對所有 ERC-20 合約都是一樣的。如果某個合約給轉賬函數安排不一樣的參數類型,比如一個地址和一個 uint128(未簽名的 128 位整數),這個合約就不是 “ERC-20 兼容” 的。
要獲得一個函數的簽名的十六進制形式,我們先要獲得這個函數的 SHA-3(或者說 Keccak-256)哈希值的前面 4 個字節(也就是 8 個十六進制字符)。而要想知道一個數據的 Keccak-256 哈希值,你可以使用 JavaSceript 語言的 web3 庫,或者求助於這樣的在線工具。在這個工具頁面填入 transfer(address,uint256)
,它會顯示 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b
作為結果。取前 8 個字符(忽略掉 0x),就是 a9059cbb
,恰好跟上述事務的 MethodID 一致。
另一個例子:ERC-20 標準合約的 approve(許可)函數的函數簽名是 approve(address,uint256)
,其 SHA-3 哈希值是 0x095ea7b334ae44009aa867bfb386f5c3b4b443ac6f0ee573fa91c4608fbadfba
,首 8 個字符是 095ea7b3
,因此,調用許可函數的 input data 開頭就會是 0x095ea7b3。這筆發往 DAI token 合約的事務就是如此。
地址和數量
每一個參數(除了 列表/數組 和純文本 —— 這些我們後文再說)的長度都是 32 字節,或者說 64 個十六進制字符。但以太坊地址只有 40 個字節長(不算 0x 的話)。為了解決這個問題,地址參數要用 0 來填充。在十六進制裡面,0x0000123 和 0x123 是一樣的,因此 0x0000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa261520
(上述事務中的地址參數)等同於 0x4bbeeb066ed09b7aed07bf39eee0460dfa261520
,而且 0x00000000000000000000000000000000000000000000000002a34892d36d6c74
也就等於 0x2a34892d36d6c74
。那為什麼我們要填充這些 0 呢?
就像我們上面說到的,Solidity 合約可以接受的最大數值是 2256 – 1,剛好是 32 字節。使用固定的長度可以讓 EVM 和其他應用在解碼數據時候更輕鬆,因為你可以假設每一個參數的長度都是一樣的。
那數組和字符串呢?
如上所述,在 input data 中使用數組和字符串,情形會有些許不同。因為數組本質是多個東西組成的一個列表。舉個例子,1、2、3 三個數所組成的列表在大多數編程語言中都可以寫為 [1, 2, 3]。要在事務中發送這種數據,列表中的每一個對象都要作為 32 字節一組的數據發送,列在 input data 的結尾。指明數組長度的指針就作為參數。
假定我們有一個叫做 calledmyFunction
的函數,接收一個地址和數字的數組作為參數,即 myFunction(address,uint256[])
。該函數的函數簽名是 0x4b294170。地址這一項,我們照上面所說的操作。因為我們的數組包含 3 個對象,數組的長度用十六進製表示為 0x3。然後每個對象都要佔據恰好 32 自己的空間,且數組要放在所有其它參數之後,所以數組會從 32+32 = 64 字節之後開始。
000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
– 例子:input 數據要按照 32 字節一組來切分 –
因為字符串的長度是任意的(可能長過 32 字節),它們要按 32 字節一組來切分,處理方式跟數組相同。
像 Etherscan 這樣的網站是如何解碼 input data 的?
哈希函數是單向函數,所以如果你只有函數簽名的哈希值,是不可能會恢復出函數簽名的(你要試試暴力破解嗎老弟)。合約的所有者可以將合約的 ABI 作為 JSON 文件上傳,就像這個例子,這可以用來拿到函數簽名的哈希值。
即使合約的所有者不上傳合約的 ABI,也能夠解碼 input 數據(對大多數合約而言)。因為,ERC-20 合約函數的簽名都是一樣的,因此 Etherscan 只需使用一個預定義的合約 ABI 即可服務大部分合約。舉個例子,ERC 20 合約的轉賬函數的合約 ABI 如下文所示
如果輸入數據里的簽名與任意一個預定義的函數相匹配,Etherscan 都能解碼 input data。
input data 的大小有沒有什麼限制?
既有,也沒有。以太坊協議沒有為 input data 的長度設固定的上限,但 input data 也消耗 gas。單個區塊可用的 Gas 數量是有上限的,在本文撰寫時是 800 萬(譯者註:原文撰寫於 2019 年 2 月,在 2021 年 4 月,已經上升到 1500 萬)。每一個 0 字節(0x00)都要消耗 4 gas,而非零的字節要消耗 68 gas。一筆標準的 ETH 轉賬事務要消耗 21000 單位 gas,所以,如果不考慮調用合約的交易,當前 input data 的最大長度是 2 MB(全部由 0 組成),或者全部用非零字節的話,就是 0.12 MB。因為 input data 不會只有零,也不會一個 0 也沒有,所以實際的大小會在兩者之間。
如果你想看實時的 區塊 Gas 上限,可以看 ETHStats.net。
– 特定區塊的 Gas 上限 –
只需將鼠標停留在 “Gas Limit” 部分的某個區塊上,就可以看到其 Gas 上限。
更多信息
- 合約 ABI 規範
- ERC-20 Token 標準
- 以太坊虛擬機
參考
- 以太坊黃皮書
- Solidity 文檔
(完)
(文內有許多超鏈接,可點擊左下 ”閱讀原文“ 從 EthFans 網站上獲取)
原文鏈接:
https://blog.mycrypto.com/why-do-we-need-transaction-data-/
作者: Maarten Zuidhoorn
翻譯: 阿劍
本文鏈接:https://www.8btc.com/article/6633644
轉載請註明文章出處