文章目录
一、描述
记录下这两天对接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: **/@Slf4jpublic 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@Builderpublic 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@Builderpublic 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节点多调用几次响应速度越来越快。