BFT 是什么
⚫ BFT( Byzantine Fault Tolerance)称为拜占庭容错
⚫ 拜占庭容错技术是一类分布式计算领域的容错技术,用来解决拜占庭问题,拜占庭
假设是对现实世界的模型化,由于硬件错误、网络拥塞或中断以及遭到恶意攻击等
原因,计算机和网络也有可能出现不可预料的行为
⚫ 拜占庭容错技术被设计用来处理这些异常行为,并满足所要解决的问题的规范要求
拜占庭容错系统
⚫ 区块链网络的记账共识和拜占庭将军的问题是相似的
⚫ 参与共识记账的每一个节点相当于将军,节点之间的消息传递相当于信使,某些节 点可能由于各种原因而产生错误的信息传递给其他节点。通常这些发生故障的节点 被称为拜占庭节点,而正常的节点即为非拜占庭节点
⚫ 假设分布式系统拥有 n 台节点,并假设整个系统拜占庭节点不超过 m 台(n ≥ 3m + 1),拜占庭容错系统需要满足如下两个条件
◼所有非拜占庭节点使用相同的输入信息,产生同样的结果。在区块链系统中, 可以理解为,随机数相同、区块算法相同、原账本相同的时候,计算结果相同
◼如果输入的信息正确,那么所有非拜占庭节点必须接收这个消息,并计算相应 的结果。在区块链系统中,可以理解为,非拜占庭节点需要对客户的请求进行 计算并生成区块
⚫ 拜占庭容错系统需要达成如下两个指标
◼安全性:任何已经完成的请求都不会被更改,它可以在以后请求看到。在区块 链系统中,可以理解为,已经生成的账本不可篡改,并且可以被节点随时查看
◼活性:可以接受并且执行非拜占庭客户端的请求,不会被任何因素影响而导致 非拜占庭客户端的请求不能执行。在区块链系统中,可以理解为,系统需要持 续生成区块,为用户记账,这主要靠挖矿的激励机制来保证
⚫ 在分析拜占庭问题的时候,假设信道是可信的,拓展开来,在拜占庭容错系统,普 遍采用的假设包括如下条件
◼拜占庭节点的行为可以是任意的,拜占庭节点之间可以共谋
◼节点之间的错误是不相关的
◼节点之间通过异步网络连接,网络中的消息可能丢失、乱序并延时到达,但大 部分协议假设消息在有限的时间里能传达到目的地
◼节点之间传递的信息,第三方可以嗅探到,但是不能篡改、伪造信息的内容和 破坏信息的完整性
PBFT 的一致性协议
⚫一致性协议至少包含请求(request)、序号分配(pre-prepare)、响应(reply)三 个阶段。根据协议设计的不同,可能包含相互交互(prepare) 、序号确认(commit) 等阶段
⚫PBFT 系统通常假设故障节点个数为 m 个,而整个服务节点数为 3m+1 个
//简单模拟PBFT系统节点信息同步校验
//声明nodeInfo节点,代表各个小国家
type nodeInfo struct {
//节点名称
id string
//路径
path string
//http响应
writer http.ResponseWriter
}
//存系欸但名字和节点地址
var nodeTable = make(map[string]string)
func main() {
//接收终端传来的参数
userId := os.Args[1]
fmt.Println(userId)
//存储四个国家的地址
nodeTable = map[string]string{
"N0": "localhost:1111",
"N1": "localhost:1112",
"N2": "localhost:1113",
"N3": "localhost:1114",
}
//创建国家
node := nodeInfo{id: userId, path: nodeTable[userId]}
//http协议的回调函数
//http://localhost:1111/req?warTime=hello
http.HandleFunc("/req", node.request)
http.HandleFunc("/prePrepare", node.prePrepare)
http.HandleFunc("/prepare", node.prepare)
http.HandleFunc("/commit", node.commit)
//启动监听
if err := http.ListenAndServe(node.path, nil); err != nil {
fmt.Println(err)
}
}
//当http服务器接收到网络请求,带/req,则回调此方法
func (node *nodeInfo) request(writer http.ResponseWriter, request *http.Request) {
//接收请求数据
request.ParseForm()
if (len(request.Form["warTime"]) > 0) {
//证明接收到了客户端发来的数据
node.writer = writer
fmt.Println("主节点接收到的参数信息为", request.Form["warTime"][0])
//激活主节点后,将数据分发给其他节点,广播
node.broadcast(request.Form["warTime"][0], "/prePrepare")
}
}
//节点发送广播的方法
func (node *nodeInfo) broadcast(msg string, path string) {
fmt.Println("广播", path)
//遍历所有节点进行广播
for nodeId, url := range nodeTable {
//判断是否是自己,若为自己,则跳出档次循环
if nodeId == node.id {
continue
}
//要进行分发的节点
//像req那样,通过http请求广播
http.Get("http://" + url + path + "?warTime=" + msg + "&nodeId=" + node.id)
}
}
func (node *nodeInfo) prePrepare(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
fmt.Println("接收到的广播", request.Form["warTime"][0])
//若数据有值,则进行广播
if len(request.Form["warTime"]) > 0 {
//有值
node.broadcast(request.Form["warTime"][0], "/prepare")
}
}
func (node *nodeInfo) prepare(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
fmt.Println("接收到子节点的广播", request.Form["warTime"][0])
//数据大于0,认为接收到
if len(request.Form["warTime"]) > 0 {
//进行拜占庭校验
node.authentication(request)
}
}
//用于记录正常响应的好的节点
var authenticationNodeMap = make(map[string]string)
//定义标签
var authenticationSuccess = false
//拜占庭校验
func (node *nodeInfo) authentication(request *http.Request) {
//第一次进去
if !authenticationSuccess {
//运行的正常节点的判断
if len(request.Form["nodeId"][0]) > 0 {
//证明节点是OK的
authenticationNodeMap[request.Form["nodeId"][0]] = "OK"
//如果有两个国家节点正确返回了结果,可以发送
if len(authenticationNodeMap) > len(nodeTable)/3 {
//(n-1)/3的容错性
//进入commit阶段
authenticationSuccess = true
node.broadcast(request.Form["warTime"][0], "/commit")
}
}
}
}
func (node *nodeInfo) commit(writer http.ResponseWriter, request *http.Request) {
if writer != nil {
fmt.Println("拜占庭校验成功")
io.WriteString(node.writer, "ok")
}
}