本章将介绍基于FISCO BCOS区块链的业务应用场景开发的全流程。介绍包括业务场景分析、合约设计实现、合约编译、区块链开发等。最后,我们介绍一个应用模块实现,即通过我们提供的Java SDK实现对区块链上合约的调用访问。
本教程要求用户熟悉Linux操作环境,具备Java开发的基本技能,能够使用Gradle工具,熟悉Solidity语法。
通过本教程,您将了解以下内容:
- 如何以契约的形式表达业务场景的逻辑
- 如何将Solidity合约转换为Java类
- 如何配置Java开发工具包
- 如何构建应用程序并将Java SDK集成到应用程序工程中
- 如何通过Java SDK调用合约接口,并理解其原理
本教程中提供了该示例的完整项目源代码,用户可以基于它快速开发自己的应用程序。
示例应用程序要求
区块链自然是防篡改和可追溯的。这些特点使其对金融部门更具吸引力。本文将提供资产管理开发的简单示例,并最终实现以下功能:
- 能够在区块链上注册资产
- 能够从不同的账户转移资金
- 能够检查账户中的资产数量
合同设计和实施
在区块链上开发应用时,为了结合业务需求,首先需要设计相应的智能合约,确定合约需要的存储数据,并在此基础上确定智能合约提供的接口。最后,具体实现每个接口。
存储设计
FISCO BCOS提供了合约CRUD接口开发模型,可以通过合约创建表,并对创建的表进行添加、删除和修改。对于此应用程序,我们需要设计一个用于存储资产管理的表。该表的字段如下所示:t_asset
- 账户:主键、资产账户(字符串类型)
- asset_value:资产金额(UINT256型)
account是主键,是操作表时需要传递的字段。区块链根据主键字段查询表中的匹配记录。表的示例如下:t_assett_asset
界面设计
根据业务的设计目标,需要实现资产登记、转账、查询功能。对应函数的接口如下:
// query the amount of assets
function select(string account) public constant returns(int256, uint256)
// asset registration
function register(string account, uint256 amount) public returns(int256)
// asset transfer
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)
完整来源
pragma solidity ^0.4.24;
import "./Table.sol";
contract Asset {
// event
event RegisterEvent(int256 ret, string account, uint256 asset_value);
event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);
constructor() public {
// create a t_asset table in the constructor
createTable();
}
function createTable() private {
TableFactory tf = TableFactory(0x1001);
// asset management table, key : account, field : asset_value
// | account(primary key) | amount |
// |-------------------- |-------------------|
// | account | asset_value |
// |---------------------|-------------------|
//
// create table
tf.createTable("t_asset", "account", "asset_value");
}
function openTable() private returns(Table) {
TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_asset");
return table;
}
/*
description: query asset amount according to asset account
parameter:
account: asset account
return value:
parameter1: successfully returns 0, the account does not exist and returns -1
parameter2: valid when the first parameter is 0, the amount of assets
*/
function select(string account) public constant returns(int256, uint256) {
// open table
Table table = openTable();
// query
Entries entries = table.select(account, table.newCondition());
uint256 asset_value = 0;
if (0 == uint256(entries.size())) {
return (-1, asset_value);
} else {
Entry entry = entries.get(0);
return (0, uint256(entry.getInt("asset_value")));
}
}
/*
description : asset registration
parameter :
account : asset account
amount : asset amount
return value:
0 regist successfully
-1 asset account already exists
-2 other error
*/
function register(string account, uint256 asset_value) public returns(int256){
int256 ret_code = 0;
int256 ret= 0;
uint256 temp_asset_value = 0;
// to query whather the account exists
(ret, temp_asset_value) = select(account);
if(ret != 0) {
Table table = openTable();
Entry entry = table.newEntry();
entry.set("account", account);
entry.set("asset_value", int256(asset_value));
// insert
int count = table.insert(account, entry);
if (count == 1) {
// true
ret_code = 0;
} else {
// false. no permission or other error
ret_code = -2;
}
} else {
// account already exists
ret_code = -1;
}
emit RegisterEvent(ret_code, account, asset_value);
return ret_code;
}
/*
description : asset transfer
parameter :
from_account : transferred asset account
to_account :received asset account
amount : transferred amount
return value:
0 transfer asset successfully
-1 transfe asset account does not exist
-2 receive asset account does not exist
-3 amount is insufficient
-4 amount is excessive
-5 other error
*/
function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
// query transferred asset account information
int ret_code = 0;
int256 ret = 0;
uint256 from_asset_value = 0;
uint256 to_asset_value = 0;
// whather transferred asset account exists?
(ret, from_asset_value) = select(from_account);
if(ret != 0) {
ret_code = -1;
// not exist
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
// whather received asset account exists?
(ret, to_asset_value) = select(to_account);
if(ret != 0) {
ret_code = -2;
// not exist
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
if(from_asset_value < amount) {
ret_code = -3;
// amount of transferred asset account is insufficient
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
if (to_asset_value + amount < to_asset_value) {
ret_code = -4;
// amount of received asset account is excessive
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
Table table = openTable();
Entry entry0 = table.newEntry();
entry0.set("account", from_account);
entry0.set("asset_value", int256(from_asset_value - amount));
// update transferred account
int count = table.update(from_account, entry0, table.newCondition());
if(count != 1) {
ret_code = -5;
// false? no permission or other error?
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
Entry entry1 = table.newEntry();
entry1.set("account", to_account);
entry1.set("asset_value", int256(to_asset_value + amount));
// update received account
table.update(to_account, entry1, table.newCondition());
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}
}
注意:合约的实现需要引入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
$ mkdir -p ~/fisco
# download console
$ cd ~/fisco && curl -#LO https://github.com/FISCO-BCOS/console/releases/download/v2.9.2/download_console.sh && bash download_console.sh
# switch to the fisco/console/ directory
$ cd ~/fisco/console/
# compile the contract, specify a Java package name parameter later, you can specify the package name according to the actual project path.
$ ./sol2java.sh -p org.fisco.bcos.asset.contract
操作成功后,java、abi、bin目录将生成如下图。console/contracts/sdkdirectory
|-- abi # The generated abi directory, which stores the abi file generated by Solidity contract compilation.
| |-- Asset.abi
| |-- Table.abi
|-- bin # The generated bin directory, which stores the bin file generated by Solidity contract compilation.
| |-- Asset.bin
| |-- Table.bin
|-- contracts # The source code file that stores Solidity contract. Copy the contract that needs to be compiled to this directory.
| |-- Asset.sol # A copied Asset.sol contract, depends on Table.sol
| |-- Table.sol # The contract interface file that implements the CRUD operation
|-- java # Storing compiled package path and Java contract file
| |-- org
| |--fisco
| |--bcos
| |--asset
| |--contract
| |--Asset.java # Java file generated by the Asset.sol contract
| |--Table.java # Java file generated by the Table.sol contract
|-- sol2java.sh
包路径目录在java目录中生成。该目录包含两个文件和,其中是Java应用程序调用Asset.sol合约所需的文件。
org/fisco/bcos/asset/contract/Asset.javaTable.javaAsset.java
Asset.java的主界面:
package org.fisco.bcos.asset.contract;
public class Asset extends Contract {
// Asset.sol contract transfer interface generation
public TransactionReceipt transfer(String from_account, String to_account, BigInteger amount);
// Asset.sol contract register interface generation
public TransactionReceipt register(String account, BigInteger asset_value);
// Asset.sol contract select interface generation
public Tuple2<BigInteger, BigInteger> select(String account) throws ContractException;
// Load the Asset contract address, to generate Asset object
public static Asset load(String contractAddress, Client client, CryptoKeyPair credential);
// Deploy Assert.sol contract, to generate Asset object
public static Asset deploy(Client client, CryptoKeyPair credential) throws ContractException;
}
加载和部署函数用于构造Asset对象,其他接口用于调用相应solidity协定的接口。具体用法将在下面介绍。
开发工具包配置
我们为开发提供了一个Java工程项目。首先,获取Java工程项目:
$ mkdir -p ~/fisco
# get the Java project project archive
$ cd ~/fisco
$ curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz
# extract the Java project project asset-app directory
$ 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
资产应用项目的目录结构如下:
|-- build.gradle // gradle configuration file
|-- gradle
| |-- wrapper
| |-- gradle-wrapper.jar // related code implementation for downloading Gradle
| |-- gradle-wrapper.properties // Configuration information used by the wrapper, such as the version of gradle
|-- gradlew // shell script for executing wrapper commands under Linux or Unix
|-- gradlew.bat // batch script for executing wrapper commands under Windows
|-- src
| |-- main
| | |-- java
| | |-- org
| | |-- fisco
| | |-- bcos
| | |-- asset
| | |-- client // the client calling class
| | |-- AssetClient.java
| | |-- contract // the Java contract class
| | |-- Asset.java
| |-- test
| |-- resources // resource files
| |-- applicationContext.xml // project configuration file
| |-- contract.properties // file that stores the deployment contract address
| |-- log4j.properties // log configuration file
| |-- contract // Solidity contract files
| |-- Asset.sol
| |-- Table.sol
|
|-- tool
|-- asset_run.sh // project running script
项目介绍Java SDK
该项目的文件已引入Java SDK,无需修改。介绍方法如下:
- 您需要将maven远程存储库添加到文件中:build.gradle
repositories {
mavenCentral()
maven {
url "http://maven.aliyun.com/nexus/content/groups/public/"
}
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
- 介绍Java SDKjar包
compile ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2')
证书和配置文件
- 区块链节点证书配置
复制区块链节点对应的SDK证书
# go to the ~ directory
# copy the node certificate to the project's resource directory
$ cd ~/fisco
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/test/resources/conf
# if you want to run this app in IDE, copy the certificate to the main resource directory
$ mkdir -p asset-app/src/main/resources/conf
$ 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和凭证的对象,在创建相应的合约类对象(调用合约类的部署或加载函数)时需要使用这些对象。
@SuppressWarnings("resource")
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
// init the client that can send requests to the group one
client = bcosSDK.getClient(1);
// create the keyPair
cryptoKeyPair = client.getCryptoSuite().createKeyPair();
client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
- 构造协定类对象
合约对象可以使用部署或加载函数进行初始化,这些函数在不同场景下使用。前者适用于初始部署协定,后者在协定已部署且合约地址已知时使用。
// deploy contract
Asset asset = Asset.deploy(client, cryptoKeyPair);
// load contract address
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
- 接口调用
使用合约对象调用对应的接口,处理返回的结果。
// select interface calling
Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
// register interface calling
TransactionReceipt receipt = asset.register(assetAccount, amount);
// transfer interface
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);
运行
到目前为止,我们已经介绍了使用区块链的资产管理应用程序的所有流程并实现了这些功能。然后我们可以运行项目并测试函数是否正常。
- 汇编
# switch to project directory
$ cd ~/asset-app
# compile project
$ ./gradlew build
编译成功后,将在项目根目录下生成目录。dist目录中有一个脚本来简化项目操作。现在,让我们首先验证本文中列出的要求。distasset_run.sh
- 部署合约Asset.sol
# enter dist directory
$ cd dist
$ bash asset_run.sh deploy
Deploy Asset successfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
- 注册资产
$ bash asset_run.sh register Alice 100000
Register account successfully => account: Alice, value: 100000
$ bash asset_run.sh register Bob 100000
Register account successfully => account: Bob, value: 100000
- 查询资产
$ bash asset_run.sh query Alice
account Alice, value 100000
$ bash asset_run.sh query Bob
account Bob, value 100000
- 转移资产
$ bash asset_run.sh transfer Alice Bob 50000
Transfer successfully => from_account: Alice, to_account: Bob, amount: 50000
$ bash asset_run.sh query Alice
account Alice, value 50000
$ bash asset_run.sh query Bob
account Bob, value 150000
总结:到目前为止,我们已经通过合约开发、合约编译、SDK配置和业务开发,构建了基于FISCO BCOS联盟区块链的应用。