Sawtooth版本:1.2
Docker版本:19.03.11
单节点Sawtooth可以满足测试交易族功能等的需求,但是在测试性能或者搭建真正的生产环境时,就需要使用到多节点环境了。如果以Ubuntu为节点容器的话,每个节点就是一个操作系统为Ubuntu的计算设备,如电脑或者服务器虚拟机等,而且每一个节点都是一个单节点环境的克隆,如下图。
每个节点拥有Validator、REST API、Consensus Engine等组件,并且很重要的一点,每个节点都需要运行完全相同的交易族集合。在多节点环境下,交易仍然会被批量处理,但是Validator之间会使用PBFT(最少启动四个节点)或者PoET(最少启动三个节点)共识协议进行共识。
这里我选择了PBFT共识协议来进行搭建。
这里为了模拟四个单节点的环境,我使用了四个Ubuntu 18.04镜像运行的容器来作为四个节点,如果已经有四台空闲电脑或者服务器环境的话,就不用docker来模拟了。
0.事前准备
如果重用了单节点Sawtooth搭建时的环境,需要先把该环境下一些多余痕迹清除掉,注意,这里清除的前提是你的环境下没有什么重要数据,如果之前的区块链上已经有了很多重要的数据,建议先备份之后再清除。
- 如果环境中已经有Sawtooth组件在运行了,停止他们。
- 删除/var/lib/sawtooth来移除已有区块链数据。
- 删除/var/log/sawtooth来移除已有日志数据。
- 可以重用已有的用户和validator密钥,但是如果想使用新的密钥,删除/home/用户名/.sawtooth/keys和/etc/sawtooth/keys下的.priv和.pub文件。
1.环境准备
为了简化操作,我决定先在Ubuntu 18.04的第一层安装Sawtooth必需依赖、配置基本交易族等,然后再导出成新的镜像,然后把这个镜像启动四份。
1.1.镜像启动
这里首先启动第一个镜像。
docker run -it ubuntu:18.04 /bin/bash
之后就进入这个Ubuntu环境了。
1.2.安装Sawtooth
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 8AA7AF1F1091A5FD
sudo add-apt-repository 'deb [arch=amd64] http://repo.sawtooth.me/ubuntu/chime/stable bionic universe'
sudo apt-get install -y sawtooth
sudo apt-get install -y sawtooth sawtooth-pbft-engine # 安装PBFT共识包,如果使用PoET则不需要安装这个
可以使用如下命令来查看安装了的组件。
dpkg -l '*sawtooth*'
输出如下:
||/ Name Version Architecture Description
+++-==============-============-============-=================================
ii python3-sawtoo 1.2.6-1 all Sawtooth CLI
ii python3-sawtoo 1.2.5-1 all Sawtooth Intkey Python Example
ii python3-sawtoo 1.2.6-1 all Sawtooth REST API
ii python3-sawtoo 1.2.5-1 all Sawtooth Python SDK
ii python3-sawtoo 1.2.6-1 all Sawtooth Validator
ii python3-sawtoo 1.2.5-1 all Sawtooth XO Example
ii sawtooth 1.2.6 all Hyperledger Sawtooth Distributed
ii sawtooth-ident 1.2.6 amd64 The Sawtooth Identity TP for vali
ii sawtooth-pbft- 1.0.3 amd64 PBFT consensus algorithm for Hype
ii sawtooth-setti 1.2.6 amd64 The Sawtooth Settings transaction
如果在容器中报如下错误:
gnupg, gnupg2 and gnupg1 do not seem to be installed, but one of them is required for this operation
使用如下命令解决。
apt-get update && apt-get install -y gnupg2
如果说找不到add-apt-repository命令,用如下命令安装;
apt-get install software-properties-common
apt-get update
1.3.导出镜像并启动四个容器
首先找到容器的id
docker ps
拿到id之后,这里是a5fad1d8371f,导出容器文件:
docker export a5fad1d8371f > ubuntu.tar # 导出容器文件
然后使用该文件导入一个新的镜像,这里命名为sawtooth-node:
cat ubuntu.tar | docker import - sawtooth-node:v1 #将快照导入到镜像中
然后可以查看镜像列表发现已经成功加入:
$ docker images | grep sawtooth
sawtooth-node v1 48b8e7184d5c 7 seconds ago 366MB
然后该容器的使命就结束了,我们可以停止它。
docker stop a5fad1d8371f
docker rm a5fad1d8371f
下一步是使用该镜像来启动四个容器分别充当四个节点,在四个窗口中分别执行四次该命令。
docker run -it --name=node1 sawtooth-node:v1 /bin/bash
docker run -it --name=node2 sawtooth-node:v1 /bin/bash
docker run -it --name=node3 sawtooth-node:v1 /bin/bash
docker run -it --name=node4 sawtooth-node:v1 /bin/bash
2.生成用户和Validator密钥
2.1.生成用户密钥
在四个节点窗口分别执行如下命令来生成四个节点用户密钥,生产环境下,用户名应该是自己指定,但是这里为了好区分,使用序号进行标识。
sawtooth keygen node1
sawtooth keygen node2
sawtooth keygen node3
sawtooth keygen node4
2.2.生成Validator密钥
在四个节点窗口分别执行如下命令来生成Validator密钥。
sudo sawadm keygen
3.在第一个节点上生成创世块与peer网络配置
第一个节点用于创建创世块,指定初始化的链上设置(共识协议和允许修改设置的节点或者用户密钥,对PBFT来说,还包括初始网络中的其他节点密钥),当其他节点加入时会读取这个第一个节点写入的配置。这里由于我们使用的是PBFT协议,必须要保证所有节点都已经完成了第二步,生成了Validator密钥,才能生成创世块。
以下操作在node1容器中进行。
3.1.创世块准备
cd /tmp # 进入工作目录
sawset genesis --key $HOME/.sawtooth/keys/node1.priv \
-o config-genesis.batch # 创建一个设置提议批次
上述命令保证了node1所代表的用户拥有对于网络设置的设置和更新权限。
接下来是初始化PBFT下的共识设置,这里需要用到所有节点的公钥信息,所以需要依次到所有节点中使用如下命令查询公钥信息:
$ cat /etc/sawtooth/keys/validator.pub
查询出来差不多就是一串哈希值,如下的例子:
root@ed328379dd64:/tmp# cat /etc/sawtooth/keys/validator.pub
02a7c341a3fb5f447e07229576247736707c544ef2b2a09631f971023143d0511c
查出所有的公钥之后使用如下命令初始化共识设置。
sawset proposal create --key $HOME/.sawtooth/keys/node1.priv \ # 使用私钥对交易进行签名
-o config-consensus.batch \ # 为批次文件命名
sawtooth.consensus.algorithm.name=pbft \ # 指定共识算法名称
sawtooth.consensus.algorithm.version=1.0 \ # 指定共识算法版本
sawtooth.consensus.pbft.members='["02a7c341a3fb5f447e07229576247736707c544ef2b2a09631f971023143d0511c","0304686f6ce93762995086a5f3135665c22e9da5f349d86987f703b735bbc2e1e4","02f70aeb669299c77b641e1b251bd33a313c8d4310a45b6d3aba484086fdb2c179","0337cce85bc6e6466287c9c9ee1261f59caff095fa14ad435dd6f42910cd902045"]' # 指定参与初始化网络的各节点
将两个批次合成一个批次准备进行提交
sudo -u sawtooth sawadm genesis \
config-genesis.batch config-consensus.batch
在容器中,sudo -u sawtooth
可以删除掉,这样相当于使用root身份进行配置,目前启动还没有发现什么问题。
3.2.配置Peers网络
PBFT中,每个节点都需要知道所有其他节点的坐标来进行这种两两配对的通信,这里的坐标就是IP+端口号。在每个节点下执行如下命令来进行配置。
sudo cp -a /etc/sawtooth/validator.toml.example /etc/sawtooth/validator.toml # 复制模板文件
sudo vim /etc/sawtooth/validator.toml # 编辑文件
在文件中,首先确定peering
设置被设置为static
。
找到peers
设置然后输入其他验证者的IP地址,如下:
peers = ["tcp://node1:8800", "tcp://node2:8800", "tcp://node3:8800"]
当验证者启动之后配置就会生效。
以下是使用docker的一些提示。
1.不想在容器中安装vim确无法编辑文件。
有两种方式可以实现文件的编辑,如果编辑的内容很多,可以尝试从docker容器中将文件复制到宿主机中,编辑完毕之后再复制回去。
对于本文中的简单的编辑一行的方式,可以使用sed命令实现。
首先确认要修改的行数,这里可以使用cat命令加-n参数确定。
root@ed328379dd64:/tmp# cat -n /etc/sawtooth/validator.toml | grep peers
36 # which only attempts to peer with candidates provided with the peers option,
38 # any static peers will be processed first, prior to the topology buildout
50 # A list of peers to attempt to connect to in the format tcp://hostname:port.
52 # peers = ["tcp://host1:8800"]
64 # The minimum number of peers required before stopping peer search.
67 # The maximum number of peers that will be accepted.
可见这里要修改的是52行,假设要将其修改为:peers = ["tcp://172.17.0.4:8800", "tcp://172.17.0.5:8800", "tcp://172.17.0.6:8800"]
使用如下命令实现。
cat /etc/sawtooth/validator.toml | sed '52c peers\ =\ ["tcp://172.17.0.4:8800", "tcp://172.17.0.5:8800", "tcp://172.17.0.6:8800"]' > /etc/sawtooth/validator.toml.bak && mv /etc/sawtooth/validator.toml.bak /etc/sawtooth/validator.toml
其中52c
的c前面的是行数,后面的是内容。
2.寻找容器的ip地址
首先使用docker ps
命令获取容器的id。
然后使用docker inspect 容器id
来检查容器,可以在最后部分找到容器的IP地址。
"NetworkID": "86fff81bb8fd2d8dabe8332917e9997ee7b3dda5b839e3acf881bea67543eeff",
"EndpointID": "b38b6ba7586f9cdc3a5bbf6f2024ab59ba6f69e9ed503c2d4f2efccee8a40bba",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3", # IP地址
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
}
}
}
}
]
4.启动第一个节点的sawtooth
这里需要开启多个窗口来启动各个组件,如果使用容器代替主机可以使用如下命令让某个窗口连接到某个docker容器中。
docker exec -it 容器id /bin/bash
以下命令在node1中进行。
4.1.启动validator
sudo -u sawtooth sawtooth-validator \
--bind component:tcp://172.17.0.3:4004 \
--bind network:tcp://172.17.0.3:8800 \
--bind consensus:tcp://172.17.0.3:5050 \
--endpoint tcp://172.17.0.3:8800 \
--peers tcp://172.17.0.4:8800,tcp://172.17.0.5:8800,tcp://172.17.0.6:8800
使用后控制台输出如下内容:
root@ed328379dd64:/# sawtooth-validator \
> --bind component:tcp://172.17.0.3:4004 \
> --bind network:tcp://172.17.0.3:8800 \
> --bind consensus:tcp://172.17.0.3:5050 \
> --endpoint tcp://172.17.0.3:8800 \
> --peers tcp://172.17.0.4:8800,tcp://172.17.0.5:8800,tcp://172.17.0.6:8800
[2022-06-06 14:00:36.900 WARNING (unknown file)] [src/pylogger.rs: 40] Started logger at level WARN
如果在容器中,忽略sudo -u
,下同。
4.2.启动REST API
打开一个新的窗口来执行如下命令。
sudo -u sawtooth sawtooth-rest-api -v --connect 172.17.0.3:4004
启动后控制台输出如下内容:
root@ed328379dd64:/tmp# sawtooth-rest-api -v --connect 172.17.0.3:4004
[2022-06-06 14:17:31.399 INFO messaging] Connecting to tcp://172.17.0.3:4004
[2022-06-06 14:17:31.400 INFO rest_api] Creating handlers for validator at tcp://172.17.0.3:4004
[2022-06-06 14:17:31.409 INFO rest_api] Starting REST API on 127.0.0.1:8008
======== Running on http://127.0.0.1:8008 ========
(Press CTRL+C to quit)
4.3.启动各交易族
打开三个新的窗口分别执行下面三条命令,其中只有第一个交易族是必须的,其他的两个交易族也可以不启动,这里启动是为了之后的演示使用。
sudo -u sawtooth settings-tp -v --connect tcp://172.17.0.3:4004 # 这里一定要指明connect的位置,需要和启动validator的保持一致,不然会出现异常,下同
sudo -u sawtooth intkey-tp-python -v --connect tcp://172.17.0.3:4004
sudo -u sawtooth xo-tp-python -v --connect tcp://172.17.0.3:4004
启动后分别输出如下内容:
root@ed328379dd64:/# settings-tp -v --connect tcp://172.17.0.3:4004
INFO | settings_tp:95 | Console logging level: INFO
INFO | sawtooth_sdk::proces | connecting to endpoint: tcp://172.17.0.3:4004
INFO | sawtooth_sdk::proces | sending TpRegisterRequest: sawtooth_settings 1.0
INFO | sawtooth_sdk::proces | Message: 9c88e51e7f634d9dbc3c358291e32cfd
INFO | settings_tp::handler | Setting "sawtooth.settings.vote.authorized_keys" changed to "036e44e52b2709d8d70013fc9e079e0f4c7f41cf4d178ae4f46634f07ba9c8bffb"
INFO | sawtooth_sdk::proces | TP_PROCESS_REQUEST sending TpProcessResponse: OK
INFO | sawtooth_sdk::proces | Message: 04d510ad8d7c4b5ba23d2587ddfe4ea5
INFO | settings_tp::handler | Setting "sawtooth.consensus.algorithm.name" changed to "pbft"
INFO | sawtooth_sdk::proces | TP_PROCESS_REQUEST sending TpProcessResponse: OK
root@ed328379dd64:/# intkey-tp-python -v --connect tcp://172.17.0.3:4004
[2022-06-06 14:18:04.808 INFO core] register attempt: OK
root@ed328379dd64:/# xo-tp-python -v --connect tcp://172.17.0.3:4004
[2022-06-06 14:18:16.234 INFO core] register attempt: OK
4.4.启动共识引擎
打开一个新的窗口来打开。
sudo -u sawtooth pbft-engine -vv --connect tcp://172.17.0.3:5050
启动后输出如下内容:
root@ed328379dd64:/# pbft-engine -vv --connect tcp://172.17.0.3:5050
INFO | pbft_engine:88 | Sawtooth PBFT Engine (1.0.3)
INFO | pbft_engine::engine: | Startup state received from validator: StartupState { chain_head: Block(block_num: 0, block_id: [154, 229, 235, 85, 194, 210, 71, 123, 140, 205, 195, 18, 69, 18, 56, 69, 42, 153, 18, 97, 51, 85, 238, 160, 105, 86, 138, 233, 140, 223, 148, 2, 91, 184, 242, 155, 99, 254, 11, 136, 80, 30, 66, 189, 203, 21, 57, 179, 192, 192, 233, 120, 2, 249, 253, 15, 252, 155, 121, 130, 172, 82, 239, 114], previous_id: [0, 0, 0, 0, 0, 0, 0, 0], signer_id: [2, 167, 195, 65, 163, 251, 95, 68, 126, 7, 34, 149, 118, 36, 119, 54, 112
4.5.测试启动情况
- 测试REST API:
开启新窗口,执行如下命令:
curl http://localhost:8008/blocks
输出如下:
root@ed328379dd64:/# curl http://localhost:8008/blocks
{
"data": [
{
"batches": [
{
"header": {
"signer_public_key": "036e44e52b2709d8d70013fc9e079e0f4c7f41cf4d178ae4f46634f07ba9c8bffb",
"transaction_ids": [
"13113199c7bb4563a4
- 检查区块列表
执行如下命令:
sawtooth block list
输出如下:
root@ed328379dd64:/# sawtooth block list
NUM BLOCK_ID BATS TXNS SIGNER
0 9ae5eb55c2d2477b8ccdc312451238452a9912613355eea069568ae98cdf94025bb8f29b63fe0b88501e42bdcb1539b3c0c0e97802f9fd0ffc9b7982ac52ef72 2 4 02a7c3...
- 检查设置列表
sawtooth settings list
输出如下:
root@ed328379dd64:/# sawtooth settings list
sawtooth.consensus.algorithm.name: pbft
sawtooth.consensus.algorithm.version: 1.0
sawtooth.consensus.pbft.members: ["02a7c341a3fb5f447e07229576247736707c544ef2b2...
sawtooth.settings.vote.authorized_keys: 036e44e52b2709d8d70013fc9e079e0f4c7f41c...
5.在其他各节点上仿照启动第一个节点的方式启动各组件
以下以node2,ip为172.17.0.4来进行演示,剩下两个照猫画虎即可。
5.1.启动validator
sudo -u sawtooth sawtooth-validator \
--bind component:tcp://172.17.0.4:4004 \
--bind network:tcp://172.17.0.4:8800 \
--bind consensus:tcp://172.17.0.4:5050 \
--endpoint tcp://172.17.0.4:8800 \
--peers tcp://172.17.0.3:8800,tcp://172.17.0.5:8800,tcp://172.17.0.6:8800
5.2.启动REST API
sudo -u sawtooth sawtooth-rest-api -v --connect 172.17.0.4:4004
5.3.启动各交易族
sudo -u sawtooth settings-tp -v --connect tcp://172.17.0.4:4004
sudo -u sawtooth intkey-tp-python -v --connect tcp://172.17.0.4:4004
sudo -u sawtooth xo-tp-python -v --connect tcp://172.17.0.4:4004
5.4.启动共识引擎
sudo -u sawtooth pbft-engine -vv --connect tcp://172.17.0.4:5050
输出基本和第一个节点启动时一致。。再启动另外两个,记得启动的时候修改参数IP就行。
6.确定网络功能可用
6.1.检查peer
因为我们使用的是PBFT协议,所以在确认之前必须要确保我们已经启动了四个节点。
在node1的窗口中执行如下命令:
curl http://localhost:8008/peers
可以看到如下输出:
root@ed328379dd64:/# curl http://localhost:8008/peers
{
"data": [
"tcp://172.17.0.4:8800",
"tcp://172.17.0.5:8800",
"tcp://172.17.0.5:8800",
"tcp://172.17.0.6:8800"
],
"link": "http://localhost:8008/peers"
}
使用如下命令来展示某个节点的peers,在node1中运行。
sawtooth peer list
root@ed328379dd64:/# sawtooth peer list
tcp://172.17.0.4:8800,tcp://172.17.0.5:8800,tcp://172.17.0.5:8800,tcp://172.17.0.6:8800
可以查到所有的peer的地址。
6.2.测试交易族执行
在node1中执行如下命令调用交易族intkey,将Key为MyKey的值设置为999。
intkey set MyKey 999 --keyfile /root/.sawtooth/keys/node1.priv
如果在容器中执行的话,--keyfile /root/.sawtooth/keys/node1.priv
必不可少,因为如果不加的话会报如下错误:
root@ed328379dd64:/# intkey set MyKey 999
Error: Failed to read private key: [Errno 2] No such file or directory: '/root/.sawtooth/keys/root.priv'
然后在另一个节点,比如node3中新开一个窗口,执行如下命令看是否能够查询到MyKey的值。
intkey show MyKey
可以看到这里并没有加--keyfile /root/.sawtooth/keys/node3.priv
,因为写的时候才会用到私钥对批次进行签名,而读的话并不需要私钥签名,而是直接通过命名空间读取数据,其实从交易族源码也可以看出来。
以下是set的命令解析。
def add_set_parser(subparsers, parent_parser):
message = 'Sends an intkey transaction to set <name> to <value>.'
parser = subparsers.add_parser(
'set',
parents=[parent_parser],
description=message,
help='Sets an intkey value')
parser.add_argument(
'name',
type=str,
help='name of key to set')
parser.add_argument(
'value',
type=int,
help='amount to set')
parser.add_argument(
'--url',
type=str,
help='specify URL of REST API')
parser.add_argument(
'--keyfile',
type=str,
help="identify file containing user's private key")
parser.add_argument(
'--wait',
nargs='?',
const=sys.maxsize,
type=int,
help='set time, in seconds, to wait for transaction to commit')
而以下是show命令解析
def add_show_parser(subparsers, parent_parser):
message = 'Shows the value of the key <name>.'
parser = subparsers.add_parser(
'show',
parents=[parent_parser],
description=message,
help='Displays the specified intkey value')
parser.add_argument(
'name',
type=str,
help='name of key to show')
parser.add_argument(
'--url',
type=str,
help='specify URL of REST API')
总之,到这里我们就成功搭建了一个Sawtooth网络,我们可以利用这个思路来搭建承载自己交易族的网络进一步进行多节点下的功能测试。