文章目录
一、描述
记录下这两天对接Filecoin充值的流程...
1.记录区块高度值
2.轮训区块高度值获取当前所属区块的Cids
3.拿到所属区块的Cids,获取区块中的区块消息
4.组装当前区块高度的所有区块消息-去重
5.比较去重之后的区块消息地址和自己的区块地址
6.匹配地址成功后,通过hash查询交易确认状态
7.确认状态成功,上账到交易所钱包
8.归集这个地址的充值金额到归集钱包地址
二、充值上账的流程
1、读取指定高度的TipSet【ChainGetTipSetByHeight】
返回数据只拿到所属区块的Cids
result --> Cids
/*查询指定高度的TipSet*/
public List<FilCid> getChainGetTipSetByHeight(Long blockNumber) {
List<FilCid> filCids = new ArrayList<>();
List<Object> info = new ArrayList<>();
info.add(blockNumber);
info.add(new ArrayList<>());
Optional<String> response = FilHttpUtils.post(info, FilApiConfig.FIL_METHOD_CHAINGETTIPSETBYHEIGHT);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (StringUtils.isNotBlank(result)) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
Long height = object.getLongValue("Height");
if (Long.compare(height, blockNumber) == 0) {
System.out.println("返回区块高度:" + height + "和查询区块高度和返回的一致...");
String cids = object.getString("Cids");
JSONArray jsonArray = JSONArray.parseArray(cids);
if (CollectionUtils.isNotEmpty(jsonArray)) {
for (Object o : jsonArray) {
JSONObject cidObject = JSONObject.parseObject(o.toString());
FilCid cid = FilCid.builder().cid(cidObject.get("/").toString()).build();
filCids.add(cid);
}
}
}
}
}
}
}
}
return filCids;
}
2、读取区块内消息【ChainGetBlockMessages】
返回数据拿到SecpkMessages、CID。组装地址、余额、哈希。
result --> SecpkMessages --> Message
result --> SecpkMessages --> CID
/*查询区块内的消息*/
public List<FilMessage> getChainGetBlockMessages(String cid) {
List<FilMessage> filMessages = new ArrayList<>();
JSONObject cidObj = new JSONObject();
cidObj.put("/", cid);
List<Object> info = new ArrayList<>();
info.add(cidObj);
Optional<String> response = FilHttpUtils.post(info, FilApiConfig.FIL_METHOD_CHAINGETBLOCKMESSAGES);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (StringUtils.isNotBlank(result)) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
String secpkMessages = object.getString("SecpkMessages");
JSONArray jsonArray = JSONArray.parseArray(secpkMessages);
if (CollectionUtils.isNotEmpty(jsonArray)) {
for (Object secpkMessagesObj : jsonArray) {
JSONObject secpkMessagesJson = JSONObject.parseObject(secpkMessagesObj.toString());
if (secpkMessagesJson != null) {
String message = secpkMessagesJson.getString("Message");
if (StringUtils.isNotBlank(message)) {
JSONObject messageObject = JSONObject.parseObject(message);
if (messageObject != null) {
String hashStr = secpkMessagesJson.getString("CID");
String hash = "";
if (StringUtils.isNotBlank(hashStr)) {
JSONObject hashObject = JSONObject.parseObject(hashStr);
if (hashObject != null) {
hash = hashObject.getString("/");
}
}
String to = messageObject.getString("To");
String from = messageObject.getString("From");
String value = messageObject.getString("Value");
FilMessage filMessage = FilMessage.builder().to(to).from(from).value(value).hash(hash).build();
filMessages.add(filMessage);
}
}
}
}
}
}
}
}
}
}
return filMessages;
}
3、累计区块消息去重
区块高度对应所属区块有多个,上账之前去重消息
System.out.println("1-区块高度去重之前的消息总数:" + filMessageList.size());
List<FilMessage> filMessageListCollect = filMessageList.stream().distinct().collect(Collectors.toList());
System.out.println("2-区块高度去重之后的消息总数:" + filMessageListCollect.size());
4、遍历匹配自己地址
/*轮训遍历匹配自己交易所地址数据*/
for (FilMessage filMessage : filMessageListCollect) {
if (StringUtils.isNotBlank(filMessage.getTo()) && filMessage.getTo().equals(FIL_SELF_ADDRESS)) {
System.out.println("区块高度:" + blockNumber + ",地址:" + filMessage.getTo() + ",充值到账地址和自己地址一致...");
/*查询这笔交易的hash交易确认及交易状态*/
int confirm = fileRecharge.transactionConfirm(filMessage.getHash());
if (confirm == 1) {
System.out.println(filMessage.getTo() + " 交易hash确认完成,上账到交易所钱包...");
}
}
}
5、确认交易状态
查询区块浏览器:Filfox的接口、区块链头接口判断交易是否确认
/*根据交易hash进行交易确认*/
public int transactionConfirm(String hash) {
Optional<String> str = FilHttpUtils.get(FIL_URL_MESSAGE_ID + hash);
if (str.isPresent()) {
String urlinfo = str.get();
try {
JSONObject message = JSONObject.parseObject(urlinfo);
if (message == null) {
return 0;
}
JSONObject receipt = JSONObject.parseObject(message.getString("receipt"));
if (receipt == null) {
return 0;
}
long height = message.getLong("height") != null ? message.getLong("height") : 0L;
if (receipt.getIntValue("exitCode") != 0) {
return 0;
}
if (height == 0) {
return 0;
}
/*获取最新区块高度*/
long head = getBlockHeight();
if (head - height > 10) {
return 1;
}
System.out.println("查询Fil的交易hash确认失败!hash:" + hash);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
return 0;
}
/*查询当前链头的区块高度*/
public Integer getBlockHeight() {
Optional<String> response = FilHttpUtils.post(new ArrayList<>(), FilApiConfig.FIL_METHOD_CHAINHEAD);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (result != null) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
int height = Integer.parseInt(object.getString("Height"));
return height;
}
}
}
}
}
return 0;
}
三、main方法测试
Connected to the target VM, address: '127.0.0.1:61949', transport: 'socket'
返回区块高度:687530和查询区块高度和返回的一致...
fil,查询块:687530,花费时长:2947ms
1-区块高度去重之前的消息总数:30
2-区块高度去重之后的消息总数:19
区块高度:687530,地址:f13f5aqqgn6pktmje7j4jcd2wsvehgspzw7q7qa3y,充值到账地址和自己地址一致...
f13f5aqqgn6pktmje7j4jcd2wsvehgspzw7q7qa3y 交易hash确认完成,上账到交易所钱包...
Disconnected from the target VM, address: '127.0.0.1:61949', transport: 'socket'
Process finished with exit code 0
四、代码
FilRechargeTest
package com.hlf.fil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hlf.fil.config.FilApiConfig;
import com.hlf.fil.entity.FilCid;
import com.hlf.fil.entity.FilMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @ClassName: FilRechargeTest
* @Author: huanglefei
* @CreateDate: 2021/4/21 下午5:06
* @Version: 1.0
* @Description:
**/
@Slf4j
public class FilRechargeTest {
/*查询交易hash地址*/
private final String FIL_URL_MESSAGE_ID = "https://filfox.info/api/v1/message/";
/*匹配自己fil的地址*/
private static final String FIL_SELF_ADDRESS = "f13f5aqqgn6pktmje7j4jcd2wsvehgspzw7q7qa3y";
public static void main(String[] args) {
Long blockNumber = 687530L;
List<FilMessage> filMessageList = new ArrayList<>();
FilRechargeTest fileRecharge = new FilRechargeTest();
/*查询区块高度的所有交易区块*/
List<FilCid> filCids = fileRecharge.getChainGetTipSetByHeight(blockNumber);
if (CollectionUtils.isNotEmpty(filCids)) {
long queryStart = System.currentTimeMillis();
filCids.forEach(filCid -> {
/*查询区块内消息*/
List<FilMessage> chainGetBlockMessages = fileRecharge.getChainGetBlockMessages(filCid.getCid());
/*累计这个区块的高度*/
filMessageList.addAll(chainGetBlockMessages);
});
long queryEnd = System.currentTimeMillis();
System.out.println("fil,查询块:" + blockNumber + ",花费时长:" + (queryEnd - queryStart) + "ms");
}
System.out.println("1-区块高度去重之前的消息总数:" + filMessageList.size());
List<FilMessage> filMessageListCollect = filMessageList.stream().distinct().collect(Collectors.toList());
System.out.println("2-区块高度去重之后的消息总数:" + filMessageListCollect.size());
/*轮训遍历匹配自己交易所地址数据*/
for (FilMessage filMessage : filMessageListCollect) {
if (StringUtils.isNotBlank(filMessage.getTo()) && filMessage.getTo().equals(FIL_SELF_ADDRESS)) {
System.out.println("区块高度:" + blockNumber + ",地址:" + filMessage.getTo() + ",充值到账地址和自己地址一致...");
/*查询这笔交易的hash交易确认及交易状态*/
int confirm = fileRecharge.transactionConfirm(filMessage.getHash());
if (confirm == 1) {
System.out.println(filMessage.getTo() + " 交易hash确认完成,上账到交易所钱包...");
}
}
}
}
/*查询指定高度的TipSet*/
public List<FilCid> getChainGetTipSetByHeight(Long blockNumber) {
List<FilCid> filCids = new ArrayList<>();
List<Object> info = new ArrayList<>();
info.add(blockNumber);
info.add(new ArrayList<>());
Optional<String> response = FilHttpUtils.post(info, FilApiConfig.FIL_METHOD_CHAINGETTIPSETBYHEIGHT);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (StringUtils.isNotBlank(result)) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
Long height = object.getLongValue("Height");
if (Long.compare(height, blockNumber) == 0) {
System.out.println("返回区块高度:" + height + "和查询区块高度和返回的一致...");
String cids = object.getString("Cids");
JSONArray jsonArray = JSONArray.parseArray(cids);
if (CollectionUtils.isNotEmpty(jsonArray)) {
for (Object o : jsonArray) {
JSONObject cidObject = JSONObject.parseObject(o.toString());
FilCid cid = FilCid.builder().cid(cidObject.get("/").toString()).build();
filCids.add(cid);
}
}
}
}
}
}
}
}
return filCids;
}
/*查询区块内的消息*/
public List<FilMessage> getChainGetBlockMessages(String cid) {
List<FilMessage> filMessages = new ArrayList<>();
JSONObject cidObj = new JSONObject();
cidObj.put("/", cid);
List<Object> info = new ArrayList<>();
info.add(cidObj);
Optional<String> response = FilHttpUtils.post(info, FilApiConfig.FIL_METHOD_CHAINGETBLOCKMESSAGES);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (StringUtils.isNotBlank(result)) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
String secpkMessages = object.getString("SecpkMessages");
JSONArray jsonArray = JSONArray.parseArray(secpkMessages);
if (CollectionUtils.isNotEmpty(jsonArray)) {
for (Object secpkMessagesObj : jsonArray) {
JSONObject secpkMessagesJson = JSONObject.parseObject(secpkMessagesObj.toString());
if (secpkMessagesJson != null) {
String message = secpkMessagesJson.getString("Message");
if (StringUtils.isNotBlank(message)) {
JSONObject messageObject = JSONObject.parseObject(message);
if (messageObject != null) {
String hashStr = secpkMessagesJson.getString("CID");
String hash = "";
if (StringUtils.isNotBlank(hashStr)) {
JSONObject hashObject = JSONObject.parseObject(hashStr);
if (hashObject != null) {
hash = hashObject.getString("/");
}
}
String to = messageObject.getString("To");
String from = messageObject.getString("From");
String value = messageObject.getString("Value");
FilMessage filMessage = FilMessage.builder().to(to).from(from).value(value).hash(hash).build();
filMessages.add(filMessage);
}
}
}
}
}
}
}
}
}
}
return filMessages;
}
/*根据交易hash进行交易确认*/
public int transactionConfirm(String hash) {
Optional<String> str = FilHttpUtils.get(FIL_URL_MESSAGE_ID + hash);
if (str.isPresent()) {
String urlinfo = str.get();
try {
JSONObject message = JSONObject.parseObject(urlinfo);
if (message == null) {
return 0;
}
JSONObject receipt = JSONObject.parseObject(message.getString("receipt"));
if (receipt == null) {
return 0;
}
long height = message.getLong("height") != null ? message.getLong("height") : 0L;
if (receipt.getIntValue("exitCode") != 0) {
return 0;
}
if (height == 0) {
return 0;
}
/*获取最新区块高度*/
long head = getBlockHeight();
if (head - height > 10) {
return 1;
}
System.out.println("查询Fil的交易hash确认失败!hash:" + hash);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
return 0;
}
/*查询当前链头的区块高度*/
public Integer getBlockHeight() {
Optional<String> response = FilHttpUtils.post(new ArrayList<>(), FilApiConfig.FIL_METHOD_CHAINHEAD);
if (response.isPresent()) {
String body = response.get();
if (StringUtils.isNotBlank(body)) {
JSONObject jsonBody = JSONObject.parseObject(body);
if (jsonBody != null) {
String result = jsonBody.getString("result");
if (result != null) {
JSONObject object = JSONObject.parseObject(result);
if (object != null) {
int height = Integer.parseInt(object.getString("Height"));
return height;
}
}
}
}
}
return 0;
}
}
FilApiConfig
package com.hlf.fil.config;
/**
* @ClassName: FilApiConfig
* @Author: huanglefei
* @CreateDate: 2021/4/22 上午10:01
* @Version: 1.0
* @Description:
**/
public class FilApiConfig {
public final static String FIL_RPC_URL = "https://filecoin.infura.io";
public final static String FIL_METHOD_CHAINGETTIPSETBYHEIGHT = "Filecoin.ChainGetTipSetByHeight";
public final static String FIL_METHOD_CHAINGETBLOCKMESSAGES = "Filecoin.ChainGetBlockMessages";
public final static String FIL_METHOD_CHAINHEAD = "Filecoin.ChainHead";
}
FilCid
package com.hlf.fil.entity;
import lombok.Builder;
import lombok.Data;
/**
* @ClassName: FilCids
* @Author: huanglefei
* @CreateDate: 2021/4/22 上午11:36
* @Version: 1.0
* @Description:
**/
@Data
@Builder
public class FilCid {
private String cid;
}
FilMessage
package com.hlf.fil.entity;
import lombok.Builder;
import lombok.Data;
/**
* @ClassName: FilMessage
* @Author: huanglefei
* @CreateDate: 2021/4/22 下午3:23
* @Version: 1.0
* @Description:
**/
@Data
@Builder
public class FilMessage {
private int version;
private String to;
private String from;
private int nonce;
private String value;
private int gasLimit;
private String gasFeeCap;
private String gasPremium;
private int method;
private String params;
private String hash;
}
FilHttpUtils
package com.hlf.fil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hlf.fil.config.FilApiConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Optional;
/**
* @ClassName: FilHttpUtils
* @Author: huanglefei
* @CreateDate: 2021/4/19 下午4:52
* @Version: 1.0
* @Description:
**/
public class FilHttpUtils {
static Logger logger = LoggerFactory.getLogger(FilHttpUtils.class);
private static RestTemplate restTemplate;
private static ObjectMapper objectMapper = new ObjectMapper();
private static HttpHeaders headers = new HttpHeaders();
private static JSONObject jsonObject = new JSONObject();
static {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(10 * 1000);
clientHttpRequestFactory.setReadTimeout(10 * 1000);
restTemplate = new RestTemplate(clientHttpRequestFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
headers.add("Content-Type", "application/json");
//Infura提供的访问点需要http basic身份认证
headers.add("Authorization", "Basic ...");
jsonObject.put("jsonrpc", "2.0");
jsonObject.put("id", 1);
}
public static Optional<String> post(List<Object> object, String method) {
jsonObject.put("method", method);
jsonObject.put("params", object);
HttpEntity<String> httpEntity = new HttpEntity<>(jsonObject.toString(), headers);
ResponseEntity<String> mapResponseEntity = restTemplate.postForEntity(FilApiConfig.FIL_RPC_URL, httpEntity, String.class);
return Optional.ofNullable(mapResponseEntity).filter(x -> x.getStatusCode() == HttpStatus.OK).map(x -> x.getBody());
}
public static Optional<String> get(String url) {
try {
ResponseEntity<String> mapResponseEntity = restTemplate.getForEntity(url, String.class);
return Optional.ofNullable(mapResponseEntity).filter(x -> x.getStatusCode() == HttpStatus.OK).map(x -> x.getBody());
} catch (RestClientException e) {
e.printStackTrace();
}
return Optional.empty();
}
}
五、总结
调用infura节点接口读取区块内消息时,有时接口响应很慢,几十秒的都有。用自己搭建的Filcoin Lotus节点响应很快1秒内。测试了调用infura节点多调用几次响应速度越来越快。