Go-ethereum 源码解析之 miner/unconfirmed.go
package miner
import (
"container/ring"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// chainRetriever is used by the unconfirmed block set to verify whether a previously
// mined block is part of the canonical chain or not.
type chainRetriever interface {
// GetHeaderByNumber retrieves the canonical header associated with a block number.
GetHeaderByNumber(number uint64) *types.Header
// GetBlockByNumber retrieves the canonical block associated with a block number.
GetBlockByNumber(number uint64) *types.Block
}
// unconfirmedBlock is a small collection of metadata about a locally mined block
// that is placed into a unconfirmed set for canonical chain inclusion tracking.
type unconfirmedBlock struct {
index uint64
hash common.Hash
}
// unconfirmedBlocks implements a data structure to maintain locally mined blocks
// have have not yet reached enough maturity to guarantee chain inclusion. It is
// used by the miner to provide logs to the user when a previously mined block
// has a high enough guarantee to not be reorged out of the canonical chain.
type unconfirmedBlocks struct {
chain chainRetriever // Blockchain to verify canonical status through
depth uint // Depth after which to discard previous blocks
blocks *ring.Ring // Block infos to allow canonical chain cross checks
lock sync.RWMutex // Protects the fields from concurrent access
}
// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks {
return &unconfirmedBlocks{
chain: chain,
depth: depth,
}
}
// Insert adds a new block to the set of unconfirmed ones.
func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) {
// If a new block was mined locally, shift out any old enough blocks
set.Shift(index)
// Create the new item as its own ring
item := ring.New(1)
item.Value = &unconfirmedBlock{
index: index,
hash: hash,
}
// Set as the initial ring or append to the end
set.lock.Lock()
defer set.lock.Unlock()
if set.blocks == nil {
set.blocks = item
} else {
set.blocks.Move(-1).Link(item)
}
// Display a log for the user to notify of a new mined block unconfirmed
log.Info(" mined potential block", "number", index, "hash", hash)
}
// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth
// allowance, checking them against the canonical chain for inclusion or staleness
// report.
func (set *unconfirmedBlocks) Shift(height uint64) {
set.lock.Lock()
defer set.lock.Unlock()
for set.blocks != nil {
// Retrieve the next unconfirmed block and abort if too fresh
next := set.blocks.Value.(*unconfirmedBlock)
if next.index+uint64(set.depth) > height {
break
}
// Block seems to exceed depth allowance, check for canonical status
header := set.chain.GetHeaderByNumber(next.index)
switch {
case header == nil:
log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash)
case header.Hash() == next.hash:
log.Info(" block reached canonical chain", "number", next.index, "hash", next.hash)
default:
// Block is not canonical, check whether we have an uncle or a lost block
included := false
for number := next.index; !included && number < next.index+uint64(set.depth) && number <= height; number++ {
if block := set.chain.GetBlockByNumber(number); block != nil {
for _, uncle := range block.Uncles() {
if uncle.Hash() == next.hash {
included = true
break
}
}
}
}
if included {
log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash)
} else {
log.Info(" block lost", "number", next.index, "hash", next.hash)
}
}
// Drop the block out of the ring
if set.blocks.Value == set.blocks.Next().Value {
set.blocks = nil
} else {
set.blocks = set.blocks.Move(-1)
set.blocks.Unlink(1)
set.blocks = set.blocks.Move(1)
}
}
}
Appendix A. 总体批注
文件 miner/unconfirmed.go 主要用于这样一种场景:存在一个经典链(这里经典链是指其链上的区块都已经得到所有节点的确认);一个待确认区块的集合(这些待确认区块都是已经被挖出的区块)。存在两种操作:一种是向待确认区块的集合中插入一个新的待确认区块;另一种是剔除待确认区块的集合中的所有过期的待确认区块。这里的过期是指之前的待确认区块的编号和新插入的待确认区块的编号之间已经超过最大深度。
特别需要注意下面的日志信息:
- log.Info(" mined potential block", "number", index, "hash", hash)
- 表示矿工挖出了一个新的区块,但是这个区块还没有得到其它节点的认证,没有成为经典链的一部分。
- log.Info(" block reached canonical chain", "number", next.index, "hash", next.hash)
- 表示之前矿工挖出的区块已经得到其它节点的认证成为经典链的一部分。
下面这些日志应该不常见到:
- log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash)
- 表示之前矿工挖出的区块已经得到其它节点的认证,但是是作为叔区块的方式被经典链接受的。
下面这些日志如果出现,则表明出现了问题:
- log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash)
- log.Info(" block lost", "number", next.index, "hash", next.hash)
1. type chainRetriever interface
接口 chainRetriever 提供了从链中获取区块的几个接口,BlockChain 实现了此接口。
- 通过函数 GetHeaderByNumber() 根据给定的区块编号获取经典链中的区块头。
- 通过函数 GetBlockByNumber() 根据给定的区块编号获取经典链中的区块。
2. type unconfirmedBlock struct
数据结构 unconfirmedBlock 描述了本地已挖出区块的一小部分元数据集,这些元数据集被放入待确认区块集以用于检查是否已存在于经典链中。
- index uint64: 区块编号
- hash common.Hash: 区块哈希,即区块头哈希,在区块中被缓存
3. type unconfirmedBlocks struct
数据结构 unconfirmedBlocks 用于维护本地已经挖出的区块,但这些区块由于还没有得到足够的接受度从而不能保证存在于经典链中。它被矿工用于向用户提供日志,当前面已经挖出的区块有足够的保证不会被重新组织从而踢出经典链。
chain chainRetriever: 用于验证经典状态的区块链,实现接口 chainRetriever
depth uint: 达到多少深度时可以丢弃之前的区块,深度即区块编号之间的差值
blocks *ring.Ring: 允许经典链交叉检查的待确定区块的双向链表,从后面的实现看这个双向链表是有序的,区块编号最大的在最左边,区块编号最小的在最右边。
lock sync.RWMutex: 用于保护并发访问的读写锁
通过函数 newUnconfirmedBlocks() 用于构建对象 unconfirmedBlocks。
通过方法 Insert() 向 unconfirmedBlocks 中插入一个新的 unconfirmedBlock,且 unconfirmedBlock 的由参数 index 和 hash 初始化。
通过方法 Shift() 从集合中剔除所有超过深度允许的待确认区块,检查并报告这些待确认区块是已经包含在经典中还是已经过期。
Appendix B. 详细批注
1. type chainRetriever interface
接口 chainRetriever 被待确认的区块集用于验证之前挖出的一个区块是否已经属于经典链的一部分。
(1) GetHeaderByNumber(number uint64) *types.Header
函数 GetHeaderByNumber() 根据给定的区块编号获取经典链中的区块头。
(2) GetBlockByNumber(number uint64) *types.Block
函数 GetBlockByNumber() 根据给定的区块编号获取欧典链中的区块。
2. type unconfirmedBlock struct
数据结构 unconfirmedBlock 描述了本地已挖出区块的一小部分元数据集,这些元数据集被放入待确认区块集以用于检查是否已存在于经典链中。
- index uint64: 区块编号
- hash common.Hash: 区块哈希,即区块头哈希,在区块中被缓存
3. type unconfirmedBlocks struct
数据结构 unconfirmedBlocks 用于维护本地已经挖出的区块,但这些区块由于还没有得到足够的接受度从而不能保证存在于经典链中。它被矿工用于向用户提供日志,当前面已经挖出的区块有足够的保证不会被重新组织从而踢出经典链。
- chain chainRetriever: 用于验证经典状态的区块链,实现接口 chainRetriever
- depth uint: 达到多少深度时可以丢弃之前的区块,深度即区块编号之间的差值
- blocks *ring.Ring: 允许经典链交叉检查的待确定区块的双向链表,从后面的实现看这个双向链表是有序的,区块编号最大的在最左边,区块编号最小的在最右边。
- lock sync.RWMutex: 用于保护并发访问的读写锁
(1) func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks
函数 newUnconfirmedBlocks() 用于构建对象 unconfirmedBlocks。
主要实现细节如下:
- 函数 newUnconfirmedBlocks() 相当于 unconfirmedBlocks 的构造函数
- 只初始化了对象 unconfirmedBlocks 的部分信息,如字段 chain 和 depth
(2) func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash)
方法 Insert() 向 unconfirmedBlocks 中插入一个新的 unconfirmedBlock,且 unconfirmedBlock 的由参数 index 和 hash 初始化。
主要实现细节如下:
- 每次有新的 unconfirmedBlock 需要加入时,尝试剔除足够老的 unconfirmedBlock。
- 具体的插入算法由 ring.Ring 实现,需要考虑 ring.Ring 原来是否为空。
- 向用户显示日志以通知挖出了一个待确认的新区块
- log.Info(" mined potential block", "number", index, "hash", hash)
(3) func (set *unconfirmedBlocks) Shift(height uint64)
方法 Shift() 从集合中剔除所有超过深度允许的待确认区块,检查并报告这些待确认区块是已经包含在经典中还是已经过期。
主要实现细节如下:
- 具体的剔除算法由 ring.Ring 实现,需要考虑剔除元素之后 ring.Ring 是否为空。
- 对于每一个将被剔除的待确认区块,会报告该待确认区块是已经存在于经典链中,还是属于叔区块,或者已经丢失。
- log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash)
- log.Info(" block reached canonical chain", "number", next.index, "hash", next.hash)
- log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash)
- log.Info(" block lost", "number", next.index, "hash", next.hash)
Reference
- https://github.com/ethereum/go-ethereum/blob/master/miner/unconfirmed.go
Contributor
- Windstamp, https://github.com/windstamp