// Brute force nonces until a valid solution is found.
func genHashes(ctx context.Context, c Challenge) <-chan Solution {
var (
out = make(chan Solution, 1)
sha = sha256.New()
nonce = rand.Uint32()
)
go func() {
defer close(out)
for {
select {
case <-ctx.Done():
return
default:
}
sha.Write(fmt.Append(nil, c.Salt, nonce))
sol := Solution{
Hash: sha.Sum(nil),
Nonce: nonce,
}
// Ensure we don't hang if out channel is full on ctx close.
select {
case <-ctx.Done():
return
case out <- sol:
}
// Reset hasher input for next iteration.
sha.Reset()
nonce++
}
}()
return out
}
// Solve Challenge c. Returns Solution that can be submitted.
func Solve(ctx context.Context, c Challenge) (Solution, error) {
duration, err := time.ParseDuration(fmt.Sprintf("%dm", c.Patience))
if err != nil {
// Fallback to common value.
duration = 3 * time.Minute
}
ctx, cancel := context.WithTimeout(ctx, duration)
defer cancel()
sol := make(chan Solution, 1)
go func() {
// A reasonable hardware-based job limiter.
// Use 1 worker per thread at most.
threads := runtime.NumCPU()
for i := 0; i < threads; i++ {
go func() {
hf := genHashes(ctx, c)
// Loop until answer has been found.
// Should break when hash worker terminates.
for h := range hf {
if checkZeros(c.Diff, h.Hash) {
h.Salt = c.Salt
// Set host url to submit this to.
h.host = c.host
sol <- h
}
}
}()
}
}()
select {
case <-ctx.Done():
return Solution{}, ctx.Err()
case s := <-sol:
return s, nil
}
}