Compare commits

..
3 changed files with 10 additions and 399 deletions

View file

@ -1,14 +1,12 @@
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Numerics;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
@ -87,11 +85,10 @@ internal static class Program
password = GetArg(args, "-p") ?? "x";
var threadText = GetArg(args, "-t");
var threadsAuto = threadText == null;
threadCount = threadsAuto
? GetPhysicalCoreCount()
: int.Parse(threadText!, CultureInfo.InvariantCulture);
threadCount = threadText != null
? int.Parse(threadText, CultureInfo.InvariantCulture)
: Environment.ProcessorCount;
if(threadCount < 1)
throw new ArgumentException("Thread count must be at least 1");
@ -128,12 +125,9 @@ internal static class Program
};
PrintBanner();
PrintStartupDiagnostics();
Console.WriteLine($"Pool : {host}:{port}");
Console.WriteLine($"Worker : {worker}");
Console.WriteLine(
$"Mining Threads : {threadCount}" +
(threadsAuto ? " (auto)" : " (manual)"));
Console.WriteLine($"Threads : {threadCount}");
Console.WriteLine();
var statsTask = StatsLoopAsync(shutdown.Token);
@ -820,235 +814,6 @@ internal static class Program
}
}
private static void PrintStartupDiagnostics()
{
var logicalCpus = Environment.ProcessorCount;
var physicalCores = GetPhysicalCoreCount();
var cpuModel = GetCpuModel();
var aesSupported = GetAesSupport();
var hugePages = GetHugePagesStatus();
string randomXStatus;
string randomXPath;
if(OperatingSystem.IsLinux())
{
// Accessing these properties initializes VextaRandomX and
// explicitly loads librandomx.so from the miner directory.
randomXStatus = VextaRandomX.NativeLibraryLoaded
? "Loaded OK"
: "Not loaded";
randomXPath = VextaRandomX.NativeLibraryPath ?? "Unknown";
}
else
{
randomXStatus = "System loader";
randomXPath = "Platform default";
}
Console.WriteLine($"CPU : {cpuModel}");
Console.WriteLine($"Physical Cores : {physicalCores}");
Console.WriteLine($"Logical CPUs : {logicalCpus}");
Console.WriteLine($"AES : {aesSupported}");
Console.WriteLine($"Huge Pages : {hugePages}");
Console.WriteLine($"RandomX Library: {randomXStatus}");
Console.WriteLine($"Library Path : {randomXPath}");
Console.WriteLine();
}
private static string GetCpuModel()
{
try
{
if(OperatingSystem.IsLinux() && File.Exists("/proc/cpuinfo"))
{
foreach(var line in File.ReadLines("/proc/cpuinfo"))
{
if(line.StartsWith("model name", StringComparison.OrdinalIgnoreCase))
{
var colon = line.IndexOf(':');
if(colon >= 0)
return line[(colon + 1)..].Trim();
}
}
}
var identifier = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
if(!string.IsNullOrWhiteSpace(identifier))
return identifier.Trim();
}
catch
{
}
return "Unknown";
}
private static int GetPhysicalCoreCount()
{
try
{
if(OperatingSystem.IsLinux() && File.Exists("/proc/cpuinfo"))
{
var cores = new HashSet<string>();
string? physicalId = null;
string? coreId = null;
foreach(var rawLine in File.ReadLines("/proc/cpuinfo"))
{
var line = rawLine.Trim();
if(line.Length == 0)
{
if(physicalId != null && coreId != null)
cores.Add($"{physicalId}:{coreId}");
physicalId = null;
coreId = null;
continue;
}
if(line.StartsWith("physical id", StringComparison.OrdinalIgnoreCase))
{
var colon = line.IndexOf(':');
if(colon >= 0)
physicalId = line[(colon + 1)..].Trim();
}
else if(line.StartsWith("core id", StringComparison.OrdinalIgnoreCase))
{
var colon = line.IndexOf(':');
if(colon >= 0)
coreId = line[(colon + 1)..].Trim();
}
}
if(physicalId != null && coreId != null)
cores.Add($"{physicalId}:{coreId}");
if(cores.Count > 0)
return cores.Count;
}
if(OperatingSystem.IsWindows())
{
var windowsCores = GetWindowsPhysicalCoreCount();
if(windowsCores > 0)
return windowsCores;
}
}
catch
{
}
return Environment.ProcessorCount;
}
private static int GetWindowsPhysicalCoreCount()
{
uint length = 0;
GetLogicalProcessorInformation(IntPtr.Zero, ref length);
if(length == 0)
return 0;
var buffer = Marshal.AllocHGlobal((int)length);
try
{
if(!GetLogicalProcessorInformation(buffer, ref length))
return 0;
// SYSTEM_LOGICAL_PROCESSOR_INFORMATION is 32 bytes on x64
// and 24 bytes on x86.
var entrySize = IntPtr.Size == 8 ? 32 : 24;
var relationshipOffset = IntPtr.Size;
var count = 0;
for(var offset = 0;
offset + entrySize <= length;
offset += entrySize)
{
var relationship = Marshal.ReadInt32(
buffer,
offset + relationshipOffset);
// RelationProcessorCore = 0
if(relationship == 0)
count++;
}
return count;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetLogicalProcessorInformation(
IntPtr buffer,
ref uint returnedLength);
private static string GetAesSupport()
{
try
{
if(System.Runtime.Intrinsics.X86.Aes.IsSupported)
return "Supported";
}
catch
{
}
return "Not detected";
}
private static string GetHugePagesStatus()
{
try
{
if(OperatingSystem.IsLinux() && File.Exists("/proc/meminfo"))
{
foreach(var line in File.ReadLines("/proc/meminfo"))
{
if(!line.StartsWith("HugePages_Total:", StringComparison.Ordinal))
continue;
var value = line["HugePages_Total:".Length..].Trim();
var parts = value.Split(
' ',
StringSplitOptions.RemoveEmptyEntries);
if(parts.Length > 0 &&
long.TryParse(
parts[0],
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out var total))
{
return total > 0
? $"Configured ({total})"
: "Not configured";
}
}
}
}
catch
{
}
return "Unknown";
}
private static async Task StatsLoopAsync(CancellationToken ct)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
@ -1058,10 +823,6 @@ internal static class Program
var lastTime = sw.Elapsed;
var measuring = false;
var averageHistory = new List<(TimeSpan Time, long Hashes)>();
TimeSpan? averageStartTime = null;
var nextAverageReport = TimeSpan.Zero;
try
{
while(await timer.WaitForNextTickAsync(ct))
@ -1069,34 +830,6 @@ internal static class Program
var now = sw.Elapsed;
var hashes = Interlocked.Read(ref totalHashes);
if(hashes > 0)
{
if(averageStartTime == null)
{
averageStartTime = now;
nextAverageReport = now + TimeSpan.FromMinutes(1);
averageHistory.Add((now, hashes));
}
else if(now >= nextAverageReport)
{
averageHistory.Add((now, hashes));
WriteAverageHashrate(
now,
hashes,
averageHistory,
averageStartTime.Value);
do
{
nextAverageReport += TimeSpan.FromMinutes(1);
}
while(nextAverageReport <= now);
TrimAverageHistory(now, averageHistory);
}
}
if(hashes == 0)
{
lastHashes = hashes;
@ -1144,74 +877,6 @@ internal static class Program
}
}
private static void WriteAverageHashrate(
TimeSpan now,
long hashes,
List<(TimeSpan Time, long Hashes)> history,
TimeSpan startTime)
{
var avg5 = FormatAverageWindow(
now, hashes, history, startTime, TimeSpan.FromMinutes(5));
var avg15 = FormatAverageWindow(
now, hashes, history, startTime, TimeSpan.FromMinutes(15));
var avg60 = FormatAverageWindow(
now, hashes, history, startTime, TimeSpan.FromMinutes(60));
WriteColor(
$"AVG HASHRATE 5m:{avg5} 15m:{avg15} 60m:{avg60}",
ConsoleColor.DarkCyan);
}
private static string FormatAverageWindow(
TimeSpan now,
long hashes,
List<(TimeSpan Time, long Hashes)> history,
TimeSpan startTime,
TimeSpan window)
{
if(now - startTime < window)
return "warming up";
var cutoff = now - window;
(TimeSpan Time, long Hashes)? sample = null;
foreach(var item in history)
{
if(item.Time <= cutoff)
sample = item;
else
break;
}
if(sample == null)
return "warming up";
var elapsed = (now - sample.Value.Time).TotalSeconds;
if(elapsed <= 0)
return "warming up";
var deltaHashes = hashes - sample.Value.Hashes;
var rate = deltaHashes / elapsed;
return FormatHashrate(rate);
}
private static void TrimAverageHistory(
TimeSpan now,
List<(TimeSpan Time, long Hashes)> history)
{
var keepAfter = now - TimeSpan.FromMinutes(62);
while(history.Count > 2 &&
history[1].Time < keepAfter)
{
history.RemoveAt(0);
}
}
private static string FormatHashrate(double value)
{
if(value >= 1_000_000)
@ -1291,7 +956,7 @@ internal static class Program
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
Console.WriteLine("Vexta CPU Miner 0.1.2");
Console.WriteLine("Vexta CPU Miner 0.1.1");
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("Copyright (c) 2026 Vexta Project");
@ -1331,7 +996,7 @@ internal static class Program
Console.WriteLine(" -u Vexta address / worker name");
Console.WriteLine(" -p Password (default: x)");
Console.WriteLine(
" -t CPU threads (default: auto-detect physical cores)");
" -t CPU threads (default: logical CPU count)");
Console.WriteLine();
Console.WriteLine("Ctrl+C stops the miner cleanly.");
}

View file

@ -20,11 +20,8 @@ Prebuilt binaries are available for:
- Vexta RandomX mining
- RandomX full-memory mode
- Windows x64 and Linux x64 support
- Automatic CPU thread selection based on physical cores
- Manual CPU thread override with `-t`
- Startup diagnostics for CPU, cores, AES, Huge Pages, and RandomX library
- Configurable CPU thread count
- Local hashrate display
- 5 / 15 / 60 minute average hashrate
- Accepted / rejected share statistics
- Automatic RandomX seed changes
- Daemon-confirmed `BLOCK FOUND` notifications when supported by the pool
@ -62,7 +59,7 @@ Example:
| `-o` | Stratum pool address |
| `-u` | Vexta wallet address, optionally followed by `.worker` |
| `-p` | Pool password, normally `x` |
| `-t` | Number of CPU mining threads (optional; auto-detects physical cores when omitted) |
| `-t` | Number of CPU mining threads |
## Requirements
@ -72,11 +69,6 @@ Example:
RandomX full-memory mode is enabled.
On startup, the miner reports detected CPU information, physical and logical CPU counts,
AES support, Huge Pages status, RandomX library status, and the selected mining thread count.
If `-t` is omitted, the miner automatically uses the detected physical core count.
## Building from Source
### Requirements

View file

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -10,51 +9,6 @@ namespace Vexta.CpuMiner;
public static unsafe class VextaRandomX
{
private static IntPtr nativeLibraryHandle = IntPtr.Zero;
public static string? NativeLibraryPath { get; private set; }
public static bool NativeLibraryLoaded => nativeLibraryHandle != IntPtr.Zero;
static VextaRandomX()
{
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var libraryPath = Path.Combine(AppContext.BaseDirectory, "librandomx.so");
if(!File.Exists(libraryPath))
throw new DllNotFoundException(
$"RandomX library was not found at '{libraryPath}'.");
try
{
nativeLibraryHandle = NativeLibrary.Load(libraryPath);
NativeLibraryPath = libraryPath;
}
catch(Exception ex)
{
throw new DllNotFoundException(
$"Unable to load RandomX library from '{libraryPath}': {ex.Message}",
ex);
}
NativeLibrary.SetDllImportResolver(
typeof(VextaRandomX).Assembly,
(libraryName, assembly, searchPath) =>
{
if(string.Equals(
libraryName,
"librandomx",
StringComparison.Ordinal))
{
return nativeLibraryHandle;
}
return IntPtr.Zero;
});
}
}
[Flags]
public enum RandomXFlags
{