为什么要使用 transaction data?
可能你也注意到了,在跟智能合约交互(例如发送 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 上查看这些数据,你会看到它以下文这个形式呈现
十六进制是啥?
十六进制是一种计数系统,就像十进制和二进制一样;十六进制使用数字 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 位的数字(大小上限为 2
256
-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 合约可以接受的最大数值是 2 256 - 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 字节之后开始。
- 例子: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 文档
原文链接:
https://blog.mycrypto.com/why-do-we-need-transaction-data-/
作者: Maarten Zuidhoorn
翻译: 阿剑
Bitcoin Price Consolidates Below Resistance, Are Dips Still Supported?
Bitcoin Price Consolidates Below Resistance, Are Dips Still Supported?
XRP, Solana, Cardano, Shiba Inu Making Up for Lost Time as Big Whale Transaction Spikes Pop Up
XRP, Solana, Cardano, Shiba Inu Making Up for Lost Time as Big Whale Transaction Spikes Pop Up
Justin Sun suspected to have purchased $160m in Ethereum
Justin Sun suspected to have purchased $160m in Ethereum