Java对接Filecoin 充值上账流程

文章目录

一、描述

记录下这两天对接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节点多调用几次响应速度越来越快。

CSDN

上一篇:Filecoin投资200万美元建立Starling实验室


下一篇:Filecoin后续治理与发展方向的探讨