PBFT——拜占庭算法 go简单实现

BFT 是什么
⚫ BFT( Byzantine Fault Tolerance)称为拜占庭容错

⚫ 拜占庭容错技术是一类分布式计算领域的容错技术,用来解决拜占庭问题,拜占庭
假设是对现实世界的模型化,由于硬件错误、网络拥塞或中断以及遭到恶意攻击等
原因,计算机和网络也有可能出现不可预料的行为

⚫ 拜占庭容错技术被设计用来处理这些异常行为,并满足所要解决的问题的规范要求

拜占庭容错系统

⚫ 区块链网络的记账共识和拜占庭将军的问题是相似的

⚫ 参与共识记账的每一个节点相当于将军,节点之间的消息传递相当于信使,某些节 点可能由于各种原因而产生错误的信息传递给其他节点。通常这些发生故障的节点 被称为拜占庭节点,而正常的节点即为非拜占庭节点

⚫ 假设分布式系统拥有 n 台节点,并假设整个系统拜占庭节点不超过 m 台(n ≥ 3m + 1),拜占庭容错系统需要满足如下两个条件

◼所有非拜占庭节点使用相同的输入信息,产生同样的结果。在区块链系统中, 可以理解为,随机数相同、区块算法相同、原账本相同的时候,计算结果相同

◼如果输入的信息正确,那么所有非拜占庭节点必须接收这个消息,并计算相应 的结果。在区块链系统中,可以理解为,非拜占庭节点需要对客户的请求进行 计算并生成区块

⚫ 拜占庭容错系统需要达成如下两个指标

◼安全性:任何已经完成的请求都不会被更改,它可以在以后请求看到。在区块 链系统中,可以理解为,已经生成的账本不可篡改,并且可以被节点随时查看

◼活性:可以接受并且执行非拜占庭客户端的请求,不会被任何因素影响而导致 非拜占庭客户端的请求不能执行。在区块链系统中,可以理解为,系统需要持 续生成区块,为用户记账,这主要靠挖矿的激励机制来保证

⚫ 在分析拜占庭问题的时候,假设信道是可信的,拓展开来,在拜占庭容错系统,普 遍采用的假设包括如下条件

◼拜占庭节点的行为可以是任意的,拜占庭节点之间可以共谋

◼节点之间的错误是不相关的

◼节点之间通过异步网络连接,网络中的消息可能丢失、乱序并延时到达,但大 部分协议假设消息在有限的时间里能传达到目的地

◼节点之间传递的信息,第三方可以嗅探到,但是不能篡改、伪造信息的内容和 破坏信息的完整性

PBFT 的一致性协议

⚫一致性协议至少包含请求(request)、序号分配(pre-prepare)、响应(reply)三 个阶段。根据协议设计的不同,可能包含相互交互(prepare) 、序号确认(commit) 等阶段

⚫PBFT 系统通常假设故障节点个数为 m 个,而整个服务节点数为 3m+1 个

PBFT——拜占庭算法 go简单实现_第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")
	}
}

 

你可能感兴趣的:(go)