ETH钱包地址到账相关处理
大约 3 分钟
项目需要对大量ETH账号的交易情况进行监听,包括ETH交易和ERC20代币交易。如果按单个账号不断轮询交易信息的话,并不能满足我们的数千万的地址监控要求 这个方案延迟无法容忍,同时对节点接口的压力太大。那么换一个思路,我们能否一个区块一个区块来解析呢?这样压力就小了很多了。
以太坊节点有查询区块所有交易的接口,但此接口无法直接获取以太坊的内部交易信息,导致查询的交易数据缺失,有什么替代方案呢。 我们可以将普通ETH交易,ETH内部交易和ERC20代币交易分开查询,确保不会丢失交易数据。 采用web3j库和infura提供的节点以及etherscan的api来查询:
获取区块ETH交易
通过web3j请求节点接口就可以获取
DefaultBlockParameter blockParameter = new DefaultBlockParameterNumber(currentHeight);
org.web3j.protocol.core.methods.response.EthBlock.Block block = web3j.ethGetBlockByNumber(blockParameter, true).send().getResult();
List<EthBlock.TransactionResult> blockTransactions = block.getTransactions();
for (org.web3j.protocol.core.methods.response.EthBlock.TransactionResult res : blockTransactions) {
// 只查询以太坊普通交易
if ("0x".equalsIgnoreCase(input)) {
// 交易处理
}
}
处理交易时需要查询交易收据来确认交易的状态。
获取区块ETH内部交易
我们使用etherscanapi来获取区块的内部交易:
https://api.etherscan.io/api
?module=account
&action=txlistinternal
&startblock=13481773
&endblock=13491773
&page=1
&offset=10
&sort=asc
&apikey=YourApiKeyToken
请求参数说明:
| Parameter | Description |
|---|---|
| startblock | the integer block number to start searching for transactions |
| endblock | the integer block number to stop searching for transactions |
| page | the integer page number, if pagination is enabled |
| offset | the number of transactions displayed per page |
| sort | the sorting preference, use asc to sort by ascending and desc to sort by descending |
此接口返回的数据类型如下
{
"status":"1",
"message":"OK",
"result":[
{
"blockNumber":"50107",
"timeStamp":"1438984016",
"hash":"0x3f97c969ddf71f515ce5373b1f8e76e9fd7016611d8ce455881009414301789e",
"from":"0x109c4f2ccc82c4d77bde15f306707320294aea3f",
"to":"0x881b0a4e9c55d08e31d8d3c022144d75a454211c",
"value":"1000000000000000000",
"contractAddress":"",
"input":"",
"type":"call",
"gas":"2300",
"gasUsed":"0",
"traceId":"0",
"isError":"1",
"errCode":""
},
{
"blockNumber":"50111",
"timeStamp":"1438984075",
"hash":"0x893c428fed019404f704cf4d9be977ed9ca01050ed93dccdd6c169422155586f",
"from":"0x109c4f2ccc82c4d77bde15f306707320294aea3f",
"to":"0x881b0a4e9c55d08e31d8d3c022144d75a454211c",
"value":"1000000000000000000",
"contractAddress":"",
"input":"",
"type":"call",
"gas":"2300",
"gasUsed":"0",
"traceId":"0",
"isError":"0",
"errCode":""
}
]
}
“isError”字段返回“0”表示成功的transaction,返回“1”表示被拒绝的transaction。
获取区块ERC20代币交易
以太坊每个block都会有很多transaction,每个transaction一般都会有一些log。ERC20资产进行token转移的时候,都会emit一个Transfer event,这个event就会写到log里面。下面是ERC20的event Transfer定义:
// erc20
event Transfer(address indexed _from, address indexed _to, uint256 _value)
此事件里就包含交易的from,to,value信息
因此我们可以通过获取此区块的log信息来解析erc20的交易,避免了代币内部交易解析的问题,同时获取区块所有erc20代币交易log
EthFilter filter = new EthFilter(
DefaultBlockParameter.valueOf(BigInteger.valueOf(currentHeight)),
DefaultBlockParameter.valueOf(BigInteger.valueOf(currentHeight)),
// contractAddressList erc20合约列表
contractAddressList);
Event event = new Event("Transfer",
Arrays.<TypeReference<?>>asList(
new TypeReference<Address>(true) {
},
new TypeReference<Address>(true) {
},
new TypeReference<Uint256>(false) {
}));
filter.addSingleTopic(EventEncoder.encode(event));
// 查询token交易log
List<EthLog.LogResult> logs = web3j.ethGetLogs(filter).send().getLogs();
logger.info("{} event request complete, find {}", chainName, logs == null ? "0" : logs.size());
if (logs != null) {
for (EthLog.LogResult log : logs) {
Log lo = (Log) log.get();
String address = lo.getAddress();
String txHash = lo.getTransactionHash();
List<Type> typeList = FunctionReturnDecoder.decode(lo.getData(), event.getNonIndexedParameters());
BigDecimal value = new BigDecimal((BigInteger) typeList.get(0).getValue());
String from = "0x" + lo.getTopics().get(1).substring(26, 66);
String to = "0x" + lo.getTopics().get(2).substring(26, 66);
TransactionReceipt receipt = web3j.ethGetTransactionReceipt(txHash).send().getResult();
if (receipt != null) {
receiptMap.put(receipt.getTransactionHash(), receipt);
// 不成功的erc20交易
if (!"0x1".equalsIgnoreCase(receipt.getStatus())) {
logger.warn("{}, txhash {}, status not ok,skip", chainName, txHash);
continue;
{
// 处理交易...
} else {
// 收据为空,报错
logger.error("{}, txhash {}, TransactionReceipt null", chainName, txHash);
throw new RuntimeException("error null TransactionReceipt");
}
}
}
总结
- 我们通过获取单个区块的交易信息来支持近乎无限的地址交易监控。
- 区分了
普通交易和难以处理的内部交易以及erc20代币交易,确保交易信息没有遗漏。 - 由于交易可能成功也可能失败,因此我们需要查询交易收据来获取交易的状态。
- 可以使用布隆过滤器来大幅降低数据库的查询压力。