跳到主要内容

📠 Ubuntu搭建Fisco区块链

1. 搭建单群组四节点FISCO BCOS联盟链

使用开发部署工具build_chain.sh脚本快速搭建FISCO BCOS区块链, 本次以Ubuntu 16.04 64bit系统为例操作.

部署工具build_chain脚本目标是让用户最快地搭建,对于企业级应用部署FISCO BCOS请参考运维部署工具.

官方推荐下载预编译程序进行使用. 同时提供Docker程序与未编译源码.

ARM架构linux与X86架构macOS需要自行编译, ARM架构macOS暂未适配.

CentOS系统下openssl默认路径在开始搭建时可能会出现路径错误.

推荐新手开发者使用Ubuntu平台搭建.

第一步: 安装依赖


  • 使用build_chain.sh脚本需要先安装openssl与curl. 因此先安装这两个依赖.
sudo apt install -y openssl curl

第二步: 创建目录, 下载脚本


  • 首先创建fisco目录, 并且进入刚创建的目录.
cd ~ && mkdir -p fisco && cd fisco
  • 通过curl下载build_chain.sh脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.7.2/build_chain.sh && chmod u+x build_chain.sh

如果因为网络问题无法正常下载, 可以尝试更换链接 curl -#LO https://osp-1257653870.cos.ap-guangzhou.myqcloud.com/FISCO-BCOS/FISCO-BCOS/releases/v2.7.2/build_chain.sh && chmod u+x build_chain.sh

第三步: 搭建联盟链


注意要确保机器的30300~30303,20200~20203,8545~8548端口没有被占用, 否则会导致后续无法成功搭建.

  • 在fisco目录下执行指令, 生成一条单群组4节点的FISCO链.
bash build_chain.sh -l 127.0.0.1:4 -p 30300,20200,8545

-p选项为起始端口, 30300为p2p_port, 20200为channel_port, 8545为jsonrpc_port. 更多搭建时的选项可以参考开发部署工具.

命令执行成功会在最后一行显示[INFO] All completed. Files in /home/ubuntu/fisco/nodes

第四步: 启动节点


  • 通过脚本启动所有所有节点
bash nodes/127.0.0.1/start_all.sh

四个节点均启动成功时会显示

try to start node0

try to start node1

try to start node2

try to start node3

node0 start successfully

node1 start successfully

node2 start successfully

node3 start successfully

  • 验证节点是否被成功启动
ps -ef | grep -v grep | grep fisco-bcos

若显示四行数据, 则FISCO BCOS链成功启动

第五步: 检查日志输出


  • 查看节点链接的其他节点个数
tail -f nodes/127.0.0.1/node0/log/log*  | grep connected

如果输出正常中每十秒会弹出一行信息.

info|2021-06-25 09:06:04.855163|[P2P][Service] heartBeat,connected count=3

info|2021-06-25 09:06:14.859185|[P2P][Service] heartBeat,connected count=3

info|2021-06-25 09:06:24.863178|[P2P][Service] heartBeat,connected count=3

输出中可以看到第0个节点与其他3个节点相连.

如果检查其他节点, 输出信息相同.

  • 检查链中是否存在共识
tail -f nodes/127.0.0.1/node0/log/log*  | grep +++

如果共识正常会弹出如下信息.

info|2021-06-25 08:59:58.199388|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=c7e899af...

info|2021-06-25 09:08:20.095493|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=fbe312af...

info|2021-06-25 09:08:24.135362|[g:1][CONSENSUS][SEALER]++++++++++++++++ Generating seal on,blkNum=1,tx=0,nodeIdx=2,hash=6e7ef1a2...

2. 配置与使用控制台

第一步: 准备控制台依赖


我们通过脚本下载的程序为最新版本. 2.6版本之后的控制台均用Java SDK实现.

新版控制台文档参考此处.

  • 安装Java
sudo apt install -y default-jdk
  • 回到fisco目录并且下载并运行download_console的脚本
cd ~/fisco && curl -LO https://github.com/FISCO-BCOS/console/releases/download/v2.7.2/download_console.sh && bash download_console.sh

如果因为网络问题无法正常下载, 可以尝试将中间的链接替换为 curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh

  • 将默认的控制台配置拷贝到对应的路径与名称
cp -n console/conf/config-example.toml console/conf/config.toml
  • 将控制台证书拷贝到对应路径
cp -r nodes/127.0.0.1/sdk/* console/conf/

第二步: 启动并使用控制台


  • 进入控制台路径并启动控制台
cd ~/fisco/console && bash start.sh

控制台启动成功后会显示版本号以及由$符号组成的ASCII art

Welcome to FISCO BCOS console(2.7.2)!

  • 在控制台中获取信息
===================================================
[group:1]> getNodeVersion
ClientVersion{
version='2.7.2',
supportedVersion='2.7.2',
chainId='1',
buildTime='20210201 10:03:03',
buildType='Linux/clang/Release',
gitBranch='HEAD',
gitCommitHash='4c8a5bbe44c19db8a002017ff9dbb16d3d28e9da'
}

[group:1]> getPeers
[
PeerInfo{
nodeID='6d9f62cea16879e16ffe4004c64fa717b62c3853c915f79306972da0dac4133f25b97bedfe522d982a3fd606c2ac90ef085a45f1786ea1e5dbd4ad8fdd57b97e',
iPAndPort='127.0.0.1:50050',
node='node3',
agency='agency',
topic='[

]'
},
PeerInfo{
nodeID='fae1c9ff63b77de57759555d0cfcf01f43db418f7d75ecc969c1262364b3d6ae0d53ae92e05adf259e4a56f91369d66ca29c4e9c4e8a6342d23bbdcd9e2fb4ab',
iPAndPort='127.0.0.1:50042',
node='node1',
agency='agency',
topic='[
_block_notify_1
]'
},
PeerInfo{
nodeID='4fd5a7d73b3f002e76c2e1adbcbdbc0903197a2a10cf449ddc6507d33cd050e414b52aef9d5444f9206aec2cfc0d976e235b10fd9d4fa56c8168e59a6b234ada',
iPAndPort='127.0.0.1:30302',
node='node2',
agency='agency',
topic='[

]'
}
]

3. 部署并调用HelloWorld的智能合约

第一步: 部署合约


  • 为了方便用户快速体验, HelloWorld的合约已经内置于控制台中, 位于控制台目录下contracts/solidity/HelloWorld.sol. 我们直接部署HelloWorld合约.
deploy HelloWorld

合约具体内容为:

pragma solidity ^0.4.24;

contract HelloWorld {
// 声明变量name
string name;

// 将name值更新为Hello, World!
function HelloWorld() {
name = "Hello, World!";
}

// 调用get方法时返回name的值
function get()constant returns(string) {
return name;
}

// 调用set方法时修改name的值为n
function set(string n) {
name = n;
}
}

部署成功后会返回合约地址:

transaction hash: 0x24d98970eb355755f66f8b5e8ba4b94a1e69de0ef53fdff9f0a011ce4c57bdaa

contract address: {address hash}

currentAccount: 0xe6925c66ad07d894f066a2229addb2b350d373c3

  • 查看当前区块高度, 调用get接口获取name变量
[group:1]> getBlockNumber
1

[group:1]> call HelloWorld {address hash} get
---------------------------------------------------------------------------------------------
Return code: 0
description: transaction executed successfully
Return message: Success
---------------------------------------------------------------------------------------------
Return value size:1
Return types: (STRING)
Return values:(Hello, World!)
---------------------------------------------------------------------------------------------

[group:1]> getBlockNumber
1

get接口不会改变账本状态, 所以调用之后区块高度仍然为1.

  • 调用set接口设置name变量
[group:1]> call HelloWorld {address hash} set "Hello, Fisco BcoS"
transaction hash: 0x08e2f53419d1e327aa38fa3ce3d6e529927e4f2bac05bea757406b27bf122f1d
---------------------------------------------------------------------------------------------
transaction status: 0x0
description: transaction executed successfully
---------------------------------------------------------------------------------------------
Receipt message: Success
Return message: Success
Return values:[]
---------------------------------------------------------------------------------------------
Event logs
Event: {}

[group:1]> getBlockNumber
2

[group:1]> call HelloWorld {address hash} get
---------------------------------------------------------------------------------------------
Return code: 0
description: transaction executed successfully
Return message: Success
---------------------------------------------------------------------------------------------
Return value size:1
Return types: (STRING)
Return values:(Hello, Fisco BcoS)
---------------------------------------------------------------------------------------------

再次查看区块高度会发现高度已改变, 再次调用get接口发现返回值已经修改为我们set的值.

FISCO BCOS区块链简易的资产管理的开发全过程

我们将会开发一个简易的资产管理的系统,并最终实现以下功能:

  • 能够在区块链上进行资产账户注册
  • 能够实现不同账户之间的转账
  • 可以查询账户的资产金额

第一步: 应用程序设计

  • 存储设计:

针对需要实现的功能, 本应用需要设计存储资产管理的表t_asset. 内容如下:

accountasset_value
Alex1000
Bob2000
  • 接口设计:

按照业务的设计目标,需要实现资产注册,转账,查询资产金额功能,对应功能的接口如下:

// 查询账户的资产金额
return值一: 成功找到账户返回0, 账户不存在返回-1
return值二: 资产金额(第一个参数为0时有效)
function select(string account) public constant returns(int256, uint256)
// 资产账户注册
0 资产账户注册成功
-1 资产账户已存在
-2 其他错误
function register(string account, uint256 amount) public returns(int256)
// 账户间的资产转移
返回值:
0 资产转移成功
-1 转移资产账户不存在
-2 接收资产账户不存在
-3 金额不足
-4 金额溢出
-5 其他错误
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)

第二步: 开发接口源码


  • 编写智能合约
// 进入console/contracts/solidity目录
cd ~/fisco/console/contracts/solidity
// 创建Asset.sol合约文件, 进入文件的编写
vi Asset.sol

// 将合约内容写入Asset.sol文件.
// ** 重要: vim中保存退出为:wq **

Assert.sol的内容如下:

pragma solidity ^0.4.24;

// Table.sol已在~/fisco/console/contracts/solidity目录下.
// 该系统合约文件中的接口由FISCO BCOS底层实现.
// 当业务合约需要操作CRUD接口时,均需要引入该接口合约文件.
import "./Table.sol";

contract Asset {
event RegisterEvent(int256 ret, string account, uint256 asset_value);
event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);

constructor() public {
// 构造函数中创建t_asset表
createTable();
}

function createTable() private {
TableFactory tf = TableFactory(0x1001);
// 资产管理表, key : account, field : asset_value
// | 资产账户(key) | 资产金额(field) |
// |-------------------- |-------------------|
// | account | asset_value |
// |---------------------|-------------------|
tf.createTable("t_asset", "account", "asset_value");
}

function openTable() private returns(Table) {
TableFactory tf = TableFactory(0x1001);
Table table = tf.openTable("t_asset");
return table;
}

/*
描述 : 根据资产账户查询资产金额
参数 :
account : 资产账户
返回值:
参数一: 成功找到账户返回0, 账户不存在返回-1
参数二: 资产金额(第一个参数为0时有效)
*/
function select(string account) public constant returns(int256, uint256) {
// 打开表
Table table = openTable();
// 查询
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")));
}
}

/*
描述 : 资产注册
参数 :
account : 资产账户
amount : 资产金额
返回值:
0 资产账户注册成功
-1 资产账户已存在
-2 其他错误
*/
function register(string account, uint256 asset_value) public returns(int256){
int256 ret_code = 0;
int256 ret= 0;
uint256 temp_asset_value = 0;
// 查询账户是否存在
(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));
// 插入
int count = table.insert(account, entry);
if (count == 1) {
// 成功
ret_code = 0;
} else {
// 失败? 无权限或者其他错误
ret_code = -2;
}
} else {
// 账户已存在
ret_code = -1;
}

emit RegisterEvent(ret_code, account, asset_value);

return ret_code;
}

/*
描述 : 资产转移
参数 :
from_account : 转移资产的账户
to_account : 接收资产的账户
amount : 转移金额数量
返回值:
0 资产转移成功
-1 转移资产账户不存在
-2 接收资产账户不存在
-3 金额不足
-4 金额溢出
-5 其他错误
*/
function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
// 查询转移资产账户信息
int ret_code = 0;
int256 ret = 0;
uint256 from_asset_value = 0;
uint256 to_asset_value = 0;

// 转移账户是否存在?
(ret, from_asset_value) = select(from_account);
if(ret != 0) {
ret_code = -1;
// 转移账户不存在
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;

}

// 接受账户是否存在?
(ret, to_asset_value) = select(to_account);
if(ret != 0) {
ret_code = -2;
// 接收资产的账户不存在
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}

if(from_asset_value < amount) {
ret_code = -3;
// 转移资产的账户金额不足
emit TransferEvent(ret_code, from_account, to_account, amount);
return ret_code;
}

if (to_asset_value + amount < to_asset_value) {
ret_code = -4;
// 接收账户金额溢出
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));
// 更新转账账户
int count = table.update(from_account, entry0, table.newCondition());
if(count != 1) {
ret_code = -5;
// 失败? 无权限或者其他错误?
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));
// 更新接收账户
table.update(to_account, entry1, table.newCondition());

emit TransferEvent(ret_code, from_account, to_account, amount);

return ret_code;
}
}
  • 保存并退出后运行ls, 可以看到Assert.sol和Table.sol.

第三步: 编译智能合约


控制台提供的编译工具可以编译出ABI和BIN文件, 使得.sol文件可以部署到区块链上.

同时还会自动生成一个与编译的智能合约同名的根据ABI生成的合约Java类. 这个Java类帮助用户解析好了参数, 提供同名的方法.

当应用需要部署和调用合约时, 可以直接调用该合约类的对应方法, 传入指定参数即可.

  • 编译合约
// 切换到fisco/console/目录
cd ~/fisco/console/

// 编译合约, 指定Java的包名参数
./sol2java.sh org.fisco.bcos.asset.contract

运行成功后会显示INFO: Compile for solidity TableTest.sol success.


第四步: 创建区块链应用项目

本步骤项目源码可以通过如下命令直接获得, 获得后可以直接跳过本步骤:

cd ~/fisco

curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz

# 解压得到Java工程项目asset-app
tar -zxf asset-app.tar.gz
  1. 安装Java与IDE

在开发环境下载Java JDK 14(JDK1.8 至JDK 14都支持); 下载IntelliJ IDE.

  1. 创建Java工程

在IntelliJ IDE中创建一个新项目, 选择Gradle, 勾选Java, 输入工程名.

  1. 引入Java SDK

在build.gradle文件中替换repositories, 加入Java SDK的引用.

repositories {
mavenCentral()
maven {
url "http://maven.aliyun.com/nexus/content/groups/public/"
}
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}

将dependencies替换, 引入Java SDK jar包.

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2')
}
  1. 配置SDK证书
  • 修改build.gradle文件, 引入Spring框架.
// 加入
def spring_version = "4.3.27.RELEASE"
List spring = [
"org.springframework:spring-core:$spring_version",
"org.springframework:spring-beans:$spring_version",
"org.springframework:spring-context:$spring_version",
"org.springframework:spring-tx:$spring_version",
]

//替换
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2")
compile spring
}
  • asset-app/test/resources目录下创建配置文件applicationContext.xml, 写入配置内容(如下).
<?xml version="1.0" encoding="UTF-8" ?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="defaultConfigProperty" class="org.fisco.bcos.sdk.config.model.ConfigProperty">
<property name="cryptoMaterial">
<map>
<entry key="certPath" value="conf" />
</map>
</property>
<property name="network">
<map>
<entry key="peers">
<list>
<value>127.0.0.1:20200</value>
<value>127.0.0.1:20201</value>
</list>
</entry>
</map>
</property>
<property name="account">
<map>
<entry key="keyStoreDir" value="account" />
<entry key="accountAddress" value="" />
<entry key="accountFileFormat" value="pem" />
<entry key="password" value="" />
<entry key="accountFilePath" value="" />
</map>
</property>
<property name="threadPool">
<map>
<entry key="channelProcessorThreadSize" value="16" />
<entry key="receiptProcessorThreadSize" value="16" />
<entry key="maxBlockingQueueSize" value="102400" />
</map>
</property>
</bean>

<bean id="defaultConfigOption" class="org.fisco.bcos.sdk.config.ConfigOption">
<constructor-arg name="configProperty">
<ref bean="defaultConfigProperty"/>
</constructor-arg>
</bean>

<bean id="bcosSDK" class="org.fisco.bcos.sdk.BcosSDK">
<constructor-arg name="configOption">
<ref bean="defaultConfigOption"/>
</constructor-arg>
</bean>
</beans>
  • 我们指定了证书存放的位certPath的值为conf, 接下去我们把SDK用于连接节点的证书放到指定的conf目录下. 如果路径不相同, 需要自行调整.
# 假设我们将asset-app放在~/fisco目录下 进入~/fisco目录
$ cd ~/fisco
# 创建放置证书的文件夹
$ mkdir -p asset-app/src/test/resources/conf
# 拷贝节点证书到项目的资源目录
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/test/resources/conf
# 若在IDE直接运行,拷贝证书到resources路径
$ mkdir -p asset-app/src/main/resources/conf
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/main/resources/conf
  1. 业务逻辑开发
  • 通过3中的编译配置进行编译, 将合约引入到项目中.
cd ~/fisco
cp console/contracts/sdk/java/org/fisco/bcos/asset/contract/Asset.java asset-app/src/main/java/org/fisco/bcos/asset/contract/Asset.java
  • 开发业务逻辑

/src/main/java/org/fisco/bcos/asset/client目录下,创建AssetClient.java类,调用Asset.java实现对合约的部署与调用.

AssetClient.java:

package org.fisco.bcos.asset.client;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Properties;
import org.fisco.bcos.asset.contract.Asset;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated.Tuple2;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

public class AssetClient {
static Logger logger = LoggerFactory.getLogger(AssetClient.class);

private BcosSDK bcosSDK;
private Client client;
private CryptoKeyPair cryptoKeyPair;

public void initialize() throws Exception {
// 初始化BcoS的SDK
@SuppressWarnings("resource")
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
// 初始化可向群组1发交易的Client
client = bcosSDK.getClient(1);
// 随机生成发送交易的公钥与私钥, 并且匹配给Client
cryptoKeyPair = client.getCryptoSuite().createKeyPair();
client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
}

public void deployAssetAndRecordAddr() {

try {
// 初次部署合约
Asset asset = Asset.deploy(client, cryptoKeyPair);
System.out.println(
" deploy Asset success, contract address is " + asset.getContractAddress());

recordAssetAddr(asset.getContractAddress());
} catch (Exception e) {
System.out.println(" deploy Asset contract failed, error message is " + e.getMessage());
}
}

public void recordAssetAddr(String address) throws FileNotFoundException, IOException {
Properties prop = new Properties();
prop.setProperty("address", address);
final Resource contractResource = new ClassPathResource("contract.properties");
FileOutputStream fileOutputStream = new FileOutputStream(contractResource.getFile());
prop.store(fileOutputStream, "contract address");
}

public String loadAssetAddr() throws Exception {
// load Asset contact address from contract.properties
Properties prop = new Properties();
final Resource contractResource = new ClassPathResource("contract.properties");
prop.load(contractResource.getInputStream());

String contractAddress = prop.getProperty("address");
if (contractAddress == null || contractAddress.trim().equals("")) {
throw new Exception(" load Asset contract address failed, please deploy it first. ");
}
logger.info(" load Asset address from contract.properties, address is {}", contractAddress);
return contractAddress;
}

public void queryAssetAmount(String assetAccount) {
try {
// 已知合约地址或已部署合约时加载合约
String contractAddress = loadAssetAddr();
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
// 调用select接口
Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
if (result.getValue1().compareTo(new BigInteger("0")) == 0) {
System.out.printf(" asset account %s, value %s \n", assetAccount, result.getValue2());
} else {
System.out.printf(" %s asset account is not exist \n", assetAccount);
}
} catch (Exception e) {
logger.error(" queryAssetAmount exception, error message is {}", e.getMessage());

System.out.printf(" query asset account failed, error message is %s\n", e.getMessage());
}
}

public void registerAssetAccount(String assetAccount, BigInteger amount) {
try {
String contractAddress = loadAssetAddr();

Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
// 调用register接口
TransactionReceipt receipt = asset.register(assetAccount, amount);
List<Asset.RegisterEventEventResponse> response = asset.getRegisterEventEvents(receipt);
if (!response.isEmpty()) {
if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
System.out.printf(
" register asset account success => asset: %s, value: %s \n", assetAccount, amount);
} else {
System.out.printf(
" register asset account failed, ret code is %s \n", response.get(0).ret.toString());
}
} else {
System.out.println(" event log not found, maybe transaction not exec. ");
}
} catch (Exception e) {
logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
}
}

public void transferAsset(String fromAssetAccount, String toAssetAccount, BigInteger amount) {
try {
String contractAddress = loadAssetAddr();
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
// 调用transfer接口
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);
List<Asset.TransferEventEventResponse> response = asset.getTransferEventEvents(receipt);
if (!response.isEmpty()) {
if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
System.out.printf(
" transfer success => from_asset: %s, to_asset: %s, amount: %s \n",
fromAssetAccount, toAssetAccount, amount);
} else {
System.out.printf(
" transfer asset account failed, ret code is %s \n", response.get(0).ret.toString());
}
} else {
System.out.println(" event log not found, maybe transaction not exec. ");
}
} catch (Exception e) {
// TODO Auto-generated catch block
// e.printStackTrace();

logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
}
}

public static void Usage() {
System.out.println(" Usage:");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient deploy");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient query account");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient register account value");
System.out.println(
"\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient transfer from_account to_account amount");
System.exit(0);
}

public static void main(String[] args) throws Exception {
if (args.length < 1) {
Usage();
}

AssetClient client = new AssetClient();
client.initialize();

switch (args[0]) {
case "deploy":
client.deployAssetAndRecordAddr();
break;
case "query":
if (args.length < 2) {
Usage();
}
client.queryAssetAmount(args[1]);
break;
case "register":
if (args.length < 3) {
Usage();
}
client.registerAssetAccount(args[1], new BigInteger(args[2]));
break;
case "transfer":
if (args.length < 4) {
Usage();
}
client.transferAsset(args[1], args[2], new BigInteger(args[3]));
break;
default:
{
Usage();
}
}
System.exit(0);
}

}
  • asset-app/tool目录下添加一个调用AssetClient的脚本asset_run.sh. 内容如下:
#!/bin/bash

function usage()
{
echo " Usage : "
echo " bash asset_run.sh deploy"
echo " bash asset_run.sh query asset_account "
echo " bash asset_run.sh register asset_account asset_amount "
echo " bash asset_run.sh transfer from_asset_account to_asset_account amount "
echo " "
echo " "
echo "examples : "
echo " bash asset_run.sh deploy "
echo " bash asset_run.sh register Asset0 10000000 "
echo " bash asset_run.sh register Asset1 10000000 "
echo " bash asset_run.sh transfer Asset0 Asset1 11111 "
echo " bash asset_run.sh query Asset0"
echo " bash asset_run.sh query Asset1"
exit 0
}

case $1 in
deploy)
[ $# -lt 1 ] && { usage; }
;;
register)
[ $# -lt 3 ] && { usage; }
;;
transfer)
[ $# -lt 4 ] && { usage; }
;;
query)
[ $# -lt 2 ] && { usage; }
;;
*)
usage
;;
esac

java -Djdk.tls.namedGroups="secp256k1" -cp 'apps/*:conf/:lib/*' org.fisco.bcos.asset.client.AssetClient $@
  • asset-app/test/resources目录下创建log4j.properties. 内容如下:
### set log levels ###
log4j.rootLogger=DEBUG, file

### output the log information to the file ###
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyyMMddHH'.log'
log4j.appender.file.File=./log/sdk.log
log4j.appender.file.Append=true
log4j.appender.file.filter.traceFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n

###output the log information to the console ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n
  • 修改build.gradle文件中的编译任务.
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.7.2")
compile spring
compile ('org.slf4j:slf4j-log4j12:1.7.25')
runtime ('org.slf4j:slf4j-log4j12:1.7.25')
}
  • 配置添加build.gradle文件中的编译任务. 直接加入到底部即可.
jar {
destinationDir file('dist/apps')
archiveName project.name + '.jar'
exclude '**/*.xml'
exclude '**/*.properties'
exclude '**/*.crt'
exclude '**/*.key'

doLast {
copy {
from configurations.runtime
into 'dist/lib'
}
copy {
from file('src/test/resources/')
into 'dist/conf'
}
copy {
from file('tool/')
into 'dist/'
}
copy {
from file('src/test/resources/contract')
into 'dist/contract'
}
}
}
  • 引入日志库, 在asset-app/test/resources目录下, 创建一个空的contract.properties文件, 用于应用在运行时存放合约地址.
  1. 完成开发

最终的目录结构如下:

|-- build.gradle // gradle配置文件
|-- gradle
| |-- wrapper
| |-- gradle-wrapper.jar // 用于下载Gradle的相关代码实现
| |-- gradle-wrapper.properties // wrapper所使用的配置信息,比如gradle的版本等信息
|-- gradlew // Linux或者Unix下用于执行wrapper命令的Shell脚本
|-- gradlew.bat // Windows下用于执行wrapper命令的批处理脚本
|-- src
| |-- main
| | |-- java
| | | |-- org
| | | |-- fisco
| | | |-- bcos
| | | |-- asset
| | | |-- client // 放置客户端调用类
| | | |-- AssetClient.java
| | | |-- contract // 放置Java合约类
| | | |-- Asset.java
| | |-- resources
| | |-- conf
| | |-- ca.crt
| | |-- node.crt
| | |-- node.key
| | |-- sdk.crt
| | |-- sdk.key
| | |-- sdk.publickey
| | |-- applicationContext.xml // 项目配置文件
| | |-- contract.properties // 存储部署合约地址的文件
| | |-- log4j.properties // 日志配置文件
| | |-- contract //存放solidity约文件
| | |-- Asset.sol
| | |-- Table.sol
| |-- test
| |-- resources // 存放代码资源文件
| |-- conf
| |-- ca.crt
| |-- node.crt
| |-- node.key
| |-- sdk.crt
| |-- sdk.key
| |-- sdk.publickey
| |-- applicationContext.xml // 项目配置文件
| |-- contract.properties // 存储部署合约地址的文件
| |-- log4j.properties // 日志配置文件
| |-- contract //存放solidity约文件
| |-- Asset.sol
| |-- Table.sol
|
|-- tool
|-- asset_run.sh // 项目运行脚本
  1. 运行应用
  • 部署应用

    • 编译项目

      cd ~/fisco/asset-app
      ./gradlew build
    • 部署Asset.sol合约

      # 进入dist目录
      cd dist
      bash asset_run.sh deploy
  • 应用的主要功能:

    • 注册资产账户

      bash asset_run.sh register Foo 100000
    • 查询账户资产

      bash asset_run.sh query Foo
    • 转移账户资产

      bash asset_run.sh transfer Foo Bar  50000

以上就是全部内容.