使用Go语言从零编写PoS区块链

导语:本文作者在前几篇文章中展示了一个简单的区块链,包括生成块,验证数据,广播通信等。本文继续前文,介绍了PoS算法的基本原理,并且用golang实现了简单的PoS区块链。


译者: ChainGod(孙飞)

原文链接: http://chaingod.io/article/16


在本系列前三篇文章中[1][2][3],我们向大家展示了如何通过精炼的Go代码实现一个简单的区块链。包括生成块,验证块数据,广播通信等等,这一篇让我们聚焦在如何实现 PoW算法。


PoS简介

在上一篇文章[3]中,我们讨论了工作量证明(Proof of Work),并向您展示了如何编写自己的工作量证明区块链。当前最流行的两个区块链平台,比特币和以太坊都是基于工作量证明的。

但是工作证明的缺点是什么呢?其中一个主要的问题是电力能源的消耗。为了挖掘更多的比特币,就需要建立更多的挖矿硬件池,现在在世界各地,挖矿池都在不断建立中,而且呈现出规模越来越大的趋势。例如以下这张照片(仅仅是矿池的一角):640?wx_fmt=png

挖矿工作需要耗费大量的电力,仅比特币开采耗费的能源就超过了159个国家的电力能源消耗总和!!这种能源消耗是非常非常不合理的,而且,从技术的角度来看,工作量证明还有其他不足之处:随着越来越多的人参与到挖矿工作中,共识算法的难度就需要提高,难度的提高意味着需要更多、更长时间的挖矿,也意味着区块和交易需要更长的时间才能得到处理,因此能源的消耗就会越发的高。总之,工作量证明的方式就是一场竞赛,你需要更多的计算能力才能有更大的概率赢得比赛。

有很多区块链学者都试图找到工作量证明的替代品,到目前为止最有希望的就是PoS(权益证明,Proof of Stake)。目前在生产环境,已经有数个区块链平台使用了PoS,例如Nxt 和Neo。以太坊在不远的未来也很可能会使用PoS——他们的Casper项目已经在测试网络上运行和测试了。

那么,到底什么才是股权证明PoS呢?

在PoW中,节点之间通过hash的计算力来竞赛以获取下一个区块的记账权,而在PoS中,块是已经铸造好的(这里没有“挖矿”的概念,所以我们不用这个词来证明股份),铸造的过程是基于每个节点(Node)愿意作为抵押的令牌(Token)数量。

这些参与抵押的节点被称为验证者(Validator),**注意在本文后续内容中,验证者和节点的概念是等同的!**令牌的含义对于不同的区块链平台是不同的,例如,在以太坊中,每个验证者都将Ether作为抵押品。

如果验证者愿意提供更多的令牌作为抵押品,他们就有更大的机会记账下一个区块并获得奖励。你可以把奖励的区块看作是存款利息,你在银行存的钱越多,你每月的利息就会越高。

因此,这种共识机制被称为股权证明PoS。

PoS的缺陷是什么?


您可能已经猜到,一个拥有大量令牌的验证者会在创建新块时根据持有的令牌数量获得更高的概率。然而,这与我们在工作量证明中看到的并没有什么不同:比特币矿场变得越来越强大,普通人在自己的电脑上开采多年也未必能获得一个区块。

因此,许多人认为,使用了PoS后,区块的分配将更加民主化,因为任何人都可以在自己的笔记本上参与,而不需要建立一个巨大的采矿平台,他们不需要昂贵的硬件,只需要一定的筹码,就算筹码不多,也有一定概率能获得区块的记账权,希望总是有的,你说呢?

从技术和经济的角度来看,还有其他不利因素。我们不会一一介绍,但这里有一个很好的介绍。在实际应用中,PoS和PoW都有自己的优点和缺点,因此以太坊的Casper具有两者混合的特征。

编写PoS代码


我们建议在继续之前看一下200行Go代码编写区块链Part2[1],因为在接下来的文章中,一些基础知识不再会介绍,因此这篇文章能帮助你回顾一下。

注意


我们将实现PoS的核心概念,然后因为文章长度有限,因此一些不必要的代码奖省去!

  • P2P网络的实现。文中的网络是模拟的,区块链状态只在其中一个中心化节点持有,而不是每个节点,同时状态通过该持有节点广播到其它节点

  • 钱包和余额变动。本文没有实现一个钱包,持有的令牌数量是通过stdin(标准输入)输入的,你可以输入你想要的任何数量。一个完整的实现会为每个节点分配一个hash地址,并在节点中跟踪余额的变动


架构图

640?wx_fmt=png

  • 我们将有一个中心化的TCP服务节点,其他节点可以连接该服务器

  • 最新的区块链状态将定期广播到每个节点

  • 每个节点都能提议建立新的区块

  • 基于每个节点的令牌数量,其中一个节点将随机地(以令牌数作为加权值)作为获胜者,并且将该区块添加到区块链中


设置和导入


在开始写代码之前,我们需要一个环境变量来设置TCP服务器的端口,首先在工作文件夹中创建.env文件,写入一行配置:

640?wx_fmt=png

  • spew 可以把我们的区块链用漂亮的格式打印到终端terminal中

  • godotenv 允许我们从之前创建的.env文件读取配置


快速脉搏检查


如果你读过我们的其他教程,就会知道我们是一家医疗保健公司,目前要去收集人体脉搏信息,同时添加到我们的区块上。把两个手指放在你的手腕上,数一下你一分钟能感觉到多少次脉搏,这将是您的BPM整数,我们将在接下来的文章中使用。

全局变量


现在,让我们声明我们需要的所有全局变量(main.go中)。

640?wx_fmt=png

  • Block是每个区块的内容

  • Blockchain是我们的官方区块链,它只是一串经过验证的区块集合。每个区块中的PrevHash与前面块的Hash相比较,以确保我们的链是正确的

  • tempBlocks是临时存储单元,在区块被选出来并添加到BlockChain之前,临时存储在这里

  • candidateBlocks是Block的通道,任何一个节点在提出一个新块时都将它发送到这个通道

  • announcements也是一个通道,我们的主Go TCP服务器将向所有节点广播最新的区块链

  • mutex是一个标准变量,允许我们控制读/写和防止数据竞争

  • validators是节点的存储map,同时也会保存每个节点持有的令牌数


基本的区块链函数


在继续PoS算法之前,我们先来实现标准的区块链函数。如果你之前看过200行Go代码编写区块链,那接下来应该更加熟悉。

640?wx_fmt=png

这里先从hash函数开始,calculateHash函数会接受一个string,并且返回一个SHA256 hash。calculateBlockHash是对一个block进行hash,将一个block的所有字段连接到一起后,再进行hash。

main.go

640?wx_fmt=png

generateBlock是用来创建新块的。每个新块都有的一个重要字段是它的hash签名(通过calculateBlockHash计算的)和上一个连接块的PrevHash(因此我们可以保持链的完整性)。我们还添加了一个Validator字段,这样我们就知道了该构建块的获胜节点。

main.go

640?wx_fmt=png

isBlockValid会验证Block的当前hash和PrevHash,来确保我们的区块链不会被污染。

节点(验证者)


当一个验证者连接到我们的TCP服务,我们需要提供一些函数达到以下目标:

  • 输入令牌的余额(之前提到过,我们不做钱包等逻辑)

  • 接收区块链的最新广播

  • 接收验证者赢得区块的广播信息

  • 将自身节点添加到全局的验证者列表中(validators)

  • 输入Block的BPM数据- BPM是每个验证者的人体脉搏值

  • 提议创建一个新的区块

这些目标,我们用handleConn函数来实现

main.go

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

第一个Go协程接收并打印出来自TCP服务器的任何通知,这些通知包含了获胜验证者的通知。

io.WriteString(conn, “Enter token balance:”)允许验证者输入他持有的令牌数量,然后,该验证者被分配一个SHA256地址,随后该验证者地址和验证者的令牌数被添加到验证者列表validators中。

接着我们输入BPM,验证者的脉搏值,并创建一个单独的Go协程来处理这块儿逻辑,下面这一行代码很重要:

如果验证者试图提议一个被污染(例如伪造)的block,例如包含一个不是整数的BPM,那么程序会抛出一个错误,我们会立即从我们的验证器列表validators中删除该验证者,他们将不再有资格参与到新块的铸造过程同时丢失相应的抵押令牌。

正式因为这种抵押令牌的机制,使得PoS协议是一种更加可靠的机制。如果一个人试图伪造和破坏,那么他将被抓住,并且失去所有抵押和未来的权益,因此对于恶意者来说,是非常大的威慑。

接着,我们用generateBlock函数创建一个新的block,然后将其发送到candidateBlocks通道进行进一步处理。将Block发送到通道使用的语法:

上面代码中最后一段的循环会周期性的打印出最新的区块链,这样每个验证者都能获知最新的状态

选择获胜者


这里是PoS的主题逻辑。我们需要编写代码以实现获胜验证者的选择;他们所持有的令牌数量越高,他们就越有可能被选为胜利者。

为了简化代码,我们只会让提出新块儿的验证者参与竞争。在传统的PoS,一个验证者即使没有提出一个新的区块,也可以被选为胜利者。切记,PoS不是一种确定的定义(算法),而是一种概念,因此对于不同的平台来说,可以有不同的PoS实现。

下面来看看pickWinner函数:

640?wx_fmt=png

640?wx_fmt=png

每隔30秒,我们选出一个胜利者,这样对于每个验证者来说,都有时间提议新的区块,参与到竞争中来。接着创建一个lotteryPool,它会持有所有验证者的地址,这些验证者都有机会成为一个胜利者。然后,对于提议块的暂存区域,我们会通过if len(temp) > 0来判断是否已经有了被提议的区块。

在OUTER FOR循环中,要检查暂存区域是否和lotteryPool中存在同样的验证者,如果存在,则跳过。

在以k, ok := setValidators[block.Validator]开始的代码块中,我们确保了从temp中取出来的验证者都是合法的,即这些验证者在验证者列表validators已存在。若合法,则把该验证者加入到lotteryPool中。

那么我们怎么根据这些验证者持有的令牌数来给予他们合适的随机权重呢?

首先,用验证者的令牌填充lotteryPool数组,例如一个验证者有100个令牌,那么在lotteryPool中就将有100个元素填充;如果有1个令牌,那么将仅填充1个元素。

然后,从lotteryPool中随机选择一个元素,元素所属的验证者即是胜利者,把胜利验证者的地址赋值给lotteryWinner。这里能够看出来,如果验证者持有的令牌越多,那么他在数组中的元素也越多,他获胜的概率就越大;同时,持有令牌很少的验证者,也是有概率获胜的。

接着我们把获胜者的区块添加到整条区块链上,然后通知所有节点关于胜利者的消息:announcements <- “\nwinning validator: “ + lotteryWinner + “\n”。

最后,清空tempBlocks,以便下次提议的进行。

以上便是PoS一致性算法的核心内容,该算法简单、明了、公正,所以很酷!

收尾


下面我们把之前的内容通过代码都串联起来

640?wx_fmt=png

这里从.env文件开始,然后创建一个创世区块genesisBlock,形成了区块链。接着启动了Tcp服务,等待所有验证者的连接。

启动了一个Go协程从candidateBlocks通道中获取提议的区块,然后填充到临时缓冲区tempBlocks中,最后启动了另外一个Go协程来完成pickWinner函数。

最后面的for循环,用来接收验证者节点的连接。

这里是所有的源代码[2]:

结果


下面来运行程序,打开一个终端窗口,通过go run main.go来启动整个TCP程序,如我们所料,首先创建了创始区块genesisBlock。640?wx_fmt=png

接着,我们启动并连接一个验证者。打开一个新的终端窗口,通过linux命令nc localhost 9000来连接到之前的TCP服务。然后在命令提示符后输入一个持有的令牌数额,最后再输入一个验证者的脉搏速率BPM。

640?wx_fmt=png

然后观察第一个窗口(主程序),可以看到验证者被分配了地址,而且每次有新的验证者加入时,都会打印所有的验证者列表640?wx_fmt=png

稍等片刻,检查下你的新窗口(验证者),可以看到正在发生的事:我们的程序在花费时间选择胜利者,然后Boom一声,一个胜利者就诞生了!

640?wx_fmt=png

再稍等一下,boom! 我们看到新的区块链被广播给所有的验证者窗口,包含了胜利者的区块和他的BPM信息。很酷吧!640?wx_fmt=png

下一步做什么

你应该为能通过本教程感到骄傲。大多数区块链的发烧友和许多程序员都听说过PoS的证明,但他们很多都无法解释它到底是什么。你已经做得更深入了,而且实际上已经从头开始实现了一遍,你离成为下一代区块链技术的专家又近了一步!

因为这是一个教程,我们可以做更多的事情来让它成为区块链,例如:

  • 阅读我们的PoW,然后结合PoS,看看你是否可以创建一个混合区块链

  • 添加时间机制,验证者根据时间块来获得提议新区快的概率。我们这个版本的代码让验证者可以在任何时候提议新的区块。

  • 添加完整的点对点的能力。这基本上意味着每个验证者将运行自己的TCP服务器,并连接到其他的验证者节点。这里需要添加逻辑,这样每个节点都可以找到彼此,这里[3]有更多的内容。

或者你可以学习一下我们其它的教程。

参考链接:

[1] 只用200行Go代码写一个自己的区块链!

[2] 200行Go代码实现自己的区块链——区块生成与网络通信

[3] 200行Go代码实现区块链 —— 挖矿算法

相关阅读:


只用200行Go代码写一个自己的区块链!

200行Go代码实现自己的区块链——区块生成与网络通信

200行Go代码实现区块链 —— 挖矿算法

区块链及比特币入门指南


特别推荐:

比特币、以太坊、ERC20、PoW、PoS、智能合约、闪电网络……

想深入了解及讨论这些话题?高可用架构在知识星球(小密圈)创建了区块链学习小组,共同学习区块链包括数字货币前沿技术,欢迎点击链接加入。

区块链学习小组

本文作者 Coral Health,由ChainGod(孙飞)翻译。转载译文请注明出处,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。


高可用架构

改变互联网的构建方式

640?wx_fmt=jpeg

长按二维码 关注「高可用架构」公众号


什么是共识算法背景分布式系统集群设计中面临着一个不可回避的问题,一致性问题对于系统中的多个服务节点,给定一系列操作,如何试图使全局对局部处理结果达成某种程度的一致?这个一致性问题大致有如下的场景:节点之间通讯不可靠的,延迟和阻塞节点的处理可能是错误的,甚至节点自身随时可能宕机节点作恶举例说明,就比如有两家电影院同时售卖总量一定的电影票,在这样的场景下,要如何设计方式来保证两家电影院协调同步不出现超卖或者错卖的问题呢?共识算法,就是解决对某一提案(目标,投票等各种协作工作),大家达成一致意见的过程比如上述的买票问题,就可以有如下的设计:1.每次卖票打电话给其他电影院,确认当前票数2.协商售卖时间,比如一三五A卖,二四六B卖3.成立个第三方存票机构,它统一发票通过以上的设计,可以看出一个很重要的解决一致性算法的解决思路,即:将可能引发不一致的并行操作进行串行化,就是现在计算机系统里处理分布式一致性问题基础思路和唯一秘诀著名的共识设计理论FLP 不可能性原理  共识算法的理论下限提出该定理的论文是由 Fischer, Lynch 和 Patterson 三位作者于 1985 年发表,该论文后来获得了 Dijkstra(就是发明最短路径算法的那位)奖。FLP 原理认为对于允许节点失效情况下,纯粹异步系统无法确保一致性在有限时间内完成。三人三房间投票例子三个人在不同房间,进行投票(投票结果是 0 或者 1)。三个人彼此可以通过电话进行沟通,但经常会有人时不时地睡着。比如某个时候,A 投票 0,B 投票 1,C 收到了两人的投票,然后 C 睡着了。A 和 B 则永远无法在有限时间内获知最终的结果。如果可以重新投票,则类似情形每次在取得结果前发生带入到计算机领域就是说,即便在网络通信可靠情况下,一个可扩展的分布式系统的共识问题的下限是无解。即可靠性的下限是0%CAP  分布式系统领域的重要原理CAP 原理最早由 Eric Brewer 在 2000 年,ACM 组织的一个研讨会上提出猜想,后来 Lynch 等人进行了证明• C(一致性):所有的节点上的数据时刻保持同步,即数据一致• A(可用性):每个请求都能在一定时间内接受到一个响应,即低延迟• P(分区容错):当系统发生分区时仍然可以运行的定理:任何分布式系统只可同时满足二点,没法三者兼顾。即数据一致,响应及时,可分区执行不可能同时满足。举个例子:一个分布式网路上,某一个节点有一组依赖数据A,当网络无延迟,无阻塞时,依赖于X的操作可正常进行。但网络无延迟阻塞在现实世界中是没法100%保证的,那么当网络异常时,必然会产生分布式系统的分区和孤岛,那当一个执行操作在A分区之外时,如果要保证P,即当系统发生分区时仍可运行,就需要在分布式系统中多个节点有X的备份数据,以应对分区情况。则这时候就需要在C,A之间做出选择。假如选择C,即要保证数据在分布式网络中的一致性,那么就需要在X每次改动时,需要将全网节点的X数据同步刷新成最新的状态,那么在等待数据刷新完成之前,分布式系统是不可响应X的依赖操作的,即A的功能缺失假如选择A,即要突出低延迟的实时响应。那么在响应的时候,可能全节点的X数据并没有同步到最新的状态,则会导致C的缺失。上面看上去有些绕,那么你只要记住这句话,CAP原理在分布式网络系统的应用讨论,其实就是讨论在允许网络发生故障的系统中,该选择一致性还是可靠性?如果系统重视一致性,那么可以基于ACID原则做系统设计即 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)。ACID 原则描述了对分布式数据库的一致性需求,同时付出了可用性的代价。• Atomicity:每次操作是原子的,要么成功,要么不执行;• Consistency:数据库的状态是一致的,无中间状态;• Isolation:各种操作彼此互相不影响;• Durability:状态的改变是持久的,不会失效相应的有一个BASE原则,(Basic Availiability,Soft state,Eventually Consistency)则强调了可用性。经典的共识算法设计业内,针对节点异常的情况,会有两种分类1.故障的,不响应的节点,成为非拜占庭错误2.恶意响应的节点,称为非拜占庭错误Paxos 最早的共识算法  非拜占庭算法的代表Paxos有三种角色:• proposer:提出一个提案,等待大家批准为结案。客户端担任该角色;• acceptor:负责对提案进行投票。往往是服务端担任该角色;• learner:被告知结案结果,并与之统一,不参与投票过程。即普通节点系统运行由proposer驱动,当合法提案在一定时间内收到1/2以上投票后达成共识。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值