golang实现dpos

Steven_25bb · · 884 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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简单实现


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:Steven_25bb

查看原文:golang实现dpos

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

884 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传