天天看點

共識算法3--委托權益證明機制簡介及算法實作

共識算法3--委托權益證明機制簡介及算法實作

在區塊鍊中使用PoS會導緻貧富差距增加的問題,為解決該問題,提出了DPoS機制,DPoS已經在EOS中得到了應用。實際區塊鍊産品中,由x個投票主節點和y個候選節點實作共識,x個投票主節點負責共識和挖礦,當系統發現x中的某個節點有問題(如惡意破壞)則使用y中的候選節點替代x中的節點,進而繼續保證系統正常運作。筆者此處簡要介紹一下DPoS算法的原理和一個基于DPoS算法的簡單挖礦算法。

1、委托權益證明介紹

委托權益證明機制( 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]

2、DPoS算法實作

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.      

3、說明

本代碼目前測試環境為golang1.9.2。

參考文獻: