Skip to content
6 changes: 4 additions & 2 deletions GetworkStratumProxy.ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
31 changes: 30 additions & 1 deletion GetworkStratumProxy/Constants.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
145 changes: 142 additions & 3 deletions GetworkStratumProxy/Proxy/Client/NicehashProxyClient.cs
Original file line number Diff line number Diff line change
@@ -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()
/// <summary>
/// Blocking listen and respond to EthProxy RPC messages.
/// </summary>
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();
}
}
}
27 changes: 22 additions & 5 deletions GetworkStratumProxy/Proxy/NicehashProxy.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,12 +14,29 @@ public class NicehashProxy : BaseProxy<NicehashProxyClient>

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;
}
}
}
2 changes: 2 additions & 0 deletions GetworkStratumProxy/Rpc/JsonRpcMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
18 changes: 18 additions & 0 deletions GetworkStratumProxy/Rpc/Nicehash/JsonRpcNotification.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
20 changes: 20 additions & 0 deletions GetworkStratumProxy/Rpc/Nicehash/MiningNotifyNotification.cs
Original file line number Diff line number Diff line change
@@ -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
};
}
}
}
11 changes: 11 additions & 0 deletions GetworkStratumProxy/Rpc/Nicehash/SetDifficultyNotification.cs
Original file line number Diff line number Diff line change
@@ -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 };
}
}
}