使用宝塔搭建自己的区块链私链
不要装到 root 文件夹下面!
使用 WeBASE 一键部署可直接跳到第二节
0x00 虚拟机上的安装宝塔
建议使用 xshell 和 xftp 进行辅助搭建,具体使用教程请自行百度。
在虚拟机上部署宝塔环境,这里我用的是 CentOS 7 版本,安装命令如下:
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
其他版本的系统参见 宝塔官网
安装完成后会出现
此时,就可以使用相应的网址在浏览器中访问宝塔的面板了,初次会需要登录,账号密码如上图所示
0x01 搭建 FISCO BCOS 区块链网络
这里主要摘要 CentOS 版本系统的安装命令,其他版本系统简洁明了详细的参考文档 FISCO BCOS中文文档 ,这里的一切都是基于未安装过 FISCO BCOS 的条件完成的,如果是想要升级版本的话,请移步官方文档。
1、搭建单节点联盟链
1.1 搭建 FISCO BCOS 环境
使用命令 sudo yum install -y openssl openssl-devel
安装依赖
创建操作目录,下载安装脚本文件,虽然名字任意,但是还是建议命名为 fisco
## 创建操作目录
cd ~ && mkdir -p fisco && cd fisco
## 下载脚本
curl -#LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v2.7.2/build_chain.sh && chmod u+x build_chain.sh
如果因为网络问题导致长时间无法下载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
在 fisco 目录下执行下面的指令,生成一条单群组4节点的 FISCO 链。请确保机器的30300~30303,20200~20203,8545~8548
端口没有被占用。
bash build_chain.sh -l 127.0.0.1:4 -p 30300,20200,8545
成功后会提示 All completed。
启动 FISCO BCOS 链
bash nodes/127.0.0.1/start_all.sh
## 检查进程是否成功
## 正常情况会有类似下面的输出; 如果进程数不为4,则进程没有启动(一般是端口被占用导致的)
ps -ef | grep -v grep | grep fisco-bcos
## 检查日志输出
## 正常情况会不停地输出连接信息,从输出可以看出node0与另外3个节点有连接。
tail -f nodes/127.0.0.1/node0/log/log* | grep connected
## 检查是否在共识
## 正常情况会不停输出++++Generating seal,表示共识正常。
tail -f nodes/127.0.0.1/node0/log/log* | grep +++
2、配置及使用控制台
2.1 配置控制台
安装 java
#centos系统安装java
sudo yum install -y java java-devel
获取控制台并回到 fisco 目录
cd ~/fisco && curl -LO https://github.com/FISCO-BCOS/console/releases/download/v2.7.2/download_console.sh && bash download_console.sh
## 速度慢的话用这个
cd ~/fisco && curl -#LO https://gitee.com/FISCO-BCOS/console/raw/master/tools/download_console.sh
拷贝控制台配置文件
## 最新版本控制台使用如下命令拷贝配置文件
## 若节点未采用默认端口,请将文件中的20200替换成节点对应的channel端口。
cp -n console/conf/config-example.toml console/conf/config.toml
配置控制台证书
cp -r nodes/127.0.0.1/sdk/* console/conf/
2.2 使用控制台
## 启动
cd ~/fisco/console && bash start.sh
输出这样就算成功
## 查看版本
getNodeVersion
## 查看节点
getPeers
0x02 WeBase 的安装配置
WeBASE 的运行原理
1、环境安装
需要的环境
环境 | 版本 |
---|---|
Java | JDK 8 至JDK 14 |
MySQL | MySQL-5.6及以上 |
Python | Python3.6及以上 |
PyMySQL |
-
Java 刚才我们已经安装了
-
MySQL 可以在宝塔的面板里直接快捷安装
-
PyMySQL 安装
sudo yum -y install python36-pip sudo pip3 install PyMySQL
2、WeBASE 的部署
新建一个文件夹用于存放 WeBASE
mkdir webase
获取部署安装包:
# 建议直接挂个梯子在本机上下好后拖进虚拟机,不然可能会很慢,快的话当我没说
wget https://github.com/WeBankFinTech/WeBASELargeFiles/releases/download/v1.4.3/webase-deploy.zip
解压安装包:
unzip webase-deploy.zip
进入目录:
cd webase-deploy
解压刚才下好的压缩包
unzip webase-deploy.zip
进入解压后的目录
cd webase-deploy
Nginx 修改参见第三节,务必修改完毕再部署!
修改 common.properties 文件 vi common.properties
# 服务端口不能小于 1024
# WeBASE子系统的最新版本(v1.1.0或以上版本)
webase.web.version=v1.4.3
webase.mgr.version=v1.4.3
webase.sign.version=v1.4.3
webase.front.version=v1.4.3
# 节点管理子系统mysql数据库配置
# 将设置里的 user 和 password 改成自己的
mysql.ip=127.0.0.1
mysql.port=3306
mysql.user=dbUsername
mysql.password=dbPassword
mysql.database=webasenodemanager
# 签名服务子系统mysql数据库配置
sign.mysql.ip=localhost
sign.mysql.port=3306
sign.mysql.user=dbUsername
sign.mysql.password=dbPassword
sign.mysql.database=webasesign
# 节点前置子系统h2数据库名和所属机构
front.h2.name=webasefront
front.org=fisco
# WeBASE管理平台服务端口
web.port=5000
# 节点管理子系统服务端口
mgr.port=5001
# 节点前置子系统端口
front.port=5002
# 签名服务子系统端口
sign.port=5004
# 节点监听Ip
node.listenIp=127.0.0.1
# 节点p2p端口
node.p2pPort=30300
# 节点链上链下端口
node.channelPort=20200
# 节点rpc端口
node.rpcPort=8545
# Encrypt type (0: standard, 1: guomi)
encrypt.type=0
# ssl encrypt type (0: standard ssl, 1: guomi ssl)
# only guomi type support guomi ssl
encrypt.sslType=0
# 是否使用已有的链(yes/no)
if.exist.fisco=no
# 使用已有链时需配置
# 已有链的路径,start_all.sh脚本所在路径
# 路径下要存在sdk目录
# 当使用非国密链,或者使用国密链,但是sdk和节点使用非国密ssl连接时,sdk目录里存放非国密sdk证书(ca.crt、node.crt和node.key)
# 当使用国密链,并且sdk和节点使用国密ssl连接时,需在sdk目录里创建gm目录,gm目录存放国密sdk证书(gmca.crt、gmsdk.crt、gmsdk.key、gmensdk.crt和gmensdk.key)
fisco.dir=/data/app/nodes/127.0.0.1
# 前置所连接节点的绝对路径
# 路径下要存在conf文件夹,conf里存放节点证书(ca.crt、node.crt和node.key)
node.dir=/data/app/nodes/127.0.0.1/node0
# 搭建新链时需配置
# FISCO-BCOS版本
fisco.version=2.7.0
# 搭建节点个数(默认两个)
node.counts=nodeCounts
配置完成后执行 installAll 命令,部署服务将自动部署FISCO BCOS节点,并部署 WeBASE 中间件服务,包括签名服务(sign)、节点前置(front)、节点管理服务(node-mgr)、节点管理前端(web)
- 部署脚本会拉取相关安装包进行部署,需保持网络畅通
- 首次部署需要下载编译包和初始化数据库,重复部署时可以根据提示不重复操作
- 部署过程中出现报错时,可根据错误提示进行操作,或根据本文档中的常见问题进行排查
- 不要用sudo执行脚本,例如
sudo python3 deploy.py installAll
(sudo会导致无法获取当前用户的环境变量如JAVA_HOME),如果没有用 sudo 命令还是出现了无法获取 JAVA_HOME 环境变量的情况,请参见 Linux 下无法获取 JAVA_HOME 环境变量的解决方案
# 部署并启动所有服务
python3 deploy.py installAll
执行过程中可能会报错端口正在被占用,那么只需要执行命令 vi common.properties
进行相应的端口更换即可,全部部署完毕后
后续使用命令
# 一键部署
部署并启动所有服务 python3 deploy.py installAll
停止一键部署的所有服务 python3 deploy.py stopAll
启动一键部署的所有服务 python3 deploy.py startAll
# 各子服务启停
启动FISCO-BCOS节点: python3 deploy.py startNode
停止FISCO-BCOS节点: python3 deploy.py stopNode
启动WeBASE-Web: python3 deploy.py startWeb
停止WeBASE-Web: python3 deploy.py stopWeb
启动WeBASE-Node-Manager: python3 deploy.py startManager
停止WeBASE-Node-Manager: python3 deploy.py stopManager
启动WeBASE-Sign: python3 deploy.py startSign
停止WeBASE-Sign: python3 deploy.py stopSign
启动WeBASE-Front: python3 deploy.py startFront
停止WeBASE-Front: python3 deploy.py stopFront
# 可视化部署
部署并启动可视化部署的所有服务 python3 deploy.py installWeBASE
停止可视化部署的所有服务 python3 deploy.py stopWeBASE
启动可视化部署的所有服务 python3 deploy.py startWeBASE
3、检查执行
执行命令
$ ps -ef | grep node
$ ps -ef | grep webase.front
## 检查节点管理服务 webase-node-manager 的进程
$ ps -ef | grep webase.node.mgr
。。。。其余的参照官网上的进行检查
0x03 Nginx 的问题
当我们使用宝塔安装 Nginx 后,需要在 webase-deploy/comm/temp.conf 中修改 /etc/nginx/mime.types 的文件位置为我们自己的nginx 目录,一般来说用宝塔安装的都在根目录下的 www 文件夹下
在 webase-deploy/comm/ 文件夹下执行命令 vi temp.conf
进行更改
之后回到 webase-deploy 目录下执行命令 python3 deploy.py installAll
重新安装部署整个项目
之后若无问题应该就可以直接访问了,初始账号为 admin ,密码为 Abcd1234
之后的配置就参见官方文档了 WeBASE使用手册
0x04 智能合约的部署及应用
1、新建合约
pragma solidity>=0.4.24 <0.6.11;
// 导入 kv 表的合约
import "./Table.sol";
contract Modeus {
event SetResult(int256 count);
KVTableFactory tableFactory;
string constant TABLE_NAME = "t_proof";
constructor() public {
//The fixed address is 0x1010 for KVTableFactory
tableFactory = KVTableFactory(0x1010);
// the parameters of createTable are tableName,keyField,"vlaueFiled1,vlaueFiled2,vlaueFiled3,..."
// 创建 kv 表单,id:序号,sid:数据库所做修改对应的 id ,base 修改前,newbase 修改后
tableFactory.createTable(TABLE_NAME, "id", "sid,base,newbase");
}
// 获取链上的信息
function get(string memory id) public view returns (bool, string memory, string memory,string memory) {
KVTable table = tableFactory.openTable(TABLE_NAME);
bool ok = false;
Entry entry;
(ok, entry) = table.get(id);
string memory sid;
string memory base;
string memory newbase;
if (ok) {
sid= entry.getString("sid");
base = entry.getString("base");
newbase = entry.getString("newbase");
}
return (ok, sid, base,newbase);
}
// 数据库的修改上链
function set(string memory id, string memory sid, string memory base,string memory newbase)
public
returns (int256)
{
KVTable table = tableFactory.openTable(TABLE_NAME);
Entry entry = table.newEntry();
// the length of entry's field value should < 16MB
entry.set("id", id);
entry.set("sid", sid);
entry.set("base", base);
entry.set("newbase", newbase);
// the first parameter length of set should <= 255B
int256 count = table.set(id, entry);
emit SetResult(count);
return count;
}
}
官方的 table.sol 代码如下:
contract TableFactory {
function openTable(string memory) public view returns (Table) {} //open table
function createTable(string memory, string memory, string memory) public returns (int256) {} //create table
}
//select condition
contract Condition {
function EQ(string memory, int256) public {}
function EQ(string memory, string memory) public {}
function NE(string memory, int256) public {}
function NE(string memory, string memory) public {}
function GT(string memory, int256) public {}
function GE(string memory, int256) public {}
function LT(string memory, int256) public {}
function LE(string memory, int256) public {}
function limit(int256) public {}
function limit(int256, int256) public {}
}
//one record
contract Entry {
function getInt(string memory) public view returns (int256) {}
function getUInt(string memory) public view returns (int256) {}
function getAddress(string memory) public view returns (address) {}
function getBytes64(string memory) public view returns (bytes1[64] memory) {}
function getBytes32(string memory) public view returns (bytes32) {}
function getString(string memory) public view returns (string memory) {}
function set(string memory, int256) public {}
function set(string memory, uint256) public {}
function set(string memory, string memory) public {}
function set(string memory, address) public {}
}
//record sets
contract Entries {
function get(int256) public view returns (Entry) {}
function size() public view returns (int256) {}
}
//Table main contract
contract Table {
function select(string memory, Condition) public view returns (Entries) {}
function insert(string memory, Entry) public returns (int256) {}
function update(string memory, Entry, Condition) public returns (int256) {}
function remove(string memory, Condition) public returns (int256) {}
function newEntry() public view returns (Entry) {}
function newCondition() public view returns (Condition) {}
}
contract KVTableFactory {
function openTable(string memory) public view returns (KVTable) {}
function createTable(string memory, string memory, string memory) public returns (int256) {}
}
//KVTable per permiary key has only one Entry
contract KVTable {
function get(string memory) public view returns (bool, Entry) {}
function set(string memory, Entry) public returns (int256) {}
function newEntry() public view returns (Entry) {}
}
编写完成后进行编译,编译成功后进行部署
部署完成后进行本地测试
交易发起后去首页查看是否有交易记录,若有则成功
2、智能合约的本地调用
这里我们选择使用 rest api 来调用智能合约,使用 post 方式接口 http://localhost:5002/WeBASE-Front/trans/handleWithSign
这里可以看一下官方文档中的相关部分接口文档 handleWithSign 文档
1)参数表
序号 | 中文 | 参数名 | 类型 | 最大长度 | 必填 | 说明 |
---|---|---|---|---|---|---|
1 | 用户地址 | user | String | 是 | 用户地址,可通过/privateKey 接口创建 |
|
2 | 合约名称 | contractName | String | 是 | ||
3 | 合约地址 | contractAddress | String | 是 | ||
4 | 方法名 | funcName | String | 是 | ||
5 | 合约编译后生成的abi文件内容 | contractAbi | List | 是 | 合约中单个函数的ABI,若不存在同名函数可以传入整个合约ABI,格式:JSONArray | |
6 | 方法参数 | funcParam | List | 否 | JSON数组,多个参数以逗号分隔(参数为数组时同理),如:["str1",["arr1","arr2"]],根据所调用的合约方法判断是否必填 | |
7 | 群组ID | groupId | int | 是 | 默认为1 | |
8 | 合约路径 | contractPath | int | 否 | ||
9 | 是否使用cns调用 | useCns | bool | 是 | ||
10 | cns名称 | cnsName | String | 否 | CNS名称,useCns为true时不能为空 | |
11 | cns版本 | version | String | 否 | CNS版本,useCns为true时不能为空 |
然后去构建参数列表,格式为:
{
"groupId" :1,
"signUserId": "458ecc77a08c486087a3dcbc7ab5a9c3",
"contractAbi":[{"constant":true,"inputs":[],"name":"getVersion","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStorageCell","outputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"n","type":"string"}],"name":"setVersion","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"storageHash","type":"string"},{"name":"storageInfo","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],
"contractAddress":"0x14d5af9419bb5f89496678e3e74ce47583f8c166",
"funcName":"set",
"funcParam":["test"],
"useCns":false
}
其中的 signUserId
在私钥管理中可以查询到,如果没有私钥的话新建一个
contractAbi
和 contractAddress(合约地址)
可以在 合约管理 -> 合约列表 中查询到。
将模板中的 funcParam
更换为自己编写的智能合约中的参数列表对应的数据。使用 postman 进行测试
如图,测试成功。
3、在应用中使用智能合约
编写工具类 BlockChainUtils
package utils;
import cn.hutool.http.HttpUtil;
public class BlockChainUtils {
// public static long id = 1;
public static String set(String id ,String sid,String base,String newBase){
String body="{
" +
"
" +
" "groupId" :1,
" +
" "signUserId": "bbc341c4e982xxxxxxxxxxx76167f00ab4842e",
" +
" "contractAbi":[{"constant":true,"inputs":[{"name":"id","type":"string"}],"name":"get","outputs":[{"name":"","type":"bool"},{"name":"","type":"int256"},{"name":"","type":"string"},{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"string"},{"name":"sid","type":"int256"},{"name":"nickname","type":"string"},{"name":"content","type":"string"}],"name":"set","outputs":[{"name":"","type":"int256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"count","type":"int256"}],"name":"SetResult","type":"event"}],
" +
" "contractAddress":"0xef860c2xxxx24b38bxxe1b317ad2",
" +
" "funcName":"set",
" +
" "funcParam":[""+comment.getId()+"","+comment.getSid()+",""+comment.getNickname()+"",""+comment.getContent()+""]
" +
"
" +
"}";
System.out.println(body);
String url = "http://xxxxxxxxx:5002/WeBASE-Front/trans/handleWithSign";
String res = HttpUtil.post(url,body);
return res;
// id ++;
}
public static void get(){}
}
然后在其他地方调用 set
方法就行了。