POW,proof of work,即工作量证明,是著名公bitcoin所采用的共识算法。简单来说,pow就是一个证明,由矿工使用算力进行计算(挖矿),竞争记账权,获得记账权的矿工将获得奖励和记录账本的权力。
其过程大致如下:
全网矿工会监听全网的数据记录
收到数据后,矿工验证交易并构建区块信息,包括区块头和区块体
努力进行hash运算(求X)
找到hash的矿工向全网公告自己产生的新区块
// Engine 引擎是一种算法无关的共识引擎
type Engine interface {
// Author 获取创建给定区块的账户的以太坊地址,如果共识引擎基于签名,可能与头的coinbase不同。
Author(header *types.Header) (common.Address, error)
//VerifyHeader 用于校验区块头,通过共识规则来校验,验证区块可以在这里进行也科通通过VerifySeal方法
VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error
// VerifyHeaders与VerifyHeader相似,同时这个用于批量操作校验头。这个方法返回一个退出信号
VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
// VerifyUncles 用于校验叔块以符合共识引擎的规则
VerifyUncles(chain ChainReader, block *types.Block) error
//根据特定引擎的规则,准备初始化块头的共识字段。这些改变是内联执行的。
Prepare(chain ChainHeaderReader, header *types.Header) error
//Finalize运行任何事务后的状态修改(例如块奖励),但不组装块。
//注意:块头和状态数据库可能会被更新,以反映在结束时发生的任何共识规则(例如块奖励)。
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header)
//与Finalize类似,FinalizeAndAssemble运行任何事务后的状态修改(例如块奖励),但会组装最终的块
//其他的都一样
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)
//Seal 为给定的输入块生成一个新的密封请求(新的区块),并将结果推送到给定的通道中。
//注意,该方法立即返回并将发送异步结果。根据共识算法,还可以返回多个结果。
Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
//SealHash 返回一个块被密封之前的哈希值。
SealHash(header *types.Header) common.Hash
//CalcDifficulty 是难度调整算法。它返回一个新方块应该具有的难度。
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
//APIs 返回这个共识引擎提供的RPC api。
APIs(chain ChainHeaderReader) []rpc.API
//Close 终止由共识引擎维护的所有后台线程。
Close() error
}
实现了consensus.Engine 该方法获取了挖出这个块的矿工地址。
func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
检查一个头是否符合以太坊ethash引擎的共识规则,返回时又调用了另一个verifyHeader方法
func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
// If we're running a full engine faking, accept any input as valid
// 如果处于ModeFullFake模式,接受任何有效的输入
if ethash.config.PowMode == ModeFullFake {
return nil
}
// Short circuit if the header is known, or its parent not
//如果header是已知的,或者父header是未知的,则返回
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
}
// Sanity checks passed, do a proper verification
//合理的通过检查,做适当的验证
return ethash.verifyHeader(chain, header, parent, false, seal, time.Now().Unix())
}
另一个verifyHeader
verifyHeader检查一个头是否符合以太坊ethash引擎的共识规则。参见YP 4.3.4节。“块头有效性”
func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error {
// Ensure that the header's extra-data section is of a reasonable size
// 确保额外数据段具有合理的长度
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
// Verify the header's timestamp
// 校验时间戳
if !uncle {
if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) {
return consensus.ErrFutureBlock
}
}
// 要求当前时间戳大于父时间戳
if header.Time <= parent.Time {
return errOlderBlockTime
}
// Verify the block's difficulty based on its timestamp and parent's difficulty
// 根据时间戳和父级块的难度校验块的难度。
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)
}
// 校验gas limit <= 2^63-1
if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
}
// 校验 gasUsed <= gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// Verify the block's gas usage and (if applicable) verify the base fee.
// 核实区块的gas使用情况,并核实基础费用(如果适用)。
if !chain.Config().IsLondon(header.Number) {
// Verify BaseFee not present before EIP-1559 fork.
//验证BaseFee这一操作在EIP-1559分支前是不存在的
if header.BaseFee != nil {
return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee)
}
if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil {
return err
}
} else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil {
// Verify the header's EIP-1559 attributes.
return err
}
// 验证块号是父级的+1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber
}
// Verify the engine specific seal securing the block
//校验特定的块是否符合要求
if seal {
if err := ethash.verifySeal(chain, header, false); err != nil {
return err
}
}
// If all checks passed, validate any special fields for hard forks
// 如果所有检查通过,则验证硬分叉的特殊字段。
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
}
VerifyHeaders和VerifyHeader类似,只是VerifyHeaders进行批量校验操作。
创建多个goroutine用于执行校验操作,再创建一个goroutine用于赋值控制任务分配和结果获取。最后返回一个结果channel
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
// If we're running a full engine faking, accept any input as valid
//ModeFullFake模式下,任何输入都是有效的
if ethash.config.PowMode == ModeFullFake || len(headers) == 0 {
abort, results := make(chan struct{}), make(chan error, len(headers))
for i := 0; i < len(headers); i++ {
results <- nil
}
return abort, results
}
// Spawn as many workers as allowed threads
//生成尽可能多的工作线程
workers := runtime.GOMAXPROCS(0)
if len(headers) < workers {
workers = len(headers)
}
// Create a task channel and spawn the verifiers
//创建任务通道并生成验证器
var (
inputs = make(chan int)
done = make(chan int, workers)
errors = make([]error, len(headers))
abort = make(chan struct{})
unixNow = time.Now().Unix()
)
for i := 0; i < workers; i++ {
// 产生workers个goroutine用于校验头
go func() {
for index := range inputs {
errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index, unixNow)
done <- index
}
}()
}
// goroutine 用于发送消息到workers个goroutine上
errorsOut := make(chan error, len(headers))
go func() {
defer close(inputs)
var (
in, out = 0, 0
checked = make([]bool, len(headers))
inputs = inputs
)
for {
select {
case inputs <- in:
if in++; in == len(headers) {
// Reached end of headers. Stop sending to workers.
inputs = nil
}
// 统计结果,并把错误消息发送到errorsOut上
case index := <-done:
for checked[index] = true; checked[out]; out++ {
errorsOut <- errors[out]
if out == len(headers)-1 {
return
}
}
case <-abort:
return
}
}
}()
return abort, errorsOut
}
Prepare实现共识引擎的Prepare接口,用于填充区块头的难度字段,使之符合ethash协议。这个改变是在线的。
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
}
header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent)
return nil
}
Finalize实现共识引擎的Finalize接口,奖励挖到区块账户和叔块账户,并填充状态树的根的值。并返回新的区块。
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// Accumulate any block and uncle rewards and commit the final state root
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}
accumulateRewards用于计算奖励,总奖励包括静态块奖励和包含叔叔的奖励。每个叔叔块的coinbase也会得到奖励。
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
//根据进程选择正确的区块奖励
blockReward := FrontierBlockReward
//拜占庭和君士坦丁堡两个阶段的奖励是不同的
if config.IsByzantium(header.Number) {
blockReward = ByzantiumBlockReward
}
if config.IsConstantinople(header.Number) {
blockReward = ConstantinopleBlockReward
}
// Accumulate the rewards for the miner and any included uncles
//基础的blockReward挖矿奖励 再加上其他叔块的奖励
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
// 叔块奖励为(叔块number+8 - 当前块number) * blockReward/8
// 引用叔块奖励为标准块的32分之一
for _, uncle := range uncles {
// (叔块number+8 - 当前块number) * blockReward/8
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
//AddBalance添加金额到与addr相关联的帐户。
state.AddBalance(uncle.Coinbase, r)
//正常块奖励的32分之一
r.Div(blockReward, big32)
reward.Add(reward, r)
}
// 奖励coinbase账户
state.AddBalance(header.Coinbase, reward)
}
在CPU挖矿部分,CpuAgent的mine函数,执行挖矿操作的时候调用了Seal函数。Seal函数尝试找出一个满足区块难度的nonce值。
在ModeFake和ModeFullFake模式下,快速返回,并且直接将nonce值取0。
在shared PoW模式下,使用shared的Seal函数。
开启threads个goroutine进行挖矿(查找符合条件的nonce值)。
// 尝试去寻找一个满足区块难度要求的nonce
func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
// If we're running a fake PoW, simply return a 0 nonce immediately
//如果我们正在运行一个假的PoW,只需立即返回一个0
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
}
// If we're running a shared PoW, delegate sealing to it
//如果我们正在运行一个共享的PoW,将密封委托给它
if ethash.shared != nil {
return ethash.shared.Seal(chain, block, results, stop)
}
// Create a runner and the multiple search threads it directs
abort := make(chan struct{})
ethash.lock.Lock()
// 使用多线程去寻找nonce
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
}
// Push new work to remote sealer
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()))
}
// Wait until sealing is terminated or a nonce is found
// 等待直到密封终止或发现一个瞬间
go func() {
var result *types.Block
select {
case <-stop:
// Outside abort, stop all miner threads
// 在外部中止,停止所有矿工线程
close(abort)
case result = <-locals:
// One of the threads found a block, abort all others
// 其中一个线程发现了一个块,中止所有其他线程
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:
// Thread count was changed on user request, restart
//线程数在用户请求时改变,重新启动
close(abort)
if err := ethash.Seal(chain, block, results, stop); err != nil {
ethash.config.Log.Error("Failed to restart sealing after update", "err", err)
}
}
// Wait for all miners to terminate and return the block
// 等待所有的挖矿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越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。
// mine是真正的查找nonce值的函数,它不断遍历查找nonce值,并计算PoW值与目标值进行比较。
func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) {
// Extract some data from the header
// 从区块头中获取一些数据
var (
header = block.Header()
hash = ethash.SealHash(header).Bytes()
// target 即查找的PoW的上限 target = maxUint256/Difficulty
// 其中maxUint256 = 2^256-1 Difficulty即难度值
target = new(big.Int).Div(two256, header.Difficulty)
number = header.Number.Uint64()
dataset = ethash.dataset(number, false)
)
// Start generating random nonces until we abort or find a good one
// 尝试查找一个nonce值,直到终止或者找到目标值
var (
attempts = int64(0)
nonce = seed
powBuffer = new(big.Int)
)
logger := ethash.config.Log.New("miner", id)
logger.Trace("Started ethash search for new nonces", "seed", seed)
search:
for {
select {
case <-abort:
// Mining terminated, update stats and abort
// 终止挖矿
logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed)
ethash.hashrate.Mark(attempts)
break search
default:
// We don't have to update hash rate on every nonce, so update after after 2^X nonces
// 不必在每个nonce值都更新hash rate,每2^x个nonce值更新一次hash rate
attempts++
if (attempts % (1 << 15)) == 0 {
ethash.hashrate.Mark(attempts)
attempts = 0
}
// Compute the PoW value of this nonce
// 用这个nonce计算PoW值
digest, result := hashimotoFull(dataset.dataset, hash, nonce)
// 将计算的结果与目标值比较,如果小于目标值,则查找成功。
if powBuffer.SetBytes(result).Cmp(target) <= 0 {
// Correct nonce found, create a new header with it
// 查找到nonce值,更新区块头
header = types.CopyHeader(header)
header.Nonce = types.EncodeNonce(nonce)
header.MixDigest = common.BytesToHash(digest)
// Seal and return a block (if still needed)
// 打包区块头并返回
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++
}
}
// Datasets are unmapped in a finalizer. Ensure that the dataset stays live
// during sealing so it's not unmapped while being read.
runtime.KeepAlive(dataset)
}
上述函数调用了hashimotoFull函数用来计算PoW的值。
hashimoto用于聚合数据以产生特定的后部的hash和nonce值。
func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
lookup := func(index uint32) []uint32 {
offset := index * hashWords
return dataset[offset : offset+hashWords]
}
return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
}
之后又调用了hashimoto函数:
简述该部分流程:
//以特定的头哈希和nonce生成最终值。
func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) {
// Calculate the number of theoretical rows (we use one buffer nonetheless)
// 计算理论行数
rows := uint32(size / mixBytes)
// Combine header+nonce into a 64 byte seed
// 将 header+nonce into 装换为64字节的seed
seed := make([]byte, 40)
copy(seed, hash)
binary.LittleEndian.PutUint64(seed[32:], nonce)
seed = crypto.Keccak512(seed)
seedHead := binary.LittleEndian.Uint32(seed)
// Start the mix with replicated seed
// 将seed[]转化成以uint32为元素的数组mix[]
mix := make([]uint32, mixBytes/4)
for i := 0; i < len(mix); i++ {
mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:])
}
// Mix in random dataset nodes
// 向mix[]数组中混入未知的数据
temp := make([]uint32, len(mix))
for i := 0; i < loopAccesses; i++ {
parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows
for j := uint32(0); j < mixBytes/hashBytes; j++ {
copy(temp[j*hashWords:], lookup(2*parent+j))
}
fnvHash(mix, temp)
}
// Compress mix
// 压缩成一个长度缩小成原长1/4的uint32数组
for i := 0; i < len(mix); i += 4 {
mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
}
mix = mix[:len(mix)/4]
digest := make([]byte, common.HashLength)
for i, val := range mix {
binary.LittleEndian.PutUint32(digest[i*4:], val)
}
return digest, crypto.Keccak256(append(seed, digest...))
}
// PoW is a consensus engine based on proof-of-work.
//PoW是一个基于工作证明的共识引擎。
type PoW interface {
Engine
// Hashrate returns the current mining hashrate of a PoW consensus engine.
//返回PoW共识引擎的当前挖掘哈希率。
Hashrate() float64
}
hashrate:每秒可以完成哈希碰撞的次数
哈希碰撞:矿工要解的数学题难度,相当于扔1亿个骰子,扔出小于1亿零6的数字,谁先扔出来,谁就赢得记账权。
1亿零6,就是哈希值。扔骰子的过程,就是哈希碰撞。
Ethhash是实现PoW的具体实现,由于要使用到大量的数据集,所有有两个指向lru的指针。并且通过threads控制挖矿线程数。并在测试模式或fake模式下,简单快速处理,使之快速得到结果。
type Ethash struct {
//Config是ethash的配置参数。
config Config
//缓存
caches *lru // In memory caches to avoid regenerating too often
//内存数据集
datasets *lru // In memory datasets to avoid regenerating too often
// Mining related fields
//挖矿相关字段
//随机数种子
rand *rand.Rand // Properly seeded random source for nonces
// 挖矿线程数量
threads int // Number of threads to mine on if mining
// channel 用于更新挖矿通知
update chan struct{} // Notification channel to update mining parameters
//平均哈希率记录表
hashrate metrics.Meter // Meter tracking the average hashrate
remote *remoteSealer
// The fields below are hooks for testing
// 测试网络相关参数
//共享PoW验证器,以避免缓存再生
shared *Ethash // Shared PoW verifier to avoid cache regeneration
//即使在假模式下也不能通过PoW检查的方块号
Fail uint64 // Block number which fails PoW check even in fake mode
//从verify返回前的延迟睡眠时间
fakeDelay time.Duration // Time delay to sleep for before returning from verify
//确保缓存和挖矿的线程安全锁
lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
//确保出口通道不会被关闭两次。
closeOnce sync.Once // Ensures exit channel will not be closed twice.
}