使用宝塔搭建自己的区块链私链

不要装到 root 文件夹下面!

使用 WeBASE 一键部署可直接跳到第二节

建议使用 xshell 和 xftp 进行辅助搭建,具体使用教程请自行百度。

在虚拟机上部署宝塔环境,这里我用的是 CentOS 7 版本,安装命令如下:

yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh

其他版本的系统参见 宝塔官网

安装完成后会出现

记一下使用 WeBASE 搭建自己的联盟链过程

此时,就可以使用相应的网址在浏览器中访问宝塔的面板了,初次会需要登录,账号密码如上图所示

记一下使用 WeBASE 搭建自己的联盟链过程

0x01 搭建 FISCO BCOS 区块链网络

这里主要摘要 CentOS 版本系统的安装命令,其他版本系统简洁明了详细的参考文档 FISCO BCOS中文文档 ,这里的一切都是基于未安装过 FISCO BCOS 的条件完成的,如果是想要升级版本的话,请移步官方文档。

1、搭建单节点联盟链

1.1 搭建 FISCO BCOS 环境

使用命令 sudo yum install -y openssl openssl-devel 安装依赖

记一下使用 WeBASE 搭建自己的联盟链过程

创建操作目录,下载安装脚本文件,虽然名字任意,但是还是建议命名为 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

记一下使用 WeBASE 搭建自己的联盟链过程

如果因为网络问题导致长时间无法下载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

记一下使用 WeBASE 搭建自己的联盟链过程

成功后会提示 All completed。

启动 FISCO BCOS 链

bash nodes/127.0.0.1/start_all.sh

记一下使用 WeBASE 搭建自己的联盟链过程

## 检查进程是否成功
## 正常情况会有类似下面的输出; 如果进程数不为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 +++

记一下使用 WeBASE 搭建自己的联盟链过程

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

记一下使用 WeBASE 搭建自己的联盟链过程

拷贝控制台配置文件

## 最新版本控制台使用如下命令拷贝配置文件
## 若节点未采用默认端口,请将文件中的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

记一下使用 WeBASE 搭建自己的联盟链过程

输出这样就算成功

## 查看版本
getNodeVersion

## 查看节点
getPeers

记一下使用 WeBASE 搭建自己的联盟链过程

0x02 WeBase 的安装配置

WeBASE 的运行原理

记一下使用 WeBASE 搭建自己的联盟链过程

1、环境安装

需要的环境

环境 版本
Java JDK 8 至JDK 14
MySQL MySQL-5.6及以上
Python Python3.6及以上
PyMySQL
  1. Java 刚才我们已经安装了

  2. MySQL 可以在宝塔的面板里直接快捷安装

    记一下使用 WeBASE 搭建自己的联盟链过程

    记一下使用 WeBASE 搭建自己的联盟链过程

  3. Python 3 命令行形式安装

  4. 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 进行相应的端口更换即可,全部部署完毕后

记一下使用 WeBASE 搭建自己的联盟链过程

后续使用命令

# 一键部署
部署并启动所有服务        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

记一下使用 WeBASE 搭建自己的联盟链过程

$ ps -ef | grep webase.front 

记一下使用 WeBASE 搭建自己的联盟链过程

## 检查节点管理服务 webase-node-manager 的进程
$ ps -ef  | grep webase.node.mgr

记一下使用 WeBASE 搭建自己的联盟链过程

。。。。其余的参照官网上的进行检查

0x03 Nginx 的问题

当我们使用宝塔安装 Nginx 后,需要在 webase-deploy/comm/temp.conf 中修改 /etc/nginx/mime.types 的文件位置为我们自己的nginx 目录,一般来说用宝塔安装的都在根目录下的 www 文件夹下

记一下使用 WeBASE 搭建自己的联盟链过程

在 webase-deploy/comm/ 文件夹下执行命令 vi temp.conf 进行更改

记一下使用 WeBASE 搭建自己的联盟链过程

之后回到 webase-deploy 目录下执行命令 python3 deploy.py installAll 重新安装部署整个项目

之后若无问题应该就可以直接访问了,初始账号为 admin ,密码为 Abcd1234

记一下使用 WeBASE 搭建自己的联盟链过程

之后的配置就参见官方文档了 WeBASE使用手册

0x04 智能合约的部署及应用

1、新建合约

记一下使用 WeBASE 搭建自己的联盟链过程

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) {}
 
}

编写完成后进行编译,编译成功后进行部署

记一下使用 WeBASE 搭建自己的联盟链过程

部署完成后进行本地测试

记一下使用 WeBASE 搭建自己的联盟链过程

交易发起后去首页查看是否有交易记录,若有则成功

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 在私钥管理中可以查询到,如果没有私钥的话新建一个

记一下使用 WeBASE 搭建自己的联盟链过程

contractAbicontractAddress(合约地址) 可以在 合约管理 -> 合约列表 中查询到。

将模板中的 funcParam 更换为自己编写的智能合约中的参数列表对应的数据。使用 postman 进行测试

记一下使用 WeBASE 搭建自己的联盟链过程

如图,测试成功。

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="{\n" +

            " \n" +

            " \"groupId\" :1,\n" +

            " \"signUserId\": \"bbc341c4e982xxxxxxxxxxx76167f00ab4842e\",\n" +

            " \"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\"}],\n" +

            " \"contractAddress\":\"0xef860c2xxxx24b38bxxe1b317ad2\",\n" +

            " \"funcName\":\"set\",\n" +

            " \"funcParam\":[\""+comment.getId()+"\","+comment.getSid()+",\""+comment.getNickname()+"\",\""+comment.getContent()+"\"]\n" +

            "\n" +

            "}";
        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 方法就行了。

相关文章: