• FISCO BCOS | 构建第一个区块链应用程序


    本章将介绍基于FISCO BCOS区块链的业务应用场景开发的全流程。介绍包括业务场景分析、合约设计实现、合约编译、区块链开发等。最后,我们介绍一个应用模块实现,即通过我们提供的Java SDK实现对区块链上合约的调用访问。

    本教程要求用户熟悉Linux操作环境,具备Java开发的基本技能,能够使用Gradle工具,熟悉Solidity语法。

    通过本教程,您将了解以下内容:

    1. 如何以契约的形式表达业务场景的逻辑
    2. 如何将Solidity合约转换为Java类
    3. 如何配置Java开发工具包
    4. 如何构建应用程序并将Java SDK集成到应用程序工程中
    5. 如何通过Java SDK调用合约接口,并理解其原理

    本教程中提供了该示例的完整项目源代码,用户可以基于它快速开发自己的应用程序。

    示例应用程序要求

    区块链自然是防篡改和可追溯的。这些特点使其对金融部门更具吸引力。本文将提供资产管理开发的简单示例,并最终实现以下功能:

    • 能够在区块链上注册资产
    • 能够从不同的账户转移资金
    • 能够检查账户中的资产数量

    合同设计和实施

    在区块链上开发应用时,为了结合业务需求,首先需要设计相应的智能合约,确定合约需要的存储数据,并在此基础上确定智能合约提供的接口。最后,具体实现每个接口。

    存储设计

    FISCO BCOS提供了合约CRUD接口开发模型,可以通过合约创建表,并对创建的表进行添加、删除和修改。对于此应用程序,我们需要设计一个用于存储资产管理的表。该表的字段如下所示:t_asset

    • 账户:主键、资产账户(字符串类型)
    • asset_value:资产金额(UINT256型)

    account是主键,是操作表时需要传递的字段。区块链根据主键字段查询表中的匹配记录。表的示例如下:t_assett_asset

    图片

    界面设计

    根据业务的设计目标,需要实现资产登记、转账、查询功能。对应函数的接口如下:

    1. // query the amount of assets
    2. function select(string account) public constant returns(int256, uint256)
    3. // asset registration
    4. function register(string account, uint256 amount) public returns(int256)
    5. // asset transfer
    6. function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)

    完整来源

    1. pragma solidity ^0.4.24;
    2. import "./Table.sol";
    3. contract Asset {
    4. // event
    5. event RegisterEvent(int256 ret, string account, uint256 asset_value);
    6. event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);
    7. constructor() public {
    8. // create a t_asset table in the constructor
    9. createTable();
    10. }
    11. function createTable() private {
    12. TableFactory tf = TableFactory(0x1001);
    13. // asset management table, key : account, field : asset_value
    14. // | account(primary key) | amount |
    15. // |-------------------- |-------------------|
    16. // | account | asset_value |
    17. // |---------------------|-------------------|
    18. //
    19. // create table
    20. tf.createTable("t_asset", "account", "asset_value");
    21. }
    22. function openTable() private returns(Table) {
    23. TableFactory tf = TableFactory(0x1001);
    24. Table table = tf.openTable("t_asset");
    25. return table;
    26. }
    27. /*
    28. description: query asset amount according to asset account
    29. parameter:
    30. account: asset account
    31. return value
    32. parameter1: successfully returns 0, the account does not exist and returns -1
    33. parameter2valid when the first parameter is 0, the amount of assets
    34. */
    35. function select(string account) public constant returns(int256, uint256) {
    36. // open table
    37. Table table = openTable();
    38. // query
    39. Entries entries = table.select(account, table.newCondition());
    40. uint256 asset_value = 0;
    41. if (0 == uint256(entries.size())) {
    42. return (-1, asset_value);
    43. } else {
    44. Entry entry = entries.get(0);
    45. return (0, uint256(entry.getInt("asset_value")));
    46. }
    47. }
    48. /*
    49. description : asset registration
    50. parameter :
    51. account : asset account
    52. amount : asset amount
    53. return value
    54. 0 regist successfully
    55. -1 asset account already exists
    56. -2 other error
    57. */
    58. function register(string account, uint256 asset_value) public returns(int256){
    59. int256 ret_code = 0;
    60. int256 ret= 0;
    61. uint256 temp_asset_value = 0;
    62. // to query whather the account exists
    63. (ret, temp_asset_value) = select(account);
    64. if(ret != 0) {
    65. Table table = openTable();
    66. Entry entry = table.newEntry();
    67. entry.set("account", account);
    68. entry.set("asset_value", int256(asset_value));
    69. // insert
    70. int count = table.insert(account, entry);
    71. if (count == 1) {
    72. // true
    73. ret_code = 0;
    74. } else {
    75. // false. no permission or other error
    76. ret_code = -2;
    77. }
    78. } else {
    79. // account already exists
    80. ret_code = -1;
    81. }
    82. emit RegisterEvent(ret_code, account, asset_value);
    83. return ret_code;
    84. }
    85. /*
    86. description : asset transfer
    87. parameter :
    88. from_account : transferred asset account
    89. to_account :received asset account
    90. amount : transferred amount
    91. return value
    92. 0 transfer asset successfully
    93. -1 transfe asset account does not exist
    94. -2 receive asset account does not exist
    95. -3 amount is insufficient
    96. -4 amount is excessive
    97. -5 other error
    98. */
    99. function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
    100. // query transferred asset account information
    101. int ret_code = 0;
    102. int256 ret = 0;
    103. uint256 from_asset_value = 0;
    104. uint256 to_asset_value = 0;
    105. // whather transferred asset account exists?
    106. (ret, from_asset_value) = select(from_account);
    107. if(ret != 0) {
    108. ret_code = -1;
    109. // not exist
    110. emit TransferEvent(ret_code, from_account, to_account, amount);
    111. return ret_code;
    112. }
    113. // whather received asset account exists?
    114. (ret, to_asset_value) = select(to_account);
    115. if(ret != 0) {
    116. ret_code = -2;
    117. // not exist
    118. emit TransferEvent(ret_code, from_account, to_account, amount);
    119. return ret_code;
    120. }
    121. if(from_asset_value < amount) {
    122. ret_code = -3;
    123. // amount of transferred asset account is insufficient
    124. emit TransferEvent(ret_code, from_account, to_account, amount);
    125. return ret_code;
    126. }
    127. if (to_asset_value + amount < to_asset_value) {
    128. ret_code = -4;
    129. // amount of received asset account is excessive
    130. emit TransferEvent(ret_code, from_account, to_account, amount);
    131. return ret_code;
    132. }
    133. Table table = openTable();
    134. Entry entry0 = table.newEntry();
    135. entry0.set("account", from_account);
    136. entry0.set("asset_value", int256(from_asset_value - amount));
    137. // update transferred account
    138. int count = table.update(from_account, entry0, table.newCondition());
    139. if(count != 1) {
    140. ret_code = -5;
    141. // false? no permission or other error?
    142. emit TransferEvent(ret_code, from_account, to_account, amount);
    143. return ret_code;
    144. }
    145. Entry entry1 = table.newEntry();
    146. entry1.set("account", to_account);
    147. entry1.set("asset_value", int256(to_asset_value + amount));
    148. // update received account
    149. table.update(to_account, entry1, table.newCondition());
    150. emit TransferEvent(ret_code, from_account, to_account, amount);
    151. return ret_code;
    152. }
    153. }

    注意:合约的实现需要引入FISCO BCOS提供的系统合约接口文件。系统合约文件的接口由底层FISCO BCOS实现。当业务合约需要操作CRUD接口时,需要引入接口合约文件。合同详细接口参考在这里:

    ​https://fisco-bcos-documentation.readthedocs.io/en/latest/docs/manual/smart_contract.html#crud​

    Asset.solTable.solTable.sol

    合同编制

    在上一节中,我们根据业务需求设计了合约的存储和接口,并将它们完整地实现了。但是,Java程序不能直接调用Solidity合约。Solidity合约文件需要先编译成Java文件。Asset.sol

    控制台提供了一个编译工具,用于将合约文件存储在目录中。使用控制台目录中提供的脚本进行编译,如下所示:

    Asset.solconsole/contract/soliditysol2java.sh

    1. $ mkdir -p ~/fisco
    2. # download console
    3. $ cd ~/fisco && curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v2.9.2/download_console.sh && bash download_console.sh
    4. # switch to the fisco/console/ directory
    5. $ cd ~/fisco/console/
    6. # compile the contract, specify a Java package name parameter later, you can specify the package name according to the actual project path.
    7. $ ./sol2java.sh -p org.fisco.bcos.asset.contract

    操作成功后,java、abi、bin目录将生成如下图。console/contracts/sdkdirectory

    1. |-- abi # The generated abi directory, which stores the abi file generated by Solidity contract compilation.
    2. | |-- Asset.abi
    3. | |-- Table.abi
    4. |-- bin # The generated bin directory, which stores the bin file generated by Solidity contract compilation.
    5. | |-- Asset.bin
    6. | |-- Table.bin
    7. |-- contracts # The source code file that stores Solidity contract. Copy the contract that needs to be compiled to this directory.
    8. | |-- Asset.sol # A copied Asset.sol contract, depends on Table.sol
    9. | |-- Table.sol # The contract interface file that implements the CRUD operation
    10. |-- java # Storing compiled package path and Java contract file
    11. | |-- org
    12. | |--fisco
    13. | |--bcos
    14. | |--asset
    15. | |--contract
    16. | |--Asset.java # Java file generated by the Asset.sol contract
    17. | |--Table.java # Java file generated by the Table.sol contract
    18. |-- sol2java.sh

    包路径目录在java目录中生成。该目录包含两个文件和,其中是Java应用程序调用Asset.sol合约所需的文件。

    org/fisco/bcos/asset/contract/Asset.javaTable.javaAsset.java

    Asset.java的主界面:

    1. package org.fisco.bcos.asset.contract;
    2. public class Asset extends Contract {
    3. // Asset.sol contract transfer interface generation
    4. public TransactionReceipt transfer(String from_account, String to_account, BigInteger amount);
    5. // Asset.sol contract register interface generation
    6. public TransactionReceipt register(String account, BigInteger asset_value);
    7. // Asset.sol contract select interface generation
    8. public Tuple2<BigInteger, BigInteger> select(String account) throws ContractException;
    9. // Load the Asset contract address, to generate Asset object
    10. public static Asset load(String contractAddress, Client client, CryptoKeyPair credential);
    11. // Deploy Assert.sol contract, to generate Asset object
    12. public static Asset deploy(Client client, CryptoKeyPair credential) throws ContractException;
    13. }

    加载和部署函数用于构造Asset对象,其他接口用于调用相应solidity协定的接口。具体用法将在下面介绍。

    开发工具包配置

    我们为开发提供了一个Java工程项目。首先,获取Java工程项目:

    1. $ mkdir -p ~/fisco
    2. # get the Java project project archive
    3. $ cd ~/fisco
    4. $ curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz
    5. # extract the Java project project asset-app directory
    6. $ tar -zxf asset-app.tar.gz

    如果资产app.tar.gz由于网络问题长时间无法下载,请尝试:

    curl-#LOhttps://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/tools/asset-app.tar.gz

    资产应用项目的目录结构如下:

    1. |-- build.gradle // gradle configuration file
    2. |-- gradle
    3. | |-- wrapper
    4. | |-- gradle-wrapper.jar // related code implementation for downloading Gradle
    5. | |-- gradle-wrapper.properties // Configuration information used by the wrapper, such as the version of gradle
    6. |-- gradlew // shell script for executing wrapper commands under Linux or Unix
    7. |-- gradlew.bat // batch script for executing wrapper commands under Windows
    8. |-- src
    9. | |-- main
    10. | | |-- java
    11. | | |-- org
    12. | | |-- fisco
    13. | | |-- bcos
    14. | | |-- asset
    15. | | |-- client // the client calling class
    16. | | |-- AssetClient.java
    17. | | |-- contract // the Java contract class
    18. | | |-- Asset.java
    19. | |-- test
    20. | |-- resources // resource files
    21. | |-- applicationContext.xml // project configuration file
    22. | |-- contract.properties // file that stores the deployment contract address
    23. | |-- log4j.properties // log configuration file
    24. | |-- contract // Solidity contract files
    25. | |-- Asset.sol
    26. | |-- Table.sol
    27. |
    28. |-- tool
    29. |-- asset_run.sh // project running script

    项目介绍Java SDK

    该项目的文件已引入Java SDK,无需修改。介绍方法如下:

    • 您需要将maven远程存储库添加到文件中:build.gradle
    1. repositories {
    2. mavenCentral()
    3. maven {
    4. url "http://maven.aliyun.com/nexus/content/groups/public/"
    5. }
    6. maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
    7. }

    • 介绍Java SDKjar包
    compile ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2')

    证书和配置文件

    • 区块链节点证书配置

    复制区块链节点对应的SDK证书

    1. # go to the ~ directory
    2. # copy the node certificate to the project's resource directory
    3. $ cd ~/fisco
    4. $ cp -r nodes/127.0.0.1/sdk/* asset-app/src/test/resources/conf
    5. # if you want to run this app in IDE, copy the certificate to the main resource directory
    6. $ mkdir -p asset-app/src/main/resources/conf
    7. $ cp -r nodes/127.0.0.1/sdk/* asset-app/src/main/resources/conf

    • 应用程序上下文.xml

    注意:

    如果链中设置的channel_listen_ip(如果节点版本低于v2.3.0,勾选listen_ip)为127.0.0.1或0.0.0.0,channel_listen_port为20200,则无需修改配置。如果区块链节点的配置发生变化,则需要修改。

    applicationContext.xmlapplicationContext.xml

    业务发展

    我们已经介绍了如何在您自己的项目中引入和配置Java SDK。本节介绍如何通过Java程序调用合约,以及一个示例资产管理说明。资产应用项目已包含示例的完整源代码,用户可以直接使用。现在介绍核心类的设计和实现。AssetClient

    AssetClient.java:合约的部署和调用是通过调用来实现的,路径、初始化和调用过程都在这个类中。

    Asset.java/src/main/java/org/fisco/bcos/asset/client

    • 初始化

    初始化代码的主要功能是构造Web3j和凭证的对象,在创建相应的合约类对象(调用合约类的部署或加载函数)时需要使用这些对象。

    1. @SuppressWarnings("resource")
    2. ApplicationContext context =
    3. new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    4. bcosSDK = context.getBean(BcosSDK.class);
    5. // init the client that can send requests to the group one
    6. client = bcosSDK.getClient(1);
    7. // create the keyPair
    8. cryptoKeyPair = client.getCryptoSuite().createKeyPair();
    9. client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
    10. logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());

    • 构造协定类对象

    合约对象可以使用部署或加载函数进行初始化,这些函数在不同场景下使用。前者适用于初始部署协定,后者在协定已部署且合约地址已知时使用。

    1. // deploy contract
    2. Asset asset = Asset.deploy(client, cryptoKeyPair);
    3. // load contract address
    4. Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);

    • 接口调用

    使用合约对象调用对应的接口,处理返回的结果。

    1. // select interface calling
    2. Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
    3. // register interface calling
    4. TransactionReceipt receipt = asset.register(assetAccount, amount);
    5. // transfer interface
    6. TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);

    运行

    到目前为止,我们已经介绍了使用区块链的资产管理应用程序的所有流程并实现了这些功能。然后我们可以运行项目并测试函数是否正常。

    • 汇编
    1. # switch to project directory
    2. $ cd ~/asset-app
    3. # compile project
    4. $ ./gradlew build

    编译成功后,将在项目根目录下生成目录。dist目录中有一个脚本来简化项目操作。现在,让我们首先验证本文中列出的要求。distasset_run.sh

    • 部署合约Asset.sol
    1. # enter dist directory
    2. $ cd dist
    3. $ bash asset_run.sh deploy
    4. Deploy Asset successfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
    • 注册资产
    1. $ bash asset_run.sh register Alice 100000
    2. Register account successfully => account: Alice, value: 100000
    3. $ bash asset_run.sh register Bob 100000
    4. Register account successfully => account: Bob, value: 100000
    • 查询资产
    1. $ bash asset_run.sh query Alice
    2. account Alice, value 100000
    3. $ bash asset_run.sh query Bob
    4. account Bob, value 100000

    • 转移资产
    1. $ bash asset_run.sh transfer Alice Bob 50000
    2. Transfer successfully => from_account: Alice, to_account: Bob, amount: 50000
    3. $ bash asset_run.sh query Alice
    4. account Alice, value 50000
    5. $ bash asset_run.sh query Bob
    6. account Bob, value 150000

    总结:到目前为止,我们已经通过合约开发、合约编译、SDK配置和业务开发,构建了基于FISCO BCOS联盟区块链的应用。

  • 相关阅读:
    nginx详细配置
    制作本地kubernetes镜像仓库(使用reposync、createrepo、httpd)
    基于Java的高校科研信息管理系统设计与实现(亮点:完整严谨的科研项目审批流程、多文件上传、多角色)
    Unity的IPreprocessComputeShaders:深入解析与实用案例
    处理conda安装工具的动态库问题——解决记录 libssl.1.0.0 系统中所有openssl位置全览 whereis openssl
    k8s之pod控制器
    SpringBoot-39-Dubbo环境搭建
    WPF 控件专题 TreeView控件详解
    uniapp使用nfc功能及详解
    Web3 来了,让我们展开双手拥抱它吧!
  • 原文地址:https://blog.csdn.net/BSN_yanxishe/article/details/133788585