forked from cruzbit/cruzbit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathminer.go
More file actions
320 lines (280 loc) · 8.13 KB
/
Copy pathminer.go
File metadata and controls
320 lines (280 loc) · 8.13 KB
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Copyright 2019 cruzbit developers
// Use of this source code is governed by a MIT-style license that can be found in the LICENSE file.
package cruzbit
import (
"log"
"math/big"
"math/rand"
"sync"
"time"
"golang.org/x/crypto/ed25519"
)
// Miner tries to mine a new tip block.
type Miner struct {
pubKeys []ed25519.PublicKey // receipients of any block rewards we mine
memo string // memo for coinbase of any blocks we mine
blockStore BlockStorage
txQueue TransactionQueue
ledger Ledger
processor *Processor
num int
keyIndex int
hashUpdateChan chan int64
shutdownChan chan struct{}
wg sync.WaitGroup
}
// HashrateMonitor collects hash counts from all miners in order to monitor and display the aggregate hashrate.
type HashrateMonitor struct {
hashUpdateChan chan int64
shutdownChan chan struct{}
wg sync.WaitGroup
}
// NewMiner returns a new Miner instance.
func NewMiner(pubKeys []ed25519.PublicKey, memo string,
blockStore BlockStorage, txQueue TransactionQueue,
ledger Ledger, processor *Processor,
hashUpdateChan chan int64, num int) *Miner {
return &Miner{
pubKeys: pubKeys,
memo: memo,
blockStore: blockStore,
txQueue: txQueue,
ledger: ledger,
processor: processor,
num: num,
keyIndex: rand.Intn(len(pubKeys)),
hashUpdateChan: hashUpdateChan,
shutdownChan: make(chan struct{}),
}
}
// NewHashrateMonitor returns a new HashrateMonitor instance.
func NewHashrateMonitor(hashUpdateChan chan int64) *HashrateMonitor {
return &HashrateMonitor{
hashUpdateChan: hashUpdateChan,
shutdownChan: make(chan struct{}),
}
}
// Run executes the miner's main loop in its own goroutine.
func (m *Miner) Run() {
m.wg.Add(1)
go m.run()
}
func (m *Miner) run() {
defer m.wg.Done()
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// don't start mining until we think we're synced.
// we're just wasting time and slowing down the sync otherwise
ibd, _, err := IsInitialBlockDownload(m.ledger, m.blockStore)
if err != nil {
panic(err)
}
if ibd {
log.Printf("Miner %d waiting for blockchain sync\n", m.num)
ready:
for {
select {
case _, ok := <-m.shutdownChan:
if !ok {
log.Printf("Miner %d shutting down...\n", m.num)
return
}
case <-ticker.C:
var err error
ibd, _, err = IsInitialBlockDownload(m.ledger, m.blockStore)
if err != nil {
panic(err)
}
if ibd == false {
// time to start mining
break ready
}
}
}
}
// register for tip changes
tipChangeChan := make(chan TipChange, 1)
m.processor.RegisterForTipChange(tipChangeChan)
defer m.processor.UnregisterForTipChange(tipChangeChan)
// register for new transactions
newTxChan := make(chan NewTx, 1)
m.processor.RegisterForNewTransactions(newTxChan)
defer m.processor.UnregisterForNewTransactions(newTxChan)
// main mining loop
var hashes, medianTimestamp int64
var block *Block
var targetInt *big.Int
for {
select {
case tip := <-tipChangeChan:
if !tip.Connect || tip.More {
// only build off newly connected tip blocks
continue
}
// give up whatever block we were working on
log.Printf("Miner %d received notice of new tip block %s\n", m.num, tip.BlockID)
var err error
// start working on a new block
block, err = m.createNextBlock(tip.BlockID, tip.Block.Header)
if err != nil {
// ledger state is broken
panic(err)
}
// make sure we're at least +1 the median timestamp
medianTimestamp, err = computeMedianTimestamp(tip.Block.Header, m.blockStore)
if err != nil {
panic(err)
}
if block.Header.Time <= medianTimestamp {
block.Header.Time = medianTimestamp + 1
}
// convert our target to a big.Int
targetInt = block.Header.Target.GetBigInt()
case newTx := <-newTxChan:
log.Printf("Miner %d received notice of new transaction %s\n", m.num, newTx.TransactionID)
if block == nil {
// we're not working on a block yet
continue
}
if MAX_TRANSACTIONS_TO_INCLUDE_PER_BLOCK != 0 &&
len(block.Transactions) >= MAX_TRANSACTIONS_TO_INCLUDE_PER_BLOCK {
log.Printf("Per-block transaction limit hit (%d)\n", len(block.Transactions))
continue
}
// add the transaction to the block (it updates the coinbase fee)
if err := block.AddTransaction(newTx.TransactionID, newTx.Transaction); err != nil {
log.Printf("Error adding new transaction %s to block: %s\n",
newTx.TransactionID, err)
// abandon the block
block = nil
}
case _, ok := <-m.shutdownChan:
if !ok {
log.Printf("Miner %d shutting down...\n", m.num)
return
}
case <-ticker.C:
// update hashcount for hashrate monitor
m.hashUpdateChan <- hashes
hashes = 0
if block != nil {
// update block time every so often
now := time.Now().Unix()
if now > medianTimestamp {
block.Header.Time = now
}
}
default:
if block == nil {
// find the tip to start working off of
tipID, tipHeader, _, err := getChainTipHeader(m.ledger, m.blockStore)
if err != nil {
panic(err)
}
// create a new block
block, err = m.createNextBlock(*tipID, tipHeader)
if err != nil {
panic(err)
}
// make sure we're at least +1 the median timestamp
medianTimestamp, err = computeMedianTimestamp(tipHeader, m.blockStore)
if err != nil {
panic(err)
}
if block.Header.Time <= medianTimestamp {
block.Header.Time = medianTimestamp + 1
}
// convert our target to a big.Int
targetInt = block.Header.Target.GetBigInt()
}
// hash the block and check the proof-of-work
hashes++
idInt := block.Header.IDFast()
if idInt.Cmp(targetInt) <= 0 {
// found a solution
id := new(BlockID).SetBigInt(idInt)
log.Printf("Miner %d mined new block %s\n", m.num, *id)
// process the block
if err := m.processor.ProcessBlock(*id, block, "localhost"); err != nil {
log.Printf("Error processing mined block: %s\n", err)
}
block = nil
m.keyIndex = rand.Intn(len(m.pubKeys))
} else {
// no solution yet
block.Header.Nonce += 1
if block.Header.Nonce > MAX_NUMBER {
block.Header.Nonce = 0
}
}
}
}
}
// Shutdown stops the miner synchronously.
func (m *Miner) Shutdown() {
close(m.shutdownChan)
m.wg.Wait()
log.Printf("Miner %d shutdown\n", m.num)
}
// Create a new block off of the given tip block.
func (m *Miner) createNextBlock(tipID BlockID, tipHeader *BlockHeader) (*Block, error) {
log.Printf("Miner %d mining new block from current tip %s\n", m.num, tipID)
// fetch transactions to confirm from the queue
txs := m.txQueue.Get(MAX_TRANSACTIONS_TO_INCLUDE_PER_BLOCK - 1)
// calculate total fees
var fees int64 = 0
for _, tx := range txs {
fees += tx.Fee
}
// calculate total block reward
var newHeight int64 = tipHeader.Height + 1
reward := BlockCreationReward(newHeight) + fees
// build coinbase
tx := NewTransaction(nil, m.pubKeys[m.keyIndex], reward, 0, 0, 0, newHeight, m.memo)
// prepend coinbase
txs = append([]*Transaction{tx}, txs...)
// compute the next target
newTarget, err := computeTarget(tipHeader, m.blockStore)
if err != nil {
return nil, err
}
// create the block
block, err := NewBlock(tipID, newHeight, newTarget, tipHeader.ChainWork, txs)
if err != nil {
return nil, err
}
return block, nil
}
// Run executes the hashrate monitor's main loop in its own goroutine.
func (h *HashrateMonitor) Run() {
h.wg.Add(1)
go h.run()
}
func (h *HashrateMonitor) run() {
defer h.wg.Done()
var totalHashes int64
updateInterval := 5 * time.Minute
ticker := time.NewTicker(updateInterval)
defer ticker.Stop()
for {
select {
case _, ok := <-h.shutdownChan:
if !ok {
log.Println("Hashrate monitor shutting down...")
return
}
case hashes := <-h.hashUpdateChan:
totalHashes += hashes
case <-ticker.C:
hps := float64(totalHashes) / updateInterval.Seconds()
totalHashes = 0
log.Printf("Hashrate: %.2f MH/s", hps/1000/1000)
}
}
}
// Shutdown stops the hashrate monitor synchronously.
func (h *HashrateMonitor) Shutdown() {
close(h.shutdownChan)
h.wg.Wait()
log.Println("Hashrate monitor shutdown")
}