本节介绍如何使用Java语言进行智能合约的开发。
链码结构
在Java语言的链码主要由以下方法组成:
/**
* Defines methods that all chaincodes must implement.
*/
publicinterfaceChaincode{
/**
*Called during an instantiate transaction after the container has been
*established, allowing the chaincode to initialize its internal data
*/
publicResponseinit(ChaincodeStubstub);
/**
*Called for every Invoke transaction. The chaincode may change its state
*variables.
*/
publicResponseinvoke(ChaincodeStubstub);
}
init: 链码在初始化和升级时调用此接口,初始化相关的数据。
invoke:主要用于实现链码的内部业务逻辑,您可以在该方法中实现相关的业务。
链码示例
Hyperledger Fabric 提供了很多官方链码样例,具体请参考fabric 官方示例。 我们以 Hyperledger Fabric 官方提供的 example02 样例为例,为大家介绍链码的开发规范。
简单示例
首先,我们看一个空链码结构的示例代码
importjava.util.List;
importcom.google.protobuf.ByteString;
importio.netty.handler.ssl.OpenSsl;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
importorg.hyperledger.fabric.shim.ChaincodeBase;
importorg.hyperledger.fabric.shim.ChaincodeStub;
importstaticjava.nio.charset.StandardCharsets.UTF_8;
/*
* 一个管理资产的简单链码
*/
publicclassSimpleAssetDemoextendsChaincodeBase{
/*
* 在链码实例化期时调用Init初始化数据
*/
@Override
publicResponseinit(ChaincodeStubstub){
}
/*
* Invoke在每一比交易时都会被调用,该方法应该包含 set 以及 get 来创建和获取对应的键值
*/
@Override
publicResponseinvoke(ChaincodeStubstub){
}
publicstaticvoidmain(String[]args){
newSimpleAssetDemo().start(args);
}
}
init 示例
Init 函数在链码实例化以及升级的时候会被调用。在实现 Init 函数的过程中,可使用 JAVA 语言版本的合约 API 列表来对参数和分布式账本进行操作。
@Override
publicResponseinit(ChaincodeStubstub){
try{
_logger.info("Init java simple chaincode");
// 调用 getFunction 方法获取当前调用的函数
Stringfunc=stub.getFunction();
if(!func.equals("init")){
returnnewErrorResponse("function other than init is not supported");
}
// 调用API getParameters 获取调用的参数
Listargs=stub.getParameters();
if(args.size()!=4){
returnnewErrorResponse("Incorrect number of arguments. Expecting 4");
}
// 初始化相关数据
Stringaccount1Key=args.get(0);
intaccount1Value=Integer.parseInt(args.get(1));
Stringaccount2Key=args.get(2);
intaccount2Value=Integer.parseInt(args.get(3));
_logger.info(String.format("account %s, value = %s; account %s, value %s",account1Key,account1Value,account2Key,account2Value));
// 调用 putStringState 方法将数据写入账本中
stub.putStringState(account1Key,args.get(1));
stub.putStringState(account2Key,args.get(3));
returnnewSuccessResponse();
}catch(Throwablee){
returnnewErrorResponse(e);
}
}
本示例要求用户输入的参数为KEY1_NAME, VALUE1, KEY2_NAME, VALUE2,并初始化2个键值对,调用 putStringState 将数据写入分布式账本中。
invoke 示例
invoke 函数是对用户具体业务逻辑的实现,用户可以根据不同的业务处理逻辑,调用不同的业务处理函数,如invoke,delete 和 query 函数。
// invoke把用户调用的function细分到几个子function, 包含invoke,delete和query
@Override
publicResponseinvoke(ChaincodeStubstub){
try{
_logger.info("Invoke java simple chaincode");
Stringfunc=stub.getFunction();
Listparams=stub.getParameters();
if(func.equals("invoke")){
returninvoke(stub,params);
}
if(func.equals("delete")){
returndelete(stub,params);
}
if(func.equals("query")){
returnquery(stub,params);
}
returnnewErrorResponse("Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]");
}catch(Throwablee){
returnnewErrorResponse(e);
}
}
invoke 函数
业务逻辑 invoke 函数实现了业务逻辑中的资产转移,将 accountFrom 的资产转移 amount 个单位给 accountTo。
// invoke实现了两个键之间的value转移,输入参数为KEY1_NAME, KEY2_NAME,VALUE
privateResponseinvoke(ChaincodeStubstub,Listargs){
if(args.size()!=3){
returnnewErrorResponse("Incorrect number of arguments. Expecting 3");
}
StringaccountFromKey=args.get(0);
StringaccountToKey=args.get(1);
// 获取accountFromKey的当前资产情况
StringaccountFromValueStr=stub.getStringState(accountFromKey);
if(accountFromValueStr==null){
returnnewErrorResponse(String.format("Entity %s not found",accountFromKey));
}
intaccountFromValue=Integer.parseInt(accountFromValueStr);
// 获取accountToKey的当前资产情况
StringaccountToValueStr=stub.getStringState(accountToKey);
if(accountToValueStr==null){
returnnewErrorResponse(String.format("Entity %s not found",accountToKey));
}
intaccountToValue=Integer.parseInt(accountToValueStr);
intamount=Integer.parseInt(args.get(2));
if(amount>accountFromValue){
returnnewErrorResponse(String.format("not enough money in account %s",accountFromKey));
}
// 业务逻辑:实现资产的转移
accountFromValue-=amount;
accountToValue+=amount;
_logger.info(String.format("new value of A: %s",accountFromValue));
_logger.info(String.format("new value of B: %s",accountToValue));
// 将更新后的资产更新到账本中
stub.putStringState(accountFromKey,Integer.toString(accountFromValue));
stub.putStringState(accountToKey,Integer.toString(accountToValue));
_logger.info("Transfer complete");
returnnewSuccessResponse("invoke finished successfully",ByteString.copyFrom(accountFromKey+": "+accountFromValue+" "+accountToKey+": "+accountToValue,UTF_8).toByteArray());
}
使用 API getStringState 获取到 KEY_NAME 对应的资产总值
调用业务逻辑实现 amount 个资产单位的转移
调用 API putStringState 将更新后的资产情况写入到账本中
注:上述实现的是一个类似转账的简单逻辑,但并未对参数的合法性诸如转账金额大于零、余额不为负等进行校验。
delete 函数
业务逻辑 delete 函数实现了业务逻辑中的账户删除功能。
// 从状态中删除实体
privateResponsedelete(ChaincodeStubstub,Listargs){
if(args.size()!=1){
returnnewErrorResponse("Incorrect number of arguments. Expecting 1");
}
Stringkey=args.get(0);
// 从分类帐中的状态删除密钥
stub.delState(key);
returnnewSuccessResponse();
}
query 函数
业务逻辑 query 函数实现了业务逻辑中的账户查询功能,通过调用 API GetState 查询对应账户的资产。
// query callback representing the query of a chaincode
privateResponsequery(ChaincodeStubstub,Listargs){
if(args.size()!=1){
returnnewErrorResponse("Incorrect number of arguments. Expecting name of the person to query");
}
Stringkey=args.get(0);
//byte[] stateBytes
Stringval=stub.getStringState(key);
if(val==null){
returnnewErrorResponse(String.format("Error: state for %s is null",key));
}
_logger.info(String.format("Query Response:\nName: %s, Amount: %s\n",key,val));
returnnewSuccessResponse(val,ByteString.copyFrom(val,UTF_8).toByteArray());
}