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) } } }