DPOS股份授权证明,即 Delegated Proof of Stake 译为股份授权证明,最早于 2013 年由 Bitshares 提出,目的为解决 PoW 和 PoS 机制的不足,DPoS 机制的加密货币,每个节点都可以操作区块,并按照个人的持股比例获得“利 息”,DPoS 是由被社区选举的可信帐户(受托人,得票数排行前 101 位)来创建区块, 为了成为正式受托人,用户要去社区拉票,获得足够多用户的信任,用户根据自己 持有的加密货币数量占总量的百分比来投票,DPoS 机制类似于股份制公司,普通股民进不了董事会,要投票选举代表(受托人) 代他们做决策,这 101 个受托人可以理解为 101 个矿池,而这 101 个矿池彼此的权利是完全相等的,那些握着加密货币的用户可以随时通过投票更换这些代表(矿池),只要他们提供 的算力不稳定,计算机宕机、或者试图利用手中的权力作恶,他们将会立刻被愤怒 的选民门踢出整个系统,而后备代表可以随时顶上去。
因为大多数区块链底层代码都比较多,为了实现简单的dpos,本文只是为了演示dpos,若有更深层次的研究可以参考EOS源码中的BFT-DPOS
BFT即拜占庭容错算法,EOS引入这个算法,主要是赋予出块节点更大的权力,加快出块速度,解决节点出的块都被漏掉的问题。EOS共识算法的升级,势必需要超级节点们更新代码,使用新的程序,然后在当前链上继续运行。但如果超过1/3的节点拒绝更新代码,可能会出现硬分叉问题。所以如何很好地做好过渡是EOS最大难题。
EOS共识机制升级后是如何工作的?
EOS使用BFT+DPOS共识机制后,不再按照出块顺序让超级节点一个个验证区块内容,而是让出块节点成为主节点。出块后,同时向其他20个超级节点进行广播该区块,并获得他们的验证。如果超过2/3的节点验证通过后,则该区块将成为不可逆转区块。
BFT可以使EOS出块速度显著增加。目前使用BFT+DPOS共识机制的EOS,可以实现0.5秒的出块速度,1秒实现区块的不可逆转。为避免因出块速度过快而漏块,EOS的超级节点按照其他的地理位置依次轮流成为主节点,尽可能减少超级节点的网络延迟。比如超级节点有中国、美国、加拿大、日本,那么成为主节点的顺序是中国>日本>美国>加拿大或者反过来,总之保证相邻最近的超级节点要依次交接主节点角色。
同时规定每个主节点连续生产6个区块,至少保证6个区块的前几个能确认完成,不存在整个超级节点被跳过的现象。可以看出每轮记账节点的出块总时间还是3秒钟,在这3秒里,因为他对他自己出的块是信任的,所以可以持续出块。一边出块一边广播,3秒之内率先广播的区块肯定能够得到确认,在网络通畅的情况下,6个区块都会可能得到确认。
EOS共识处理分叉问题非常简单,和比特币一样,节点只会认可最长的链作为合法链。假如某个节点开始作恶,自己出块并生成自己的链,也就是每次轮到它就产生6个块。但是超级节点总共21个,每轮产生理论126个块。根据选择最长链作为主链原则,肯定作恶的链得不到认可。所以EOS不会发生分叉问题。
下面我们golang实现dpos
源码
package main
import (
"crypto/sha256"
"encoding/hex"
"log"
"math/rand"
"sort"
"time"
)
//定义区块结构体
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
Delegate string
}
// 创建区块函数
func generateBlock(oldBlock Block, _BMP int, address string) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = _BMP
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = createBlockHash(newBlock)
newBlock.Delegate = address
return newBlock, nil
}
//生成区块hash
func createBlockHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
sha3 := sha256.New()
sha3.Write([]byte(record))
hash := sha3.Sum(nil)
return hex.EncodeToString(hash)
}
// 简单的检验区块函数
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}
if newBlock.PrevHash != oldBlock.Hash {
return false
}
return true
}
// 区块集合
var blockChain []Block
// dpos里的超级节点结构体(受托人)
type Trustee struct {
name string
votes int
}
type trusteeList []Trustee
// 下面的三个函数是为了排序使用,大家可以查下go的排序还是很强大的
func (_trusteeList trusteeList) Len() int {
return len(_trusteeList)
}
func (_trusteeList trusteeList) Swap(i, j int) {
_trusteeList[i], _trusteeList[j] = _trusteeList[j], _trusteeList[i]
}
func (_trusteeList trusteeList) Less(i, j int) bool {
return _trusteeList[j].votes < _trusteeList[i].votes
}
// 选举获得投票数最高的前5节点作为超级节点,并打乱其顺序
func selectTrustee() []Trustee {
_trusteeList := []Trustee{
{"node1", rand.Intn(100)},
{"node2", rand.Intn(100)},
{"node3", rand.Intn(100)},
{"node4", rand.Intn(100)},
{"node5", rand.Intn(100)},
{"node6", rand.Intn(100)},
{"node7", rand.Intn(100)},
{"node8", rand.Intn(100)},
{"node9", rand.Intn(100)},
{"node10", rand.Intn(100)},
{"node11", rand.Intn(100)},
{"node12", rand.Intn(100)},
}
sort.Sort(trusteeList(_trusteeList))
result := _trusteeList[:5]
_trusteeList = result[1:]
_trusteeList = append(_trusteeList, result[0])
log.Println("当前超级节点列表是", _trusteeList)
return _trusteeList
}
func main() {
t := time.Now()
// init gensis block(创建创世块,真正的可不是这么简单的,这里只做流程实现)
genesisBlock := Block{0, t.String(), 0, createBlockHash(Block{}), "", ""}
blockChain = append(blockChain, genesisBlock)
// // 这里只是完成了一次dpos的写区块操作,eos真正的是每个节点实现6个区块,并且所有超级节点(21个)轮完,之后再做选举
var trustee Trustee
for _, trustee = range selectTrustee() {
_BPM := rand.Intn(100)
blockHeight := len(blockChain)
oldBlock := blockChain[blockHeight-1]
newBlock, err := generateBlock(oldBlock, _BPM, trustee.name)
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, oldBlock) {
blockChain = append(blockChain, newBlock)
log.Println("当前操作区块的节点是: ", trustee.name)
log.Println("当前区块数量是: ", len(blockChain)-1)
log.Println("当前区块信息: ", blockChain[len(blockChain)-1])
}
}
}
运行结果如下
demo$ go run dpos.go
2019/05/13 17:55:47 当前超级节点列表是 [{node2 87} {node1 81} {node5 81} {node4 59} {node11 94}]
2019/05/13 17:55:47 当前操作区块的节点是: node2
2019/05/13 17:55:47 当前区块数量是: 1
2019/05/13 17:55:47 当前区块信息: {1 2019-05-13 17:55:47.03021 +0800 CST m=+0.000460350 62 93749cd4d7c07735579252537a9362e1a6daa5495127b386963083e8db7ff92c 96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7 node2}
2019/05/13 17:55:47 当前操作区块的节点是: node1
2019/05/13 17:55:47 当前区块数量是: 2
2019/05/13 17:55:47 当前区块信息: {2 2019-05-13 17:55:47.030269 +0800 CST m=+0.000519152 89 d7a2d3eb17f5a9a85863dc2235c7dc15a5dfd38be31d44452f8bfeb1b017cd9e 93749cd4d7c07735579252537a9362e1a6daa5495127b386963083e8db7ff92c node1}
2019/05/13 17:55:47 当前操作区块的节点是: node5
2019/05/13 17:55:47 当前区块数量是: 3
2019/05/13 17:55:47 当前区块信息: {3 2019-05-13 17:55:47.030303 +0800 CST m=+0.000553346 28 c361e0820ce15721adb0c6bdff85ed3420190710a317d26c024de166cbe5be32 d7a2d3eb17f5a9a85863dc2235c7dc15a5dfd38be31d44452f8bfeb1b017cd9e node5}
2019/05/13 17:55:47 当前操作区块的节点是: node4
2019/05/13 17:55:47 当前区块数量是: 4
2019/05/13 17:55:47 当前区块信息: {4 2019-05-13 17:55:47.030326 +0800 CST m=+0.000576225 74 560ed09b8a9a2886eea641dd349141fcdc18ec50e59cbc736b302bfff7037cc2 c361e0820ce15721adb0c6bdff85ed3420190710a317d26c024de166cbe5be32 node4}
2019/05/13 17:55:47 当前操作区块的节点是: node11
2019/05/13 17:55:47 当前区块数量是: 5
2019/05/13 17:55:47 当前区块信息: {5 2019-05-13 17:55:47.030356 +0800 CST m=+0.000605936 11 949b1f79d5b13ca810ead7f720469ffcc90ed9f29cc1b7116f1cfc3633e64896 560ed09b8a9a2886eea641dd349141fcdc18ec50e59cbc736b302bfff7037cc2 node11}
实现投票功能
代码如下:
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"time"
)
//实现投票的功能
//定义全节点
type Node struct {
//节点名称
Name string
//被选举的票数
Votes int
}
//区块
type Block struct {
Index int
Timestamp string
Prehash string
Hash string
Data []byte
//代理人
delegate *Node
}
func firstBlock() Block {
gene := Block{0, time.Now().String(),
"", "", []byte("first block"), nil}
gene.Hash = string(blockHash(gene))
return gene
}
//计算哈希
func blockHash(block Block) []byte {
hash := strconv.Itoa(block.Index) + block.Timestamp +
block.Prehash + hex.EncodeToString(block.Data)
h := sha256.New()
h.Write([]byte(hash))
hashed := h.Sum(nil)
return hashed
}
//生成新的区块
func (node *Node) GenerateNewBlock(lastBlock Block, data []byte) Block {
var newBlock = Block{lastBlock.Index + 1,
time.Now().String(), lastBlock.Hash, "", data, nil}
newBlock.Hash = hex.EncodeToString(blockHash(newBlock))
newBlock.delegate = node
return newBlock
}
//创建10个节点
var NodeAddr = make([]Node, 10)
//创建节点
func CreateNode() {
for i := 0; i < 10; i++ {
name := fmt.Sprintf("节点 %d 票数", i)
//初始化时票数为0
NodeAddr[i] = Node{name, 0}
}
}
//简单模拟投票
func Vote() {
for i := 0; i < 10; i++ {
rand.Seed(time.Now().UnixNano())
time.Sleep(100000)
vote := rand.Intn(10000)
//为10个节点投票
//每个节点的票数,就是随机出来的值,0~9999
NodeAddr[i].Votes = vote
fmt.Printf("节点 [%d] 票数 [%d]\n", i, vote)
}
}
//一共10个节点,选出票数最多的前三名
func SortNodes() []Node {
//10个节点
n := NodeAddr
//外层遍历节点个数
for i := 0; i < len(n); i++ {
for j := 0; j < len(n)-1; j++ {
//按票数排序
if n[j].Votes < n[j+1].Votes {
n[j], n[j+1] = n[j+1], n[j]
}
}
}
//返回三个票数多的节点
return n[:3]
}
func main() {
//初始化10个全节点
CreateNode()
fmt.Printf("创建的节点列表: \n")
fmt.Println(NodeAddr)
fmt.Print("节点票数: \n")
//投票
Vote()
//选出前三名
nodes := SortNodes()
fmt.Print("获胜者: \n")
fmt.Println(nodes)
//创世区块
first := firstBlock()
lastBlock := first
fmt.Print("开始生成区块: \n")
for i := 0; i < len(nodes); i++ {
fmt.Printf("[%s %d] 生成新的区块\n", nodes[i].Name, nodes[i].Votes)
lastBlock = nodes[i].GenerateNewBlock(lastBlock, []byte(fmt.Sprintf("new Block %d", i)))
}
}
代码运行结果:
demo$ go run BDpos.go
创建的节点列表:
[{节点 0 票数 0} {节点 1 票数 0} {节点 2 票数 0} {节点 3 票数 0} {节点 4 票数 0} {节点 5 票数 0} {节点 6 票数 0} {节点 7 票数 0} {节点 8 票数 0} {节点 9 票数 0}]
节点票数:
节点 [0] 票数 [3452]
节点 [1] 票数 [8096]
节点 [2] 票数 [3831]
节点 [3] 票数 [9406]
节点 [4] 票数 [7217]
节点 [5] 票数 [3851]
节点 [6] 票数 [3023]
节点 [7] 票数 [6202]
节点 [8] 票数 [3651]
节点 [9] 票数 [2270]
获胜者:
[{节点 3 票数 9406} {节点 1 票数 8096} {节点 4 票数 7217}]
开始生成区块:
[节点 3 票数 9406] 生成新的区块
[节点 1 票数 8096] 生成新的区块
[节点 4 票数 7217] 生成新的区块
Dpos的优缺点:
(1)DPOS优点
- 能耗低
- 更去中心化
- 更快的确认速度
(2)DPOS缺点
- 投票的积极性不高
- 社区选举有可能存在网络安全问题
真正区块交易信息、校验、选举不是呢么简单,但是大概的流程是这样的,希望这篇文章能帮助到你 _ ~
本文参考:
[x] EOS最新版共识机制BFT-DPOS
[x] go 实现DPOS 机制
[x] DPoS——股份授权证明 go简单实现
有疑问加站长微信联系(非本文作者)