PBFT算法

联盟链中PBFT实现

在联盟链中,联盟各个节点往往都是来自同一个行业,有着共同的行业困扰和痛点,因此联盟链往往更注重于对实际问题的高效解决。而POW算法相对低效且费时费力,因此在联盟链中并不适用。

1.PBFT算法定义

PBFT是Practical Byzantine Fault Tolerance的缩写,即:实用拜占庭容错算法。该算法是Miguel Castro(卡斯特罗)和Barbara Liskov(利斯科夫)在1999年提出来的,解决了原始拜占庭容错算法效率不高的问题,算法的时间复杂度是O(n^2),使得在实际系统应用中可以解决拜占庭容错问题。该论文发表在1999年的操作系统设计与实现国际会议上(OSDI99)。其中Barbara Liskov就是提出了著名的里氏替换原则(LSP)的人,2008年图灵奖得主。以下拜占庭容错问题简称BFT。实现了在有限个节点的情况下的拜占庭问题,有3f+1的容错性,并同时保证一定的性能。

2.PBFT算法流程

如图:

主要分为5个阶段实施,执行前需要先在全网节点选举出一个主节点,新区块的生成由主节点负责。选举出主节点后,开始落实PBFT。五个阶段分别是:Request阶段、Pre-prepare阶段、Prepare阶段、Commit阶段、执行并Reply。

假设此时有主节点、从1节点、从2节点、从3节点共4个节点。

第一阶段:Request阶段

客户端发起请求。

第二阶段:Pre-prepare阶段

主节点收到客户端请求后给请求进行编号,并发送pre-pre类型信息给其他节点。

第三阶段:Prepare阶段

从1节点同意主节点请求的编号,并发送prepare类型消息给主节点和其他两个从节点。如果从1节点不同意请求的编号,则不进行处理。

从1节点如果收到另外两个从节点都发出的同意主节点分配的编号的prepare类型消息,则表示从1节点的状态wei Prepared,进入Commit阶段。

第四阶段:Commit阶段

从1节点进入Prepared状态后,将发送一个commit类型消息给其他所有节点,告诉其他节点当前从1节点已经进入Prepared状态。

如果从1节点收到2f+1条commit消息,则证明从1节点已经进入了Commit状态。当一个请求在某个节点中到达Commit状态后,该请求就会被执行。

第五阶段:执行并Reply

执行区块 生成并Reply生成结果。

目前Hyperledger Fabric已经将PBFT纳入候选共识算法。但是PBFT缺点也很明显,由于先选举主节点,因此当不得不重新选举主节点时,PBFGT将无法达成共识。

3.部分代码

3.1消息的载体:

package com.mindata.blockchain.socket.pbft.msg;

/**
 * pbft算法传输prepare和commit消息的载体
 * @author wuweifeng wrote on 2018/4/23.
 */
public class VoteMsg {
    /**
     * 当前投票状态(Prepare,commit)
     */
    private byte voteType;
    /**
     * 区块的hash
     */
    private String hash;
    /**
     * 区块的number
     */
    private int number;
    /**
     * 是哪个节点传来的
     */
    private String appId;
    /**
     * 是否同意
     */
    private boolean agree;

    @Override
    public String toString() {
        return "VoteMsg{" +
                "voteType=" + voteType +
                ", hash='" + hash + '\'' +
                ", number=" + number +
                ", appId='" + appId + '\'' +
                ", agree=" + agree +
                '}';
    }

    public byte getVoteType() {
        return voteType;
    }

    public void setVoteType(byte voteType) {
        this.voteType = voteType;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public boolean isAgree() {
        return agree;
    }

    public void setAgree(boolean agree) {
        this.agree = agree;
    }
}
package com.mindata.blockchain.socket.pbft.msg;

import com.mindata.blockchain.block.Block;

/**
 * @author wuweifeng wrote on 2018/4/25.
 */
public class VotePreMsg extends VoteMsg {
    private Block block;

    public Block getBlock() {
        return block;
    }

    public void setBlock(Block block) {
        this.block = block;
    }
}

3.2算法流程定义:


/**
 * pbft算法的各状态
 *
 * 算法流程如下:
 *
 * 当前时间片的锻造者将收集到的交易打包成block并进行广播(这一步与之前的算法一致)
 * 收到block的委托人节点如果还没有收到过这个block并且验证合法后,就广播一个prepare<h, d, s>消息,其中h为block的高度,d是block的摘要,s是本节点签名
 * 收到prepare消息后,节点开始在内存中累加消息数量,当收到超过f+1不同节点的prepare消息后,节点进入prepared状态,之后会广播一个commit<h, d, s>消息
 * 每个节点收到超过2f+1个不同节点的commit消息后,就认为该区块已经达成一致,进入committed状态,并将其持久化到区块链数据库中
 * 系统在在收到第一个高度为h的block时,启动一个定时器,当定时到期后,如果还没达成一致,就放弃本次共识。
*/
public class VoteType {
    /**
     * 节点自己打算生成Block
     */
    public static final byte PREPREPARE = 1;
    /**
     * 节点收到请求生成block消息,进入准备状态,对外广播该状态
     */
    public static final byte PREPARE = 2;
    /**
     * 每个节点收到超过2f+1个不同节点的commit消息后,就认为该区块已经达成一致,进入committed状态,并将其持久化到区块链数据库中
     */
    public static final byte COMMIT = 3;
}

package com.mindata.blockchain.socket.pbft.queue;

import cn.hutool.core.bean.BeanUtil;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.common.AppId;
import com.mindata.blockchain.common.timer.TimerManager;
import com.mindata.blockchain.core.event.AddBlockEvent;
import com.mindata.blockchain.core.sqlite.SqliteManager;
import com.mindata.blockchain.socket.pbft.VoteType;
import com.mindata.blockchain.socket.pbft.event.MsgPrepareEvent;
import com.mindata.blockchain.socket.pbft.msg.VoteMsg;
import com.mindata.blockchain.socket.pbft.msg.VotePreMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;

/**
 * preprepare消息的存储,但凡收到请求生成Block的信息,都在这里处理
 *
 * @author wuweifeng wrote on 2018/4/23.
 */
@Component
public class PreMsgQueue extends BaseMsgQueue {
    @Resource
    private SqliteManager sqliteManager;
    @Resource
    private PrepareMsgQueue prepareMsgQueue;
    @Resource
    private ApplicationEventPublisher eventPublisher;

    private ConcurrentHashMap<String, VotePreMsg> blockConcurrentHashMap = new ConcurrentHashMap<>();

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void push(VoteMsg voteMsg) {
        //该队列里的是votePreMsg
        VotePreMsg votePreMsg = (VotePreMsg) voteMsg;
        String hash = votePreMsg.getHash();
        //避免收到重复消息
        if (blockConcurrentHashMap.get(hash) != null) {
            return;
        }
        //但凡是能进到该push方法的,都是通过基本校验的,但在并发情况下可能会相同number的block都进到投票队列中
        //需要对新进来的Vote信息的number进行校验,如果在比prepre阶段靠后的阶段中,已经出现了认证OK的同number的vote,则拒绝进入该队列
        if (prepareMsgQueue.otherConfirm(hash, voteMsg.getNumber())) {
            logger.info("拒绝进入Prepare阶段,hash为" + hash);
            return;
        }
        // 检测脚本是否正常
        try {
            sqliteManager.tryExecute(votePreMsg.getBlock());
        } catch (Exception e) {
            // 执行异常
            logger.info("sql指令预执行失败");
            return;
        }

        //存入Pre集合中
        blockConcurrentHashMap.put(hash, votePreMsg);

        //加入Prepare行列,推送给所有人
        VoteMsg prepareMsg = new VoteMsg();
        BeanUtil.copyProperties(voteMsg, prepareMsg);
        prepareMsg.setVoteType(VoteType.PREPARE);
        prepareMsg.setAppId(AppId.value);
        eventPublisher.publishEvent(new MsgPrepareEvent(prepareMsg));
    }

    /**
     * 根据hash,得到内存中的Block信息
     *
     * @param hash
     *         hash
     * @return Block
     */
    public Block findByHash(String hash) {
        VotePreMsg votePreMsg = blockConcurrentHashMap.get(hash);
        if (votePreMsg != null) {
            return votePreMsg.getBlock();
        }
        return null;
    }

    /**
     * 新区块生成后,clear掉map中number比区块小的所有数据
     */
    @Order(3)
    @EventListener(AddBlockEvent.class)
    public void blockGenerated(AddBlockEvent addBlockEvent) {
        Block block = (Block) addBlockEvent.getSource();
        int number = block.getBlockHeader().getNumber();
        TimerManager.schedule(() -> {
            for (String key : blockConcurrentHashMap.keySet()) {
                if (blockConcurrentHashMap.get(key).getNumber() <= number) {
                    blockConcurrentHashMap.remove(key);
                }
            }
            return null;
        }, 2000);
    }
}
package com.mindata.blockchain.socket.pbft.event;

import com.mindata.blockchain.socket.pbft.msg.VoteMsg;
import org.springframework.context.ApplicationEvent;

/**
 * 消息已被验证,进入到Prepare集合中
 * @author wuweifeng wrote on 2018/4/25.
 */
public class MsgPrepareEvent extends ApplicationEvent {
    public MsgPrepareEvent(VoteMsg source) {
        super(source);
    }
}
package com.mindata.blockchain.socket.pbft.queue;

import cn.hutool.core.bean.BeanUtil;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.common.AppId;
import com.mindata.blockchain.core.event.AddBlockEvent;
import com.mindata.blockchain.socket.pbft.VoteType;
import com.mindata.blockchain.socket.pbft.event.MsgCommitEvent;
import com.mindata.blockchain.socket.pbft.msg.VoteMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * Prepare阶段的消息队列
 *
 * @author wuweifeng wrote on 2018/4/25.
 */
@Component
public class PrepareMsgQueue extends AbstractVoteMsgQueue {
    @Resource
    private CommitMsgQueue commitMsgQueue;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 收到节点(包括自己)针对某Block的Prepare消息
     *
     * @param voteMsg
     *         voteMsg
     */
    @Override
    protected void deal(VoteMsg voteMsg, List<VoteMsg> voteMsgs) {
        String hash = voteMsg.getHash();
        VoteMsg commitMsg = new VoteMsg();
        BeanUtil.copyProperties(voteMsg, commitMsg);
        commitMsg.setVoteType(VoteType.COMMIT);
        commitMsg.setAppId(AppId.value);
        //开始校验并决定是否进入commit阶段
        //校验该vote是否合法
        if (commitMsgQueue.hasOtherConfirm(hash, voteMsg.getNumber())) {
             agree(commitMsg, false);
        } else {
            //开始校验拜占庭数量,如果agree的超过2f + 1,就commit
            long agreeCount = voteMsgs.stream().filter(VoteMsg::isAgree).count();
            long unAgreeCount = voteMsgs.size() - agreeCount;

            //开始发出commit的同意or拒绝的消息
            if (agreeCount >= pbftAgreeSize()) {
                agree(commitMsg, true);
            } else if (unAgreeCount >= pbftSize() + 1) {
                agree(commitMsg, false);
            }
        }

    }

    private void agree(VoteMsg commitMsg, boolean flag) {
        logger.info("Prepare阶段完毕,是否进入commit的标志是:" + flag);
        //发出拒绝commit的消息
        commitMsg.setAgree(flag);
        voteStateConcurrentHashMap.put(commitMsg.getHash(), flag);
        eventPublisher.publishEvent(new MsgCommitEvent(commitMsg));
    }

    /**
     * 判断大家是否已对其他的Block达成共识,如果true,则拒绝即将进入队列的Block
     *
     * @param hash
     *         hash
     * @return 是否存在
     */
    public boolean otherConfirm(String hash, int number) {
        if (commitMsgQueue.hasOtherConfirm(hash, number)) {
            return true;
        }
        return hasOtherConfirm(hash, number);
    }

    /**
     * 新区块生成后,clear掉map中number比区块小的所有数据
     *
     * @param addBlockEvent  addBlockEvent
     */
    @Order(3)
    @EventListener(AddBlockEvent.class)
    public void blockGenerated(AddBlockEvent addBlockEvent) {
        Block block = (Block) addBlockEvent.getSource();
        clearOldBlockHash(block.getBlockHeader().getNumber());
    }
}
package com.mindata.blockchain.socket.pbft.event;

import com.mindata.blockchain.socket.pbft.msg.VoteMsg;
import org.springframework.context.ApplicationEvent;

/**
 * 消息已被验证,进入到Commit集合中
 * @author wuweifeng wrote on 2018/4/25.
 */
public class MsgCommitEvent extends ApplicationEvent {
    public MsgCommitEvent(VoteMsg source) {
        super(source);
    }
}

3.3传输消息的载体

package com.mindata.blockchain.socket.pbft.msg;

/**
 * pbft算法传输prepare和commit消息的载体
 * @author wuweifeng wrote on 2018/4/23.
 */
public class VoteMsg {
    /**
     * 当前投票状态(Prepare,commit)
     */
    private byte voteType;
    /**
     * 区块的hash
     */
    private String hash;
    /**
     * 区块的number
     */
    private int number;
    /**
     * 是哪个节点传来的
     */
    private String appId;
    /**
     * 是否同意
     */
    private boolean agree;

    @Override
    public String toString() {
        return "VoteMsg{" +
                "voteType=" + voteType +
                ", hash='" + hash + '\'' +
                ", number=" + number +
                ", appId='" + appId + '\'' +
                ", agree=" + agree +
                '}';
    }

    public byte getVoteType() {
        return voteType;
    }

    public void setVoteType(byte voteType) {
        this.voteType = voteType;
    }

    public String getHash() {
        return hash;
    }

    public void setHash(String hash) {
        this.hash = hash;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public boolean isAgree() {
        return agree;
    }

    public void setAgree(boolean agree) {
        this.agree = agree;
    }
}
package com.mindata.blockchain.socket.pbft.msg;

import com.mindata.blockchain.block.Block;

/**
 * @author wuweifeng wrote on 2018/4/25.
 */
public class VotePreMsg extends VoteMsg {
    private Block block;

    public Block getBlock() {
        return block;
    }

    public void setBlock(Block block) {
        this.block = block;
    }
}

小结

在这里仅仅是做一个笔记用。如果想了解更多,建议参考官方文档http://pmg.csail.mit.edu/papers/osdi99.pdf。这一篇主要是为了弄清楚PBFT 的流程,这几天在看《区块链底层设计 Java实战》一直没弄得太懂,这一段代码源自gitee:https://gitee.com/tianyalei/md_blockchain?_from=gitee_search。

posted @ 2021-10-13 20:48  providence2320  阅读(588)  评论(0)    收藏  举报