-
Notifications
You must be signed in to change notification settings - Fork 0
/
pow.go
221 lines (194 loc) · 5.9 KB
/
pow.go
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package blc
import (
"context"
"crypto/sha256"
"errors"
log "github.com/treeforest/logger"
"math"
"math/big"
"runtime"
"strconv"
"sync"
"time"
)
type ProofOfWork struct {
block *Block // 目标区块
target *big.Int // 难度值(哈希长度)
}
func NewProofOfWork(block *Block) *ProofOfWork {
//target := big.NewInt(1)
//// 左移256-Bits, 计算出的哈希满足小于target即挖矿成功
//target = target.Lsh(target, 256-block.Bits)
target := UnCompact(block.Bits)
return &ProofOfWork{block: block, target: target}
}
// Mining 挖矿
func (pow *ProofOfWork) Mining(ctx context.Context) ([32]byte, uint64, bool) {
// 组装挖矿结构 |preHash|height|timestamp|merkelRoot|targetBit|nonce|
data := pow.block.MarshalHeaderWithoutNonceAndHash()
wg := sync.WaitGroup{}
c, cancel := context.WithCancel(ctx)
nonceCh := make(chan uint64, 1)
done := make(chan struct{}, 1)
now := time.Now()
// 开n个协程同时挖矿
n := runtime.NumCPU()
wg.Add(n)
for i := uint64(0); i < uint64(n); i++ {
begin := math.MaxUint64 / uint64(n) * i
end := math.MaxUint64 / uint64(n) * (i + 1)
go pow.mining(c, &wg, begin, end, data, nonceCh)
}
go func() {
wg.Wait()
done <- struct{}{}
}()
for {
select {
case <-ctx.Done():
// 取消挖矿
cancel()
<-done
log.Debug("cancel mining")
return [32]byte{}, 0, false
case nonce := <-nonceCh:
sub := time.Now().Sub(now)
log.Debugf("mining time used: %fs(%dms)", sub.Seconds(), sub.Milliseconds())
cancel() // 退出协程
hash := sha256.Sum256(append(data, []byte(strconv.FormatUint(nonce, 10))...))
return hash, nonce, true
case <-done:
// 需要进一步修改coinbase data,然后再次挖矿
cancel() // 此时调用只是为了释放cancel函数
return [32]byte{}, 0, false
}
}
}
func (pow *ProofOfWork) mining(ctx context.Context, wg *sync.WaitGroup, begin, end uint64, data []byte, nonceChan chan uint64) {
defer wg.Done()
var hash [32]byte // 计算出的区块哈希值
var hashInt big.Int // 哈希对应的大整数
nonce := begin
for {
select {
case <-ctx.Done():
return
default:
hash = sha256.Sum256(append(data, []byte(strconv.FormatUint(nonce, 10))...))
hashInt.SetBytes(hash[:])
if pow.target.Cmp(&hashInt) == 1 {
nonceChan <- nonce // 挖到区块
return
}
nonce++
if nonce == end {
// 当前组合的最大值都没能挖出区块
return
}
}
}
}
const (
// DifficultyAdjustmentInterval 难度值调整区块间隔
DifficultyAdjustmentInterval = uint64(2016)
// PowTargetTimespan 预定出2100个块规定的时间(每个出块时间大约为10分钟)
PowTargetTimespan = 60 * 10 * 2016
)
var (
// PowLimit 最低难度值
PowLimit = UnCompact(uint32(0x1d00ffff))
)
// GetWorkRequired 获取指定区块高度的目标难度值
func (chain *BlockChain) GetWorkRequired(preBlock, block *Block) (uint32, error) {
if preBlock == nil {
if block == nil {
return 0, errors.New("preBlock and block is nil")
}
if block.Height == 0 {
return 0x1d00ffff, nil
}
return 0, errors.New("illegal block")
}
if preBlock.Height != block.Height-1 {
return 0, errors.New("illegal preBlock")
}
if block.Height%DifficultyAdjustmentInterval != 0 {
// 不需要难度调整,使用上一个区块的难度值
return preBlock.Bits, nil
}
// 找到最近2016个区块的首个区块
heightFirst := preBlock.Height - (DifficultyAdjustmentInterval - 1)
firstBlock, _ := chain.GetAncestor(heightFirst)
// 重新计算难度值
return chain.CalculateNextWorkRequired(preBlock, firstBlock), nil
}
// GetNextWorkRequired 计算下一个区块的难度目标值,规定10分钟出一个块
func (chain *BlockChain) GetNextWorkRequired() uint32 {
lastBlock := chain.GetLatestBlock()
if (lastBlock.Height+1)%DifficultyAdjustmentInterval != 0 {
// 不需要难度调整,使用上一个区块的难度值
return lastBlock.Bits
}
// 找到最近2016个区块的首个区块
heightFirst := lastBlock.Height - (DifficultyAdjustmentInterval - 1)
firstBlock, _ := chain.GetAncestor(heightFirst)
// 重新计算难度值
return chain.CalculateNextWorkRequired(lastBlock, firstBlock)
}
// CalculateNextWorkRequired 计算难度值
func (chain *BlockChain) CalculateNextWorkRequired(latest *Block, first *Block) uint32 {
// 如果最近2016个区块生成时间小于1/4,就按照1/4来计算,大于4倍,就按照4倍来计算,以防止出现难度偏差过大的现象
actualTimespan := latest.GetBlockTime() - first.GetBlockTime()
if actualTimespan < PowTargetTimespan/4 {
actualTimespan = PowTargetTimespan / 4
}
if actualTimespan > PowTargetTimespan*4 {
actualTimespan = PowTargetTimespan * 4
}
// 调整难度值
newTarget := UnCompact(latest.Bits)
newTarget.Mul(newTarget, big.NewInt(actualTimespan))
newTarget.Div(newTarget, big.NewInt(PowTargetTimespan))
// target 越大,挖矿难度越低。这里要求最低难度不能大于 PowLimit
if newTarget.Cmp(PowLimit) == 1 {
newTarget = PowLimit
}
return Compact(newTarget)
}
// UnCompact 对目标值进行解压缩,返回对应的难度值
func UnCompact(bits uint32) *big.Int {
high := bits & 0xFF000000 >> 24 // 指数
low := bits & 0x00FFFFFF // 系数
x := big.NewInt(int64(low))
y := big.NewInt(0).Lsh(big.NewInt(1), uint(8*(high-3)))
target := big.NewInt(0).Mul(x, y)
return target
}
// Compact 对难度值进行压缩,返回对应的目标值
func Compact(target *big.Int) uint32 {
bs := target.Bytes()
exponent := uint(0) // 指数
for i := len(bs) - 1; i > 0; i-- {
if bs[i] == 0x00 {
exponent += 8
continue
}
v := bs[i]
for {
// 最低位是否为0,即 0^1=0
if v^0x1 == 0 {
exponent++
v = v >> 1
continue
}
break
}
break
}
y := big.NewInt(0).Lsh(big.NewInt(1), exponent)
x := target.Div(target, y)
high := uint32((exponent/8 + 3) << 24 & 0xFF000000) // 高位
low := uint32(x.Int64() & 0x00FFFFFF)
bits := high | low
return bits
}