以太坊源码阅读5——POW源码分析

介绍

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
}

共识引擎接口的实现

Author

实现了consensus.Engine 该方法获取了挖出这个块的矿工地址。

func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {return header.Coinbase, nil
}

VerifyHeader

检查一个头是否符合以太坊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-1if header.GasLimit > params.MaxGasLimit {return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)}// 校验 gasUsed <= gasLimitif 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}// 验证块号是父级的+1if 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

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, 0checked = 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实现共识引擎的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实现共识引擎的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 rootaccumulateRewards(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/8r.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)
}

Seal (设置挖矿要用的nonce)

seal

在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,只需立即返回一个0if 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 directsabort := make(chan struct{})ethash.lock.Lock()// 使用多线程去寻找noncethreads := ethash.threadsif 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 sealerif ethash.remote != nil {ethash.remote.workCh <- &sealTask{block: block, results: results}}var (pend   sync.WaitGrouplocals = 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.Blockselect {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

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     = seedpowBuffer = 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 searchdefault:// 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 rateattempts++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

上述函数调用了hashimotoFull函数用来计算PoW的值。

hashimoto用于聚合数据以产生特定的后部的hash和nonce值。

func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {lookup := func(index uint32) []uint32 {offset := index * hashWordsreturn dataset[offset : offset+hashWords]}return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
}

之后又调用了hashimoto函数:

简述该部分流程:

  • 首先,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)
//以特定的头哈希和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字节的seedseed := 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)]) % rowsfor 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...))
}

ethHash分析

  • POW接口
// 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,就是哈希值。扔骰子的过程,就是哈希碰撞

  • Ethash 结构体

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 hashrateremote   *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.
}

以太坊源码阅读5——POW源码分析相关推荐

  1. 封装成jar包_通用源码阅读指导mybatis源码详解:io包

    io包 io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作. 说到输入/输出,首先想到的就是对磁盘文件的读写.在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的 ...

  2. 如何在以太坊浏览器上上传发布合约源码(合并上传单文件方式)

    区块链上的defi业务,在合约上线后为了使得业务公开透明化,往往需要上传合约代码.本文介绍一种从合约代码合并到成功发布的一条切实可行的发布路线. 〇.准备 1. 待发布的,能成功编的合约代码 2. R ...

  3. 以太坊ETH-智能合约开发-solidity源码分析-truffle进阶

    0. 背景 上一篇文章我们从合约编写.编译.部署.交互等几个方面介绍了truffle的大致用法. 本篇主要继续深入地介绍truffle的高级用法 + 合约源码分析 1. 将合约部署到测试网Ropste ...

  4. 以太坊源码深入分析(10)-- 以太坊Bloom过滤器实现原理及应用场景分析

    上一节分析reciept产生过程的时候提到:reciept会为日志数据生成一个Bloom过滤器,那Bloom过滤器是用来干嘛的呢?有什么用呢? 一,Bloom过滤器的数据结构和reciept创建Blo ...

  5. ZooKeeper源码阅读心得分享+源码基本结构+源码环境搭建

    首发CSDN:徐同学呀,原创不易,转载请注明源链接.我是徐同学,用心输出高质量文章,希望对你有所帮助. 一.心得分享 如何阅读ZooKeeper源码?从哪里开始阅读?最近把ZooKeeper源码看了个 ...

  6. 以太坊Bloom过滤器实现原理及应用场景分析

    一,Bloom过滤器的数据结构和reciept创建Bloom的过程 type Bloom [BloomByteLength]byte BloomByteLength = 256 Bloom 就是一个2 ...

  7. MongoDB源码阅读之ReplSet源码分析

    1. ReplSet源码结构 rs_config.h replSet间同步设置的工具类 rs_member.h 心跳检测类和replSet成员状态的定义 rs_sync.h 同步数据类 rs.h 定义 ...

  8. mybatis源码阅读系列之源码下载

    一.百度输入mybatis 二.选择Git 项目 三.选择 mybatis 四.拉到页面最下面,选择最新 下载jar包 下载源码

  9. 以太坊地址算法php,以太坊ETH源码分析(1):地址生成过程

    一.生成一个以太坊钱包地址 通过以太坊命令行客户端geth可以很简单的获得一个以太坊地址,如下: ~/go/src/github.com/ethereum/go-ethereum/build/bin$ ...

最新文章

  1. 知乎上看到一个关注软件测试的问答,太直接了
  2. 打开WORD2016提示 您正在试图打开带有宏的 。。。。 解决办法
  3. 使用flush-logs命令重新生成MySQL的相关日志文件
  4. (自定义组件)通用- X轴横向:溢出滚动 (含代码)- 案例篇
  5. ECCV 2020 论文大盘点 - OCR 篇
  6. 计算机协会小游戏,网页闯关小游戏闯关记录(一)ISA TEST
  7. jQuery实现滚动时动态加载页面内容
  8. 推荐系统实践之:召回算法和工程协同优化的若干经验
  9. labelImg(pyqt4 )出现错误(segmentation fault(core dumped) )
  10. 自动服务器批量装机,PXE高效批量网络装机
  11. Linux/Unix 多线程通信
  12. fatal error C1010
  13. Mpass – PHP做Socket服务的解决方案
  14. VB中关于Name属性和Caption属性
  15. 基于AD5933 生物复阻抗
  16. GNN学习笔记(三) Graph Neural Network概述
  17. ubuntu上传代码文件到github
  18. Python爬虫报错 ImportError: cannot import name Morsel
  19. 金誉半导体:MOS管耗尽型和增强型是什么意思?
  20. python socket 域名_Python网络编程中的套接字名和DNS解析。

热门文章

  1. 蓝牙耳机什么牌子好?人气特高的蓝牙耳机分享
  2. 【浅谈】数学与生活(二)
  3. 大话西游服务端启动注意事项
  4. Appfuse搭建过程(下源码不需要maven,lib直接就在项目里(否则痛苦死!))
  5. 剑指offer-18
  6. arch_linux 虚拟机安装
  7. 电梯媒体卷起来,行业利润打下去
  8. Qt工作笔记(常用代码段)
  9. XSS bypass
  10. 关于晶体管所制作的门电路