Java - SDK
PlatONE Java SDK是面向java开发者,提供的PlatONE联盟链的java开发工具包,提供了在应用层(java 代码)访问区块链节点并获取服务的接口,比如部署合约、调用合约、查询链上数据等。
1. 下载与安装
请首先下载SDK最新版本的发布包,下载地址。
将发布包解压到本地目录,如下所示:
# 下载
wget https://github.com/PlatONEnterprise/PlatONE-Go/releases/download/v0.9.0/java_sdk_linux_v0.9.0.tar.gz
# 解压
tar -zxvf java_sdk_linux_v0.9.0.tar.gz && export SDKPATH=java-sdk
安装依赖环境
# java版本:jdk1.8
sudo apt install cmake g++ maven
# 安装maven依赖
cd ${SDKPATH}/bin && ./mvn.sh
创建java项目并在maven配置文件中添加如下的依赖项:
<dependencies>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>core</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>crypto</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>abi</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>rlp</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>tuples</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>com.platone.client</groupId>
<artifactId>utils</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-unixsocket</artifactId>
<version>0.15</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.54</version>
</dependency>
</dependencies>
2. 连接节点
首先需要与PlatONE节点建立连接,以获取链上有关服务。PlatONE支持建立http连接和websocket连接两种方式。
//http短连接
Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:6791"));
//ws长连接
WebSocketClient webSocketClient = new WebSocketClient(newURI("ws://127.0.0.1:6791"));
WebSocketService ws = new WebSocketService(webSocketClient,true);
ws.connect();
Web3j web3j = Web3j.build(ws);
说明:
- 建立Websocket连接需要显式调用connect方法(与HTTP不同)。
- PlatONE节点需要在启动时打开websocket监听功能,即启动时加入参数:--ws。
3. 合约交互
为了方便在java项目中调用链上合约,需要首先生成合约对应的java类,在项目中创建合约类实例后,便可以调用合约。
3.1 合约骨架生成
-
编写合约(以demo为例),编写合约的步骤请参阅Wasm合约开发。
#include <stdlib.h> #include <string.h> #include <string> #include <bcwasm/bcwasm.hpp> namespace demo { class FirstDemo : public bcwasm::Contract { public: FirstDemo(){} /// 实现父类: bcwasm::Contract 的虚函数 /// 该函数在合约首次发布时执行,仅调用一次 void init() { bcwasm::println("init success..."); } public: void setName(const char *msg) { // 定义状态变量 bcwasm::setState("NAME_KEY",std::string(msg)); } const char* getName() const { std::string value; bcwasm::getState("NAME_KEY",value); // 读取合约数据并返回 return value.c_str(); } }; } // 此处定义的函数会生成ABI文件供外部调用 BCWASM_ABI(demo::FirstDemo,setName) BCWASM_ABI(demo::FirstDemo,getName)
合约编译后会产生demo.cpp.abi.json和demo.wasm,在生成java合约代码时需要用到这两个文件。
-
使用合约骨架生成工具生成java合约骨架:
cd java_sdk_linux_v0.9.0/java-sdk/bin ./client-sdk wasm generate --javaTypes \ </path/to/demo.wasm> \ </path/to/demo.cpp.abi.json> \ -o </path/to/src/main/java> \ -p <com.your.organisation.name> \ -t wasm
说明:把尖括号内的内容替换成自己的内容。 运行后会生成合约对应的java类。 java类中包含了合约中的方法,方便在应用层中调用合约。
3.2 合约操作
-
部署合约
//optional class NodeConfiguration { public static final String WALLETSOURCE = "/home/username/Work/PlatONE/data/keystore/keyfile.json"; public static final String DEMOBIN = "/home/user/Work/client-sdk-0.4.1/contract/firstdemo.wasm"; } //建立连接 Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:6791")); //加载钱包 Credentials credentials = WalletUtils.loadCredentials("<wallet password>",NodeConfiguration.WALLETSOURCE); //部署合约 byte[] dataBytes = Files.readBytes(new File(NodeConfiguration.DEMOBIN)); String binData = Hex.toHexString(dataBytes); Firstdemo demo = Firstdemo.deploy(web3j,credentials,binData,new DefaultWasmGasProvider()).send();
-
加载合约
//optional class NodeConfiguration { public static final String WALLETSOURCE = "/home/username/Work/PlatONE/data/keystore/keyfile.json"; public static final String DEMOBIN = "/home/user/Work/client-sdk-0.4.0/contract/firstdemo.wasm"; } //建立连接 Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:6791")); //加载钱包 Credentials credentials = WalletUtils.loadCredentials("<wallet password>",NodeConfiguration.WALLETSOURCE); //加载合约 byte[] dataBytes = Files.readBytes(new File(NodeConfiguration.DEMOBIN)); String binData = Hex.toHexString(dataBytes); Firstdemo contract = Firstdemo.load(binData,“<contract address>”,web3j,credentials,new DefaultWasmGasProvider());
-
调用合约示例
在合约部署后,客户端可以通过合约地址进行合约调用。
-
合约地址
public static void main(String args[]) { Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:6791")); try { // 密钥账户,keyfile.json为ethkey工具生成的账户文件,参照《PlatONE密钥工具文档》 Credentials credentials = WalletUtils.loadCredentials("1","/home/wxuser/keyfile.json"); // 合约数据 byte[] dataBytes = Files.readBytes(new File("/home/user/PlatONE-Workspace-0.2/contracts/build/appContract/demo/demo.wasm")); String binData = Hex.toHexString(dataBytes); // 加载合约 Demo demo = Demo.load(binData,"0x1d7f2695b43be56f52f24baa199420f8c10ac1d3",web3j,credentials,new DefaultWasmGasProvider()); // 调用demo合约的setName方法,参数输入字符串"platone" TransactionReceipt ret = demo.setName("platone").send(); System.out.println("Transaction Hash: "+ret.getTransactionHash()); // 调用demo合约的getName方法 System.out.println("getName: " + demo.getName().send()); }catch (Exception e){ System.out.println(e); } }
-
合约名称
public static void main(String[] args) { try { Web3j web3j = Web3j.build(new HttpService("http://127.0.0.1:6791")); Credentials credentials = WalletUtils.loadCredentials("1","/home/wxuser/keyfile.json"); byte[] dataBytes = Files.readBytes(new File("/home/user/PlatONE-Workspace-0.2/contracts/build/appContract/demo/demo.wasm")); String binData = Hex.toHexString(dataBytes); // load contract CnsManager cns = CnsManager.load(null,"0x0000000000000000000000000000000000000011",web3j,credentials,new DefaultWasmGasProvider()); TransactionReceipt r = cns.cnsRegister("demo","1.0.0.0","0x1d7f2695b43be56f52f24baa199420f8c10ac1d3").send(); if (r.isStatusOK()){ Demo d = Demo.load(null,"demo",web3j,c,new DefaultWasmGasProvider()); d.setName("cns").send(); System.out.println(d.getName().send()); } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("Done..."); } }
-
3.3 订阅事件
3.3.1 订阅区块:
在新区块产生时,client可以得到节点的区块数据推送。
Subscription sub = web3j.blockObservable(false).subscribe( block -> {
System.out.println(block.getBlock().getNumber());
});
3.3.2 订阅event:
在合约中可以自定义事件,client通过订阅事件的方式来获悉合约调用中所触发的事件。
合约中定义如下的event,每次setName被调用时,就会触发该event。
// event定义
BCWASM_EVENT(setName,const char *)
void setName(const char *msg)
{
// 定义状态变量
bcwasm::setState("NAME_KEY",int, std::string(msg));
// 日志输出
// 事件返回
BCWASM_EMIT_EVENT(setName,2020, "std::string(msg)");
}
在Java合约框架中会生成与setName
事件相关数据结构与接口,在服务层可以通过JavaSDK,监听该事件,示例代码如下:
String contractAddress = "0x1d7f2695b43be56f52f24baa199420f8c10ac1d3";
String eventHash = Hash.sha3String("setName");
EthFilter filter = new EthFilter(DefaultBlockParameterName.EARLIEST,DefaultBlockParameterName.LATEST,contractAddress).addSingleTopic(eventHash);
Subscription subTx = demo.setNameEventObservable(filter).subscribe(
r -> {
System.out.println(r.param1);
System.out.println(r.param2);
}
);
说明:Filter实例化的输入,第三个是合约的地址,第四个是Topic的哈希值(SHA-3),返回结果中log的Data字段是事件值的rlp编码。
3.4 根据Receipt,获取Event事件内容
// 调用demo合约的setName方法,参数输入字符串"platone"
TransactionReceipt ret = demo.setName("platone").send();
System.out.println("Transaction Hash: "+ret.getTransactionHash());
// 根据receipt获取event数据
List<Demo.SetNameEventResponse> eventParams = demo.getSetNameEvents(ret);
System.out.println(eventParams.get(0).param1); // Event中第一个参数
System.out.println(eventParams.get(0).param2); // Event中第二个参数
3.5 web3 api调用
web3j.ethBlockNumber(); // 当前最新区块高度
web3j.ethGetTransactionByHash("0x..."); // 根据交易哈希多去交易内容
web3j.ethGetTransactionReceipt("0x..."); // 根据交易哈希获取交易的回执