Release Vexta CPU Miner v0.1.1

This commit is contained in:
VextaProject 2026-09-24 13:48:16 +00:00
commit 17949bc516
2 changed files with 213 additions and 42 deletions

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
@ -44,9 +45,11 @@ internal static class Program
private static string? currentSeed; private static string? currentSeed;
private static long nextSubmitId = 100; private static long nextSubmitId = 100;
private static long nextExtraNonce2;
private static long totalHashes; private static long totalHashes;
private static long acceptedShares; private static long acceptedShares;
private static long rejectedShares; private static long rejectedShares;
private static long foundBlocks;
private static async Task Main(string[] args) private static async Task Main(string[] args)
{ {
@ -178,7 +181,8 @@ internal static class Program
Console.WriteLine(); Console.WriteLine();
Console.WriteLine( Console.WriteLine(
$"Stopped. Accepted: {Interlocked.Read(ref acceptedShares)}, " + $"Stopped. Accepted: {Interlocked.Read(ref acceptedShares)}, " +
$"Rejected: {Interlocked.Read(ref rejectedShares)}"); $"Rejected: {Interlocked.Read(ref rejectedShares)}, " +
$"Blocks: {Interlocked.Read(ref foundBlocks)}");
} }
private static async Task RunSessionAsync( private static async Task RunSessionAsync(
@ -293,8 +297,11 @@ internal static class Program
var height = parameters[0].GetInt64(); var height = parameters[0].GetInt64();
var hash = parameters[1].GetString() ?? ""; var hash = parameters[1].GetString() ?? "";
var blockCount =
Interlocked.Increment(ref foundBlocks);
WriteColor( WriteColor(
$"*** BLOCK FOUND! Height {height} ***", $"*** BLOCK FOUND! #{blockCount} Height {height} ***",
ConsoleColor.Magenta); ConsoleColor.Magenta);
WriteColor( WriteColor(
@ -352,8 +359,11 @@ internal static class Program
var height = parameters[0].GetInt64(); var height = parameters[0].GetInt64();
var hash = parameters[1].GetString() ?? ""; var hash = parameters[1].GetString() ?? "";
var blockCount =
Interlocked.Increment(ref foundBlocks);
WriteColor( WriteColor(
$"*** BLOCK FOUND! Height {height} ***", $"*** BLOCK FOUND! #{blockCount} Height {height} ***",
ConsoleColor.Magenta); ConsoleColor.Magenta);
WriteColor( WriteColor(
@ -558,6 +568,26 @@ internal static class Program
cts?.Dispose(); cts?.Dispose();
} }
private static string NextExtraNonce2()
{
if(extraNonce2Size <= 0)
return string.Empty;
var value = unchecked(
(ulong)(Interlocked.Increment(ref nextExtraNonce2) - 1));
var width = extraNonce2Size * 2;
var hex = value.ToString(
"x",
CultureInfo.InvariantCulture);
if(hex.Length > width)
hex = hex[^width..];
return hex.PadLeft(width, '0');
}
private static MiningJob ParseJob(JsonElement job) private static MiningJob ParseJob(JsonElement job)
{ {
var jobId = job[0].GetString() var jobId = job[0].GetString()
@ -586,7 +616,7 @@ internal static class Program
var seed = job[9].GetString() var seed = job[9].GetString()
?? throw new Exception("Invalid RandomX seed"); ?? throw new Exception("Invalid RandomX seed");
var extraNonce2 = new string('0', extraNonce2Size * 2); var extraNonce2 = NextExtraNonce2();
var coinbaseHex = var coinbaseHex =
coinbase1 + coinbase1 +
@ -652,12 +682,6 @@ internal static class Program
tasks[workerIndex] = Task.Run( tasks[workerIndex] = Task.Run(
async () => async () =>
{ {
for(ulong n = (ulong)localIndex;
n <= uint.MaxValue && !ct.IsCancellationRequested;
n += (ulong)threadCount)
{
var nonce = (uint)n;
#pragma warning disable 618 #pragma warning disable 618
var header = new BlockHeader var header = new BlockHeader
#pragma warning restore 618 #pragma warning restore 618
@ -668,15 +692,37 @@ internal static class Program
BlockTime = DateTimeOffset.FromUnixTimeSeconds(job.NTime), BlockTime = DateTimeOffset.FromUnixTimeSeconds(job.NTime),
Bits = new Target( Bits = new Target(
Encoders.Hex.DecodeData(job.BitsHex)), Encoders.Hex.DecodeData(job.BitsHex)),
Nonce = nonce Nonce = 0
}; };
// Bitcoin/Vexta block header is always 80 bytes.
// Only the final 4 bytes (nonce) change inside the hot loop.
var headerBytes = header.ToBytes(); var headerBytes = header.ToBytes();
var rxHash = new byte[32]; var rxHash = new byte[32];
VextaRandomX.CalculateHash( using var vmLease =
VextaRandomX.AcquireVm(
Realm, Realm,
job.Seed, job.Seed,
ct)
?? throw new InvalidOperationException(
"Unable to acquire RandomX VM.");
if(headerBytes.Length != 80)
throw new InvalidOperationException(
$"Unexpected block-header size: {headerBytes.Length}");
for(ulong n = (ulong)localIndex;
n <= uint.MaxValue && !ct.IsCancellationRequested;
n += (ulong)threadCount)
{
var nonce = (uint)n;
BinaryPrimitives.WriteUInt32LittleEndian(
headerBytes.AsSpan(76, 4),
nonce);
vmLease.CalculateHash(
headerBytes, headerBytes,
rxHash); rxHash);
@ -817,7 +863,8 @@ internal static class Program
WriteColor( WriteColor(
$"HASHRATE {FormatHashrate(rate)} " + $"HASHRATE {FormatHashrate(rate)} " +
$"A:{Interlocked.Read(ref acceptedShares)} " + $"A:{Interlocked.Read(ref acceptedShares)} " +
$"R:{Interlocked.Read(ref rejectedShares)}", $"R:{Interlocked.Read(ref rejectedShares)} " +
$"Blocks:{Interlocked.Read(ref foundBlocks)}",
ConsoleColor.Cyan); ConsoleColor.Cyan);
lastHashes = hashes; lastHashes = hashes;
@ -909,7 +956,7 @@ internal static class Program
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Vexta CPU Miner 0.1.0"); Console.WriteLine("Vexta CPU Miner 0.1.1");
Console.ForegroundColor = ConsoleColor.DarkGray; Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("Copyright (c) 2026 Vexta Project"); Console.WriteLine("Copyright (c) 2026 Vexta Project");

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -162,6 +163,47 @@ public static unsafe class VextaRandomX
private static readonly Dictionary<string, Dictionary<string, SeedContext>> Realms = new(); private static readonly Dictionary<string, Dictionary<string, SeedContext>> Realms = new();
private static SeedContext BuildSeedContext(
ReadOnlySpan<byte> key,
RandomXFlags flags,
int vmCount)
{
var context = new GenContext
{
VmCount = vmCount
};
var vms = new BlockingCollection<RxVm>();
try
{
context.Init(key, flags);
Parallel.For(0, vmCount, _ =>
{
var vm = new RxVm();
vm.Init(flags, context.Cache, context.Dataset);
vms.Add(vm);
});
return new SeedContext
{
Context = context,
Vms = vms
};
}
catch
{
while(vms.TryTake(out var vm))
vm.Dispose();
vms.Dispose();
context.Dispose();
throw;
}
}
public static void CreateSeed( public static void CreateSeed(
string realm, string realm,
string seedHex, string seedHex,
@ -181,29 +223,41 @@ public static unsafe class VextaRandomX
if(vmCount == -1) if(vmCount == -1)
vmCount = Environment.ProcessorCount; vmCount = Environment.ProcessorCount;
var flags = GetFlags() | RandomXFlags.FullMem; var key = Convert.FromHexString(seedHex);
var context = new GenContext var baseFlags =
(GetFlags() | RandomXFlags.FullMem) &
~RandomXFlags.LargePages;
SeedContext seed;
try
{ {
VmCount = vmCount var hugePageFlags =
}; baseFlags | RandomXFlags.LargePages;
context.Init(Convert.FromHexString(seedHex), flags); seed = BuildSeedContext(
key,
hugePageFlags,
vmCount);
var vms = new BlockingCollection<RxVm>(); Console.WriteLine("Huge Pages: ENABLED");
}
Parallel.For(0, vmCount, _ => catch(Exception ex)
{ {
var vm = new RxVm(); Console.WriteLine(
vm.Init(flags, context.Cache, context.Dataset); $"Huge Pages: unavailable ({ex.Message})");
vms.Add(vm);
});
seeds[seedHex] = new SeedContext Console.WriteLine(
{ "Huge Pages: falling back to normal memory pages.");
Context = context,
Vms = vms seed = BuildSeedContext(
}; key,
baseFlags,
vmCount);
}
seeds[seedHex] = seed;
} }
} }
@ -230,6 +284,76 @@ public static unsafe class VextaRandomX
seed.Vms.Dispose(); seed.Vms.Dispose();
} }
public sealed class VmLease : IDisposable
{
private readonly SeedContext seed;
private RxVm? vm;
private VmLease(
SeedContext seed,
RxVm vm)
{
this.seed = seed;
this.vm = vm;
}
public void CalculateHash(
ReadOnlySpan<byte> data,
Span<byte> result)
{
if(result.Length < 32)
throw new ArgumentException(
"RandomX result buffer must be at least 32 bytes.",
nameof(result));
var current = vm
?? throw new ObjectDisposedException(nameof(VmLease));
current.CalculateHash(data, result);
}
public void Dispose()
{
var current =
Interlocked.Exchange(ref vm, null);
if(current is not null)
seed.Vms.Add(current);
}
internal static VmLease? Acquire(
string realm,
string seedHex,
CancellationToken cancellationToken)
{
SeedContext? seed = null;
lock(Realms)
{
if(Realms.TryGetValue(realm, out var seeds))
seeds.TryGetValue(seedHex, out seed);
}
if(seed is null)
return null;
var vm = seed.Vms.Take(cancellationToken);
return new VmLease(seed, vm);
}
}
public static VmLease? AcquireVm(
string realm,
string seedHex,
CancellationToken cancellationToken)
{
return VmLease.Acquire(
realm,
seedHex,
cancellationToken);
}
public static void CalculateHash( public static void CalculateHash(
string realm, string realm,
string seedHex, string seedHex,