區塊鏈五:共識機制,用Rust原生寫PBFT協議

背景

因爲Libra學習rust,想用rust寫些東西,而剛好看區塊鏈共識機制的PBFT,就用原生Rust實現一個PBFT的分佈式系統,業餘中間斷斷續續寫,代碼比較零散。 中間也有些邏輯值得記錄, 主要在Rust的多線程,網絡通信的處理上。

總體架構

在這裏插入圖片描述

PBFT 節點實現

Node: 節點服務,提供PBFT的能力,
核心功能:處理client request, pre-prepare, prepare,commit,消息,最後reply給client,
其他:視圖轉換, check point創建,節點管理,消息簽名與驗籤。

主業務流程

在這裏插入圖片描述
這裏不一一講解每個階段,其中一階段代碼,有興趣可以clone下來看。


```cpp
pub fn receiveCommit(& mut self, msg:Bft_Commit_Message, mut executor:&mut Command_Executor) {

        // check sign for node
        let mut sign_msg = msg.clone();
        let msg_digest = msg.get_msg_digest();
        sign_msg.set_msg_digest(String::new());
        let msg_node_id = sign_msg.get_node_id();
        let pub_key_result = self.get_node_pub_key(&msg_node_id);

        if pub_key_result.is_none() {
            error!("can not found  node id for {}", sign_msg.get_node_id());
            return;
        }

        let signMsgStr = json::encode(&sign_msg).unwrap();
        if !Bft_Signtor::check_sign(signMsgStr.as_str(), pub_key_result.unwrap().as_str(), msg_digest.as_str()) {
            error!("commit msg sign not pass for {}", sign_msg.get_node_id());
            return;
        }

        info!("check pass, process commit msg");
        // check pass add to prepare cache;
        if self.commit_cache.contains_key(&msg.get_sequence_num()) {
            let list = self.commit_cache.get_mut(&msg.get_sequence_num()).unwrap();
            list.push(msg.clone());
        } else {
            let mut commit_msg_list = Vec::new();
            commit_msg_list.push(msg.clone());
            self.commit_cache.insert(msg.get_sequence_num(), commit_msg_list);
        }

        self.doReplay(msg.get_sequence_num(), executor);

    }

通信模塊

對P2P的網絡不熟悉,根據自己理解,實現了一個非阻塞式的網絡通信模塊,用於Node跟其他Node或者Client之間的通信。
在這裏插入圖片描述

非阻塞式通信

這裏實現的是一個非阻塞的通信服務, 可以用於跟其他節點或者Client來接發消息,基於Rust原生的TCP(TcpListener, TcpStream)實現。每個網絡連接TCPStream都分配一個線程作爲worker,處理連接進來的作爲reader worker,只用於讀,而外發到其他則創建一個TCPStream用於寫,並起一個write worker。

通信模塊只暴露startListener, sendmssage方法, 都支持非阻塞。 非阻塞通過Rust的channel來實現。
Read worker 線程一直嘗試讀取TCPStream消息,當讀取消息後,完成消息協議格式處理,把消息扔到channel。
Write worker 線程一直監聽channel是否有消息進來,有消息則把消息轉成String後,通過TCPStream發送出去。

message parser: 作爲一個獨立線程,因爲rust的channel支持一對多,等待所有worker讀的消息,根據消息類型,調對應外部PBFT Node的方法處理消息。

同步消息通信

當然通信模塊也支持同步發送消息,阻塞等待對方返回, 但同步同樣基於上訴的異步過程,但在消息協議中會註明是同步,並分配一個唯一id, 爲同步消息專門開通一個channel,當writer發送完消息,就通過channel的receiver阻塞,等待reply,而message parser收到消息,發現是reply並且是同步的,直接扔到同步的channel中。
通信消息協議
因爲基於TCP實現的網絡通信,這裏定義了一個簡單協議,通信模塊的worker負責協議的解析和文本序列化。
協議格式:
command version sync id length
body
body 爲文本, 格式內容由上層業務決定,這裏的PBFT格式爲消息的Json文本。
Command 預留reply,作爲同步消息的同步響應消息, 其他上層業務定義的業務命令,這裏是PBFT的receive, pre-prepare, prepare, commit

業務執行模塊

這裏的業務執行模塊,只是做了簡單的key-value緩存,支持 put key=value, get key, delete key 命令,業務器解析命令, 通過Rust原生Map緩存鍵值對。支持持久化,在checkpoint階段,把未持久化的值放入文本。 另外實時記錄執行的命令,理論支持上checkpoint恢復後再通過實時命令恢復緩存。

就是基於PBFT實現了一個分佈式的緩存系統, 而command executor爲接口,可以切換其他的實現把這分佈系統實現其他能力。

Message parse

消息解析模塊,根據command來解析PBFT消息格式,做業務路由,創建一個新線程處理消息。 這裏涉及Rust的多線程編程。


```cpp
//code
let node_mutex: Arc<Mutex<Btf_Node>> = Arc::new(Mutex::new(node));
let mutex = Arc::clone(&node_mutex);
thread::Builder::new().name(i.to_string()).spawn(move|| {

    let mut node = mutex.lock().unwrap();
let node_msg = node_msg_result.unwrap();
let result = node.doPrepare(node_msg, &mut executor);

if result.is_some() {
    let (view_num, sequece_num) = result.unwrap();
}
……

});

Rust多線程編程:

Rust都是通過thread創建新的線程,而rust沒有自己的runtime線程模型,使用的系統的的線程, 線程間有幾種通信或併發控制方式。
1、 mutex用於共享一個變量,使用變量前先獲得鎖,因爲rust有ownership的原因,每個變量只能有一個owner,而多個線程持有一個變量,需要通過Arc實現,通過Arc讓多線程同時持有mutex。Metux再由 lock then use的機制,併發操作共享變量。

2、 線程間通信,通過channel來實現,有點類似q + wait-notify的作用,做系統間的協同操作,上文的通信模塊就多處使用channel。 一個channel能有多個sender但只能有一個receiver,receiver可以阻塞等待消息,也可以非阻塞。

這裏創建讀網絡線程,讀線程讀到網絡數據後,通過sender通知業務線程。


```rust
let (msg_sender, msg_receiver) = channel();
thread::Builder::new().name("bft_node_listener".to_string()).spawn(move|| {
    let mut thread_index = 0;
    for stream in listener.incoming() {
        info!("receive one connection");
        let mut stream = stream.unwrap();
        let mut msg_sender_reader = Sender::clone(&msg_sender_sub);
        Default_TCP_Communication::create_new_reader(msg_sender_reader,   stream);
    }
});

//  阻塞式獲取讀線程通過channel投遞的消息對象, 轉交給message parser.
let msg_result = msg_receiver.recv();

if msg_result.is_ok() {
    let msg_box:Box<BftCommunicationMsg> = msg_result.unwrap();
……

3、 Send ,Sync接口,對多線程編程,這兩個是特殊接口,兩個接口都不需要手工實現,只要struct的成員都實現了, 對象就默認實現,而基礎類型都實現了這兩個接口。
Send: 實現了該接口的,可以在線程之間傳送變量的ownership。
Sync: 實現了該接口, 可以在線程之間出送變量的引用。

總結

至此說明鏈原生Rust寫PBFT過程中重點處理的邏輯,按PBFT論文實現消息的多階段狀態機處理,checkpoint處理,這裏沒具體提到PBFT邏輯,具體可以參考之前多文章“區塊鏈四” 另外重點提到原生多通信模塊,具備簡單異步同步通信能力; 另外Rust多線程特性也在此次實現中多次用到。

往期:
區塊鏈四:共識機制——PBFT算法深入講解
區塊鏈三:深入解析比特幣交易原理
區塊鏈二:比特幣的區塊數據結構
區塊鏈一 :區塊鏈應用介紹和展望

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章