源码
GitHub地址 https://github.com/TTCECO/gttc
目录
基于以太坊go-ethereum的DPOS实现(一)源码及测试运行
基于以太坊go-ethereum的DPOS实现(二)简要说明
基于以太坊go-ethereum的DPOS实现(三)创世块
基于以太坊go-ethereum的DPOS实现(四)共识接口
基于以太坊go-ethereum的DPOS实现(五)定时出块
定时出块
原因
比特币是每10分钟出一个块,以太坊是15秒,但需要注意的是在POW共识机制下,出块的时间并不是特别准的,或者换句话说,不会根据出块时间来验证这个块出的是否正确,这个时间只是根据历史的全球算力来调整工作量证明的难度达到的近似时间,其目的也是为了减少费块等。
在DPOS共识机制当中,能够有权限出块的见证人,或者超级节点,必须按照特定的时间顺序出块,某个时间范围之内,只有一个见证人节点所生产的块被认为是有效的,所以需要控制节点的出块时间。(也许会有疑问,每个节点的时间戳不同会发生什么?可以参见简要说明中引用的链接)
统一启动时间
在创世块中有genesisTimestamp的配置,其指定的时间就是这个主链所有节点统一的启动时间。当启动时间未到的时候,在Prepare方法中会等待。
if header.Number.Uint64() == 1 {
for {
delay := time.Unix(int64(a.config.GenesisTimestamp-2), 0).Sub(time.Now())
if delay <= time.Duration(0) {
log.Info("Ready for seal block", "time", time.Now())
break
} else if delay > time.Duration(a.config.Period)*time.Second {
delay = time.Duration(a.config.Period) * time.Second
}
log.Info("Waiting for seal block", "delay", common.PrettyDuration(time.Unix(int64(a.config.GenesisTimestamp-2), 0).Sub(time.Now())))
select {
case <-time.After(delay):
continue
}
}
}
如果节点启动时,已经超过了创世块中所定义的启动时间,则会按照DPOS机制所定义的方式进行验证。具体的实现方式在snapshot.go当中。
// inturn returns if a signer at a given block height is in-turn or not.
func (s *Snapshot) inturn(signer common.Address, headerTime uint64) bool {
// if all node stop more than period of one loop
loopIndex := int((headerTime-s.LoopStartTime)/s.config.Period) % len(s.Signers)
if loopIndex >= len(s.Signers) {
return false
} else if *s.Signers[loopIndex] != signer {
return false
}
return true
}
我们会发现一个问题,如果有见证人节点在特定时间内,假设一轮都没有出块,那按照DPOS机制这个主链将如何继续下去呢?
简单举一个例子来说明,比如三个见证人节点A,B,C。本轮的出块顺序是A–>B–>C,时间间隔为1秒,假设A在第一秒出块之后,B第二秒没有出块,C第三秒也没有出块,那么A有权利在第四秒出块。一次类推,如果在一段时间内所有见证人节点均为出块,则知道有某个见证人恢复出块的时候,整个主链依然能够继续,而那个恢复出块的节点也只能在属于自己的时间内出块。在实现过程中,每个时间间隔,所有的见证人节点都可以去尝试出块,当然只有一个可能成功。
定时触发添加在worker.go当中,这里也是为数不多的consensus目录以外的修改。
func (self *worker) update() {
...
// set the delay equal to period if use alien consensus
alienDelay := time.Duration(300) * time.Second
if self.config.Alien != nil && self.config.Alien.Period > 0 {
alienDelay = time.Duration(self.config.Alien.Period) * time.Second
}
for {
// A real event arrived, process interesting content
select {
case <-time.After(alienDelay):
// try to seal block in each period, even no new block received in dpos
if self.config.Alien != nil && self.config.Alien.Period > 0 {
self.commitNewWork()
}
// Handle ChainHeadEvent
case
...