diff --git a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs index 02b0c0a2a..2282c2ead 100644 --- a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs @@ -42,7 +42,7 @@ public void BitcoinJob_Should_Accept_Valid_Share() var clock = new MockMasterClock { CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869874).UtcDateTime }; job.Init(bt, "1", poolConfig, clusterConfig, clock, poolAddressDestination, BitcoinNetworkType.RegTest, - false, 1, sha256d, sha256d, sha256dReverse); + false, 1, 1, sha256d, sha256d, sha256dReverse); // set clock to submission time clock.CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869907).UtcDateTime; @@ -78,7 +78,7 @@ public void BitcoinJob_Should_Not_Accept_Invalid_Share() var clock = new MockMasterClock { CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869874).UtcDateTime }; job.Init(bt, "1", poolConfig, clusterConfig, clock, poolAddressDestination, BitcoinNetworkType.RegTest, - false, 1, sha256d, sha256d, sha256dReverse); + false, 1, 1, sha256d, sha256d, sha256dReverse); // set clock to submission time clock.CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869907).UtcDateTime; diff --git a/src/MiningCore.Tests/Blockchain/ZCash/ZCashJobTests.cs b/src/MiningCore.Tests/Blockchain/ZCash/ZCashJobTests.cs index 5d3462fa2..1264b8be9 100644 --- a/src/MiningCore.Tests/Blockchain/ZCash/ZCashJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/ZCash/ZCashJobTests.cs @@ -49,7 +49,7 @@ public void ZCashJob_Testnet_Validate_FoundersRewardAddress_At_Height() var clock = new MockMasterClock { CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869874).UtcDateTime }; job.Init(bt, "1", poolConfig, clusterConfig, clock, poolAddressDestination, BitcoinNetworkType.Test, - false, 1, sha256d, sha256d, sha256dReverse); + false, 1, 1, sha256d, sha256d, sha256dReverse); bt.Height = 1; Assert.Equal(job.GetFoundersRewardAddress(), "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi"); diff --git a/src/MiningCore.Tests/Crypto/HashingTests.cs b/src/MiningCore.Tests/Crypto/HashingTests.cs index 0f6d3d634..a0be06bd4 100644 --- a/src/MiningCore.Tests/Crypto/HashingTests.cs +++ b/src/MiningCore.Tests/Crypto/HashingTests.cs @@ -33,6 +33,22 @@ public void Blake_Hash_Should_Throw_On_Null_Input() Assert.Throws(() => hasher.Digest(null)); } + [Fact] + public void Blake2s_Hash_Should_Match() + { + var hasher = new Blake2s(); + var result = hasher.Digest(testValue2).ToHexString(); + + Assert.Equal("c3ee938582d70ccd9a323b6097357449365d1fdfbbe2ecd7ee44e4bdbbb24392", result); + } + + [Fact] + public void Blake2s_Hash_Should_Throw_On_Null_Input() + { + var hasher = new Blake2s(); + Assert.Throws(() => hasher.Digest(null)); + } + [Fact] public void Groestl_Hash_Should_Match() { diff --git a/src/MiningCore/Api/ApiServer.cs b/src/MiningCore/Api/ApiServer.cs index bc347181d..7138fa742 100644 --- a/src/MiningCore/Api/ApiServer.cs +++ b/src/MiningCore/Api/ApiServer.cs @@ -412,6 +412,8 @@ private async Task GetMinerInfoAsync(HttpContext context, Match m) return; } + var perfMode = context.GetQueryParameter("perfMode", "day"); + var statsResult = cf.RunTx((con, tx) => statsRepo.GetMinerStats(con, tx, pool.Id, address), true, IsolationLevel.Serializable); @@ -432,7 +434,7 @@ private async Task GetMinerInfoAsync(HttpContext context, Match m) stats.LastPaymentLink = string.Format(baseUrl, statsResult.LastPayment.TransactionConfirmationData); } - stats.Performance24H = GetMinerPerformanceInternal("day", pool, address); + stats.PerformanceSamples = GetMinerPerformanceInternal(perfMode, pool, address); } await SendJson(context, stats); diff --git a/src/MiningCore/Api/Extensions/MiningPoolExtensions.cs b/src/MiningCore/Api/Extensions/MiningPoolExtensions.cs index 96dacf9ad..4ca943b59 100644 --- a/src/MiningCore/Api/Extensions/MiningPoolExtensions.cs +++ b/src/MiningCore/Api/Extensions/MiningPoolExtensions.cs @@ -42,13 +42,10 @@ public static PoolInfo ToPoolInfo(this PoolConfig pool, IMapper mapper, Persiste private static string GetPoolAlgorithm(PoolConfig pool) { - var result = pool.Coin.Algorithm; + string result = null; - if (string.IsNullOrEmpty(result)) - { - if (CoinMetaData.CoinAlgorithm.TryGetValue(pool.Coin.Type, out var getter)) - result = getter(pool.Coin.Type); - } + if (CoinMetaData.CoinAlgorithm.TryGetValue(pool.Coin.Type, out var getter)) + result = getter(pool.Coin.Type, pool.Coin.Algorithm); // Capitalize if (!string.IsNullOrEmpty(result) && result.Length > 1) diff --git a/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs b/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs index 1695b68c4..84241e3b4 100644 --- a/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs +++ b/src/MiningCore/Api/Responses/GetMinerStatsResponse.cs @@ -50,6 +50,6 @@ public class MinerStats public DateTime? LastPayment { get; set; } public string LastPaymentLink { get; set; } public WorkerPerformanceStatsContainer Performance { get; set; } - public WorkerPerformanceStatsContainer[] Performance24H { get; set; } + public WorkerPerformanceStatsContainer[] PerformanceSamples { get; set; } } } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinConstants.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinConstants.cs index ea30b2ab5..9ab266014 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinConstants.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinConstants.cs @@ -110,7 +110,6 @@ public static class BitcoinCommands public const string GetMiningInfo = "getmininginfo"; public const string GetPeerInfo = "getpeerinfo"; public const string ValidateAddress = "validateaddress"; - public const string GetDifficulty = "getdifficulty"; public const string GetBlockTemplate = "getblocktemplate"; public const string GetBlockSubsidy = "getblocksubsidy"; public const string SubmitBlock = "submitblock"; @@ -118,5 +117,10 @@ public static class BitcoinCommands public const string GetBlock = "getblock"; public const string GetTransaction = "gettransaction"; public const string SendMany = "sendmany"; + + // Legacy commands + public const string GetInfo = "getinfo"; + public const string GetDifficulty = "getdifficulty"; + public const string GetConnectionCount = "getconnectioncount"; } } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs index 2d555fc80..de532815b 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs @@ -46,6 +46,7 @@ public class BitcoinJob protected IMasterClock clock; protected IHashAlgorithm coinbaseHasher; protected double shareMultiplier; + protected decimal blockRewardMultiplier; protected int extraNoncePlaceHolderLength; protected IHashAlgorithm headerHasher; protected bool isPoS; @@ -239,7 +240,7 @@ protected virtual Script GenerateScriptSigInitial() protected virtual Transaction CreateOutputTransaction() { - rewardToPool = new Money(BlockTemplate.CoinbaseValue, MoneyUnit.Satoshi); + rewardToPool = new Money(BlockTemplate.CoinbaseValue * blockRewardMultiplier, MoneyUnit.Satoshi); var tx = new Transaction(); @@ -411,7 +412,7 @@ protected virtual byte[] BuildRawTransactionBuffer() public virtual void Init(TBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, IDestination poolAddressDestination, BitcoinNetworkType networkType, - bool isPoS, double shareMultiplier, + bool isPoS, double shareMultiplier, decimal blockrewardMultiplier, IHashAlgorithm coinbaseHasher, IHashAlgorithm headerHasher, IHashAlgorithm blockHasher) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); @@ -436,6 +437,7 @@ public virtual void Init(TBlockTemplate blockTemplate, string jobId, extraNoncePlaceHolderLength = BitcoinConstants.ExtranoncePlaceHolderLength; this.isPoS = isPoS; this.shareMultiplier = shareMultiplier; + this.blockRewardMultiplier = blockrewardMultiplier; this.coinbaseHasher = coinbaseHasher; this.headerHasher = headerHasher; diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs index 9c3a84d2b..60752b458 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -23,6 +23,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Linq; using System.Net; using System.Reactive.Linq; +using System.Text; using System.Threading.Tasks; using Autofac; using MiningCore.Blockchain.Bitcoin.Configuration; @@ -74,7 +75,9 @@ public BitcoinJobManager( protected readonly IHashAlgorithm sha256d = new Sha256D(); protected readonly IHashAlgorithm sha256dReverse = new DigestReverser(new Sha256D()); protected int maxActiveJobs = 4; + protected bool hasLegacyDaemon; protected BitcoinPoolConfigExtra extraPoolConfig; + protected BitcoinPoolPaymentProcessingConfigExtra extraPoolPaymentProcessingConfig; protected readonly IHashAlgorithm sha256s = new Sha256S(); protected readonly List validJobs = new List(); protected IHashAlgorithm blockHasher; @@ -95,6 +98,7 @@ public BitcoinJobManager( } }; + protected virtual void SetupJobUpdates() { if (poolConfig.ExternalStratum) @@ -115,7 +119,27 @@ protected virtual void SetupJobUpdates() logger.Info(() => $"[{LogCat}] Subscribing to ZMQ push-updates from {string.Join(", ", zmq.Values)}"); var newJobsPubSub = daemon.ZmqSubscribe(zmq, BitcoinConstants.ZmqPublisherTopicBlockHash, 2) - .Do(x=> x.Dispose()) // we don't care about the contents + .Select(frames => + { + try + { + // second frame contains the block hash as binary data + if (frames.Length > 1) + { + var hash = frames[1].ToHexString(); + return hash; + } + } + + finally + { + frames.Dispose(); + } + + return null; + }) + .Where(x=> x != null) + .DistinctUntilChanged() .Select(_ => Observable.FromAsync(() => UpdateJob(false, "ZMQ pub/sub"))) .Concat() .Publish() @@ -182,6 +206,12 @@ protected virtual async Task> GetBlockTemplateAsy protected virtual async Task ShowDaemonSyncProgressAsync() { + if (hasLegacyDaemon) + { + await ShowDaemonSyncProgressLegacyAsync(); + return; + } + var infos = await daemon.ExecuteCmdAllAsync(BitcoinCommands.GetBlockchainInfo); if (infos.Length > 0) @@ -228,7 +258,6 @@ private async Task UpdateNetworkStatsAsync() var networkInfoResponse = results[2].Response.ToObject(); BlockchainStats.BlockHeight = infoResponse.Blocks; - BlockchainStats.NetworkDifficulty = miningInfoResponse.Difficulty; BlockchainStats.NetworkHashRate = miningInfoResponse.NetworkHashps; BlockchainStats.ConnectedPeers = networkInfoResponse.Connections; } @@ -261,8 +290,8 @@ private async Task UpdateNetworkStatsAsync() if (!accepted) { - logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} submission failed because block was not found after submission"); - notificationService.NotifyAdmin("Block submission failed", $"Block {share.BlockHeight} submission failed because block was not found after submission"); + logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} submission failed for pool {poolConfig.Id} because block was not found after submission"); + notificationService.NotifyAdmin("Block submission failed", $"Block {share.BlockHeight} submission failed for pool {poolConfig.Id} because block was not found after submission"); } return (accepted, block?.Transactions.FirstOrDefault()); @@ -277,10 +306,79 @@ protected virtual void SetupCrypto() coinbaseHasher = coinProps.CoinbaseHasher; headerHasher = coinProps.HeaderHasher; - blockHasher = !isPoS ? coinProps.BlockHasher : coinProps.PoSBlockHasher; + blockHasher = !isPoS ? coinProps.BlockHasher : (coinProps.PoSBlockHasher ?? coinProps.BlockHasher); ShareMultiplier = coinProps.ShareMultiplier; } + protected virtual async Task AreDaemonsHealthyLegacyAsync() + { + var responses = await daemon.ExecuteCmdAllAsync(BitcoinCommands.GetInfo); + + if (responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) + .Select(x => (DaemonClientException)x.Error.InnerException) + .Any(x => x.Code == HttpStatusCode.Unauthorized)) + logger.ThrowLogPoolStartupException($"Daemon reports invalid credentials", LogCat); + + return responses.All(x => x.Error == null); + } + + protected virtual async Task AreDaemonsConnectedLegacyAsync() + { + var response = await daemon.ExecuteCmdAnyAsync(BitcoinCommands.GetInfo); + + return response.Error == null && response.Response.Connections > 0; + } + + protected virtual async Task ShowDaemonSyncProgressLegacyAsync() + { + var infos = await daemon.ExecuteCmdAllAsync(BitcoinCommands.GetInfo); + + if (infos.Length > 0) + { + var blockCount = infos + .Max(x => x.Response?.Blocks); + + if (blockCount.HasValue) + { + // get list of peers and their highest block height to compare to ours + var peerInfo = await daemon.ExecuteCmdAnyAsync(BitcoinCommands.GetPeerInfo); + var peers = peerInfo.Response; + + if (peers != null && peers.Length > 0) + { + var totalBlocks = peers.Max(x => x.StartingHeight); + var percent = totalBlocks > 0 ? (double)blockCount / totalBlocks * 100 : 0; + logger.Info(() => $"[{LogCat}] Daemons have downloaded {percent:0.00}% of blockchain from {peers.Length} peers"); + } + } + } + } + + private async Task UpdateNetworkStatsLegacyAsync() + { + logger.LogInvoke(LogCat); + + var results = await daemon.ExecuteBatchAnyAsync( + new DaemonCmd(BitcoinCommands.GetMiningInfo), + new DaemonCmd(BitcoinCommands.GetConnectionCount) + ); + + if (results.Any(x => x.Error != null)) + { + var errors = results.Where(x => x.Error != null).ToArray(); + + if (errors.Any()) + logger.Warn(() => $"[{LogCat}] Error(s) refreshing network stats: {string.Join(", ", errors.Select(y => y.Error.Message))}"); + } + + var miningInfoResponse = results[0].Response.ToObject(); + var connectionCountResponse = results[1].Response.ToObject(); + + BlockchainStats.BlockHeight = miningInfoResponse.Blocks; + //BlockchainStats.NetworkHashRate = miningInfoResponse.NetworkHashps; + BlockchainStats.ConnectedPeers = (int) (long) connectionCountResponse; + } + #region API-Surface public IObservable Jobs { get; private set; } @@ -428,10 +526,13 @@ public virtual async Task SubmitShareAsync(StratumClient worker, o public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig) { extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs(); + extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs(); if (extraPoolConfig?.MaxActiveJobs.HasValue == true) maxActiveJobs = extraPoolConfig.MaxActiveJobs.Value; + hasLegacyDaemon = extraPoolConfig?.HasLegacyDaemon == true; + base.Configure(poolConfig, clusterConfig); } @@ -443,20 +544,26 @@ protected override void ConfigureDaemons() daemon.Configure(poolConfig.Daemons); } - protected override async Task AreDaemonsHealthy() + protected override async Task AreDaemonsHealthyAsync() { + if (hasLegacyDaemon) + return await AreDaemonsHealthyLegacyAsync(); + var responses = await daemon.ExecuteCmdAllAsync(BitcoinCommands.GetBlockchainInfo); if (responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) + .Select(x => (DaemonClientException)x.Error.InnerException) .Any(x => x.Code == HttpStatusCode.Unauthorized)) logger.ThrowLogPoolStartupException($"Daemon reports invalid credentials", LogCat); return responses.All(x => x.Error == null); } - protected override async Task AreDaemonsConnected() + protected override async Task AreDaemonsConnectedAsync() { + if (hasLegacyDaemon) + return await AreDaemonsConnectedLegacyAsync(); + var response = await daemon.ExecuteCmdAnyAsync(BitcoinCommands.GetNetworkInfo); return response.Error == null && response.Response.Connections > 0; @@ -497,9 +604,9 @@ protected override async Task PostStartInitAsync() var commands = new[] { new DaemonCmd(BitcoinCommands.ValidateAddress, new[] { poolConfig.Address }), - new DaemonCmd(BitcoinCommands.GetDifficulty), new DaemonCmd(BitcoinCommands.SubmitBlock), - new DaemonCmd(BitcoinCommands.GetBlockchainInfo) + new DaemonCmd(!hasLegacyDaemon ? BitcoinCommands.GetBlockchainInfo : BitcoinCommands.GetInfo), + new DaemonCmd(BitcoinCommands.GetDifficulty), }; var results = await daemon.ExecuteBatchAnyAsync(commands); @@ -516,9 +623,10 @@ protected override async Task PostStartInitAsync() // extract results var validateAddressResponse = results[0].Response.ToObject(); - var difficultyResponse = results[1].Response.ToObject(); - var submitBlockResponse = results[2]; - var blockchainInfoResponse = results[3].Response.ToObject(); + var submitBlockResponse = results[1]; + var blockchainInfoResponse = !hasLegacyDaemon ? results[2].Response.ToObject() : null; + var daemonInfoResponse = hasLegacyDaemon ? results[2].Response.ToObject() : null; + var difficultyResponse = results[3].Response.ToObject(); // ensure pool owns wallet if (!validateAddressResponse.IsValid) @@ -530,18 +638,23 @@ protected override async Task PostStartInitAsync() isPoS = difficultyResponse.Values().Any(x => x.Path == "proof-of-stake"); // Create pool address script from response - if (isPoS) - poolAddressDestination = new PubKey(validateAddressResponse.PubKey); - else - poolAddressDestination = AddressToDestination(validateAddressResponse.Address); + poolAddressDestination = !isPoS ? + AddressToDestination(validateAddressResponse.Address) : + new PubKey(validateAddressResponse.PubKey); // chain detection - if (blockchainInfoResponse.Chain.ToLower() == "test") - networkType = BitcoinNetworkType.Test; - else if (blockchainInfoResponse.Chain.ToLower() == "regtest") - networkType = BitcoinNetworkType.RegTest; + if (!hasLegacyDaemon) + { + if (blockchainInfoResponse.Chain.ToLower() == "test") + networkType = BitcoinNetworkType.Test; + else if (blockchainInfoResponse.Chain.ToLower() == "regtest") + networkType = BitcoinNetworkType.RegTest; + else + networkType = BitcoinNetworkType.Main; + } + else - networkType = BitcoinNetworkType.Main; + networkType = daemonInfoResponse.Testnet ? BitcoinNetworkType.Test : BitcoinNetworkType.Main; ConfigureRewards(); @@ -557,7 +670,10 @@ protected override async Task PostStartInitAsync() else logger.ThrowLogPoolStartupException($"Unable detect block submission RPC method", LogCat); - await UpdateNetworkStatsAsync(); + if(!hasLegacyDaemon) + await UpdateNetworkStatsAsync(); + else + await UpdateNetworkStatsLegacyAsync(); SetupCrypto(); SetupJobUpdates(); @@ -624,7 +740,7 @@ protected virtual async Task UpdateJob(bool forceUpdate, string via = null job.Init(blockTemplate, NextJobId(), poolConfig, clusterConfig, clock, poolAddressDestination, networkType, isPoS, - ShareMultiplier, + ShareMultiplier, extraPoolPaymentProcessingConfig?.BlockrewardMultiplier ?? 1.0m, coinbaseHasher, headerHasher, blockHasher); if (isNew) @@ -640,11 +756,17 @@ protected virtual async Task UpdateJob(bool forceUpdate, string via = null lock (jobLock) { + if(isNew) + validJobs.Clear(); + validJobs.Add(job); - // trim active jobs - while (validJobs.Count > maxActiveJobs) - validJobs.RemoveAt(0); + if (!isNew) + { + // trim active jobs + while(validJobs.Count > maxActiveJobs) + validJobs.RemoveAt(0); + } } currentJob = job; diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 8c7b6e88b..ae09376c7 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -46,9 +46,8 @@ namespace MiningCore.Blockchain.Bitcoin [CoinMetadata( 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, CoinType.MOON, CoinType.XVG, - CoinType.GBX, CoinType.CRC)] + CoinType.GRS, CoinType.MONA, CoinType.VTC, CoinType.BTG, + CoinType.GLT, CoinType.STAK, CoinType.MOON, CoinType.XVG)] public class BitcoinPayoutHandler : PayoutHandlerBase, IPayoutHandler { @@ -190,9 +189,9 @@ public virtual async Task ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public virtual Task CalculateBlockEffortAsync(Block block, ulong accumulatedBlockShareDiff) + public virtual Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) { - block.Effort = (double) accumulatedBlockShareDiff / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } @@ -213,7 +212,7 @@ public virtual Task UpdateBlockRewardBalancesAsync(IDbConnection con, I if (address != poolConfig.Address) { logger.Info(() => $"Adding {FormatAmount(amount)} to balance of {address}"); - balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount); + balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount, $"Reward for block {block.BlockHeight}"); } } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs index a9525f54c..c9c7940b6 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPool.cs @@ -34,7 +34,7 @@ namespace MiningCore.Blockchain.Bitcoin CoinType.BTC, CoinType.BCH, CoinType.NMC, CoinType.PPC, CoinType.LTC, CoinType.DOGE, CoinType.DGB, CoinType.VIA, CoinType.GRS, CoinType.MONA, CoinType.VTC, CoinType.GLT, - CoinType.MOON, CoinType.XVG, CoinType.GBX, CoinType.CRC)] + CoinType.MOON, CoinType.XVG)] public class BitcoinPool : BitcoinPoolBase, BlockTemplate> { public BitcoinPool(IComponentContext ctx, diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs index ee1a85d91..8e372ac26 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs @@ -110,7 +110,7 @@ protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped< // extract worker/miner var split = workerValue?.Split('.'); - var minerName = split?.FirstOrDefault(); + var minerName = split?.FirstOrDefault()?.Trim(); var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; // assumes that workerName is an address @@ -183,8 +183,7 @@ protected virtual async Task OnSubmitAsync(StratumClient client, Timestamped $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning - if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true) - ConsiderBan(client, context, poolConfig.Banning); + ConsiderBan(client, context, poolConfig.Banning); } } @@ -333,7 +332,8 @@ protected override async Task OnRequestAsync(StratumClient client, break; case BitcoinStratumMethods.GetTransactions: - OnGetTransactions(client, tsRequest); + //OnGetTransactions(client, tsRequest); + // ignored break; case BitcoinStratumMethods.ExtraNonceSubscribe: @@ -354,7 +354,9 @@ public override double HashrateFromShares(double shares, double interval) var result = shares * multiplier / interval; // OW: tmp hotfix - if (poolConfig.Coin.Type == CoinType.MONA || poolConfig.Coin.Type == CoinType.VTC || poolConfig.Coin.Type == CoinType.STAK) + if (poolConfig.Coin.Type == CoinType.MONA || poolConfig.Coin.Type == CoinType.VTC || + poolConfig.Coin.Type == CoinType.STAK || + (poolConfig.Coin.Type == CoinType.XVG && poolConfig.Coin.Algorithm.ToLower() == "lyra")) result *= 4; return result; diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs index 80036d849..40e4a661d 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs @@ -35,6 +35,7 @@ public class BitcoinProperties private static readonly IHashAlgorithm sha256D = new Sha256D(); private static readonly IHashAlgorithm sha256DReverse = new DigestReverser(sha256D); private static readonly IHashAlgorithm x11 = new X11(); + private static readonly IHashAlgorithm blake2s = new Blake2s(); private static readonly IHashAlgorithm x17 = new X17(); private static readonly IHashAlgorithm groestl = new Groestl(); private static readonly IHashAlgorithm lyra2Rev2 = new Lyra2Rev2(); @@ -75,10 +76,19 @@ public class BitcoinProperties new BitcoinCoinProperties(1, new DummyHasher(), sha256D, sha256DReverse, "Equihash"); private static readonly BitcoinCoinProperties neoScryptCoin = - new BitcoinCoinProperties(Math.Pow(2, 16), sha256D, neoScryptProfile1, sha256DReverse, "Neoscrypt"); + new BitcoinCoinProperties(Math.Pow(2, 16), sha256D, neoScryptProfile1, new DigestReverser(neoScryptProfile1), "Neoscrypt"); - private static readonly BitcoinCoinProperties x17Coin = - new BitcoinCoinProperties(1, sha256D, x17, new DigestReverser(x17), "X17"); + private static readonly BitcoinCoinProperties vergeLyraCoin = + new BitcoinCoinProperties(Math.Pow(2, 8), sha256D, lyra2Rev2, new DigestReverser(scrypt_1024_1), "Lyra2re2"); + + private static readonly BitcoinCoinProperties vergeBlake2sCoin = + new BitcoinCoinProperties(1, sha256D, blake2s, new DigestReverser(scrypt_1024_1), "Blake2s"); + + private static readonly BitcoinCoinProperties vergeX17Coin = + new BitcoinCoinProperties(1, x17, blake2s, new DigestReverser(scrypt_1024_1), "X17"); + + private static readonly BitcoinCoinProperties vergeGroestlCoin = + new BitcoinCoinProperties(1, groestlMyriad, blake2s, new DigestReverser(scrypt_1024_1), "Groestl-Myriad"); private static readonly Dictionary coinProperties = new Dictionary { @@ -113,6 +123,7 @@ public class BitcoinProperties { CoinType.BTG, equihashCoin }, { CoinType.ZCL, equihashCoin }, { CoinType.ZEN, equihashCoin }, + { CoinType.BTCP, equihashCoin }, // Neoscrypt { CoinType.GBX, neoScryptCoin }, @@ -160,26 +171,28 @@ private static BitcoinCoinProperties GetVergeProperties(string algorithm) switch (algorithm.ToLower()) { - case "lyra2rev2": - return lyra2Rev2CoinVariantA; + case "lyra": + return vergeLyraCoin; - case "groestl-myriad": - return groestlMyriadCoin; + case "groestl": + return vergeGroestlCoin; case "x17": - return x17Coin; + return vergeX17Coin; - case "blake2s": - throw new NotSupportedException($"algorithm {algorithm} not yet supported"); + case "blake": + return vergeBlake2sCoin; default: // scrypt return scryptCoin; } } - public static string GetAlgorithm(CoinType coin) + public static string GetAlgorithm(CoinType coin, string configuredAlgorithm) { - if (coinProperties.TryGetValue(coin, out var props)) + var props = GetCoinProperties(coin, configuredAlgorithm); + + if (props != null) return props.Algorithm; return string.Empty; diff --git a/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolConfigExtra.cs b/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolConfigExtra.cs index 56607d93c..7f3577292 100644 --- a/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolConfigExtra.cs +++ b/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolConfigExtra.cs @@ -22,6 +22,15 @@ namespace MiningCore.Blockchain.Bitcoin.Configuration { public class BitcoinPoolConfigExtra { + /// + /// Maximum number of tracked jobs. + /// Default: 12 - you should increase this value if your blockrefreshinterval is higher than 300ms + /// public int? MaxActiveJobs { get; set; } + + /// + /// Set to true to limit RPC commands to old Bitcoin command set + /// + public bool? HasLegacyDaemon { get; set; } } } diff --git a/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolPaymentProcessingConfigExtra.cs b/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolPaymentProcessingConfigExtra.cs index e6d7fcdc8..1e78a2020 100644 --- a/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolPaymentProcessingConfigExtra.cs +++ b/src/MiningCore/Blockchain/Bitcoin/Configuration/BitcoinPoolPaymentProcessingConfigExtra.cs @@ -26,5 +26,10 @@ public class BitcoinPoolPaymentProcessingConfigExtra /// if True, miners pay payment tx fees /// public bool MinersPayTxFees { get; set; } + + /// + /// Multiply blockreward by this amount + /// + public decimal? BlockrewardMultiplier { get; set; } } } diff --git a/src/MiningCore/Blockchain/Bitcoin/DaemonResponses/GetInfoResponse.cs b/src/MiningCore/Blockchain/Bitcoin/DaemonResponses/GetInfoResponse.cs new file mode 100644 index 000000000..f4aa9a7d0 --- /dev/null +++ b/src/MiningCore/Blockchain/Bitcoin/DaemonResponses/GetInfoResponse.cs @@ -0,0 +1,34 @@ +/* +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. +*/ + +namespace MiningCore.Blockchain.Bitcoin.DaemonResponses +{ + public class DaemonInfo + { + public string Version { get; set; } + public int ProtocolVersion { get; set; } + public int WalletVersion { get; set; } + public decimal Balance { get; set; } + public ulong Blocks { get; set; } + public bool Testnet { get; set; } + public int Connections { get; set; } + public double Difficulty { get; set; } + } +} diff --git a/src/MiningCore/Blockchain/BitcoinGold/BitcoinGoldJob.cs b/src/MiningCore/Blockchain/BitcoinGold/BitcoinGoldJob.cs index 5906f9ad1..3069159dd 100644 --- a/src/MiningCore/Blockchain/BitcoinGold/BitcoinGoldJob.cs +++ b/src/MiningCore/Blockchain/BitcoinGold/BitcoinGoldJob.cs @@ -79,7 +79,7 @@ protected override byte[] SerializeHeader(uint nTime, string nonce) public override void Init(ZCashBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, IDestination poolAddressDestination, BitcoinNetworkType networkType, - bool isPoS, double shareMultiplier, + bool isPoS, double shareMultiplier, decimal blockrewardMultiplier, IHashAlgorithm coinbaseHasher, IHashAlgorithm headerHasher, IHashAlgorithm blockHasher) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); diff --git a/src/MiningCore/Blockchain/CoinMetaData.cs b/src/MiningCore/Blockchain/CoinMetaData.cs index 85892af61..d29fa8c2d 100644 --- a/src/MiningCore/Blockchain/CoinMetaData.cs +++ b/src/MiningCore/Blockchain/CoinMetaData.cs @@ -77,7 +77,7 @@ public static class CoinMetaData { CoinType.GBX, "http://gobyte.ezmine.io/tx/{0}" }, { CoinType.CRC, "http://explorer.cryptopros.us/tx/{0}" }, }; - + public static readonly Dictionary AddressInfoLinks = new Dictionary { { CoinType.ETH, "https://etherscan.io/address/{0}" }, @@ -110,10 +110,10 @@ public static class CoinMetaData private const string Cryptonight = "Cryptonight"; private const string CryptonightLight = "Cryptonight-Light"; - public static readonly Dictionary> CoinAlgorithm = new Dictionary> + public static readonly Dictionary> CoinAlgorithm = new Dictionary> { - { CoinType.ETH, (coin)=> Ethash }, - { CoinType.ETC, (coin)=> Ethash }, + { CoinType.ETH, (coin, alg)=> Ethash }, + { CoinType.ETC, (coin, alg)=> Ethash }, { CoinType.LTC, BitcoinProperties.GetAlgorithm }, { CoinType.BCH, BitcoinProperties.GetAlgorithm }, { CoinType.DASH, BitcoinProperties.GetAlgorithm }, @@ -121,6 +121,7 @@ public static class CoinMetaData { CoinType.DOGE, BitcoinProperties.GetAlgorithm }, { CoinType.ZEC, BitcoinProperties.GetAlgorithm }, { CoinType.ZCL, BitcoinProperties.GetAlgorithm }, + { CoinType.BTCP, BitcoinProperties.GetAlgorithm }, { CoinType.ZEN, BitcoinProperties.GetAlgorithm }, { CoinType.DGB, BitcoinProperties.GetAlgorithm }, { CoinType.NMC, BitcoinProperties.GetAlgorithm }, @@ -130,13 +131,13 @@ public static class CoinMetaData { CoinType.GLT, BitcoinProperties.GetAlgorithm }, { CoinType.VTC, BitcoinProperties.GetAlgorithm }, { CoinType.BTG, BitcoinProperties.GetAlgorithm }, - { CoinType.ELLA, (coin)=> Ethash }, - { CoinType.EXP, (coin)=> Ethash }, + { CoinType.ELLA, (coin, alg)=> Ethash }, + { CoinType.EXP, (coin, alg)=> Ethash }, { CoinType.MOON, BitcoinProperties.GetAlgorithm }, { CoinType.XVG, BitcoinProperties.GetAlgorithm }, - { CoinType.XMR, (coin)=> Cryptonight }, - { CoinType.ETN, (coin)=> Cryptonight }, - { CoinType.AEON, (coin)=> CryptonightLight }, + { CoinType.XMR, (coin, alg)=> Cryptonight }, + { CoinType.ETN, (coin, alg)=> Cryptonight }, + { CoinType.AEON, (coin, alg)=> CryptonightLight }, { CoinType.GBX, BitcoinProperties.GetAlgorithm }, { CoinType.CRC, BitcoinProperties.GetAlgorithm }, }; diff --git a/src/MiningCore/Blockchain/Dash/DashJob.cs b/src/MiningCore/Blockchain/Dash/DashJob.cs index a85747399..c8143901a 100644 --- a/src/MiningCore/Blockchain/Dash/DashJob.cs +++ b/src/MiningCore/Blockchain/Dash/DashJob.cs @@ -31,7 +31,7 @@ public class DashJob : BitcoinJob { protected override Transaction CreateOutputTransaction() { - var blockReward = new Money(BlockTemplate.CoinbaseValue, MoneyUnit.Satoshi); + var blockReward = new Money(BlockTemplate.CoinbaseValue * blockRewardMultiplier, MoneyUnit.Satoshi); rewardToPool = new Money(BlockTemplate.CoinbaseValue, MoneyUnit.Satoshi); var tx = new Transaction(); diff --git a/src/MiningCore/Blockchain/Dash/DashPayoutHandler.cs b/src/MiningCore/Blockchain/Dash/DashPayoutHandler.cs index cea070768..689f8e01b 100644 --- a/src/MiningCore/Blockchain/Dash/DashPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Dash/DashPayoutHandler.cs @@ -35,7 +35,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Dash { - [CoinMetadata(CoinType.DASH)] + [CoinMetadata(CoinType.DASH, CoinType.GBX, CoinType.CRC)] public class DashPayoutHandler : BitcoinPayoutHandler { public DashPayoutHandler( @@ -79,7 +79,7 @@ public override 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 false, // Whether to add confirmations to transactions locked via InstantSend @@ -94,7 +94,7 @@ public override 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/Dash/DashPool.cs b/src/MiningCore/Blockchain/Dash/DashPool.cs index 705944a6f..2d57d8844 100644 --- a/src/MiningCore/Blockchain/Dash/DashPool.cs +++ b/src/MiningCore/Blockchain/Dash/DashPool.cs @@ -30,7 +30,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Dash { - [CoinMetadata(CoinType.DASH)] + [CoinMetadata(CoinType.DASH, CoinType.GBX, CoinType.CRC)] public class DashPool : BitcoinPoolBase { public DashPool(IComponentContext ctx, diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs b/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs index 93f5a5d4d..b1fba4620 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs @@ -454,7 +454,7 @@ protected override void ConfigureDaemons() daemon.Configure(daemonEndpoints); } - protected override async Task AreDaemonsHealthy() + protected override async Task AreDaemonsHealthyAsync() { var responses = await daemon.ExecuteCmdAllAsync(EC.GetBlockByNumber, new[] { (object) "pending", true }); @@ -466,7 +466,7 @@ protected override async Task AreDaemonsHealthy() return responses.All(x => x.Error == null); } - protected override async Task AreDaemonsConnected() + protected override async Task AreDaemonsConnectedAsync() { var response = await daemon.ExecuteCmdAnyAsync(EC.GetPeerCount); diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 771ac68be..d69793894 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -233,9 +233,9 @@ public async Task ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public Task CalculateBlockEffortAsync(Block block, ulong accumulatedBlockShareDiff) + public Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) { - block.Effort = (double) accumulatedBlockShareDiff / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } @@ -256,7 +256,7 @@ public Task UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransa if (address != poolConfig.Address) { logger.Info(() => $"Adding {FormatAmount(amount)} to balance of {address}"); - balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount); + balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount, $"Reward for block {block.BlockHeight}"); } } diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs b/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs index cbde01973..f1a3b8f5a 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs @@ -198,8 +198,7 @@ private async Task OnSubmitAsync(StratumClient client, Timestamped $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning - if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true) - ConsiderBan(client, context, poolConfig.Banning); + ConsiderBan(client, context, poolConfig.Banning); } } @@ -230,7 +229,7 @@ private void OnNewJob(object jobParams) { var context = client.GetContextAs(); - if (context.IsSubscribed && context.IsAuthorized) + if (context.IsSubscribed && context.IsAuthorized && context.IsInitialWorkSent) { // check alive var lastActivityAgo = clock.Now - context.LastActivity; diff --git a/src/MiningCore/Blockchain/JobManagerBase.cs b/src/MiningCore/Blockchain/JobManagerBase.cs index fa683e422..6b9af018d 100644 --- a/src/MiningCore/Blockchain/JobManagerBase.cs +++ b/src/MiningCore/Blockchain/JobManagerBase.cs @@ -55,7 +55,7 @@ protected JobManagerBase(IComponentContext ctx) protected virtual async Task StartDaemonAsync() { - while(!await AreDaemonsHealthy()) + while(!await AreDaemonsHealthyAsync()) { logger.Info(() => $"[{LogCat}] Waiting for daemons to come online ..."); @@ -64,7 +64,7 @@ protected virtual async Task StartDaemonAsync() logger.Info(() => $"[{LogCat}] All daemons online"); - while(!await AreDaemonsConnected()) + while(!await AreDaemonsConnectedAsync()) { logger.Info(() => $"[{LogCat}] Waiting for daemons to connect to peers ..."); @@ -83,8 +83,8 @@ protected string NextJobId(string format = null) return value.ToStringHex8(); } - protected abstract Task AreDaemonsHealthy(); - protected abstract Task AreDaemonsConnected(); + protected abstract Task AreDaemonsHealthyAsync(); + protected abstract Task AreDaemonsConnectedAsync(); protected abstract Task EnsureDaemonsSynchedAsync(); protected abstract Task PostStartInitAsync(); diff --git a/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs b/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs index a3de79acd..8c3bcc98a 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs @@ -326,7 +326,7 @@ protected override void ConfigureDaemons() walletDaemon.Configure(walletDaemonEndpoints, MoneroConstants.DaemonRpcLocation); } - protected override async Task AreDaemonsHealthy() + protected override async Task AreDaemonsHealthyAsync() { // test daemons var responses = await daemon.ExecuteCmdAllAsync(MC.GetInfo); @@ -350,7 +350,7 @@ protected override async Task AreDaemonsHealthy() return responses2.All(x => x.Error == null); } - protected override async Task AreDaemonsConnected() + protected override async Task AreDaemonsConnectedAsync() { var response = await daemon.ExecuteCmdAnyAsync(MC.GetInfo); diff --git a/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs b/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs index 6ad8b2efc..6fdd55325 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroPayoutHandler.cs @@ -124,7 +124,7 @@ private async Task GetNetworkTypeAsync() { if (!networkType.HasValue) { - var infoResponse = await daemon.ExecuteCmdAnyAsync(MC.GetInfo); + var infoResponse = await daemon.ExecuteCmdAnyAsync(MC.GetInfo, true); var info = infoResponse.Response.ToObject(); networkType = info.IsTestnet ? MoneroNetworkType.Test : MoneroNetworkType.Main; @@ -163,21 +163,62 @@ private async Task PayoutBatch(Balance[] balances) var transferResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.Transfer, request); // gracefully handle error -4 (transaction would be too large. try /transfer_split) - if (transferResponse.Error?.Code == -4 && walletSupportsTransferSplit) + if (transferResponse.Error?.Code == -4) { - logger.Info(() => $"[{LogCategory}] Retrying transfer using {MWC.TransferSplit}"); + if (walletSupportsTransferSplit) + { + logger.Info(() => $"[{LogCategory}] Retrying transfer using {MWC.TransferSplit}"); - var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.TransferSplit, request); + var transferSplitResponse = await walletDaemon.ExecuteCmdSingleAsync(MWC.TransferSplit, request); - // 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) + { + HandleTransferResponse(transferSplitResponse, balances); + return; + } + } + + // 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); + + for (var i = 0; i < pageCount; i++) { - HandleTransferResponse(transferSplitResponse, balances); - return; + var page = validBalances + .Skip(i * pageSize) + .Take(pageSize) + .ToArray(); + + // update request + request.Destinations = page + .Where(x => x.Amount > 0) + .Select(x => + { + ExtractAddressAndPaymentId(x.Address, out var address, out var paymentId); + + return new TransferDestination + { + Address = address, + Amount = (ulong)Math.Floor(x.Amount * MoneroConstants.SmallestUnit[poolConfig.Coin.Type]) + }; + }).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); + + if (transferResponse.Error != null) + break; } } - HandleTransferResponse(transferResponse, balances); + else + HandleTransferResponse(transferResponse, balances); } private void ExtractAddressAndPaymentId(string input, out string address, out string paymentId) @@ -352,9 +393,9 @@ public async Task ClassifyBlocksAsync(Block[] blocks) return result.ToArray(); } - public Task CalculateBlockEffortAsync(Block block, ulong accumulatedBlockShareDiff) + public Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff) { - block.Effort = (double) accumulatedBlockShareDiff / block.NetworkDifficulty; + block.Effort = accumulatedBlockShareDiff / block.NetworkDifficulty; return Task.FromResult(true); } @@ -375,7 +416,7 @@ public Task UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransa if (address != poolConfig.Address) { logger.Info(() => $"Adding {FormatAmount(amount)} to balance of {address}"); - balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount); + balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount, $"Reward for block {block.BlockHeight}"); } } @@ -389,16 +430,16 @@ public async Task PayoutAsync(Balance[] balances) { Contract.RequiresNonNull(balances, nameof(balances)); +#if !DEBUG // ensure we have peers var infoResponse = await daemon.ExecuteCmdAnyAsync(MC.GetInfo); if (infoResponse.Error != null || infoResponse.Response == null || infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3) { -#if !DEBUG logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peers (4 required)"); return; -#endif } +#endif // validate addresses balances = balances diff --git a/src/MiningCore/Blockchain/Monero/MoneroPool.cs b/src/MiningCore/Blockchain/Monero/MoneroPool.cs index 85ba4b8f6..ca23a8503 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroPool.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroPool.cs @@ -81,16 +81,16 @@ private void OnLogin(StratumClient client, Timestamped tsRequest // extract worker/miner/paymentid var split = loginRequest.Login.Split('.'); - context.MinerName = split[0]; - context.WorkerName = split.Length > 1 ? split[1] : null; - context.UserAgent = loginRequest.UserAgent; + context.MinerName = split[0].Trim(); + context.WorkerName = split.Length > 1 ? split[1].Trim() : null; + context.UserAgent = loginRequest.UserAgent.Trim(); // extract paymentid var index = context.MinerName.IndexOf('#'); if (index != -1) { - context.PaymentId = context.MinerName.Substring(index + 1); - context.MinerName = context.MinerName.Substring(0, index); + context.PaymentId = context.MinerName.Substring(index + 1).Trim(); + context.MinerName = context.MinerName.Substring(0, index).Trim(); } // validate login @@ -105,6 +105,13 @@ private void OnLogin(StratumClient client, Timestamped tsRequest return; } + // validate payment Id + if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != MoneroConstants.PaymentIdHexLength) + { + client.RespondError(StratumError.MinusOne, "invalid payment id", request.Id); + return; + } + // respond var loginResponse = new MoneroLoginResponse { @@ -249,8 +256,7 @@ private async Task OnSubmitAsync(StratumClient client, Timestamped $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Message}"); // banning - if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true) - ConsiderBan(client, context, poolConfig.Banning); + ConsiderBan(client, context, poolConfig.Banning); } } diff --git a/src/MiningCore/Blockchain/ZCash/ZCashConstants.cs b/src/MiningCore/Blockchain/ZCash/ZCashConstants.cs index e8ac8fe22..fa89154a6 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashConstants.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashConstants.cs @@ -275,7 +275,8 @@ public class ZCashConstants { { CoinType.ZEC, ZCashCoinbaseTxConfig }, { CoinType.ZCL, ZCLCoinbaseTxConfig }, - { CoinType.ZEN, ZencashCoinbaseTxConfig } + { CoinType.ZEN, ZencashCoinbaseTxConfig }, + { CoinType.BTCP, ZCLCoinbaseTxConfig }, }; } diff --git a/src/MiningCore/Blockchain/ZCash/ZCashJob.cs b/src/MiningCore/Blockchain/ZCash/ZCashJob.cs index 889531fb4..2f39d4881 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashJob.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashJob.cs @@ -142,7 +142,7 @@ protected override void BuildCoinbase() public override void Init(ZCashBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, IDestination poolAddressDestination, BitcoinNetworkType networkType, - bool isPoS, double shareMultiplier, + bool isPoS, double shareMultiplier, decimal blockrewardMultiplier, IHashAlgorithm coinbaseHasher, IHashAlgorithm headerHasher, IHashAlgorithm blockHasher) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); diff --git a/src/MiningCore/Blockchain/ZCash/ZCashPayoutHandler.cs b/src/MiningCore/Blockchain/ZCash/ZCashPayoutHandler.cs index 40543b7a8..4b764a623 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashPayoutHandler.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashPayoutHandler.cs @@ -39,7 +39,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.ZCash { - [CoinMetadata(CoinType.ZEC, CoinType.ZCL, CoinType.ZEN)] + [CoinMetadata(CoinType.ZEC, CoinType.ZCL, CoinType.ZEN, CoinType.BTCP)] public class ZCashPayoutHandler : BitcoinPayoutHandler { public ZCashPayoutHandler( diff --git a/src/MiningCore/Blockchain/ZCash/ZCashPool.cs b/src/MiningCore/Blockchain/ZCash/ZCashPool.cs index 571078eb9..651ca2b07 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashPool.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashPool.cs @@ -32,7 +32,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.ZCash { - [CoinMetadata(CoinType.ZEC, CoinType.ZCL, CoinType.ZEN)] + [CoinMetadata(CoinType.ZEC, CoinType.ZCL, CoinType.ZEN, CoinType.BTCP)] public class ZCashPool : ZCashPoolBase { public ZCashPool(IComponentContext ctx, diff --git a/src/MiningCore/Configuration/ClusterConfig.cs b/src/MiningCore/Configuration/ClusterConfig.cs index 69c15ed49..428d357d1 100644 --- a/src/MiningCore/Configuration/ClusterConfig.cs +++ b/src/MiningCore/Configuration/ClusterConfig.cs @@ -56,6 +56,7 @@ public enum CoinType XVG, // Verge GBX, // GoByte CRC, // CrowdCoin + BTCP, // Bitcoin Private } public class CoinConfig diff --git a/src/MiningCore/Crypto/Hashing/Algorithms/Blake2s.cs b/src/MiningCore/Crypto/Hashing/Algorithms/Blake2s.cs new file mode 100644 index 000000000..4570a7ad3 --- /dev/null +++ b/src/MiningCore/Crypto/Hashing/Algorithms/Blake2s.cs @@ -0,0 +1,46 @@ +/* +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 MiningCore.Contracts; +using MiningCore.Native; + +namespace MiningCore.Crypto.Hashing.Algorithms +{ + public unsafe class Blake2s : IHashAlgorithm + { + public byte[] Digest(byte[] data, params object[] extra) + { + Contract.RequiresNonNull(data, nameof(data)); + + var result = new byte[32]; + + fixed(byte* input = data) + { + fixed(byte* output = result) + { + LibMultihash.blake2s(input, output, (uint) data.Length); + } + } + + return result; + } + } +} diff --git a/src/MiningCore/DaemonInterface/DaemonClient.cs b/src/MiningCore/DaemonInterface/DaemonClient.cs index 5f3a7b902..f3d20f144 100644 --- a/src/MiningCore/DaemonInterface/DaemonClient.cs +++ b/src/MiningCore/DaemonInterface/DaemonClient.cs @@ -142,22 +142,19 @@ public async Task[]> ExecuteCmdAllAsync(str /// /// Executes the request against all configured demons and returns the first successful response /// - /// /// - public Task> ExecuteCmdAnyAsync(string method) + public Task> ExecuteCmdAnyAsync(string method, bool throwOnError = false) { - return ExecuteCmdAnyAsync(method); + return ExecuteCmdAnyAsync(method, null, null, throwOnError); } /// /// Executes the request against all configured demons and returns the first successful response /// /// - /// - /// /// public async Task> ExecuteCmdAnyAsync(string method, object payload = null, - JsonSerializerSettings payloadJsonSerializerSettings = null) + JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false) where TResponse : class { Contract.Requires(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); @@ -167,7 +164,7 @@ public async Task> ExecuteCmdAnyAsync(strin var tasks = endPoints.Select(endPoint => BuildRequestTask(endPoint, method, payload, payloadJsonSerializerSettings)).ToArray(); var taskFirstCompleted = await Task.WhenAny(tasks); - var result = MapDaemonResponse(0, taskFirstCompleted); + var result = MapDaemonResponse(0, taskFirstCompleted, throwOnError); return result; } @@ -348,7 +345,7 @@ protected string GetRequestId() return rpcRequestId; } - private DaemonResponse MapDaemonResponse(int i, Task x) + private DaemonResponse MapDaemonResponse(int i, Task x, bool throwOnError = false) where TResponse : class { var resp = new DaemonResponse @@ -365,6 +362,9 @@ private DaemonResponse MapDaemonResponse(int i, Task $"[{LogCat}] [{client.ConnectionId}] Banning worker for {config.Time} sec: {Math.Floor(ratioBad * 100)}% of the last {totalShares} shares were invalid"); + if (poolConfig.Banning?.Enabled == true && + (clusterConfig.Banning?.BanOnInvalidShares.HasValue == false || + clusterConfig.Banning?.BanOnInvalidShares == true)) + { + logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Banning worker for {config.Time} sec: {Math.Floor(ratioBad * 100)}% of the last {totalShares} shares were invalid"); - banManager.Ban(client.RemoteEndpoint.Address, TimeSpan.FromSeconds(config.Time)); + banManager.Ban(client.RemoteEndpoint.Address, TimeSpan.FromSeconds(config.Time)); - DisconnectClient(client); + DisconnectClient(client); + } } } } diff --git a/src/MiningCore/Mining/StatsRecorder.cs b/src/MiningCore/Mining/StatsRecorder.cs index e7dc5a10b..b796b4a81 100644 --- a/src/MiningCore/Mining/StatsRecorder.cs +++ b/src/MiningCore/Mining/StatsRecorder.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net.Sockets; using System.Threading; -using System.Threading.Tasks; using Autofac; using AutoMapper; using MiningCore.Configuration; @@ -55,8 +54,9 @@ public StatsRecorder(IComponentContext ctx, private readonly Dictionary pools = new Dictionary(); private const int HashrateCalculationWindow = 1200; // seconds private const int MinHashrateCalculationWindow = 300; // seconds + private const double HashrateBoostFactor = 1.07d; private ClusterConfig clusterConfig; - private Thread thread; + private Thread thread1; private const int RetryCount = 4; private Policy readFaultPolicy; @@ -76,12 +76,12 @@ public void AttachPool(IMiningPool pool) public void Start() { - thread = new Thread(async () => - { - logger.Info(() => "Online"); + logger.Info(() => "Online"); + thread1 = new Thread(() => + { // warm-up delay - await Task.Delay(TimeSpan.FromSeconds(10)); + Thread.Sleep(TimeSpan.FromSeconds(10)); var interval = TimeSpan.FromMinutes(5); @@ -89,7 +89,7 @@ public void Start() { try { - await UpdatePoolsAsync(); + UpdatePoolHashrates(); } catch (Exception ex) @@ -105,8 +105,8 @@ public void Start() } }); - thread.Name = "StatsRecorder"; - thread.Start(); + thread1.Name = "StatsRecorder"; + thread1.Start(); } public void Stop() @@ -114,21 +114,14 @@ public void Stop() logger.Info(() => "Stopping .."); stopEvent.Set(); - thread.Join(); + thread1.Join(); logger.Info(() => "Stopped"); } #endregion // API-Surface - private Task UpdatePoolsAsync() - { - UpdateHashrates(); - - return Task.FromResult(true); - } - - private void UpdateHashrates() + private void UpdatePoolHashrates() { var start = clock.Now; var target = start.AddSeconds(-HashrateCalculationWindow); @@ -161,7 +154,7 @@ private void UpdateHashrates() { var poolHashesAccumulated = result.Sum(x => x.Sum); var poolHashesCountAccumulated = result.Sum(x => x.Count); - var poolHashrate = pool.HashrateFromShares(poolHashesAccumulated, windowActual); + var poolHashrate = pool.HashrateFromShares(poolHashesAccumulated, windowActual) * HashrateBoostFactor; // update pool.PoolStats.ConnectedMiners = byMiner.Length; @@ -202,7 +195,7 @@ private void UpdateHashrates() if (windowActual >= MinHashrateCalculationWindow) { - var hashrate = pool.HashrateFromShares(item.Sum, windowActual); + var hashrate = pool.HashrateFromShares(item.Sum, windowActual) * HashrateBoostFactor; // update stats.Hashrate = hashrate; diff --git a/src/MiningCore/Native/LibMultihash.cs b/src/MiningCore/Native/LibMultihash.cs index 2a44cf53c..68500c750 100644 --- a/src/MiningCore/Native/LibMultihash.cs +++ b/src/MiningCore/Native/LibMultihash.cs @@ -64,6 +64,9 @@ public static unsafe class LibMultihash [DllImport("libmultihash", EntryPoint = "blake_export", CallingConvention = CallingConvention.Cdecl)] public static extern int blake(byte* input, byte* output, uint inputLength); + [DllImport("libmultihash", EntryPoint = "blake2s_export", CallingConvention = CallingConvention.Cdecl)] + public static extern int blake2s(byte* input, byte* output, uint inputLength); + [DllImport("libmultihash", EntryPoint = "dcrypt_export", CallingConvention = CallingConvention.Cdecl)] public static extern int dcrypt(byte* input, byte* output, uint inputLength); diff --git a/src/MiningCore/Payments/Abstractions.cs b/src/MiningCore/Payments/Abstractions.cs index b89474131..eff19d325 100644 --- a/src/MiningCore/Payments/Abstractions.cs +++ b/src/MiningCore/Payments/Abstractions.cs @@ -30,7 +30,7 @@ public interface IPayoutHandler Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig); Task ClassifyBlocksAsync(Block[] blocks); - Task CalculateBlockEffortAsync(Block block, ulong accumulatedBlockShareDiff); + Task CalculateBlockEffortAsync(Block block, double accumulatedBlockShareDiff); Task UpdateBlockRewardBalancesAsync(IDbConnection con, IDbTransaction tx, Block block, PoolConfig pool); Task PayoutAsync(Balance[] balances); diff --git a/src/MiningCore/Payments/PayoutHandlerBase.cs b/src/MiningCore/Payments/PayoutHandlerBase.cs index 0fc2d0062..7aa791519 100644 --- a/src/MiningCore/Payments/PayoutHandlerBase.cs +++ b/src/MiningCore/Payments/PayoutHandlerBase.cs @@ -129,7 +129,7 @@ protected virtual void PersistPayments(Balance[] balances, string transactionCon // reset balance logger.Debug(() => $"[{LogCategory}] Resetting balance of {balance.Address}"); - balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, balance.Address, -balance.Amount); + balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, balance.Address, -balance.Amount, $"Balance reset after payment"); } }); }); diff --git a/src/MiningCore/Payments/PayoutSchemes/PPLNS.cs b/src/MiningCore/Payments/PayoutSchemes/PPLNS.cs index 99dc5c27d..085c08326 100644 --- a/src/MiningCore/Payments/PayoutSchemes/PPLNS.cs +++ b/src/MiningCore/Payments/PayoutSchemes/PPLNS.cs @@ -98,7 +98,7 @@ public Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block.BlockHeight}"); - balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount); + balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block.BlockHeight}"); } } diff --git a/src/MiningCore/Persistence/Model/BalanceChange.cs b/src/MiningCore/Persistence/Model/BalanceChange.cs new file mode 100644 index 000000000..39af85716 --- /dev/null +++ b/src/MiningCore/Persistence/Model/BalanceChange.cs @@ -0,0 +1,42 @@ +/* +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 MiningCore.Configuration; + +namespace MiningCore.Persistence.Model +{ + public class BalanceChange + { + public long Id { get; set; } + public string PoolId { get; set; } + public CoinType Coin { get; set; } + public string Address { get; set; } + + /// + /// Amount owed in pool-base-currency (ie. Bitcoin, not Satoshis) + /// + public decimal Amount { get; set; } + + public string Usage { get; set; } + + public DateTime Created { get; set; } + } +} diff --git a/src/MiningCore/Persistence/Model/MinerWorkerStatsPreAgg.cs b/src/MiningCore/Persistence/Model/MinerWorkerStatsPreAgg.cs new file mode 100644 index 000000000..a008039dd --- /dev/null +++ b/src/MiningCore/Persistence/Model/MinerWorkerStatsPreAgg.cs @@ -0,0 +1,37 @@ +/* +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; + +namespace MiningCore.Persistence.Model +{ + public class MinerWorkerStatsPreAgg + { + public string PoolId { get; set; } + public string Miner { get; set; } + public string Worker { get; set; } + + public long ShareCount { get; set; } + public double SharesAccumulated { get; set; } + + public DateTime Created { get; set; } + public DateTime Updated { get; set; } + } +} diff --git a/src/MiningCore/Persistence/Postgres/Entities/BalanceChange.cs b/src/MiningCore/Persistence/Postgres/Entities/BalanceChange.cs new file mode 100644 index 000000000..0c2222ac4 --- /dev/null +++ b/src/MiningCore/Persistence/Postgres/Entities/BalanceChange.cs @@ -0,0 +1,35 @@ +/* +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; + +namespace MiningCore.Persistence.Postgres.Entities +{ + public class BalanceChange + { + public long Id { get; set; } + public string PoolId { get; set; } + public string Coin { get; set; } + public string Address { get; set; } + public decimal Amount { get; set; } + public string Usage { get; set; } + public DateTime Created { get; set; } + } +} diff --git a/src/MiningCore/Persistence/Postgres/Repositories/BalanceRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/BalanceRepository.cs index 8d363f6c1..014d8d984 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/BalanceRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/BalanceRepository.cs @@ -42,17 +42,34 @@ public BalanceRepository(IMapper mapper) private readonly IMapper mapper; private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); - public void AddAmount(IDbConnection con, IDbTransaction tx, string poolId, CoinType coin, string address, decimal amount) + public void AddAmount(IDbConnection con, IDbTransaction tx, string poolId, CoinType coin, string address, decimal amount, string usage) { logger.LogInvoke(); - var query = "SELECT * FROM balances WHERE poolid = @poolId AND coin = @coin AND address = @address"; + var now = DateTime.UtcNow; + + // record balance change + var query = "INSERT INTO balances_changes(poolid, coin, address, amount, usage, created) " + + "VALUES(@poolid, @coin, @address, @amount, @usage, @created)"; + + var balanceChange = new Entities.BalanceChange + { + PoolId = poolId, + Coin = coin.ToString(), + Created = now, + Address = address, + Amount = amount, + Usage = usage, + }; + + con.Execute(query, balanceChange, tx); + + // update balance + query = "SELECT * FROM balances WHERE poolid = @poolId AND coin = @coin AND address = @address"; var balance = con.Query(query, new { poolId, coin = coin.ToString(), address }, tx) .FirstOrDefault(); - var now = DateTime.UtcNow; - if (balance == null) { balance = new Entities.Balance diff --git a/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs index 5ea3b4ff2..f5a3c9526 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/ShareRepository.cs @@ -127,7 +127,7 @@ public long CountSharesBetweenCreated(IDbConnection con, string poolId, string m return con.QuerySingle(query, new { poolId, miner, start, end }); } - public ulong? GetAccumulatedShareDifficultyBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end) + public double? GetAccumulatedShareDifficultyBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end) { logger.LogInvoke(new[] { poolId }); @@ -136,6 +136,16 @@ public long CountSharesBetweenCreated(IDbConnection con, string poolId, string m return con.QuerySingle(query, new { poolId, start, end }); } + public MinerWorkerHashes[] GetAccumulatedShareDifficultyTotal(IDbConnection con, string poolId) + { + logger.LogInvoke(new[] { (object)poolId }); + + var query = "SELECT SUM(difficulty) AS sum, COUNT(difficulty) AS count, miner, worker FROM shares WHERE poolid = @poolid group by miner, worker"; + + return con.Query(query, new { poolId }) + .ToArray(); + } + public MinerWorkerHashes[] GetHashAccumulationBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end) { logger.LogInvoke(new[] { poolId }); diff --git a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs index c7de52c9d..ffb5bb77e 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs @@ -19,6 +19,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using AutoMapper; @@ -27,6 +28,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using MiningCore.Persistence.Model; using MiningCore.Persistence.Model.Projections; using MiningCore.Persistence.Repositories; +using MiningCore.Time; using NLog; using MinerStats = MiningCore.Persistence.Model.Projections.MinerStats; @@ -34,13 +36,16 @@ namespace MiningCore.Persistence.Postgres.Repositories { public class StatsRepository : IStatsRepository { - public StatsRepository(IMapper mapper) + public StatsRepository(IMapper mapper, IMasterClock clock) { this.mapper = mapper; + this.clock = clock; } private readonly IMapper mapper; + private readonly IMasterClock clock; private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + private static readonly TimeSpan MinerStatsMaxAge = TimeSpan.FromMinutes(15); public void InsertPoolStats(IDbConnection con, IDbTransaction tx, PoolStats stats) { @@ -115,10 +120,15 @@ public MinerStats GetMinerStats(IDbConnection con, IDbTransaction tx, string poo { logger.LogInvoke(new[] { poolId, miner }); +#if true var query = "SELECT (SELECT SUM(difficulty) FROM shares WHERE poolid = @poolId AND miner = @miner) AS pendingshares, " + - "(SELECT amount FROM balances WHERE poolid = @poolId AND address = @miner) AS pendingbalance, " + - "(SELECT SUM(amount) FROM payments WHERE poolid = @poolId and address = @miner) as totalpaid"; - + "(SELECT amount FROM balances WHERE poolid = @poolId AND address = @miner) AS pendingbalance, " + + "(SELECT SUM(amount) FROM payments WHERE poolid = @poolId and address = @miner) as totalpaid"; +#else + var query = "SELECT (SELECT SUM(sharesaccumulated) FROM minerstats_pre_agg WHERE poolid = @poolId AND miner = @miner) AS pendingshares, " + + "(SELECT amount FROM balances WHERE poolid = @poolId AND address = @miner) AS pendingbalance, " + + "(SELECT SUM(amount) FROM payments WHERE poolid = @poolId and address = @miner) as totalpaid"; +#endif var result = con.QuerySingleOrDefault(query, new { poolId, miner }, tx); if (result != null) @@ -134,6 +144,10 @@ public MinerStats GetMinerStats(IDbConnection con, IDbTransaction tx, string poo var lastUpdate = con.QuerySingleOrDefault(query, new { poolId, miner }, tx); + // ignore stale minerstats + if (lastUpdate.HasValue && (clock.Now - lastUpdate) > MinerStatsMaxAge) + lastUpdate = null; + if (lastUpdate.HasValue) { // load rows rows by timestamp @@ -194,7 +208,7 @@ public WorkerPerformanceStatsContainer[] GetMinerPerformanceBetweenHourly(IDbCon var entitiesByDate = entities .GroupBy(x=> x.Created); - var result = entitiesByDate.Select(x => new WorkerPerformanceStatsContainer + var tmp = entitiesByDate.Select(x => new WorkerPerformanceStatsContainer { Created = x.Key, Workers = x.ToDictionary(y => y.Worker ?? string.Empty, y => new WorkerPerformanceStats @@ -206,7 +220,29 @@ public WorkerPerformanceStatsContainer[] GetMinerPerformanceBetweenHourly(IDbCon .OrderBy(x=> x.Created) .ToArray(); - return result; + // fill in blanks + //var result = new List(); + //var lastCreated = start; + //var maxItemCount = 24; + + //foreach (var item in tmp) + //{ + // while (result.Count < maxItemCount && + // (item.Created - lastCreated > TimeSpan.FromHours(1))) + // { + // result.Add(new WorkerPerformanceStatsContainer { Created = lastCreated }); + // lastCreated = lastCreated.AddHours(1); + // } + + // if (result.Count >= maxItemCount) + // break; + + // result.Add(item); + // lastCreated = item.Created; + //} + + //return result.ToArray(); + return tmp; } public WorkerPerformanceStatsContainer[] GetMinerPerformanceBetweenDaily(IDbConnection con, string poolId, string miner, DateTime start, DateTime end) @@ -223,7 +259,7 @@ public WorkerPerformanceStatsContainer[] GetMinerPerformanceBetweenDaily(IDbConn .ToArray() .GroupBy(x => x.Created); - var result = entitiesByDate.Select(x => new WorkerPerformanceStatsContainer + var tmp = entitiesByDate.Select(x => new WorkerPerformanceStatsContainer { Created = x.Key, Workers = x.ToDictionary(y => y.Worker, y => new WorkerPerformanceStats @@ -235,7 +271,30 @@ public WorkerPerformanceStatsContainer[] GetMinerPerformanceBetweenDaily(IDbConn .OrderBy(x => x.Created) .ToArray(); - return result; + //// fill in blanks + //var result = new List(); + //var lastCreated = start; + //var maxItemCount = 31; + + //foreach (var item in tmp) + //{ + // while (result.Count < maxItemCount && + // (item.Created - lastCreated > TimeSpan.FromDays(1))) + // { + // result.Add(new WorkerPerformanceStatsContainer { Created = lastCreated }); + // lastCreated = lastCreated.AddDays(1); + // } + + // if (result.Count >= maxItemCount) + // break; + + // result.Add(item); + // lastCreated = item.Created; + //} + + //return result.ToArray(); + + return tmp; } public MinerWorkerPerformanceStats[] PagePoolMinersByHashrate(IDbConnection con, string poolId, DateTime from, int page, int pageSize) diff --git a/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql b/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql index 9655093dd..25debffd5 100644 --- a/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql +++ b/src/MiningCore/Persistence/Postgres/Scripts/createdb.sql @@ -15,10 +15,9 @@ CREATE TABLE shares created TIMESTAMP NOT NULL ); -CREATE INDEX IDX_SHARES_POOL_BLOCK on shares(poolid, blockheight); CREATE INDEX IDX_SHARES_POOL_MINER on shares(poolid, miner); CREATE INDEX IDX_SHARES_POOL_CREATED ON shares(poolid, created); -CREATE INDEX IDX_SHARES_POOL_MINER_DIFF on shares(poolid, miner, difficulty); +CREATE INDEX IDX_SHARES_POOL_MINER_DIFFICULTY on shares(poolid, miner, difficulty); CREATE TABLE blocks ( @@ -50,6 +49,17 @@ CREATE TABLE balances primary key(poolid, address, coin) ); +CREATE TABLE balance_changes +( + id BIGSERIAL NOT NULL PRIMARY KEY, + poolid TEXT NOT NULL, + coin TEXT NOT NULL, + address TEXT NOT NULL, + amount decimal(28,12) NOT NULL DEFAULT 0, + usage TEXT NULL, + created TIMESTAMP NOT NULL +); + CREATE TABLE payments ( id BIGSERIAL NOT NULL PRIMARY KEY, @@ -95,3 +105,18 @@ CREATE INDEX IDX_MINERSTATS_POOL_CREATED on minerstats(poolid, created); CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED on minerstats(poolid, miner, created); CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED_HOUR on minerstats(poolid, miner, date_trunc('hour',created)); CREATE INDEX IDX_MINERSTATS_POOL_MINER_CREATED_DAY on minerstats(poolid, miner, date_trunc('day',created)); + +CREATE TABLE minerstats_pre_agg +( + poolid TEXT NOT NULL, + miner TEXT NOT NULL, + worker TEXT NOT NULL, + + sharecount BIGINT NOT NULL, + sharesaccumulated DOUBLE PRECISION NOT NULL, + + created TIMESTAMP NOT NULL, + updated TIMESTAMP NOT NULL, + + primary key(poolid, miner, worker) +); diff --git a/src/MiningCore/Persistence/Repositories/IBalanceRepository.cs b/src/MiningCore/Persistence/Repositories/IBalanceRepository.cs index d04ed8d25..d5091b1ec 100644 --- a/src/MiningCore/Persistence/Repositories/IBalanceRepository.cs +++ b/src/MiningCore/Persistence/Repositories/IBalanceRepository.cs @@ -26,8 +26,7 @@ namespace MiningCore.Persistence.Repositories { public interface IBalanceRepository { - void AddAmount(IDbConnection con, IDbTransaction tx, string poolId, CoinType coin, string address, - decimal amount); + void AddAmount(IDbConnection con, IDbTransaction tx, string poolId, CoinType coin, string address, decimal amount, string usage); Balance[] GetPoolBalancesOverThreshold(IDbConnection con, string poolId, decimal minimum); } diff --git a/src/MiningCore/Persistence/Repositories/IShareRepository.cs b/src/MiningCore/Persistence/Repositories/IShareRepository.cs index 3178a6bc8..3c48fb26e 100644 --- a/src/MiningCore/Persistence/Repositories/IShareRepository.cs +++ b/src/MiningCore/Persistence/Repositories/IShareRepository.cs @@ -36,7 +36,8 @@ public interface IShareRepository void DeleteSharesBeforeCreated(IDbConnection con, IDbTransaction tx, string poolId, DateTime before); long CountSharesBetweenCreated(IDbConnection con, string poolId, string miner, DateTime? start, DateTime? end); - ulong? GetAccumulatedShareDifficultyBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end); + double? GetAccumulatedShareDifficultyBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end); + MinerWorkerHashes[] GetAccumulatedShareDifficultyTotal(IDbConnection con, string poolId); MinerWorkerHashes[] GetHashAccumulationBetweenCreated(IDbConnection con, string poolId, DateTime start, DateTime end); } } diff --git a/src/MiningCore/Program.cs b/src/MiningCore/Program.cs index eb438a599..4a40bba4e 100644 --- a/src/MiningCore/Program.cs +++ b/src/MiningCore/Program.cs @@ -39,6 +39,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using Microsoft.Extensions.CommandLineUtils; using MiningCore.Api; using MiningCore.Api.Responses; +using MiningCore.Blockchain; using MiningCore.Configuration; using MiningCore.Crypto.Hashing.Algorithms; using MiningCore.Crypto.Hashing.Equihash; @@ -49,6 +50,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using MiningCore.Persistence.Postgres; using MiningCore.Persistence.Postgres.Repositories; using MiningCore.Util; +using NBitcoin; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using NLog; @@ -101,6 +103,7 @@ public static void Main(string[] args) ValidateConfig(); Bootstrap(); + LogRuntimeInfo(); if (!shareRecoveryOption.HasValue()) { @@ -151,6 +154,11 @@ public static void Main(string[] args) } } + private static void LogRuntimeInfo() + { + logger.Info(() => $"Running on {RuntimeInformation.FrameworkDescription} under {RuntimeInformation.OSDescription} [{RuntimeInformation.OSArchitecture} - {RuntimeInformation.ProcessArchitecture}]"); + } + private static void ValidateConfig() { try @@ -360,10 +368,14 @@ private static void Logo() "); Console.WriteLine($" https://github.com/coinfoundry/miningcore\n"); Console.WriteLine($" Please contribute to the development of the project by donating:\n"); - Console.WriteLine($" BTC - 17QnVor1B6oK1rWnVVBrdX9gFzVkZZbhDm"); - Console.WriteLine($" ETH - 0xcb55abBfe361B12323eb952110cE33d5F28BeeE1"); - Console.WriteLine($" LTC - LTK6CWastkmBzGxgQhTTtCUjkjDA14kxzC"); - Console.WriteLine($" XMR - 475YVJbPHPedudkhrcNp1wDcLMTGYusGPF5fqE7XjnragVLPdqbCHBdZg3dF4dN9hXMjjvGbykS6a77dTAQvGrpiQqHp2eH"); + Console.WriteLine($" BTC - 17QnVor1B6oK1rWnVVBrdX9gFzVkZZbhDm"); + Console.WriteLine($" LTC - LTK6CWastkmBzGxgQhTTtCUjkjDA14kxzC"); + Console.WriteLine($" DASH - XqpBAV9QCaoLnz42uF5frSSfrJTrqHoxjp"); + Console.WriteLine($" ZEC - t1YHZHz2DGVMJiggD2P4fBQ2TAPgtLSUwZ7"); + Console.WriteLine($" ZCL - t1MFU1vD3YKgsK6Uh8hW7UTY8mKAV2xVqBr"); + Console.WriteLine($" ETH - 0xcb55abBfe361B12323eb952110cE33d5F28BeeE1"); + Console.WriteLine($" ETC - 0xF8cCE9CE143C68d3d4A7e6bf47006f21Cfcf93c0"); + Console.WriteLine($" XMR - 475YVJbPHPedudkhrcNp1wDcLMTGYusGPF5fqE7XjnragVLPdqbCHBdZg3dF4dN9hXMjjvGbykS6a77dTAQvGrpiQqHp2eH"); Console.WriteLine(); } @@ -505,7 +517,7 @@ private static void ConfigurePostgres(DatabaseConfig pgConfig, ContainerBuilder logger.ThrowLogPoolStartupException("Postgres configuration: invalid or missing 'user'"); // build connection string - var connectionString = $"Server={pgConfig.Host};Port={pgConfig.Port};Database={pgConfig.Database};User Id={pgConfig.User};Password={pgConfig.Password};CommandTimeout=300;"; + var connectionString = $"Server={pgConfig.Host};Port={pgConfig.Port};Database={pgConfig.Database};User Id={pgConfig.User};Password={pgConfig.Password};CommandTimeout=900;"; // register connection factory builder.RegisterInstance(new ConnectionFactory(connectionString)) diff --git a/src/MiningCore/VarDiff/VarDiffManager.cs b/src/MiningCore/VarDiff/VarDiffManager.cs index 5c27ace89..869d7d24a 100644 --- a/src/MiningCore/VarDiff/VarDiffManager.cs +++ b/src/MiningCore/VarDiff/VarDiffManager.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. */ @@ -64,7 +64,7 @@ public VarDiffManager(VarDiffConfig varDiffOptions, IMasterClock clock) } var minDiff = options.MinDiff; - var maxDiff = options.MaxDiff ?? Math.Max(minDiff, double.MaxValue); // for regtest + var maxDiff = options.MaxDiff ?? Math.Max(minDiff, double.MaxValue); // for regtest var sinceLast = ts - ctx.LastTs.Value; // Always calculate the time until now even there is no share submitted. @@ -85,6 +85,22 @@ public VarDiffManager(VarDiffConfig varDiffOptions, IMasterClock clock) // Possible New Diff var newDiff = difficulty * options.TargetTime / avg; + + // Max delta + if (options.MaxDelta.HasValue && options.MaxDelta > 0) + { + var delta = Math.Abs(newDiff - difficulty); + + if (delta > options.MaxDelta) + { + if (newDiff > difficulty) + newDiff -= delta - options.MaxDelta.Value; + else if (newDiff < difficulty) + newDiff += delta - options.MaxDelta.Value; + } + } + + // Clamp to valid range if (newDiff < minDiff) newDiff = minDiff; if (newDiff > maxDiff) @@ -98,7 +114,7 @@ public VarDiffManager(VarDiffConfig varDiffOptions, IMasterClock clock) // Due to change of diff, Buffer needs to be cleared ctx.TimeBuffer = new CircularDoubleBuffer(bufferSize); - + return newDiff; } } diff --git a/src/MiningCore/runtimes/win-x64/native/libmultihash.dll b/src/MiningCore/runtimes/win-x64/native/libmultihash.dll index ee0b76888..230fef5e9 100644 Binary files a/src/MiningCore/runtimes/win-x64/native/libmultihash.dll and b/src/MiningCore/runtimes/win-x64/native/libmultihash.dll differ diff --git a/src/MiningCore/runtimes/win-x86/native/libmultihash.dll b/src/MiningCore/runtimes/win-x86/native/libmultihash.dll index db45fc4d2..8cc42e2b8 100644 Binary files a/src/MiningCore/runtimes/win-x86/native/libmultihash.dll and b/src/MiningCore/runtimes/win-x86/native/libmultihash.dll differ diff --git a/src/Native/libmultihash/Makefile b/src/Native/libmultihash/Makefile index 8bd23a1bd..75e80e1b4 100644 --- a/src/Native/libmultihash/Makefile +++ b/src/Native/libmultihash/Makefile @@ -5,12 +5,12 @@ LDFLAGS = -shared LDLIBS = -lsodium TARGET = libmultihash.so -OBJECTS = bcrypt.o blake.o c11.o dcrypt.o fresh.o \ +OBJECTS = bcrypt.o blake.o blake2s.o c11.o dcrypt.o fresh.o \ fugue.o groestl.o hefty1.o jh.o keccak.o neoscrypt.o exports.o nist5.o quark.o qubit.o s3.o scryptn.o \ sha3/aes_helper.o sha3/hamsi.o sha3/hamsi_helper.o sha3/sph_blake.o sha3/sph_bmw.o sha3/sph_cubehash.o \ sha3/sph_echo.o sha3/sph_fugue.o sha3/sph_groestl.o sha3/sph_hefty1.o sha3/sph_jh.o sha3/sph_keccak.o \ sha3/sph_luffa.o sha3/sph_shabal.o sha3/sph_shavite.o sha3/sph_simd.o sha3/sph_skein.o sha3/sph_whirlpool.o \ - sha3/sph_haval.o sha3/sph_sha2.o sha3/sph_sha2big.o \ + sha3/sph_haval.o sha3/sph_sha2.o sha3/sph_sha2big.o sha3/sph_blake2s.o \ shavite3.o skein.o x11.o x15.o x17.o \ Lyra2.o Lyra2RE.o Sponge.o \ equi/endian.o equi/equi.o \ diff --git a/src/Native/libmultihash/blake2s.c b/src/Native/libmultihash/blake2s.c new file mode 100644 index 000000000..466540a0e --- /dev/null +++ b/src/Native/libmultihash/blake2s.c @@ -0,0 +1,15 @@ +#include "blake2s.h" +#include +#include +#include +#include + +#include "sha3/sph_blake2s.h" + +void blake2s_hash(const char* input, char* output, int inlen) +{ + blake2s_state ctx_blake2s; + blake2s_init(&ctx_blake2s, BLAKE2S_OUTBYTES); + blake2s_update(&ctx_blake2s, input, inlen); + blake2s_final(&ctx_blake2s, output, BLAKE2S_OUTBYTES); +} diff --git a/src/Native/libmultihash/blake2s.h b/src/Native/libmultihash/blake2s.h new file mode 100644 index 000000000..4e13893c1 --- /dev/null +++ b/src/Native/libmultihash/blake2s.h @@ -0,0 +1,16 @@ +#ifndef BLAKE2S_H +#define BLAKE2S_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void blake2s_hash(const char* input, char* output, int inlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Native/libmultihash/exports.cpp b/src/Native/libmultihash/exports.cpp index 4c2be0c56..d963d7501 100644 --- a/src/Native/libmultihash/exports.cpp +++ b/src/Native/libmultihash/exports.cpp @@ -25,6 +25,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "x11.h" #include "groestl.h" #include "blake.h" +#include "blake2s.h" #include "fugue.h" #include "qubit.h" #include "s3.h" @@ -121,6 +122,11 @@ extern "C" MODULE_API void blake_export(const char* input, char* output, uint32_ blake_hash(input, output, input_len); } +extern "C" MODULE_API void blake2s_export(const char* input, char* output, uint32_t input_len) +{ + blake2s_hash(input, output, input_len); +} + extern "C" MODULE_API void dcrypt_export(const char* input, char* output, uint32_t input_len) { dcrypt_hash(input, output, input_len); diff --git a/src/Native/libmultihash/libmultihash.vcxproj b/src/Native/libmultihash/libmultihash.vcxproj index a12686db6..029f7196a 100644 --- a/src/Native/libmultihash/libmultihash.vcxproj +++ b/src/Native/libmultihash/libmultihash.vcxproj @@ -164,6 +164,7 @@ + @@ -196,6 +197,7 @@ + @@ -229,6 +231,7 @@ + @@ -258,6 +261,7 @@ + diff --git a/src/Native/libmultihash/libmultihash.vcxproj.filters b/src/Native/libmultihash/libmultihash.vcxproj.filters index 96053bcf3..190142f2f 100644 --- a/src/Native/libmultihash/libmultihash.vcxproj.filters +++ b/src/Native/libmultihash/libmultihash.vcxproj.filters @@ -200,6 +200,12 @@ Header Files + + Header Files + + + Header Files + @@ -373,6 +379,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/Native/libmultihash/sha3/sph_blake2s.c b/src/Native/libmultihash/sha3/sph_blake2s.c new file mode 100644 index 000000000..cbb9d890c --- /dev/null +++ b/src/Native/libmultihash/sha3/sph_blake2s.c @@ -0,0 +1,387 @@ +/** + * BLAKE2 reference source code package - reference C implementations + * + * Written in 2012 by Samuel Neves + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along with + * this software. If not, see . + */ + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "sph_types.h" +#include "sph_blake2s.h" + +static const uint32_t blake2s_IV[8] = +{ + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const uint8_t blake2s_sigma[10][16] = +{ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , +}; + +static inline int blake2s_set_lastnode( blake2s_state *S ) +{ + S->f[1] = ~0U; + return 0; +} + +static inline int blake2s_clear_lastnode( blake2s_state *S ) +{ + S->f[1] = 0U; + return 0; +} + +/* Some helper functions, not necessarily useful */ +static inline int blake2s_set_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_set_lastnode( S ); + + S->f[0] = ~0U; + return 0; +} + +static inline int blake2s_clear_lastblock( blake2s_state *S ) +{ + if( S->last_node ) blake2s_clear_lastnode( S ); + + S->f[0] = 0U; + return 0; +} + +static inline int blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) +{ + S->t[0] += inc; + S->t[1] += ( S->t[0] < inc ); + return 0; +} + +// Parameter-related functions +static inline int blake2s_param_set_digest_length( blake2s_param *P, const uint8_t digest_length ) +{ + P->digest_length = digest_length; + return 0; +} + +static inline int blake2s_param_set_fanout( blake2s_param *P, const uint8_t fanout ) +{ + P->fanout = fanout; + return 0; +} + +static inline int blake2s_param_set_max_depth( blake2s_param *P, const uint8_t depth ) +{ + P->depth = depth; + return 0; +} + +static inline int blake2s_param_set_leaf_length( blake2s_param *P, const uint32_t leaf_length ) +{ + store32( &P->leaf_length, leaf_length ); + return 0; +} + +static inline int blake2s_param_set_node_offset( blake2s_param *P, const uint64_t node_offset ) +{ + store48( P->node_offset, node_offset ); + return 0; +} + +static inline int blake2s_param_set_node_depth( blake2s_param *P, const uint8_t node_depth ) +{ + P->node_depth = node_depth; + return 0; +} + +static inline int blake2s_param_set_inner_length( blake2s_param *P, const uint8_t inner_length ) +{ + P->inner_length = inner_length; + return 0; +} + +static inline int blake2s_param_set_salt( blake2s_param *P, const uint8_t salt[BLAKE2S_SALTBYTES] ) +{ + memcpy( P->salt, salt, BLAKE2S_SALTBYTES ); + return 0; +} + +static inline int blake2s_param_set_personal( blake2s_param *P, const uint8_t personal[BLAKE2S_PERSONALBYTES] ) +{ + memcpy( P->personal, personal, BLAKE2S_PERSONALBYTES ); + return 0; +} + +static inline int blake2s_init0( blake2s_state *S ) +{ + memset( S, 0, sizeof( blake2s_state ) ); + + for( int i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i]; + + return 0; +} + +/* init2 xors IV with input parameter block */ +int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) +{ + blake2s_init0( S ); + uint32_t *p = ( uint32_t * )( P ); + + /* IV XOR ParamBlock */ + for( size_t i = 0; i < 8; ++i ) + S->h[i] ^= load32( &p[i] ); + + return 0; +} + + +// Sequential blake2s initialization +int blake2s_init( blake2s_state *S, const uint8_t outlen ) +{ + blake2s_param P[1]; + + /* Move interval verification here? */ + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + P->digest_length = outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store48( &P->node_offset, 0 ); + P->node_depth = 0; + P->inner_length = 0; + // memset(P->reserved, 0, sizeof(P->reserved) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + return blake2s_init_param( S, P ); +} + +int blake2s_init_key( blake2s_state *S, const uint8_t outlen, const void *key, const uint8_t keylen ) +{ + blake2s_param P[1]; + + if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; + + if ( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; + + P->digest_length = outlen; + P->key_length = keylen; + P->fanout = 1; + P->depth = 1; + store32( &P->leaf_length, 0 ); + store48( &P->node_offset, 0 ); + P->node_depth = 0; + P->inner_length = 0; + // memset(P->reserved, 0, sizeof(P->reserved) ); + memset( P->salt, 0, sizeof( P->salt ) ); + memset( P->personal, 0, sizeof( P->personal ) ); + + if( blake2s_init_param( S, P ) < 0 ) return -1; + + { + uint8_t block[BLAKE2S_BLOCKBYTES]; + memset( block, 0, BLAKE2S_BLOCKBYTES ); + memcpy( block, key, keylen ); + blake2s_update( S, block, BLAKE2S_BLOCKBYTES ); + secure_zero_memory( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ + } + return 0; +} + +int blake2s_compress( blake2s_state *S, const uint8_t block[BLAKE2S_BLOCKBYTES] ) +{ + uint32_t m[16]; + uint32_t v[16]; + + for( size_t i = 0; i < 16; ++i ) + m[i] = load32( block + i * sizeof( m[i] ) ); + + for( size_t i = 0; i < 8; ++i ) + v[i] = S->h[i]; + + v[ 8] = blake2s_IV[0]; + v[ 9] = blake2s_IV[1]; + v[10] = blake2s_IV[2]; + v[11] = blake2s_IV[3]; + v[12] = S->t[0] ^ blake2s_IV[4]; + v[13] = S->t[1] ^ blake2s_IV[5]; + v[14] = S->f[0] ^ blake2s_IV[6]; + v[15] = S->f[1] ^ blake2s_IV[7]; +#define G(r,i,a,b,c,d) \ + do { \ + a = a + b + m[blake2s_sigma[r][2*i+0]]; \ + d = SPH_ROTR32(d ^ a, 16); \ + c = c + d; \ + b = SPH_ROTR32(b ^ c, 12); \ + a = a + b + m[blake2s_sigma[r][2*i+1]]; \ + d = SPH_ROTR32(d ^ a, 8); \ + c = c + d; \ + b = SPH_ROTR32(b ^ c, 7); \ + } while(0) +#define ROUND(r) \ + do { \ + G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ + G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ + G(r,2,v[ 2],v[ 6],v[10],v[14]); \ + G(r,3,v[ 3],v[ 7],v[11],v[15]); \ + G(r,4,v[ 0],v[ 5],v[10],v[15]); \ + G(r,5,v[ 1],v[ 6],v[11],v[12]); \ + G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ + G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ + } while(0) + ROUND( 0 ); + ROUND( 1 ); + ROUND( 2 ); + ROUND( 3 ); + ROUND( 4 ); + ROUND( 5 ); + ROUND( 6 ); + ROUND( 7 ); + ROUND( 8 ); + ROUND( 9 ); + + for( size_t i = 0; i < 8; ++i ) + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + +#undef G +#undef ROUND + return 0; +} + + +int blake2s_update( blake2s_state *S, const uint8_t *in, uint64_t inlen ) +{ + while( inlen > 0 ) + { + size_t left = S->buflen; + size_t fill = 2 * BLAKE2S_BLOCKBYTES - left; + + if( inlen > fill ) + { + memcpy( S->buf + left, in, fill ); // Fill buffer + S->buflen += fill; + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); // Compress + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left + S->buflen -= BLAKE2S_BLOCKBYTES; + in += fill; + inlen -= fill; + } + else // inlen <= fill + { + memcpy(S->buf + left, in, (size_t) inlen); + S->buflen += (size_t) inlen; // Be lazy, do not compress + in += inlen; + inlen -= inlen; + } + } + + return 0; +} + +int blake2s_final( blake2s_state *S, uint8_t *out, uint8_t outlen ) +{ + uint8_t buffer[BLAKE2S_OUTBYTES]; + + if( S->buflen > BLAKE2S_BLOCKBYTES ) + { + blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); + blake2s_compress( S, S->buf ); + S->buflen -= BLAKE2S_BLOCKBYTES; + memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen ); + } + + blake2s_increment_counter( S, ( uint32_t )S->buflen ); + blake2s_set_lastblock( S ); + memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ + blake2s_compress( S, S->buf ); + + for( int i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ + store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); + + memcpy( out, buffer, outlen ); + return 0; +} + +int blake2s( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen ) +{ + blake2s_state S[1]; + + /* Verify parameters */ + if ( NULL == in ) return -1; + + if ( NULL == out ) return -1; + + if ( NULL == key ) keylen = 0; /* Fail here instead if keylen != 0 and key == NULL? */ + + if( keylen > 0 ) + { + if( blake2s_init_key( S, outlen, key, keylen ) < 0 ) return -1; + } + else + { + if( blake2s_init( S, outlen ) < 0 ) return -1; + } + + blake2s_update( S, ( uint8_t * )in, inlen ); + blake2s_final( S, out, outlen ); + return 0; +} + +#if defined(__cplusplus) +} +#endif + + +#if defined(BLAKE2S_SELFTEST) +#include +#include "blake2-kat.h" /* test data not included */ +int main( int argc, char **argv ) +{ + uint8_t key[BLAKE2S_KEYBYTES]; + uint8_t buf[KAT_LENGTH]; + + for( size_t i = 0; i < BLAKE2S_KEYBYTES; ++i ) + key[i] = ( uint8_t )i; + + for( size_t i = 0; i < KAT_LENGTH; ++i ) + buf[i] = ( uint8_t )i; + + for( size_t i = 0; i < KAT_LENGTH; ++i ) + { + uint8_t hash[BLAKE2S_OUTBYTES]; + blake2s( hash, buf, key, BLAKE2S_OUTBYTES, i, BLAKE2S_KEYBYTES ); + + if( 0 != memcmp( hash, blake2s_keyed_kat[i], BLAKE2S_OUTBYTES ) ) + { + puts( "error" ); + return -1; + } + } + + puts( "ok" ); + return 0; +} +#endif diff --git a/src/Native/libmultihash/sha3/sph_blake2s.h b/src/Native/libmultihash/sha3/sph_blake2s.h new file mode 100644 index 000000000..64aa25b54 --- /dev/null +++ b/src/Native/libmultihash/sha3/sph_blake2s.h @@ -0,0 +1,150 @@ +/** + * BLAKE2 reference source code package - reference C implementations + * + * Written in 2012 by Samuel Neves + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along with + * this software. If not, see . + */ +#pragma once +#ifndef __BLAKE2_H__ +#define __BLAKE2_H__ + +#include +#include + +#if defined(_MSC_VER) +#include +#define inline __inline +#define ALIGN(x) __declspec(align(x)) +#else +#define ALIGN(x) __attribute__((aligned(x))) +#endif + +/* blake2-impl.h */ + +static inline uint32_t load32(const void *src) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + return *(uint32_t *)(src); +#else + const uint8_t *p = (uint8_t *)src; + uint32_t w = *p++; + w |= (uint32_t)(*p++) << 8; + w |= (uint32_t)(*p++) << 16; + w |= (uint32_t)(*p++) << 24; + return w; +#endif +} + +static inline void store32(void *dst, uint32_t w) +{ +#if defined(NATIVE_LITTLE_ENDIAN) + *(uint32_t *)(dst) = w; +#else + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +static inline uint64_t load48(const void *src) +{ + const uint8_t *p = (const uint8_t *)src; + uint64_t w = *p++; + w |= (uint64_t)(*p++) << 8; + w |= (uint64_t)(*p++) << 16; + w |= (uint64_t)(*p++) << 24; + w |= (uint64_t)(*p++) << 32; + w |= (uint64_t)(*p++) << 40; + return w; +} + +static inline void store48(void *dst, uint64_t w) +{ + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; w >>= 8; + *p++ = (uint8_t)w; +} + +/* prevents compiler optimizing out memset() */ +static inline void secure_zero_memory(void *v, size_t n) +{ + volatile uint8_t *p = ( volatile uint8_t * )v; + + while( n-- ) *p++ = 0; +} + +/* blake2.h */ + +enum blake2s_constant +{ + BLAKE2S_BLOCKBYTES = 64, + BLAKE2S_OUTBYTES = 32, + BLAKE2S_KEYBYTES = 32, + BLAKE2S_SALTBYTES = 8, + BLAKE2S_PERSONALBYTES = 8 +}; + +#pragma pack(push, 1) +typedef struct __blake2s_param +{ + uint8_t digest_length; // 1 + uint8_t key_length; // 2 + uint8_t fanout; // 3 + uint8_t depth; // 4 + uint32_t leaf_length; // 8 + uint8_t node_offset[6];// 14 + uint8_t node_depth; // 15 + uint8_t inner_length; // 16 + // uint8_t reserved[0]; + uint8_t salt[BLAKE2S_SALTBYTES]; // 24 + uint8_t personal[BLAKE2S_PERSONALBYTES]; // 32 +} blake2s_param; + +ALIGN( 64 ) typedef struct __blake2s_state +{ + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[2 * BLAKE2S_BLOCKBYTES]; + size_t buflen; + uint8_t last_node; +} blake2s_state; +#pragma pack(pop) + +#if defined(__cplusplus) +extern "C" { +#endif + + int blake2s_compress( blake2s_state *S, const uint8_t block[BLAKE2S_BLOCKBYTES] ); + + // Streaming API + int blake2s_init( blake2s_state *S, const uint8_t outlen ); + int blake2s_init_key( blake2s_state *S, const uint8_t outlen, const void *key, const uint8_t keylen ); + int blake2s_init_param( blake2s_state *S, const blake2s_param *P ); + int blake2s_update( blake2s_state *S, const uint8_t *in, uint64_t inlen ); + int blake2s_final( blake2s_state *S, uint8_t *out, uint8_t outlen ); + + // Simple API + int blake2s( uint8_t *out, const void *in, const void *key, const uint8_t outlen, const uint64_t inlen, uint8_t keylen ); + + // Direct Hash Mining Helpers + #define blake2s_salt32(out, in, inlen, key32) blake2s(out, in, key32, 32, inlen, 32) /* neoscrypt */ + #define blake2s_simple(out, in, inlen) blake2s(out, in, NULL, 32, inlen, 0) + +#if defined(__cplusplus) +} +#endif + +#endif