PBFT algorithm java implementation

java PBFT algorithm implementation (on)

In this blog, I will go to achieve added in node PBFT, and certified by Java. Wherein the information transmission network implemented using socket.

About some introductory PBFT algorithm, we can go take a look at the online blog, you can also refer to one on my last blog , about how to build P2P network can refer to my previous blog .

Address of the project: GitHub

Preparation before use

Use maven to build the project, of course, may not be used, this depends on your idea of ​​it.

You need to use Java packages:

  • t-io: using t-io network socket communication, emm, the framework document to charges (699RMB), but here we simply use, which does not need to use very complex functions.
  • fastjson: Json data analysis
  • lombok: fast get, set, and toString
  • hutool: In case to use it?
  • lombok: save code
  • log4j: Logs
  • guava: Google and some contract

Node data structure

First things first, we need to define what data structure nodes.

First, the data structure of the node Node:

@Data
public class Node extends NodeBasicInfo{

    /**
     * 单例设计模式
     * @return
     */
    public static Node getInstance(){
        return node;
    }
    private Node(){}
    
    private static Node node = new Node();

    /**
     * 判断结点是否运行
     */
    private boolean isRun = false;

    /**
     * 视图状态,判断是否ok,
     */
    private volatile boolean viewOK;
}

@Data
public class NodeBasicInfo {
    /**
     * 结点地址的信息
     */
    private NodeAddress address;
    /**
     * 这个代表了结点的序号
     */
    private int index;

}

@Data
public class NodeAddress {
    /**
     * ip地址
     */
    private String ip;
    /**
     * 通信地址的端口号
     */
    private int port;

}

The above code looks a bit more, but in fact very few (the above three categories, in order to show, I put them together). Node attribute information defined above should contain: ip, port, sequence number index, view whether or not ok.

Information node is simple. Then we can look at the data structure of the PbftMsg. Representative PbftMsg is Pbft algorithm sends the data structure information.

@Data
public class PbftMsg {
    /**
     * 消息类型
     */
    private int msgType;

    /**
     * 消息体
     */
    private String body;

    /**
     * 消息发起的结点编号
     */
    private int node;

    /**
     * 消息发送的目的地
     */
    private int toNode;

    /**
     * 消息时间戳
     */
    private long time;

    /**
     * 检测是否通过
     */
    private boolean isOk;

    /**
     * 结点视图
     */
    private int viewNum;

    /**
     * 使用UUID进行生成
     */
    private String id;

    private PbftMsg() {
    }

    public PbftMsg(int msgType, int node) {
        this.msgType = msgType;
        this.node = node;
        this.time = System.currentTimeMillis();
        this.id = IdUtil.randomUUID();
        this.viewNum = AllNodeCommonMsg.view;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PbftMsg msg = (PbftMsg) o;
        return node == msg.node &&
                time == msg.time &&
                viewNum == msg.viewNum &&
                body.equals(msg.body) &&
                id.equals(msg.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(body, node, time, viewNum, id);
    }
}

PBFTMSG Here I simply define a little, not very rigorous. Here referring to the next major important attributes:

msgType represents a message type Pbft algorithm, because there are different types of algorithms pbft request message.

Similarly, we need to preserve some state data:

public class AllNodeCommonMsg {
    /**
     * 获得最大失效结点的数量
     *
     * @return
     */
    public static int getMaxf() {
        return (size - 1) / 3;
    }

    /**
     * 获得主节点的index序号
     *
     * @return
     */
    public static int getPriIndex() {
        return (view + 1) % size;
    }

    /**
     * 保存结点对应的ip地址和端口号
     */
    public static ConcurrentHashMap<Integer, NodeBasicInfo> allNodeAddressMap = new ConcurrentHashMap<>(2 << 10) ;

    /**
     * view的值,0代表view未被初始化
     * 当前视图的编号,通过这个编号可以算出主节点的序号
     */
    public volatile static int view = 0;
    /**
     * 区块链中结点的总结点数
     */
    public static int size = allNodeAddressMap.size()+1;
}

Logical flow

The above definition of look on the line, where we mostly understand the process better PBFT algorithm. In the following we will properly analyze the process PBFT algorithm.

Encircle the trees began Haomo, towering building from the base soil. All in all the start, we need to start from the beginning of the joining node.

Previous previous blog , we know that there is a master node in a PBFT algorithm, the master node is how out of it? Of course, by view is counted out.

Provided: the number of nodes is N, the current view is view, the main node id is:

$$primaryId = (view +1) mod N$$

Therefore, when a node starts, he must be confused, I do not know who he is, this time we need to find a node to ask what is the current situation, ask? Certainly ask the master node, the master node but who is it? Node in the chain block, of course, knows who the master node Yes. This time, the start of the new node (let's call it little brother) will go ask all the nodes: Brother, your view is how much ah, can not be good enough to tell my brother! Then big brother who will tell their own view brother. But also worried that the younger brother had lied to him to give him the wrong view, so I decided when to return the view to meet a certain number, I decided to use the view.

So a certain amount of this is how much?

quorum: number of nodes reached consensus required $ quorum = \ lceil \ frac {N + f +1} {2} \ rceil $

He says so many things in theory, now let's talk about the code aspect to consider is how.

It defined the two simple data structure, since we can think of the process Pbft algorithm.

Code flow

First things first, let's define: number of nodes starting at 0, view but also from zero, of course, this is certainly not the time to size 0, 1. No. so, the master node is $ primaryId = (0 + 1)% 1 = 0 $.

Since we use socket communication, using a t-io framework. We have in terms of service and client to understand the process of obtaining this view. Magic Pen Ma Liang coming! !


The angle from the socket interpretation process.

First block chain as a server node, the new node is called the client (follow the philosophical attitude, client sends a request to ask server). Because there are a plurality of server, so for D节点it, requires a corresponding plurality of different client sends a request to the server, respectively. The server then returns the view to the client.

然后说下代码,服务端接受到client发送的请求后,就将自己的view返回给client,然后client根据view的num决定哪一个才是真正的view。这里可以分为3个步骤:客户端请求view,服务端返回view,客户端处理view。

客户端请求view:

    /**
     * 发送view请求
     *
     * @return
     */
    public boolean pubView() {
        log.info("结点开始进行view同步操作");
        // 初始化view的msg
        PbftMsg view = new PbftMsg(MsgType.GET_VIEW, node.getIndex());
        // 将消息进行广播
        ClientUtil.clientPublish(view);
        return true;
    }

上面的代码很简单,就是客户端向服务端广播PbftMsg,然后该消息的类型是GET_VIEW类型(也就是告诉大哥们,我是来请求view的)。

既然客户端广播了PBFT消息,当然服务端就会接受到。

下面是server端的代码,至于服务端是怎么接收到的,参考我的上一篇博客,或者别人的博客。当服务端接受到view的请求消息后,就会将自己的view发送给client。

    /**
     * 将自己的view发送给client
     *
     * @param channelContext
     * @param msg
     */
    private void onGetView(ChannelContext channelContext, PbftMsg msg) {
        log.info("server结点回复视图请求操作");
        int fromNode = msg.getNode();
        // 设置消息的发送方
        msg.setNode(node.getIndex());
        // 设置消息的目的地
        msg.setToNode(fromNode);
        // 设置消息的view
        msg.setViewNum(AllNodeCommonMsg.view);
        String jsonView = JSON.toJSONString(msg);
        MsgPacket msgPacket = new MsgPacket();
        try {
            msgPacket.setBody(jsonView.getBytes(MsgPacket.CHARSET));
            // 将消息发送给client
            Tio.send(channelContext, msgPacket);
        } catch (UnsupportedEncodingException e) {
            log.error(String.format("server结点发送view消息失败%s", e.getMessage()));
        }
    }

然后是client接受到server返回的消息,然后进行处理。

    /**
     * 获得view
     *
     * @param msg
     */
    private void getView(PbftMsg msg) {
        // 如果节点的view好了,当然也就不要下面的处理了
        if (node.isViewOK()) {
            return;
        }
        // count代表有多少位大哥返回该view
        long count = collection.getViewNumCount().incrementAndGet(msg.getViewNum());
        // count >= 2 * AllNodeCommonMsg.getMaxf()则代表该view 可以
        if (count >= 2 * AllNodeCommonMsg.getMaxf() + 1 && !node.isViewOK()) {
            collection.getViewNumCount().clear();
            node.setViewOK(true);
            AllNodeCommonMsg.view = msg.getViewNum();
            log.info("视图初始化完成OK");
        }
    }

在这里大家可能会发现一个问题,我在第二个if中还是使用了!node.isViewOK()。那是因为我发现在多线程的情况下,即使view设置为true了,下面的代码还是会执行,也就是说log.info("视图初始化完成OK");会执行两次,因此我又加了一个view检测。

同样,我们可以来实现一下视图变更(ViewChange)的算法。

什么时候会产生viewChange呢?当然是主节点失效的时候,就会进行viewchange的执行。当某一个节点发现主节点失效时(也即是断开连接的时候),他就会告诉所有的节点(进行广播):啊!!不好了,主节点GG了,让我们重新选择一个主节点吧。因此,当节点收到quorum个重新选举节点的消息时,他就会将改变自己的视图。

这里有一个前提,就是当主节点和客户端断开的时候,客户端会察觉到。

client的代码:

重新选举view就是将目前的veiw+1,然后讲该view广播出去。

    /**
     * 发送重新选举的消息
     * 这个onChangeView是通过其它函数调用的,msg的内容如下所示
     *  PbftMsg msg = new PbftMsg(MsgType.CHANGE_VIEW,node.getIndex());
     */
    private void onChangeView(PbftMsg msg) {
        // view进行加1处理
        int viewNum = AllNodeCommonMsg.view + 1;
        msg.setViewNum(viewNum);
        ClientUtil.clientPublish(msg);
    }

服务端代码:

服务端代码和前面的的代码很类似。

    /**
     * 重新设置view
     *
     * @param channelContext
     * @param msg
     */
    private void changeView(ChannelContext channelContext, PbftMsg msg) {
        if (node.isViewOK()) {
            return;
        }
        long count = collection.getViewNumCount().incrementAndGet(msg.getViewNum());

        if (count >= 2 * AllNodeCommonMsg.getMaxf() + 1 && !node.isViewOK()) {
            collection.getViewNumCount().clear();
            node.setViewOK(true);
            AllNodeCommonMsg.view = msg.getViewNum();
            log.info("视图变更完成OK");
        }
    }

总结

在这里,大家可能会有个疑惑,为什么进行广播消息不是使用服务端去广播消息,反而是使用client一个一个的去广播消息。原因有一下两点:

  • 因为没有购买t-io文档,因此我也不知道server怎么进行广播消息。因为它取消了学生优惠,现在需要699¥,实在是太贵了(当然这个贵是针对与我而言的,不过这个框架还是真的挺好用的)舍不得买。

  • 为了是思路清晰,client就是为了请求数据,而server就是为了返回数据。这样想的时候,不会是自己的思路断掉

在这里为止,我们就简单的实现了节点加入和view的变迁(当然是最简单的实现,emm,大佬勿喷)。在下篇博客中,我将会介绍共识过程的实现。如果这篇博客有错误的地方,望大佬指正。可以在评论区留言或者邮箱联系。

项目地址:GitHub

Guess you like

Origin www.cnblogs.com/xiaohuiduan/p/12339955.html