web3j文档:https://web3j.readthedocs.io/en/latest
java-ipfs-api文档:https://github.com/ipfs/java-ipfs-api
之前一直用nodejs调用web3.js与智能合约交互,但是没找到与Java项目进行交互的方法。原来以太坊是有Java接口web3j的。
本文实现:
1、将数据存在ipfs上面,获取hash,将hash存在区块链上面。
2、从区块链上获取hash,通过hash从ipfs上的数据取下来。
-------------------------------------------------------------------------------------------------------------------------
环境配置:
首先将web3j和ipfs的jar包导入项目(参考最上面的文档即可),或者下载下来自己导入,下载地址:
web3j:https://github.com/web3j/web3j/releases
java-ipfs-api: https://github.com/ipfs/java-ipfs-api/releases
----------------------------------------------------------------------------------------------------------------------------
1、用geth搭建一条私有链,创建一个账户,进行挖矿获得一些ether,下面部署或加载合约的时候会用到这个账户。
2、创建一个只能合约:
pragma solidity ^0.4.17;
contract Data{
string public data;
function Data()public{
data = "";
}
function setData(string str) public payable{
data = str;
}
function getData() public view returns (string) {
return data;
}
}
3、编译,生成java文件
solcjs Data.sol --abi --bin -o ./
此时生成了Data_sol_Data.abi文件和Data_sol_Data.bin文件,下面命令用到这两个文件
web3j solidity generate --solidityTypes <智能合约编译之后的.bin文件的地址>.bin <智能合约编译之后的.abi文件的地址>.abi -o /path/to/src/main/java -p com.your.organisation.name
-o 后接生成好的java文件放置的位置,-p 后接生成的java文件的包名
(web3j是个命令行工具,安装方法最上面文档中有,我用的linux,使用的是解压包中的web3j)
将Java文件直接生成在Java项目中,或者生成后复制过去改一下包名。下面是自动生成的Java文件:
package test_eth;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.RemoteCall;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.tx.Contract;
import org.web3j.tx.TransactionManager;
/**
* Auto generated code.
*
Do not modify!
*
Please use the web3j command line tools,
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the
* codegen module to update.
*
*
Generated with web3j version 3.3.1.
*/
public class Data_sol_Data extends Contract {
private static final String BINARY = "6060604052341561000f57600080fd5b6040805190810160405280600981526020017f696e6974206461746100000000000000000000000000000000000000000000008152506000908051906020019061005a929190610060565b50610105565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100cf565b828001600101855582156100cf579182015b828111156100ce5782518255916020019190600101906100b3565b5b5090506100dc91906100e0565b5090565b61010291905b808211156100fe5760008160009055506001016100e6565b5090565b90565b61040f806101146000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bc5de301461005c57806347064d6a146100ea57806373d4a13a1461013c575b600080fd5b341561006757600080fd5b61006f6101ca565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100af578082015181840152602081019050610094565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61013a600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610272565b005b341561014757600080fd5b61014f61028c565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561018f578082015181840152602081019050610174565b50505050905090810190601f1680156101bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101d261032a565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102685780601f1061023d57610100808354040283529160200191610268565b820191906000526020600020905b81548152906001019060200180831161024b57829003601f168201915b5050505050905090565b806000908051906020019061028892919061033e565b5050565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103225780601f106102f757610100808354040283529160200191610322565b820191906000526020600020905b81548152906001019060200180831161030557829003601f168201915b505050505081565b602060405190810160405280600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061037f57805160ff19168380011785556103ad565b828001600101855582156103ad579182015b828111156103ac578251825591602001919060010190610391565b5b5090506103ba91906103be565b5090565b6103e091905b808211156103dc5760008160009055506001016103c4565b5090565b905600a165627a7a72305820c88de5343e43686be6997856d3a1239da233f21f97cf4a51b590864fd723010c0029";
protected Data_sol_Data(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
}
protected Data_sol_Data(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}
public RemoteCall getData() {
final Function function = new Function("getData",
Arrays.asList(),
Arrays.>asList(new TypeReference() {}));
return executeRemoteCallSingleValueReturn(function, String.class);
}
public RemoteCall setData(String str, BigInteger weiValue) {
final Function function = new Function(
"setData",
Arrays.asList(new org.web3j.abi.datatypes.Utf8String(str)),
Collections.>emptyList());
return executeRemoteCallTransaction(function, weiValue);
}
public RemoteCall data() {
final Function function = new Function("data",
Arrays.asList(),
Arrays.>asList(new TypeReference() {}));
return executeRemoteCallSingleValueReturn(function, String.class);
}
public static RemoteCall deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(Data_sol_Data.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
}
public static RemoteCall deploy(Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return deployRemoteCall(Data_sol_Data.class, web3j, transactionManager, gasPrice, gasLimit, BINARY, "");
}
public static Data_sol_Data load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return new Data_sol_Data(contractAddress, web3j, credentials, gasPrice, gasLimit);
}
public static Data_sol_Data load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return new Data_sol_Data(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}
}
4、编写与ipfs交互的代码IpfsFile.java:
package test_eth;
import java.io.IOException;
import io.ipfs.api.IPFS;
import io.ipfs.api.MerkleNode;
import io.ipfs.api.NamedStreamable;
public class IpfsFile {
public static String add(String data) throws IOException {
IPFS ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001");
NamedStreamable.ByteArrayWrapper file = new NamedStreamable.ByteArrayWrapper(data.getBytes());
MerkleNode hash = ipfs.add(file).get(0);
return hash.hash.toString();
}
public static String get(String hash) throws IOException {
IPFS ipfs = new IPFS("/ip4/127.0.0.1/tcp/5001");
MerkleNode md = new MerkleNode(hash);
byte[] data = ipfs.cat(md.hash);
return new String(data);
}
// public static void main(String []argv) {
// try {
// String hash = add("\"name\":\"zhj\"");
// System.out.println("hash:"+hash);
//
// String data = get(hash);
// System.out.println("data:"+data);
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
}
5、编写blockchain-ipfs交互代码dataOperator.java:
package test_eth;
import java.io.IOException;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
public class dataOperator {
// get hash form blockchain, then get data from ipfs using the hash
public String getData(Web3j web3j) throws Exception {
Credentials credentials = WalletUtils.loadCredentials(Consts.PASSWORD, Consts.PATH);
String address = Consts.ADDRESS;
Data_sol_Data dataOp = Data_sol_Data.load(Consts.ADDRESS, web3j, credentials, Consts.GAS_PRICE,
Consts.GAS_LIMIT);
String ipfs_hash = dataOp.getData().send();
String data = IpfsFile.get(ipfs_hash);
return data;
}
// set data to ipfs and get a hash, the save the hash to blockchain
public Boolean setData(Web3j web3j, String data) throws Exception {
Credentials credentials = WalletUtils.loadCredentials(Consts.PASSWORD, Consts.PATH);
String address = Consts.ADDRESS;
Data_sol_Data dataOp = Data_sol_Data.load(Consts.ADDRESS, web3j, credentials, Consts.GAS_PRICE,
Consts.GAS_LIMIT);
String ipfs_hash = IpfsFile.add(data);
dataOp.setData(ipfs_hash, Consts.GAS_VALUE).send();
return true;
}
}
上面没有部署合约,直接加载之前部署好的。如果还没有部署,可以使用Data_sol_Data.deploy()函数来部署,只部署一次记录下合约的地址,以后调用前直接加载已经部署好的就可以了。
(补充:上面连接链的方式是不安全的,因为没有指定chain_id,所以有可能会将信息广播到其他链上,可以通过下面的方式来制定id:
TransactionManager transactionManager = new RawTransactionManager(web3j, credentials, Consts.CHAINID);
dataOp = DataOperatorContract.load(address, web3j, transactionManager, Consts.GAS_PRICE, Consts.GAS_LIMIT);
这样就不会广播到其他链上了)
其中用到的常量写在一个Consts.java文件中:
package test_eth;
import java.math.BigInteger;
public class Consts {
// GAS价格
public static BigInteger GAS_PRICE = BigInteger.valueOf(20_000_000_000L);
// GAS上限
public static BigInteger GAS_LIMIT = BigInteger.valueOf(4_300_000L);
// 交易费用
public static BigInteger GAS_VALUE = BigInteger.valueOf(100_000L);;
// 账户密码
public static String PASSWORD = "123";
// 账户文件路径
public static String PATH = "/home/zhj/project/test_chain/web3j/keystore/UTC--2018-03-25T08-56-52.659408004Z--5daa1392dc380cbbd7fb86614514c80bb7b54424";
// 合约地址,第一次部署之后记录下来
public static String ADDRESS = "0x9bc65f8c4F3Dc31436E561CD6D893669710225e2";
public static byte CHAINID = (byte) 1234; //chain id,在创世区块中定义的
}
6、测试入口:
package test_eth;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
public class test {
public static void main(String[] argv) {
try {
Web3j web3j = Web3j.build(new HttpService("http://localhost:8545"));
dataOperator dataOp = new dataOperator();
String data = "";
// set data and get data
dataOp.setData(web3j, "fly");
data = dataOp.getData(web3j);
System.out.println("Data:" + data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
7、运行:
启动ipfs,命令行输入:ipfs daemon
进入geth console界面,进行挖矿(如果挖矿卡,可以只在部署合约和setData的时候进行挖矿)
(本文中只实现了一些简单的功能,复杂操作参考官方文档)
文章参考:https://www.jianshu.com/p/3671b65462aa