天天看點

一個基于DPoS共識算法的區塊鍊案例解析

一個基于DPoS共識算法的區塊鍊案例解析

一、前言

前面我們介紹了PoW以及PoS的案例,我們會發現它們都有一些缺點,比如PoW耗費能源比較多,而PoS是持有的币越多,成功挖礦的幾率越大,這會造成貧富差距越來越大,并且人們都不太願意消耗自己的币。

而我們的DPoS,全名為Delegated Proof of Stake,也就是股份授權證明就解決了這些不足。

DPoS就是大家投票選出一定數量的節點來挖礦,使用者擁有的票的數量和他持有的币數量有關。這就和股份制公司很像了,大家投票選出董事會成員。

這些被選出來的擁有挖礦權的節點的挖礦權力是一模一樣的。

如果某個節點挖到了礦,那麼他就要将獲得的币分一些給投票給他的人。

一、定義區塊、區塊鍊

type Node struct {
    Name string
    Votes int
}

type Block struct {
    Index     int
    Timestamp string
    Prehash   string
    Hash      string
    Data      []byte
    delegate *Node
}           

相信關注這個專欄前幾篇文章的老朋友應該都知道區塊内的資訊代表什麼,這裡簡單說一下Index是區塊高度,TimeStamp是時間戳,Data是塊儲存的一些資料,Hash是目前區塊的哈希值,PrevHash是先前區塊的哈希值,delegate是區塊的挖掘者。

而這裡的節點資訊就是之前沒有介紹的了,Name是節點名稱,Votes是被投的票數。

二、生成創世區塊

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
}           

還是講過的邏輯,将這些資料放入區塊中,便生成了一個新的區塊。

五、建立節點

var NodeAddr = make([]Node, 10)


func CreateNode() {
    for i := 0; i < 10; i++ {
        name := fmt.Sprintf("節點 %d 票數", i)
        //初始化時票數為0
        NodeAddr[i] = Node{name, 0}
    }
}           

假設我們這個區塊鍊項目有10個節點,然後初始化節點,将節點的名字設為 節點0到節點9,然後初始化票數為0,将初始化的節點放入節點清單中。

六、模拟投票

func Vote() {
    for i := 0; i < 10; i++ {
        rand.Seed(time.Now().UnixNano())
        time.Sleep(100000)
        vote := rand.Intn(10000)
        NodeAddr[i].Votes = vote
        fmt.Printf("節點 [%d] 票數 [%d]\n", i, vote)
    }
}           

我們這裡使用随機數來配置設定節點被投的票數,因為要給10個節點投票,是以周遊10次,每次給節點投範圍為0-9999的票數。

七、選拔挖礦節點

func SortNodes() []Node {
    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() {
    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)))
    }
}           

主邏輯也是比較簡單的,先初始化10個節點,然後投票,再根據票數選出前三名。然後前三名來生成新的區塊。

是不是很簡單,如果有興趣的話,還可以看看前面描述的關于PoW和PoS的簡單執行個體。

繼續閱讀