Release Vexta CPU Miner v0.1.1
This commit is contained in:
parent
d39c284285
commit
17949bc516
2 changed files with 213 additions and 42 deletions
95
Program.cs
95
Program.cs
|
|
@ -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,31 +682,47 @@ internal static class Program
|
||||||
tasks[workerIndex] = Task.Run(
|
tasks[workerIndex] = Task.Run(
|
||||||
async () =>
|
async () =>
|
||||||
{
|
{
|
||||||
|
#pragma warning disable 618
|
||||||
|
var header = new BlockHeader
|
||||||
|
#pragma warning restore 618
|
||||||
|
{
|
||||||
|
Version = unchecked((int)job.Version),
|
||||||
|
HashPrevBlock = uint256.Parse(job.PrevDisplay),
|
||||||
|
HashMerkleRoot = new uint256(job.MerkleRoot),
|
||||||
|
BlockTime = DateTimeOffset.FromUnixTimeSeconds(job.NTime),
|
||||||
|
Bits = new Target(
|
||||||
|
Encoders.Hex.DecodeData(job.BitsHex)),
|
||||||
|
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 rxHash = new byte[32];
|
||||||
|
|
||||||
|
using var vmLease =
|
||||||
|
VextaRandomX.AcquireVm(
|
||||||
|
Realm,
|
||||||
|
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;
|
for(ulong n = (ulong)localIndex;
|
||||||
n <= uint.MaxValue && !ct.IsCancellationRequested;
|
n <= uint.MaxValue && !ct.IsCancellationRequested;
|
||||||
n += (ulong)threadCount)
|
n += (ulong)threadCount)
|
||||||
{
|
{
|
||||||
var nonce = (uint)n;
|
var nonce = (uint)n;
|
||||||
|
|
||||||
#pragma warning disable 618
|
BinaryPrimitives.WriteUInt32LittleEndian(
|
||||||
var header = new BlockHeader
|
headerBytes.AsSpan(76, 4),
|
||||||
#pragma warning restore 618
|
nonce);
|
||||||
{
|
|
||||||
Version = unchecked((int)job.Version),
|
|
||||||
HashPrevBlock = uint256.Parse(job.PrevDisplay),
|
|
||||||
HashMerkleRoot = new uint256(job.MerkleRoot),
|
|
||||||
BlockTime = DateTimeOffset.FromUnixTimeSeconds(job.NTime),
|
|
||||||
Bits = new Target(
|
|
||||||
Encoders.Hex.DecodeData(job.BitsHex)),
|
|
||||||
Nonce = nonce
|
|
||||||
};
|
|
||||||
|
|
||||||
var headerBytes = header.ToBytes();
|
vmLease.CalculateHash(
|
||||||
var rxHash = new byte[32];
|
|
||||||
|
|
||||||
VextaRandomX.CalculateHash(
|
|
||||||
Realm,
|
|
||||||
job.Seed,
|
|
||||||
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");
|
||||||
|
|
|
||||||
158
VextaRandomX.cs
158
VextaRandomX.cs
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue