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

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

不要装到 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

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

安装完成后会出现

image-20210405204742359

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

image-20210405204848915

0x01 搭建 FISCO BCOS 区块链网络

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

1、搭建单节点联盟链

1.1 搭建 FISCO BCOS 环境

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

image-20210405211919472

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

image-20210405211950254

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

image-20210405212039510

成功后会提示 All completed。

启动 FISCO BCOS 链

bash nodes/127.0.0.1/start_all.sh

image-20210405212224919

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

image-20210405212405446

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

image-20210405213631932

拷贝控制台配置文件

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

image-20210405214301704

输出这样就算成功

## 查看版本
getNodeVersion

## 查看节点
getPeers

image-20210405214501870

0x02 WeBase 的安装配置

WeBASE 的运行原理

1、环境安装

需要的环境

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

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

    image-20210405235144940

    image-20210406121320731

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

image-20210406124925876

后续使用命令

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

image-20210406125219705

$ ps -ef | grep webase.front 

image-20210406125322186

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

image-20210406125429497

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

0x03 Nginx 的问题

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

image-20210406170137039

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

image-20210406170400209

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

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

image-20210406170744298

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

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

1、新建合约

image-20210408190157786

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

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

image-20210408190304555

部署完成后进行本地测试

image-20210408190424192

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

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

image-20210408191335510

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

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

image-20210408191949547

如图,测试成功。

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

posted @ 2021-04-08 19:41  绯狱丸丶  阅读(1991)  评论(0编辑  收藏  举报