diff --git a/GetworkStratumProxy.ConsoleApp/Program.cs b/GetworkStratumProxy.ConsoleApp/Program.cs
index e062525..29955fe 100644
--- a/GetworkStratumProxy.ConsoleApp/Program.cs
+++ b/GetworkStratumProxy.ConsoleApp/Program.cs
@@ -33,10 +33,12 @@ private static async Task OptionParseOkAsync(CommandLineOptions options)
};
using (BaseNode pollingNode = new PollingNode(options.RpcUri, options.PollInterval))
- using (IProxy proxy = new EthProxy(pollingNode, options.StratumIPAddress, options.StratumPort))
+ using (IProxy ethProxy = new EthProxy(pollingNode, options.StratumIPAddress, options.StratumPort))
+ using (IProxy nicehashProxy = new NicehashProxy(pollingNode, options.StratumIPAddress, options.StratumPort + 1))
{
pollingNode.Start();
- proxy.Start();
+ ethProxy.Start();
+ nicehashProxy.Start();
while (IsRunning)
{
diff --git a/GetworkStratumProxy/Constants.cs b/GetworkStratumProxy/Constants.cs
index f53faad..c0fb721 100644
--- a/GetworkStratumProxy/Constants.cs
+++ b/GetworkStratumProxy/Constants.cs
@@ -1,7 +1,36 @@
-namespace GetworkStratumProxy
+using Nethereum.Hex.HexTypes;
+using System.Numerics;
+
+namespace GetworkStratumProxy
{
internal class Constants
{
public const int WorkHeaderCharactersPrefixCount = 10;
+
+ private static readonly BigInteger MaxTarget = BigInteger.Pow(16, 64) - 1;
+ private const long BaseDifficultyOfOne = 4294967296;
+
+ public static decimal GetDifficultyFromTarget(HexBigInteger currentTarget)
+ {
+ var calculatedDifficulty = MaxTarget / currentTarget.Value;
+ return (decimal)calculatedDifficulty / BaseDifficultyOfOne;
+ }
+
+ public static HexBigInteger GetTargetFromDifficulty(decimal difficulty)
+ {
+ var calculatedDifficulty = difficulty * BaseDifficultyOfOne;
+ return new HexBigInteger(MaxTarget / (BigInteger)calculatedDifficulty);
+ }
+
+ public static decimal GetDifficultySize(HexBigInteger currentTarget)
+ {
+ var targetDiff = GetDifficultyFromTarget(currentTarget);
+ return BaseDifficultyOfOne * targetDiff;
+ }
+
+ public static decimal GetDifficultySize(decimal difficulty)
+ {
+ return BaseDifficultyOfOne * difficulty;
+ }
}
}
diff --git a/GetworkStratumProxy/Proxy/Client/NicehashProxyClient.cs b/GetworkStratumProxy/Proxy/Client/NicehashProxyClient.cs
index 6128c2c..6a21d7f 100644
--- a/GetworkStratumProxy/Proxy/Client/NicehashProxyClient.cs
+++ b/GetworkStratumProxy/Proxy/Client/NicehashProxyClient.cs
@@ -1,17 +1,156 @@
-using System;
+using GetworkStratumProxy.Extension;
+using GetworkStratumProxy.Rpc;
+using GetworkStratumProxy.Rpc.Nicehash;
+using Nethereum.Hex.HexTypes;
+using Nethereum.RPC.Eth.Mining;
+using StreamJsonRpc;
+using System;
+using System.IO;
using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
namespace GetworkStratumProxy.Proxy.Client
{
public sealed class NicehashProxyClient : BaseProxyClient
{
- public NicehashProxyClient(TcpClient tcpClient) : base(tcpClient)
+ public const string ProtocolVersion = "EthereumStratum/1.0.0";
+
+ private IEthGetWork GetWorkService { get; set; }
+ private IEthSubmitWork SubmitWorkService { get; set; }
+
+ private HexBigInteger CurrentJobId { get; set; } = new HexBigInteger(0);
+ private bool XNSub { get; set; } = false;
+
+ private event EventHandler CanAcceptWorkEvent;
+
+ private EthWork CurrentWork;
+
+ private decimal PreviousDifficulty { get; set; } = -1;
+
+ public NicehashProxyClient(TcpClient tcpClient, IEthGetWork getWorkService, IEthSubmitWork submitWorkService) : base(tcpClient)
{
+ var networkStream = TcpClient.GetStream();
+ BackgroundWorkWriter = new StreamWriter(networkStream);
+
+ GetWorkService = getWorkService;
+ SubmitWorkService = submitWorkService;
+
+ CanAcceptWorkEvent += (o, e) =>
+ {
+ // TO-DO: very hacky workaround, not a surefire fix for race condition
+ System.Threading.Thread.Sleep(1000); // Hold this event so authorise can go through
+ // Fix for when miner subscribes but does not get work refresh triggered
+ string[] ethGetWorkRaw = getWorkService.SendRequestAsync().Result;
+ var ethWork = new EthWork(ethGetWorkRaw);
+ NewJobNotificationEvent(null, ethWork);
+ };
}
- public override void Dispose()
+ ///
+ /// Blocking listen and respond to EthProxy RPC messages.
+ ///
+ internal async Task StartListeningAsync()
+ {
+ using var networkStream = TcpClient.GetStream();
+ using var formatter = new JsonMessageFormatter { ProtocolVersion = new Version(1, 0) };
+ using var handler = new NewLineDelimitedMessageHandler(networkStream, networkStream, formatter);
+ using var jsonRpc = new JsonRpc(handler, this);
+
+ jsonRpc.StartListening();
+ await jsonRpc.Completion;
+ ConsoleHelper.Log(GetType().Name, $"RPC service stopped for {Endpoint}", LogLevel.Debug);
+ }
+
+ internal void NewJobNotificationEvent(object sender, EthWork e)
+ {
+ if (StratumState.HasFlag(StratumState.Authorised) && StratumState.HasFlag(StratumState.Subscribed))
+ {
+ CurrentWork = e;
+
+ // e[] = { headerHash, seedHash, Target }
+ string headerHash = CurrentWork.Header.HexValue;
+ string seedHash = CurrentWork.Seed.HexValue;
+ string target = CurrentWork.Target.HexValue;
+ bool clearJobQueue = true;
+
+ decimal difficulty = Constants.GetDifficultyFromTarget(new HexBigInteger(target));
+ if (PreviousDifficulty == -1 || difficulty != PreviousDifficulty)
+ {
+ var setDifficultyNotification = new SetDifficultyNotification(difficulty);
+ ConsoleHelper.Log(GetType().Name, $"Setting mining difficulty " +
+ $"({target}) to {Endpoint}", LogLevel.Information);
+ Notify(setDifficultyNotification);
+ PreviousDifficulty = difficulty;
+ }
+
+ var miningNotifyNotification = new MiningNotifyNotification(CurrentJobId.HexValue, seedHash, headerHash, clearJobQueue);
+ ConsoleHelper.Log(GetType().Name, $"Sending job " +
+ $"({headerHash[..Constants.WorkHeaderCharactersPrefixCount]}...) to {Endpoint}", LogLevel.Information);
+ Notify(miningNotifyNotification);
+
+ CurrentJobId = (CurrentJobId.Value + 1).ToHexBigInteger();
+ }
+ }
+
+ [JsonRpcMethod("mining.subscribe")]
+ public object[] Subscribe(string minerName, string protocol)
+ {
+ ConsoleHelper.Log(GetType().Name, $"Miner {Endpoint} subscribe ({minerName}-{protocol})", LogLevel.Debug);
+ if (protocol != ProtocolVersion)
+ {
+ throw new Exception($"Unsupported protocol \"{protocol}\"");
+ }
+
+ // No login handler therefore always successful
+ StratumState |= StratumState.Subscribed;
+ ConsoleHelper.Log(GetType().Name, $"Miner {Endpoint} successfully subscribed", LogLevel.Information);
+
+ byte[] connectionIdBytes = new byte[16];
+ RandomNumberGenerator.Fill(connectionIdBytes);
+ string connectionId = Convert.ToHexString(connectionIdBytes);
+
+ string[] miningParams = new string[] { "mining.notify", connectionId, ProtocolVersion };
+ string extraNonce = ""; // Let the client infer their own nonce
+
+ object[] response = new object[] { miningParams, extraNonce };
+ ConsoleHelper.Log(GetType().Name, $"Sending subscribe response to {Endpoint}", LogLevel.Debug);
+ return response;
+ }
+
+ [JsonRpcMethod("mining.authorize")]
+ public bool Authorise(string username, string password)
+ {
+ ConsoleHelper.Log(GetType().Name, $"Miner {Endpoint} login ({username}:{password})", LogLevel.Debug);
+
+ // No login handler therefore always successful
+ StratumState |= StratumState.Authorised;
+ ConsoleHelper.Log(GetType().Name, $"Miner {Endpoint} logged in successfully", LogLevel.Information);
+
+ ConsoleHelper.Log(GetType().Name, $"Sending login response to {Endpoint}", LogLevel.Debug);
+
+ CanAcceptWorkEvent(null, null); // Trigger sending of work
+ return StratumState.HasFlag(StratumState.Authorised) &&
+ StratumState.HasFlag(StratumState.Subscribed);
+ }
+
+ [JsonRpcMethod("mining.extranonce.subscribe")]
+ public bool ExtraNonceSubscribe()
+ {
+ ConsoleHelper.Log(GetType().Name, $"Miner {Endpoint} requested XNSUB", LogLevel.Debug);
+ XNSub = true;
+ return true;
+ }
+
+ [JsonRpcMethod("mining.submit")]
+ public bool Submit(string minerUser, string extraNonce, string foundNonce)
{
throw new NotImplementedException();
}
+
+ public override void Dispose()
+ {
+ TcpClient.Dispose();
+ }
}
}
diff --git a/GetworkStratumProxy/Proxy/NicehashProxy.cs b/GetworkStratumProxy/Proxy/NicehashProxy.cs
index 3e088de..b5f4488 100644
--- a/GetworkStratumProxy/Proxy/NicehashProxy.cs
+++ b/GetworkStratumProxy/Proxy/NicehashProxy.cs
@@ -1,6 +1,6 @@
-using GetworkStratumProxy.Node;
+using GetworkStratumProxy.Extension;
+using GetworkStratumProxy.Node;
using GetworkStratumProxy.Proxy.Client;
-using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
@@ -14,12 +14,29 @@ public class NicehashProxy : BaseProxy
public NicehashProxy(BaseNode node, IPAddress address, int port) : base(node, address, port)
{
- throw new NotImplementedException();
+
+ }
+
+ protected override async Task BeginClientSessionAsync(TcpClient client)
+ {
+ var endpoint = client.Client.RemoteEndPoint;
+ using NicehashProxyClient proxyClient = GetClientOrNew(client);
+ Node.NewWorkReceived += proxyClient.NewJobNotificationEvent; // Subscribe to new jobs
+ await proxyClient.StartListeningAsync(); // Blocking listen
+ Node.NewWorkReceived -= proxyClient.NewJobNotificationEvent; // Unsubscribe
+ ConsoleHelper.Log(GetType().Name, $"Client {endpoint} unsubscribed from jobs", LogLevel.Information);
}
- protected override Task BeginClientSessionAsync(TcpClient client)
+ private NicehashProxyClient GetClientOrNew(TcpClient tcpClient)
{
- throw new NotImplementedException();
+ if (!Clients.TryGetValue(tcpClient.Client.RemoteEndPoint, out NicehashProxyClient nicehashProxyClient))
+ {
+ // Remote endpoint not registered, add new client
+ ConsoleHelper.Log(GetType().Name, $"Registered new client {tcpClient.Client.RemoteEndPoint}", LogLevel.Debug);
+ nicehashProxyClient = new NicehashProxyClient(tcpClient, Node.Web3.Eth.Mining.GetWork, Node.Web3.Eth.Mining.SubmitWork);
+ Clients.TryAdd(tcpClient.Client.RemoteEndPoint, nicehashProxyClient);
+ }
+ return nicehashProxyClient;
}
}
}
diff --git a/GetworkStratumProxy/Rpc/JsonRpcMessage.cs b/GetworkStratumProxy/Rpc/JsonRpcMessage.cs
index 3c2b63c..288af80 100644
--- a/GetworkStratumProxy/Rpc/JsonRpcMessage.cs
+++ b/GetworkStratumProxy/Rpc/JsonRpcMessage.cs
@@ -5,9 +5,11 @@ namespace GetworkStratumProxy.Rpc
public abstract class JsonRpcMessage
{
[JsonPropertyName("id")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public int? Id { get; set; }
[JsonPropertyName("jsonrpc")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string JsonRpc { get; set; }
}
}
diff --git a/GetworkStratumProxy/Rpc/Nicehash/JsonRpcNotification.cs b/GetworkStratumProxy/Rpc/Nicehash/JsonRpcNotification.cs
new file mode 100644
index 0000000..97de456
--- /dev/null
+++ b/GetworkStratumProxy/Rpc/Nicehash/JsonRpcNotification.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace GetworkStratumProxy.Rpc.Nicehash
+{
+ public abstract class JsonRpcNotification : JsonRpcResponse
+ {
+ [JsonPropertyName("method")]
+ public string Method { get; set; }
+
+ [JsonPropertyName("params")]
+ public object[] Params { get; set; }
+
+ public JsonRpcNotification()
+ {
+ Id = null;
+ }
+ }
+}
diff --git a/GetworkStratumProxy/Rpc/Nicehash/MiningNotifyNotification.cs b/GetworkStratumProxy/Rpc/Nicehash/MiningNotifyNotification.cs
new file mode 100644
index 0000000..7c5d143
--- /dev/null
+++ b/GetworkStratumProxy/Rpc/Nicehash/MiningNotifyNotification.cs
@@ -0,0 +1,20 @@
+using Nethereum.Hex.HexTypes;
+
+namespace GetworkStratumProxy.Rpc.Nicehash
+{
+ public sealed class MiningNotifyNotification : JsonRpcNotification
+ {
+ public MiningNotifyNotification(string jobId, string seedHash, string headerHash, bool clearJobQueue)
+ {
+ Method = "mining.notify";
+
+ Params = new object[]
+ {
+ jobId.Replace("0x", ""),
+ new HexBigInteger(seedHash).HexValue.Replace("0x", ""),
+ new HexBigInteger(headerHash).HexValue.Replace("0x", ""),
+ clearJobQueue
+ };
+ }
+ }
+}
diff --git a/GetworkStratumProxy/Rpc/Nicehash/SetDifficultyNotification.cs b/GetworkStratumProxy/Rpc/Nicehash/SetDifficultyNotification.cs
new file mode 100644
index 0000000..74fa41a
--- /dev/null
+++ b/GetworkStratumProxy/Rpc/Nicehash/SetDifficultyNotification.cs
@@ -0,0 +1,11 @@
+namespace GetworkStratumProxy.Rpc.Nicehash
+{
+ public sealed class SetDifficultyNotification : JsonRpcNotification
+ {
+ public SetDifficultyNotification(decimal difficulty)
+ {
+ Method = "mining.set_difficulty";
+ Params = new object[] { difficulty };
+ }
+ }
+}