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算法

相關文章
相關標籤/搜索