-
Notifications
You must be signed in to change notification settings - Fork 458
/
sealer.go
187 lines (179 loc) · 5.56 KB
/
sealer.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
package progpow
import (
crand "crypto/rand"
"errors"
"math"
"math/big"
"math/rand"
"runtime"
"runtime/debug"
"sync"
"github.com/dominant-strategies/go-quai/common"
"github.com/dominant-strategies/go-quai/core/types"
"github.com/dominant-strategies/go-quai/log"
"github.com/dominant-strategies/go-quai/params"
"modernc.org/mathutil"
)
const (
// staleThreshold is the maximum depth of the acceptable stale but valid progpow solution.
staleThreshold = 7
mantBits = 64
)
var (
errNoMiningWork = errors.New("no mining work available yet")
errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
)
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
// the header's difficulty requirements.
func (progpow *Progpow) Seal(header *types.WorkObject, results chan<- *types.WorkObject, stop <-chan struct{}) error {
// If we're running a fake PoW, simply return a 0 nonce immediately
if progpow.config.PowMode == ModeFake || progpow.config.PowMode == ModeFullFake {
header.WorkObjectHeader().SetNonce(types.BlockNonce{})
select {
case results <- header:
default:
progpow.logger.WithFields(log.Fields{
"mode": "fake",
"sealhash": header.SealHash(),
}).Warn("Sealing result is not read by miner")
}
return nil
}
// If we're running a shared PoW, delegate sealing to it
if progpow.shared != nil {
return progpow.shared.Seal(header, results, stop)
}
// Create a runner and the multiple search threads it directs
abort := make(chan struct{})
progpow.lock.Lock()
threads := progpow.threads
if progpow.rand == nil {
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
progpow.lock.Unlock()
return err
}
progpow.rand = rand.New(rand.NewSource(seed.Int64()))
}
progpow.lock.Unlock()
if threads == 0 {
threads = runtime.NumCPU()
}
if threads < 0 {
threads = 0 // Allows disabling local mining without extra logic around local/remote
}
var (
pend sync.WaitGroup
locals = make(chan *types.WorkObject)
)
for i := 0; i < threads; i++ {
pend.Add(1)
go func(id int, nonce uint64) {
defer func() {
if r := recover(); r != nil {
progpow.logger.WithFields(log.Fields{
"error": r,
"stacktrace": string(debug.Stack()),
}).Error("Go-Quai Panicked")
}
}()
defer pend.Done()
progpow.mine(header, id, nonce, abort, locals)
}(i, uint64(progpow.rand.Int63()))
}
// Wait until sealing is terminated or a nonce is found
go func() {
defer func() {
if r := recover(); r != nil {
progpow.logger.WithFields(log.Fields{
"error": r,
"stacktrace": string(debug.Stack()),
}).Error("Go-Quai Panicked")
}
}()
var result *types.WorkObject
select {
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:
progpow.logger.WithFields(log.Fields{
"mode": "local",
"sealhash": header.SealHash(),
}).Warn("Sealing result is not read by miner")
}
close(abort)
case <-progpow.update:
// Thread count was changed on user request, restart
close(abort)
if err := progpow.Seal(header, results, stop); err != nil {
progpow.logger.WithField("err", err).Error("Failed to restart sealing after update")
}
}
// Wait for all miners to terminate and return the block
pend.Wait()
}()
return nil
}
// mine is the actual proof-of-work miner that searches for a nonce starting from
// seed that results in correct final block difficulty.
func (progpow *Progpow) mine(header *types.WorkObject, id int, seed uint64, abort chan struct{}, found chan *types.WorkObject) {
// Extract some data from the header
diff := new(big.Int).Set(header.Difficulty())
c, _ := mathutil.BinaryLog(diff, mantBits)
if c <= params.WorkSharesThresholdDiff {
return
}
workShareThreshold := c - params.WorkSharesThresholdDiff
workShareDiff := new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(workShareThreshold)), nil)
var (
target = new(big.Int).Div(big2e256, workShareDiff)
nodeCtx = progpow.config.NodeLocation.Context()
)
// Start generating random nonces until we abort or find a good one
var (
attempts = int64(0)
nonce = seed
)
search:
for {
select {
case <-abort:
// Mining terminated, update stats and abort
break search
default:
// We don't have to update hash rate on every nonce, so update after after 2^X nonces
attempts++
if (attempts % (1 << 15)) == 0 {
attempts = 0
}
powLight := func(size uint64, cache []uint32, hash []byte, nonce uint64, blockNumber uint64) ([]byte, []byte) {
ethashCache := progpow.cache(blockNumber)
if ethashCache.cDag == nil {
cDag := make([]uint32, progpowCacheWords)
generateCDag(cDag, ethashCache.cache, blockNumber/epochLength, progpow.logger)
ethashCache.cDag = cDag
}
return progpowLight(size, cache, hash, nonce, blockNumber, ethashCache.cDag)
}
cache := progpow.cache(header.NumberU64(nodeCtx))
size := datasetSize(header.NumberU64(nodeCtx))
// Compute the PoW value of this nonce
digest, result := powLight(size, cache.cache, header.SealHash().Bytes(), nonce, header.NumberU64(common.ZONE_CTX))
if new(big.Int).SetBytes(result).Cmp(target) <= 0 {
// Correct nonce found, create a new header with it
header = types.CopyWorkObject(header)
header.WorkObjectHeader().SetNonce(types.EncodeNonce(nonce))
hashBytes := common.BytesToHash(digest)
header.SetMixHash(hashBytes)
found <- header
break search
}
nonce++
}
}
}