如何同步BNB智能合约logs

        我们可以直接使用下面代码引入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 getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static  T getBean(Class requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static  T getBean(String name, Class 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配置如下:

    org.web3j
    core
    3.4.0

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配置
    /**
     * 
     *    org.web3j
     *    core
     *    3.4.0
     * 
     */

    //第三步,配置好数据库的定时任务
    /**
     * 启动类上面加上下面注解
     *
     * @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 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 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 dataList = new ArrayList<>(data.length() / 64);
            int beginIndex = 0;
            while (beginIndex < data.length()) {
                dataList.add(data.substring(beginIndex, beginIndex + 64));
                beginIndex += 64;
            }
        }

        //这就是获取出来的数据,数据是日志加了indexed参数的
        List 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);
}

你可能感兴趣的