337 lines
7.1 KiB
Go
337 lines
7.1 KiB
Go
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 ""
|
|
}
|