智能合約Java開發(fā)指南
本節(jié)介紹如何使用Java語言進(jìn)行智能合約的開發(fā)。
鏈碼結(jié)構(gòu)
在Java語言中,鏈碼主要由以下方法組成。
/**
* Defines methods that all chaincodes must implement.
*/
public interface Chaincode {
/**
*Called during an instantiate transaction after the container has been
*established, allowing the chaincode to initialize its internal data
*/
public Response init(ChaincodeStub stub);
/**
*Called for every Invoke transaction. The chaincode may change its state
*variables.
*/
public Response invoke(ChaincodeStub stub);
}
init: 鏈碼在初始化和升級(jí)時(shí)調(diào)用此接口,初始化相關(guān)的數(shù)據(jù)。
invoke:主要用于實(shí)現(xiàn)鏈碼的內(nèi)部業(yè)務(wù)邏輯,您可以在該方法中實(shí)現(xiàn)相關(guān)的業(yè)務(wù)。
在上述方法實(shí)現(xiàn)過程中,用戶可以調(diào)用 ChaincodeStubImpl 的 API 接口和鏈上進(jìn)行交互。
鏈碼示例
Hyperledger Fabric 提供了很多官方鏈碼樣例,具體請(qǐng)參考fabric 官方示例。 我們以 Hyperledger Fabric 官方提供的 example02 樣例為例,為大家介紹鏈碼的開發(fā)規(guī)范。
簡單示例
首先,我們看一個(gè)空鏈碼結(jié)構(gòu)的示例代碼
import java.util.List;
import com.google.protobuf.ByteString;
import io.netty.handler.ssl.OpenSsl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
import static java.nio.charset.StandardCharsets.UTF_8;
/*
* 一個(gè)管理資產(chǎn)的簡單鏈碼
*/
public class SimpleAssetDemo extends ChaincodeBase {
/*
* 在鏈碼實(shí)例化期時(shí)調(diào)用Init初始化數(shù)據(jù)
*/
@Override
public Response init(ChaincodeStub stub) {
}
/*
* Invoke在每一筆交易時(shí)都會(huì)被調(diào)用,該方法應(yīng)該包含 set 以及 get 來創(chuàng)建和獲取對(duì)應(yīng)的鍵值
*/
@Override
public Response invoke(ChaincodeStub stub) {
}
public static void main(String[] args) {
new SimpleAssetDemo().start(args);
}
}
init 示例
Init 函數(shù)在鏈碼實(shí)例化以及升級(jí)的時(shí)候會(huì)被調(diào)用。在實(shí)現(xiàn) Init 函數(shù)的過程中,可使用 Java 語言版本的合約 API 列表來對(duì)參數(shù)和分布式賬本進(jìn)行操作。
@Override
public Response init(ChaincodeStub stub) {
try {
_logger.info("Init java simple chaincode");
// 調(diào)用 getFunction 方法獲取當(dāng)前調(diào)用的函數(shù)
String func = stub.getFunction();
if (!func.equals("init")) {
return newErrorResponse("function other than init is not supported");
}
// 調(diào)用API getParameters 獲取調(diào)用的參數(shù)
List<String> args = stub.getParameters();
if (args.size() != 4) {
return newErrorResponse("Incorrect number of arguments. Expecting 4");
}
// 初始化相關(guān)數(shù)據(jù)
String account1Key = args.get(0);
int account1Value = Integer.parseInt(args.get(1));
String account2Key = args.get(2);
int account2Value = Integer.parseInt(args.get(3));
_logger.info(String.format("account %s, value = %s; account %s, value %s", account1Key, account1Value, account2Key, account2Value));
// 調(diào)用 putStringState 方法將數(shù)據(jù)寫入賬本中
stub.putStringState(account1Key, args.get(1));
stub.putStringState(account2Key, args.get(3));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse(e);
}
}
本示例要求用戶輸入的參數(shù)為KEY1_NAME, VALUE1, KEY2_NAME, VALUE2
,并初始化2個(gè)鍵值對(duì),調(diào)用 putStringState 將數(shù)據(jù)寫入分布式賬本中。
invoke 示例
invoke 函數(shù)是對(duì)用戶具體業(yè)務(wù)邏輯的實(shí)現(xiàn),用戶可以根據(jù)不同的業(yè)務(wù)處理邏輯,調(diào)用不同的業(yè)務(wù)處理函數(shù),如invoke,delete 和 query 函數(shù)。
// invoke把用戶調(diào)用的function細(xì)分到幾個(gè)子function, 包含invoke,delete和query
@Override
public Response invoke(ChaincodeStub stub) {
try {
_logger.info("Invoke java simple chaincode");
String func = stub.getFunction();
List<String> params = stub.getParameters();
if (func.equals("invoke")) {
return invoke(stub, params);
}
if (func.equals("delete")) {
return delete(stub, params);
}
if (func.equals("query")) {
return query(stub, params);
}
return newErrorResponse("Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]");
} catch (Throwable e) {
return newErrorResponse(e);
}
}
invoke 函數(shù)
業(yè)務(wù)邏輯 invoke 函數(shù)實(shí)現(xiàn)了業(yè)務(wù)邏輯中的資產(chǎn)轉(zhuǎn)移,將 accountFrom 的資產(chǎn)轉(zhuǎn)移 amount 個(gè)單位給 accountTo。
// invoke實(shí)現(xiàn)了兩個(gè)鍵之間的value轉(zhuǎn)移,輸入?yún)?shù)為KEY1_NAME, KEY2_NAME,VALUE
private Response invoke(ChaincodeStub stub, List<String> args) {
if (args.size() != 3) {
return newErrorResponse("Incorrect number of arguments. Expecting 3");
}
String accountFromKey = args.get(0);
String accountToKey = args.get(1);
// 獲取accountFromKey的當(dāng)前資產(chǎn)情況
String accountFromValueStr = stub.getStringState(accountFromKey);
if (accountFromValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountFromKey));
}
int accountFromValue = Integer.parseInt(accountFromValueStr);
// 獲取accountToKey的當(dāng)前資產(chǎn)情況
String accountToValueStr = stub.getStringState(accountToKey);
if (accountToValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountToKey));
}
int accountToValue = Integer.parseInt(accountToValueStr);
int amount = Integer.parseInt(args.get(2));
if (amount > accountFromValue) {
return newErrorResponse(String.format("not enough money in account %s", accountFromKey));
}
// 業(yè)務(wù)邏輯:實(shí)現(xiàn)資產(chǎn)的轉(zhuǎn)移
accountFromValue -= amount;
accountToValue += amount;
_logger.info(String.format("new value of A: %s", accountFromValue));
_logger.info(String.format("new value of B: %s", accountToValue));
// 將更新后的資產(chǎn)更新到賬本中
stub.putStringState(accountFromKey, Integer.toString(accountFromValue));
stub.putStringState(accountToKey, Integer.toString(accountToValue));
_logger.info("Transfer complete");
return newSuccessResponse("invoke finished successfully", ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " " + accountToKey + ": " + accountToValue, UTF_8).toByteArray());
}
使用 API getStringState 獲取到 KEY_NAME 對(duì)應(yīng)的資產(chǎn)總值
調(diào)用業(yè)務(wù)邏輯實(shí)現(xiàn) amount 個(gè)資產(chǎn)單位的轉(zhuǎn)移
調(diào)用 API putStringState 將更新后的資產(chǎn)情況寫入到賬本中
上述實(shí)現(xiàn)的是一個(gè)類似轉(zhuǎn)賬的簡單邏輯,但并未對(duì)參數(shù)的合法性諸如轉(zhuǎn)賬金額大于零、余額不為負(fù)等進(jìn)行校驗(yàn)。
delete 函數(shù)
業(yè)務(wù)邏輯 delete 函數(shù)實(shí)現(xiàn)了業(yè)務(wù)邏輯中的賬戶刪除功能。
// 從狀態(tài)中刪除實(shí)體
private Response delete(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("Incorrect number of arguments. Expecting 1");
}
String key = args.get(0);
// 從分類帳中的狀態(tài)刪除密鑰
stub.delState(key);
return newSuccessResponse();
}
query 函數(shù)
業(yè)務(wù)邏輯 query 函數(shù)實(shí)現(xiàn)了業(yè)務(wù)邏輯中的賬戶查詢功能,通過調(diào)用 API GetState 查詢對(duì)應(yīng)賬戶的資產(chǎn)。
// query callback representing the query of a chaincode
private Response query(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("Incorrect number of arguments. Expecting name of the person to query");
}
String key = args.get(0);
//byte[] stateBytes
String val = stub.getStringState(key);
if (val == null) {
return newErrorResponse(String.format("Error: state for %s is null", key));
}
_logger.info(String.format("Query Response:\nName: %s, Amount: %s\n", key, val));
return newSuccessResponse(val, ByteString.copyFrom(val, UTF_8).toByteArray());
}