一个基于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的简单实例。