diff --git a/README.md b/README.md index dfcc6e94b..c5d9fb0de 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Namecoin | Yes | No | | Viacoin | Yes | No | | Peercoin | Yes | No | | Straks | Yes | Yes | | +Electroneum | Yes | No | | #### Ethereum Miningcore implements the [Ethereum stratum mining protocol](https://github.com/nicehash/Specifications/blob/master/EthereumStratum_NiceHash_v1.0.0.txt) authored by NiceHash. This protocol is implemented by all major Ethereum miners. diff --git a/src/MiningCore/Api/ApiServer.cs b/src/MiningCore/Api/ApiServer.cs index ff90b14d5..88d2cb175 100644 --- a/src/MiningCore/Api/ApiServer.cs +++ b/src/MiningCore/Api/ApiServer.cs @@ -100,6 +100,7 @@ public ApiServer( private readonly List pools = new List(); private IWebHost webHost; private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private static readonly Encoding encoding = new UTF8Encoding(false); private static readonly JsonSerializer serializer = new JsonSerializer { @@ -120,7 +121,7 @@ private async Task SendJson(HttpContext context, object response) using (var stream = context.Response.Body) { - using (var writer = new StreamWriter(stream, Encoding.UTF8)) + using (var writer = new StreamWriter(stream, encoding)) { serializer.Serialize(writer, response); diff --git a/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs b/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs index 6417cbff5..16a080347 100644 --- a/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs +++ b/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs @@ -19,10 +19,22 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; -using MiningCore.Persistence.Model; +using System.Collections.Generic; namespace MiningCore.Api.Responses { + public class WorkerPerformanceStats + { + public double Hashrate { get; set; } + public double SharesPerSecond { get; set; } + } + + public class WorkerPerformanceStatsContainer + { + public DateTime Created { get; set; } + public Dictionary Workers { get; set; } + } + public class MinerStats { public ulong PendingShares { get; set; } @@ -30,6 +42,6 @@ public class MinerStats public decimal TotalPaid { get; set; } public DateTime? LastPayment { get; set; } public string LastPaymentLink { get; set; } - public MinerWorkerPerformanceStats[] PerformanceStats { get; set; } + public WorkerPerformanceStatsContainer Performance { get; set; } } } diff --git a/src/MiningCore/AutoMapperProfile.cs b/src/MiningCore/AutoMapperProfile.cs index cbe6e2196..a2f804026 100644 --- a/src/MiningCore/AutoMapperProfile.cs +++ b/src/MiningCore/AutoMapperProfile.cs @@ -58,6 +58,9 @@ public AutoMapperProfile() .ForMember(dest => dest.LastPayment, opt => opt.Ignore()) .ForMember(dest => dest.LastPaymentLink, opt => opt.Ignore()); + CreateMap(); + CreateMap(); + // PostgreSQL CreateMap(); CreateMap(); diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 662c4e5c3..206fdb8d7 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -44,7 +44,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Bitcoin { [CoinMetadata( - CoinType.BTC, CoinType.BCC, CoinType.NMC, CoinType.PPC, + CoinType.BTC, CoinType.BCH, CoinType.NMC, CoinType.PPC, CoinType.LTC, CoinType.DOGE, CoinType.DGB, CoinType.VIA, CoinType.GRS, CoinType.MONA, CoinType.VTC, CoinType.BTG, CoinType.GLT, CoinType.STAK)] @@ -178,6 +178,7 @@ public virtual async Task ClassifyBlocksAsync(Block[] blocks) logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned. Category: {transactionInfo.Details[0].Category}"); block.Status = BlockStatus.Orphaned; + block.Reward = 0; result.Add(block); break; } @@ -241,7 +242,7 @@ public virtual async Task PayoutAsync(Balance[] balances) args = new object[] { - string.Empty, // default account + string.Empty, // default account amounts, // addresses and associated amounts 1, // only spend funds covered by this many confirmations comment, // tx comment @@ -253,7 +254,7 @@ public virtual async Task PayoutAsync(Balance[] balances) { args = new object[] { - string.Empty, // default account + string.Empty, // default account amounts, // addresses and associated amounts }; } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs index c2a6a9b6c..951f6e6e7 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs @@ -31,7 +31,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Bitcoin { [CoinMetadata( - CoinType.BTC, CoinType.BCC, CoinType.NMC, CoinType.PPC, + CoinType.BTC, CoinType.BCH, CoinType.NMC, CoinType.PPC, CoinType.LTC, CoinType.DOGE, CoinType.DGB, CoinType.VIA, CoinType.GRS, CoinType.MONA, CoinType.VTC, CoinType.GLT)] public class BitcoinPool : BitcoinPoolBase, BlockTemplate> diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs index 0699478d9..dbe14cd23 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs @@ -1,382 +1,380 @@ -/* -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, -subject to the following conditions: - -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 -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading.Tasks; -using Autofac; -using AutoMapper; -using MiningCore.Blockchain.Bitcoin.DaemonResponses; -using MiningCore.Configuration; -using MiningCore.JsonRpc; -using MiningCore.Mining; -using MiningCore.Notifications; -using MiningCore.Persistence; -using MiningCore.Persistence.Repositories; -using MiningCore.Stratum; -using MiningCore.Time; -using Newtonsoft.Json; -using NLog; - -namespace MiningCore.Blockchain.Bitcoin -{ - public class BitcoinPoolBase : PoolBase - where TBlockTemplate : BlockTemplate - where TJob : BitcoinJob, new() - { - public BitcoinPoolBase(IComponentContext ctx, - JsonSerializerSettings serializerSettings, - IConnectionFactory cf, - IStatsRepository statsRepo, - IMapper mapper, - IMasterClock clock, - NotificationService notificationService) : - base(ctx, serializerSettings, cf, statsRepo, mapper, clock, notificationService) - { - } - - protected object currentJobParams; - protected BitcoinJobManager manager; - - protected virtual void OnSubscribe(StratumClient client, Timestamped tsRequest) - { - var request = tsRequest.Value; - - if (request.Id == null) - { - client.RespondError(StratumError.Other, "missing request id", request.Id); - return; - } - - var context = client.GetContextAs(); - var requestParams = request.ParamsAs(); - - var data = new object[] - { - new object[] - { - new object[] { BitcoinStratumMethods.SetDifficulty, client.ConnectionId }, - new object[] { BitcoinStratumMethods.MiningNotify, client.ConnectionId } - } - } - .Concat(manager.GetSubscriberData(client)) - .ToArray(); - - client.Respond(data, request.Id); - - // setup worker context - context.IsSubscribed = true; - context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; - - // send intial update - client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); - } - - protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped tsRequest) - { - var request = tsRequest.Value; - - if (request.Id == null) - { - client.RespondError(StratumError.Other, "missing request id", request.Id); - return; - } - - var context = client.GetContextAs(); - var requestParams = request.ParamsAs(); - var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; - //var password = requestParams?.Length > 1 ? requestParams[1] : null; - - // extract worker/miner - var split = workerValue?.Split('.'); - var minerName = split?.FirstOrDefault(); +/* +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, +subject to the following conditions: + +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 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; +using Autofac; +using AutoMapper; +using MiningCore.Blockchain.Bitcoin.DaemonResponses; +using MiningCore.Configuration; +using MiningCore.JsonRpc; +using MiningCore.Mining; +using MiningCore.Notifications; +using MiningCore.Persistence; +using MiningCore.Persistence.Repositories; +using MiningCore.Stratum; +using MiningCore.Time; +using Newtonsoft.Json; +using NLog; + +namespace MiningCore.Blockchain.Bitcoin +{ + public class BitcoinPoolBase : PoolBase + where TBlockTemplate : BlockTemplate + where TJob : BitcoinJob, new() + { + public BitcoinPoolBase(IComponentContext ctx, + JsonSerializerSettings serializerSettings, + IConnectionFactory cf, + IStatsRepository statsRepo, + IMapper mapper, + IMasterClock clock, + NotificationService notificationService) : + base(ctx, serializerSettings, cf, statsRepo, mapper, clock, notificationService) + { + } + + protected object currentJobParams; + protected BitcoinJobManager manager; + + protected virtual void OnSubscribe(StratumClient client, Timestamped tsRequest) + { + var request = tsRequest.Value; + + if (request.Id == null) + { + client.RespondError(StratumError.Other, "missing request id", request.Id); + return; + } + + var context = client.GetContextAs(); + var requestParams = request.ParamsAs(); + + var data = new object[] + { + new object[] + { + new object[] { BitcoinStratumMethods.SetDifficulty, client.ConnectionId }, + new object[] { BitcoinStratumMethods.MiningNotify, client.ConnectionId } + } + } + .Concat(manager.GetSubscriberData(client)) + .ToArray(); + + client.Respond(data, request.Id); + + // setup worker context + context.IsSubscribed = true; + context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; + + // send intial update + client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + + protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped tsRequest) + { + var request = tsRequest.Value; + + if (request.Id == null) + { + client.RespondError(StratumError.Other, "missing request id", request.Id); + return; + } + + var context = client.GetContextAs(); + var requestParams = request.ParamsAs(); + var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; + //var password = requestParams?.Length > 1 ? requestParams[1] : null; + + // extract worker/miner + var split = workerValue?.Split('.'); + var minerName = split?.FirstOrDefault(); var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; - - // assumes that workerName is an address - context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName); - context.MinerName = minerName; - context.WorkerName = workerName; - - // respond - client.Respond(context.IsAuthorized, request.Id); - - // log association - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}"); - } - - protected virtual async Task OnSubmitAsync(StratumClient client, Timestamped tsRequest) - { - var request = tsRequest.Value; - var context = client.GetContextAs(); - - try - { - if (request.Id == null) - throw new StratumException(StratumError.MinusOne, "missing request id"); - - // check age of submission (aged submissions are usually caused by high server load) - var requestAge = clock.Now - tsRequest.Timestamp.UtcDateTime; - - if (requestAge > maxShareAge) - { - logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); - return; + + // assumes that workerName is an address + context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName); + context.MinerName = minerName; + context.WorkerName = workerName; + + // respond + client.Respond(context.IsAuthorized, request.Id); + + // log association + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}"); + } + + protected virtual async Task OnSubmitAsync(StratumClient client, Timestamped tsRequest) + { + var request = tsRequest.Value; + var context = client.GetContextAs(); + + try + { + if (request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + // check age of submission (aged submissions are usually caused by high server load) + var requestAge = clock.Now - tsRequest.Timestamp.UtcDateTime; + + if (requestAge > maxShareAge) + { + logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); + return; } - // check worker state - context.LastActivity = clock.Now; - - // validate worker - if (!context.IsAuthorized) - throw new StratumException(StratumError.UnauthorizedWorker, "Unauthorized worker"); - else if (!context.IsSubscribed) - throw new StratumException(StratumError.NotSubscribed, "Not subscribed"); - - // submit - var requestParams = request.ParamsAs(); - var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; - - var share = await manager.SubmitShareAsync(client, requestParams, poolEndpoint.Difficulty); - - // success - client.Respond(true, request.Id); - shareSubject.OnNext(new ClientShare(client, share)); - + // check worker state + context.LastActivity = clock.Now; + + // validate worker + if (!context.IsAuthorized) + throw new StratumException(StratumError.UnauthorizedWorker, "Unauthorized worker"); + else if (!context.IsSubscribed) + throw new StratumException(StratumError.NotSubscribed, "Not subscribed"); + + // submit + var requestParams = request.ParamsAs(); + var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; + + var share = await manager.SubmitShareAsync(client, requestParams, poolEndpoint.Difficulty); + + // success + client.Respond(true, request.Id); + shareSubject.OnNext(new ClientShare(client, share)); + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats - if (share.IsBlockCandidate) - poolStats.LastPoolBlockTime = clock.Now; - - // update client stats - context.Stats.ValidShares++; - UpdateVarDiff(client); - } - - catch (StratumException ex) - { - client.RespondError(ex.Code, ex.Message, request.Id, false); - - // update client stats - context.Stats.InvalidShares++; - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); - - // banning - if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true) - ConsiderBan(client, context, poolConfig.Banning); - } - } - - private void OnSuggestDifficulty(StratumClient client, Timestamped tsRequest) - { - var request = tsRequest.Value; + if (share.IsBlockCandidate) + poolStats.LastPoolBlockTime = clock.Now; + + // update client stats + context.Stats.ValidShares++; + UpdateVarDiff(client); + } + + catch (StratumException ex) + { + client.RespondError(ex.Code, ex.Message, request.Id, false); + + // update client stats + context.Stats.InvalidShares++; + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); + + // banning + if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true) + ConsiderBan(client, context, poolConfig.Banning); + } + } + + private void OnSuggestDifficulty(StratumClient client, Timestamped tsRequest) + { + var request = tsRequest.Value; var context = client.GetContextAs(); - - // acknowledge - client.Respond(true, request.Id); - - try - { - var requestedDiff = (double) Convert.ChangeType(request.Params, TypeCode.Double); - - // client may suggest higher-than-base difficulty, but not a lower one - var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; - - if (requestedDiff > poolEndpoint.Difficulty) - { - context.SetDifficulty(requestedDiff); - client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner"); - } - } - - catch(Exception ex) - { - logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}"); - } - } - - protected void OnGetTransactions(StratumClient client, Timestamped tsRequest) - { - var request = tsRequest.Value; - - try - { - var transactions = manager.GetTransactions(client, request.ParamsAs()); - - client.Respond(transactions, request.Id); - } - - catch(StratumException ex) - { - client.RespondError(ex.Code, ex.Message, request.Id, false); - } - - catch(Exception ex) - { - logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}"); - } - } - - protected virtual void OnNewJob(object jobParams) - { - currentJobParams = jobParams; - + + // acknowledge + client.Respond(true, request.Id); + + try + { + var requestedDiff = (double) Convert.ChangeType(request.Params, TypeCode.Double); + + // client may suggest higher-than-base difficulty, but not a lower one + var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; + + if (requestedDiff > poolEndpoint.Difficulty) + { + context.SetDifficulty(requestedDiff); + client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner"); + } + } + + catch(Exception ex) + { + logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}"); + } + } + + protected void OnGetTransactions(StratumClient client, Timestamped tsRequest) + { + var request = tsRequest.Value; + + try + { + var transactions = manager.GetTransactions(client, request.ParamsAs()); + + client.Respond(transactions, request.Id); + } + + catch(StratumException ex) + { + client.RespondError(ex.Code, ex.Message, request.Id, false); + } + + catch(Exception ex) + { + logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}"); + } + } + + protected virtual void OnNewJob(object jobParams) + { + currentJobParams = jobParams; + logger.Info(() => $"[{LogCat}] Broadcasting job"); - - ForEachClient(client => - { - var context = client.GetContextAs(); - - if (context.IsSubscribed && context.IsAuthorized) - { - // check alive - var lastActivityAgo = clock.Now - context.LastActivity; - - if (poolConfig.ClientConnectionTimeout > 0 && - lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) - { - logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); - DisconnectClient(client); - return; - } - - // varDiff: if the client has a pending difficulty change, apply it now - if (context.ApplyPendingDifficulty()) - client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - - // send job - client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); - } - }); - } - - #region Overrides - - protected virtual BitcoinJobManager CreateJobManager() - { - return ctx.Resolve>( - new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider())); - } - - protected override async Task SetupJobManager() - { - manager = CreateJobManager(); - manager.Configure(poolConfig, clusterConfig); - - await manager.StartAsync(); - - if (!poolConfig.ExternalStratum) - { - disposables.Add(manager.Jobs.Subscribe(OnNewJob)); - - // we need work before opening the gates - await manager.Jobs.Take(1).ToTask(); - } - } - - protected override WorkerContextBase CreateClientContext() - { - return new BitcoinWorkerContext(); - } - - protected override async Task OnRequestAsync(StratumClient client, - Timestamped tsRequest) - { - var request = tsRequest.Value; - - switch(request.Method) - { - case BitcoinStratumMethods.Subscribe: - OnSubscribe(client, tsRequest); - break; - - case BitcoinStratumMethods.Authorize: - await OnAuthorizeAsync(client, tsRequest); - break; - - case BitcoinStratumMethods.SubmitShare: - await OnSubmitAsync(client, tsRequest); - break; - - case BitcoinStratumMethods.SuggestDifficulty: - OnSuggestDifficulty(client, tsRequest); - break; - - case BitcoinStratumMethods.GetTransactions: - OnGetTransactions(client, tsRequest); - break; - - case BitcoinStratumMethods.ExtraNonceSubscribe: - // ignored + + ForEachClient(client => + { + var context = client.GetContextAs(); + + if (context.IsSubscribed && context.IsAuthorized) + { + // check alive + var lastActivityAgo = clock.Now - context.LastActivity; + + if (poolConfig.ClientConnectionTimeout > 0 && + lastActivityAgo.TotalSeconds > poolConfig.ClientConnectionTimeout) + { + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Booting zombie-worker (idle-timeout exceeded)"); + DisconnectClient(client); + return; + } + + // varDiff: if the client has a pending difficulty change, apply it now + if (context.ApplyPendingDifficulty()) + client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + + // send job + client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + }); + } + + #region Overrides + + protected virtual BitcoinJobManager CreateJobManager() + { + return ctx.Resolve>( + new TypedParameter(typeof(IExtraNonceProvider), new BitcoinExtraNonceProvider())); + } + + protected override async Task SetupJobManager() + { + manager = CreateJobManager(); + manager.Configure(poolConfig, clusterConfig); + + await manager.StartAsync(); + + if (!poolConfig.ExternalStratum) + { + disposables.Add(manager.Jobs.Subscribe(OnNewJob)); + + // we need work before opening the gates + await manager.Jobs.Take(1).ToTask(); + } + } + + protected override WorkerContextBase CreateClientContext() + { + return new BitcoinWorkerContext(); + } + + protected override async Task OnRequestAsync(StratumClient client, + Timestamped tsRequest) + { + var request = tsRequest.Value; + + switch(request.Method) + { + case BitcoinStratumMethods.Subscribe: + OnSubscribe(client, tsRequest); break; - - default: - logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); - - client.RespondError(StratumError.Other, $"Unsupported request {request.Method}", request.Id); - break; - } - } - - public override ulong HashrateFromShares(double shares, double interval) - { - var multiplier = BitcoinConstants.Pow2x32 / manager.ShareMultiplier; - var result = Math.Ceiling(shares * multiplier / interval); - - // OW: tmp hotfix - if (poolConfig.Coin.Type == CoinType.MONA || poolConfig.Coin.Type == CoinType.VTC) - result *= 1.3; - Console.WriteLine(result); - return (ulong)result; - } - - protected override void OnVarDiffUpdate(StratumClient client, double newDiff) - { - var context = client.GetContextAs(); - context.EnqueueNewDifficulty(newDiff); - - // apply immediately and notify client - if (context.HasPendingDifficulty) - { - context.ApplyPendingDifficulty(); - - client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); - client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); - } - } - - protected override async Task UpdateBlockChainStatsAsync() - { - await manager.UpdateNetworkStatsAsync(); - - blockchainStats = manager.BlockchainStats; - } - - #endregion // Overrides - } -} + + case BitcoinStratumMethods.Authorize: + await OnAuthorizeAsync(client, tsRequest); + break; + + case BitcoinStratumMethods.SubmitShare: + await OnSubmitAsync(client, tsRequest); + break; + + case BitcoinStratumMethods.SuggestDifficulty: + OnSuggestDifficulty(client, tsRequest); + break; + + case BitcoinStratumMethods.GetTransactions: + OnGetTransactions(client, tsRequest); + break; + + case BitcoinStratumMethods.ExtraNonceSubscribe: + // ignored + break; + + default: + logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); + + client.RespondError(StratumError.Other, $"Unsupported request {request.Method}", request.Id); + break; + } + } + + public override ulong HashrateFromShares(double shares, double interval) + { + var multiplier = BitcoinConstants.Pow2x32 / manager.ShareMultiplier; + var result = Math.Ceiling(shares * multiplier / interval); + + // OW: tmp hotfix + if (poolConfig.Coin.Type == CoinType.MONA || poolConfig.Coin.Type == CoinType.VTC || poolConfig.Coin.Type == CoinType.STAK) + result *= 2; + + return (ulong)result; + } + + protected override void OnVarDiffUpdate(StratumClient client, double newDiff) + { + var context = client.GetContextAs(); + context.EnqueueNewDifficulty(newDiff); + + // apply immediately and notify client + if (context.HasPendingDifficulty) + { + context.ApplyPendingDifficulty(); + + client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); + client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams); + } + } + + protected override async Task UpdateBlockChainStatsAsync() + { + await manager.UpdateNetworkStatsAsync(); + + blockchainStats = manager.BlockchainStats; + } + + #endregion // Overrides + } +} diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs index 8a8ae6b34..9c1b689b6 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs @@ -74,7 +74,7 @@ public class BitcoinProperties { // SHA256 { CoinType.BTC, sha256Coin }, - { CoinType.BCC, sha256Coin }, + { CoinType.BCH, sha256Coin }, { CoinType.NMC, sha256Coin }, { CoinType.PPC, sha256Coin }, { CoinType.GLT, sha256Coin }, diff --git a/src/MiningCore/Blockchain/CoinMetaData.cs b/src/MiningCore/Blockchain/CoinMetaData.cs index dad8a726e..f3f926de2 100644 --- a/src/MiningCore/Blockchain/CoinMetaData.cs +++ b/src/MiningCore/Blockchain/CoinMetaData.cs @@ -21,8 +21,9 @@ public static class CoinMetaData }}, { CoinType.XMR, new Dictionary { { string.Empty, "https://chainradar.com/xmr/block/{0}" }}}, + { CoinType.ETN, new Dictionary { { string.Empty, "https://blockexplorer.electroneum.com/block/{0}" } }}, { CoinType.LTC, new Dictionary { { string.Empty, "http://explorer.litecoin.net/block/{0}" }}}, - { CoinType.BCC, new Dictionary { { string.Empty, "https://www.blocktrail.com/BCC/block/{0}" }}}, + { CoinType.BCH, new Dictionary { { string.Empty, "https://www.blocktrail.com/BCC/block/{0}" }}}, { CoinType.DASH, new Dictionary { { string.Empty, "https://chainz.cryptoid.info/dash/block.dws?{0}.htm" }}}, { CoinType.BTC, new Dictionary { { string.Empty, "https://blockchain.info/block/{0}" }}}, { CoinType.DOGE, new Dictionary { { string.Empty, "https://dogechain.info/block/{0}" }}}, @@ -43,10 +44,11 @@ public static class CoinMetaData public static readonly Dictionary PaymentInfoLinks = new Dictionary { { CoinType.XMR, "https://chainradar.com/xmr/transaction/{0}" }, + { CoinType.ETN, "https://blockexplorer.electroneum.com/tx/{0}" }, { CoinType.ETH, "https://etherscan.io/tx/{0}" }, { CoinType.ETC, "https://gastracker.io/tx/{0}" }, { CoinType.LTC, "http://explorer.litecoin.net/tx/{0}" }, - { CoinType.BCC, "https://www.blocktrail.com/BCC/tx/{0}" }, + { 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}" }, { CoinType.DOGE, "https://dogechain.info/tx/{0}" }, @@ -69,7 +71,7 @@ public static class CoinMetaData { CoinType.ETH, "https://etherscan.io/address/{0}" }, { CoinType.ETC, "https://gastracker.io/addr/{0}" }, { CoinType.LTC, "http://explorer.litecoin.net/address/{0}" }, - { CoinType.BCC, "https://www.blocktrail.com/BCC/address/{0}" }, + { 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}" }, { CoinType.DOGE, "https://dogechain.info/address/{0}" }, diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 78d87d4f5..771ac68be 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -225,6 +225,7 @@ public async Task ClassifyBlocksAsync(Block[] blocks) { // we've lost this one block.Status = BlockStatus.Orphaned; + block.Reward = 0; } } } diff --git a/src/MiningCore/Blockchain/Monero/MoneroConstants.cs b/src/MiningCore/Blockchain/Monero/MoneroConstants.cs index 48ac21e0c..f8de9eb21 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroConstants.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroConstants.cs @@ -41,6 +41,7 @@ public class MoneroConstants public static readonly Dictionary AddressLength = new Dictionary { { CoinType.XMR, 95 }, + { CoinType.ETN, 98 }, { CoinType.AEON, 97 }, }; diff --git a/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs b/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs index 60e2b04a6..2dd396ab2 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs @@ -45,7 +45,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Monero { - [CoinMetadata(CoinType.XMR, CoinType.AEON)] + [CoinMetadata(CoinType.XMR, CoinType.AEON, CoinType.ETN)] public class MoneroPayoutHandler : PayoutHandlerBase, IPayoutHandler { @@ -156,27 +156,29 @@ private async Task PayoutBatch(Balance[] balances) // send command var transferResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.Transfer, request); - if (walletSupportsTransferSplit) + // gracefully handle error -4 (transaction would be too large. try /transfer_split) + if (transferResponse.Error?.Code == -4) { - // gracefully handle error -4 (transaction would be too large. try /transfer_split) - if (transferResponse.Error?.Code == -4) + if (walletSupportsTransferSplit) { logger.Info(() => $"[{LogCategory}] Retrying transfer using {MWC.TransferSplit}"); var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.TransferSplit, request); - HandleTransferResponse(transferSplitResponse, balances); - } - else - HandleTransferResponse(transferResponse, balances); - } + // gracefully handle error -4 (transaction would be too large. try /transfer_split) + if (transferResponse.Error?.Code != -4) + { + HandleTransferResponse(transferSplitResponse, balances); + return; + } + } - else - { // retry paged + logger.Info(() => $"[{LogCategory}] Retrying paged"); + var validBalances = balances.Where(x => x.Amount > 0).ToArray(); var pageSize = 10; - var pageCount = (int) Math.Ceiling((double) validBalances.Length / pageSize); + var pageCount = (int)Math.Ceiling((double)validBalances.Length / pageSize); for (var i = 0; i < pageCount; i++) { @@ -191,9 +193,11 @@ private async Task PayoutBatch(Balance[] balances) .Select(x => new TransferDestination { Address = x.Address, - Amount = (ulong) Math.Floor(x.Amount * MoneroConstants.Piconero) + Amount = (ulong)Math.Floor(x.Amount * MoneroConstants.Piconero) }).ToArray(); + logger.Info(() => $"[{LogCategory}] Page {i + 1}: Paying out {FormatAmount(page.Sum(x => x.Amount))} to {page.Length} addresses"); + transferResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.Transfer, request); HandleTransferResponse(transferResponse, page); @@ -201,6 +205,9 @@ private async Task PayoutBatch(Balance[] balances) break; } } + + else + HandleTransferResponse(transferResponse, balances); } private async Task PayoutToPaymentId(Balance balance) @@ -339,6 +346,7 @@ public async Task ClassifyBlocksAsync(Block[] blocks) if (blockHeader.IsOrphaned || blockHeader.Hash != block.TransactionConfirmationData) { block.Status = BlockStatus.Orphaned; + block.Reward = 0; continue; } diff --git a/src/MiningCore/Blockchain/Monero/MoneroPool.cs b/src/MiningCore/Blockchain/Monero/MoneroPool.cs index 4cec21014..0d3a6b66d 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroPool.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroPool.cs @@ -19,18 +19,14 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reactive; -using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Threading; using System.Threading.Tasks; using Autofac; -using Autofac.Features.Metadata; using AutoMapper; using MiningCore.Blockchain.Monero.StratumRequests; using MiningCore.Blockchain.Monero.StratumResponses; @@ -46,7 +42,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Monero { - [CoinMetadata(CoinType.XMR, CoinType.AEON)] + [CoinMetadata(CoinType.XMR, CoinType.AEON, CoinType.ETN)] public class MoneroPool : PoolBase { public MoneroPool(IComponentContext ctx, diff --git a/src/MiningCore/Blockchain/Straks/DaemonResponses/StraksBlockTemplateResponse.cs b/src/MiningCore/Blockchain/Straks/DaemonResponses/StraksBlockTemplateResponse.cs index c47f2f690..de06cea9b 100644 --- a/src/MiningCore/Blockchain/Straks/DaemonResponses/StraksBlockTemplateResponse.cs +++ b/src/MiningCore/Blockchain/Straks/DaemonResponses/StraksBlockTemplateResponse.cs @@ -22,13 +22,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Straks.DaemonResponses { - public class StraksMasternode - { - public string Payee { get; set; } - public string Script { get; set; } - public long Amount { get; set; } - } - public class StraksCoinbaseTransaction { public string Data { get; set; } @@ -56,8 +49,6 @@ public class StraksBlockTemplate : Bitcoin.DaemonResponses.BlockTemplate [JsonProperty("payee_amount")] public long? PayeeAmount { get; set; } - public StraksMasternode Masternode { get; set; } - [JsonProperty("masternode_payments")] public bool MasternodePaymentsStarted { get; set; } diff --git a/src/MiningCore/Blockchain/Straks/StraksJob.cs b/src/MiningCore/Blockchain/Straks/StraksJob.cs index 5b2c98b99..2a149a8a6 100644 --- a/src/MiningCore/Blockchain/Straks/StraksJob.cs +++ b/src/MiningCore/Blockchain/Straks/StraksJob.cs @@ -46,6 +46,12 @@ protected override Transaction CreateOutputTransaction() return tx; } + private bool ShouldHandleMasternodePayment() + { + return BlockTemplate.MasternodePaymentsStarted && + BlockTemplate.MasternodePaymentsEnforced && + !string.IsNullOrEmpty(BlockTemplate.Payee) && BlockTemplate.PayeeAmount.HasValue; + } private Money CreateStraksOutputs(Transaction tx, Money reward) { @@ -59,21 +65,10 @@ private Money CreateStraksOutputs(Transaction tx, Money reward) reward -= treasuryReward; } - if (!string.IsNullOrEmpty(BlockTemplate.Masternode?.Payee)) - { - var payeeAddress = BitcoinUtils.AddressToDestination(BlockTemplate.Masternode.Payee); - var payeeReward = BlockTemplate.Masternode.Amount; - - reward -= payeeReward; - rewardToPool -= payeeReward; - - tx.AddOutput(payeeReward, payeeAddress); - } - - if (!string.IsNullOrEmpty(BlockTemplate.Payee)) + if (ShouldHandleMasternodePayment()) { var payeeAddress = BitcoinUtils.AddressToDestination(BlockTemplate.Payee); - var payeeReward = BlockTemplate.PayeeAmount ?? reward / 5; + var payeeReward = BlockTemplate.PayeeAmount.Value; reward -= payeeReward; rewardToPool -= payeeReward; diff --git a/src/MiningCore/Configuration/ClusterConfig.cs b/src/MiningCore/Configuration/ClusterConfig.cs index 7915b3362..768ad7fdb 100644 --- a/src/MiningCore/Configuration/ClusterConfig.cs +++ b/src/MiningCore/Configuration/ClusterConfig.cs @@ -28,7 +28,7 @@ public enum CoinType { // ReSharper disable InconsistentNaming BTC = 1, // Bitcoin - BCC, // Bitcoin Cash + BCH, // Bitcoin Cash LTC, // Litecoin DOGE, // Dogecoin, XMR, // Monero @@ -46,9 +46,10 @@ public enum CoinType VTC, // Vertcoin BTG, // Bitcoin Gold GLT, // Globaltoken - ELLA, //Ellaism + ELLA, // Ellaism AEON, // AEON - STAK //Straks + STAK, // Straks + ETN // Electroneum } public class CoinConfig diff --git a/src/MiningCore/Persistence/Model/Projections/MinerStats.cs b/src/MiningCore/Persistence/Model/Projections/MinerStats.cs index d50b61a15..5091541aa 100644 --- a/src/MiningCore/Persistence/Model/Projections/MinerStats.cs +++ b/src/MiningCore/Persistence/Model/Projections/MinerStats.cs @@ -18,6 +18,23 @@ portions of the Software. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +using System; +using System.Collections.Generic; + +namespace MiningCore.Persistence.Model.Projections +{ + public class WorkerPerformanceStats + { + public double Hashrate { get; set; } + public double SharesPerSecond { get; set; } + } + + public class WorkerPerformanceStatsContainer + { + public DateTime Created { get; set; } + public Dictionary Workers { get; set; } + } + namespace MiningCore.Persistence.Model.Projections { public class MinerStats @@ -26,6 +43,7 @@ public class MinerStats public decimal PendingBalance { get; set; } public decimal TotalPaid { get; set; } public Payment LastPayment { get; set; } + public WorkerPerformanceStatsContainer Performance { get; set; } public MinerWorkerPerformanceStats[] PerformanceStats { get; set; } } } diff --git a/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs index 00e35cc6f..5ea3b4ff2 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs @@ -40,7 +40,7 @@ public ShareRepository(IMapper mapper) } private readonly IMapper mapper; - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); public void Insert(IDbConnection con, IDbTransaction tx, Share share) { @@ -140,7 +140,7 @@ public MinerWorkerHashes[] GetHashAccumulationBetweenCreated(IDbConnection con, { logger.LogInvoke(new[] { poolId }); - var query = "SELECT SUM(difficulty), COUNT(difficulty), min(created) as firstshare, max(created) as lastshare, miner, worker FROM shares " + + var query = "SELECT SUM(difficulty), COUNT(difficulty), MIN(created) AS firstshare, MAX(created) AS lastshare, miner, worker FROM shares " + "WHERE poolid = @poolId AND created >= @start AND created <= @end " + "GROUP BY miner, worker"; diff --git a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs index 6ced6bf24..41172cf8e 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs @@ -40,7 +40,7 @@ public StatsRepository(IMapper mapper) } private readonly IMapper mapper; - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); public void InsertPoolStats(IDbConnection con, IDbTransaction tx, PoolStats stats) { @@ -96,12 +96,12 @@ public PoolStats[] GetPoolStatsBetweenHourly(IDbConnection con, string poolId, D logger.LogInvoke(new []{ poolId }); var query = "SELECT date_trunc('hour', created) AS created, " + - " AVG(poolhashrate) AS poolhashrate, " + - " CAST(AVG(connectedminers) AS BIGINT) AS connectedminers " + - "FROM poolstats " + - "WHERE poolid = @poolId AND created >= @start AND created <= @end " + - "GROUP BY date_trunc('hour', created) " + - "ORDER BY created;"; + "AVG(poolhashrate) AS poolhashrate, " + + "CAST(AVG(connectedminers) AS BIGINT) AS connectedminers " + + "FROM poolstats " + + "WHERE poolid = @poolId AND created >= @start AND created <= @end " + + "GROUP BY date_trunc('hour', created) " + + "ORDER BY created;"; return con.Query(query, new { poolId, start, end }) .Select(mapper.Map) @@ -140,11 +140,49 @@ public MinerStats GetMinerStats(IDbConnection con, IDbTransaction tx, string poo .Select(mapper.Map) .ToArray(); - result.PerformanceStats = stats; + if (stats.Any()) + { + // replace null worker with empty string + foreach(var stat in stats) + { + if (stat.Worker == null) + { + stat.Worker = string.Empty; + break; + } + } + + // transform to dictionary + result.Performance = new WorkerPerformanceStatsContainer + { + Workers = stats.ToDictionary(x => x.Worker, x => new WorkerPerformanceStats + { + Hashrate = x.Hashrate, + SharesPerSecond = x.SharesPerSecond + }), + + Created = stats.First().Created + }; + } } } return result; } + + public MinerWorkerPerformanceStats[] GetMinerStatsBetweenHourly(IDbConnection con, string poolId, string miner, DateTime start, DateTime end) + { + logger.LogInvoke(new[] { poolId }); + + var query = "SELECT worker, date_trunc('hour', created) AS created, AVG(hashrate) AS hashrate, " + + "AVG(sharespersecond) AS sharespersecond FROM minerstats " + + "WHERE poolid = @poolId AND miner = @miner AND created >= @start AND created <= @end " + + "GROUP BY date_trunc('hour', created), worker " + + "ORDER BY created, worker;"; + + return con.Query(query, new { poolId, miner, start, end }) + .Select(mapper.Map) + .ToArray(); + } } } diff --git a/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql b/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql index 0759b5fa8..525960a9f 100644 --- a/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql +++ b/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql @@ -86,4 +86,4 @@ CREATE TABLE minerstats ); CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED on minerstats(poolid, miner, created); -CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED_HOUR on minerstats(poolid, miner, worker, date_trunc('hour',created)); +CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED_HOUR on minerstats(poolid, miner, date_trunc('hour',created)); diff --git a/src/MiningCore/Persistence/Repositories/IStatsRepository.cs b/src/MiningCore/Persistence/Repositories/IStatsRepository.cs index 80a24dcb5..e16467229 100644 --- a/src/MiningCore/Persistence/Repositories/IStatsRepository.cs +++ b/src/MiningCore/Persistence/Repositories/IStatsRepository.cs @@ -20,9 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Data; -using Dapper; using MiningCore.Persistence.Model; -using MiningCore.Persistence.Model.Projections; using MinerStats = MiningCore.Persistence.Model.Projections.MinerStats; namespace MiningCore.Persistence.Repositories @@ -30,7 +28,7 @@ namespace MiningCore.Persistence.Repositories public interface IStatsRepository { void InsertPoolStats(IDbConnection con, IDbTransaction tx, PoolStats stats); - void InsertMinerWorkerPerformanceStats(IDbConnection con, IDbTransaction tx, Model.MinerWorkerPerformanceStats stats); + void InsertMinerWorkerPerformanceStats(IDbConnection con, IDbTransaction tx, MinerWorkerPerformanceStats stats); PoolStats GetLastPoolStats(IDbConnection con, string poolId); PoolStats[] PagePoolStatsBetween(IDbConnection con, string poolId, DateTime start, DateTime end, int page, int pageSize); PoolStats[] GetPoolStatsBetweenHourly(IDbConnection con, string poolId, DateTime start, DateTime end);