1. 程式人生 > >用web3j實現與智慧合約互動

用web3j實現與智慧合約互動

之前一直用nodejs呼叫web3.js與智慧合約互動,但是沒找到與Java專案進行互動的方法。原來以太坊是有Java介面web3j的。

本文實現:

1、將資料存在ipfs上面,獲取hash,將hash存在區塊鏈上面。

2、從區塊鏈上獲取hash,通過hash從ipfs上的資料取下來。

-------------------------------------------------------------------------------------------------------------------------

環境配置:

首先將web3j和ipfs的jar包匯入專案(參考最上面的文件即可),或者下載下來自己匯入,下載地址:

----------------------------------------------------------------------------------------------------------------------------

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的時候進行挖礦)

(本文中只實現了一些簡單的功能,複雜操作參考官方文件)