PBFT共识算法详细分析及Java实现

PBFT共识算法详细分析及Java实现

为何写这个

最近研究了区块链相关的一些东西,其实就三大块:java

  1. 分布式存储(去中心)
  2. 共识机制
  3. 安全加密 分布式存储,就是一个分布式数据库,每一个节点都保存一份副本。经过非对称秘钥,hash等技术对操做数据进行签名,验证摘要,可追溯,以链式结构存储,互相以hash摘要校验数据,防篡改。以拜占庭容错共识算法解决节点间的通讯,达成一致协议。

区块链协议简介

分布式共识算法是分布式系统的核心,常见的有Paxos、pbft、bft、raft、pow等。区块链中常见的是POW、POS、DPOS、pbft等。 其中:node

  • POW、POS、DPOS是开放式的共识协议
  • PBFT为半开放式的共识协议
  • Paxos、raft等是封闭式共识协议

区别:git

  • 开放式,没法确切的知道节点的多少及链接状态,每一个节点均可能是恶意的,可是大多数是非恶意的
  • 半开放式,能够肯定节点的多少及链接状态,每一个节点均可能是恶意的,可是有知足必定条件的非恶意节点
  • 封闭式,每一个节点都是非恶意的,只不过可能断开链接或crash。

性能: 从上往下愈来愈高算法

总结:数据库

  • 私有链是封闭生态的存储系统,采用Paxos、raft最佳
  • 联盟链有半公开半开放特性,所以拜占庭容错的PBFT算法比较合适
  • 公有链来讲,POW、POS、DPOS是比较适合的高安全性的协议

那么,下面咱们要说的就是联盟链性质的共识协议:PBFT算法协议。 协议诞生已经好久了,网上的文章也很多,然而基本都是翻译原论文,稍加一些我的的阅读心得,细致的分析仍是比较少,一些关键的衔接点没有说清楚,粗看好像都懂,可是真正要实现起来,仍是有比较多坑。因而本人采用demo的方式,以多线程模拟多节点,实现完整的PBFT算法,其中有一些问题,记录下来,供各位参考,讨论。安全

PBFT算法简介


PBFT协议简单步骤:

主要有三个阶段:预准备(pre-prepare)、准备(prepare)、和确认(commit)网络

  1. 从全网节点选举出一个主节点(Leader),新区块由主节点负责生成。
  2. 其中一个节点的客户端向主节点发起请求。
  3. Pre-Prepare:主节点分配一个序列号n给收到的请求(顺序的保证!),并向全网广播<<PRE-PREPARE,v,n,d>,m>
  4. Prepare:每一个节点接收到交易请求后,模拟执行这些交易,验证交易报文的正确性。验证经过后,存入预备列表,并向全网广播<PREPARE,v,n,d,i>
  5. Commit:若是一个节点收到的2f(f为可容忍的拜占庭节点数)个其它节点发来的PREPARE消息摘要都和本身相等,就向全网广播一条commit消息<COMMIT,v,n,D(m),i>
  6. Reply:若是一个节点收到2f+1条commit消息,便可提交新区块及其交易到本地的区块链和状态数据库(操做确认完成)。

问题分析


整个协议理解起来还算比较简单,可是这里面有好些问题,须要一一的剖析。多线程

  1. 主节点怎么产生?
  2. 主节点失效了怎么办?
  3. 主节点造假怎么办?
  4. 数据怎么重传?

主节点的产生

由于节点的操做都须要经过主节点,因此每一个节点启动后的第一件事,找主节点。 主节点由公式p = v mod |R|计算获得,这里v是视图编号,p是副本编号,|R|是副本集合的个数。 因此其实找主节点就是初始化视图view。tcp

  1. 全网广播获取视图协议:
public static final int VIEW = -1;
  1. 超过2f+1的节点回复的view做为初始化的view(q1:若是没法知足怎么办?)
if(this.viewOk)return;
long count = vnumAggreCount.incrementAndGet(msg.getVnum());
if(count >= 2*maxf+1){
	vnumAggreCount.clear();
	this.view = msg.getVnum();
	viewOk = true;
	System.out.println("视图初始化完成["+index+"]:"+ view);
}

主节点失效了(这里失效指应用挂掉、或被他人控制做恶)

谁先发现主节点失效了,固然,这里是不能经过链接断开来看,由于即便tcp链接正常,也不必定业务处理正常。答案是,超时控制。 当发起请求的节点在超时时间内没有完成操做确认,则能够怀疑主节点失效,因而:分布式

  1. 客户端全网广播超时的请求报文,为何不用一个专门的包来发起失效提议?主要是防止发起请求的节点做恶,好比循环发起提议。致使不断产生提议检验,致使网络拥堵
  2. 副本节点检查,若是处理过(说明多是网络问题),从新将处理结果返回便可,若是未处理,则可能主节点宕机,将请求从新转发给主节点,且增长超时校验。这时,若是主节点是正常的,那么就会走正常流程,最终会确认操做请求。若是主节点真的有问题,则设置的超时将触发
  3. 超时后全网广播主节点切换提议
if(!this.viewOk) return; // 已经开始选举视图,不用重复发起
this.viewOk = false;
// 做为副本节点,广播视图变动投票
PbftMsg cv = new PbftMsg(CV, this.index);
cv.setVnum(this.view+1);
PbftMain.publish(cv);

当每一个节点都收到2f+1个对同一个view的提议后,则切换成新的view。且检查是否有请求待发送,一切恢复正常逻辑:

long count = vnumAggreCount.incrementAndGet(msg.getVnum());
if(count >= 2*maxf+1){
	vnumAggreCount.clear();
	this.view = msg.getVnum();
	viewOk = true;
	System.out.println("视图变动完成["+index+"]:"+ view);
	// 能够继续发请求
	if(curMsg != null){
		curMsg.setVnum(this.view);
		System.out.println("请求重传["+index+"]:"+ curMsg);
		doSendCurMsg();
	}
}

提议时,若是有恶意节点重复屡次发起,须要检测每一个节点只能投票一次。

String vkey = msg.getNode()+"@"+msg.getVnum();
if(votes_vnum.contains(vkey)){
	return;
}
votes_vnum.add(vkey);

清理状态

当commit经过后,即确认了操做,则能够对该消息相关的状态进行清理:

// 清理请求相关状态
	private void remove(String it) {
		votes_pre.remove(it);
		votes_pare.removeIf((vp)->{
			return StringUtils.startsWith(vp, it);
		});
		votes_comm.removeIf((vp)->{
			return StringUtils.startsWith(vp, it);
		});
		aggre_pare.remove(it);
		aggre_comm.remove(it);
		timeOuts.remove(it);
	}

其余关键点

PbftMsg消息的DataKey必须是惟一的,能够经过uuid或者其余方式定义

private int type; // 消息类型
	private int node; // 节点
	private int onode; // 发起请求的节点
	private int vnum; // 视图编号
	private int no; // 序列号
	private long time; // 时间戳
	private String data; // 数据,表示数据的hash,必须惟一

好吧,就这样吧,有问题再说。

代码实现

参考:

拜占庭共识算法之PBFT PBFT算法

相关文章
相关标签/搜索