From 7f0492a30c28d6e8c2969ef4e6a1880f73a32315 Mon Sep 17 00:00:00 2001 From: Ziyu Wu Date: Mon, 1 Aug 2022 00:46:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=8A=8A"=E4=BA=8B=E5=8A=A1"=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BA"=E4=BA=A4=E6=98=93"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...244\252\345\235\212fast sync\347\256\227\346\263\225.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/\344\273\245\345\244\252\345\235\212fast sync\347\256\227\346\263\225.md" "b/\344\273\245\345\244\252\345\235\212fast sync\347\256\227\346\263\225.md" index 851ef0e..5c0de8c 100644 --- "a/\344\273\245\345\244\252\345\235\212fast sync\347\256\227\346\263\225.md" +++ "b/\344\273\245\345\244\252\345\235\212fast sync\347\256\227\346\263\225.md" @@ -8,7 +8,7 @@ This PR aggregates a lot of small modifications to core, trie, eth and other pac The goal of the the fast sync algorithm is to exchange processing power for bandwidth usage. Instead of processing the entire block-chain one link at a time, and replay all transactions that ever happened in history, fast syncing downloads the transaction receipts along the blocks, and pulls an entire recent state database. This allows a fast synced node to still retain its status an an archive node containing all historical data for user queries (and thus not influence the network's health in general), but at the same time to reassemble a recent network state at a fraction of the time it would take full block processing. -快速同步算法的目标是用带宽换计算。 快速同步不是通过一个链接处理整个区块链,而是重放历史上发生的所有事务,快速同步会沿着这些块下载事务处理单据,然后拉取整个最近的状态数据库。 这允许快速同步的节点仍然保持其包含用于用户查询的所有历史数据的存档节点的状态(并且因此不会一般地影响网络的健康状况),对于最新的区块状态更改,会使用全量的区块处理方式。 +快速同步算法的目标是用带宽换计算。 快速同步不是通过一个链接处理整个区块链,而是重放历史上发生的所有交易,快速同步会沿着这些块下载交易处理单据,然后拉取整个最近的状态数据库。 这允许快速同步的节点仍然保持其包含用于用户查询的所有历史数据的存档节点的状态(并且因此不会一般地影响网络的健康状况),对于最新的区块状态更改,会使用全量的区块处理方式。 An outline of the fast sync algorithm would be: @@ -42,7 +42,7 @@ By downloading and verifying the entire header chain, we can guarantee with all ## 注意事项 Caveats The historical block-processing based synchronization mechanism has two (approximately similarly costing) bottlenecks: transaction processing and PoW verification. The baseline fast sync algorithm successfully circumvents the transaction processing, skipping the need to iterate over every single state the system ever was in. However, verifying the proof of work associated with each header is still a notably CPU intensive operation. -基于历史块处理的同步机制具有两个(近似相似成本)瓶颈:交易处理和PoW验证。 基线快速同步算法成功地绕开了事务处理,跳过了对系统曾经处于的每一个状态进行迭代的需要。但是,验证与每个头相关联的工作证明仍然是CPU密集型操作。 +基于历史块处理的同步机制具有两个(近似相似成本)瓶颈:交易处理和PoW验证。 基线快速同步算法成功地绕开了交易处理,跳过了对系统曾经处于的每一个状态进行迭代的需要。但是,验证与每个头相关联的工作证明仍然是CPU密集型操作。 However, we can notice an interesting phenomenon during header verification. With a negligible probability of error, we can still guarantee the validity of the chain, only by verifying every K-th header, instead of each and every one. By selecting a single header at random out of every K headers to verify, we guarantee the validity of an N-length chain with the probability of (1/K)^(N/K) (i.e. we have 1/K chance to spot a forgery in K blocks, a verification that's repeated N/K times). @@ -82,7 +82,7 @@ Blockchain protocols in general (i.e. Bitcoin, Ethereum, and the others) are sus Compared to the classical Sybil attack, fast sync provides such an attacker with an extra ability, that of feeding a node a view of the network that's not only different from the real network, but also that might go around the EVM mechanics. The Ethereum protocol only validates state root hashes by processing all the transactions against the previous state root. But by skipping the transaction processing, we cannot prove that the state root contained within the fast sync pivot point is valid or not, so as long as an attacker can maintain a fake blockchain that's on par with the real network, it could create an invalid view of the network's state. -与传统的女巫攻击相比,快速同步为攻击者提供了一种额外的能力,即为节点提供一个不仅与真实网络不同的网络视图,而且还可能绕过EVM机制。 以太坊协议只通过处理所有事务与以前的状态根来验证状态根散列。 但是通过跳过事务处理,我们无法证明快速同步pivot point中包含的state root是否有效,所以只要攻击者能够保持与真实网络相同的假区块链,就可以创造一个无效的网络状态视图。 +与传统的女巫攻击相比,快速同步为攻击者提供了一种额外的能力,即为节点提供一个不仅与真实网络不同的网络视图,而且还可能绕过EVM机制。 以太坊协议只通过处理所有交易与以前的状态根来验证状态根散列。 但是通过跳过交易处理,我们无法证明快速同步pivot point中包含的state root是否有效,所以只要攻击者能够保持与真实网络相同的假区块链,就可以创造一个无效的网络状态视图。 To avoid opening up nodes to this extra attacker ability, fast sync (beside being solely opt-in) will only ever run during an initial sync (i.e. when the node's own blockchain is empty). After a node managed to successfully sync with the network, fast sync is forever disabled. This way anybody can quickly catch up with the network, but after the node caught up, the extra attack vector is plugged in. This feature permits users to safely use the fast sync flag (--fast), without having to worry about potential state root attacks happening to them in the future. As an additional safety feature, if a fast sync fails close to or after the random pivot point, fast sync is disabled as a safety precaution and the node reverts to full, block-processing based synchronization. From 70fd149fda67759894ca4c8b5290c9dc4fba1bc5 Mon Sep 17 00:00:00 2001 From: Ziyu Wu Date: Mon, 1 Aug 2022 00:50:00 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=20pow=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=E7=AE=97=E6=B3=95.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...64\346\200\247\347\256\227\346\263\225.md" | 150 +++++++++++------- 1 file changed, 96 insertions(+), 54 deletions(-) diff --git "a/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" "b/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" index c0ea7ab..00a17b4 100644 --- "a/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" +++ "b/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" @@ -1,46 +1,53 @@ -##eth POW分析 +## eth POW 分析 + ### 共识引擎描述 -在CPU挖矿部分,CpuAgent的mine函数,执行挖矿操作的时候调用了self.engine.Seal函数。这里的engine是就是共识引擎。Seal为其中很重要的一个接口。它实现了nonce值的寻找和hash的计算。并且该函数是保证共识并且不能伪造的一个重要的函数。 -再PoW共识算法中,Seal函数实现了工作证明。该部分源码在consensus/ethhash下。 + +在 CPU 挖矿部分,CpuAgent 的 mine 函数,执行挖矿操作的时候调用了 self.engine.Seal 函数。这里的 engine 是就是共识引擎。Seal 为其中很重要的一个接口。它实现了 nonce 值的寻找和 hash 的计算。并且该函数是保证共识并且不能伪造的一个重要的函数。 +再 PoW 共识算法中,Seal 函数实现了工作证明。该部分源码在 consensus/ethhash 下。 + ### 共识引擎接口 + type Engine interface { - // 获取区块挖掘者, 即coinbase - Author(header *types.Header) (common.Address, error) +// 获取区块挖掘者, 即 coinbase +Author(header \*types.Header) (common.Address, error) + // VerifyHeader 用于校验区块头,通过共识规则来校验,验证区块可以在这里进行也科通通过VerifySeal方法 + VerifyHeader(chain ChainReader, header *types.Header, seal bool) error - // VerifyHeader 用于校验区块头,通过共识规则来校验,验证区块可以在这里进行也科通通过VerifySeal方法 - VerifyHeader(chain ChainReader, header *types.Header, seal bool) error + // VerifyHeaders与VerifyHeader相似,同时这个用于批量操作校验头。这个方法返回一个退出信号 + // 用于终止操作,用于异步校验。 + VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) - // VerifyHeaders与VerifyHeader相似,同时这个用于批量操作校验头。这个方法返回一个退出信号 - // 用于终止操作,用于异步校验。 - VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) + // VerifyUncles 用于校验叔块以符合共识引擎的规则 + VerifyUncles(chain ChainReader, block *types.Block) error - // VerifyUncles 用于校验叔块以符合共识引擎的规则 - VerifyUncles(chain ChainReader, block *types.Block) error + // VerifySeal根据共识算法的规则校验区块头 + VerifySeal(chain ChainReader, header *types.Header) error - // VerifySeal根据共识算法的规则校验区块头 - VerifySeal(chain ChainReader, header *types.Header) error + // Prepare 用于初始化区块头的共识字段根据共识引擎。这些改变都是内联执行的。 + Prepare(chain ChainReader, header *types.Header) error - // Prepare 用于初始化区块头的共识字段根据共识引擎。这些改变都是内联执行的。 - Prepare(chain ChainReader, header *types.Header) error + // Finalize 完成所有的状态修改,并最终组装成块。 + // 区块头和状态数据库在最终确认的时候可以被更新使之符合共识规则。 + Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, + uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) - // Finalize 完成所有的状态修改,并最终组装成块。 - // 区块头和状态数据库在最终确认的时候可以被更新使之符合共识规则。 - Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) + // Seal 根据输入区块打包生产一个新的区块 + Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) - // Seal 根据输入区块打包生产一个新的区块 - Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) + // CalcDifficulty 是难度调整算法,它返回新的区块的难度值。 + CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int - // CalcDifficulty 是难度调整算法,它返回新的区块的难度值。 - CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int + // APIs 返回由共识引擎提供的RPC APIs + APIs(chain ChainReader) []rpc.API - // APIs 返回由共识引擎提供的RPC APIs - APIs(chain ChainReader) []rpc.API } + ### ethhash 实现分析 + #### ethhash 结构体 + ``` type Ethash struct { config Config @@ -67,15 +74,19 @@ type Ethash struct { lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields } ``` -Ethhash是实现PoW的具体实现,由于要使用到大量的数据集,所有有两个指向lru的指针。并且通过threads控制挖矿线程数。并在测试模式或fake模式下,简单快速处理,使之快速得到结果。 -Athor方法获取了挖出这个块的矿工地址。 +Ethhash 是实现 PoW 的具体实现,由于要使用到大量的数据集,所有有两个指向 lru 的指针。并且通过 threads 控制挖矿线程数。并在测试模式或 fake 模式下,简单快速处理,使之快速得到结果。 + +Athor 方法获取了挖出这个块的矿工地址。 + ``` func (ethash *Ethash) Author(header *types.Header) (common.Address, error) { return header.Coinbase, nil } ``` -VerifyHeader 用于校验区块头部信息是否符合ethash共识引擎规则。 + +VerifyHeader 用于校验区块头部信息是否符合 ethash 共识引擎规则。 + ``` // VerifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum ethash engine. @@ -97,7 +108,9 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.He return ethash.verifyHeader(chain, header, parent, false, seal) } ``` -然后再看看verifyHeader的实现, + +然后再看看 verifyHeader 的实现, + ``` func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error { // 确保额外数据段具有合理的长度 @@ -163,7 +176,9 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * return nil } ``` -Ethash通过CalcDifficulty函数计算下一个区块难度,分别为不同阶段的难度创建了不同的难度计算方法,这里暂不展开描述 + +Ethash 通过 CalcDifficulty 函数计算下一个区块难度,分别为不同阶段的难度创建了不同的难度计算方法,这里暂不展开描述 + ``` func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { return CalcDifficulty(chain.Config(), time, parent) @@ -181,7 +196,9 @@ func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Heade } } ``` -VerifyHeaders和VerifyHeader类似,只是VerifyHeaders进行批量校验操作。创建多个goroutine用于执行校验操作,再创建一个goroutine用于赋值控制任务分配和结果获取。最后返回一个结果channel + +VerifyHeaders 和 VerifyHeader 类似,只是 VerifyHeaders 进行批量校验操作。创建多个 goroutine 用于执行校验操作,再创建一个 goroutine 用于赋值控制任务分配和结果获取。最后返回一个结果 channel + ``` func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { // If we're running a full engine faking, accept any input as valid @@ -248,7 +265,9 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type return abort, errorsOut } ``` -VerifyHeaders在校验单个区块头的时候使用了verifyHeaderWorker,该函数获取父区块后,调用verifyHeader进行校验 + +VerifyHeaders 在校验单个区块头的时候使用了 verifyHeaderWorker,该函数获取父区块后,调用 verifyHeader 进行校验 + ``` func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error { var parent *types.Header @@ -267,7 +286,9 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers [] } ``` -VerifyUncles用于叔块的校验。和校验区块头类似,叔块校验在ModeFullFake模式下直接返回校验成功。获取所有的叔块,然后遍历校验,校验失败即终止,或者校验完成返回。 + +VerifyUncles 用于叔块的校验。和校验区块头类似,叔块校验在 ModeFullFake 模式下直接返回校验成功。获取所有的叔块,然后遍历校验,校验失败即终止,或者校验完成返回。 + ``` func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { // If we're running a full engine faking, accept any input as valid @@ -319,7 +340,9 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo return nil } ``` -Prepare实现共识引擎的Prepare接口,用于填充区块头的难度字段,使之符合ethash协议。这个改变是在线的。 + +Prepare 实现共识引擎的 Prepare 接口,用于填充区块头的难度字段,使之符合 ethash 协议。这个改变是在线的。 + ``` func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error { parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) @@ -330,7 +353,9 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) return nil } ``` -Finalize实现共识引擎的Finalize接口,奖励挖到区块账户和叔块账户,并填充状态树的根的值。并返回新的区块。 + +Finalize 实现共识引擎的 Finalize 接口,奖励挖到区块账户和叔块账户,并填充状态树的根的值。并返回新的区块。 + ``` func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root @@ -365,11 +390,14 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header state.AddBalance(header.Coinbase, reward) } ``` -#### Seal函数实现分析 -在CPU挖矿部分,CpuAgent的mine函数,执行挖矿操作的时候调用了Seal函数。Seal函数尝试找出一个满足区块难度的nonce值。 -在ModeFake和ModeFullFake模式下,快速返回,并且直接将nonce值取0。 -在shared PoW模式下,使用shared的Seal函数。 -开启threads个goroutine进行挖矿(查找符合条件的nonce值)。 + +#### Seal 函数实现分析 + +在 CPU 挖矿部分,CpuAgent 的 mine 函数,执行挖矿操作的时候调用了 Seal 函数。Seal 函数尝试找出一个满足区块难度的 nonce 值。 +在 ModeFake 和 ModeFullFake 模式下,快速返回,并且直接将 nonce 值取 0。 +在 shared PoW 模式下,使用 shared 的 Seal 函数。 +开启 threads 个 goroutine 进行挖矿(查找符合条件的 nonce 值)。 + ``` // Seal implements consensus.Engine, attempting to find a nonce that satisfies // the block's difficulty requirements. @@ -433,13 +461,17 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop return result, nil } ``` -mine是真正的查找nonce值的函数,它不断遍历查找nonce值,并计算PoW值与目标值进行比较。 + +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越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。 + +这里 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) { // 从区块头中获取一些数据 @@ -502,7 +534,9 @@ search: runtime.KeepAlive(dataset) } ``` -上诉函数调用了hashimotoFull函数用来计算PoW的值。 + +上诉函数调用了 hashimotoFull 函数用来计算 PoW 的值。 + ``` func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) { lookup := func(index uint32) []uint32 { @@ -512,14 +546,17 @@ func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup) } ``` -hashimoto用于聚合数据以产生特定的后部的hash和nonce值。 + +hashimoto 用于聚合数据以产生特定的后部的 hash 和 nonce 值。 ![图片来源:https://blog.csdn.net/metal1/article/details/79682636](picture/pow_hashimoto.png) 简述该部分流程: -- 首先,hashimoto()函数将入参@hash和@nonce合并成一个40 bytes长的数组,取它的SHA-512哈希值取名seed,长度为64 bytes。 -- 然后,将seed[]转化成以uint32为元素的数组mix[],注意一个uint32数等于4 bytes,故而seed[]只能转化成16个uint32数,而mix[]数组长度32,所以此时mix[]数组前后各半是等值的。 -- 接着,lookup()函数登场。用一个循环,不断调用lookup()从外部数据集中取出uint32元素类型数组,向mix[]数组中混入未知的数据。循环的次数可用参数调节,目前设为64次。每次循环中,变化生成参数index,从而使得每次调用lookup()函数取出的数组都各不相同。这里混入数据的方式是一种类似向量“异或”的操作,来自于FNV算法。 -待混淆数据完成后,得到一个基本上面目全非的mix[],长度为32的uint32数组。这时,将其折叠(压缩)成一个长度缩小成原长1/4的uint32数组,折叠的操作方法还是来自FNV算法。 -- 最后,将折叠后的mix[]由长度为8的uint32型数组直接转化成一个长度32的byte数组,这就是返回值@digest;同时将之前的seed[]数组与digest合并再取一次SHA-256哈希值,得到的长度32的byte数组,即返回值@result。(转自https://blog.csdn.net/metal1/article/details/79682636) + +- 首先,hashimoto()函数将入参@hash 和@nonce 合并成一个 40 bytes 长的数组,取它的 SHA-512 哈希值取名 seed,长度为 64 bytes。 +- 然后,将 seed[]转化成以 uint32 为元素的数组 mix[],注意一个 uint32 数等于 4 bytes,故而 seed[]只能转化成 16 个 uint32 数,而 mix[]数组长度 32,所以此时 mix[]数组前后各半是等值的。 +- 接着,lookup()函数登场。用一个循环,不断调用 lookup()从外部数据集中取出 uint32 元素类型数组,向 mix[]数组中混入未知的数据。循环的次数可用参数调节,目前设为 64 次。每次循环中,变化生成参数 index,从而使得每次调用 lookup()函数取出的数组都各不相同。这里混入数据的方式是一种类似向量“异或”的操作,来自于 FNV 算法。 + 待混淆数据完成后,得到一个基本上面目全非的 mix[],长度为 32 的 uint32 数组。这时,将其折叠(压缩)成一个长度缩小成原长 1/4 的 uint32 数组,折叠的操作方法还是来自 FNV 算法。 +- 最后,将折叠后的 mix[]由长度为 8 的 uint32 型数组直接转化成一个长度 32 的 byte 数组,这就是返回值@digest;同时将之前的 seed[]数组与 digest 合并再取一次 SHA-256 哈希值,得到的长度 32 的 byte 数组,即返回值@result。(转自https://blog.csdn.net/metal1/article/details/79682636) + ``` func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) { // 计算理论行数 @@ -561,8 +598,11 @@ func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) return digest, crypto.Keccak256(append(seed, digest...)) } ``` -#### VerifySeal函数实现分析 -VerifySeal用于校验区块的nonce值是否满足PoW难度要求。 + +#### VerifySeal 函数实现分析 + +VerifySeal 用于校验区块的 nonce 值是否满足 PoW 难度要求。 + ``` func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error { // ModeFake、ModeFullFake模式不校验,直接验证通过。 @@ -605,7 +645,9 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head return nil } ``` -hashimotoLight和hashimotoFull功能类似,只是hashimotoLight使用了占用内存更小的缓存。 + +hashimotoLight 和 hashimotoFull 功能类似,只是 hashimotoLight 使用了占用内存更小的缓存。 + ``` func hashimotoLight(size uint64, cache []uint32, hash []byte, nonce uint64) ([]byte, []byte) { keccak512 := makeHasher(sha3.NewKeccak512()) From 80dea803a2918166ec4b1a6bf9f5af771f41e30c Mon Sep 17 00:00:00 2001 From: Ziyu Wu Date: Mon, 1 Aug 2022 00:54:17 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=20pow=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=E7=AE=97=E6=B3=95.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...64\346\200\247\347\256\227\346\263\225.md" | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git "a/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" "b/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" index 00a17b4..0bfdc53 100644 --- "a/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" +++ "b/pow\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.md" @@ -7,6 +7,7 @@ ### 共识引擎接口 +```go type Engine interface { // 获取区块挖掘者, 即 coinbase Author(header \*types.Header) (common.Address, error) @@ -43,12 +44,13 @@ Author(header \*types.Header) (common.Address, error) APIs(chain ChainReader) []rpc.API } +``` ### ethhash 实现分析 #### ethhash 结构体 -``` +```go type Ethash struct { config Config @@ -79,7 +81,7 @@ Ethhash 是实现 PoW 的具体实现,由于要使用到大量的数据集, Athor 方法获取了挖出这个块的矿工地址。 -``` +```go func (ethash *Ethash) Author(header *types.Header) (common.Address, error) { return header.Coinbase, nil } @@ -87,7 +89,7 @@ func (ethash *Ethash) Author(header *types.Header) (common.Address, error) { VerifyHeader 用于校验区块头部信息是否符合 ethash 共识引擎规则。 -``` +```go // VerifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum ethash engine. func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { @@ -109,9 +111,9 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.He } ``` -然后再看看 verifyHeader 的实现, +然后再看看 verifyHeader 的实现: -``` +```go func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error { // 确保额外数据段具有合理的长度 if uint64(len(header.Extra)) > params.MaximumExtraDataSize { @@ -179,7 +181,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * Ethash 通过 CalcDifficulty 函数计算下一个区块难度,分别为不同阶段的难度创建了不同的难度计算方法,这里暂不展开描述 -``` +```go func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { return CalcDifficulty(chain.Config(), time, parent) } @@ -199,7 +201,7 @@ func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Heade VerifyHeaders 和 VerifyHeader 类似,只是 VerifyHeaders 进行批量校验操作。创建多个 goroutine 用于执行校验操作,再创建一个 goroutine 用于赋值控制任务分配和结果获取。最后返回一个结果 channel -``` +```go func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { // If we're running a full engine faking, accept any input as valid if ethash.config.PowMode == ModeFullFake || len(headers) == 0 { @@ -268,7 +270,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type VerifyHeaders 在校验单个区块头的时候使用了 verifyHeaderWorker,该函数获取父区块后,调用 verifyHeader 进行校验 -``` +```go func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error { var parent *types.Header if index == 0 { @@ -289,7 +291,7 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers [] VerifyUncles 用于叔块的校验。和校验区块头类似,叔块校验在 ModeFullFake 模式下直接返回校验成功。获取所有的叔块,然后遍历校验,校验失败即终止,或者校验完成返回。 -``` +```go func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { // If we're running a full engine faking, accept any input as valid if ethash.config.PowMode == ModeFullFake { @@ -343,7 +345,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo Prepare 实现共识引擎的 Prepare 接口,用于填充区块头的难度字段,使之符合 ethash 协议。这个改变是在线的。 -``` +```go func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error { parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { @@ -356,7 +358,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) Finalize 实现共识引擎的 Finalize 接口,奖励挖到区块账户和叔块账户,并填充状态树的根的值。并返回新的区块。 -``` +```go func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root accumulateRewards(chain.Config(), state, header, uncles) @@ -398,7 +400,7 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header 在 shared PoW 模式下,使用 shared 的 Seal 函数。 开启 threads 个 goroutine 进行挖矿(查找符合条件的 nonce 值)。 -``` +```go // Seal implements consensus.Engine, attempting to find a nonce that satisfies // the block's difficulty requirements. func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { @@ -465,14 +467,12 @@ func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop mine 是真正的查找 nonce 值的函数,它不断遍历查找 nonce 值,并计算 PoW 值与目标值进行比较。 其原理可以简述为下: -``` - RAND(h, n) <= M / d -``` +```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 越大则可取范围越小。所以当难度值增加时,挖出区块的难度也在增加。 -``` +```go func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { // 从区块头中获取一些数据 var ( @@ -537,7 +537,7 @@ search: 上诉函数调用了 hashimotoFull 函数用来计算 PoW 的值。 -``` +```go func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) { lookup := func(index uint32) []uint32 { offset := index * hashWords @@ -557,7 +557,7 @@ hashimoto 用于聚合数据以产生特定的后部的 hash 和 nonce 值。 待混淆数据完成后,得到一个基本上面目全非的 mix[],长度为 32 的 uint32 数组。这时,将其折叠(压缩)成一个长度缩小成原长 1/4 的 uint32 数组,折叠的操作方法还是来自 FNV 算法。 - 最后,将折叠后的 mix[]由长度为 8 的 uint32 型数组直接转化成一个长度 32 的 byte 数组,这就是返回值@digest;同时将之前的 seed[]数组与 digest 合并再取一次 SHA-256 哈希值,得到的长度 32 的 byte 数组,即返回值@result。(转自https://blog.csdn.net/metal1/article/details/79682636) -``` +```go func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) { // 计算理论行数 rows := uint32(size / mixBytes) @@ -603,7 +603,7 @@ func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) VerifySeal 用于校验区块的 nonce 值是否满足 PoW 难度要求。 -``` +```go func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error { // ModeFake、ModeFullFake模式不校验,直接验证通过。 if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake { @@ -648,7 +648,7 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head hashimotoLight 和 hashimotoFull 功能类似,只是 hashimotoLight 使用了占用内存更小的缓存。 -``` +```go func hashimotoLight(size uint64, cache []uint32, hash []byte, nonce uint64) ([]byte, []byte) { keccak512 := makeHasher(sha3.NewKeccak512())