Files

99 lines
1.9 KiB
Go

package p2p
import (
"blockchain/chain"
"blockchain/config"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
type miner struct {
hashDelay *time.Duration
onMined func(*chain.Block, time.Duration)
work chan *chain.Block
}
func newMiner(hashDelay *time.Duration, onMined func(*chain.Block, time.Duration)) *miner {
return &miner{
hashDelay: hashDelay,
onMined: onMined,
work: make(chan *chain.Block, 1),
}
}
// SubmitWork sends a new block template to the miner.
// If the miner is busy, the previous job is cancelled automatically.
func (m *miner) SubmitWork(block *chain.Block) {
// drain old work if any
select {
case <-m.work:
default:
}
m.work <- block
}
func (m *miner) Run(stop <-chan struct{}) {
var (
cancel chan struct{}
done chan bool
t0 time.Time
block *chain.Block
)
for {
select {
case block = <-m.work:
// cancel previous mining if running
if cancel != nil {
close(cancel)
<-done
}
cancel = make(chan struct{})
done = make(chan bool, 1)
t0 = time.Now()
go func(b *chain.Block, c chan struct{}, d chan bool) {
d <- mine(b, c, *m.hashDelay)
}(block, cancel, done)
case ok := <-done:
cancel = nil
done = nil
if ok {
m.onMined(block, time.Since(t0))
}
case <-stop:
if cancel != nil {
close(cancel)
}
return
}
}
}
// mine перебирает nonce пока хеш не начнётся с нужного кол-ва нулей.
func mine(block *chain.Block, cancel chan struct{}, hashDelay time.Duration) bool {
prefix := strings.Repeat("0", config.Default.Chain.Difficulty)
base := block.HashBase()
for {
select {
case <-cancel:
return false
default:
}
data := base + strconv.Itoa(block.Nonce)
h := sha256.Sum256([]byte(data))
block.Hash = hex.EncodeToString(h[:])
if strings.HasPrefix(block.Hash, prefix) {
return true
}
block.Nonce++
if hashDelay > 0 {
time.Sleep(hashDelay)
}
}
}