在区块链中使用PoS会导致贫富差距增加的问题,为解决该问题,提出了DPoS机制,DPoS已经在EOS中得到了应用。实际区块链产品中,由x个投票主节点和y个候选节点实现共识,x个投票主节点负责共识和挖矿,当系统发现x中的某个节点有问题(如恶意破坏)则使用y中的候选节点替代x中的节点,从而继续保证系统正常运行。笔者此处简要介绍一下DPoS算法的原理和一个基于DPoS算法的简单挖矿算法。
委托权益证明机制( Delegated Proof of Stake, 以下简称DPoS)机制是PoS算法的改进。DPoS算法中使用见证人机制(witness)解决中心化问题。总共有N个见证人对区块进行签名。DPoS消除了交易需要等待一定数量区块被非信任节点验证的时间消耗。通过减少确认的要求,DPoS算法大大提高了交易的速度。通过信任少量的诚信节点,可以去除区块签名过程中不必要的步骤。DPoS的区块可以比PoW或者PoS容纳更多的交易数量,从而使加密数字货币的交易速度接近像Visa和Mastercard这样的中心化清算系统。[1]
DPoS使得区块链网络保留了一些中心化系统的关键优势, 同时又能保证一定的去中心化。 见证人机制使得交易只用等待少量诚信节点(见证人)的响应,而不必等待其他非信任节点的响应。其具有如下优缺点--优点:能耗更低;更加去中心化;更快的确认速度;缺点:投票的积极性不高;对坏节点的处理存在诸多困难。
DPoS的具体权益委托和选举过程可以通过EOS来体现,EOS系统中通过投票选举21个超级节点(超级节点前期一般会采用各种方式来拉票),然后由这21个节点负责系统的出块和共识。
具体的选举和出块,可以通过如下来理解:一般选举过程是这样的,首先设立一个评审委员会,全球所有节点都可以报名参加,报名的前提是交纳保证金,通过审核的最为满足条件的前N个节点将作为候选节点,进入下一轮,也就是竞选阶段。这些候选节点会将会各种演说游说其他的持币人,让他们给自己投票,这里可能场外会给投票人某些好处。最终投票总数前m名的候选节点成为公链的代理人,负责出块。每次出块时,系统会随机顺序挑选指定某个代理人出块。每次选举出来的代理人都有任期,任期期间如果被监管发现某些作恶行为将会被追责和卸任。[2]
DPoS编程思路大致如下:
1)确认挖矿节点:先生成x个节点(此处暂且设置5个节点),然后通过排序或者某种方式选出y个委托的投票主节点(以下通过选举者的投票数选出3个节点挖矿);
2)生成创世区块
3)被委托的节点轮流挖矿
DPoS单机版本源码:
// c5_DPoS.go
package main
//本代码主要实现DPoS基本原理
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"strconv"
"time"
)
//选举结构体
type Node struct {
Name string //节点名称
Votes int //被选举的票数
}
//创建数组保存节点
var n = make([]*Node, 5)
//创建节点
func createNodes() {
//创建随机种子
rand.Seed(time.Now().Unix())
node1 := Node{"node1", rand.Intn(10)}
node2 := Node{"node2", rand.Intn(10)}
node3 := Node{"node3", rand.Intn(10)}
node4 := Node{"node4", rand.Intn(10)}
node5 := Node{"node5", rand.Intn(10)}
n[0] = &node1
n[1] = &node2
n[2] = &node3
n[3] = &node4
n[4] = &node5
}
//DPoS 中选出票数最高的前n位用户(EoS中21位)
func sortNodes() []*Node {
//对所有选民的票数进行排序,此处选出前3个作为委托节点,实际中可以根据需要确定
for i := 0; i < 4; i++ {
for j := 0; j < 4-i; j++ {
if n[j].Votes < n[j+1].Votes {
tmp := n[j]
n[j] = n[j+1]
n[j+1] = tmp
}
}
}
return n[:3]
}
//定义区块结构体
type Block struct {
Index int
Timestamp string
Prehash string
Hash string
Data int
//增加代理
delegate *Node //用于记录由谁挖出区块
}
var Blockchain []*Block //存放产生的区块
//产生区块
func generateNextBlock(oldBlock Block, data int) *Block {
var newBlock = Block{oldBlock.Index + 1, time.Now().String(),
oldBlock.Hash, "", data, &Node{"", 0}}
calculateHash(&newBlock)
Blockchain = append(Blockchain, &newBlock)
return &newBlock
}
//计算区块hash
func calculateHash(block *Block) {
record := strconv.Itoa(block.Index) + strconv.Itoa(block.Data) + block.Timestamp + block.Prehash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
block.Hash = hex.EncodeToString(hashed)
}
//设置代理人
func (block *Block) setDelegate(node *Node) {
block.delegate = node
}
//生成创世区块
func genesisBlock() *Block {
genesis := Block{0, time.Now().String(), "", "", 0, &Node{"", 0}}
calculateHash(&genesis)
Blockchain = append(Blockchain, &genesis)
return &genesis
}
func main() {
fmt.Println("Hello DPoS!")
//创建所有选民
createNodes()
//确定选出的节点,然后所有挖矿均由这三个节点完成
c := sortNodes()
/*
for _, v := range c {
fmt.Println(v.Votes)
}
*/
//fmt.Println(c[0])
//创建区块
genesisBlock := genesisBlock()
genesisBlock.setDelegate(c[0]) //设置c[0]为创世块矿工
fmt.Println(*genesisBlock) //genesisBlock.delegate.Name
//指定人员轮流挖矿,此处3个节点依次挖矿一次,可根据需要通过for循环持续挖矿
newBlock := generateNextBlock(*genesisBlock, 0)
newBlock.setDelegate(c[0]) //设置c[0]位当前矿工
fmt.Println(*newBlock)
newBlock = generateNextBlock(*newBlock, 1)
newBlock.setDelegate(c[1]) //设置c[1]位当前矿工
fmt.Println(*newBlock)
newBlock = generateNextBlock(*newBlock, 2)
newBlock.setDelegate(c[2]) //设置c[2]位当前矿工
fmt.Println(*newBlock)
//遍历区块数据
for _, blockInfo := range Blockchain {
fmt.Println("\n委托挖矿用户:", blockInfo.delegate.Name)
fmt.Println("区块Hash值:", blockInfo.Hash)
fmt.Println("区块数据:", blockInfo.Data)
}
}
运行结果:
Hello DPoS!
{0 2019-01-06 19:48:08.0980002 +0800 CST m=+0.004000001 81e2657598ab5d785ad5e37ce28bdbeedde993a72959d194d569e9a781371500 0 0xc0000044a0}
{1 2019-01-06 19:48:08.1290002 +0800 CST m=+0.035000001 81e2657598ab5d785ad5e37ce28bdbeedde993a72959d194d569e9a781371500 745e932be093669729f66a3e815421db8b3a76cd3c270bf2a69e19a8d9b8e897 0 0xc0000044a0}
{2 2019-01-06 19:48:08.1290002 +0800 CST m=+0.035000001 745e932be093669729f66a3e815421db8b3a76cd3c270bf2a69e19a8d9b8e897 1b60389ee80bb5825e88018e9fed7836e549e82f09a914798db38e13d1402f32 1 0xc0000044c0}
{3 2019-01-06 19:48:08.1290002 +0800 CST m=+0.035000001 1b60389ee80bb5825e88018e9fed7836e549e82f09a914798db38e13d1402f32 8f0b799c377dfc0260859e993581b022591017a1f2507f70c05f5b3bc77cf3ea 2 0xc000004500}
委托挖矿用户: node1
区块Hash值: 81e2657598ab5d785ad5e37ce28bdbeedde993a72959d194d569e9a781371500
区块数据: 0
委托挖矿用户: node1
区块Hash值: 745e932be093669729f66a3e815421db8b3a76cd3c270bf2a69e19a8d9b8e897
区块数据: 0
委托挖矿用户: node2
区块Hash值: 1b60389ee80bb5825e88018e9fed7836e549e82f09a914798db38e13d1402f32
区块数据: 1
委托挖矿用户: node4
区块Hash值: 8f0b799c377dfc0260859e993581b022591017a1f2507f70c05f5b3bc77cf3ea
区块数据: 2
成功: 进程退出代码 0.
本代码当前测试环境为golang1.9.2。
参考文献:
[1] 白话区块链
[2] 什么是DPoS:https://www.chaindesk.cn/witbook/18/307