0%

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

首先是入口函数 Work

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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函数,计算逻辑都在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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必须小于这个目标值。可以看出难度越大,求解就越慢

下面是实例运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 // 进程挂起
}

源码在 https://github.com/pefish/go-blkchain-pow

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

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




微信关注我,及时接收最新技术文章