Web3j 读取 eth log

最近在做 nft 相关的小项目,需要通过解析一些 eth log 来触发后台的相关逻辑。看了下 web3j 发现有两种方式可以实现这个解析。

1、直接利用 web3j 生成 wrapper code

编译 solidity 代码

solc <contract>.sol --bin --abi --optimize -o <output-dir>/

solc contract.sol --bin --abi --optimize -o build/ --include-path ./node_modules --base-path ./

生成 wrapper code

web3j generate solidity -b /path/to/<smart-contract>.bin -a /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name

生成的代码里有直接相关的 event 订阅和查询方法,可以直接使用。这里也直接会把相关的日志数据解析成对应的 xxEventResponse,可以直接使用。

new Contract(contractAddress,web3j).xxxEventFlowable(filter).subscribe(x->{process(x);});

2、手动订阅解析

如果你跟我一样,需要监控多个合约,多种 event,那就可以自己定制 filter 进行订阅。

 List<String> contracts = Lists.newArrayList(contractAddressOne, contractAddressTwo);
    EthFilter filter =
        new EthFilter(
            DefaultBlockParameter.valueOf(startNumber),
            DefaultBlockParameterName.LATEST,
            contracts);
    web3j
        .ethLogFlowable(filter)
        .doOnError(
            ex -> {
              log.error("log trace failed", ex);
            })
        .subscribe(
            eventLog -> {
              process(eventLog);
            });
  }        

跟第一种方式不一样,这里拿到的 event 数据都比较原始,需要我们手动来进行解析。

event log 里的数据分为两类,一种是 topic,一种是 data。 topic 对应 solidity 中 indexed 的值,data 则对应未 indexed 的值。

举个例子,以下这个 event 定义里的 operator 就属于 topic; itemIds 和 tokenIds 则属于 data。 另外,topic[0] 是函数签名,所以取数据的时候是从下标 1 开始。

event Mint(address indexed operator, uint64[] itemIds, uint256 [] tokenIds);

使用 web3j 解析这个日志数据,我们可以先定义好对应的 Event

public static final Event MINT =
    new Event(
        "Mint",
        Arrays.asList(
            new TypeReference<Address>(true) {},
            new TypeReference<DynamicArray<Uint64>>() {},
            new TypeReference<DynamicArray<Uint256>>() {}));

然后将对应的 log 数据转换一下

public void convert(Log eventLog) {
  Address address =
      (Address)
          FunctionReturnDecoder.decodeIndexedValue(
              eventLog.getTopics().get(1), new TypeReference<Address>() {});

  List<Type> params =
      FunctionReturnDecoder.decode(
          eventLog.getData(), EthEvent.MINT.getNonIndexedParameters());
  List<Long> itemIds =
      ((DynamicArray<Uint64>) params.get(0))
          .getValue().stream().map(x -> x.getValue().longValue()).collect(Collectors.toList());
  List<String> tokens =
      ((DynamicArray<Uint256>) params.get(1))
          .getValue().stream()
              .map(x -> Numeric.toHexStringWithPrefix(x.getValue()))
              .collect(Collectors.toList());
}

这里需要注意的就是,对于 topic,我们可以通过下标一个个解析。而对于 data,则需要先统一解析出来,再依次解析。