用web3j实现与智能合约交互

版权声明:转载请注明文章出处 https://blog.csdn.net/zhj_fly/article/details/79690293

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;

/**
 * <p>Auto generated code.
 * <p><strong>Do not modify!</strong>
 * <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
 * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the 
 * <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
 *
 * <p>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<String> getData() {
        final Function function = new Function("getData", 
                Arrays.<Type>asList(), 
                Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {}));
        return executeRemoteCallSingleValueReturn(function, String.class);
    }

    public RemoteCall<TransactionReceipt> setData(String str, BigInteger weiValue) {
        final Function function = new Function(
                "setData", 
                Arrays.<Type>asList(new org.web3j.abi.datatypes.Utf8String(str)), 
                Collections.<TypeReference<?>>emptyList());
        return executeRemoteCallTransaction(function, weiValue);
    }

    public RemoteCall<String> data() {
        final Function function = new Function("data", 
                Arrays.<Type>asList(), 
                Arrays.<TypeReference<?>>asList(new TypeReference<Utf8String>() {}));
        return executeRemoteCallSingleValueReturn(function, String.class);
    }

    public static RemoteCall<Data_sol_Data> deploy(Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
        return deployRemoteCall(Data_sol_Data.class, web3j, credentials, gasPrice, gasLimit, BINARY, "");
    }

    public static RemoteCall<Data_sol_Data> 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



猜你喜欢

转载自blog.csdn.net/zhj_fly/article/details/79690293