区块链五:共识机制,用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算法深入讲解
区块链三:深入解析比特币交易原理
区块链二:比特币的区块数据结构
区块链一 :区块链应用介绍和展望

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