3

以太坊交易信息及event、input、logs、topics等概念机制

 2 years ago
source link: https://learnblockchain.cn/article/3274
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、交易信息获取

1.1 合约事件例子定义

举例,比如合约中事件如下:(以下内容均使用该事件例子)

event Transfer(address indexed from, address indexed to, uint256 value);
  • 事件名称:Transfer
  • 事件的参数:address, address, uint256
  • 注意:此事件的from和to参数前有indexed标记,value没有indexed标记

1.2 以太坊交易获取

当上述事件在合约中调用后,我们通过其交易hash获取交易信息。从以太坊得到一条交易信息的方式有两种:

  • eth_getTransactionByHash: :返回指定交易对应的交易信息
  • eth_getTransactionReceipt :返回指定交易对应的收据信息

eth_getTransactionByHash

返回的具体信息如下

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",
    "blockNumber": "0xd19505",
    "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829",
    "gas": "0x10e3d",
    "gasPrice": "0x274daee580",
    "maxFeePerGas": "0x2d48ddd9f1",
    "maxPriorityFeePerGas": "0x6ccc91d0",
    "hash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",
    "input": "0xa9059cbb000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000016512c902",
    "nonce": "0x14",
    "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",
    "transactionIndex": "0x71",
    "value": "0x0",
    "type": "0x2",
    "accessList": [],
    "chainId": "0x1",
    "v": "0x1",
    "r": "0xa1d7455286525df11602aab34e9e8ab21b092e2c7853a0d6beca0dfb2a78b2e8",
    "s": "0x75a485b8c378173a829b27a2e55312311fdb33c68ae65f4c74e5f9cc0a748e0d"
  }
}

web3调用中文文档:https://www.wenjiangs.com/doc/pr2nvcat

curl 调用:

curl -s -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36"],"id":1}' http://127.0.0.1:8545

eth_getTransactionReceipt

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",
    "blockNumber": "0xd19505",
    "contractAddress": null,
    "cumulativeGasUsed": "0x6c847e",
    "effectiveGasPrice": "0x274daee580",
    "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829",
    "gasUsed": "0xa169",
    "logs": [
      {
        "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
          "0x000000000000000000000000b8262c6a2dcabd92a77df1d5bd074afd07fc5829",
          "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7"
        ],
        "data": "0x000000000000000000000000000000000000000000000000000000016512c902",
        "blockNumber": "0xd19505",
        "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",
        "transactionIndex": "0x71",
        "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",
        "logIndex": "0xa0",
        "removed": false
      }
    ],
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000010000000000000000000000000000000000000000000000000000000008000000000080000000000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000004000000002000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000200",
    "status": "0x1",
    "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",
    "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",
    "transactionIndex": "0x71",
    "type": "0x2"
  }
}

注意:getTransaction返回结果中包含input,而getTransactionReceipts返回结果中包含logs。

二、input解析

2.1 input内容解析

input=0x则为非合约调用,否则为合约方法调用。

以合约方法function transfer(address to, uint tokens) 为例;

input数据分为3个部分:

  • 第一部分: 4 字节,是方法名的哈希

例如:a9059cbb,具体可参见:以太坊智能合约各方法对应的签名编码

  • 第二部分: 32字节,以太坊地址,目前以太坊地址是20个字节,高位补0
例如:000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca
  • 第三部分:32字节,是需要传输的代币数量,这里是1*10^18 GNT
例如:0000000000000000000000000000000000000000000000000de0b6b3a7640000

所有这些加在一起就是交易数据:

a9059cbb000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca0000000000000000000000000000000000000000000000000de0b6b3a7640000

2.2 input处理逻辑

如下为处理input的逻辑(web3.js):

if(transanctionInput=='0x'){
        // 非合约调用
    return;
}else{
    retJson['function'] = {'funcName':null,'inputs':null,'outputs':null,'exeResult':null};
    var funcArr = mxxContractABI.filter(function (per) {
        return per.signature == funcHash ;
    });
    if(funcArr!==null&&funcArr.length>0){   // 得到方法名
        if(funcArr[0].hasOwnProperty("name") && funcArr[0].name!==null){
            retJson['function']['funcName'] = funcArr[0].name;
        }
        if(funcArr[0].hasOwnProperty("inputs") && funcArr[0].inputs!==null){
            funcArr[0].inputs.map(function (res) {  // 得到方法inputs(输入)
                inputs.push(res.type);
            });
            retJson['function']['inputs'] = inputs;
        }
        if(funcArr[0].hasOwnProperty("outputs")&& funcArr[0].outputs!==null){
            funcArr[0].outputs.map(function (res) {// 得到方法outputs(输出)
                outputs.push(res.type);
            });
            retJson['function']['outputs']= outputs;
        }
 
    }
    status = transanctionReceipt.status;
    if(status == '0x1'){
        //执行成功
        retJson['function']['exeResult'] = 'Successfully Executed';
    }else {
        retJson['function']['exeResult'] = 'Failed Executed';
    }

三、logs解析

智能合约通过【事件】来产生【日志】,日志存储的Gas费用要比合约的存储便宜很多(日志每个字节花费8个Gas,而合约存储是每32个字节20000个Gas)。想要通过合约向用户返回数据,则需将数据以事件的形式传给用户,用户拿到transactionReceipt后解析log,log.args.x拿到数据。Input只能拿到调用合约以及function的信息,而不能拿到function运行后内部产生的事件(事件不一定和function拥有相同名称和参数)。

3.1 logs解析代码

logs解析步骤代码(web3.js)

web3SolidityEvent = require('./node_modules/truffle-contract/node_modules/web3/lib/web3/event.js');
var transferErc20Json = {
    "anonymous": false,
    "inputs": [{
        "indexed": true,
        "name": "from",
        "type": "address"
    }, {
        "indexed": true,
        "name": "to",
        "type": "address"
    }, {
        "indexed": false,
        "name": "value",
        "type": "uint256"
    }],
    "name": "Transfer",
    "type": "event",
    "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
};
var transferEvent = new web3SolidityEvent(null, config.transferErc20Json, null);
async function processReceiptLogs(transactionReceipt){
        let ethErc20LogRecord = null;
        let logs = transactionReceipt.logs;
 
        for (let i = 0; i < logs.length; i++) {
            let isContractExists = await redis.contractExists(logs[i].address);
 
            if (isContractExists === true) {//判断redis中是否存在
 
//是否是ERC-20 transfer事件,用topics来判断
                if(logs[i].topics[0] === transferErc20Json.signature){ 
 
                    var log = await transferEvent.decode(logs[i]);
                    logger.debug(prefixOfLogger +'--'+'transferEvent decode result:',log);
                    ethErc20LogRecord = {
                        txHash: log.transactionHash,
                        logIndex: log.logIndex,
                        contractAddress: log.address,
                        fromAddress: log.args.from ,
                        toAddress: log.args.to,
                        value: log.args.value.div(Math.pow(10, 18)).toString(),
                        data: log.args.data,
                        blockNumber: log.blockNumber,
                        removed: log.removed
                    };
                    db.setCreateAndUpdateTime(ethErc20LogRecord);
                    let createLogRecord = await tables.EthErc20Log.create(ethErc20LogRecord);
 
                }
            }
}
  1. 取出transactionReceipt中logs;
  2. 取出logs中一条log;
  3. 使用event.js得到transferEvent,然后用transferEvent的decode方法解析log;

若是公链转币,以太坊转账,则交易回执转logs为空。

若是执行合约的事件,则logs不为空,故ERC-20代币转账,会执行合约Transfer方法,产生logs

四、topics

4.1 概念

Topics[]是一个数组

  • Topics[0] :指向特定的事件,是事件的签名,如ERC-20 Transfer方法:
sha3('Transfer(address,address,uint256)') => 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
  • Topics[1] :是address类型的from参数补齐64位结果(合约交易中真正的from地址)
  • Topics[2] :是address类型to参数补齐64位的结果(合约交易中真正的to地址)

由于value没有indexed标记,因此放在data中,将value的值转化为16进制并补齐64位

4.2 indexed

Transfer事件的第一个和第二个参数被标记为indexed, 因此他们的值被放在 topics array 中. 由于value参数没有标记为indexed, 所以value值被放在data字段. 如果事件中有多个字段未标记为indexed, 那么他们的值都会被记录在data字段中。

  • topic[0] : keccak("Transfer(address,address,uint256)"),对事件的字符做keccak散列运算
  • topic[1] : address类型from参数补齐64位
  • topic[2] : address类型to参数补齐64位

data: 没有indexed标记的value的值转化为16进制,并补齐64位

事件中的参数类型需要写成完整的,如Transfer(address,address,uint)就不行,需要将uint改为uint256。

五、合约交易关键字段解释

{
  "jsonrpc": "2.0",   //RPC版本号,2.0
  "id": 1,      //RPC请求编号
  "result": {  //调用结果,为交易收据,主要包含如下字段:
    "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",  // 区块哈希
    "blockNumber": "0xd19505",   // 区块高度
    "contractAddress": null,    //合约地址
    "cumulativeGasUsed": "0x6c847e",  //当前交易执行后累计花费的gas总值
    "effectiveGasPrice": "0x274daee580",  //当前交易预计使用的gas总值
    "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829",   //当前交易发送者的地址
    "gasUsed": "0xa169",    //执行当前这个交易单独花费的gas
    "logs": [   //这个交易产生的日志对象数组
      {
        "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",   //当前交易被调用的合约地址
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",  //keccak(Transfer(address,address,uint256)), //合约事件签名哈希值,对事件的字符做keccak散列运算
          "0x000000000000000000000000b8262c6a2dcabd92a77df1d5bd074afd07fc5829",  //当前交易from的地址
          "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7"   //当前交易to的地址
        ], 
        "data": "0x000000000000000000000000000000000000000000000000000000016512c902",     //包含日志的非索引参数
        "blockNumber": "0xd19505",    // 区块高度
        "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",   //交易的哈希值,32字节
        "transactionIndex": "0x71"   //交易在区块里面的序号,当交易为pending时为空
        "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528",
        "logIndex": "0xa0",    //块中日志索引位置的整数,当交易为pending时日志为空。
        "removed": false   //当由于链重组而删除日志时,为True。 如果它是一个有效的日志则为False。  
      }
    ],
    //bloom过滤器,当交易为pending时日志为空
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000010000000000000000000000000000000000000000000000000000000008000000000080000000000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000004000000002000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000200",
    "status": "0x1",   //交易事务状态,1(成功)或0(失败)
    "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",   //当前交易被调用的合约地址
    "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36",  //交易的哈希值,32字节
    "transactionIndex": "0x71",  //交易在区块里面的序号,当交易为pending时为空
    "type": "0x2"  
  }
}

getTransactionReceipt返回信息字段详情可参考:https://infura.io/docs/ethereum/json-rpc/eth-getTransactionReceipt


以上,就是今天分享的全部内容了,希望大家通过以上方式可以解决自己的实际需求,解决自己目前所遇到的问题。

如果文章中有不太正确的地方,欢迎指正,可以添加我的个人微信,备注:地区-职业方向-昵称,欢迎来撩,加入区块链技术交流群,与更多的区块链技术大佬学习交流。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK