# ETH一笔Transaction的一生

By [Psycho Labs](https://paragraph.com/@ljzbtc) · 2022-11-13

---

[liujingze@skiff.com](mailto: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](https://bscscan.com/token/0x2AB0e9e4eE70FFf1fB9D67031E44F6410170d00e)的合约地址 0x2AB0e9e4eE70FFf1fB9D67031E44F6410170d00e
    
*   构造data
    
    *   函数名为`claimRank(uint256)` 哈希值为0x9ff054df332b8cc0fe227eba34d8a766e94e38ed70233b4baf5cde49445bc11a
        
    *   我们铸造一天0000000000000000000000000000000000000000000000000000000000000001
        
    *   因此data项为0x9ff054df0000000000000000000000000000000000000000000000000000000000000001
        
*   使用metamask发送交易，点击发送，输入该合约地址。16进制的数据中填入data项
    

![](https://storage.googleapis.com/papyrus_images/391be569e37786e3e516bfc329b3c2b365e8177c3f2b71cfd57c20f9af39e29c.png)

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

![](https://storage.googleapis.com/papyrus_images/49ee9f95ecc682b5535d2b56c22c721ac9bd335ed4b49ea1555afa771a8958eb.png)

*   在BSCSCAN上查看
    

![](https://storage.googleapis.com/papyrus_images/dc98da1aa75d0e395d7c46afc0c0b80de968a98cb43564f6054487c2f744bce6.png)

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

---

*Originally published on [Psycho Labs](https://paragraph.com/@ljzbtc/eth-transaction)*
