参考教程:开发第一个区块链应用 — FISCO BCOS v2.9.0 文档 (fisco-bcos-documentation.readthedocs.io)
如果您还未搭建区块链网络,或未下载控制台,请先走完教程搭建第一个区块链网络,再回到本教程。
走完《搭建第一个区块链网络》后,将会部署好FISCO BCOS控制台,同时~/fisco等目录也已创建好,官方文档在《开发第一个区块链应用》中的极少数步骤(例如创建fisco目录、下载控制台等)也可以跳过了。
本博客仅用于个人记录学习过程(也就是不管原理、先跑起来再说),对于更详尽全面的教程内容、需求与设计等,请参考官方文档而非本文章。
操作系统:CentOS 7
java版本:1.8
FISCO BCOS控制台版本:2.9.2(在启动控制台时可以查看版本。如何启动?详见搭建第一个区块链网络 — FISCO BCOS v2.9.0 文档)
目录
一、源码
创建一个Asset的智能合约,实现注册、转账、查询功能。
打开终端、输入命令,进入对应目录、创建 Asset.sol合约:
$ cd ~/fisco/console/contracts/solidity
$ vi Asset.sol
Asset.sol内容如下,直接拷贝进去:
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 {
// 构造函数中创建t_asset表
createTable();
}
function createTable() private {
TableFactory tf = TableFactory(0x1001);
// 资产管理表, key : account, field : asset_value
// | 资产账户(主键) | 资产金额 |
// |-------------------- |-------------------|
// | 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;
}
}
Asset.sol所引用的Table.sol已在~/fisco/console/contracts/solidity目录下。Table是系统合约,提供了CRUD接口。
二、编译智能合约
在终端输入命令,切换到fisco/console/目录:
$ cd ~/fisco/console/
由于我的控制台版本是2.9.2,执行以下命令:
# 若控制台版本大于等于2.8.0,编译合约方法如下:
$ bash sol2java.sh -p org.fisco.bcos.asset.contract
如果小于2.8.0,则使用以下命令:
$ ./sol2java.sh org.fisco.bcos.asset.contract
运行成功之后,将会在console/contracts/sdk目录生成java、abi和bin目录,如下所示。
# 其它无关文件省略
|-- abi # 生成的abi目录,存放solidity合约编译生成的abi文件
| |-- Asset.abi
| |-- Table.abi
|-- bin # 生成的bin目录,存放solidity合约编译生成的bin文件
| |-- Asset.bin
| |-- Table.bin
|-- contracts # 存放solidity合约源码文件,将需要编译的合约拷贝到该目录下
| |-- Asset.sol # 拷贝进来的Asset.sol合约,依赖Table.sol
| |-- Table.sol # 实现系统CRUD操作的合约接口文件
|-- java # 存放编译的包路径及Java合约文件
| |-- org
| |--fisco
| |--bcos
| |--asset
| |--contract
| |--Asset.java # Asset.sol合约生成的Java文件
| |--Table.java # Table.sol合约生成的Java文件
|-- sol2java.sh
但是注意!编译运行成功后是不会生成contracts目录的!(我当时没注意这里,所以后面没启动成功)
但我复盘时也没想明白,为什么我都没有contracts目录和合约文件,却可以有两个合约对应的java文件?
这里需要自己创建contracts目录、把合约文件拷贝进来。
# 在~/fisco/console/contracts/sdk下创建contracts目录
$ cd ~/fisco/console/contracts/sdk
$ mkdir contracts
# 从solidity目录下拷贝合约文件到sdk/contracts里
$ cd ~/fisco/console/contracts/solidity
$ cp Asset.sol ~/fisco/console/contracts/sdk/contracts
$ cp Table.sol ~/fisco/console/contracts/sdk/contracts
三、区块链应用项目
因为源码已经引入FISCO BCOS Java SDK、配置SDK证书、将编译好的Java合约引入项目中、完成AssetClient.java类、完成调用AssetClient的脚本asset_run.sh、配置log,所以我就直接引入源码了(项目名为asset-app):
$ cd ~/fisco
$ curl -#LO https://github.com/FISCO-BCOS/LargeFiles/raw/master/tools/asset-app.tar.gz
$ tar -zxf asset-app.tar.gz
(这里想从零开始写也可以,详细参考官方文档)
四、运行应用
# 编译项目
$ cd ~/fisco/asset-app
$ ./gradlew build
把编译智能合约后生成的Asset.java拷贝到项目对应的目录下:
$ 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
检查节点进程是否启动:
$ ps aux |grep -i fisco-bcos
如果没有启动的话,则启动全部节点:
$ cd ~/fisco
$ bash nodes/127.0.0.1/start_all.sh
确认节点全部启动后,部署Asset.sol合约: (如果节点没有启动,这一步会报错)
$ cd dist
$ bash asset_run.sh deploy
如果这里SDK连接节点失败了,可以参考该文档进行排查:SDK连接节点失败排查思路 — FISCO BCOS v2.9.0 文档
部署Asset.sol合约成功后,会出现“deploy Asset success, contract address is ”的提示。
继而可以验证以下功能:
注册资产:
$ bash asset_run.sh register Alice 100000
$ bash asset_run.sh register Bob 100000
查询资产:
$ bash asset_run.sh query Alice
$ bash asset_run.sh query Bob
资产转移:
$ bash asset_run.sh transfer Alice Bob 50000
$ bash asset_run.sh query Alice
$ bash asset_run.sh query Bob
注册资产、查询资产与资产转移的运行结果如下 :