一、获取交易信息
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 :返回指定交易对应的收据信息
具体返回信息如下
{ "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" } }
web三调用中文文档:web3.eth.getTransaction - Web3.js API 中文文档 - 文江博客
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
{ "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内容解析
若非合同调用,否则为合同调用。
以合约方法function transfer(address to, uint tokens) 为例;
input数据分为三部分:
- 第一部分: 4 字节是方法名的哈希
例如:a9059cbb,详见:以太坊智能合同各种方法对应的签名代码
- 第二部分: 以太坊地址为32字节,目前以太坊地址为20字节,高位为0
例如:000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca
- 第三部分:32字节,是需要传输的代币数量,这里是1*10^18 GNT
例如:0000000000000000000000000000000000000000000000000de0b6b3a7640000
所有这些都是交易数据:
a9059cbb000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca000000000000000000000000000000000000000000000000de0b6b3a7640000
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);
}
}
}
- 取出transactionReceipt中logs;
- 取出logs中一条log;
- 使用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返回信息字段详情可参考:Welcome to Infura docs - Infura Docs
以上,就是今天分享的全部内容了,希望大家通过以上方式可以解决自己的实际需求,解决自己目前所遇到的问题。
如果文章中有不太正确的地方,欢迎指正,可以添加我的个人微信,备注:地区-职业方向-昵称,欢迎来撩,加入区块链技术交流群,与更多的区块链技术大佬学习交流。