《7天以太坊源码解读》— 第五天,实战写出简单pow挖矿算法

746 阅读2分钟

首先是入口函数 Work

func (proofOfWorkManager *ProofOfWorkManager) Work(block *Block, result chan *Result) error {
	// 检查设置的线程数量
	if proofOfWorkManager.threads < 0 {
		return errors.New(`threads set error`)
	}
	abort := make(chan string)
	// 多开线程开始计算
	for i := 0; i < proofOfWorkManager.threads; i++ {
		initNonce := uint64(proofOfWorkManager.rand.Int63())
		proofOfWorkManager.logger.DebugF("Thread %d: InitNonce: %d", i, initNonce)
		go func(id int, nonce uint64) {
			proofOfWorkManager.mine(block, id, nonce, abort, result)
		}(i, initNonce)
	}
	return nil
}

然后是mine函数,计算逻辑都在这里

func (proofOfWorkManager *ProofOfWorkManager) mine(block *Block, threadId int, nonce uint64, abort chan string, found chan *Result) {
	var (
		hash = proofOfWorkManager.hashHeader(block.Header)
		// target = 2^256 / Difficulty
		target   = new(big.Int).Div(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)), block.Header.Difficulty)
		attempts = int64(0)
	)
	proofOfWorkManager.logger.DebugF("Thread %d: Started search for new nonces", threadId)
search:
	for {
		select {
		case <-abort: // 如果收到abort,则退出求解
			proofOfWorkManager.logger.DebugF(`Thread %d: Abort`, threadId)
			break search
		default:
			attempts++
			// 计算hash
			hash := sha256.Sum256(bytes.Join([][]byte{
				hash,
				util.MustToBuffer(nonce),
			}, []byte{}))
			realHash := hash[:]
			if attempts%1000000 == 0 {
				proofOfWorkManager.logger.DebugF(`Thread %d: attempted %d, current hash: %s`, threadId, attempts, util.BufferToHexString(realHash, true))
			}
			if new(big.Int).SetBytes(realHash).Cmp(target) <= 0 {
				proofOfWorkManager.logger.DebugF(`Thread %d: Found`, threadId)
				// 找到了正确解
				select {
				case found <- &Result{ // 这里要等待别人接收结果
					Nonce:      nonce,
					AttemptNum: attempts,
					Hash: realHash,
				}:
					proofOfWorkManager.logger.DebugF("Thread %d: Nonce found and reported", threadId)
				case <-abort: // 可能两个线程同时算出来了,这里的效果就是丢弃多余的线程求出的解
					proofOfWorkManager.logger.DebugF("Thread %d: Nonce found but discarded", threadId)
				}
				go func() { // 开启新线程通知终止计算
					proofOfWorkManager.logger.DebugF("Thread %d: Stop all calc", threadId)
					close(abort) // 所有监听abort通道的都触发
				}()
			}
			nonce++
		}
	}
}

这里目标值解释一下,假设难度值是2的16次方,则目标值是 2的256次方/2的16次方,就是 2的240 次方,也就是一个8字节的数值二进制的前面两个字节全部是0,求出来的hash必须小于这个目标值。可以看出难度越大,求解就越慢

下面是实例运行

func main() {
	forever := make(chan string)

	go_logger.Logger.Init(`test`, ``) // 初始化日志打印
	powManager, err := pow.NewProofOfWorkManager(go_logger.Logger, pow.WithThreads(5)) // 设置5个线程
	if err != nil {
		panic(err)
	}
	result := make(chan *pow.Result)
	err = powManager.Work(&pow.Block{
		Header: &pow.Header{
			Difficulty: new(big.Int).Exp(big.NewInt(2), big.NewInt(8 * 3), big.NewInt(0)), // 难度初始化,应当是根据出块时间自动调整的,这里先设置固定值
		},
	}, result)
	if err != nil {
		panic(err)
	}
	select {
	case result_ := <- result: // 监听结果
		fmt.Println(result_.Nonce, result_.AttemptNum, util.BufferToHexString(result_.Hash, true))
	}


	<- forever // 进程挂起
}

源码在 github.com/pefish/go-b…

还有一些功能未实现,比如自动调整难度值。欢迎一起参与编码

文章仅供参考,若有错误,还望不吝指正 !!!