本篇文章主要是DPOS共识的简单实现,其中有许多地方都做了简化。DPOS的原理已在上篇文章中描述过,如果对DPOS的原理不太清晰的可以进行查看。文章地址:共识算法学习总结。
代码实现的功能比简单,主要有:添加区块,代理者的投票以及查看所有的代理者。代码中使用bolt数据库进行数据的持久化,p2p模块主要使用了libp2p。
创建一个区块链
在系统开始时,要创建一条区块链。该函数首先初始化数据库,然后生成创世块并保存在数据中。
func NewBlockchain() {
// 初始化数据库
setupDB()
db := common.GetDB()
defer db.Close()
// 生成创世区块
block := genGenesisBlock()
err := db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(common.BlocksBucket))
err = bucket.Put([]byte("lastHash"), []byte(block.Hash))
err = bucket.Put([]byte(block.Hash), serializeBlock(block))
if err != nil {
log.Panic(err)
}
return nil
})
log.Println(">>> 创建区块链成功")
}
添加一个区块
初始化区块链后就可以进行区块的添加。该函数首先启动共识模块和p2p模块,接着打包区块。为了简化代码,这里直接创建了区块,实际上应该由特定的代理者进行打包创建区块。接着把创建的区块添加到候选区块通道中,等待共识模块的确认。
func add(data string, port int, target string) {
log.Println(">>> 开始添加区块链")
// 启动共识
go consensus.Start()
log.Println(">>> 启动共识...")
// 启动p2p节点
go p2p.Start(port, target, 0)
log.Println(">>> 启动p2p网络...")
// 打包区块
newBlock := common.Block{}
newBlock.Height = getLastHeight() + 1
newBlock.Data = data
newBlock.Timestamp = time.Now().String()
newBlock.PrevHash = getLastBlockHash()
newBlock.Hash = calculateBlockHash(newBlock)
// 新生成的区块添加到候选区块通道中,等待共识确认
common.CandidateBlokcs <- newBlock
}
DPOS共识
该函数主要做了两件事:循环读取候选区块通道并追加到临时区块切片中,另一件是读取临时区块切片中的数据,并选择代理人打包区块。
func Start() {
// 循环读取候选区块通道中的区块信息
go func() {
for{
for candidate := range common.CandidateBlokcs{
common.TempBlocks = append(common.TempBlocks, candidate)
}
}
}()
// 开始共识
for {
if len(common.TempBlocks) > 0{
for _, block := range common.TempBlocks{
// 挑选验证者
delegate := getDelegate()
block.Validator = delegate.Address
// 序列化
result, err := json.Marshal(block)
if err != nil{
log.Fatal("marshal block error: ", err)
}
//添加到数据库
db := common.GetDB()
db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(common.BlocksBucket))
bucket.Put([]byte(block.Hash), result)
err = bucket.Put([]byte("lastHash"),[]byte(block.Hash))
return nil
})
log.Println(">>> 添加区块链成功!")
db.Close()
common.TempBlocks = nil
}
}
}
}
获取代理人
这里假设得票数最高的两个代理人进行随机出块。
func getDelegate() common.Delegate {
// 从数据库中查出所有的代理人
var delegates []common.Delegate
db := common.GetDB()
defer db.Close()
db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(common.PeerBucket))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
delegate := unmarshalDelegate(v)
delegates = append(delegates, delegate)
}
return nil
})
// 挑选候选人,假设取得票数最高的两个代理人
quickSort(0, len(delegates) - 1, delegates)
rand.Seed(time.Now().Unix())
randNumber := rand.Intn(2)
// 返回指定的代理人
return delegates[randNumber]
}
启动P2P网络
该函数首先创建一个libp2p节点,端口可以自己指定。如果不指定目标地址target,创建节点完毕后就等待新的连接。如果指定目标地址target,创建完节点后会连接到目标地址。seed为随机数种子。
func Start(port int, target string, seed int64) {
// 创建一个libp2p节点
ha, err := makeBasicHost(port, seed)
if err != nil {
log.Fatal(err)
}
// 判断是否有目标地址
if target == "" {
log.Println(">>> 等待新的连接")
// 设置stream
ha.SetStreamHandler("/p2p/1.0.0", handleStream)
select {}
}else{
// 设置stream
ha.SetStreamHandler("/p2p/1.0.0", handleStream)
// 获取peer ID
ipfsaddr, err := ma.NewMultiaddr(target)
if err != nil {
log.Fatalln(err)
}
info, err := peer.AddrInfoFromP2pAddr(ipfsaddr)
if err != nil {
log.Println(err)
}
// 把节点添加到peer store中,以便libp2p连接这个节点
ha.Peerstore().AddAddr(info.ID, info.Addrs[0], peerstore.PermanentAddrTTL)
// 创建当前节点与目标节点的stream
s, err := ha.NewStream(context.Background(), info.ID, "/p2p/1.0.0")
if err != nil {
log.Fatalln(err)
}
// 读写stream中的数据
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
go writeData(rw)
go readData(rw)
select {}
}
return
}
查看网络中的节点
查看网络中的节点比较简单,只是查询数据库并进行打印。
func view() {
var delegates []common.Delegate
db := common.GetDB()
defer db.Close()
db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(common.PeerBucket))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next(){
delegate := unmarshalDelegate(v)
delegates = append(delegates, delegate)
}
return nil
})
spew.Dump(delegates)
}
投票
进行投票时,首选输入代理人的地址,然后输入投票的数量。
func Vote( address string, voteNum int) {
if address == "" {
log.Fatal("节点名称不能为空")
return
}
if voteNum < 0 {
log.Fatal("最小投票数为1")
return
}
var delegate common.Delegate
db := common.GetDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(common.PeerBucket))
result := bucket.Get([]byte(address))
err := json.Unmarshal(result, &delegate)
if err != nil{
log.Fatal("反序列化代理人错误: ", err)
}
log.Println(delegate)
delegate.Number += voteNum
mashalResult, err := json.Marshal(delegate)
if err != nil{
log.Fatal("序列化代理人错误: ", err)
}
err = bucket.Put([]byte(address), mashalResult)
if err != nil{
log.Fatal("更新代理人票数错误: ", err)
}
return nil
})
}
运行截图
最后
项目中做了很多简化并且有很多设计不合理的地方,以后会继续进行改进。源码:https://github.com/blockchainGuide/Consensus_Algorithm