共识算法2--股权权益证明简介及算法实现
区块链中共识机制种类繁多,其中比特币的爆热让PoW进入了众多区块链研究人员的视线,ETH的流行让PoS出现在众人面前。PoS作为大名鼎鼎的ETH的共识机制,其减少了PoW算法对资源、能源的浪费,在区块链总其最基本的概念就是选择生成新的区块的机会应和股权的大小成比例,笔者此处简要介绍一下PoS算法的原理和一个基于PoS算法的简单挖矿算法。
1、股权权益证明介绍
PoS(Proof of Stack)即:股权权益证明,该算法主要是针对PoW算法的缺点进行改进的。PoS由QuantumMechanic 2011年在bitcointalk首先提出, 后经Peercoin和NXT以不同思路实现。PoS不像PoW那样,无论什么人,买了矿机,下载了软件,就可
以参与。PoS要求参与者预先放一些代币(利益) 在区块链上,类似将财产存储在银行,这种模式会根据你持有数字货币的量和时间, 分配给你相应的利息。用户只有将一些利益放进链里,相当于押金,用户才会更关注,做出的决定才会更理性。同时也可以引入奖惩机制,使节点的运行更可控,同时更好地防止攻击。[1]
具体到区块链中,可以通过币龄来理解PoS,PoS指出了一个全新的概念-币龄,币龄 = 持有的币数 * 持有币的天数,例如钱包里有90个点点币,持有了10天,则币龄=900。决定下一个区块是由谁出,是看当前时刻谁的币龄大。出块后币龄归零,重新计时持币时间。币龄是对应账户恒定数量持币数的值,如果账户的持币数量发生变化,那么币龄也会归零,重新计时。通过币龄来决定出块,不再需要比拼算力,该方式比较环保,相对PoW拼算力它可以节省很多资源。[2]
2、PoS算法实现
简单来说,在PoS共识算法中,任何持有币的用户都可能当做矿工,但是概率是不一样的,概率与当前节点持有币龄和币的数量有关。一个用户持有的 币*持币时间 越大,其挖矿的概率越大。
具体实施过程中,可初始指定几个用户,每个用户分配一定数量的代币和持币时间,如以下代码中,5个用户的 币数量*持币时间 分别为1、2、3、4、5,在该种状态下各用户理论上获取新区块的概率分别为1/15、2/15、3/15、4/15、5/15。在实际中,存在币龄更新和区块奖励问题,即:若某用户没有挖到矿,那么其币龄会增加,其下一次将会有更多机会挖到矿;挖到区块的用户持币时间变为0,但是会获得一些系统的奖励代币。
以下代码模拟了一个简单的PoS算法,相对初始状态,挖到矿的用户奖励一个代币。
PoS算法单机版本源码:
// c3_PoS 实现PoS挖矿原理
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"time"
)
type Block struct {
Data int //交易记录、数据
Prehash string
Hash string
Timestamp string
Index int
//记录挖矿那个节点的地址
Validator *Node //
}
//创建全节点类型,可以理解为持有币的用户类型
type Node struct {
Tokens int //持有币的个数
Days int //持有币的时间
Address string //用户地址
}
//创建5个村民,并且让每个用户持有一定的币
var n = make([]Node, 5)
var addr = make([]*Node, 15) //15由sum(Tokens*Days)产生,用于确定随机区间
//初始化5个用户
func initNode() {
n[0] = Node{Tokens: 1, Days: 1, Address: "001"}
n[1] = Node{Tokens: 2, Days: 1, Address: "002"}
n[2] = Node{Tokens: 3, Days: 1, Address: "003"}
n[3] = Node{Tokens: 4, Days: 1, Address: "004"}
n[4] = Node{Tokens: 5, Days: 1, Address: "005"}
cnt := 0
for i := 0; i < 5; i++ {
for j := 0; j < n[i].Tokens*n[i].Days; j++ {
addr[cnt] = &n[i]
cnt++
}
}
}
//创建一个创世区块
func genesisBlock() Block {
var genesisBlock = Block{Data: 0, Prehash: "", Timestamp: time.Now().String(),
Index: 0, Validator: &n[0]} //默认为第一个用户创建创世块,也可以根据需要设置为随机用户
calculateHash(&genesisBlock)
return genesisBlock
}
//计算区块hash值
func calculateHash(block *Block) []byte {
record := strconv.Itoa(block.Data) + strconv.Itoa(block.Index) + block.Prehash +
block.Timestamp + block.Validator.Address
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
block.Hash = hex.EncodeToString(hashed)
return hashed
}
//挖矿-生成下一个区块,采用PoS算法
func generateNextBlock(oldBlock Block, data int) Block {
//有矿的用户才能挖矿
var newBlock Block
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = time.Now().String()
newBlock.Prehash = oldBlock.Hash
newBlock.Data = data
//通过PoS计算由哪个用户挖矿
//设置随机种子
rand.Seed(time.Now().Unix())
rd := rand.Intn(15) //产生[0,15)随机数,实际中需要根据addr数组长度来动态确定随机数范围
//选出挖矿的矿工
node := addr[rd]
//设置当前区块挖矿地址为验证矿工
newBlock.Validator = node
calculateHash(&newBlock)
//当前node矿工原有的币增加1,实际区块链产品中可按照一定利率增加
node.Tokens = node.Tokens + 1
return newBlock
}
func main() {
fmt.Println("Hello PoS!")
initNode() //创建数组
/*
for i := 0; i < 15; i++ {
fmt.Println(addr[i])
}
*/
//创建区块
var genesisBlock = genesisBlock()
//生成新区块
var newBlock = generateNextBlock(genesisBlock, 1)
fmt.Println("挖出的新区块为:", newBlock)
fmt.Println("挖出的新区块Data为:", newBlock.Data)
fmt.Println("挖出的新区块Hash为:", newBlock.Hash)
fmt.Println("挖矿用户为:", newBlock.Validator.Address)
fmt.Println("该用户币数量:", newBlock.Validator.Tokens)
}
运行结果:
Hello PoS!
挖出的新区块为: {1 eae9398b6ad419ff958b89a8f5a0f14e61383052f34d625ee06b5aa90a77bef9 1048beaa2fcc7aefec0ac4a6bd1c071ad6f32ff848f6c6eab2d5c419f9c5d8c0 2019-01-23 15:10:21.9174002 +0800 CST m=+0.008000001 1 0xc000042180}
挖出的新区块Data为: 1
挖出的新区块Hash为: 1048beaa2fcc7aefec0ac4a6bd1c071ad6f32ff848f6c6eab2d5c419f9c5d8c0
挖矿用户为: 003
该用户币数量: 4
3、说明
本代码当前测试环境为golang1.9.2。
参考文献:
[1] 白话区块链
[2] PoS概述 :https://www.chaindesk.cn/witbook/18/290