全部产品

智能合约Java开发指南

更新时间:2020-03-03 11:19:42

链码结构

在 JAVA 语言的链码主要由以下方法组成:

  1. /**
  2. * Defines methods that all chaincodes must implement.
  3. */
  4. public interface Chaincode {
  5. /**
  6. *Called during an instantiate transaction after the container has been
  7. *established, allowing the chaincode to initialize its internal data
  8. */
  9. public Response init(ChaincodeStub stub);
  10. /**
  11. *Called for every Invoke transaction. The chaincode may change its state
  12. *variables.
  13. */
  14. public Response invoke(ChaincodeStub stub);
  15. }
  • init: 链码在初始化和升级时调用此接口,初始化相关的数据。
  • invoke:主要用于实现链码的内部业务逻辑,用户可以在该方法中实现相关的业务。
  • 在上述方法实现过程中,用户可以调用 ChaincodeStubImpl 的 API 接口和链上进行交互。

链码示例

Hyperledger Fabric 提供了很多官方链码样例,具体请参考fabric 官方示例。 我们以 Hyperledger Fabric 官方提供的 example02 样例为例,为大家介绍链码的开发规范。

简单示例

首先,我们看一个空链码结构的示例代码

  1. import java.util.List;
  2. import com.google.protobuf.ByteString;
  3. import io.netty.handler.ssl.OpenSsl;
  4. import org.apache.commons.logging.Log;
  5. import org.apache.commons.logging.LogFactory;
  6. import org.hyperledger.fabric.shim.ChaincodeBase;
  7. import org.hyperledger.fabric.shim.ChaincodeStub;
  8. import static java.nio.charset.StandardCharsets.UTF_8;
  9. /*
  10. * 一个管理资产的简单链码
  11. */
  12. public class SimpleAssetDemo extends ChaincodeBase {
  13. /*
  14. * 在链码实例化期时调用Init初始化数据
  15. */
  16. @Override
  17. public Response init(ChaincodeStub stub) {
  18. }
  19. /*
  20. * Invoke在每一比交易时都会被调用,该方法应该包含 set 以及 get 来创建和获取对应的键值
  21. */
  22. @Override
  23. public Response invoke(ChaincodeStub stub) {
  24. }
  25. public static void main(String[] args) {
  26. new SimpleAssetDemo().start(args);
  27. }
  28. }

init 示例

Init 函数在链码实例化以及升级的时候会被调用。在实现 Init 函数的过程中,可使用 JAVA 语言版本的合约 API 列表来对参数和分布式账本进行操作。

  1. @Override
  2. public Response init(ChaincodeStub stub) {
  3. try {
  4. _logger.info("Init java simple chaincode");
  5. // 调用 getFunction 方法获取当前调用的函数
  6. String func = stub.getFunction();
  7. if (!func.equals("init")) {
  8. return newErrorResponse("function other than init is not supported");
  9. }
  10. // 调用API getParameters 获取调用的参数
  11. List<String> args = stub.getParameters();
  12. if (args.size() != 4) {
  13. return newErrorResponse("Incorrect number of arguments. Expecting 4");
  14. }
  15. // 初始化相关数据
  16. String account1Key = args.get(0);
  17. int account1Value = Integer.parseInt(args.get(1));
  18. String account2Key = args.get(2);
  19. int account2Value = Integer.parseInt(args.get(3));
  20. _logger.info(String.format("account %s, value = %s; account %s, value %s", account1Key, account1Value, account2Key, account2Value));
  21. // 调用 putStringState 方法将数据写入账本中
  22. stub.putStringState(account1Key, args.get(1));
  23. stub.putStringState(account2Key, args.get(3));
  24. return newSuccessResponse();
  25. } catch (Throwable e) {
  26. return newErrorResponse(e);
  27. }
  28. }

本示例要求用户输入的参数为KEY1_NAME, VALUE1, KEY2_NAME, VALUE2,并初始化2个键值对,调用 putStringState 将数据写入分布式账本中。

invoke 示例

invoke 函数是对用户具体业务逻辑的实现,用户可以根据不同的业务处理逻辑,调用不用的业务处理函数,如invoke,delete 和 query 函数。

  1. // invoke把用户调用的function细分到几个子function, 包含invoke,delete和query
  2. @Override
  3. public Response invoke(ChaincodeStub stub) {
  4. try {
  5. _logger.info("Invoke java simple chaincode");
  6. String func = stub.getFunction();
  7. List<String> params = stub.getParameters();
  8. if (func.equals("invoke")) {
  9. return invoke(stub, params);
  10. }
  11. if (func.equals("delete")) {
  12. return delete(stub, params);
  13. }
  14. if (func.equals("query")) {
  15. return query(stub, params);
  16. }
  17. return newErrorResponse("Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]");
  18. } catch (Throwable e) {
  19. return newErrorResponse(e);
  20. }
  21. }
invoke 函数

业务逻辑 invoke 函数实现了业务逻辑中的资产转移,将 accountFrom 的资产转移 amount 个单位给 accountTo。

  1. // invoke实现了两个键之间的value转移,输入参数为KEY1_NAME, KEY2_NAME,VALUE
  2. private Response invoke(ChaincodeStub stub, List<String> args) {
  3. if (args.size() != 3) {
  4. return newErrorResponse("Incorrect number of arguments. Expecting 3");
  5. }
  6. String accountFromKey = args.get(0);
  7. String accountToKey = args.get(1);
  8. // 获取accountFromKey的当前资产情况
  9. String accountFromValueStr = stub.getStringState(accountFromKey);
  10. if (accountFromValueStr == null) {
  11. return newErrorResponse(String.format("Entity %s not found", accountFromKey));
  12. }
  13. int accountFromValue = Integer.parseInt(accountFromValueStr);
  14. // 获取accountToKey的当前资产情况
  15. String accountToValueStr = stub.getStringState(accountToKey);
  16. if (accountToValueStr == null) {
  17. return newErrorResponse(String.format("Entity %s not found", accountToKey));
  18. }
  19. int accountToValue = Integer.parseInt(accountToValueStr);
  20. int amount = Integer.parseInt(args.get(2));
  21. if (amount > accountFromValue) {
  22. return newErrorResponse(String.format("not enough money in account %s", accountFromKey));
  23. }
  24. // 业务逻辑:实现资产的转移
  25. accountFromValue -= amount;
  26. accountToValue += amount;
  27. _logger.info(String.format("new value of A: %s", accountFromValue));
  28. _logger.info(String.format("new value of B: %s", accountToValue));
  29. // 将更新后的资产更新到账本中
  30. stub.putStringState(accountFromKey, Integer.toString(accountFromValue));
  31. stub.putStringState(accountToKey, Integer.toString(accountToValue));
  32. _logger.info("Transfer complete");
  33. return newSuccessResponse("invoke finished successfully", ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " " + accountToKey + ": " + accountToValue, UTF_8).toByteArray());
  34. }
  • 使用 API getStringState 获取到 KEY_NAME 对应的资产总值
  • 调用业务逻辑实现 amount 个资产单位的转移
  • 调用 API putStringState 将更新后的资产情况写入到账本中

注:上述实现的是一个类似转账的简单逻辑,但并未对参数的合法性诸如转账金额大于零、余额不为负等进行校验。

delete 函数

业务逻辑 delete 函数实现了业务逻辑中的账户删除功能。

  1. // Deletes an entity from state
  2. private Response delete(ChaincodeStub stub, List<String> args) {
  3. if (args.size() != 1) {
  4. return newErrorResponse("Incorrect number of arguments. Expecting 1");
  5. }
  6. String key = args.get(0);
  7. // Delete the key from the state in ledger
  8. stub.delState(key);
  9. return newSuccessResponse();
  10. }

query 函数

业务逻辑 query 函数实现了业务逻辑中的账户查询功能,通过调用 API GetState 查询对应账户的资产。

  1. // query callback representing the query of a chaincode
  2. private Response query(ChaincodeStub stub, List<String> args) {
  3. if (args.size() != 1) {
  4. return newErrorResponse("Incorrect number of arguments. Expecting name of the person to query");
  5. }
  6. String key = args.get(0);
  7. //byte[] stateBytes
  8. String val = stub.getStringState(key);
  9. if (val == null) {
  10. return newErrorResponse(String.format("Error: state for %s is null", key));
  11. }
  12. _logger.info(String.format("Query Response:\nName: %s, Amount: %s\n", key, val));
  13. return newSuccessResponse(val, ByteString.copyFrom(val, UTF_8).toByteArray());
  14. }