源码中/consensus/consensus.go
中的type Engine interface {}
定义了与算法无关的共识引擎,然后在ethash和clique中分别实现了各自的共识方式,其中ethash实现的是pow,clique实现的是poa。接下来对这个接口中定义的方法逐个分析,以ethash包为主。
首先看下Engine
中都定义了哪些接口:
type Engine interface {
Author(header *types.Header) (common.Address, error)
VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error
VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
VerifyUncles(chain ChainReader, block *types.Block) error
VerifySeal(chain ChainHeaderReader, header *types.Header) error
Prepare(chain ChainHeaderReader, header *types.Header) error
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header)
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
SealHash(header *types.Header) common.Hash
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
APIs(chain ChainHeaderReader) []rpc.API
Close() error
}
接下来看看各自的实现与作用
1,Author
返回传入区块头的coinbase,即挖矿人
func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
2,VerifyHeader
主要是用来校验区块头是否满足共识算法规则
func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
// 非正式环境,直接通过
if ethash.config.PowMode == ModeFullFake {
return nil
}
// 如果是已经存在的区块头,通过
number := header.Number.Uint64()
if chain.GetHeader(header.Hash(), number) != nil {
return nil
}
// 如果父区块或者叔块不存在,返回错误
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
return ethash.verifyHeader(chain, header, parent, false, seal)
}
看下verifyHeader方法,该方法多出被用到
func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error {
// Extra字段不能超长
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
// 时间戳在未来太远
if !uncle {
if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
return consensus.ErrFutureBlock
}
}
// 时间戳太小,小于父块
if header.Time <= parent.Time {
return errOlderBlockTime
}
// 调用CalcDifficulty方法查看当前区块的难度值,该方法后文会介绍
expected := ethash.CalcDifficulty(chain, header.Time, parent)
// 难度不匹配
if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
}
// GasLimit超标
cap := uint64(0x7fffffffffffffff)
if header.GasLimit > cap {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
}
// gasUsed不能大于gasLimit(<= 2^63-1)
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// GasLimit不能超过允许的范围: abs(gasLimit - 父gasLimit) < (父gasLimit/1024) && gasLimit > 5000
diff := int64(parent.GasLimit) - int64(header.GasLimit)
if diff < 0 {
diff *= -1
}
limit := parent.GasLimit / params.GasLimitBoundDivisor
if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
}
// 区块高度是父块+1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber
}
// 调用VerifySeal验证是否满足pow难度要求,该方法后文介绍
if seal {
if err := ethash.VerifySeal(chain, header); err != nil {
return err
}
}
// 验证硬分叉的字段
if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
return err
}
if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
return err
}
return nil
}
3,VerifyHeaders
该方法与VerifyHeader类似,区别在于VerifyHeaders可以传入多个header进行校验,调用多个协程同步进行,并以channel的方式异步返回错误
4,VerifyUncles
校验叔块是否合规,传入的block中带有uncles字段
func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
if ethash.config.PowMode == ModeFullFake {
return nil
}
// 叔块最多有2个
if len(block.Uncles()) > maxUncles {
return errTooManyUncles
}
if len(block.Uncles()) == 0 {
return nil
}
// 第一步:
// 获取当前block往前数7个块以及block自身,一共8个块,放入ancestors中(超过7个块,叔块就失效了)
// 获取ancestors中所有的叔块及block自身,放入uncles中
uncles, ancestors := mapset.NewSet(), make(map[common.Hash]*types.Header)
number, parent := block.NumberU64()-1, block.ParentHash()
for i := 0; i < 7; i++ {
ancestor := chain.GetBlock(parent, number)
if ancestor == nil {
break
}
ancestors[ancestor.Hash()] = ancestor.Header()
for _, uncle := range ancestor.Uncles() {
uncles.Add(uncle.Hash())
}
parent, number = ancestor.ParentHash(), number-1
}
ancestors[block.Hash()] = block.Header()
uncles.Add(block.Hash())
// 校验每个叔块,保证叔块是最新的,而不是包含在ancestors中的祖先区块或者是uncles中的祖先区块的叔块
for _, uncle := range block.Uncles() {
// Make sure every uncle is rewarded only once
hash := uncle.Hash()
if uncles.Contains(hash) {
return errDuplicateUncle
}
uncles.Add(hash)
// Make sure the uncle has a valid ancestry
if ancestors[hash] != nil {
return errUncleIsAncestor
}
if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
return errDanglingUncle
}
// 调用verifyHeader方法校验叔块的有效性,上文介绍过
if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
return err
}
}
return nil
}
5,VerifySeal
VerifySeal方法是检查区块是否符合pow的难度要求
func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error {
return ethash.verifySeal(chain, header, false)
}
func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, fulldag bool) error {
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
time.Sleep(ethash.fakeDelay)
if ethash.fakeFail == header.Number.Uint64() {
return errInvalidPoW
}
return nil
}
if ethash.shared != nil {
return ethash.shared.verifySeal(chain, header, fulldag)
}
// 确保难度合法
if header.Difficulty.Sign() <= 0 {
return errInvalidDifficulty
}
number := header.Number.Uint64()
var (
digest []byte
result []byte
)
// 根据传入的fulldag参数,决定是采用dataset还是cache的方式去计算digest和result
if fulldag {
dataset := ethash.dataset(number, true)
if dataset.generated() {
digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
// Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
// until after the call to hashimotoFull so it's not unmapped while being used.
runtime.KeepAlive(dataset)
} else {
// Dataset not yet generated, don't hang, use a cache instead
fulldag = false
}
}
if !fulldag {
cache := ethash.cache(number)
size := datasetSize(number)
if ethash.config.PowMode == ModeTest {
size = 32 * 1024
}
digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64()).
runtime.KeepAlive(cache)
}
// 比较digest是否相同
if !bytes.Equal(header.MixDigest[:], digest) {
return errInvalidMixDigest
}
// 比较难度是否符合要求
target := new(big.Int).Div(two256, header.Difficulty)
if new(big.Int).SetBytes(result).Cmp(target) > 0 {
return errInvalidPoW
}
return nil
}
6,Prepare
该方法是将传递进来的区块头填充Difficulty字段进去
func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
// 调用CalcDifficulty方法查看当前区块的难度值,该方法后文会介绍
header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent)
return nil
}
7,Finalize
计算块的奖励,根据不同的协议,给与不同的奖励数量,并设置Root字段
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// 计算块的奖励,根据不同的协议,给与不同的奖励数量。叔块的奖励也是在这里计算并保存
accumulateRewards(chain.Config(), state, header, uncles)
// 设置header的Root字段
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}
8,FinalizeAndAssemble
与Finalize方法相同,之后再组装成block并返回
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
//前面两步与Finalize方法相同
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// 区别在于会组装成一个block并返回
return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil
}
9,Seal
Seal方法是寻找一个符合pow难度要求的nonce的过程
func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
// 如果是非正式环境,直接返回0作为nonce,返回值通过chan results进行传递
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
header := block.Header()
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{}
select {
case results <- block.WithSeal(header):
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ethash.SealHash(block.Header()))
}
return nil
}
// 如果是shared PoW, 使用shared的Seal函数
if ethash.shared != nil {
return ethash.shared.Seal(chain, block, results, stop)
}
abort := make(chan struct{})
ethash.lock.Lock()
threads := ethash.threads
if ethash.rand == nil {
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
ethash.lock.Unlock()
return err
}
ethash.rand = rand.New(rand.NewSource(seed.Int64()))
}
ethash.lock.Unlock()
if threads == 0 {
threads = runtime.NumCPU()
}
if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote
}
if ethash.remote != nil {
ethash.remote.workCh <- &sealTask{block: block, results: results}
}
var (
pend sync.WaitGroup
locals = make(chan *types.Block)
)
for i := 0; i < threads; i++ {
pend.Add(1)
go func(id int, nonce uint64) {
defer pend.Done()
ethash.mine(block, id, nonce, abort, locals)
}(i, uint64(ethash.rand.Int63()))
}
go func() {
var result *types.Block
select {
case <-stop:
close(abort)
case result = <-locals:
select {
case results <- result:
default:
ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ethash.SealHash(block.Header()))
}
close(abort)
case <-ethash.update:
close(abort)
if err := ethash.Seal(chain, block, results, stop); err != nil {
ethash.config.Log.Error("Failed to restart sealing after update", "err", err)
}
}
// 等待所有的挖矿goroutine返回
pend.Wait()
}()
return nil
}
mine是真正的查找nonce值的函数,它不断遍历查找nonce值,并计算PoW值与目标值进行比较。 其原理可以简述为下:
RAND(h, n) <= M / d
这里M表示一个极大的数,这里是2^256-1;d表示Header成员Difficulty。RAND()是一个概念函数,它代表了一系列复杂的运算,并最终产生一个类似随机的数。这个函数包括两个基本入参:h是Header的哈希值(Header.HashNoNonce()),n表示Header成员Nonce。整个关系式可以大致理解为,在最大不超过M的范围内,以某个方式试图找到一个数,如果这个数符合条件(<=M/d),那么就认为Seal()成功。 由上面的公式可以得知,M恒定,d越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。
func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
// 从区块头中获取一些数据
var (
header = block.Header()
hash = header.HashNoNonce().Bytes()
// target 即查找的PoW的上限 target = maxUint256/Difficulty
// 其中maxUint256 = 2^256-1 Difficulty即难度值
target = new(big.Int).Div(maxUint256, header.Difficulty)
number = header.Number.Uint64()
dataset = ethash.dataset(number)
)
// 尝试查找一个nonce值,直到终止或者找到目标值
var (
attempts = int64(0)
nonce = seed
)
logger := log.New("miner", id)
logger.Trace("Started ethash search for new nonces", "seed", seed)
search:
for {
select {
case <-abort:
// 终止挖矿
logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)
ethash.hashrate.Mark(attempts)
break search
default:
// 不必在每个nonce值都更新hash rate,每2^x个nonce值更新一次hash rate
attempts++
if (attempts % (1 << 15)) == 0 {
ethash.hashrate.Mark(attempts)
attempts = 0
}
// 用这个nonce计算PoW值
digest, result := hashimotoFull(dataset.dataset, hash, nonce)
// 将计算的结果与目标值比较,如果小于目标值,则查找成功。
if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
// 查找到nonce值,更新区块头
header = types.CopyHeader(header)
header.Nonce = types.EncodeNonce(nonce)
header.MixDigest = common.BytesToHash(digest)
// 打包区块头并返回
select {
// WithSeal 将新的区块头替换旧的区块头
case found <- block.WithSeal(header):
logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce)
case <-abort:
logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce)
}
break search
}
nonce++
}
}
runtime.KeepAlive(dataset)
}
10,SealHash
SealHash方法是在seal之前返回一个区块的hash值
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
hasher.Sum(hash[:0])
return hash
}
11,CalcDifficulty
CalcDifficulty是调整难度的算法,用父块的出块时间和难度作为基数,计算创建新块时候应该具有的难度,不同的版本有不同的计算方式。
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
return CalcDifficulty(chain.Config(), time, parent)
}
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1)
switch {
case config.IsMuirGlacier(next):
return calcDifficultyEip2384(time, parent)
case config.IsConstantinople(next):
return calcDifficultyConstantinople(time, parent)
case config.IsByzantium(next):
return calcDifficultyByzantium(time, parent)
case config.IsHomestead(next):
return calcDifficultyHomestead(time, parent)
default:
return calcDifficultyFrontier(time, parent)
}
}
12,APIs
注册API
13,Close
通知所有线程关闭
转载请注明来源