对于fil的充提,公司原来用的是www.ztpay.org这个网站提供的服务。这个平台后面卷款跑路了,导致公司损失了十几万。没办法,后面想去找一些比较可靠的第三方平台,但是由于每个月的服务费都要几万块。所以公司就要求能不能自己部署节点,然后自己提供充提服务。但是部署节点有个问题是,由于服务器的性能不是很好,磁盘容量不够,导致需要经常清数据,这样的运维成本比较大。后面发现可以直接使用infura提供的服务。Ethereum API | IPFS API & Gateway | ETH Nodes as a Service | Infura
但是infura有个缺点就是,它不提供完整的区块链信息。只是提供三天内产生的区块信息,这个要特别注意一下。不过这个对于大多数的应用来说,都不是什么问题。
一、在infura上注册一个应用
1、注册并登陆infura平台,进入到以下控制台
2、点击左边的菜单栏"FILCOIN",进入filcoin的设置页面
3、点击右上角的"CREATE NEW PROJECT"按钮,在弹出的弹框中输入项目的名称
4、如下图所示,我们就在infura创建了一个filCoin的项目,记住“PROJECT ID” 和"PROJECT SECRET",这两个值在后面的操作中,我们要用到。
二、关键代码
1、fil节点地址配置类
public class FilApiConfig {
//指定fil节点的地址
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";
}
2、fil的http请求工具类(主要配置权限认证)
import com.alibaba.fastjson.JSONObject;
import com.alpha.fil.mining.pool.common.util.filChain.config.FilApiConfig;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.Credentials;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class FilHttpUtils {
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(20 * 1000);
clientHttpRequestFactory.setReadTimeout(20 * 1000);
restTemplate = new RestTemplate(clientHttpRequestFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
headers.add("Content-Type", "application/json");
//Infura提供的访问点需要http basic身份认证
String credential = Credentials.basic("项目ID", "秘钥");
//如果是自己部署的fil节点,则在节点配置上拿到请求秘钥,配置如下
//String credential = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJyZWFkIiwid3JpdGUiLCJzaWduIiwiYWRtaW4iXX0.eqQKY2EE9dgQ3f6PgEM7B-IEPLMYWJpkOZkUf-BemhE";
headers.add("Authorization", credential);
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();
}
}
3、fil的操作类(包含充提操作,测试例子都在main函数中)
package com.canye.fil.demo.filChain;
import cn.hutool.core.codec.Base32;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.HexUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.canye.fil.demo.filChain.blake.Blake2b;
import com.canye.fil.demo.filChain.config.FilecoinCnt;
import com.canye.fil.demo.filChain.crypto.ECKey;
import com.canye.fil.demo.filChain.handler.TransactionHandler;
import com.canye.fil.demo.filChain.vo.*;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* fil的工具类
* @author plb
* @date 2021-06-04 14:50
*/
public class FilCoinUtil {
/**
* 获取余额
* @param address 地址
* @return
*/
public static BigDecimal getBalance(String address){
try{
String method = "Filecoin.WalletBalance";
List<Object> object = new ArrayList<>();
object.add(address);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println("FilCoinUtil.getBalance " + address + " | res:" + dataJson);
BigDecimal amount = dataJson.getBigDecimal("result");
amount = amount.divide(BigDecimal.TEN.pow(18));
return amount;
}catch (Exception e){
System.out.println("FilCoinUtil.getBalance: " + address + e.getMessage());
throw new RuntimeException("获取余额失败",e);
}
}
/**
* 检测地址是否有效
* @param address 地址
* @return
*/
public static boolean checkAddress(String address){
try{
String method = "Filecoin.WalletValidateAddress";
List<Object> object = new ArrayList<>();
object.add(address);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println("FilCoinUtil.checkAddress " + address + "|res:" + dataJson);
if(dataJson.containsKey("result") && dataJson.getString("result").equals(address)) {
return true;
}
return false;
}catch (Exception e){
System.out.println("FilCoinUtil.checkAddress: " + address+ e.getMessage());
throw new RuntimeException("检验地址失败",e);
}
}
/**
* 生成地址
* @return
*/
public static WalletResult createWallet() {
try {
ECKey ecKey = new ECKey();
byte[] privKeyBytes = ecKey.getPrivKeyBytes();
byte[] pubKey = ecKey.getPubKey();
if (privKeyBytes == null || privKeyBytes.length < 1) {
throw new RuntimeException("create wallet error");
}
String filAddress = byteToAddress(pubKey);
String privatekey = HexUtil.encodeHexStr(privKeyBytes);
return WalletResult.builder().address(filAddress).privatekey(privatekey).build();
}catch (Exception e){
System.out.println("createWallet error"+ e.getMessage());
throw new RuntimeException("生成地址失败!");
}
}
/**
* 字节转地址
* @param pub
* @return
*/
private static String byteToAddress(byte[] pub) {
Blake2b.Digest digest = Blake2b.Digest.newInstance(20);
String hash = HexUtil.encodeHexStr(digest.digest(pub));
//4.计算校验和
String pubKeyHash = "01" + HexUtil.encodeHexStr(digest.digest(pub));
Blake2b.Digest blake2b3 = Blake2b.Digest.newInstance(4);
String checksum = HexUtil.encodeHexStr(blake2b3.digest(HexUtil.decodeHex(pubKeyHash)));
//5.生成地址
return "f1" + Base32.encode(HexUtil.decodeHex(hash + checksum)).toLowerCase();
}
/**
* 根据区块链高度获取区块链信息
*/
public static ChainResult getChainTipSetByHeight(BigInteger heigth){
try{
String method = "Filecoin.ChainGetTipSetByHeight";
List<Object> object = new ArrayList<>();
object.add(heigth);
object.add(null);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println("FilCoinUtil.getChainTipSetByHeight " + heigth + " | res:" + dataJson);
ChainResult chainResult = chainJsonToChainResult(dataJson);
return chainResult;
}catch (Exception e){
System.out.println("FilCoinUtil.getChainTipSetByHeight: " + heigth+ e.getMessage());
throw new RuntimeException("getChainTipSetByHeight error",e);
}
}
/**
* 区块链json数据转对象
* @param jsonObject
* @return
*/
private static ChainResult chainJsonToChainResult(JSONObject jsonObject){
JSONObject result = jsonObject.getJSONObject("result");
BigInteger height = result.getBigInteger("Height");
ArrayList<String> cidList = new ArrayList<>();
ArrayList<String> parentCidList = new ArrayList<>();
JSONArray cidJsonArr = result.getJSONArray("Cids");
JSONArray blocksArr = result.getJSONArray("Blocks");
if (cidJsonArr != null){
for (Object o : cidJsonArr) {
cn.hutool.json.JSONObject cidKy = new cn.hutool.json.JSONObject(o);
String cid = cidKy.getStr("/");
if (!StringUtils.isEmpty(cid)){
cidList.add(cid);
}
}
}
if (blocksArr != null && blocksArr.size() > 0){
JSONArray cidArr = JSONObject.parseObject(String.valueOf(blocksArr.get(0))).getJSONArray("Parents");
if (cidArr != null){
for (Object o : cidArr) {
String cid = new cn.hutool.json.JSONObject(o).getStr("/");
if (!StringUtils.isEmpty(cid)){
parentCidList.add(cid);
}
}
}
}
return ChainResult.builder().height(height).blockCidList(cidList).parentBlockCidList(parentCidList).build();
}
/**
* 根据消息cid获取消息详情
* @param cid 消息cid
*/
public static MessagesResult getMessageByCid(String cid){
try{
String method = "Filecoin.ChainGetMessage";
JSONObject jsonObject = new JSONObject();
jsonObject.put("/", cid);
List<Object> object = new ArrayList<>();
object.add(jsonObject);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println(optional.get());
System.out.println("FilCoinUtil.getTradeMessageByCid " + cid + " | res:" + dataJson);
System.out.println(dataJson);
MessagesResult messagesResult = messagesJsonToMessagesResult(dataJson.getJSONObject("result"));
return messagesResult;
}catch (Exception e){
System.out.println("FilCoinUtil.getTradeMessageByCid: " + cid + e.getMessage());
throw new RuntimeException("获取消息详情失败",e);
}
}
/**
* 消息json转为消息对象
* @param jsonObject
* @return
*/
private static MessagesResult messagesJsonToMessagesResult(JSONObject jsonObject){
return MessagesResult.builder().from(jsonObject.getString("From"))
.to(jsonObject.getString("To"))
.version(jsonObject.getInteger("Version"))
.nonce(jsonObject.getInteger("Nonce"))
.value(Convert.fromAtto(jsonObject.getBigInteger("Value").toString(), Convert.Unit.FIL))
.gasLimit(jsonObject.getInteger("GasLimit"))
.gasFeeCap(jsonObject.getBigInteger("GasFeeCap"))
.gasPremium(jsonObject.getBigInteger("GasPremium"))
.method(jsonObject.getInteger("Method"))
.params(jsonObject.getString("Params"))
.cid(jsonObject.getJSONObject("CID")
.getString("/"))
.build();
}
/**
* 根据区块cid获取区块内所有消息
* @param blockCid 区块id
* @return
*/
public static List<MessagesResult> getMessagesByBlockCid(String blockCid){
try{
String method = "Filecoin.ChainGetBlockMessages";
JSONObject _cid = new JSONObject();
_cid.put("/", blockCid);
List<Object> object = new ArrayList<>();
object.add(_cid);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println("FilCoinUtil.getMessagesByBlockCid " + blockCid + " | res:" + dataJson);
System.out.println(dataJson.toJSONString());
ArrayList<MessagesResult> messagesResults = new ArrayList<>();
JSONObject resultJson = dataJson.getJSONObject("result");
if(resultJson == null){
resultJson = new JSONObject();
}
JSONArray blsMessagesArr = resultJson.getJSONArray("BlsMessages");
JSONArray secpkMessagesArr = resultJson.getJSONArray("SecpkMessages");
if (blsMessagesArr != null){
for (Object o : blsMessagesArr) {
messagesResults.add(messagesJsonToMessagesResult(JSONObject.parseObject(String.valueOf(o))));
}
}
if (secpkMessagesArr != null){
for (Object o : secpkMessagesArr) {
JSONObject secpkMessagesObj = JSONObject.parseObject(String.valueOf(o));
JSONObject message = secpkMessagesObj.getJSONObject("Message");
message.put("CID", secpkMessagesObj.get("CID"));
messagesResults.add(messagesJsonToMessagesResult(message));
}
}
return messagesResults;
}catch (Exception e){
System.out.println("FilCoinUtil.getMessagesByBlockCid: " + blockCid + e.getMessage());
throw new RuntimeException("获取区块内所有消息",e);
}
}
/**
* 获取指定高度的所有消息
* @param height
* @return
*/
public static ChainMessagesResult getAllMessagesByHeight(BigInteger height) {
ChainMessagesResult res = null;
ChainResult chainTipSet = getChainTipSetByHeight(height);
if (chainTipSet != null && chainTipSet.getBlockCidList() != null){
res = ChainMessagesResult.builder().blockCidList(chainTipSet.getBlockCidList()).build();
ArrayList<MessagesResult> messageList = new ArrayList<>();
for (String blockCid : chainTipSet.getBlockCidList()) {
List<MessagesResult> messagesList = getMessagesByBlockCid(blockCid);
messageList.addAll(messagesList);
}
//消息去重
List list = messageList.stream().distinct().collect(Collectors.toList());
res.setMessageList(list);
}
return res;
}
/**
* 获取nonce值
* @param address 地址
* @return
*/
public static int getNonce(String address) {
try{
String method = FilecoinCnt.GET_NONCE;
List<Object> object = new ArrayList<>();
object.add(address);
Optional optional = FilHttpUtils.post(object,method);
JSONObject dataJson = JSONObject.parseObject(String.valueOf(optional.get()));
System.out.println("FilCoinUtil.getNonce " + address + " | res:" + dataJson);
return dataJson.getIntValue("result");
}catch (Exception e){
System.out.println("FilCoinUtil.getNonce: " + address + e.getMessage());
throw new RuntimeException("获取Nonce失败",e);
}
}
/**
* 获取gas信息
* @param gas
* @return
*/
public static GasResult getGas(GetGas gas) {
if (gas == null || StringUtils.isBlank(gas.getFrom())
|| StringUtils.isBlank(gas.getTo())
|| gas.getValue() == null) {
throw new RuntimeException("paramter cannot be empty");
}
if (gas.getValue().compareTo(BigInteger.ZERO) < 1) {
throw new RuntimeException("the transfer amount must be greater than 0 !" + JSONObject.toJSONString(gas));
}
List<Object> list = new ArrayList<>();
JSONObject json = new JSONObject();
json.put("From", gas.getFrom());
json.put("To", gas.getTo());
json.put("Value", gas.getValue().toString());
list.add(json);
list.add(null);
list.add(null);
Optional optional = FilHttpUtils.post(list,FilecoinCnt.GET_GAS);
GasResult gasResult = null;
try {
JSONObject result = JSONObject.parseObject(String.valueOf(optional.get()));
JSONObject jsonObject = result.getJSONObject("result");
gasResult = GasResult.builder().gasFeeCap(jsonObject.getString("GasFeeCap"))
.gasLimit(jsonObject.getBigInteger("GasLimit"))
.gasPremium(jsonObject.getString("GasPremium")).build();
return gasResult;
} catch (Exception e) {
System.out.println("FilCoinUtil.getGas " + e.getMessage());
throw new RuntimeException("get gas error " ,e);
}
}
public static SendResult send(Transaction transaction, String privatekey) throws RuntimeException {
if (transaction == null || StringUtils.isBlank(transaction.getFrom())
|| StringUtils.isBlank(transaction.getTo())
|| StringUtils.isBlank(transaction.getGasFeeCap())
|| StringUtils.isBlank(transaction.getGasPremium())
|| StringUtils.isBlank(transaction.getValue())
|| transaction.getGasLimit() == null
|| transaction.getMethod() == null
|| transaction.getNonce() == null
|| StringUtils.isBlank(privatekey)) {
throw new RuntimeException("parameter cnanot be empty");
}
BigInteger account = new BigInteger(transaction.getValue());
if (account.compareTo(BigInteger.ZERO) < 1) {
throw new RuntimeException("the transfer amount must be greater than 0");
}
byte[] cidHash = null;
try {
cidHash = TransactionHandler.transactionSerialize(transaction);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("transaction entity serialization failed");
}
//签名
ECKey ecKey = ECKey.fromPrivate(HexUtil.decodeHex(privatekey));
String sing = Base64.encode(ecKey.sign(cidHash).toByteArray());
List<Object> list = new ArrayList<>();
JSONObject signatureJson = new JSONObject();
JSONObject messageJson = JSONObject.parseObject(JSONObject.toJSONString(transaction));
JSONObject json = new JSONObject();
messageJson.put("version", 0);
signatureJson.put("data", sing);
signatureJson.put("type", 1);
json.put("message", messageJson);
json.put("signature", signatureJson);
list.add(json);
SendResult build = null;
Optional optional = null;
try {
optional = FilHttpUtils.post(list,FilecoinCnt.BOARD_TRANSACTION);
JSONObject executeJson = JSONObject.parseObject(String.valueOf(optional.get()));
String result = executeJson.getJSONObject("result").getString("/");
build = SendResult.builder().cid(result)
.nonce(transaction.getNonce()).build();
return build;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("send error " + optional , e);
}
}
public static SendResult easySend(EasySend send) {
if (send == null || StringUtils.isBlank(send.getFrom())
|| StringUtils.isBlank(send.getTo())
|| StringUtils.isBlank(send.getPrivatekey())
|| send.getValue() == null) {
throw new RuntimeException("parameter cannot be empty");
}
BigDecimal amount = Convert.toAtto(send.getValue(), Convert.Unit.FIL);
BigInteger bigInteger = amount.toBigInteger();
//获取gas
GasResult gas = getGas(GetGas.builder().from(send.getFrom())
.to(send.getTo())
.value(bigInteger).build());
//获取nonce
int nonce = getNonce(send.getFrom());
//拼装交易参数
Transaction transaction = Transaction.builder().from(send.getFrom())
.to(send.getTo())
.gasFeeCap(gas.getGasFeeCap())
.gasLimit(gas.getGasLimit().longValue() * 2)
.gasPremium(gas.getGasPremium())
.method(0L)
.nonce((long) nonce)
.params("")
.value(bigInteger.toString()).build();
return send(transaction, send.getPrivatekey());
}
/**
* 获取消息收据
* @param messageCid 消息id
* @return
*/
public static StateGetReceiptResult stateGetReceipt(String messageCid) {
List<Object> list = new ArrayList<>();
HashMap<String, String> cidParam = new HashMap<>(8);
cidParam.put("/", messageCid);
list.add(cidParam);
list.add(null);
StateGetReceiptResult res = null;
JSONObject jsonObject = null;
try {
Optional optional = FilHttpUtils.post(list,FilecoinCnt.STATE_GET_RECEIPT);
jsonObject = JSONObject.parseObject(String.valueOf(optional.get()));
JSONObject result = jsonObject.getJSONObject("result");
res = StateGetReceiptResult.builder().exitCode(result.getInteger("ExitCode"))
.messageReturn(result.getString("Return"))
.gasUsed(result.getBigInteger("GasUsed"))
.build();
return res;
} catch (Exception e) {
System.out.println("stateGetReceipt error messgeid:" + messageCid + ",jsonObject=" + JSON.toJSONString(jsonObject) + e.getMessage());
throw new RuntimeException("stateGetReceipt error " + messageCid + ",jsonObject=" + JSON.toJSONString(jsonObject) + e.getMessage());
}
}
public static void main(String[] args){
//获取地址余额
//System.out.println(getBalance("f1l3aboc6csauxuqrb2mftpvsjahuli4gxro6tllq"));
//校验地址
// System.out.println(checkAddress("f1l3aboc6csauxuqrb2mftpvsjahuli4gxro6tll2"));
//测试生成地址
//System.out.println(createWallet());
//获取某个高度的所有消息(充值到账)
// ChainMessagesResult chainMessagesResult = getAllMessagesByHeight(BigInteger.valueOf(1142577L));
// for(MessagesResult messagesResult:chainMessagesResult.getMessageList()){
//
// //判断消息是否是交易信息,等于0
// if(messagesResult.getMethod().intValue() == 0) {
// //校验交易是否正常
// StateGetReceiptResult stateGetReceiptResult = FilCoinUtil.stateGetReceipt(messagesResult.getCid());
// if(stateGetReceiptResult.getExitCode().intValue() == 0 ) {
// //到账地址
// String toAddress = messagesResult.getTo();
// //交易hash
// String hash = messagesResult.getCid();
// //todo 业务处理,充值到账
// }
// }
// }
//测试提币上链
// //私钥
// String privatekey = "fil秘钥";
// EasySend easySend = new EasySend();
// //来源地址
// easySend.setFrom("f1sle4slx2xqap7p2ehatextarj5nlgeilclgs723");
// //目标地址
// easySend.setTo("f1sle4slx2xqap7p2ehatextarj5nlgeilclgs7qi");
// easySend.setPrivatekey(privatekey);
// //转账金额
// BigDecimal amount = new BigDecimal("0.0001");
// easySend.setValue(amount);
// System.out.println(easySend(easySend));
}
}
如在使用的过程中,有问题,麻烦发信息到邮箱408337459@qq.com