Release Vexta CPU Miner v0.1.2

This commit is contained in:
VextaProject 2026-09-27 17:41:36 +00:00
commit d3b9876b45
3 changed files with 399 additions and 10 deletions

View file

@ -1,12 +1,14 @@
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;
@ -85,10 +87,11 @@ internal static class Program
password = GetArg(args, "-p") ?? "x";
var threadText = GetArg(args, "-t");
var threadsAuto = threadText == null;
threadCount = threadText != null
? int.Parse(threadText, CultureInfo.InvariantCulture)
: Environment.ProcessorCount;
threadCount = threadsAuto
? GetPhysicalCoreCount()
: int.Parse(threadText!, CultureInfo.InvariantCulture);
if(threadCount < 1)
throw new ArgumentException("Thread count must be at least 1");
@ -125,9 +128,12 @@ internal static class Program
};
PrintBanner();
Console.WriteLine($"Pool : {host}:{port}");
Console.WriteLine($"Worker : {worker}");
Console.WriteLine($"Threads : {threadCount}");
PrintStartupDiagnostics();
Console.WriteLine($"Pool : {host}:{port}");
Console.WriteLine($"Worker : {worker}");
Console.WriteLine(
$"Mining Threads : {threadCount}" +
(threadsAuto ? " (auto)" : " (manual)"));
Console.WriteLine();
var statsTask = StatsLoopAsync(shutdown.Token);
@ -814,6 +820,235 @@ 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));
@ -823,6 +1058,10 @@ 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))
@ -830,6 +1069,34 @@ 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;
@ -877,6 +1144,74 @@ 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)
@ -956,7 +1291,7 @@ internal static class Program
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
Console.WriteLine("Vexta CPU Miner 0.1.1");
Console.WriteLine("Vexta CPU Miner 0.1.2");
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("Copyright (c) 2026 Vexta Project");
@ -996,7 +1331,7 @@ internal static class Program
Console.WriteLine(" -u Vexta address / worker name");
Console.WriteLine(" -p Password (default: x)");
Console.WriteLine(
" -t CPU threads (default: logical CPU count)");
" -t CPU threads (default: auto-detect physical cores)");
Console.WriteLine();
Console.WriteLine("Ctrl+C stops the miner cleanly.");
}