From e16a3c70459805861191a8dca216f4c33bbfa921 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Fri, 23 Feb 2018 21:24:35 +0100 Subject: [PATCH 01/17] Stage 1 --- src/MiningCore/MiningCore.csproj | 1 - src/MiningCore/Stratum/StratumClient.cs | 125 ++++++++----------- src/MiningCore/Stratum/StratumServer.cs | 159 +++++++++++------------- 3 files changed, 128 insertions(+), 157 deletions(-) diff --git a/src/MiningCore/MiningCore.csproj b/src/MiningCore/MiningCore.csproj index 74b9172f..34d9a521 100644 --- a/src/MiningCore/MiningCore.csproj +++ b/src/MiningCore/MiningCore.csproj @@ -56,7 +56,6 @@ - diff --git a/src/MiningCore/Stratum/StratumClient.cs b/src/MiningCore/Stratum/StratumClient.cs index e78d6922..47c0af79 100644 --- a/src/MiningCore/Stratum/StratumClient.cs +++ b/src/MiningCore/Stratum/StratumClient.cs @@ -20,17 +20,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Buffers; -using System.Collections.Concurrent; using System.IO; using System.Net; +using System.Net.Sockets; using System.Reactive.Disposables; +using System.Threading.Tasks.Dataflow; using Autofac; using MiningCore.Buffers; using MiningCore.JsonRpc; using MiningCore.Mining; using MiningCore.Time; using MiningCore.Util; -using NetUV.Core.Handles; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using NLog; @@ -40,13 +40,18 @@ namespace MiningCore.Stratum { public class StratumClient { + public StratumClient(IPEndPoint endpointConfig, string connectionId) + { + PoolEndpoint = endpointConfig; + ConnectionId = connectionId; + } + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); private const int MaxInboundRequestLength = 8192; private const int MaxOutboundRequestLength = 0x4000; - private ConcurrentQueue> sendQueue; - private Async sendQueueDrainer; + private BufferBlock> sendQueue; private readonly PooledLineBuffer plb = new PooledLineBuffer(logger, MaxInboundRequestLength); private IDisposable subscription; private bool isAlive = true; @@ -59,43 +64,28 @@ public class StratumClient #region API-Surface - public void Init(Loop loop, Tcp tcp, IComponentContext ctx, IMasterClock clock, - IPEndPoint endpointConfig, string connectionId, - Action> onNext, Action onCompleted, Action onError) + public void Init(Socket socket, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) { - PoolEndpoint = endpointConfig; - ConnectionId = connectionId; - RemoteEndpoint = tcp.GetPeerEndPoint(); + RemoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; // initialize send queue - sendQueue = new ConcurrentQueue>(); - sendQueueDrainer = loop.CreateAsync(DrainSendQueue); - sendQueueDrainer.UserToken = tcp; + sendQueue = new BufferBlock>(); // cleanup preparation - var sub = Disposable.Create(() => + subscription = Disposable.Create(() => { - if (tcp.IsValid) + if (isAlive) { logger.Debug(() => $"[{ConnectionId}] Last subscriber disconnected from receiver stream"); isAlive = false; - tcp.Shutdown(); + socket.Close(); } }); - // ensure subscription is disposed on loop thread - var disposer = loop.CreateAsync((handle) => - { - sub.Dispose(); - - handle.Dispose(); - }); - - subscription = Disposable.Create(() => { disposer.Send(); }); - // go - Receive(tcp, clock, onNext, onCompleted, onError); + DoReceive(socket, clock, onNext, onCompleted, onError); + DoSend(socket, onError); } public string ConnectionId { get; private set; } @@ -235,54 +225,50 @@ public JsonRpcRequest DeserializeRequest(PooledArraySegment data) #endregion // API-Surface - private void Receive(Tcp tcp, IMasterClock clock, + private async void DoReceive(Socket socket, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) { - tcp.OnRead((handle, buffer) => + while (isAlive) { - // onAccept - using (buffer) + var buf = ArrayPool.Shared.Rent(0x10000); + var aseg = new ArraySegment(buf); + + try { - if (buffer.Count == 0 || !isAlive) + var cb = await socket.ReceiveAsync(aseg, SocketFlags.None); + + if (cb == 0 || !isAlive) + { + onCompleted(); return; + } LastReceive = clock.Now; - plb.Receive(buffer, buffer.Count, - (src, dst, count) => src.ReadBytes(dst, count), + plb.Receive(buf, cb, + (src, dst, count) => Array.Copy(src, dst, count), onNext, onError); } - }, (handle, ex) => - { - // onError - onError(ex); - }, handle => - { - // onCompleted - isAlive = false; - onCompleted(); - - // release handles - sendQueueDrainer.UserToken = null; - sendQueueDrainer.Dispose(); - - // empty queues - while (sendQueue.TryDequeue(out var fragment)) - fragment.Dispose(); - plb.Dispose(); + catch(Exception ex) + { + onError(ex); + break; + } - handle.CloseHandle(); - }); + finally + { + ArrayPool.Shared.Return(buf); + } + } } private void SendInternal(PooledArraySegment buffer) { try { - sendQueue.Enqueue(buffer); - sendQueueDrainer.Send(); + sendQueue.Post(buffer); } catch (ObjectDisposedException) @@ -291,31 +277,28 @@ private void SendInternal(PooledArraySegment buffer) } } - private void DrainSendQueue(Async handle) + private async void DoSend(Socket socket, Action onError) { - try + while(isAlive) { - var tcp = (Tcp)handle.UserToken; - - if (tcp?.IsValid == true && !tcp.IsClosing && tcp.IsWritable && sendQueue != null) + try { - var queueSize = sendQueue.Count; - if (queueSize >= 256) - logger.Warn(() => $"[{ConnectionId}] Send queue backlog now at {queueSize}"); - - while (sendQueue.TryDequeue(out var segment)) + while (await sendQueue.OutputAvailableAsync()) { + var segment = await sendQueue.ReceiveAsync(); + using (segment) { - tcp.QueueWrite(segment.Array, 0, segment.Size); + await socket.SendAsync(new ArraySegment(segment.Array, segment.Offset, segment.Size), SocketFlags.None); } } } - } - catch (Exception ex) - { - logger.Error(ex); + catch (Exception ex) + { + onError(ex); + break; + } } } } diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 29469114..9d9cf55d 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -19,10 +19,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Reactive; using System.Threading; using System.Threading.Tasks; @@ -33,8 +33,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using MiningCore.JsonRpc; using MiningCore.Time; using MiningCore.Util; -using NetUV.Core.Handles; -using NetUV.Core.Native; +using NetMQ; using Newtonsoft.Json; using NLog; using Contract = MiningCore.Contracts.Contract; @@ -56,7 +55,7 @@ protected StratumServer(IComponentContext ctx, IMasterClock clock) protected readonly IComponentContext ctx; protected readonly IMasterClock clock; - protected readonly Dictionary ports = new Dictionary(); + protected readonly Dictionary ports = new Dictionary(); protected ClusterConfig clusterConfig; protected IBanManager banManager; protected bool disableConnectionLogging = false; @@ -71,37 +70,34 @@ public void StartListeners(string id, params IPEndPoint[] stratumPorts) // every port gets serviced by a dedicated loop thread foreach(var endpoint in stratumPorts) { - var thread = new Thread(_ => + var thread = new Thread(async _ => { - var loop = new Loop(); + var server = new Socket(SocketType.Stream, ProtocolType.Tcp); + server.Bind(endpoint); + server.Listen(512); - var listener = loop - .CreateTcp() - .NoDelay(true) - .SimultaneousAccepts(false) - .Listen(endpoint, (con, ex) => - { - if (ex == null) - OnClientConnected(con, endpoint, loop); - else - logger.Error(() => $"[{LogCat}] Connection error state: {ex.Message}"); - }); - - lock(ports) + lock (ports) { - ports[endpoint.Port] = listener; + ports[endpoint.Port] = server; } logger.Info(() => $"[{LogCat}] Stratum port {endpoint.Address}:{endpoint.Port} online"); - try + while (true) { - loop.RunDefault(); - } + try + { + var client = await server.AcceptAsync(); - catch(Exception ex) - { - logger.Error(ex, () => Thread.CurrentThread.Name); + #pragma warning disable 4014 + OnClientConnected(client, endpoint); + #pragma warning restore 4014 + } + + catch (Exception ex) + { + logger.Error(ex, () => Thread.CurrentThread.Name); + } } }) { Name = $"UvLoopThread {id}:{endpoint.Port}" }; @@ -117,28 +113,24 @@ public void StopListeners() for(int i = 0; i < portValues.Length; i++) { - var listener = portValues[i]; + var socket = portValues[i]; - listener.Shutdown((tcp, ex) => - { - if (tcp?.IsValid == true) - tcp.Dispose(); - }); + socket.Dispose(); } } } - private void OnClientConnected(Tcp con, IPEndPoint endpointConfig, Loop loop) + private void OnClientConnected(Socket socket, IPEndPoint endpointConfig) { try { - var remoteEndPoint = con.GetPeerEndPoint(); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; // get rid of banned clients as early as possible if (banManager?.IsBanned(remoteEndPoint.Address) == true) { logger.Debug(() => $"[{LogCat}] Disconnecting banned ip {remoteEndPoint.Address}"); - con.Dispose(); + socket.Dispose(); return; } @@ -146,23 +138,24 @@ private void OnClientConnected(Tcp con, IPEndPoint endpointConfig, Loop loop) logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndPoint.Address}:{remoteEndPoint.Port}"); // setup client connection - con.KeepAlive(true, 1); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); // setup client - var client = new StratumClient(); - - client.Init(loop, con, ctx, clock, endpointConfig, connectionId, - data => OnReceive(client, data), - () => OnReceiveComplete(client), - ex => OnReceiveError(client, ex)); + var client = new StratumClient(endpointConfig, connectionId); // register client - lock(clients) + lock (clients) { clients[connectionId] = client; } OnConnect(client); + + client.Init(socket, clock, + data => OnReceiveAsync(client, data), + () => OnReceiveComplete(client), + ex => OnReceiveError(client, ex)); } catch(Exception ex) @@ -171,68 +164,64 @@ private void OnClientConnected(Tcp con, IPEndPoint endpointConfig, Loop loop) } } - protected virtual void OnReceive(StratumClient client, PooledArraySegment data) + protected virtual async void OnReceiveAsync(StratumClient client, PooledArraySegment data) { - // get off of LibUV event-loop-thread immediately - Task.Run(async () => + using (data) { - using (data) - { - JsonRpcRequest request = null; + JsonRpcRequest request = null; - try + try + { + // boot pre-connected clients + if (banManager?.IsBanned(client.RemoteEndpoint.Address) == true) { - // boot pre-connected clients - if (banManager?.IsBanned(client.RemoteEndpoint.Address) == true) - { - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Disconnecting banned client @ {client.RemoteEndpoint.Address}"); - DisconnectClient(client); - return; - } + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Disconnecting banned client @ {client.RemoteEndpoint.Address}"); + DisconnectClient(client); + return; + } - // de-serialize - logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Received request data: {StratumConstants.Encoding.GetString(data.Array, 0, data.Size)}"); - request = client.DeserializeRequest(data); + // de-serialize + logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Received request data: {StratumConstants.Encoding.GetString(data.Array, 0, data.Size)}"); + request = client.DeserializeRequest(data); - // dispatch - if (request != null) - { - logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dispatching request '{request.Method}' [{request.Id}]"); - await OnRequestAsync(client, new Timestamped(request, clock.Now)); - } - - else - logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Unable to deserialize request"); + // dispatch + if (request != null) + { + logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dispatching request '{request.Method}' [{request.Id}]"); + await OnRequestAsync(client, new Timestamped(request, clock.Now)); } - catch (JsonReaderException jsonEx) - { - // junk received (no valid json) - logger.Error(() => $"[{LogCat}] [{client.ConnectionId}] Connection json error state: {jsonEx.Message}"); + else + logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Unable to deserialize request"); + } - if (clusterConfig.Banning?.BanOnJunkReceive.HasValue == false || clusterConfig.Banning?.BanOnJunkReceive == true) - { - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Banning client for sending junk"); - banManager?.Ban(client.RemoteEndpoint.Address, TimeSpan.FromMinutes(30)); - } - } + catch (JsonReaderException jsonEx) + { + // junk received (no valid json) + logger.Error(() => $"[{LogCat}] [{client.ConnectionId}] Connection json error state: {jsonEx.Message}"); - catch (Exception ex) + if (clusterConfig.Banning?.BanOnJunkReceive.HasValue == false || clusterConfig.Banning?.BanOnJunkReceive == true) { - if (request != null) - logger.Error(ex, () => $"[{LogCat}] [{client.ConnectionId}] Error processing request {request.Method} [{request.Id}]"); + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Banning client for sending junk"); + banManager?.Ban(client.RemoteEndpoint.Address, TimeSpan.FromMinutes(30)); } } - }); + + catch (Exception ex) + { + if (request != null) + logger.Error(ex, () => $"[{LogCat}] [{client.ConnectionId}] Error processing request {request.Method} [{request.Id}]"); + } + } } protected virtual void OnReceiveError(StratumClient client, Exception ex) { switch (ex) { - case OperationException opEx: + case SocketException opEx: // log everything but ECONNRESET which just indicates the client disconnecting - if (opEx.ErrorCode != ErrorCode.ECONNRESET) + if (opEx.SocketErrorCode != SocketError.ConnectionReset) logger.Error(() => $"[{LogCat}] [{client.ConnectionId}] Connection error state: {ex.Message}"); break; From 27e34d1ed7ec496e8aa4722bf6f26bad11c382bc Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Fri, 23 Feb 2018 21:57:32 +0100 Subject: [PATCH 02/17] Phase 2 --- src/MiningCore/Stratum/StratumClient.cs | 21 +++++++++-------- src/MiningCore/Stratum/StratumServer.cs | 31 ++++++++++++------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/MiningCore/Stratum/StratumClient.cs b/src/MiningCore/Stratum/StratumClient.cs index 47c0af79..e9a9096f 100644 --- a/src/MiningCore/Stratum/StratumClient.cs +++ b/src/MiningCore/Stratum/StratumClient.cs @@ -22,8 +22,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Buffers; using System.IO; using System.Net; +using System.Net.Security; using System.Net.Sockets; using System.Reactive.Disposables; +using System.Threading; using System.Threading.Tasks.Dataflow; using Autofac; using MiningCore.Buffers; @@ -64,9 +66,9 @@ public StratumClient(IPEndPoint endpointConfig, string connectionId) #region API-Surface - public void Init(Socket socket, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) + public void Init(Stream stream, IPEndPoint remoteEndpoint, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) { - RemoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; + RemoteEndpoint = remoteEndpoint; // initialize send queue sendQueue = new BufferBlock>(); @@ -79,13 +81,13 @@ public void Init(Socket socket, IMasterClock clock, Action $"[{ConnectionId}] Last subscriber disconnected from receiver stream"); isAlive = false; - socket.Close(); + stream.Close(); } }); // go - DoReceive(socket, clock, onNext, onCompleted, onError); - DoSend(socket, onError); + DoReceive(stream, clock, onNext, onCompleted, onError); + DoSend(stream, onError); } public string ConnectionId { get; private set; } @@ -225,17 +227,16 @@ public JsonRpcRequest DeserializeRequest(PooledArraySegment data) #endregion // API-Surface - private async void DoReceive(Socket socket, IMasterClock clock, + private async void DoReceive(Stream stream, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) { while (isAlive) { var buf = ArrayPool.Shared.Rent(0x10000); - var aseg = new ArraySegment(buf); try { - var cb = await socket.ReceiveAsync(aseg, SocketFlags.None); + var cb = await stream.ReadAsync(buf, 0, buf.Length); if (cb == 0 || !isAlive) { @@ -277,7 +278,7 @@ private void SendInternal(PooledArraySegment buffer) } } - private async void DoSend(Socket socket, Action onError) + private async void DoSend(Stream stream, Action onError) { while(isAlive) { @@ -289,7 +290,7 @@ private async void DoSend(Socket socket, Action onError) using (segment) { - await socket.SendAsync(new ArraySegment(segment.Array, segment.Offset, segment.Size), SocketFlags.None); + await stream.WriteAsync(segment.Array, segment.Offset, segment.Size); } } } diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 9d9cf55d..d1c945d7 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -87,10 +87,15 @@ public void StartListeners(string id, params IPEndPoint[] stratumPorts) { try { - var client = await server.AcceptAsync(); + var socket = await server.AcceptAsync(); + // prepare socket + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); + + // hand over #pragma warning disable 4014 - OnClientConnected(client, endpoint); + OnClientConnected(new NetworkStream(socket, true), (IPEndPoint)socket.RemoteEndPoint, endpoint); #pragma warning restore 4014 } @@ -115,34 +120,28 @@ public void StopListeners() { var socket = portValues[i]; - socket.Dispose(); + socket.Close(); } } } - private void OnClientConnected(Socket socket, IPEndPoint endpointConfig) + private void OnClientConnected(NetworkStream stream, IPEndPoint remoteEndpoint, IPEndPoint localEndpoint) { try { - var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; - // get rid of banned clients as early as possible - if (banManager?.IsBanned(remoteEndPoint.Address) == true) + if (banManager?.IsBanned(remoteEndpoint.Address) == true) { - logger.Debug(() => $"[{LogCat}] Disconnecting banned ip {remoteEndPoint.Address}"); - socket.Dispose(); + logger.Debug(() => $"[{LogCat}] Disconnecting banned ip {remoteEndpoint.Address}"); + stream.Close(); return; } var connectionId = CorrelationIdGenerator.GetNextId(); - logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndPoint.Address}:{remoteEndPoint.Port}"); - - // setup client connection - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000); - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); + logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); // setup client - var client = new StratumClient(endpointConfig, connectionId); + var client = new StratumClient(localEndpoint, connectionId); // register client lock (clients) @@ -152,7 +151,7 @@ private void OnClientConnected(Socket socket, IPEndPoint endpointConfig) OnConnect(client); - client.Init(socket, clock, + client.Init(stream, remoteEndpoint, clock, data => OnReceiveAsync(client, data), () => OnReceiveComplete(client), ex => OnReceiveError(client, ex)); From 7b2dc9018ec8a3b04560c189ba5160cead46bc43 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 24 Feb 2018 17:40:40 +0100 Subject: [PATCH 03/17] TLS WIP --- .../Blockchain/Bitcoin/BitcoinJobTests.cs | 8 +- .../Blockchain/Monero/MoneroJobTests.cs | 12 ++- src/MiningCore/Configuration/ClusterConfig.cs | 24 +++++ .../Configuration/ClusterConfigValidation.cs | 12 +++ src/MiningCore/Extensions/NumberExtensions.cs | 20 ++-- src/MiningCore/Mining/PoolBase.cs | 10 +- src/MiningCore/Stratum/StratumClient.cs | 90 ++++++++++------- src/MiningCore/Stratum/StratumServer.cs | 98 +++++++++---------- 8 files changed, 165 insertions(+), 109 deletions(-) diff --git a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs index 2282c2ea..399f50a2 100644 --- a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using MiningCore.Blockchain.Bitcoin; using MiningCore.Configuration; using MiningCore.Crypto; @@ -7,6 +8,7 @@ using MiningCore.Extensions; using MiningCore.Stratum; using MiningCore.Tests.Util; +using MiningCore.Time; using NBitcoin; using Newtonsoft.Json; using Xunit; @@ -25,7 +27,7 @@ public class BitcoinJobTests : TestBase [Fact] public void BitcoinJob_Should_Accept_Valid_Share() { - var worker = new StratumClient(); + var worker = new StratumClient(new StandardClock(), new IPEndPoint(IPAddress.Any, 3000), string.Empty); worker.SetContext(new BitcoinWorkerContext { @@ -61,9 +63,9 @@ public void BitcoinJob_Should_Accept_Valid_Share() [Fact] public void BitcoinJob_Should_Not_Accept_Invalid_Share() { - var worker = new StratumClient(); + var worker = new StratumClient(new StandardClock(), new IPEndPoint(IPAddress.Any, 3000), string.Empty); - worker.SetContext(new BitcoinWorkerContext + worker.SetContext(new BitcoinWorkerContext { Difficulty = 0.5, ExtraNonce1 = "01000058", diff --git a/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs b/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs index 39f2ac5e..6d440623 100644 --- a/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs @@ -1,7 +1,9 @@ -using MiningCore.Blockchain.Monero; +using System.Net; +using MiningCore.Blockchain.Monero; using MiningCore.Configuration; using MiningCore.Extensions; using MiningCore.Stratum; +using MiningCore.Time; using Newtonsoft.Json; using Xunit; @@ -15,9 +17,9 @@ public class BitcoinJobTests : TestBase [Fact] public void MoneroJob_Should_Accept_Valid_Share() { - var worker = new StratumClient(); + var worker = new StratumClient(new StandardClock(), new IPEndPoint(IPAddress.Any, 3000), string.Empty); - worker.SetContext(new MoneroWorkerContext + worker.SetContext(new MoneroWorkerContext { Difficulty = 1000, }); @@ -39,9 +41,9 @@ public void MoneroJob_Should_Accept_Valid_Share() [Fact] public void MoneroJob_Should_Not_Accept_Invalid_Share() { - var worker = new StratumClient(); + var worker = new StratumClient(new StandardClock(), new IPEndPoint(IPAddress.Any, 3000), string.Empty); - worker.SetContext(new MoneroWorkerContext + worker.SetContext(new MoneroWorkerContext { Difficulty = 1000, }); diff --git a/src/MiningCore/Configuration/ClusterConfig.cs b/src/MiningCore/Configuration/ClusterConfig.cs index 428d357d..ac7b69ff 100644 --- a/src/MiningCore/Configuration/ClusterConfig.cs +++ b/src/MiningCore/Configuration/ClusterConfig.cs @@ -116,6 +116,30 @@ public class PoolEndpoint public string Name { get; set; } public double Difficulty { get; set; } public VarDiffConfig VarDiff { get; set; } + + /// + /// Enable Transport layer security (TLS) + /// If set to true, you must specify values for either TlsPemFile or TlsPfxFile + /// If TlsPemFile does not include the private key, TlsKeyFile is also required + /// + public bool Tls { get; set; } + + /// + /// RFC's 1421 through 1424, path to a file that may include just the public certificate + /// or may include an entire certificate chain including public key and private key. + /// + public string TlsPemFile { get; set; } + + /// + /// This is a PEM formatted file containing just the private-key of the certificate + /// Not required if the private key is included in TlsPemFile + /// + public string TlsKeyFile { get; set; } + + /// + /// PKCS certificate file + /// + public string TlsPfxFile { get; set; } } public partial class VarDiffConfig diff --git a/src/MiningCore/Configuration/ClusterConfigValidation.cs b/src/MiningCore/Configuration/ClusterConfigValidation.cs index aefc7c57..b231474e 100644 --- a/src/MiningCore/Configuration/ClusterConfigValidation.cs +++ b/src/MiningCore/Configuration/ClusterConfigValidation.cs @@ -100,6 +100,18 @@ public PoolEndpointValidator() .GreaterThan(0) .WithMessage("Pool Endpoint: Difficulty missing or invalid"); + RuleFor(j => j.TlsPfxFile) + .NotNull() + .NotEmpty() + .When(j=> j.Tls && string.IsNullOrEmpty(j.TlsPemFile)) + .WithMessage("Pool Endpoint: Tls enabled but neither TlsPemFile nor TlsPfxFile specified"); + + RuleFor(j => j.TlsPemFile) + .NotNull() + .NotEmpty() + .When(j => j.Tls && string.IsNullOrEmpty(j.TlsPemFile)) + .WithMessage("Pool Endpoint: Tls enabled but neither TlsPemFile nor TlsPfxFile specified"); + RuleFor(j => j.VarDiff) .SetValidator(new VarDiffConfigValidator()) .When(x => x.VarDiff != null); diff --git a/src/MiningCore/Extensions/NumberExtensions.cs b/src/MiningCore/Extensions/NumberExtensions.cs index 6e0e1183..69fd5385 100644 --- a/src/MiningCore/Extensions/NumberExtensions.cs +++ b/src/MiningCore/Extensions/NumberExtensions.cs @@ -1,20 +1,20 @@ -/* +/* Copyright 2017 Coin Foundry (coinfoundry.org) Authors: Oliver Weichhold (oliver@weichhold.com) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ diff --git a/src/MiningCore/Mining/PoolBase.cs b/src/MiningCore/Mining/PoolBase.cs index 637ad9b2..51b508e4 100644 --- a/src/MiningCore/Mining/PoolBase.cs +++ b/src/MiningCore/Mining/PoolBase.cs @@ -430,11 +430,15 @@ public virtual async Task StartAsync() if (!poolConfig.ExternalStratum) { - var ipEndpoints = poolConfig.Ports.Keys - .Select(port => PoolEndpoint2IPEndpoint(port, poolConfig.Ports[port])) + var endpoints = poolConfig.Ports.Keys + .Select(port => + { + var endpointConfig = poolConfig.Ports[port]; + return (PoolEndpoint2IPEndpoint(port, endpointConfig), endpointConfig); + }) .ToArray(); - StartListeners(poolConfig.Id, ipEndpoints); + Start(poolConfig.Id, endpoints); } else diff --git a/src/MiningCore/Stratum/StratumClient.cs b/src/MiningCore/Stratum/StratumClient.cs index e9a9096f..7d389d0e 100644 --- a/src/MiningCore/Stratum/StratumClient.cs +++ b/src/MiningCore/Stratum/StratumClient.cs @@ -20,14 +20,11 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Buffers; +using System.Diagnostics; using System.IO; using System.Net; -using System.Net.Security; -using System.Net.Sockets; using System.Reactive.Disposables; -using System.Threading; using System.Threading.Tasks.Dataflow; -using Autofac; using MiningCore.Buffers; using MiningCore.JsonRpc; using MiningCore.Mining; @@ -42,8 +39,9 @@ namespace MiningCore.Stratum { public class StratumClient { - public StratumClient(IPEndPoint endpointConfig, string connectionId) + public StratumClient(IMasterClock clock, IPEndPoint endpointConfig, string connectionId) { + this.clock = clock; PoolEndpoint = endpointConfig; ConnectionId = connectionId; } @@ -53,6 +51,7 @@ public StratumClient(IPEndPoint endpointConfig, string connectionId) private const int MaxInboundRequestLength = 8192; private const int MaxOutboundRequestLength = 0x4000; + private readonly IMasterClock clock; private BufferBlock> sendQueue; private readonly PooledLineBuffer plb = new PooledLineBuffer(logger, MaxInboundRequestLength); private IDisposable subscription; @@ -66,11 +65,9 @@ public StratumClient(IPEndPoint endpointConfig, string connectionId) #region API-Surface - public void Init(Stream stream, IPEndPoint remoteEndpoint, IMasterClock clock, Action> onNext, Action onCompleted, Action onError) + public void Start(Stream stream, IPEndPoint remoteEndpoint, Action> onNext, Action onCompleted, Action onError) { RemoteEndpoint = remoteEndpoint; - - // initialize send queue sendQueue = new BufferBlock>(); // cleanup preparation @@ -81,12 +78,13 @@ public void Init(Stream stream, IPEndPoint remoteEndpoint, IMasterClock clock, A logger.Debug(() => $"[{ConnectionId}] Last subscriber disconnected from receiver stream"); isAlive = false; + sendQueue.Complete(); stream.Close(); } }); // go - DoReceive(stream, clock, onNext, onCompleted, onError); + DoReceive(stream, onNext, onCompleted, onError); DoSend(stream, onError); } @@ -227,41 +225,51 @@ public JsonRpcRequest DeserializeRequest(PooledArraySegment data) #endregion // API-Surface - private async void DoReceive(Stream stream, IMasterClock clock, - Action> onNext, Action onCompleted, Action onError) + private async void DoReceive(Stream stream, Action> onNext, Action onCompleted, Action onError) { - while (isAlive) - { - var buf = ArrayPool.Shared.Rent(0x10000); + var buf = ArrayPool.Shared.Rent(0x10000); - try + try + { + while (isAlive) { - var cb = await stream.ReadAsync(buf, 0, buf.Length); - - if (cb == 0 || !isAlive) + try { - onCompleted(); - return; + var cb = await stream.ReadAsync(buf, 0, buf.Length); + + if (cb == 0 || !isAlive) + { + onCompleted(); + return; + } + + LastReceive = clock.Now; + + plb.Receive(buf, cb, + (src, dst, count) => Array.Copy(src, dst, count), + onNext, + onError); } - LastReceive = clock.Now; + catch (ObjectDisposedException) + { + Debug.Assert(!isAlive); + break; + } - plb.Receive(buf, cb, - (src, dst, count) => Array.Copy(src, dst, count), - onNext, - onError); - } + catch (Exception ex) + { + if (isAlive) + onError(ex); - catch(Exception ex) - { - onError(ex); - break; + break; + } } + } - finally - { - ArrayPool.Shared.Return(buf); - } + finally + { + ArrayPool.Shared.Return(buf); } } @@ -286,18 +294,24 @@ private async void DoSend(Stream stream, Action onError) { while (await sendQueue.OutputAvailableAsync()) { - var segment = await sendQueue.ReceiveAsync(); - - using (segment) + using (var segment = await sendQueue.ReceiveAsync()) { await stream.WriteAsync(segment.Array, segment.Offset, segment.Size); } } } + catch (ObjectDisposedException) + { + Debug.Assert(!isAlive); + break; + } + catch (Exception ex) { - onError(ex); + if (isAlive) + onError(ex); + break; } } diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index d1c945d7..3023f1e7 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -20,10 +20,13 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; +using System.Net.Security; using System.Net.Sockets; using System.Reactive; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -33,7 +36,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using MiningCore.JsonRpc; using MiningCore.Time; using MiningCore.Util; -using NetMQ; using Newtonsoft.Json; using NLog; using Contract = MiningCore.Contracts.Contract; @@ -63,40 +65,74 @@ protected StratumServer(IComponentContext ctx, IMasterClock clock) protected abstract string LogCat { get; } - public void StartListeners(string id, params IPEndPoint[] stratumPorts) + public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEndpoint)[] stratumPorts) { Contract.RequiresNonNull(stratumPorts, nameof(stratumPorts)); // every port gets serviced by a dedicated loop thread - foreach(var endpoint in stratumPorts) + foreach(var port in stratumPorts) { var thread = new Thread(async _ => { var server = new Socket(SocketType.Stream, ProtocolType.Tcp); - server.Bind(endpoint); + server.Bind(port.IPEndpoint); server.Listen(512); lock (ports) { - ports[endpoint.Port] = server; + ports[port.IPEndpoint.Port] = server; } - logger.Info(() => $"[{LogCat}] Stratum port {endpoint.Address}:{endpoint.Port} online"); + var cert = port.PoolEndpoint.Tls ? new X509Certificate2(port.PoolEndpoint.TlsPfxFile) : null; + + logger.Info(() => $"[{LogCat}] Stratum port {port.IPEndpoint.Address}:{port.IPEndpoint.Port} online"); while (true) { try { var socket = await server.AcceptAsync(); + var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; + + // get rid of banned clients as early as possible + if (banManager?.IsBanned(remoteEndpoint.Address) == true) + { + logger.Debug(() => $"[{LogCat}] Disconnecting banned ip {remoteEndpoint.Address}"); + socket.Close(); + continue; + } // prepare socket socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); - // hand over - #pragma warning disable 4014 - OnClientConnected(new NetworkStream(socket, true), (IPEndPoint)socket.RemoteEndPoint, endpoint); - #pragma warning restore 4014 + // create stream + var stream = (Stream) new NetworkStream(socket, true); + + if (!port.PoolEndpoint.Tls) + { + var sslStream = new SslStream(stream, false); + await sslStream.AuthenticateAsServerAsync(cert); + stream = sslStream; + } + + var connectionId = CorrelationIdGenerator.GetNextId(); + logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); + + // setup client + var client = new StratumClient(clock, port.IPEndpoint, connectionId); + + lock (clients) + { + clients[connectionId] = client; + } + + OnConnect(client); + + client.Start(stream, remoteEndpoint, + data => OnReceiveAsync(client, data), + () => OnReceiveComplete(client), + ex => OnReceiveError(client, ex)); } catch (Exception ex) @@ -104,13 +140,13 @@ public void StartListeners(string id, params IPEndPoint[] stratumPorts) logger.Error(ex, () => Thread.CurrentThread.Name); } } - }) { Name = $"UvLoopThread {id}:{endpoint.Port}" }; + }) { Name = $"StratumThread {id}:{port.IPEndpoint.Port}" }; thread.Start(); } } - public void StopListeners() + public void Stop() { lock(ports) { @@ -125,44 +161,6 @@ public void StopListeners() } } - private void OnClientConnected(NetworkStream stream, IPEndPoint remoteEndpoint, IPEndPoint localEndpoint) - { - try - { - // get rid of banned clients as early as possible - if (banManager?.IsBanned(remoteEndpoint.Address) == true) - { - logger.Debug(() => $"[{LogCat}] Disconnecting banned ip {remoteEndpoint.Address}"); - stream.Close(); - return; - } - - var connectionId = CorrelationIdGenerator.GetNextId(); - logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); - - // setup client - var client = new StratumClient(localEndpoint, connectionId); - - // register client - lock (clients) - { - clients[connectionId] = client; - } - - OnConnect(client); - - client.Init(stream, remoteEndpoint, clock, - data => OnReceiveAsync(client, data), - () => OnReceiveComplete(client), - ex => OnReceiveError(client, ex)); - } - - catch(Exception ex) - { - logger.Error(ex, () => nameof(OnClientConnected)); - } - } - protected virtual async void OnReceiveAsync(StratumClient client, PooledArraySegment data) { using (data) From f1eb3472a9f5dd894527a1d4f8b0f4289b007e0e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 24 Feb 2018 17:58:17 +0100 Subject: [PATCH 04/17] TLS working --- src/MiningCore/Configuration/ClusterConfig.cs | 12 --------- .../Configuration/ClusterConfigValidation.cs | 8 +----- src/MiningCore/Stratum/StratumServer.cs | 26 ++++++++++++++----- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/MiningCore/Configuration/ClusterConfig.cs b/src/MiningCore/Configuration/ClusterConfig.cs index ac7b69ff..a61948c7 100644 --- a/src/MiningCore/Configuration/ClusterConfig.cs +++ b/src/MiningCore/Configuration/ClusterConfig.cs @@ -124,18 +124,6 @@ public class PoolEndpoint /// public bool Tls { get; set; } - /// - /// RFC's 1421 through 1424, path to a file that may include just the public certificate - /// or may include an entire certificate chain including public key and private key. - /// - public string TlsPemFile { get; set; } - - /// - /// This is a PEM formatted file containing just the private-key of the certificate - /// Not required if the private key is included in TlsPemFile - /// - public string TlsKeyFile { get; set; } - /// /// PKCS certificate file /// diff --git a/src/MiningCore/Configuration/ClusterConfigValidation.cs b/src/MiningCore/Configuration/ClusterConfigValidation.cs index b231474e..e16530ce 100644 --- a/src/MiningCore/Configuration/ClusterConfigValidation.cs +++ b/src/MiningCore/Configuration/ClusterConfigValidation.cs @@ -103,13 +103,7 @@ public PoolEndpointValidator() RuleFor(j => j.TlsPfxFile) .NotNull() .NotEmpty() - .When(j=> j.Tls && string.IsNullOrEmpty(j.TlsPemFile)) - .WithMessage("Pool Endpoint: Tls enabled but neither TlsPemFile nor TlsPfxFile specified"); - - RuleFor(j => j.TlsPemFile) - .NotNull() - .NotEmpty() - .When(j => j.Tls && string.IsNullOrEmpty(j.TlsPemFile)) + .When(j=> j.Tls) .WithMessage("Pool Endpoint: Tls enabled but neither TlsPemFile nor TlsPfxFile specified"); RuleFor(j => j.VarDiff) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 3023f1e7..5c6c594e 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -94,6 +94,9 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd var socket = await server.AcceptAsync(); var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; + var connectionId = CorrelationIdGenerator.GetNextId(); + logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); + // get rid of banned clients as early as possible if (banManager?.IsBanned(remoteEndpoint.Address) == true) { @@ -109,16 +112,27 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd // create stream var stream = (Stream) new NetworkStream(socket, true); - if (!port.PoolEndpoint.Tls) + // TLS handshake + if (port.PoolEndpoint.Tls) { - var sslStream = new SslStream(stream, false); - await sslStream.AuthenticateAsServerAsync(cert); + SslStream sslStream = null; + + try + { + sslStream = new SslStream(stream, false); + await sslStream.AuthenticateAsServerAsync(cert); + } + + catch (Exception ex) + { + logger.Error(() => $"[{LogCat}] TLS init failed: {ex.Message}"); + (sslStream ?? stream).Close(); + continue; + } + stream = sslStream; } - var connectionId = CorrelationIdGenerator.GetNextId(); - logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); - // setup client var client = new StratumClient(clock, port.IPEndpoint, connectionId); From 9305fc1eba7b729243de247e6cf0ca3640fc3eba Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 24 Feb 2018 18:02:15 +0100 Subject: [PATCH 05/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 5c6c594e..51a0e14e 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -92,8 +92,8 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd try { var socket = await server.AcceptAsync(); - var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; + var remoteEndpoint = (IPEndPoint) socket.RemoteEndPoint; var connectionId = CorrelationIdGenerator.GetNextId(); logger.Debug(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndpoint.Address}:{remoteEndpoint.Port}"); From 545d7d0b65cf09dffdd3521c3e4ac2bbd169c09e Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 24 Feb 2018 18:47:52 +0100 Subject: [PATCH 06/17] Config validation --- .../Configuration/ClusterConfigValidation.cs | 6 ++++++ src/MiningCore/Stratum/StratumServer.cs | 21 +++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/MiningCore/Configuration/ClusterConfigValidation.cs b/src/MiningCore/Configuration/ClusterConfigValidation.cs index e16530ce..06eb8e11 100644 --- a/src/MiningCore/Configuration/ClusterConfigValidation.cs +++ b/src/MiningCore/Configuration/ClusterConfigValidation.cs @@ -18,6 +18,7 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System.IO; using FluentValidation; using System.Linq; using FluentValidation.Attributes; @@ -106,6 +107,11 @@ public PoolEndpointValidator() .When(j=> j.Tls) .WithMessage("Pool Endpoint: Tls enabled but neither TlsPemFile nor TlsPfxFile specified"); + RuleFor(j => j.TlsPfxFile) + .Must(j=> File.Exists(j)) + .When(j => j.Tls) + .WithMessage(j=> $"Pool Endpoint: {j.TlsPfxFile} does not exist"); + RuleFor(j => j.VarDiff) .SetValidator(new VarDiffConfigValidator()) .When(x => x.VarDiff != null); diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 51a0e14e..edec97d7 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -69,10 +69,20 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd { Contract.RequiresNonNull(stratumPorts, nameof(stratumPorts)); - // every port gets serviced by a dedicated loop thread foreach(var port in stratumPorts) { - var thread = new Thread(async _ => + // TLS cert loading + X509Certificate2 cert = null; + + if (port.PoolEndpoint.Tls) + { + cert = new X509Certificate2(port.PoolEndpoint.TlsPfxFile); + + if (!cert.HasPrivateKey) + logger.ThrowLogPoolStartupException($"TLS certificate for stratum port {port} does not have private key and cannot be used"); + } + + var thread = new Thread(async arg => { var server = new Socket(SocketType.Stream, ProtocolType.Tcp); server.Bind(port.IPEndpoint); @@ -83,8 +93,6 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd ports[port.IPEndpoint.Port] = server; } - var cert = port.PoolEndpoint.Tls ? new X509Certificate2(port.PoolEndpoint.TlsPfxFile) : null; - logger.Info(() => $"[{LogCat}] Stratum port {port.IPEndpoint.Address}:{port.IPEndpoint.Port} online"); while (true) @@ -111,6 +119,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd // create stream var stream = (Stream) new NetworkStream(socket, true); + var tlsCert = (X509Certificate)arg; // TLS handshake if (port.PoolEndpoint.Tls) @@ -120,7 +129,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd try { sslStream = new SslStream(stream, false); - await sslStream.AuthenticateAsServerAsync(cert); + await sslStream.AuthenticateAsServerAsync(tlsCert); } catch (Exception ex) @@ -156,7 +165,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd } }) { Name = $"StratumThread {id}:{port.IPEndpoint.Port}" }; - thread.Start(); + thread.Start(cert); } } From a18d6129dd26d145b93e175c08d2376e07df3a90 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 24 Feb 2018 19:33:49 +0100 Subject: [PATCH 07/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index edec97d7..86a17a97 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -79,7 +79,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd cert = new X509Certificate2(port.PoolEndpoint.TlsPfxFile); if (!cert.HasPrivateKey) - logger.ThrowLogPoolStartupException($"TLS certificate for stratum port {port} does not have private key and cannot be used"); + logger.ThrowLogPoolStartupException($"TLS certificate for stratum port {port} does not include the private key and cannot be used"); } var thread = new Thread(async arg => From 3f08e59a2ad97a6248f0b698fd0be594424f1d8b Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 10:00:48 +0100 Subject: [PATCH 08/17] Cleanup --- src/MiningCore/Stratum/StratumClient.cs | 59 +++++++++---------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/src/MiningCore/Stratum/StratumClient.cs b/src/MiningCore/Stratum/StratumClient.cs index 7d389d0e..55956380 100644 --- a/src/MiningCore/Stratum/StratumClient.cs +++ b/src/MiningCore/Stratum/StratumClient.cs @@ -88,8 +88,8 @@ public void Start(Stream stream, IPEndPoint remoteEndpoint, Action() where T: WorkerContextBase public void Respond(T payload, object id) { - Contract.RequiresNonNull(payload, nameof(payload)); - Contract.RequiresNonNull(id, nameof(id)); - Respond(new JsonRpcResponse(payload, id)); } public void RespondError(StratumError code, string message, object id, object result = null, object data = null) { - Contract.RequiresNonNull(message, nameof(message)); - Respond(new JsonRpcResponse(new JsonRpcException((int) code, message, null), id, result)); } public void Respond(JsonRpcResponse response) { - Contract.RequiresNonNull(response, nameof(response)); - Send(response); } public void Notify(string method, T payload) { - Contract.Requires(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); - Notify(new JsonRpcRequest(method, payload, null)); } public void Notify(JsonRpcRequest request) { - Contract.RequiresNonNull(request, nameof(request)); - Send(request); } public void Send(T payload) { - Contract.RequiresNonNull(payload, nameof(payload)); - if (isAlive) { var buf = ArrayPool.Shared.Rent(MaxOutboundRequestLength); @@ -153,7 +140,6 @@ public void Send(T payload) using (var stream = new MemoryStream(buf, true)) { stream.SetLength(0); - int size; using (var writer = new StreamWriter(stream, StratumConstants.Encoding)) { @@ -162,12 +148,13 @@ public void Send(T payload) // append newline stream.WriteByte(0xa); - size = (int)stream.Position; - } + var cb = (int)stream.Position; - logger.Trace(() => $"[{ConnectionId}] Sending: {StratumConstants.Encoding.GetString(buf, 0, size)}"); + // xmit + sendQueue.Post(new PooledArraySegment(buf, 0, cb)); - SendInternal(new PooledArraySegment(buf, 0, size)); + logger.Trace(() => $"[{ConnectionId}] Sent: {StratumConstants.Encoding.GetString(buf, 0, cb)}"); + } } } @@ -181,10 +168,11 @@ public void Send(T payload) public void Disconnect() { - subscription?.Dispose(); - subscription = null; - - IsAlive = false; + if (subscription != null) + { + subscription.Dispose(); + subscription = null; + } } public void RespondError(object id, int code, string message) @@ -239,8 +227,10 @@ private async void DoReceive(Stream stream, Action> onN if (cb == 0 || !isAlive) { - onCompleted(); - return; + if(isAlive) + onCompleted(); + + break; } LastReceive = clock.Now; @@ -265,6 +255,8 @@ private async void DoReceive(Stream stream, Action> onN break; } } + + logger.Trace(() => $"[{ConnectionId}] DoReceive loop exited"); } finally @@ -273,19 +265,6 @@ private async void DoReceive(Stream stream, Action> onN } } - private void SendInternal(PooledArraySegment buffer) - { - try - { - sendQueue.Post(buffer); - } - - catch (ObjectDisposedException) - { - buffer.Dispose(); - } - } - private async void DoSend(Stream stream, Action onError) { while(isAlive) @@ -315,6 +294,8 @@ private async void DoSend(Stream stream, Action onError) break; } } + + logger.Trace(() => $"[{ConnectionId}] DoSend loop exited"); } } } From ffe5fddc2bf04e2b546e46b28f64d3e5fced989d Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 10:15:45 +0100 Subject: [PATCH 09/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 86a17a97..7e9a352e 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -85,6 +85,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd var thread = new Thread(async arg => { var server = new Socket(SocketType.Stream, ProtocolType.Tcp); + server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); server.Bind(port.IPEndpoint); server.Listen(512); From 4aac151c603ba744a2105a5da92e78717cc685e1 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 10:35:09 +0100 Subject: [PATCH 10/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 7e9a352e..8e6ace98 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -115,8 +115,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd } // prepare socket - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000); - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, true); + socket.NoDelay = true; // create stream var stream = (Stream) new NetworkStream(socket, true); From 8f27bd194db09ae0c4ad6bc179f88175e8cf9e3f Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 11:47:00 +0100 Subject: [PATCH 11/17] WIP --- .../Configuration/ClusterConfigValidation.cs | 18 ++++++++++++++++ src/MiningCore/Stratum/StratumServer.cs | 21 +++++++------------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/MiningCore/Configuration/ClusterConfigValidation.cs b/src/MiningCore/Configuration/ClusterConfigValidation.cs index 06eb8e11..257c0e56 100644 --- a/src/MiningCore/Configuration/ClusterConfigValidation.cs +++ b/src/MiningCore/Configuration/ClusterConfigValidation.cs @@ -18,9 +18,11 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System; using System.IO; using FluentValidation; using System.Linq; +using System.Security.Cryptography.X509Certificates; using FluentValidation.Attributes; namespace MiningCore.Configuration @@ -112,6 +114,22 @@ public PoolEndpointValidator() .When(j => j.Tls) .WithMessage(j=> $"Pool Endpoint: {j.TlsPfxFile} does not exist"); + RuleFor(j => j.TlsPfxFile) + .Must(j => + { + try + { + var tlsCert = new X509Certificate2(j); + return tlsCert.HasPrivateKey; + } + catch + { + return false; + } + }) + .When(j => j.Tls) + .WithMessage(j => $"Pool Endpoint: {j.TlsPfxFile} is not valid or does not include the private key and cannot be used"); + RuleFor(j => j.VarDiff) .SetValidator(new VarDiffConfigValidator()) .When(x => x.VarDiff != null); diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index 8e6ace98..a31fd9a3 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -71,19 +71,15 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd foreach(var port in stratumPorts) { - // TLS cert loading - X509Certificate2 cert = null; - - if (port.PoolEndpoint.Tls) + Task.Run(async () => { - cert = new X509Certificate2(port.PoolEndpoint.TlsPfxFile); + // TLS cert loading + X509Certificate2 tlsCert = null; - if (!cert.HasPrivateKey) - logger.ThrowLogPoolStartupException($"TLS certificate for stratum port {port} does not include the private key and cannot be used"); - } + if (port.PoolEndpoint.Tls) + tlsCert = new X509Certificate2(port.PoolEndpoint.TlsPfxFile); - var thread = new Thread(async arg => - { + // Setup socket var server = new Socket(SocketType.Stream, ProtocolType.Tcp); server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); server.Bind(port.IPEndpoint); @@ -119,7 +115,6 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd // create stream var stream = (Stream) new NetworkStream(socket, true); - var tlsCert = (X509Certificate)arg; // TLS handshake if (port.PoolEndpoint.Tls) @@ -163,9 +158,7 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd logger.Error(ex, () => Thread.CurrentThread.Name); } } - }) { Name = $"StratumThread {id}:{port.IPEndpoint.Port}" }; - - thread.Start(cert); + }); } } From 5b84ffc5a74ac316d258cc7c07409da61ad8385a Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 12:04:54 +0100 Subject: [PATCH 12/17] WIP --- src/MiningCore/Program.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MiningCore/Program.cs b/src/MiningCore/Program.cs index 4a40bba4..9965cfab 100644 --- a/src/MiningCore/Program.cs +++ b/src/MiningCore/Program.cs @@ -23,6 +23,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; @@ -244,6 +245,9 @@ private static void Bootstrap() var amConf = new MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); }); builder.Register((ctx, parms) => amConf.CreateMapper()); + // Misc + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + ConfigurePersistence(builder); container = builder.Build(); ConfigureLogging(); From 28d51a52e0ae847ff572e55675e52e490129e1da Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 25 Feb 2018 13:24:16 +0100 Subject: [PATCH 13/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index a31fd9a3..f3c87678 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -26,6 +26,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Net.Security; using System.Net.Sockets; using System.Reactive; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -124,12 +125,12 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd try { sslStream = new SslStream(stream, false); - await sslStream.AuthenticateAsServerAsync(tlsCert); + await sslStream.AuthenticateAsServerAsync(tlsCert, false, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, false); } catch (Exception ex) { - logger.Error(() => $"[{LogCat}] TLS init failed: {ex.Message}"); + logger.Error(() => $"[{LogCat}] TLS init failed: {ex.Message}: {ex.InnerException.ToString() ?? string.Empty}"); (sslStream ?? stream).Close(); continue; } From 3ff2af34b5f7e61a29353e4181d3222461654621 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sat, 10 Mar 2018 10:05:41 +0100 Subject: [PATCH 14/17] WIP --- src/MiningCore/Stratum/StratumClient.cs | 7 +- src/MiningCore/Stratum/StratumServer.cs | 2 +- src/MiningCore/Util/PooledLineBuffer.cs | 122 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/MiningCore/Stratum/StratumClient.cs b/src/MiningCore/Stratum/StratumClient.cs index 55956380..666199a8 100644 --- a/src/MiningCore/Stratum/StratumClient.cs +++ b/src/MiningCore/Stratum/StratumClient.cs @@ -24,6 +24,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.IO; using System.Net; using System.Reactive.Disposables; +using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; using MiningCore.Buffers; using MiningCore.JsonRpc; @@ -65,7 +66,7 @@ public StratumClient(IMasterClock clock, IPEndPoint endpointConfig, string conne #region API-Surface - public void Start(Stream stream, IPEndPoint remoteEndpoint, Action> onNext, Action onCompleted, Action onError) + public void Start(Stream stream, IPEndPoint remoteEndpoint, Func, Task> onNext, Action onCompleted, Action onError) { RemoteEndpoint = remoteEndpoint; sendQueue = new BufferBlock>(); @@ -213,7 +214,7 @@ public JsonRpcRequest DeserializeRequest(PooledArraySegment data) #endregion // API-Surface - private async void DoReceive(Stream stream, Action> onNext, Action onCompleted, Action onError) + private async void DoReceive(Stream stream, Func, Task> onNext, Action onCompleted, Action onError) { var buf = ArrayPool.Shared.Rent(0x10000); @@ -235,7 +236,7 @@ private async void DoReceive(Stream stream, Action> onN LastReceive = clock.Now; - plb.Receive(buf, cb, + await plb.ReceiveAsync(buf, cb, (src, dst, count) => Array.Copy(src, dst, count), onNext, onError); diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index f3c87678..e950837e 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -178,7 +178,7 @@ public void Stop() } } - protected virtual async void OnReceiveAsync(StratumClient client, PooledArraySegment data) + protected virtual async Task OnReceiveAsync(StratumClient client, PooledArraySegment data) { using (data) { diff --git a/src/MiningCore/Util/PooledLineBuffer.cs b/src/MiningCore/Util/PooledLineBuffer.cs index 1a1d4648..20e60955 100644 --- a/src/MiningCore/Util/PooledLineBuffer.cs +++ b/src/MiningCore/Util/PooledLineBuffer.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using MiningCore.Buffers; using MiningCore.Extensions; using NLog; @@ -154,5 +155,126 @@ public void Receive(T buffer, int bufferSize, ByteArrayPool.Return(buf); } } + + public async Task ReceiveAsync(T buffer, int bufferSize, + Action readBuffer, + Func, Task> onNext, + Action onError, + bool forceNewLine = false) + { + if (bufferSize == 0) + return; + + // prevent flooding + if (maxLength.HasValue && bufferSize > maxLength) + { + onError(new InvalidDataException($"Incoming data exceeds maximum of {maxLength.Value}")); + return; + } + + var remaining = bufferSize; + var buf = ArrayPool.Shared.Rent(bufferSize); + var prevIndex = 0; + var keepLease = false; + + try + { + // clear left-over contents + if (buf.Length > bufferSize) + Array.Clear(buf, bufferSize, buf.Length - bufferSize); + + // read buffer + readBuffer(buffer, buf, bufferSize); + + // diagnostics + logger.Trace(() => $"recv: {Encoding.GetString(buf, 0, bufferSize)}"); + + while (remaining > 0) + { + // check if we got a newline + var index = buf.IndexOf(0xa, prevIndex, buf.Length - prevIndex); + var found = index != -1; + + if (found || forceNewLine) + { + // fastpath + if (!forceNewLine && index + 1 == bufferSize && recvQueue.Count == 0) + { + var length = index - prevIndex; + + if (length > 0) + { + await onNext(new PooledArraySegment(buf, prevIndex, length)); + keepLease = true; + } + + break; + } + + // assemble line buffer + var queuedLength = recvQueue.Sum(x => x.Size); + var segmentLength = !forceNewLine ? index - prevIndex : bufferSize - prevIndex; + var lineLength = queuedLength + segmentLength; + var line = ArrayPool.Shared.Rent(lineLength); + var offset = 0; + + while (recvQueue.TryDequeue(out var segment)) + { + using (segment) + { + Array.Copy(segment.Array, 0, line, offset, segment.Size); + offset += segment.Size; + } + } + + // append remaining characters + if (segmentLength > 0) + Array.Copy(buf, prevIndex, line, offset, segmentLength); + + // emit + if (lineLength > 0) + await onNext(new PooledArraySegment(line, 0, lineLength)); + + if (forceNewLine) + break; + + prevIndex = index + 1; + remaining -= segmentLength + 1; + continue; + } + + // store + if (prevIndex != 0) + { + var segmentLength = bufferSize - prevIndex; + + if (segmentLength > 0) + { + var fragment = ArrayPool.Shared.Rent(segmentLength); + Array.Copy(buf, prevIndex, fragment, 0, segmentLength); + recvQueue.Enqueue(new PooledArraySegment(fragment, 0, segmentLength)); + } + } + + else + { + recvQueue.Enqueue(new PooledArraySegment(buf, 0, remaining)); + keepLease = true; + } + + // prevent flooding + if (maxLength.HasValue && recvQueue.Sum(x => x.Size) > maxLength.Value) + onError(new InvalidDataException($"Incoming request size exceeds maximum of {maxLength.Value}")); + + break; + } + } + + finally + { + if (!keepLease) + ByteArrayPool.Return(buf); + } + } } } From 5802d464482052d357f5c688ae2b3fe2feba4514 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 11 Mar 2018 20:34:55 +0100 Subject: [PATCH 15/17] WIP --- src/MiningCore/Properties/launchSettings.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/MiningCore/Properties/launchSettings.json b/src/MiningCore/Properties/launchSettings.json index 72807c19..9218cd2b 100644 --- a/src/MiningCore/Properties/launchSettings.json +++ b/src/MiningCore/Properties/launchSettings.json @@ -1,8 +1,8 @@ -{ - "profiles": { - "MiningCore": { - "commandName": "Project", - "commandLineArgs": "-c config.json" - } - } +{ + "profiles": { + "MiningCore": { + "commandName": "Project", + "commandLineArgs": "-c ..\\..\\..\\config.json" + } + } } \ No newline at end of file From 148086ee6b5feec1f8a64b15fb7759020ee9cf95 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Sun, 11 Mar 2018 20:45:34 +0100 Subject: [PATCH 16/17] WIP --- src/MiningCore/Stratum/StratumServer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MiningCore/Stratum/StratumServer.cs b/src/MiningCore/Stratum/StratumServer.cs index e950837e..4997647b 100644 --- a/src/MiningCore/Stratum/StratumServer.cs +++ b/src/MiningCore/Stratum/StratumServer.cs @@ -91,7 +91,8 @@ public void Start(string id, params (IPEndPoint IPEndpoint, PoolEndpoint PoolEnd ports[port.IPEndpoint.Port] = server; } - logger.Info(() => $"[{LogCat}] Stratum port {port.IPEndpoint.Address}:{port.IPEndpoint.Port} online"); + var portDesc = tlsCert != null ? " [TLS]" : string.Empty; + logger.Info(() => $"[{LogCat}] Stratum port {port.IPEndpoint.Address}:{port.IPEndpoint.Port} online{portDesc}"); while (true) { From 574f7593d59f1dcb5c91edaff7cd1f5f6a75515c Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Mon, 12 Mar 2018 23:02:02 +0100 Subject: [PATCH 17/17] WIP --- src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs | 1 + src/MiningCore/Blockchain/CoinMetaData.cs | 4 ++++ src/MiningCore/Program.cs | 1 + 3 files changed, 6 insertions(+) diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs index 40e4a661..35aa4a81 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs @@ -148,6 +148,7 @@ private static BitcoinCoinProperties GetDigiByteProperties(string algorithm) switch(algorithm.ToLower()) { case "sha256d": + case "sha256": return sha256Coin; case "skein": diff --git a/src/MiningCore/Blockchain/CoinMetaData.cs b/src/MiningCore/Blockchain/CoinMetaData.cs index f97494bb..e3ea96b1 100644 --- a/src/MiningCore/Blockchain/CoinMetaData.cs +++ b/src/MiningCore/Blockchain/CoinMetaData.cs @@ -28,6 +28,7 @@ public static class CoinMetaData { CoinType.XMR, new Dictionary { { string.Empty, $"https://chainradar.com/xmr/block/{BlockHeightPH}" }}}, { CoinType.ETN, new Dictionary { { string.Empty, $"https://blockexplorer.electroneum.com/block/{BlockHeightPH}" } }}, { CoinType.LTC, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/ltc/block.dws?{BlockHeightPH}.htm" } }}, + { CoinType.PPC, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/ppc/block.dws?{BlockHeightPH}.htm" } }}, { CoinType.BCH, new Dictionary { { string.Empty, $"https://www.blocktrail.com/BCC/block/{BlockHeightPH}" }}}, { CoinType.DASH, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/dash/block.dws?{BlockHeightPH}.htm" }}}, { CoinType.BTC, new Dictionary { { string.Empty, $"https://blockchain.info/block/{BlockHeightPH}" }}}, @@ -58,6 +59,7 @@ public static class CoinMetaData { CoinType.ETH, "https://etherscan.io/tx/{0}" }, { CoinType.ETC, "https://gastracker.io/tx/{0}" }, { CoinType.LTC, "https://chainz.cryptoid.info/ltc/tx.dws?{0}.htm" }, + { CoinType.PPC, "https://chainz.cryptoid.info/ppc/tx.dws?{0}.htm" }, { CoinType.BCH, "https://www.blocktrail.com/BCC/tx/{0}" }, { CoinType.DASH, "https://chainz.cryptoid.info/dash/tx.dws?{0}.htm" }, { CoinType.BTC, "https://blockchain.info/tx/{0}" }, @@ -88,6 +90,7 @@ public static class CoinMetaData { CoinType.ETH, "https://etherscan.io/address/{0}" }, { CoinType.ETC, "https://gastracker.io/addr/{0}" }, { CoinType.LTC, "https://chainz.cryptoid.info/ltc/address.dws?{0}.htm" }, + { CoinType.PPC, "https://chainz.cryptoid.info/ppc/address.dws?{0}.htm" }, { CoinType.BCH, "https://www.blocktrail.com/BCC/address/{0}" }, { CoinType.DASH, "https://chainz.cryptoid.info/dash/address.dws?{0}.htm" }, { CoinType.BTC, "https://blockchain.info/address/{0}" }, @@ -120,6 +123,7 @@ public static class CoinMetaData { CoinType.ETH, (coin, alg)=> Ethash }, { CoinType.ETC, (coin, alg)=> Ethash }, { CoinType.LTC, BitcoinProperties.GetAlgorithm }, + { CoinType.PPC, BitcoinProperties.GetAlgorithm }, { CoinType.BCH, BitcoinProperties.GetAlgorithm }, { CoinType.DASH, BitcoinProperties.GetAlgorithm }, { CoinType.BTC, BitcoinProperties.GetAlgorithm }, diff --git a/src/MiningCore/Program.cs b/src/MiningCore/Program.cs index 9ba3c9be..0bf4b1d2 100644 --- a/src/MiningCore/Program.cs +++ b/src/MiningCore/Program.cs @@ -42,6 +42,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using MiningCore.Api; using MiningCore.Api.Responses; using MiningCore.Blockchain; +using MiningCore.Blockchain.Bitcoin; using MiningCore.Blockchain.Bitcoin.DaemonResponses; using MiningCore.Blockchain.ZCash; using MiningCore.Configuration;