区块链基础通识(1)——分布式系统的共识问题

分布式系统

我们最初了解区块链的时候,很多人会形容这个区块链是一个"分布式的不可篡改账本 ",这是一个很形象的说法,但是我认为更为准确的形容是"所有节点共同维护的状态机"。为什么分布式和区块链不能划等号呢?

两种常见的分布式模型

中心化网络(Client-Server) 去中心化网络(P2P)
交互方式 用户只能和服务器交互 用户之间可以交互
优缺点 通讯速度快,客户端需要存储的信息量少,响应稳定;相对垄断 相对自由;通讯难度随着用户数量成指数级上升
涉及分布式问题 非拜占庭问题(只存在节点挂机情况) 拜占庭问题(存在干扰一致性的恶意节点)

图例

中心化网络的请求模型(响应与箭头方向相反)

复制代码

去中心化模型的请求模型

复制代码

比较和联系

  1. 对比上述两个图,我们很容易发现------去中心化的通信方式,每个节点需要保存的"通信信道"是超多的,因此这种模型无法适应大量的节点参与

  2. 我们拿"微信"来举一个例子。微信的交流,虽然看起来是点对点的(C2C结构),但是实际上是用户请求云服务器来实现的信息交流的。(箭头方向是信息传递方向)

    复制代码
  3. 以"种子网络"为例,在其发展初期存在的节点到其后期均被暴风影音等节点垄断。可以看出去中心化向中心化演变是一个自然的趋势。

  4. 所以,我们通常的中心化分布式系统,是一个"有超强中心节点的系统 ",在这个系统中只要服务器不挂机就是正常运行的,不存在恶意节点破坏一致性问题(因为只有服务器自己说了算);而区块链涉及的分布式问题往往是有恶意节点存在的,这样的问题被称为"拜占庭将军问题"

拜占庭将军问题

背景

在网络上的节点分为恶意节点(目的是扰乱一致性)和故障节点(挂机)。拜占庭问题解决的是拥有恶意节点的问题;拥有故障节点的成为"非拜问题"

首先,我们来补充一下分布式账本的节点问题。拜占庭问题是由兰波特提出的,为了解决这个问题,采用了从理想化的"口头协议"-->解决网络延时的"PBFT算法"-->区块链使用的"PoW机制"

问题本身

拜占庭帝国派出多支军队进攻一个城池,这个城池十分坚固,一支部队无法攻克,但是多支军队一起攻打即可攻克。现在,将军中有叛徒,他的目的是使得忠诚的部队行动不一致(叛徒的目的是为了扰乱系统的一致性)。这个问题有解的前提是n(总节点个数)>3m(恶意节点个数)。

问题解法

最初的解决方法------口头协议

这个解法使用的模型叫做将军-副官模型 ,在这个模型的要求下,所有的节点都可以是将军也都可以是副官(我们将第一个发送命令的节点叫做将军,其余的叫做副官,值得注意的是副官与副官之间也可以通信),所有节点需要遵守两个规则:

  1. 忠诚的副官们会遵守同一个命令

  2. 若将军忠诚,则所有的副官会遵守他的命令

下面我们看一下最基础(m=1、n=4)的情况

副官中出现叛徒:

复制代码
  1. 首先,我们将"将军"发布的命令称为A,即副官1,副官2,副官(叛徒)3收到的将军命令都是A

  2. 这时,副官1会询问另外两个副官,得到副官2的**"将军给的是命令A"** ,以及叛徒3给的**"将军给的命令是B"**(B是假消息)的回答

  3. 因此,副官1按照少数服从多数原则,推断出3是叛徒,A是一致性命令。副官2同理

将军是叛徒:

复制代码
  1. 叛徒将军向副官1、2、3分别发送命令A、B、C(叛徒是为了扰乱一致性的)

  2. 副官1接到A命令后会联系2、3,得到副官2的**"将军给的是命令B"** ,以及副官3给的**"将军给的命令是C"**

  3. 这时候副官1可以推断出将军是叛徒,同时放弃这一轮的命令(要求的第二条不成立),副官2、3同理

复杂情况(以m=2,n=7为例)
复制代码

这种方法要注意以下两点:

  1. 签名 :也就是一个人发布命令的时候需要加上自己的签名,转述别人的信息的时候需要加上他的签名(这是为了保证信息的唯一性),比如上文情况中我使用的**"副官说'将军的命令是......'"**这一说法

  2. 每个节点使用表格进行决策,这是一种递归嵌套的思想(下面表格展示的每行指的是节点的决策信息,纵列是节点给别人的应答),假设将军给出的命令是A

V1给出的应答 V2给出的应答 V3给出的应答 V4给出的应答 V5给出的应答 V6给出的应答
V1 A A A b f
V2 A A A c g
V3 A A A d h
V4 A A A e i

上述的小写字母指的是随即命令,善意节点依照少数多数的原则可以推断出结果,在这里不展开叙述

通信次数

我们简单的计算一下上面两种模型的通讯次数发现,当存在四个节点中一个恶意节点时的通讯次数为9 次,七个节点中存在两个恶意节点的通讯次数为36次,因此这种方法的算法复杂度随着节点数以指数级上升,是一种很不明智的算法。而下面要介绍的PBFT算法就将这种复杂度降到了多项式级。

用通讯次数换取安全性------PBFT()算法

和"口头协议"一样,PBFT算法也是使用通讯次数换取安全性,这样的原理也决定了它只能在小范围中使用,实际生产中的应用场景往往是联盟链。

简介

PBFT算法中节点只有两种角色,主节点(primary)副本(replica),两种角色之间可以相互转换。两者之间的转换又引入了视图(view)的概念,视图PBFT算法中起到逻辑时钟的作用。

算法实现流程

算法开始的时候,使用随机数或者一定的顺序得到主节点;首先客户端发送消息m给主节点p,主节点就开始了PBFT三阶段协议,三个阶段分别是预准备(pre-prepare)准备(prepare)提交(commit)。其中pre-prepareprepare阶段最重要的任务是保证同一个主节点发出的请求在同一个视图(view)中的顺序是一致的,preparecommit阶段最重要的任务是保证请求在不同视图之间的顺序是一致的。算法的流程图如下:(在这个周期中,C指客户端节点,0指主节点,1、2指正常副本,3指掉线副本)

  1. 主节点收到客户端发送来的消息后,构造pre-prepare消息结构体< <PRE-PREPARE, v, n, d>, m >广播到集群中的其它节点。(PRE-PREPARE标识当前消息所处的协议阶段,v表示当前视图编号,n为主节点广播消息的一个唯一递增序号,dm的消息摘要,m为客户端发来的消息)(这里面的"摘要"也就是对于客户端消息进行的哈希压缩)

  2. 副本(backup)收到主节点请求后,会对消息进行检查 ,检查通过会存储在本节点。当节点收到2f+1(包括自己)个相同的消息后,会进入PREPARE状态,广播消息< <PREPARA, v, n, d, i> >,其中i是本节点的编号。对消息的有效性有如下检查:(也就是满足超2/3节点达成共识)

    1. 检查收到的消息体中摘要d,是否和自己对m生成的摘要一致确保消息的完整性

    2. 检查v是否和当前视图v一致保证消息存在于同一周期

    3. 检查序号n是否在水线hH之间,避免快速消耗可用序号h 是当前的安全水线(safety waterline),表示系统中可以接受的最低序号;H 是当前的高水线(high waterline),表示系统中可以接受的最高序号;n需要在这两个水线之间,即 h <= n <= H,主要是为了避免以下问题:序号枯竭 :如果序号的使用没有控制在合理范围内,系统可能会迅速消耗掉所有可用的序号,这样会导致新的请求无法被处理,因为没有足够的序号可用。性能问题:不合适的序号管理可能会影响系统的性能,特别是当系统需要频繁地调整这些水线时,可能会造成额外的开销)。

    4. 检查之前是否接收过相同序号nv,但是不同摘要d的消息,确保消息的唯一性

  3. 副本收到2f+1(包括自己)个一致的PREPARE消息后,会进入COMMIT阶段,并且广播消息< COMMIT, v, n, D(m), i >给集群中的其它节点。在收到PREPARE消息后,副本同样也会对消息进行有效性检查,检查的内容是上条的1, 2, 3

  4. 副本收到2f+1(包括自己)个一致的COMMIT个消息后执行m中包含的操作,其中,如果有多个m则按照序号n从小到大执行,执行完毕后发送执行成功的消息给客户端。

日志压缩(解决内存问题)

这种压缩方式采用的是"快照 "的方法。Pbft为常数个操作创建一次稳定检查点,比如每100个操作创建一次检查点,而这个检查点就是checkpoint,当这个checkpoint得到集群中多数节点认可以后,就变成了稳定检查点stable checkpoint

  1. 当节点i生成checkpoint后会广播消息<CHECKPOINT, n, d, i>其中n是最后一次执行的消息序号,dn执行后的状态机状态的摘要 。每个节点收到2f+1个相同ndcheckpoint消息以后,checkpoint就变成了stable checkpoint

  2. 同时删除本地序号小于等于n的消息。同时checkpoint还有一个提高水线(water mark)的作用,当一个stable checkpoint被创建的时候,水线h被修改为stable checkpointn,水线Hh + kk就是之前用到创建checkpoint的那个常数。

视图切换(解决主机宕机)

view-change提供了一种当主节点宕机以后依然可以保证集群可用性的机制。view-change通过计时器来进行切换,避免副本长时间的等待请求。

  1. 当副本收到请求时,就启动一个计时器,如果这个时候刚好有定时器在运行就重置(reset)定时器,但是主节点宕机的时候,副本i就会在当前视图v中超时,这个时候副本i就会触发view-change的操作,将视图切换为v+1。副本 i 会停止接收除了 checkpoint,view-change和new view-change以外的请求,同时广播消息 <VIEW-CHANGE, v+1, n, C, P, i> 的消息到集群。

    1. n是节点i知道的最后一个stable checkpoint的消息序号。

    2. C是节点i保存的经过2f+1个节点确认stable checkpoint消息的集合。

    3. P是一个保存了n之后所有已经达到prepared状态消息的集合。

  2. 当在视图( v+1 )中的主节点p1接收到2f个有效的将视图变更为v+1的消息以后,p1就会广播一条消息<NEW-VIEW, v+1, V, Q>

    1. Vp1收到的,包括自己发送的view-change的消息集合。

    2. QPRE-PREPARE状态的消息集合,但是这个PRE-PREPARE消息是从PREPARE状态的消息转换过来的。

  3. 从节点接收到NEW-VIEW消息后,校验签名,VQ中的消息是否合法,验证通过,主节点和副本都 进入视图v+1。当p1在接收到2f+1VIEW-CHANGE消息以后,可以确定stable checkpoint之前的消息在视图切换的过程中不会丢,但是当前检查点之后,下一个检查点之前的已经PREPARE可能会被丢弃,在视图切换到v+1后,Pbft会把旧视图中已经PREPARE的消息变为PRE-PREPARE然后新广播。

    1. 如果集合P为空,广播<PRE-PREPARE, v+1, n, null>,接收节点就什么也不做。

    2. 如果集合P不为空,广播<PRE-PREPARE, v+1, n,d>

总结一下,在view-change中最为重要的就是CPQ三个消息的集合,C确保了视图变更的时候,stable checkpoint之前的状态安全。P确保了视图变更前,已经PREPARE的消息的安全。Q确保了视图变更后P集合中的消息安全。回想一下pre-prepareprepare阶段最重要的任务是保证,同一个主节点发出的请求在同一个视图(view)中的顺序是一致的,而在视图切换过程中的CPQ三个集合就是解决这个问题的。

主动恢复

集群在运行过程中,可能出现网络抖动、磁盘故障等原因,会导致部分节点的执行速度落后大多数节点,而传统的PBFT拜占庭共识算法并没有实现主动恢复的功能,因此需要添加主动恢复的功能才能参与后续的共识流程,主动恢复会索取网络中其他节点的视图,最新的区块高度等信息,更新自身的状态,最终与网络中其他节点的数据保持一致。在Raft中采用的方式是主节点记录每个跟随者提交的日志编号,发送心跳包时携带额外信息的方式来保持同步,在Pbft中采用了视图协商(NegotiateView)的机制来保持同步。一个节点落后太多,这个时候它收到主节点发来的消息时,对消息水线(water mark)的检查会失败,这个时候计时器超时,发送view-change的消息,但是由于只有自己发起view-change达不到2f+1个节点的数量,本来正常运行的节点就退化为一个拜占庭节点,尽管是非主观的原因,为了尽可能保证集群的稳定性,所以加入了视图协商(NegotiateView)机制。当一个节点多次view-change失败就触发NegotiateView同步集群数据,流程如下;

  1. 新增节点Replica 4发起NegotiateView消息给其他节点;

  2. 其余节点收到消息以后,返回自己的视图信息,节点ID,节点总数N;

  3. Replica 4收到2f+1个相同的消息后,如果quorum个视图编号和自己不同,则同步view和N;

  4. Replica 4同步完视图后,发送RevoeryToCheckpoint的消息,其中包含自身的checkpoint信息;

  5. 其余节点收到RevoeryToCheckpoint后将自身最新的检查点信息返回给Replica 4;

  6. Replica 4收到quorum个消息后,更新自己的检查点到最新,更新完成以后向正常节点索要pset、qset和cset的信息(即PBFT算法中pre-prepare阶段、prepare阶段和commit阶段的数据)同步至全网最新状态;

增删节点(解决成员加入或者删除的问题)

Replica 5新节点加入的流程如下图所示;

  1. 新节点启动以后,向网络中其他节点建立连接并且发送AddNode消息;

  2. 当集群中的节点收到AddNode消息后,会广播AgreeAdd的消息;

  3. 当一个节点收到2f+1AgreeAdd的消息后,会发送AgreeAdd的消息给`Replica 5``

  4. ```Replica 5``会从收到的消息中,挑选一个节点同步数据,具体的过程在主动恢复中有说明,同步完成以后发送JoinNet

  5. 当集群中其他节点收到JoinNet之后重新计算视图view,节点总数N,同时将PQC信息封装到AgreeJoinOrExit中广播

  6. 当收到2f+1个有效的AgreeJoinOrExit后,新的主节点广播UpdateNet消息完成新增节点流程

删除节点的流程和新增节点的过程类似:

问题

Q:为什么PBFT算法需要三个阶段? A:假如简化为两个阶段pre-prepareprepare,当一个节点A收到2f+1个相同的prepare后执行请求,一部分节点B发生view-change,在view-change的过程中是拒收prepare消息的,所以这一部分节点的状态机会少执行一个请求,当view-change切换成功后重放prepare消息,在重放的过程中,节点A也完成了view-change,这个时候A就会面临重放的prepare已经执行过了,是否需要再次执行?会导致状态机出现二义性。


Q:view-change阶段集群会不可用么? A:view-change阶段集群会出现短暂的不可用,一般在实践的时候都会实现一个缓冲区来减少影响,实现参考 以太坊TXpool分析


Q:Pbft算法的时间复杂度? A:Pbft算法的时间复杂度O(n^2),在preparecommit阶段会将消息广播两次,一般而言,Pbft集群中的节点都不会超过100。

Pow算法---------增加恶意节点的成本

由于上面的PBFT算法在节点增多到一定程度时就很难继续维持系统的一致性,因此在区块链网络的创建之初,创建者创造性的提出了PoW算法。PoW算法,使得每个需要广播公认信息的节点需要付出海量的代价,因此其原始意愿上就不倾向于去破坏系统的安全性,实现这个算法并不是依靠技术上的革新,反而像是商业模式上的变化。

PoW共识机制的特点:

  1. 维持PoW算法的三个闭环元素:系统的安全性 获得奖励的激励 破坏系统的代价(下图展示了为什么PoW算法能够使得更多的人投入挖矿,这是一个由原始利益驱动的机制)

    复制代码
  2. PoW算法必须与链式结构结合

  3. PoW算法使得共识达成只需要单轮通信,大大节省了通信成本

PoS算法

从PoW到PoS

以太坊在2014年开始了长达8年的pos研究之旅,2020年,以太坊上线了信标链( beacon chain),并在这个链上尝试做一些pos的实验,2022年9月15日,将信标链与以太坊主链合并,至此完成了以太坊的升级,宣告了pow时代的结束。具体的流程分为三个阶段:

  1. 在主链之外独立建设一条信标链( beacon chain),在这条链上进行PoS实验

  2. 合并阶段1,旧主链作为"执行层",信标链作为"共识层",融合之后进入以太坊PoS时代

  3. 后续通过分片扩容提高rollup拓展性等等

Gasper

以下主要讨论信标链使用的共识和算法。

以太坊选择的算法叫做Casper FFG,在此基础上,引入另一个规则LMD-GHOST,他们共同组成了信标链的PoS算法Gasper;其中前者是一种投票选择的规定,后者用于分叉情况的处理。(PoS共识算法有很多种,以太坊选择的是Casper FFG)

投票过程

用户通过质押32个eth成为一名验证者(validator),相当于获得了投票的权利。为了解决节点通信量大的问题,以太坊做了一些时间层次的划分。如图,一个slot(插槽)代表一个出块时间,这个时间是12秒,32个slot组成一个Epoch(纪元),代表一个大周期,时间为6m24s,在这一点上pos的出块稳定程度是高于pow的。接下来,将会随机选择一名验证者,去发起一个区块提议(propose),由它去出块。同时,其它的验证者会组成一个人数≥128委员会(committee),委员会通过投票来确认区块,整个过程由一个伪随机算法RANDAO选出(RANDAO算法比较复杂,可以先简单理解为一个黑盒)。

Casper FFG

Casper这种pos算法其实都是bft类型的容错算法,它由pbft共识算法改进而来。因此,我们根据上文"PBFT算法实现流程"可以得到------要实现一轮"视图",需要进行两轮投票,分别在prepare和commit阶段。这个算法的优点是,只要达成共识,这个节点就是合法存在的(不想BTC里面需要等待6个节点之后确认),链不会出现分叉现象;然而,由于PBFT算法的局限性,我们很难做到在大规模(超多节点)网络中应用这一共识机制。Casper算法就兼容优点并改善了缺点。

在"投票过程"中,我们将一个区块时间定为一个slot(下图中的小块),32个slot就是一个Epoch(一个Epoch作为一个被投票的整体)。最终的敲定分为两个阶段(对于每一个Epoch而言,会接受先后两次投票,两次均通过才为通过):我们以下图为例进行讲解

上面的链是从下向上挖的,对于Epoch0而言:

  1. 当链挖到第32个的时候,大家投票认证 Epoch0,如果这次通过(被2/3以上的节点认可),那么Epoch0将会从提交状态进入认证状态(justified)
  2. 这次投票之后,我们开挖的就是 Epoch1。当达到第64个区块时,进入检查点,节点将对 Epoch0和 Epoch1进行投票(被2/3以上的节点认可),如果通过Epoch1将会从提交状态进入认证状态(justified),而Epoch0将会从认证状态(justified)进入终局性状态(finalized)这时Epoch0就被达成了共识,不需要之后参与讨论了。

下面是一个正在讨论的Epoch

分叉选择 LMD-GHOST

由于网络延迟或者潜在攻击的问题,新的区块产生可能会发生分叉,这时,我们选择的策略就叫LMD(最新的消息驱动),我们会选择票数最多(权重)的链作为权威链,这一点区分于pow中的最长链原则。下图的一个笑脸代表一个验证者投票,我们选择笑脸最多的链作为合法链,虽然比上面的分叉少一个区块,但它的票数多代表认可多。

和Casper FFG看上去很相似。Casper FFG是以32个区块为单位进行的整体投票的,是一个单纯的验证方案,而LMD-GHOST算法是在"挖矿"过程中处理区块间分叉的方案。LMD-GHOST处理的是短时间局部分歧,而Casper FFG处理的是长期共识问题。

相关推荐
天晟科技16 小时前
GameFi的前景:游戏与金融的未来交汇点
游戏·金融·区块链
Roun317 小时前
Web3和区块链如何促进数据透明与隐私保护的平衡
web3·区块链·隐私保护
The_Ticker1 天前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
程序猿阿伟1 天前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
TechubNews1 天前
Helius:从数据出发,衡量 Solana 的真实去中心化程度
去中心化·区块链
dingzd951 天前
Web3的核心技术:区块链如何确保信息安全与共享
web3·去中心化·区块链
清 晨1 天前
Web3与智能合约:区块链技术下的数字信任体系
web3·区块链·智能合约
CertiK1 天前
Web3.0安全开发实践:Clarity最佳实践总结
web3·区块链·clarity
加密新世界1 天前
Move on Sui入门 004-在sui链上发布Coin合约和Faucet Coin合约
区块链
YSGZJJ1 天前
股指期货的套保策略如何精准选择和规避风险?
人工智能·区块链