如何同步BNB智能合约logs
我们可以直接使用下面代码引入redis的Beanpackage com.example.demo;import org.springframework.beans.factory.DisposableBean;import org.springframework.context.ApplicationContext;import org.springframework.context.Applic
·
我们可以直接使用下面代码引入redis的Bean
package com.example.demo;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
applicationContext = null;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext appContext) {
applicationContext = appContext;
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
}
Maven配置如下: <dependency> <groupId>org.web3j</groupId> <artifactId>core</artifactId> <version>3.4.0</version> </dependency>
com.odcchina: # 区块链节点地址 bscChainUrl: 'https://data-seed-prebsc-1-s1.binance.org:8545/' contracts: # 合约地址 bscContractUrl: '0x165fa14332d0ac163513d65D415aD2D692296B4d'
下面的每一段代码写的非常详细,我们需要注意的是我们合约日志如果加了indexed的话请使用: logObject.getTopics();
如果没有加indexed的话可以使用下面代码获取,需要注意的是没有加indexed的日志他是将你那一条所有没有加indexed的参数拼接成字符串的,所以我们需要进行拆分,代码里面也写的非常详细了:
logObject.getData().substring(2, logObject.getData().length());
package com.example.demo.web3jLog; import com.example.demo.util.RedisUtil; import io.micrometer.core.instrument.util.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.web3j.abi.EventEncoder; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameter; import org.web3j.protocol.core.methods.request.EthFilter; import org.web3j.protocol.core.methods.response.EthLog; import org.web3j.protocol.http.HttpService; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Component public class Web3jTest { //第一步操作,完成Redis的配置。 //第二步操作,完成web3j的maven配置 /** * <dependency> * <groupId>org.web3j</groupId> * <artifactId>core</artifactId> * <version>3.4.0</version> * </dependency> */ //第三步,配置好数据库的定时任务 /** * 启动类上面加上下面注解 * * @EnableCaching // 启用缓存功能 * @EnableScheduling // 开启定时任务功能 */ //定时任务类加上 @Component注解,方法上面加上 @Scheduled(cron = "*/15 * * * * ?"),里面的表达式自定义设置 /** * 需要同步的合约地址 */ @Value("${com.odcchina.contracts.bscContractUrl}") private String bscContractUrl; /** * 完成web3的初始化 下面的地址引入区块链节点地址 BNB或者ETH的 */ private Web3j bscWeb3j = Web3j.build(new HttpService("https://data-seed-prebsc-1-s1.binance.org:8545/")); /** * 每分钟同步一次,获取区块链最新区块号 * * @throws IOException */ @Scheduled(initialDelay = 1 * 1000, fixedDelay = 1 * 1000) public void latestBlockTask() throws IOException { //同步最新区块 BigInteger latestBlock = bscWeb3j.ethBlockNumber().send().getBlockNumber(); RedisUtil.set("end-wen3jLogs", latestBlock.toString()); //第一次执行将退20000块开始扫描 String start = RedisUtil.get("start-wen3jLogs"); if (start == null || start.isEmpty()) { RedisUtil.set("start-wen3jLogs", latestBlock.subtract(BigInteger.valueOf(20000)).toString()); } } /** * 开始扫描日志 */ @Scheduled(initialDelay = 1 * 1000, fixedDelay = 1 * 1000) public void bscScanTask() { String start = RedisUtil.get("start-wen3jLogs"); if (StringUtils.isEmpty(start)) { try { //如果没有块的话将获取块在进行扫描日志 latestBlockTask(); } catch (IOException e) { return; } } BigInteger scannedBlock = new BigInteger(start); //最新区块 BigInteger latestBlock = new BigInteger(RedisUtil.get("end-wen3jLogs")); //一次性同步多少块 BigInteger offset; int times = 0; //如果有1块的话就执行 并且 这里考虑性能一次性任务只跑60次 大于2是因为开始块要加1,比如上次同步到了区块5,那么这次要从第6块开始同步。 while (latestBlock.subtract(scannedBlock).longValue() > 2 && times++ < 60) { if (latestBlock.longValue() - scannedBlock.longValue() > 10000) { //如果大于1万块的话每次同步500块数据 offset = BigInteger.valueOf(500); } else if (latestBlock.longValue() - scannedBlock.longValue() > 1000) { //如果大于1000块的话每次同步200块数据 offset = BigInteger.valueOf(200); } else if (latestBlock.longValue() - scannedBlock.longValue() > 100) { //如果大于100块的话每次同步80块数据 offset = BigInteger.valueOf(80); } else if (latestBlock.longValue() - scannedBlock.longValue() > 10) { //如果大于10块的话每次同步8块数据 offset = BigInteger.valueOf(8); } else if (latestBlock.longValue() - scannedBlock.longValue() > 4) { offset = BigInteger.valueOf(3); } else { //最后一条一条数据同步 offset = BigInteger.valueOf(1); } if (latestBlock.subtract(scannedBlock).longValue() < offset.longValue()) { break; } //根据区块值查询日志 如果原来同步到区块5的话 那么现在就从区块6开始同步 List<EthLog.LogResult> resp = scanBlock(scannedBlock.add(BigInteger.ONE), scannedBlock.add(offset)); for (EthLog.LogResult logItem : resp) { //循环同步下来的地址,筛选我们需要获取的日志 handleLogEvent((EthLog.LogObject) logItem.get()); } //日志同步完毕,将本次同步的区块数量加到初始区块号上面进行缓存 scannedBlock = scannedBlock.add(offset); RedisUtil.set("start-wen3jLogs", scannedBlock.toString()); } } /** * 根据区块号查询日志 * * @param startBlock * @param endBlock * @return */ private List<EthLog.LogResult> scanBlock(BigInteger startBlock, BigInteger endBlock) { EthFilter filter = new EthFilter(DefaultBlockParameter.valueOf(startBlock), DefaultBlockParameter.valueOf(endBlock), Arrays.asList(bscContractUrl)); EthLog send = null; try { send = bscWeb3j.ethGetLogs(filter).send(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return send.getLogs(); } @Transactional void handleLogEvent(EthLog.LogObject logObject) { { //这段代码会将日志没有加indexed的打印出来,并且组成数组 //event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1); //比如上面这段合约就会将第三个地址userAddress1和第四个order1取出来,我们可以处理一些业务逻辑 String data = logObject.getData().substring(2); List<String> dataList = new ArrayList<>(data.length() / 64); int beginIndex = 0; while (beginIndex < data.length()) { dataList.add(data.substring(beginIndex, beginIndex + 64)); beginIndex += 64; } } //这就是获取出来的数据,数据是日志加了indexed参数的 List<String> topList = logObject.getTopics(); String code = handle(logObject); //对TestWeb3jToken1的日志开始处理 if (code != null && code.equals("TestWeb3jToken2")) { //下面将进行数据的解析和存储 //第0个参数一般是固定 hash直接取 String topHash = topList.get(0); //我们第1个参数为地址,需要截取取出 数据状态格式为 0x0000000000000000000000009175f9ecbbddc078e40c5e037ad31f4abf36628a String addsser = topList.get(1); addsser = "0x" + addsser.substring(26, addsser.length()); //我们日志里面的第二个参数为订单号,我们截取出来为16进制,需要我们转换成10进制 //一般数据格式为 0x00000000000000000000000000000000000000000000000004380663b7c5ec3c String order = topList.get(2); order = order.substring(2, order.length()); order = getBin16By10(order) + "" ; { //下面这段代码也是可以处理没有加indexed参数的日志 //event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1); String date = logObject.getData().substring(2, logObject.getData().length()); String address = "0x" + date.substring(0, 64).substring(26, addsser.length()); String order1 = date.substring(64, date.length()); BigInteger typePas = getBin16By10(order1); //上面的参数我们可以进行存储和业务逻辑处理 } } //记录事件 /*ChainEventLogRecord eventRecord = dslContext.newRecord(CHAIN_EVENT_LOG); eventRecord.setContract(logObject.getAddress()); eventRecord.setTxHash(logObject.getTransactionHash()); eventRecord.setBlockHash(logObject.getBlockHash()); eventRecord.setBlockNum(logObject.getBlockNumber().longValue()); eventRecord.setEvent("ignore"); eventRecord.setToppics(String.join(",", logObject.getTopics())); eventRecord.setData(logObject.getData()); eventRecord.setMd5(Md5Util.md5(eventRecord.getTxHash() + eventRecord.getToppics() + eventRecord.getData())); RedisUtil.lock("MTXM-UMTP:txLock:" + eventRecord.getMd5(), 5); int count = dslContext.selectCount().from(CHAIN_EVENT_LOG).where( CHAIN_EVENT_LOG.MD5.eq(eventRecord.getMd5()) .and(CHAIN_EVENT_LOG.TX_HASH.eq(eventRecord.getTxHash()))).fetchOneInto(Integer.class); //下面是为了防止存入重复日志 if (count > 0) { log.info("tx_hash = {} 已录入,跳过", eventRecord.getTxHash()); RedisUtil.del("MTXM-UMTP:txLock:" + eventRecord.getMd5()); return; } if (logObject.getAddress().equalsIgnoreCase(smtAddress)) { smTokenService.handle(logObject, dataList, eventRecord); } eventRecord.store();*/ } /** * 需要获取的合约日志 * 合约日志代码 * TODO TestWeb3jToken(address,uint256) 中间一定不能留空格 * event TestWeb3jToken(address indexed userAddress, uint256 indexed order); * event TestWeb3jToken1(address userAddress, uint256 order); * event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1); */ final String TESTWEB3JTOKEN = EventEncoder.buildEventSignature("TestWeb3jToken(address,uint256)"); final String TestWeb3jToken1 = EventEncoder.buildEventSignature("TestWeb3jToken1(address,uint256)"); final String TESTWEB3JTOKEN2 = EventEncoder.buildEventSignature("TestWeb3jToken2(address,uint256,address,uint256)"); /** * 如果该日志是这个方法打印出来的将该方法返回 * * @param logObject * @return */ public String handle(EthLog.LogObject logObject) { if (logObject.getTopics().get(0).equalsIgnoreCase(TESTWEB3JTOKEN)) { return "TestWeb3jToken" ; } else if (logObject.getTopics().get(0).equalsIgnoreCase(TestWeb3jToken1)) { return "TestWeb3jToken1" ; } else if (logObject.getTopics().get(0).equalsIgnoreCase(TESTWEB3JTOKEN2)) { return "TestWeb3jToken2" ; } return null; } /** * 16进制转10进制 * * @param code * @return */ public static BigInteger getBin16By10(String code) { return new BigInteger(code, 16); } }
下面为大家准备了两份合约代码,可以直接运行的,配置上面Java代码可以直接呈现和测试,第一份合约就是一个普通的方法,里面打印了3条不同的日志,第二个合约是日志的定义。
pragma solidity ^0.5.1;
import "./web3j_tokenBasic.sol";
contract business_token is web3j_tokenBasic{
function orderPayBnb() public returns (bool success) {
//不做任何业务处理,直接打印3种不同的日志
address tsc = 0xa7B049d3A19796B195B0e976ec43EB7a12f07Bf9;
emit TestWeb3jToken(msg.sender, 2555);
emit TestWeb3jToken1(msg.sender, 3000);
emit TestWeb3jToken2(msg.sender, 7000,tsc, 8000);
return true;
}
}
pragma solidity ^0.5.1;
contract web3j_tokenBasic {
event TestWeb3jToken(address indexed userAddress, uint256 indexed order);
event TestWeb3jToken1(address userAddress, uint256 order);
event TestWeb3jToken2(address indexed userAddress, uint256 indexed order,address userAddress1, uint256 order1);
}
更多推荐
所有评论(0)