|
3 | 3 | ## Ethereum geth 同步区块的三种模式 |
4 | 4 |
|
5 | 5 | - full:从开始到结束,获取区块的header、body,从创始块开始校验每一个元素,下载所有区块数据信息。速度最慢,获取到所有的历史数据。 |
6 | | -- fast:获取区块的header,获取区块的body,在同步到当前块之前不处理任何事务,不逐一验证,有可能丢失历史数据。 |
7 | | -- light:仅获取当前状态。验证元素需要向full节点发起相应的请求。 |
| 6 | +- snap:获取区块的header,获取区块的body,在同步到当前块之前不处理任何事务,不逐一验证,有可能丢失历史数据。 |
| 7 | +- light:仅获取当前状态。验证元素需要向full节点发起相应的请求。 |
| 8 | + |
| 9 | +## [以太坊交易信息及event、input、logs、topics等概念机制](https://blog.csdn.net/cljdsc/article/details/121798544) |
| 10 | + |
| 11 | +### 交易信息 |
| 12 | + |
| 13 | +#### 合约事件 |
| 14 | + |
| 15 | + - `event Transfer(address indexed from, address indexed to, uint256 value)` |
| 16 | + - 事件名称:Transfer |
| 17 | + - 事件的参数:address, address, uint256 |
| 18 | + - 注意:此事件的from和to参数前有indexed标记,value没有indexed标记 |
| 19 | + |
| 20 | +#### 以太坊交易获取 |
| 21 | + |
| 22 | + 当上述事件在合约中调用后,我们通过其交易hash获取交易信息。从以太坊得到一条交易信息的方式有两种: |
| 23 | + - eth_getTransactionByHash: :返回指定交易对应的交易信息。 |
| 24 | + ```shell |
| 25 | + 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 |
| 26 | + ``` |
| 27 | + ```json |
| 28 | + { |
| 29 | + "jsonrpc": "2.0", |
| 30 | + "id": 1, |
| 31 | + "result": { |
| 32 | + "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528", |
| 33 | + "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829", |
| 34 | + "blockNumber": "0xd19505", |
| 35 | + "gasPrice": "0x274daee580", |
| 36 | + "gas": "0x10e3d", |
| 37 | + "maxPriorityFeePerGas": "0x6ccc91d0", |
| 38 | + "maxFeePerGas": "0x2d48ddd9f1", |
| 39 | + "input": "0xa9059cbb000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000016512c902", |
| 40 | + "hash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36", |
| 41 | + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", |
| 42 | + "nonce": "0x14", |
| 43 | + "value": "0x0", |
| 44 | + "transactionIndex": "0x71", |
| 45 | + "accessList": [], |
| 46 | + "type": "0x2", |
| 47 | + "v": "0x1", |
| 48 | + "chainId": "0x1", |
| 49 | + "s": "0x75a485b8c378173a829b27a2e55312311fdb33c68ae65f4c74e5f9cc0a748e0d" |
| 50 | + "r": "0xa1d7455286525df11602aab34e9e8ab21b092e2c7853a0d6beca0dfb2a78b2e8", |
| 51 | + } |
| 52 | + } |
| 53 | + ``` |
| 54 | + |
| 55 | + - eth_getTransactionReceipt :返回指定交易对应的收据信息。 |
| 56 | + ```json |
| 57 | + { |
| 58 | + "jsonrpc": "2.0", |
| 59 | + "id": 1, |
| 60 | + "result": { |
| 61 | + "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528", |
| 62 | + "blockNumber": "0xd19505", |
| 63 | + "contractAddress": null, |
| 64 | + "cumulativeGasUsed": "0x6c847e", |
| 65 | + "effectiveGasPrice": "0x274daee580", |
| 66 | + "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829", |
| 67 | + "gasUsed": "0xa169", |
| 68 | + "logs": [ |
| 69 | + { |
| 70 | + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", |
| 71 | + "topics": [ |
| 72 | + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", |
| 73 | + "0x000000000000000000000000b8262c6a2dcabd92a77df1d5bd074afd07fc5829", |
| 74 | + "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7" |
| 75 | + ], |
| 76 | + "data": "0x000000000000000000000000000000000000000000000000000000016512c902", |
| 77 | + "blockNumber": "0xd19505", |
| 78 | + "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36", |
| 79 | + "transactionIndex": "0x71", |
| 80 | + "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528", |
| 81 | + "logIndex": "0xa0", |
| 82 | + "removed": false |
| 83 | + } |
| 84 | + ], |
| 85 | + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000010000000000000000000000000000000000000000000000000000000008000000000080000000000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000004000000002000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000200", |
| 86 | + "status": "0x1", |
| 87 | + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", |
| 88 | + "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36", |
| 89 | + "transactionIndex": "0x71", |
| 90 | + "type": "0x2" |
| 91 | + } |
| 92 | + } |
| 93 | + ``` |
| 94 | + |
| 95 | +### input解析 |
| 96 | + |
| 97 | +#### input内容解析 |
| 98 | + |
| 99 | +**若input=0x则为非合约调用,否则为合约方法调用。** |
| 100 | +以合约方法function transfer(address to, uint tokens) 为例; |
| 101 | +input数据分为3个部分: |
| 102 | +第一部分: 4 字节,是方法名的哈希 |
| 103 | +例如:`a9059cbb`,具体可参见:以太坊智能合约各方法对应的签名编码 |
| 104 | +第二部分: 32字节,以太坊地址,目前以太坊地址是20个字节,高位补0 |
| 105 | +例如:`000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca` |
| 106 | +第三部分:32字节,是需要传输的代币数量,这里是1*10^18 GNT |
| 107 | +例如:`0000000000000000000000000000000000000000000000000de0b6b3a7640000` |
| 108 | +所有这些加在一起就是交易数据: |
| 109 | +`a9059cbb000000000000000000000000abcabcabcabcabcabcabcabcabcabcabcabcabca0000000000000000000000000000000000000000000000000de0b6b3a7640000` |
| 110 | + |
| 111 | +#### input处理逻辑 |
| 112 | + |
| 113 | + ```javascript |
| 114 | + |
| 115 | +if(transanctionInput=='0x'){ |
| 116 | + // 非合约调用 |
| 117 | + return; |
| 118 | +}else{ |
| 119 | + retJson['function'] = {'funcName':null,'inputs':null,'outputs':null,'exeResult':null}; |
| 120 | + var funcArr = mxxContractABI.filter(function (per) { |
| 121 | + return per.signature == funcHash ; |
| 122 | + }); |
| 123 | + if(funcArr!==null&&funcArr.length>0){ // 得到方法名 |
| 124 | + if(funcArr[0].hasOwnProperty("name") && funcArr[0].name!==null){ |
| 125 | + retJson['function']['funcName'] = funcArr[0].name; |
| 126 | + } |
| 127 | + if(funcArr[0].hasOwnProperty("inputs") && funcArr[0].inputs!==null){ |
| 128 | + funcArr[0].inputs.map(function (res) { // 得到方法inputs(输入) |
| 129 | + inputs.push(res.type); |
| 130 | + }); |
| 131 | + retJson['function']['inputs'] = inputs; |
| 132 | + } |
| 133 | + if(funcArr[0].hasOwnProperty("outputs")&& funcArr[0].outputs!==null){ |
| 134 | + funcArr[0].outputs.map(function (res) {// 得到方法outputs(输出) |
| 135 | + outputs.push(res.type); |
| 136 | + }); |
| 137 | + retJson['function']['outputs']= outputs; |
| 138 | + } |
| 139 | + |
| 140 | + } |
| 141 | + status = transanctionReceipt.status; |
| 142 | + if(status == '0x1'){ |
| 143 | + //执行成功 |
| 144 | + retJson['function']['exeResult'] = 'Successfully Executed'; |
| 145 | + }else { |
| 146 | + retJson['function']['exeResult'] = 'Failed Executed'; |
| 147 | + } |
| 148 | + |
| 149 | + ``` |
| 150 | +
|
| 151 | + ### logs解析 |
| 152 | +
|
| 153 | +智能合约通过【事件】来产生【日志】,日志存储的Gas费用要比合约的存储便宜很多(日志每个字节花费8个Gas,而合约存储是每32个字节20000个Gas)。想要通过合约向用户返回数据,则需将数据以事件的形式传给用户,用户拿到transactionReceipt后解析log,log.args.x拿到数据。Input只能拿到调用合约以及function的信息,而不能拿到function运行后内部产生的事件(事件不一定和function拥有相同名称和参数)。 |
| 154 | +
|
| 155 | +#### 解析步骤 |
| 156 | +
|
| 157 | +1. 取出transactionReceipt中logs。 |
| 158 | +2. 取出logs中一条log。 |
| 159 | +3. 使用event.js得到transferEvent,然后用transferEvent的decode方法解析log。 |
| 160 | +```javascript |
| 161 | +web3SolidityEvent = require('./node_modules/truffle-contract/node_modules/web3/lib/web3/event.js'); |
| 162 | +var transferErc20Json = { |
| 163 | + "anonymous": false, |
| 164 | + "inputs": [{ |
| 165 | + "indexed": true, |
| 166 | + "name": "from", |
| 167 | + "type": "address" |
| 168 | + }, { |
| 169 | + "indexed": true, |
| 170 | + "name": "to", |
| 171 | + "type": "address" |
| 172 | + }, { |
| 173 | + "indexed": false, |
| 174 | + "name": "value", |
| 175 | + "type": "uint256" |
| 176 | + }], |
| 177 | + "name": "Transfer", |
| 178 | + "type": "event", |
| 179 | + "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" |
| 180 | +}; |
| 181 | +var transferEvent = new web3SolidityEvent(null, config.transferErc20Json, null); |
| 182 | +async function processReceiptLogs(transactionReceipt){ |
| 183 | + let ethErc20LogRecord = null; |
| 184 | + let logs = transactionReceipt.logs; |
| 185 | + |
| 186 | + for (let i = 0; i < logs.length; i++) { |
| 187 | + let isContractExists = await redis.contractExists(logs[i].address); |
| 188 | + |
| 189 | + if (isContractExists === true) {//判断redis中是否存在 |
| 190 | + |
| 191 | +//是否是ERC-20 transfer事件,用topics来判断 |
| 192 | + if(logs[i].topics[0] === transferErc20Json.signature){ |
| 193 | + |
| 194 | + var log = await transferEvent.decode(logs[i]); |
| 195 | + logger.debug(prefixOfLogger +'--'+'transferEvent decode result:',log); |
| 196 | + ethErc20LogRecord = { |
| 197 | + txHash: log.transactionHash, |
| 198 | + logIndex: log.logIndex, |
| 199 | + contractAddress: log.address, |
| 200 | + fromAddress: log.args.from , |
| 201 | + toAddress: log.args.to, |
| 202 | + value: log.args.value.div(Math.pow(10, 18)).toString(), |
| 203 | + data: log.args.data, |
| 204 | + blockNumber: log.blockNumber, |
| 205 | + removed: log.removed |
| 206 | + }; |
| 207 | + db.setCreateAndUpdateTime(ethErc20LogRecord); |
| 208 | + let createLogRecord = await tables.EthErc20Log.create(ethErc20LogRecord); |
| 209 | + |
| 210 | + } |
| 211 | + } |
| 212 | +} |
| 213 | +``` |
| 214 | +
|
| 215 | +### 合约交易关键字段解释 |
| 216 | +
|
| 217 | +>| [getTransactionReceipt返回信息字段详情可参考](https://infura.io/docs/ethereum/json-rpc/eth-getTransactionReceipt) |
| 218 | +
|
| 219 | +```json |
| 220 | +{ |
| 221 | + "jsonrpc": "2.0", //RPC版本号,2.0 |
| 222 | + "id": 1, //RPC请求编号 |
| 223 | + "result": { //调用结果,为交易收据,主要包含如下字段: |
| 224 | + "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528", // 区块哈希 |
| 225 | + "blockNumber": "0xd19505", // 区块高度 |
| 226 | + "contractAddress": null, //合约地址 |
| 227 | + "cumulativeGasUsed": "0x6c847e", //当前交易执行后累计花费的gas总值 |
| 228 | + "effectiveGasPrice": "0x274daee580", //当前交易预计使用的gas总值 |
| 229 | + "from": "0xb8262c6a2dcabd92a77df1d5bd074afd07fc5829", //当前交易发送者的地址 |
| 230 | + "gasUsed": "0xa169", //执行当前这个交易单独花费的gas |
| 231 | + "logs": [ //这个交易产生的日志对象数组 |
| 232 | + { |
| 233 | + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", //当前交易被调用的合约地址 |
| 234 | + "topics": [ |
| 235 | + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", //keccak(Transfer(address,address,uint256)), //合约事件签名哈希值,对事件的字符做keccak散列运算 |
| 236 | + "0x000000000000000000000000b8262c6a2dcabd92a77df1d5bd074afd07fc5829", //当前交易from的地址 |
| 237 | + "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7" //当前交易to的地址 |
| 238 | + ], |
| 239 | + "data": "0x000000000000000000000000000000000000000000000000000000016512c902", //包含日志的非索引参数 |
| 240 | + "blockNumber": "0xd19505", // 区块高度 |
| 241 | + "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36", //交易的哈希值,32字节 |
| 242 | + "transactionIndex": "0x71" //交易在区块里面的序号,当交易为pending时为空 |
| 243 | + "blockHash": "0xb0d0e3b6c5e59b7b3e7e16701f6d6cb0c3c93487415b03839e88b3f7a241c528", |
| 244 | + "logIndex": "0xa0", //块中日志索引位置的整数,当交易为pending时日志为空。 |
| 245 | + "removed": false //当由于链重组而删除日志时,为True。 如果它是一个有效的日志则为False。 |
| 246 | + } |
| 247 | + ], |
| 248 | + //bloom过滤器,当交易为pending时日志为空 |
| 249 | + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000010000000000000000000000000000000000000000000000000000000008000000000080000000000000000000000000000000000000000000000000000000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000004000000002000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000200", |
| 250 | + "status": "0x1", //交易事务状态,1(成功)或0(失败) |
| 251 | + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", //当前交易被调用的合约地址 |
| 252 | + "transactionHash": "0xae2a33da8396a6bc40e874b0f32b9967113a3dbf071ab1290c44c62d86873d36", //交易的哈希值,32字节 |
| 253 | + "transactionIndex": "0x71", //交易在区块里面的序号,当交易为pending时为空 |
| 254 | + "type": "0x2" |
| 255 | + } |
| 256 | +} |
| 257 | + |
| 258 | +``` |
| 259 | +
|
0 commit comments