共识网络BFT-SMaRt:理论与实践

关键字:BFT-SMaRt,共识算法,分布式网络,Java,区块链,状态机复制,MOD-SMaRt,VP-Consensus

BFT-SMaRt 简介

官方定义:基于拜占庭容错的状态机复制方案的性能改善。从github的发行历史来看,BFT-SMaRt项目最早于2015年4月发布了beta版本。经历了半年230个提交的迭代,当年12月BFT-SMaRt发布了1.1beta版本。然后,直到2018年10月才推出了v1.2正式版。随后,2019年2月、3月开始较活跃的发行,5月归于平静,直到现在(笔者日期:2020-01-02)。通过这个时间线,可以看出,BFT-SMaRt与比特币的发展波动还是有一定的关联。但无论怎样,这是一个优秀的项目值得我们学习,目前也有很多区块链团队在使用。

分布式计数器服务

下面通过一个简单的官方实例展开对BFT-SMaRt的学习。

功能描述

这是一个分布式计数器,它有4个节点,每个节点均可接收来自不同终端的计数请求。一个计数请求包含三个参数:

  1. 节点id
  2. 步进
  3. 循环次数

当某一个节点接收到一个终端的计数请求以后,它会将本地的计数字段的值改为请求的值,同时,其他3个节点的计数字段也会同步该结果。

在这个4节点的网络中,根据拜占庭容错算法 3f+1 = n,最多允许出现1个问题节点。

组网配置

config/hosts.config文件是组网配置文件,用于配置网络节点的访问地址,包括节点id,ip及端口号。我们为便于学习,4个节点都部署在本机,只需要调整不同的端口号即可,4个节点共享同一份hosts.config文件。

如果节点分布在不同的机器,则每个节点均需要配置相同的hosts.config,用于描述同一个共识网络。

#server id, address and port (the ids from 0 to n-1 are the service replicas) 
0 127.0.0.1 11000 11001
1 127.0.0.1 11010 11011
2 127.0.0.1 11020 11021
3 127.0.0.1 11030 11031

默认情况下,不必修改。

启动节点

节点的启动是通过runscripts/smartrun.sh脚本。

java -Djava.security.properties="./config/java.security" -Dlogback.configurationFile="./config/logback.xml" -cp bin/*:lib/* $@

该脚本为java命令配置了bin/BFT-SMaRt.jar作为类执行文件,还有日志和安全的配置策略。BFT-SMaRt.jar是编译整个BFT-SMaRt库的jar包,所有的服务都集成在此,可在jvm虚机直接执行,熟悉Java的朋友应该很了解了,此处不再赘述。

回到项目根目录,开启4个连接会话,分别执行:

runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 0
runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 1
runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 2
runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 3

节点启动成功,日志会打印出节点id、ip及端口,节点总数、容错数,超时、SSL/TLS传输协议等信息。随着节点的全部启动,节点间会互相识别、同步,

lwb@lwbpc:~/work/bft-smart$ runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 
Use: java CounterServer <processId>
lwb@lwbpc:~/work/bft-smart$ runscripts/smartrun.sh bftsmart.demo.counter.CounterServer 3
-- Using view stored on disk
-- SSL/TLS handshake complete!, Id:0  ## CipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.
-- SSL/TLS handshake complete!, Id:1  ## CipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.
-- SSL/TLS handshake complete!, Id:2  ## CipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.
-- ID = 3
-- N = 4
-- F = 1
-- Port (client <-> server) = 11030
-- Port (server <-> server) = 11031
-- requestTimeout = 2000
-- maxBatch = 1024
-- Binded replica to IP address 127.0.0.1
-- SSL/TLS enabled, protocol version: TLSv1.2
-- In current view: ID:0; F:1; Processes:0(/127.0.0.1:11000),1(/127.0.0.1:11010),2(/127.0.0.1:11020),3(/127.0.0.1:11030),
-- Retrieving State
-- Replica state is up to date
-- 
                ###################################
                    Ready to process operations    
                ###################################

当出现Ready to process operations时说明节点网络组建成功,服务可用,等待终端请求。4个节点在启动时,后启动的节点会显示与前面的节点的安全连接信息:SSL/TLS handshake complete!,这是建立连接,组网过程中的日志。

常见问题

在启动id为1的节点时(也就是第二个启动),有可能报错:

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

异常信息表示没有合适的协议,cipher-suit(密码套件,做SSL安全连接时使用)不可用。第二个节点启动以后,根据hosts配置文件对网络的描述,它会迅速与第一个节点建立安全连接,会使用到cipher-suit来建立SSL/TLS的加密连接。回到项目根目录,进入config/system.config,这是BFT-SMaRt的全局配置文件。其中就包含了cipher-suit的启用配置,可以找到:

system.ssltls.enabled_ciphers = TLS_ECDHE_ECDSA_WITH_NULL_SHA,
# With cipher (recommended).
#system.ssltls.enabled_ciphers = TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,

将默认配置改为推荐配置,

#system.ssltls.enabled_ciphers = TLS_ECDHE_ECDSA_WITH_NULL_SHA,
# With cipher (recommended).
system.ssltls.enabled_ciphers = TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,

重新启动节点即可。

计数服务

打开第5个连接会话作为终端,进入项目根目录,输入:

runscripts/smartrun.sh bftsmart.demo.counter.CounterClient 1 1 2

含义为:标识自己作为客户端的id为1向共识网络发起请求,步进为1,执行两次。得到的日志输出为:

Invocation 0, returned value: 1
Invocation 1, returned value: 2

节点的日志输出统一为:

-- Session Created, active clients=0
(1) Counter was incremented. Current value = 1
(2) Counter was incremented. Current value = 2

当前值为2,我们再次发起一个请求:

runscripts/smartrun.sh bftsmart.demo.counter.CounterClient 1 2 3

含义为:标识自己作为客户端的id为1向共识网络发起请求,步进为2,执行3次。可推测出从2加3个2,最终结果为8,实际上日志的显示也是符合我们的推测的:

-- Session Created, active clients=0
(3) Counter was incremented. Current value = 4
(4) Counter was incremented. Current value = 6
(5) Counter was incremented. Current value = 8

BUG:clients有编号,第一个默认为0,当在本地启动多个会话作为client发起请求时,会发生第二个client执行中止的情况,例如第一个client执行完请求以后并未退出线程,而此时第二个client发起一个执行次数为3的请求,那么该客户端日志会卡在Invocation 0,而节点日志会成功执行1次,剩余2次未发生。此时退出一个client,仍旧可以正常服务。

解决措施:该BUG是由于理解出错导致的,客户端请求的第一个参数是用来标识自己的。因此当两个客户端均以相同的id发起请求时,对于共识网络的节点,只能处理一个。而正确的方法应该是不同的客户端采用不同的id作为自己的标识发起访问。

容错服务

我们来模拟一个节点断线,然后再重新启动。通过日志分析,该节点再次接入网络以后,会主动请求其他节点,获得之前的所有操作,然后本地依次全部执行,最终得到与其他节点相同的状态值。

-- Retrieving State
-- Requesting state from other replicas
-- I just sent a request to the other replicas for the state up to CID 7
-- Received state. Will install it
-- Successfully installed proof for consensus 7
-- Last CID in state: 7
(1) Counter was incremented. Current value = 1
(2) Counter was incremented. Current value = 2
(3) Counter was incremented. Current value = 4
(4) Counter was incremented. Current value = 6
(5) Counter was incremented. Current value = 8
(6) Counter was incremented. Current value = 10
(7) Counter was incremented. Current value = 12
(8) Counter was incremented. Current value = 14
-- Setting last CID to 7
-- Current decided size: 0
-- All finished up to 7
-- 
                ###################
                    Ready to process operations    
                ###################
-- I updated the state!

通过这个日志,可以非常清楚地看到状态机复制的过程。

BFT-SMaRt 理论

BFT-SMaRt 是一个使用Java编程语言实现的分布式共识引擎,提升了模块化程度,更加可靠,同时提供了灵活的编程接口。前缀BFT我们熟知,那么SMaRt是什么?可以看到大写的部分SMR,其实是State Machine Replication,即状态机复制的意思。那么BFT-SMaRt认为它是BFT-SMR的改善方案,因此凑上了小写字母a和t组成了smart。所以BFT-SMaRt可以解释为:通过BFT实现SMR的一套改善解决方案

区块链的分布式网络需要解决的拜占庭问题,在此就不多介绍了。在此之前,PBFT、POW、POS以及DPOS,这些拜占庭容错类的算法由于研究人员的学术或者性能需求,大多是由Go语言或C++写成。这对于拥有广大群众基础的Java从业者是不友好的,也是区块链大规模商业化的阻力。因此,BFT-SMaRt最大的优势或者特色就是使用了Java语言实现,同时可靠、模块化、接口灵活

本章依据论文 From Byzantine Consensus to BFT State Machine Replication: A Latency-Optimal Transformation

BFT-SMR 典型模式

在BFT-SMaRt出现之前,大部分论文所提出的BFT实现SMR的典型模式,往往需要更多的步骤。我们知道PBFT本身是需要5步的,除去客户端发起请求REQUEST与节点回复客户端REPLY,中间还有三阶段共识。PBFT是用来在联盟链环境中达成正确共识的,一般来讲都会为交易排序,达成统一顺序的共识,但并没有状态校验。理论上来讲,当各个节点的交易顺序是正确一致且完整的,那么执行的结果即状态值就应该是一致的。但是当系统脱离舒适区,来到了更加不可信的环境里,需要具备状态的检验作为double-check。而状态机复制的概念,是一个相较区块链更久远的概念。集群、分布式都会有负载以及同步的需求,多点承接请求,单点执行,多点再同步结果数据,这是一个耳熟能详的过程。那么BFT-SMR所研究的内容恰恰是以上的所有,因此它会需要更多的步骤,如下图所示。

基于可靠的广播信道,首先在SMR过程发生了三个步骤:消息从客户端发出并广播到各个节点,节点间回应,节点同步完成。然后再执行与PBFT相同的3PC共识,最后再加上一个消息回复REPLY。总共七个步骤。为了优化通信的步骤,BFT-SMaRt提出了MOD-SMaRt模块化的概念,而实现BFT的共识被称作Validated and Provable Consensus (VP-Consensus),即已验证可证明的共识。

\[BFTSMaRt=MODSMaRt+VPConsensus \]

VP-Consensus改善了现有的基于领导者驱动的共识算法。

SMR 状态机复制

在这个模型中,任意数量的客户端进程向一组副本进程(分布式集群)发出命令。这些副本实现一个有状态的服务,例如一个账户的余额。该服务在处理客户端命令后更改其状态,并向发出这些命令的客户端发送响应。该技术的目标是使每个副本上的状态以保持一致的方式发展,从而使服务在每个副本上完整及准确地被复制。为了实现这种行为,需要满足四个特性:

  1. 如果有两个正确的副本r和r‘将操作o应用于状态s,r和r’均获得一个相同的状态s‘。
  2. 任意两个正确的副本r和r'均从状态s0开始;
  3. 任意两个正确的副本r和r’均执行相同的操作序列o0,…,oi;
  4. 来自正确客户端的操作总是被执行。

前两个要求可以在不使用任何分布式协议的情况下实现,但是接下来的两个要求可以直接转化为一个全顺序广播协议的实现——这相当于解决了共识问题。MOD-SMaRt满足性质3和4,再加入一个VP-Consensus共识,被复制的服务将符合性质1和2。

VP-Consensus

VP-Consensus解释为已验证可证明共识。已验证,指的是协议接收一个操作γ修改的一个值,那么任何其他节点的值必须满足修改,经受住验证。可证明,即零知识证明,意味着协议生成一个加密证据Γ,可证明一个已采纳的值v是正确的。VP-Consensus实现提供以下接口:

  1. VP-Propose(我,l,γ,v):在共识实例i中,提出了一个值v,,以及初始的leader(领导者)l和操作γ;
  2. VP-Decide(我,v,Γ):当值v在共识实例i中被采纳时触发;
  3. VP-Timeout(i, l):用于在共识实例i中触发超时,并指定一个新的leader进程l。

关于这个接口,有三件重要的事情需要注意:

  1. VP-Consensus采用leader驱动的协议,类似于任何拜占庭Paxos共识。
  2. 该接口假设VP-Consensus实现可以处理超时来更改领导者(就像Raft那样),并且在超时之后本地选择新的领导者。
  3. 我们隐式地假定所有正确的流程将为该共识实例i调用VP-Propose,执行相同的操作γ。

VPConsensus具备以下特性:

  • 终止性:每一个正确的操作最终都会被有效执行;
  • 完整性:正确的操作不会执行两次;
  • 一致性:执行两次相同的操作,均会得到一致的结果。

此外,还需要另外两个特性:

  • 外部有效性:如果在系统内一个正确的操作y修改了v,那么外部执行γ(v)一定也是正确的;
  • 外部可证性:如果一些正确的操作修改了v,同时加密证据Γ在共识实例i中,那么所有外部的正确操作均可以使用Γ来验证:值v是我的正确修改。

MOD-SMaRt 模块化

上面介绍了VP-Consensus基本上与PBFT差不多,而MOD-SMaRt则是BFT-SMaRt较为核心的部分。该协议又分为三个子算法:

  1. 客户端操作,发起请求
  2. 正常阶段(执行请求)
  3. 同步阶段

MOD-SMaRt模块化首先的前提是建立在可靠已认证的点对点信道,外加VP-Consensus共识实现。MOD-SMaRt使用VP-Consensus在共识实例i中(建立交付消息)执行一个正确的操作序列,这个操作序列也会在该共识实例中的其他副本中正确执行。MOD-SMaRt的一个副本架构如下图。

在MOD-SMaRt第二个阶段——正常阶段中,一个正确的客户端发送请求到所有副本,一个共识实例会建立整个执行顺序,执行操作,然后回复给客户端。如下图所示:

接着,是第三个阶段——同步阶段。当第二次触发消息的超时时,将启动此阶段,启动时同步运行VP-Consensus,如下图所示,虚线部分对应VP-Consensus协议的消息。

通过这个图,可以清晰地观察到MOD-SMaRt的每一个具体的步骤。

  • 初始leader为节点R0,客户端可以向任意一个节点定向发送消息,收到消息的节点会为消息排序,非leader一定会处理超时,那么它就会把原消息转发给其他节点。其他节点第一次接受到该消息也会重复以上操作。可以确保leader节点收到原消息并排序。
  • STOP:如果同一个请求有第二个超时,说明leader排序失败,则更换leader。该节点会暂停本地所有倒计时器,然后启动同步阶段,leader默认更换为下一个节点,即R1,并广播STOP消息。某节点统计本地收到STOP消息的节点数量达到确认数,即至少n/2+1(CFT),2/3n+1(BFT)则本地确定leader更换成功,并向新leader发送STOP-DATA消息。
  • STOP-DATA:新leader接收该消息达到确认数,就向其他节点发送SYNC消息,包含原消息决策。
  • SYNC:节点接收到SYNC消息后,会将原消息执行相同的计算,从而校验leader的信息是否正确。然后向外广播READ消息。
  • READ:节点本地收集READ消息到确认数,则发送COLLECT消息给leader。
  • COLLECT:leader接收COLLECT消息到确认数,则发送PROPOSE消息,代表该原消息状态已同步。
  • PROPOSE:节点接收leader发来的PROPOSE消息,完成本地对于该消息的处理。

n > 3f +1,设正确消息为t,则n > 3(n-t) +1,所以t>(2n+1)/3,由于节点必须为整数,因此最少为2/3n+1个正确消息。

如果原leader没有执行异常,则不需要更换,只需要READ-COLLECT-PROPOSE三步即可完成对于消息的共识处理。

优化措施

对于MOD-SMaRt的实现,有两种优化的措施。

日志

第一个重要的优化措施与操作日志的大小有关。在MOD-SMART中,日志是可以无限增长的,这使得它不适用于实际系统。为了避免这种情况,我们建议使用检查点checkpoint和状态转移state transfer。

①检查点

检查点将在每个副本中定期执行:在交付了一定数量的决策之后,副本将从应用程序请求状态,将其保存在内存或磁盘中,然后清除日志。

②状态转移

用于副本发现自身日志中最新决策与其他日志不一致时所使用(网络不畅或系统异常)。首先主动向其他副本请求他们最新检查点日志中保存的状态。然后等待接收到来自不同副本的 f + 1 (正确的比错误的多一个即可)个状态共识后,强制自己同步新状态,并恢复运行。

计算开销

第二个优化目标是避免在协议关键路径中生成和验证数字签名的计算开销。客户端请求和VP-Consensus证明(以满足外部可证明性)能够使用MAC(消息认证码)代替数字签名,就像在PBFT中做的那样。然而,这种情况下的客户端请求会导致一个不太健壮的状态机实现,容易受到某些性能下降攻击(例如DDOS)。如果我们使用基于BFT的VP-Consensus,并用到这种优化,MOD-SMaRt将匹配PBFT的消息模式,同时有正确的领导者同步执行。因此需要相同数量的通信步骤和密码操作。这也正是在BFT-SMaRt所做的:一种使用了BFT优化MOD-SMART的实现。

结论

尽管存在一些优化改善BFT-SMR的工作,但它们都没有将协议封装在一个共识算法中,只能是分片协作的。另一方面,所有已发布的BFT实现的整个操作顺序的广播,比实际的BFT-SMR需要更多的通信步骤。我们通过提供MOD-SMaRt(一种低延迟和弹性最优的BFT-SMR)来弥补这一缺陷,该算法使用定义良好的共识算法实现模块化。为了实现这种最优性,我们引入了经过验证和可证明的共识抽象,它可以通过对现有的共识协议进行简单修改来实现。

后记

后续文章将升级难度,从源码角度来分析分布式计数器服务的实现方法,以及BFT-SMaRt自身的包括可靠信道、共识达成、线程分配、系统架构等内容。

更多文章请转到一面千人的博客园

posted @ 2020-01-06 20:01  一面千人  阅读(3107)  评论(0编辑  收藏  举报