-
Notifications
You must be signed in to change notification settings - Fork 456
/
sealer.go
188 lines (179 loc) · 5.41 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
188
package blake3pow
import (
crand "crypto/rand"
"errors"
"math"
"math/big"
"math/rand"
"runtime"
"runtime/debug"
"sync"
"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 blake3pow 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 (blake3pow *Blake3pow) 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 blake3pow.config.PowMode == ModeFake || blake3pow.config.PowMode == ModeFullFake {
header.WorkObjectHeader().SetNonce(types.BlockNonce{})
select {
case results <- header:
default:
blake3pow.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 blake3pow.shared != nil {
return blake3pow.shared.Seal(header, results, stop)
}
// Create a runner and the multiple search threads it directs
abort := make(chan struct{})
blake3pow.lock.Lock()
threads := blake3pow.threads
if blake3pow.rand == nil {
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
blake3pow.lock.Unlock()
return err
}
blake3pow.rand = rand.New(rand.NewSource(seed.Int64()))
}
blake3pow.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 {
blake3pow.logger.WithFields(log.Fields{
"error": r,
"stacktrace": string(debug.Stack()),
}).Error("Go-Quai Panicked")
}
}()
defer pend.Done()
blake3pow.mine(header, id, nonce, abort, locals)
}(i, uint64(blake3pow.rand.Int63()))
}
// Wait until sealing is terminated or a nonce is found
go func() {
defer func() {
if r := recover(); r != nil {
blake3pow.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:
blake3pow.logger.WithFields(log.Fields{
"mode": "local",
"sealhash": header.SealHash(),
}).Warn("Sealing result is not read by miner")
}
close(abort)
case <-blake3pow.update:
// Thread count was changed on user request, restart
close(abort)
if err := blake3pow.Seal(header, results, stop); err != nil {
blake3pow.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 header difficulty.
func (blake3pow *Blake3pow) 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)
)
// Start generating random nonces until we abort or find a good one
var (
attempts = int64(0)
nonce = seed
powBuffer = new(big.Int)
)
blake3pow.logger.WithField("seed", seed).Trace("Started blake3pow search for new nonces")
search:
for {
select {
case <-abort:
// Mining terminated, update stats and abort
blake3pow.logger.WithField("attempts", nonce-seed).Trace("Blake3pow nonce search aborted")
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
}
// Compute the PoW value of this nonce
header = types.CopyWorkObject(header)
header.WorkObjectHeader().SetNonce(types.EncodeNonce(nonce))
hash := header.Hash().Bytes()
if powBuffer.SetBytes(hash).Cmp(target) <= 0 {
// Correct nonce found, create a new header with it
// Seal and return a block (if still needed)
select {
case found <- header:
blake3pow.logger.WithFields(log.Fields{
"attempts": nonce - seed,
"nonce": nonce,
}).Trace("Blake3pow nonce found and reported")
case <-abort:
blake3pow.logger.WithFields(log.Fields{
"attempts": nonce - seed,
"nonce": nonce,
}).Trace("Blake3pow nonce found but discarded")
}
break search
}
nonce++
}
}
}