使用 go 实现 Proof of Stake 共识机制

什么是 Proof of Stake

在PoW中,节点之间通过hash的计算力来竞赛以获取下一个区块的记账权,而在PoS中,块是已经铸造好的,铸造的过程是基于每个节点(Node)愿意作为抵押的令牌(Token)数量。如果验证者愿意提供更多的令牌作为抵押品,他们就有更大的机会记账下一个区块并获得奖励。

实现 Proof of Stake 主要功能点

  • 我们将有一个中心化的TCP服务节点,其他节点可以连接该服务器
  • 最新的区块链状态将定期广播到每个节点
  • 每个节点都能提议建立新的区块
  • 基于每个节点的令牌数量,其中一个节点将随机地(以令牌数作为加权值)作为获胜者,并且将该区块添加到区块链中

实现 Proof of Stake

设置 TCP 服务器的端口

新建 .env,添加如下内容 PORT=9000

安装依赖软件

$ go get github.com/davecgh/go-spew/spew$ go get github.com/joho/godotenv
  • spew 在控制台中格式化输出相应的结果。

  • godotenv 可以从我们项目的根目录的 .env 文件中读取数据。

引入相应的包

新建 main.go,引入相应的包

package mainimport ("bufio""crypto/sha256""encoding/hex""encoding/json""fmt""io""log""math/rand""net""os""strconv""sync""time""github.com/davecgh/go-spew/spew""github.com/joho/godotenv"
)

全局变量

// Block represents each 'item' in the blockchain
type Block struct {Index     intTimestamp stringBPM       intHash      stringPrevHash  stringValidator string
}// Blockchain is a series of validated Blocks
var Blockchain []Block
var tempBlocks []Block// candidateBlocks handles incoming blocks for validation
var candidateBlocks = make(chan Block)// announcements broadcasts winning validator to all nodes
var announcements = make(chan string)var mutex = &sync.Mutex{}// validators keeps track of open validators and balances
var validators = make(map[string]int)
  • Block 是每个区块的内容
  • Blockchain 是我们的官方区块链,它只是一串经过验证的区块集合。每个区块中的 PrevHash 与前面块的 Hash 相比较,以确保我们的链是正确的。 tempBlocks 是临时存储单元,在区块被选出来并添加到 BlockChain 之前,临时存储在这里
  • candidateBlocksBlock 的通道,任何一个节点在提出一个新块时都将它发送到这个通道
  • announcements 也是一个通道,我们的主Go TCP服务器将向所有节点广播最新的区块链
  • mutex是一个标准变量,允许我们控制读/写和防止数据竞争
  • validators 是节点的存储map,同时也会保存每个节点持有的令牌数

生成区块

func generateBlock(oldBlock Block, BPM int, address string) (Block, error) {var newBlock Blockt := time.Now()newBlock.Index = oldBlock.Index + 1newBlock.Timestamp = t.String()newBlock.BPM = BPMnewBlock.PrevHash = oldBlock.HashnewBlock.Hash = calculateBlockHash(newBlock)newBlock.Validator = addressreturn newBlock, nil
}

generateBlock 是用来创建新块的。
newBlock.PrevHash 存储的是上一个区块的 Hash
newBlock.Hash 是通过 calculateBlockHash(newBlock) 生成的 Hash 。
newBlock.Validator 存储的是获取记账权的节点地址

// SHA256 hasing
// calculateHash is a simple SHA256 hashing function
func calculateHash(s string) string {h := sha256.New()h.Write([]byte(s))hashed := h.Sum(nil)return hex.EncodeToString(hashed)
}//calculateBlockHash returns the hash of all block information
func calculateBlockHash(block Block) string {record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHashreturn calculateHash(record)
}

calculateHash 函数会接受一个 string ,并且返回一个SHA256 hash

calculateBlockHash 是对一个 block 进行 hash,将一个 block 的所有字段连接到一起后,再调用 calculateHash 将字符串转为 SHA256 hash

验证区块

我们通过检查 Index 来确保它们按预期递增。我们也检查以确保我们 PrevHash 的确与 Hash 前一个区块相同。最后,我们希望通过在当前块上 calculateBlockHash 再次运行该函数来检查当前块的散列。

// isBlockValid makes sure block is valid by checking index
// and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {if oldBlock.Index+1 != newBlock.Index {return false}if oldBlock.Hash != newBlock.PrevHash {return false}if calculateBlockHash(newBlock) != newBlock.Hash {return false}return true
}

验证者

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

  • 输入令牌的余额(之前提到过,我们不做钱包等逻辑)
  • 接收区块链的最新广播
  • 接收验证者赢得区块的广播信息
  • 将自身节点添加到全局的验证者列表中(validators)
  • 输入Block的BPM数据- BPM是每个验证者的人体脉搏值
  • 提议创建一个新的区块
func handleConn(conn net.Conn) {defer conn.Close()go func() {for {msg := <-announcementsio.WriteString(conn, msg)}}()// 验证者地址var address string// 验证者输入他所拥有的 tokens,tokens 的值越大,越容易获得新区块的记账权io.WriteString(conn, "Enter token balance:")scanBalance := bufio.NewScanner(conn)for scanBalance.Scan() {// 获取输入的数据,并将输入的值转为 intbalance, err := strconv.Atoi(scanBalance.Text())if err != nil {log.Printf("%v not a number: %v", scanBalance.Text(), err)return}t := time.Now()// 生成验证者的地址address = calculateHash(t.String())// 将验证者的地址和token 存储到 validatorsvalidators[address] = balancefmt.Println(validators)break}io.WriteString(conn, "\nEnter a new BPM:")scanBPM := bufio.NewScanner(conn)go func() {for {// take in BPM from stdin and add it to blockchain after conducting necessary validationfor scanBPM.Scan() {bpm, err := strconv.Atoi(scanBPM.Text())// 如果验证者试图提议一个被污染(例如伪造)的block,例如包含一个不是整数的BPM,那么程序会抛出一个错误,我们会立即从我们的验证器列表validators中删除该验证者,他们将不再有资格参与到新块的铸造过程同时丢失相应的抵押令牌。if err != nil {log.Printf("%v not a number: %v", scanBPM.Text(), err)delete(validators, address)conn.Close()}mutex.Lock()oldLastIndex := Blockchain[len(Blockchain)-1]mutex.Unlock()// 创建新的区块,然后将其发送到 candidateBlocks 通道newBlock, err := generateBlock(oldLastIndex, bpm, address)if err != nil {log.Println(err)continue}if isBlockValid(newBlock, oldLastIndex) {candidateBlocks <- newBlock}io.WriteString(conn, "\nEnter a new BPM:")}}}()// 循环会周期性的打印出最新的区块链信息for {time.Sleep(time.Minute)mutex.Lock()output, err := json.Marshal(Blockchain)mutex.Unlock()if err != nil {log.Fatal(err)}io.WriteString(conn, string(output)+"\n")}}
  • io.WriteString(conn, "Enter token balance:")允许验证者输入他持有的令牌数量,然后,该验证者被分配一个 SHA256地址,随后该验证者地址和验证者的令牌数被添加到验证者列表validators 中。

  • 接着我们输入BPM,验证者的脉搏值,并创建一个单独的Go协程来处理这块儿逻辑

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

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

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

  • 最后会循环打印出最新的区块链,这样每个验证者都能获知最新的状态。

选择获取记账权的节点

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

// pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain
// by random selecting from the pool, weighted by amount of tokens staked
func pickWinner() {time.Sleep(30 * time.Second)mutex.Lock()temp := tempBlocksmutex.Unlock()lotteryPool := []string{}if len(temp) > 0 { // slightly modified traditional proof of stake algorithm // from all validators who submitted a block, weight them by the number of staked tokens // in traditional proof of stake, validators can participate without submitting a block to be forgedOUTER:for _, block := range temp { // if already in lottery pool, skipfor _, node := range lotteryPool {if block.Validator == node {continue OUTER}} // lock list of validators to prevent data racemutex.Lock()setValidators := validatorsmutex.Unlock() // 获取验证者的tokensk, ok := setValidators[block.Validator]if ok { // 向 lotteryPool 追加 k 条数据,k 代表的是当前验证者的tokensfor i := 0; i < k; i++ {lotteryPool = append(lotteryPool, block.Validator)}}} // 通过随机获得获胜节点的地址s := rand.NewSource(time.Now().Unix())r := rand.New(s)lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))] // 把获胜者的区块添加到整条区块链上,然后通知所有节点关于胜利者的消息for _, block := range temp {if block.Validator == lotteryWinner {mutex.Lock()Blockchain = append(Blockchain, block)mutex.Unlock()for _ = range validators {announcements <- "\nwinning validator: " + lotteryWinner + "\n"}break}}}mutex.Lock()tempBlocks = []Block{}mutex.Unlock()
}
  • 每隔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,以便下次提议的进行。

主函数

func main() {err := godotenv.Load()if err != nil {log.Fatal(err)}// 创建初始区块t := time.Now()genesisBlock := Block{}genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""}spew.Dump(genesisBlock)Blockchain = append(Blockchain, genesisBlock)httpPort := os.Getenv("PORT")// 启动 TCP 服务server, err := net.Listen("tcp", ":"+httpPort)if err != nil {log.Fatal(err)}log.Println("HTTP Server Listening on port :", httpPort)defer server.Close()// 启动了一个Go routine 从 candidateBlocks 通道中获取提议的区块,然后填充到临时缓冲区 tempBlocks 中go func() {for candidate := range candidateBlocks {mutex.Lock()tempBlocks = append(tempBlocks, candidate)mutex.Unlock()}}()// 启动了一个Go routine 完成 pickWinner 函数go func() {for {pickWinner()}}()// 接收验证者节点的连接for {conn, err := server.Accept()if err != nil {log.Fatal(err)}go handleConn(conn)}
}
  • godotenv.Load() 会解析 .env 文件并将相应的Key/Value对都放到环境变量中,通过 os.Getenv 获取
  • 然后创建一个创世区块genesisBlock,形成了区块链。
  • 接着启动了Tcp服务,等待所有验证者的连接。
  • 启动了一个Go协程从 candidateBlocks 通道中获取提议的区块,然后填充到临时缓冲区 tempBlocks 中,最后启动了另外一个Go协程来完成 pickWinner 函数。
  • 最后的for循环,用来接收验证者节点的连接。

运行

go run main.go 启动您的Go程序和TCP服务器,并会打印出初始区块的信息。

$ go run main.go
(main.Block) {Index: (int) 0,Timestamp: (string) (len=50) "2018-05-08 16:45:27.14287 +0800 CST m=+0.000956793",BPM: (int) 0,Hash: (string) (len=64) "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7",PrevHash: (string) "",Validator: (string) ""
}
2018/05/08 16:45:27 HTTP Server Listening on port : 9000

打开新的终端,运行 nc localhost 9000
输入 tokens , 然后输入 BPM

可以打开多个终端,输入不同的 tokens ,来检验 PoS 算法

使用 go 实现 Proof of Stake 共识机制相关推荐

  1. 理解不同加密币的要点(一)—— 共识机制

    一.加密货币分类 (一)价值层面 与实体资产绑定的代币 为了因应市场需求而生,与实体资产做挂钩的代币,也就是我们说的稳定币.例如与美元做挂钩的USDT.TUSD.PAX与USDC等. 仰赖网络共识的代 ...

  2. web3:区块链共识机制系列-POS(Proof of Stake)股权证明算法

    web3相关学习一并收录至该博客:web3学习博客目录大全 前情衔接:web3:区块链常见的几大共识机制及优缺点 目录 前言 算法公式与原理 算法公式 运作原理 以Peer Coin为例 缺陷 优点 ...

  3. 使用 go 实现 Delegated Proof of Stake 机制

    使用 go 实现 Delegated Proof of Stake 机制 DPoS的伪代码实现 for round i //分成很多个round,round无限持续dlist_i = get N de ...

  4. 共识机制:区块链技术的根基

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. Chapter-1:什么是共识机制? 技术定义是:共识机制是一个群体决策的流程,群体中的个体会执行和支持对群体其他个人最 ...

  5. EOS共识机制——DPoS代理权益证明

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 区块链共识机制与它的演进,是由于区块链式去中心化而且分布式的系统,必须要有一套放诸四海皆准类似宪法的规则,来规范如何证明 ...

  6. 区块链共识机制及其迭代

    共识,Consensus,故名思义,共同的认识,共识问题研究的就是多个成员如何达成一致,典型的比如投票选举. 区块链是一种去中心化的分布式账本系统,区块链的共识问题实际上来源于分布式系统的一致性问题. ...

  7. 区块链去中心化的生命之源:“DPOS(委托权益证明)共识机制”

    区块链去中心化的生命之源:"DPOS(委托权益证明)共识机制" 原创2018-04-24[水伯]战略忽悠局政委水伯 移动网络时代唯一壁垒就是认知,周二有约给思想洗澡让认知破壁! D ...

  8. 共识机制-权益证明 PoS

    共识机制-权益证明 PoS 什么是权益证明 权益证明( Proof of Stake,PoS )最早在2013年被提出,并在 Peercoin 系统中实现,类似于现实生活中的股东机制,拥有股份越多的人 ...

  9. 从分布式一致性算法到区块链共识机制

    引言 分布式一致性是一个很"古典"的话题,即在分布式系统中,如何保证系统内的各个节点之间数据的一致性或能够就某个提案达成一致.这个问题想必对于很多技术同学而言并不陌生,几乎在所有的 ...

最新文章

  1. Class.forName 和 ClassLoader 到底有啥区别?
  2. python的特点和优点-【Python面试】 Python 的特点和优点是什么?
  3. 《Effective STL》学习笔记(第二部分)
  4. java中6左移2怎么算_(六)Java中的按位运算
  5. Java面向对象抽象类案例分析
  6. 《TCP/IP具体解释》读书笔记(18章)-TCP连接的建立与中止
  7. 思科交换机ping得通 traceroute不通_网络中经常接触的Ping 一次性教你弄懂如何检测三层网络...
  8. LeetCode 894. 所有可能的满二叉树(递归)
  9. 解决Ubuntu系统终端运行python文件报错“ImportError/ModuleNotFoundError:No Module named xx”
  10. 三星Galaxy Note系列年底停产:被折叠屏手机/Galaxy S系列取代
  11. wps怎么下载仿宋gb2312_仿宋gb2312字体官方下载
  12. 软考初级程序员的备考经验分享
  13. 算法(第四版)IDEA终极环境配置
  14. c语言自学基础知识视频,C语言 基础课堂视频教程
  15. 仿直播礼物涂鸦/屏幕礼物涂鸦动画
  16. 无线通信信号的功率表示方法中dBm,dB与w的换算关系
  17. 用c++做文本加密与解密程序(源码)
  18. PageHelper.startPage分页失效问题,亲测已解决
  19. OpenGL学习书籍推荐
  20. Environment 的使用

热门文章

  1. 我的心得:数据中心运维管理(一)
  2. linux搭建宝塔重启mysql_宝塔面板安装 重启等命令linux系统重启
  3. Py之configobj:configobj的简介、安装、使用方法之详细攻略
  4. DL框架之TensorFlow:深度学习框架TensorFlow Core(低级别TensorFlow API)的简介、安装、使用方法之详细攻略
  5. 成功解决IndexError: index 14 is out of bounds for axis 1 with size 14
  6. CV之NS之VGG16:基于预训练模型VGG16训练COCO的train2014数据集实现训练《神奈川冲浪里》风格配置yml文件
  7. 7.1 TensorFlow笔记(基础篇):加载数据之预加载数据与填充数据
  8. Pandas matplotlib 无法显示中文
  9. libpcap抓取数据包
  10. 使用@required注解完成依赖检查