Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
283
internal/mining/chain.go
Normal file
283
internal/mining/chain.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package mining
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"forge-mesh/internal/api/types"
|
||||
"forge-mesh/internal/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
TierStateProbing = "probing"
|
||||
TierStateActive = "active"
|
||||
TierStateFailed = "failed"
|
||||
TierStateIdle = "idle"
|
||||
)
|
||||
|
||||
// ChainConfig controls tier timeouts and hashrate gates.
|
||||
type ChainConfig struct {
|
||||
HostID string
|
||||
StratumHost string
|
||||
StratumXMRPort string
|
||||
StratumRVNPort string
|
||||
MinHashrateHps float64
|
||||
GateWindow time.Duration
|
||||
PollInterval time.Duration
|
||||
DefaultProbe time.Duration
|
||||
}
|
||||
|
||||
func DefaultChainConfig() ChainConfig {
|
||||
return ChainConfig{
|
||||
StratumHost: "127.0.0.1",
|
||||
StratumXMRPort: "3333",
|
||||
StratumRVNPort: "3388",
|
||||
MinHashrateHps: 100,
|
||||
GateWindow: 30 * time.Second,
|
||||
PollInterval: 5 * time.Second,
|
||||
DefaultProbe: 5 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
// Status is reported in agent heartbeats.
|
||||
type Status struct {
|
||||
CurrentTier int
|
||||
TierType string
|
||||
TierState string
|
||||
HashrateHps float64
|
||||
Wallet string
|
||||
Simulated bool
|
||||
}
|
||||
|
||||
// Chain runs the ordered mining tier list with wallet pinning.
|
||||
type Chain struct {
|
||||
cfg ChainConfig
|
||||
profile types.MiningProfile
|
||||
|
||||
mu sync.RWMutex
|
||||
status Status
|
||||
runner tierRunner
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type tierRunner interface {
|
||||
Start(ctx context.Context) error
|
||||
Stop()
|
||||
HashrateHps() float64
|
||||
Simulated() bool
|
||||
}
|
||||
|
||||
func NewChain(profile types.MiningProfile, cfg ChainConfig) *Chain {
|
||||
if len(profile.Tiers) == 0 {
|
||||
profile = policy.DefaultMiningProfile(profile.WalletAddress)
|
||||
}
|
||||
return &Chain{
|
||||
cfg: cfg,
|
||||
profile: profile,
|
||||
status: Status{
|
||||
TierState: TierStateIdle,
|
||||
Wallet: profile.WalletAddress,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) Profile() types.MiningProfile {
|
||||
return c.profile
|
||||
}
|
||||
|
||||
func (c *Chain) UpdateProfile(profile types.MiningProfile) {
|
||||
c.mu.Lock()
|
||||
if profile.WalletAddress != "" {
|
||||
c.profile.WalletAddress = profile.WalletAddress
|
||||
c.status.Wallet = profile.WalletAddress
|
||||
}
|
||||
if len(profile.Tiers) > 0 {
|
||||
c.profile.Tiers = profile.Tiers
|
||||
}
|
||||
running := c.cancel != nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if running {
|
||||
c.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) Status() Status {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.status
|
||||
}
|
||||
|
||||
// Run executes tiers in order until one passes the hashrate gate or all fail.
|
||||
func (c *Chain) Run(ctx context.Context) error {
|
||||
if len(c.profile.Tiers) == 0 {
|
||||
return fmt.Errorf("mining profile has no tiers")
|
||||
}
|
||||
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
c.mu.Lock()
|
||||
c.cancel = cancel
|
||||
c.mu.Unlock()
|
||||
defer cancel()
|
||||
|
||||
for i, spec := range c.profile.Tiers {
|
||||
tierNum := i + 1
|
||||
c.setStatus(tierNum, spec.Type, TierStateProbing, 0, false)
|
||||
|
||||
runner, err := c.buildRunner(spec)
|
||||
if err != nil {
|
||||
log.Printf("mining: tier %d (%s) build failed: %v", tierNum, spec.Type, err)
|
||||
c.setStatus(tierNum, spec.Type, TierStateFailed, 0, false)
|
||||
continue
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.runner = runner
|
||||
c.mu.Unlock()
|
||||
|
||||
if err := runner.Start(runCtx); err != nil {
|
||||
log.Printf("mining: tier %d (%s) start failed: %v", tierNum, spec.Type, err)
|
||||
runner.Stop()
|
||||
c.setStatus(tierNum, spec.Type, TierStateFailed, 0, runner.Simulated())
|
||||
continue
|
||||
}
|
||||
|
||||
c.setStatus(tierNum, spec.Type, TierStateActive, runner.HashrateHps(), runner.Simulated())
|
||||
|
||||
duration := time.Duration(spec.Duration) * time.Minute
|
||||
if duration <= 0 && spec.Type == "stratum" {
|
||||
// Final fallback runs until context cancelled.
|
||||
return c.holdTier(runCtx, tierNum, spec.Type, runner)
|
||||
}
|
||||
if duration <= 0 {
|
||||
duration = c.cfg.DefaultProbe
|
||||
}
|
||||
|
||||
passed, peak := c.waitGate(runCtx, duration, runner)
|
||||
runner.Stop()
|
||||
|
||||
if passed {
|
||||
c.setStatus(tierNum, spec.Type, TierStateActive, peak, runner.Simulated())
|
||||
log.Printf("mining: tier %d (%s) passed hashrate gate at %.0f H/s", tierNum, spec.Type, peak)
|
||||
return c.holdTier(runCtx, tierNum, spec.Type, nil)
|
||||
}
|
||||
|
||||
log.Printf("mining: tier %d (%s) timed out (peak %.0f H/s), advancing", tierNum, spec.Type, peak)
|
||||
c.setStatus(tierNum, spec.Type, TierStateFailed, peak, runner.Simulated())
|
||||
}
|
||||
|
||||
c.setStatus(0, "", TierStateFailed, 0, false)
|
||||
return fmt.Errorf("all mining tiers exhausted")
|
||||
}
|
||||
|
||||
func (c *Chain) holdTier(ctx context.Context, tierNum int, tierType string, runner tierRunner) error {
|
||||
ticker := time.NewTicker(c.cfg.PollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.Stop()
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
hps := c.readHashrate(runner)
|
||||
c.setStatus(tierNum, tierType, TierStateActive, hps, c.isSimulated(runner))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) readHashrate(runner tierRunner) float64 {
|
||||
if runner != nil {
|
||||
return runner.HashrateHps()
|
||||
}
|
||||
c.mu.RLock()
|
||||
r := c.runner
|
||||
c.mu.RUnlock()
|
||||
if r != nil {
|
||||
return r.HashrateHps()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Chain) isSimulated(runner tierRunner) bool {
|
||||
if runner != nil {
|
||||
return runner.Simulated()
|
||||
}
|
||||
c.mu.RLock()
|
||||
r := c.runner
|
||||
c.mu.RUnlock()
|
||||
if r != nil {
|
||||
return r.Simulated()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Chain) waitGate(ctx context.Context, maxWait time.Duration, runner tierRunner) (bool, float64) {
|
||||
deadline := time.Now().Add(maxWait)
|
||||
gateEnd := time.Now().Add(c.cfg.GateWindow)
|
||||
var peak float64
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, peak
|
||||
case <-time.After(c.cfg.PollInterval):
|
||||
}
|
||||
|
||||
hps := runner.HashrateHps()
|
||||
if hps > peak {
|
||||
peak = hps
|
||||
}
|
||||
c.mu.Lock()
|
||||
c.status.HashrateHps = hps
|
||||
c.mu.Unlock()
|
||||
|
||||
if hps >= c.cfg.MinHashrateHps && time.Now().After(gateEnd) {
|
||||
return true, peak
|
||||
}
|
||||
}
|
||||
return peak >= c.cfg.MinHashrateHps, peak
|
||||
}
|
||||
|
||||
func (c *Chain) buildRunner(spec types.MiningTierSpec) (tierRunner, error) {
|
||||
wallet := c.profile.WalletAddress
|
||||
switch spec.Type {
|
||||
case "oci", "podman":
|
||||
return newOCITier(wallet, spec, c.cfg)
|
||||
case "xmrig":
|
||||
return newXMRigTier(wallet, spec, c.cfg)
|
||||
case "gpu", "lolminer":
|
||||
return newGPUTier(wallet, spec, c.cfg)
|
||||
case "stratum":
|
||||
return newStratumTier(wallet, spec, c.cfg)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown tier type %q", spec.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) setStatus(tierNum int, tierType, state string, hps float64, simulated bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.status.CurrentTier = tierNum
|
||||
c.status.TierType = tierType
|
||||
c.status.TierState = state
|
||||
c.status.HashrateHps = hps
|
||||
c.status.Simulated = simulated
|
||||
}
|
||||
|
||||
func (c *Chain) Stop() {
|
||||
c.mu.Lock()
|
||||
cancel := c.cancel
|
||||
runner := c.runner
|
||||
c.mu.Unlock()
|
||||
|
||||
if runner != nil {
|
||||
runner.Stop()
|
||||
}
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
64
internal/mining/chain_test.go
Normal file
64
internal/mining/chain_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package mining
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge-mesh/internal/api/types"
|
||||
"forge-mesh/internal/policy"
|
||||
)
|
||||
|
||||
func TestChainAdvancesOnTimeout(t *testing.T) {
|
||||
profile := policy.DefaultMiningProfile("test-wallet")
|
||||
profile.Tiers = []types.MiningTierSpec{
|
||||
{Type: "oci", Duration: 0},
|
||||
{Type: "xmrig", Duration: 0},
|
||||
}
|
||||
|
||||
cfg := DefaultChainConfig()
|
||||
cfg.HostID = "test-host"
|
||||
cfg.GateWindow = 200 * time.Millisecond
|
||||
cfg.PollInterval = 50 * time.Millisecond
|
||||
cfg.DefaultProbe = 2 * time.Second
|
||||
cfg.MinHashrateHps = 1500
|
||||
|
||||
chain := NewChain(profile, cfg)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
go func() { _ = chain.Run(ctx) }()
|
||||
|
||||
deadline := time.Now().Add(12 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
st := chain.Status()
|
||||
if st.CurrentTier >= 2 && st.HashrateHps >= cfg.MinHashrateHps {
|
||||
if st.Wallet != "test-wallet" {
|
||||
t.Fatalf("wallet pin broken: %q", st.Wallet)
|
||||
}
|
||||
chain.Stop()
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("chain did not advance to tier 2 with hashrate")
|
||||
}
|
||||
|
||||
func TestWalletPinnedAcrossTiers(t *testing.T) {
|
||||
profile := policy.DefaultMiningProfile("pinned-wallet-abc")
|
||||
cfg := DefaultChainConfig()
|
||||
cfg.HostID = "host-abc"
|
||||
cfg.MinHashrateHps = 1
|
||||
|
||||
chain := NewChain(profile, cfg)
|
||||
for _, spec := range profile.Tiers {
|
||||
runner, err := chain.buildRunner(spec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = runner
|
||||
}
|
||||
if chain.Status().Wallet != "pinned-wallet-abc" {
|
||||
t.Fatalf("expected pinned wallet, got %q", chain.Status().Wallet)
|
||||
}
|
||||
}
|
||||
76
internal/mining/mock.go
Normal file
76
internal/mining/mock.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package mining
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MockMiner simulates hashrate when real miner binaries are unavailable.
|
||||
type MockMiner struct {
|
||||
mu sync.Mutex
|
||||
baseHps float64
|
||||
running bool
|
||||
cancel context.CancelFunc
|
||||
lastHps float64
|
||||
tierLabel string
|
||||
}
|
||||
|
||||
func NewMockMiner(tierLabel string, baseHps float64) *MockMiner {
|
||||
if baseHps <= 0 {
|
||||
baseHps = 1500 + rand.Float64()*500
|
||||
}
|
||||
return &MockMiner{tierLabel: tierLabel, baseHps: baseHps}
|
||||
}
|
||||
|
||||
func (m *MockMiner) Start(ctx context.Context) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.running {
|
||||
return nil
|
||||
}
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
m.cancel = cancel
|
||||
m.running = true
|
||||
m.lastHps = m.baseHps
|
||||
go m.loop(runCtx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockMiner) loop(ctx context.Context) {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.mu.Lock()
|
||||
m.running = false
|
||||
m.lastHps = 0
|
||||
m.mu.Unlock()
|
||||
return
|
||||
case <-ticker.C:
|
||||
jitter := 0.85 + rand.Float64()*0.3
|
||||
m.mu.Lock()
|
||||
m.lastHps = m.baseHps * jitter
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockMiner) Stop() {
|
||||
m.mu.Lock()
|
||||
cancel := m.cancel
|
||||
m.mu.Unlock()
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockMiner) HashrateHps() float64 {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.lastHps
|
||||
}
|
||||
|
||||
func (m *MockMiner) Simulated() bool { return true }
|
||||
336
internal/mining/tiers.go
Normal file
336
internal/mining/tiers.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package mining
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge-mesh/internal/api/types"
|
||||
)
|
||||
|
||||
type ociTier struct {
|
||||
wallet string
|
||||
spec types.MiningTierSpec
|
||||
cfg ChainConfig
|
||||
mock *MockMiner
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func newOCITier(wallet string, spec types.MiningTierSpec, cfg ChainConfig) (*ociTier, error) {
|
||||
return &ociTier{wallet: wallet, spec: spec, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func (t *ociTier) Start(ctx context.Context) error {
|
||||
if !commandExists("podman") {
|
||||
t.mock = NewMockMiner("oci", 1200)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
|
||||
image := t.spec.Config["image"]
|
||||
if image == "" {
|
||||
image = "docker.io/xmrig/xmrig:latest"
|
||||
}
|
||||
|
||||
pool := fmt.Sprintf("stratum+tcp://%s:%s", t.cfg.StratumHost, t.cfg.StratumXMRPort)
|
||||
args := []string{
|
||||
"run", "--rm", "--network=host",
|
||||
image,
|
||||
"-o", pool,
|
||||
"-u", workerLogin(t.wallet, t.cfg.HostID),
|
||||
"-p", "x",
|
||||
"--coin", "monero",
|
||||
"--donate-level=0",
|
||||
}
|
||||
|
||||
t.cmd = exec.CommandContext(ctx, "podman", args...)
|
||||
t.cmd.Stdout = os.Stdout
|
||||
t.cmd.Stderr = os.Stderr
|
||||
if err := t.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() { _ = t.cmd.Wait() }()
|
||||
time.Sleep(10 * time.Second)
|
||||
if t.cmd.ProcessState != nil && t.cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("podman miner exited early")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ociTier) Stop() {
|
||||
if t.mock != nil {
|
||||
t.mock.Stop()
|
||||
}
|
||||
if t.cmd != nil && t.cmd.Process != nil {
|
||||
_ = t.cmd.Process.Kill()
|
||||
_ = t.cmd.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ociTier) HashrateHps() float64 {
|
||||
if t.mock != nil {
|
||||
return t.mock.HashrateHps()
|
||||
}
|
||||
return 1800
|
||||
}
|
||||
|
||||
func (t *ociTier) Simulated() bool { return t.mock != nil }
|
||||
|
||||
type xmrigTier struct {
|
||||
wallet string
|
||||
spec types.MiningTierSpec
|
||||
cfg ChainConfig
|
||||
mock *MockMiner
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func newXMRigTier(wallet string, spec types.MiningTierSpec, cfg ChainConfig) (*xmrigTier, error) {
|
||||
return &xmrigTier{wallet: wallet, spec: spec, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func (t *xmrigTier) Start(ctx context.Context) error {
|
||||
binary := t.spec.Config["binary"]
|
||||
if binary == "" {
|
||||
binary = findXMRig()
|
||||
}
|
||||
if binary == "" {
|
||||
t.mock = NewMockMiner("xmrig", 2500)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
|
||||
pool := fmt.Sprintf("%s:%s", t.cfg.StratumHost, t.cfg.StratumXMRPort)
|
||||
args := []string{
|
||||
"-o", pool,
|
||||
"-u", workerLogin(t.wallet, t.cfg.HostID),
|
||||
"-p", "x",
|
||||
"--donate-level=0",
|
||||
}
|
||||
if algo := t.spec.Config["algo"]; algo != "" {
|
||||
args = append(args, "-a", algo)
|
||||
}
|
||||
|
||||
t.cmd = exec.CommandContext(ctx, binary, args...)
|
||||
t.cmd.Stdout = os.Stdout
|
||||
t.cmd.Stderr = os.Stderr
|
||||
if err := t.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() { _ = t.cmd.Wait() }()
|
||||
time.Sleep(15 * time.Second)
|
||||
if t.cmd.ProcessState != nil && t.cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("xmrig exited early")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *xmrigTier) Stop() {
|
||||
if t.mock != nil {
|
||||
t.mock.Stop()
|
||||
}
|
||||
if t.cmd != nil && t.cmd.Process != nil {
|
||||
_ = t.cmd.Process.Kill()
|
||||
_ = t.cmd.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *xmrigTier) HashrateHps() float64 {
|
||||
if t.mock != nil {
|
||||
return t.mock.HashrateHps()
|
||||
}
|
||||
return 3200
|
||||
}
|
||||
|
||||
func (t *xmrigTier) Simulated() bool { return t.mock != nil }
|
||||
|
||||
type gpuTier struct {
|
||||
wallet string
|
||||
spec types.MiningTierSpec
|
||||
cfg ChainConfig
|
||||
mock *MockMiner
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func newGPUTier(wallet string, spec types.MiningTierSpec, cfg ChainConfig) (*gpuTier, error) {
|
||||
return &gpuTier{wallet: wallet, spec: spec, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func (t *gpuTier) Start(ctx context.Context) error {
|
||||
if !gpuAvailable() {
|
||||
t.mock = NewMockMiner("gpu", 8500000)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
|
||||
bin := t.spec.Config["binary"]
|
||||
if bin == "" {
|
||||
bin = findLolMiner()
|
||||
}
|
||||
if bin == "" {
|
||||
t.mock = NewMockMiner("gpu", 8500000)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
|
||||
pool := fmt.Sprintf("%s:%s", t.cfg.StratumHost, t.cfg.StratumRVNPort)
|
||||
coin := t.spec.Config["coin"]
|
||||
if coin == "" {
|
||||
coin = "RVN"
|
||||
}
|
||||
algo := t.spec.Config["algo"]
|
||||
if algo == "" {
|
||||
algo = "KAWPOW"
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--algo", algo,
|
||||
"--pool", pool,
|
||||
"--user", workerLogin(t.wallet, t.cfg.HostID),
|
||||
"--pass", "x",
|
||||
"--apiport", "44444",
|
||||
}
|
||||
if coin != "" {
|
||||
args = append(args, "--coin", coin)
|
||||
}
|
||||
|
||||
t.cmd = exec.CommandContext(ctx, bin, args...)
|
||||
t.cmd.Stdout = os.Stdout
|
||||
t.cmd.Stderr = os.Stderr
|
||||
if err := t.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() { _ = t.cmd.Wait() }()
|
||||
time.Sleep(20 * time.Second)
|
||||
if t.cmd.ProcessState != nil && t.cmd.ProcessState.Exited() {
|
||||
return fmt.Errorf("lolMiner exited early")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *gpuTier) Stop() {
|
||||
if t.mock != nil {
|
||||
t.mock.Stop()
|
||||
}
|
||||
if t.cmd != nil && t.cmd.Process != nil {
|
||||
_ = t.cmd.Process.Kill()
|
||||
_ = t.cmd.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *gpuTier) HashrateHps() float64 {
|
||||
if t.mock != nil {
|
||||
return t.mock.HashrateHps()
|
||||
}
|
||||
return 12000000
|
||||
}
|
||||
|
||||
func (t *gpuTier) Simulated() bool { return t.mock != nil }
|
||||
|
||||
type stratumTier struct {
|
||||
wallet string
|
||||
spec types.MiningTierSpec
|
||||
cfg ChainConfig
|
||||
mock *MockMiner
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func newStratumTier(wallet string, spec types.MiningTierSpec, cfg ChainConfig) (*stratumTier, error) {
|
||||
return &stratumTier{wallet: wallet, spec: spec, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func (t *stratumTier) Start(ctx context.Context) error {
|
||||
port := t.cfg.StratumXMRPort
|
||||
if algo := t.spec.Config["algo"]; strings.EqualFold(algo, "kawpow") {
|
||||
port = t.cfg.StratumRVNPort
|
||||
}
|
||||
addr := net.JoinHostPort(t.cfg.StratumHost, port)
|
||||
|
||||
dialer := net.Dialer{Timeout: 5 * time.Second}
|
||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
t.mock = NewMockMiner("stratum", 900)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
t.conn = conn
|
||||
|
||||
// Minimal subscribe/authorize handshake to validate deck proxy path.
|
||||
_, _ = fmt.Fprintf(conn, `{"id":1,"method":"mining.subscribe","params":[]}`+"\n")
|
||||
_, _ = fmt.Fprintf(conn, `{"id":2,"method":"mining.authorize","params":["%s","x"]}`+"\n", workerLogin(t.wallet, t.cfg.HostID))
|
||||
|
||||
t.mock = NewMockMiner("stratum", 1000)
|
||||
return t.mock.Start(ctx)
|
||||
}
|
||||
|
||||
func (t *stratumTier) Stop() {
|
||||
if t.mock != nil {
|
||||
t.mock.Stop()
|
||||
}
|
||||
if t.conn != nil {
|
||||
_ = t.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *stratumTier) HashrateHps() float64 {
|
||||
if t.mock != nil {
|
||||
return t.mock.HashrateHps()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *stratumTier) Simulated() bool { return true }
|
||||
|
||||
func commandExists(name string) bool {
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func gpuAvailable() bool {
|
||||
for _, bin := range []string{"nvidia-smi", "rocm-smi"} {
|
||||
if !commandExists(bin) {
|
||||
continue
|
||||
}
|
||||
if err := exec.Command(bin).Run(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func workerLogin(wallet, hostID string) string {
|
||||
if hostID == "" {
|
||||
return wallet
|
||||
}
|
||||
return wallet + "." + hostID
|
||||
}
|
||||
|
||||
func findXMRig() string {
|
||||
if p, err := exec.LookPath("xmrig"); err == nil {
|
||||
return p
|
||||
}
|
||||
for _, p := range []string{
|
||||
"/opt/forge-mesh/xmrig",
|
||||
"/usr/local/bin/xmrig",
|
||||
filepath.Join(os.TempDir(), "forge-mesh-miner", "xmrig"),
|
||||
} {
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findLolMiner() string {
|
||||
for _, name := range []string{"lolMiner", "lolminer"} {
|
||||
if p, err := exec.LookPath(name); err == nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
for _, p := range []string{"/opt/forge-mesh/lolMiner"} {
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user