hash-of-wisdom/internal/pow/solver/solver.go
2025-08-22 19:30:26 +07:00

95 lines
2 KiB
Go

package solver
import (
"context"
"runtime"
"sync/atomic"
"hash-of-wisdom/internal/pow/challenge"
)
// Solver handles parallel PoW solving with configurable parallelism
type Solver struct {
workers int
}
// SolverOption represents functional options for solver configuration
type solverOption func(*Solver)
// WithWorkers sets the number of parallel workers
func WithWorkers(workers int) solverOption {
return func(s *Solver) {
s.workers = workers
}
}
// NewSolver creates a new parallel PoW solver
func NewSolver(options ...solverOption) *Solver {
solver := &Solver{
workers: runtime.NumCPU(), // Default to CPU count
}
for _, option := range options {
option(solver)
}
return solver
}
// Solve attempts to find a valid nonce for the given challenge
func (s *Solver) Solve(ctx context.Context, ch *challenge.Challenge) (*challenge.Solution, error) {
// Create cancellable context for workers
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Channel to receive solution from any worker
solutionCh := make(chan *challenge.Solution, 1)
// Shared nonce counter for work distribution
var nonceCounter atomic.Uint64
// Start parallel workers
for i := range s.workers {
go func(workerID int) {
s.worker(ctx, ch, &nonceCounter, solutionCh)
}(i)
}
// Wait for either solution or context cancellation
select {
case solution := <-solutionCh:
return solution, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// worker performs PoW computation in parallel
func (s *Solver) worker(ctx context.Context, ch *challenge.Challenge, nonceCounter *atomic.Uint64, solutionCh chan<- *challenge.Solution) {
for {
select {
case <-ctx.Done():
return
default:
// Get next nonce to try
nonce := nonceCounter.Add(1) - 1
// Try this nonce
if ch.VerifySolution(nonce) {
// Found solution! Send it back
solution := &challenge.Solution{
Challenge: *ch,
Nonce: nonce,
}
select {
case solutionCh <- solution:
case <-ctx.Done():
}
return
}
}
}
}