ETH一笔Transaction的一生

liujingze@skiff.com

在Ethereum上的每一个操作:例如注册一个ENS域名,在UNISWAP把ETH(准确的说是ETH-WETH-USDT)换成USDT都是一笔Transaction,给其他ETH地址的一笔转账,创建一个合约。本质都是一笔Transaction。我们以一个ETH主网铸造XEN操作为例,来解释这个铸造操作背后发生了什么。

1. 首先通过Etherscan获得ABI和合约地址

## 本地保存XEN合约abi的文件
abi_location = '/Users/ljz/PycharmProjects/mev_bot/xen.abi'

## XEN的合约地址
contract_address = "0x06450dEe7FD2Fb8E39061434BAbCFC05599a6Fb8"
with open(abi_location) as file:
    contract_abi = json.load(file)

## 使用WEB3.py 初始化这个合约
contract = w3.eth.contract(address=contract_address, abi=contract_abi)

## 调用铸造的函数 我们以铸造55天的为例
tx = contract.functions.claimRank(55)

2. 通过WEB3.py构造这个这笔交易

print(tx.buildTransaction(w3.eth.getTransactionCount(
{
## 当前地址交易的数量
'nonce':w3.eth.getTransactionCount("0xDD41e8C2474fe2978FAAea64C955681c94102c0f"),
## 设定GAS的价格 基本费用+小费
'gasPrice':  w3.eth.gas_price +to_wei(1.5, 9)
}
)

"""
结果如下
{
'value': 0, 
'gas': 160060, 
'gasPrice': 16048229133, 
'chainId': 1, 
'nonce': 0, 
'to': '0x06450dEe7FD2Fb8E39061434BAbCFC05599a6Fb8', 
'data': '0x9ff054df0000000000000000000000000000000000000000000000000000000000000037'
}
""""
  • value 需要发送的ETH的数量 这个操作不需要ETH 因此是零

  • gas 最大花费的gas总量

  • gasPrice 每一个gas的价格

  • nonce 这个字段,表示该账户已执行的交易总数。 每笔新交易都会增加 Nonce,这让验证节点知道交易需要执行的顺序。 Nonce 也用于防范重放攻击。这里因为是新账号,因此Nonce是0

  • to 代表发送eth的地址

  • data 发送的数据,也是与合约交互的核心字段,接下来我们详细介绍该字段是如何被构造的。

  • 首先找到该函数的名字以及输入的参数类型,生成它的hash

    func_bytes = Web3.keccak(text="claimRank(uint256)")
    print(func_bytes.hex())
    
    """
    结果如下
    0x9ff054df332b8cc0fe227eba34d8a766e94e38ed70233b4baf5cde49445bc11a
    '"""
    
  • 可以看data的前8个16进制数字就该函数签名的hash的前8个16进制数字,即4个字节

  • data后面64位16进制的数则为我们输入参数55的16进制。但是用了32个字节来表示的结果。

    data1 = encode_abi(["uint256"], [55])
    print(data1.hex())
    """
    结果如下
    0000000000000000000000000000000000000000000000000000000000000037
    '"""
    
  • 因此这个构造tx.buildTransaction() 作用就是

    1. 从abi中找出相应的函数以及相应参数类型,生成调用函数名的hash值,并截取前面4个字节(即8个16进制的数)。

    2. 把我们输入的参数转化为32个字节的字符。

    3. 把函数名和参数名组合在一起以及其他参数形成tx_dict。

3. 签署这个Transaction

并把这笔交易发送到可以验证的节点,一般都自己没有节点,只能发给第三方的节点提供商(alchemy, infura,或者一些公共节点)进行验证,该笔交易是否有效。

signed_tx = w3.eth.account.signTransaction(tx_dict, private_key=private_key)

具体是如何进行签署交易的,有兴趣的可以进一步研究。

4.交易会被广播到ETH网络

已签名的且验证成功的交易会从你发送的节点广播给它的对等节点,这些对等节点又广播给它们的对等节点,依此类推。 一旦交易被广播到网络,该节点也会输出交易 ID,就可以使用它来跟踪交易的状态。 此交易 ID 只是已签名交易对象的哈希值。剩下的事情就是等待矿工打包交易且出块。出块后该区块就会同步到网络中所有节点中,该笔交易也就永久保存在区块链上。

实践:使用十六进制数据铸造XEN

当我们可以看懂了一笔交易的Data, 就可以通过metamask 构造原生的交易,不在需要官网或者EtherScan提供的交互接口。

这里在BSC网络为例:

  • 首先找到XEN的合约地址 0x2AB0e9e4eE70FFf1fB9D67031E44F6410170d00e

  • 构造data

    • 函数名为claimRank(uint256) 哈希值为0x9ff054df332b8cc0fe227eba34d8a766e94e38ed70233b4baf5cde49445bc11a

    • 我们铸造一天0000000000000000000000000000000000000000000000000000000000000001

    • 因此data项为0x9ff054df0000000000000000000000000000000000000000000000000000000000000001

  • 使用metamask发送交易,点击发送,输入该合约地址。16进制的数据中填入data项

post image
  • 没有显示16进制数据的,需要在右上角,账户头像-设置-显示16进制数据的选项打开。

post image
  • 在BSCSCAN上查看

post image

交易成功完成。这也就意味我们只要拿到一个合约交互的Input Data的16进制数据,我们就可以复制任何一笔交易(在有权限的情况下)。