diff --git a/build-ubuntu-20.04.sh b/build-ubuntu-20.04.sh new file mode 100644 index 000000000..168883c95 --- /dev/null +++ b/build-ubuntu-20.04.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# add dotnet repo +wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +rm packages-microsoft-prod.deb + +# install dev-dependencies +sudo apt-get -y install dotnet-sdk-6.0 git cmake build-essential libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5 + +(cd src/Miningcore && \ +BUILDIR=${1:-../../build} && \ +echo "Building into $BUILDIR" && \ +dotnet publish -c Release --framework net6.0 -o $BUILDIR) diff --git a/libs/runtimes/win-x64/librandomarq.dll b/libs/runtimes/win-x64/librandomarq.dll new file mode 100644 index 000000000..110750614 Binary files /dev/null and b/libs/runtimes/win-x64/librandomarq.dll differ diff --git a/src/Miningcore.Tests/Crypto/RandomARQTests.cs b/src/Miningcore.Tests/Crypto/RandomARQTests.cs new file mode 100644 index 000000000..ce082ec86 --- /dev/null +++ b/src/Miningcore.Tests/Crypto/RandomARQTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Text; +using Miningcore.Extensions; +using Miningcore.Native; +using Xunit; + +namespace Miningcore.Tests.Crypto; + +public class RandomARQTests : TestBase +{ + const string realm = "xmr"; + private static readonly string seedHex = Encoding.UTF8.GetBytes("test key 000").ToHexString(); + private static readonly byte[] input1 = Encoding.UTF8.GetBytes("This is a test"); + private static readonly byte[] input2 = Encoding.UTF8.GetBytes("Lorem ipsum dolor sit amet"); + private const string hashExpected1 = "27f66e4650eb5657513e76c140e09e59336786f21fbef1ed6ff40fc21538221e"; + private const string hashExpected2 = "6b04e883e07e4e6c072cb064d9aed0fa5a8f7cbbeb7fd3ba653d274ebd4925b0"; + + [Fact] + public void CreateAndDeleteSeed() + { + // creation + RandomARQ.CreateSeed(realm, seedHex); + Assert.True(RandomARQ.realms.ContainsKey(realm)); + Assert.True(RandomARQ.realms[realm].ContainsKey(seedHex)); + + // accessing the created seed should work + Assert.NotNull(RandomARQ.GetSeed(realm, seedHex)); + + // creating the same realm and key twice should not result in duplicates + RandomARQ.CreateSeed(realm, seedHex); + Assert.Equal(RandomARQ.realms.Count, 1); + Assert.Equal(RandomARQ.realms[realm].Count, 1); + + // deletion + RandomARQ.DeleteSeed(realm, seedHex); + Assert.False(RandomARQ.realms[realm].ContainsKey(seedHex)); + } + + [Fact] + public void CalculateHashSlow() + { + var buf = new byte[32]; + + // light-mode + RandomARQ.CreateSeed(realm, seedHex); + + RandomARQ.CalculateHash("xmr", seedHex, input1, buf); + var result = buf.ToHexString(); + Assert.Equal(hashExpected1, result); + + Array.Clear(buf, 0, buf.Length); + + // second invocation should give the same result + RandomARQ.CalculateHash("xmr", seedHex, input1, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected1, result); + + RandomARQ.CalculateHash("xmr", seedHex, input2, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected2, result); + + RandomARQ.DeleteSeed(realm, seedHex); + } + + [Fact] + public void CalculateHashFast() + { + var buf = new byte[32]; + + // fast-mode + RandomARQ.CreateSeed(realm, seedHex, null, RandomX.randomx_flags.RANDOMX_FLAG_FULL_MEM); + + RandomARQ.CalculateHash("xmr", seedHex, input1, buf); + var result = buf.ToHexString(); + Assert.Equal(hashExpected1, result); + + Array.Clear(buf, 0, buf.Length); + + // second invocation should give the same result + RandomARQ.CalculateHash("xmr", seedHex, input1, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected1, result); + + RandomARQ.CalculateHash("xmr", seedHex, input2, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected2, result); + + RandomARQ.DeleteSeed(realm, seedHex); + } +} diff --git a/src/Miningcore.Tests/Crypto/RandomXTests.cs b/src/Miningcore.Tests/Crypto/RandomXTests.cs index f22867706..8ced98042 100644 --- a/src/Miningcore.Tests/Crypto/RandomXTests.cs +++ b/src/Miningcore.Tests/Crypto/RandomXTests.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Miningcore.Extensions; using Miningcore.Native; using Xunit; @@ -8,9 +9,11 @@ namespace Miningcore.Tests.Crypto; public class RandomXTests : TestBase { const string realm = "xmr"; - const string seedHex = "7915d56de262bf23b1fb9104cf5d2a13fcbed2f6b4b9b657309c222b09f54bc0"; - private static readonly byte[] data = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray(); - private const string hashExpected = "55ef9dc0b8e0cb82c609a003c7d99504fc87f5e2dcd31f6fef318fc172cbc887"; + private static readonly string seedHex = Encoding.UTF8.GetBytes("test key 000").ToHexString(); + private static readonly byte[] input1 = Encoding.UTF8.GetBytes("This is a test"); + private static readonly byte[] input2 = Encoding.UTF8.GetBytes("Lorem ipsum dolor sit amet"); + private const string hashExpected1 = "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f"; + private const string hashExpected2 = "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969"; [Fact] public void CreateAndDeleteSeed() @@ -41,16 +44,20 @@ public void CalculateHashSlow() // light-mode RandomX.CreateSeed(realm, seedHex); - RandomX.CalculateHash("xmr", seedHex, data, buf); + RandomX.CalculateHash("xmr", seedHex, input1, buf); var result = buf.ToHexString(); - Assert.Equal(hashExpected, result); + Assert.Equal(hashExpected1, result); Array.Clear(buf, 0, buf.Length); // second invocation should give the same result - RandomX.CalculateHash("xmr", seedHex, data, buf); + RandomX.CalculateHash("xmr", seedHex, input1, buf); result = buf.ToHexString(); - Assert.Equal(hashExpected, result); + Assert.Equal(hashExpected1, result); + + RandomX.CalculateHash("xmr", seedHex, input2, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected2, result); RandomX.DeleteSeed(realm, seedHex); } @@ -63,16 +70,20 @@ public void CalculateHashFast() // fast-mode RandomX.CreateSeed(realm, seedHex, null, RandomX.randomx_flags.RANDOMX_FLAG_FULL_MEM); - RandomX.CalculateHash("xmr", seedHex, data, buf); + RandomX.CalculateHash("xmr", seedHex, input1, buf); var result = buf.ToHexString(); - Assert.Equal(hashExpected, result); + Assert.Equal(hashExpected1, result); Array.Clear(buf, 0, buf.Length); // second invocation should give the same result - RandomX.CalculateHash("xmr", seedHex, data, buf); + RandomX.CalculateHash("xmr", seedHex, input1, buf); + result = buf.ToHexString(); + Assert.Equal(hashExpected1, result); + + RandomX.CalculateHash("xmr", seedHex, input2, buf); result = buf.ToHexString(); - Assert.Equal(hashExpected, result); + Assert.Equal(hashExpected2, result); RandomX.DeleteSeed(realm, seedHex); } diff --git a/src/Miningcore/Api/Controllers/PoolApiController.cs b/src/Miningcore/Api/Controllers/PoolApiController.cs index 24630e905..37c520104 100644 --- a/src/Miningcore/Api/Controllers/PoolApiController.cs +++ b/src/Miningcore/Api/Controllers/PoolApiController.cs @@ -87,10 +87,11 @@ public async Task Get() public ActionResult GetHelp() { var tmp = adcp.ActionDescriptors.Items + .Where(x => x.AttributeRouteInfo != null) .Select(x => { // Get and pad http method - var method = x?.ActionConstraints?.OfType().FirstOrDefault()?.HttpMethods.First(); + var method = x.ActionConstraints?.OfType().FirstOrDefault()?.HttpMethods.First(); method = $"{method,-5}"; return $"{method} -> {x.AttributeRouteInfo.Template}"; @@ -102,6 +103,12 @@ public ActionResult GetHelp() return Content(result); } + [HttpGet("/api/health-check")] + public ActionResult GetHealthCheck() + { + return Content("👍"); + } + [HttpGet("{poolId}")] public async Task GetPoolInfoAsync(string poolId) { @@ -346,6 +353,10 @@ public async Task PagePoolMinersAsync( { stats = mapper.Map(statsResult); + // pre-multiply pending shares to cause less confusion with users + if(pool.Template.Family == CoinFamily.Bitcoin) + stats.PendingShares *= pool.Template.As().ShareMultiplier; + // optional fields if(statsResult.LastPayment != null) { diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs index 8f0a76fc8..c4b8af952 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -7,6 +7,7 @@ using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; +using Miningcore.Rpc; using Miningcore.Stratum; using Miningcore.Time; using Newtonsoft.Json; diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index 1fe84f79e..9c09285cb 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -11,6 +11,7 @@ using Miningcore.Messaging; using Miningcore.Mining; using Miningcore.Notifications.Messages; +using Miningcore.Rpc; using Miningcore.Time; using NBitcoin; using Newtonsoft.Json; diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 8eef504a7..24849a9f5 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -11,6 +11,7 @@ using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; +using Miningcore.Rpc; using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs index f2de8071a..545344acc 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPool.cs @@ -467,11 +467,9 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, context.EnqueueNewDifficulty(newDiff); - // apply immediately and notify client - if(context.HasPendingDifficulty) + // apply immediately and notify + if(context.ApplyPendingDifficulty()) { - context.ApplyPendingDifficulty(); - await connection.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); } diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJob.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJob.cs index 6aeeafdcc..b43bf6cfd 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJob.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJob.cs @@ -5,6 +5,7 @@ using Miningcore.Stratum; using Miningcore.Util; using Org.BouncyCastle.Math; +using static Miningcore.Native.Cryptonight.Algorithm; using Contract = Miningcore.Contracts.Contract; namespace Miningcore.Blockchain.Cryptonote; @@ -23,19 +24,41 @@ public CryptonoteJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, BlockTemplate = blockTemplate; PrepareBlobTemplate(instanceId); PrevHash = prevHash; + RandomXRealm = randomXRealm; - switch(coin.Hash) - { - case CryptonightHashType.RandomX: - hashFunc = (seedHex, data, result, height) => - { - RandomX.CalculateHash(randomXRealm, seedHex, data, result); - }; - break; - } + hashFunc = hashFuncs[coin.Hash]; } - public delegate void HashFunc(string seedHex, ReadOnlySpan data, Span result, ulong height); + protected delegate void HashFunc(string realm, string seedHex, ReadOnlySpan data, Span result, ulong height); + + protected static readonly Dictionary hashFuncs = new() + { + { CryptonightHashType.RandomX, (realm, seedHex, data, result, _) => RandomX.CalculateHash(realm, seedHex, data, result) }, + { CryptonightHashType.RandomARQ, (realm, seedHex, data, result, _) => RandomARQ.CalculateHash(realm, seedHex, data, result) }, + { CryptonightHashType.Crytonight0, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_0, height) }, + { CryptonightHashType.Crytonight1, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_1, height) }, + { CryptonightHashType.Crytonight2, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_2, height) }, + { CryptonightHashType.CrytonightHalf, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_HALF, height) }, + { CryptonightHashType.CrytonightDouble, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_DOUBLE, height) }, + { CryptonightHashType.CrytonightR, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_R, height) }, + { CryptonightHashType.CrytonightRTO, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_RTO, height) }, + { CryptonightHashType.CrytonightRWZ, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_RWZ, height) }, + { CryptonightHashType.CrytonightZLS, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_ZLS, height) }, + { CryptonightHashType.CrytonightCCX, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_CCX, height) }, + { CryptonightHashType.CrytonightGPU, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_GPU, height) }, + { CryptonightHashType.CrytonightFast, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_FAST, height) }, + { CryptonightHashType.CrytonightXAO, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_XAO, height) }, + { CryptonightHashType.Ghostrider, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, GHOSTRIDER_RTM, height) }, + { CryptonightHashType.CrytonightLite0, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_LITE_0, height) }, + { CryptonightHashType.CrytonightLite1, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_LITE_1, height) }, + { CryptonightHashType.CrytonightHeavy, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_HEAVY_0, height) }, + { CryptonightHashType.CrytonightHeavyXHV, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_HEAVY_XHV, height) }, + { CryptonightHashType.CrytonightHeavyTube, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_HEAVY_TUBE, height) }, + { CryptonightHashType.CrytonightPico, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, CN_PICO_0, height) }, + { CryptonightHashType.ArgonCHUKWA, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, AR2_CHUKWA, height) }, + { CryptonightHashType.ArgonCHUKWAV2, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, AR2_CHUKWA_V2, height) }, + { CryptonightHashType.ArgonWRKZ, (_, _, data, result, height) => Cryptonight.CryptonightHash(data, result, AR2_WRKZ, height) }, + }; private byte[] blobTemplate; private int extraNonce; @@ -93,6 +116,7 @@ private void ComputeBlockHash(ReadOnlySpan blobConverted, Span resul public string PrevHash { get; } public GetBlockTemplateResponse BlockTemplate { get; } + public string RandomXRealm { get; set; } public void PrepareWorkerJob(CryptonoteWorkerJob workerJob, out string blob, out string target) { @@ -138,7 +162,7 @@ public void PrepareWorkerJob(CryptonoteWorkerJob workerJob, out string blob, out // hash it Span headerHash = stackalloc byte[32]; - hashFunc(BlockTemplate.SeedHash, blobConverted, headerHash, BlockTemplate.Height); + hashFunc(RandomXRealm, BlockTemplate.SeedHash, blobConverted, headerHash, BlockTemplate.Height); var headerHashString = headerHash.ToHexString(); if(headerHashString != workerHash) diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs index ecd5b7ad9..f2740ad1c 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonoteJobManager.cs @@ -16,6 +16,7 @@ using Miningcore.Mining; using Miningcore.Native; using Miningcore.Notifications.Messages; +using Miningcore.Rpc; using Miningcore.Stratum; using Miningcore.Time; using Miningcore.Util; diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs index 94f94ba8d..fe2fe0392 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePayoutHandler.cs @@ -14,6 +14,7 @@ using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; +using Miningcore.Rpc; using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; diff --git a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs index e77ec99b8..0d35590a7 100644 --- a/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs +++ b/src/Miningcore/Blockchain/Cryptonote/CryptonotePool.cs @@ -417,10 +417,8 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, // apply immediately and notify client var context = connection.ContextAs(); - if(context.HasPendingDifficulty) + if(context.ApplyPendingDifficulty()) { - context.ApplyPendingDifficulty(); - // re-send job var job = CreateWorkerJob(connection); await connection.NotifyAsync(CryptonoteStratumMethods.JobNotify, job); diff --git a/src/Miningcore/Blockchain/Equihash/DaemonResponses/GetBlockSubsidyResponse.cs b/src/Miningcore/Blockchain/Equihash/DaemonResponses/GetBlockSubsidyResponse.cs index 5b9006d8a..4ed0425ba 100644 --- a/src/Miningcore/Blockchain/Equihash/DaemonResponses/GetBlockSubsidyResponse.cs +++ b/src/Miningcore/Blockchain/Equihash/DaemonResponses/GetBlockSubsidyResponse.cs @@ -5,6 +5,8 @@ public class ZCashBlockSubsidy public decimal Miner { get; set; } public decimal? Founders { get; set; } public decimal? Community { get; set; } + public decimal? Securenodes { get; set; } + public decimal? Supernodes { get; set; } public List FundingStreams { get; set; } } diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJob.cs b/src/Miningcore/Blockchain/Equihash/EquihashJob.cs index aac54d2f2..8735cfaeb 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJob.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJob.cs @@ -89,6 +89,20 @@ protected virtual Transaction CreateOutputTransaction() tx.Outputs.Add(amount, destination); } } + else if(networkParams.vOuts) + { + rewardToPool = new Money(Math.Round(blockReward * (1m - (networkParams.vPercentFoundersReward) / 100m)) + rewardFees, MoneyUnit.Satoshi); + tx.Outputs.Add(rewardToPool, poolAddressDestination); + var destination = FoundersAddressToScriptDestination(networkParams.vTreasuryRewardAddress); + var amount = new Money(Math.Round(blockReward * (networkParams.vPercentTreasuryReward / 100m)), MoneyUnit.Satoshi); + tx.Outputs.Add(amount, destination); + destination = FoundersAddressToScriptDestination(networkParams.vSecureNodesRewardAddress); + amount = new Money(Math.Round(blockReward * (networkParams.percentSecureNodesReward / 100m)), MoneyUnit.Satoshi); + tx.Outputs.Add(amount, destination); + destination = FoundersAddressToScriptDestination(networkParams.vSuperNodesRewardAddress); + amount = new Money(Math.Round(blockReward * (networkParams.percentSuperNodesReward / 100m)), MoneyUnit.Satoshi); + tx.Outputs.Add(amount, destination); + } else if(networkParams.PayFoundersReward && (networkParams.LastFoundersRewardBlockHeight >= BlockTemplate.Height || networkParams.TreasuryRewardStartBlockHeight > 0)) @@ -370,6 +384,10 @@ public virtual void Init(EquihashBlockTemplate blockTemplate, string jobId, fundingstreamTotal = blockTemplate.Subsidy.FundingStreams.Sum(x => x.Value); blockReward = (blockTemplate.Subsidy.Miner + fundingstreamTotal) * BitcoinConstants.SatoshisPerBitcoin; } + else if(networkParams?.vOuts == true) + { + blockReward = (decimal) ((blockTemplate.Subsidy.Miner + blockTemplate.Subsidy.Community + blockTemplate.Subsidy.Securenodes + blockTemplate.Subsidy.Supernodes) * BitcoinConstants.SatoshisPerBitcoin); + } else if(networkParams?.PayFoundersReward == true) { var founders = blockTemplate.Subsidy.Founders ?? blockTemplate.Subsidy.Community; diff --git a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs index 72b97e9e1..487bb70bc 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashJobManager.cs @@ -10,6 +10,7 @@ using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; +using Miningcore.Rpc; using Miningcore.Stratum; using Miningcore.Time; using NBitcoin; diff --git a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs index 930063836..3651f5873 100644 --- a/src/Miningcore/Blockchain/Equihash/EquihashPool.cs +++ b/src/Miningcore/Blockchain/Equihash/EquihashPool.cs @@ -397,11 +397,9 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, context.EnqueueNewDifficulty(newDiff); - // apply immediately and notify client - if(context.HasPendingDifficulty) + // apply immediately and notify + if(context.ApplyPendingDifficulty()) { - context.ApplyPendingDifficulty(); - await connection.NotifyAsync(EquihashStratumMethods.SetTarget, new object[] { EncodeTarget(context.Difficulty) }); await connection.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); } diff --git a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs index d89290f76..5f1871b4d 100644 --- a/src/Miningcore/Blockchain/Ergo/ErgoPool.cs +++ b/src/Miningcore/Blockchain/Ergo/ErgoPool.cs @@ -382,10 +382,8 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, context.EnqueueNewDifficulty(newDiff); - if(context.HasPendingDifficulty) + if(context.ApplyPendingDifficulty()) { - context.ApplyPendingDifficulty(); - await SendJob(connection, context, currentJobParams); } } diff --git a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs index dd8c546ee..9b554517e 100644 --- a/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs +++ b/src/Miningcore/Blockchain/Ergo/RPC/ErgoClient.cs @@ -7407,13 +7407,13 @@ private string ConvertToString(object? value, System.Globalization.CultureInfo c return converted == null ? string.Empty : converted; } } - else if (value is bool) + else if (value is bool b) { - return Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + return Convert.ToString(b, cultureInfo).ToLowerInvariant(); } - else if (value is byte[]) + else if (value is byte[] bytes) { - return Convert.ToBase64String((byte[]) value); + return Convert.ToBase64String(bytes); } else if (value.GetType().IsArray) { diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs b/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs index 93cdbfea0..1d1331efa 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumConstants.cs @@ -37,6 +37,8 @@ public class EthereumConstants public const decimal ConstantinopleReward = 2.0m; public const int MinConfimations = 16; + + public const string RpcRequestWorkerPropertyName = "worker"; } // Callisto Monetary Policy diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs index 638f86369..0b4321832 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJob.cs @@ -50,7 +50,7 @@ private void RegisterNonce(StratumConnection worker, string nonce) } public async ValueTask<(Share Share, string FullNonceHex, string HeaderHash, string MixHash)> ProcessShareAsync( - StratumConnection worker, string fullNonceHex, EthashFull ethash, CancellationToken ct) + StratumConnection worker, string workerName, string fullNonceHex, EthashFull ethash, CancellationToken ct) { // duplicate nonce? lock(workerNonces) @@ -103,7 +103,7 @@ private void RegisterNonce(StratumConnection worker, string nonce) BlockHeight = (long) BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = context.Miner, - Worker = context.Worker, + Worker = workerName, UserAgent = context.UserAgent, IsBlockCandidate = isBlockCandidate, Difficulty = stratumDifficulty * EthereumConstants.Pow2x32 diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs index 13908c524..e4f336d31 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs @@ -23,6 +23,7 @@ using static Miningcore.Util.ActionUtils; using System.Reactive; using Miningcore.Mining; +using Miningcore.Rpc; using Newtonsoft.Json.Linq; namespace Miningcore.Blockchain.Ethereum; @@ -53,8 +54,6 @@ public EthereumJobManager( private readonly IMasterClock clock; private readonly IExtraNonceProvider extraNonceProvider; - private const int MaxBlockBacklog = 3; - protected readonly Dictionary validJobs = new(); private EthereumPoolConfigExtra extraPoolConfig; protected async Task UpdateJob(CancellationToken ct, string via = null) @@ -98,24 +97,7 @@ protected bool UpdateJob(EthereumBlockTemplate blockTemplate, string via = null) { messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Height, poolConfig.Template); - var jobId = NextJobId("x8"); - - // update template - job = new EthereumJob(jobId, blockTemplate, logger); - - lock(jobLock) - { - // add jobs - validJobs[jobId] = job; - - // remove old ones - var obsoleteKeys = validJobs.Keys - .Where(key => validJobs[key].BlockTemplate.Height < job.BlockTemplate.Height - MaxBlockBacklog).ToArray(); - - foreach(var key in obsoleteKeys) - validJobs.Remove(key); - } - + job = new EthereumJob(NextJobId("x8"), blockTemplate, logger); currentJob = job; logger.Info(() => $"New work at height {currentJob.BlockTemplate.Height} and header {currentJob.BlockTemplate.Header} via [{(via ?? "Unknown")}]"); @@ -170,7 +152,7 @@ private async Task GetBlockTemplateAsync(CancellationToke if(block == null) return null; - var currentHeight = block.Height.Value; + var currentHeight = block.Height!.Value; work = work.Concat(new[] { (currentHeight + 1).ToStringHexWithPrefix() }).ToArray(); } @@ -374,7 +356,7 @@ public void PrepareWorker(StratumConnection client) context.ExtraNonce1 = extraNonceProvider.Next(); } - public async ValueTask SubmitShareV1Async(StratumConnection worker, string[] request, CancellationToken ct) + public async ValueTask SubmitShareV1Async(StratumConnection worker, string[] request, string workerName, CancellationToken ct) { Contract.RequiresNonNull(worker, nameof(worker)); Contract.RequiresNonNull(request, nameof(request)); @@ -383,21 +365,8 @@ public async ValueTask SubmitShareV1Async(StratumConnection worker, strin var context = worker.ContextAs(); var nonce = request[0]; - var hash = request[1]; - - EthereumJob job; - // stale? - lock(jobLock) - { - // locate job by header - job = validJobs.Values.FirstOrDefault(x => x.BlockTemplate.Header == hash); - - if(job == null) - throw new StratumException(StratumError.MinusOne, "stale share"); - } - - return await SubmitShareAsync(worker, context, job, nonce.StripHexPrefix(), ct); + return await SubmitShareAsync(worker, context, workerName, currentJob, nonce.StripHexPrefix(), ct); } public async ValueTask SubmitShareV2Async(StratumConnection worker, string[] request, CancellationToken ct) @@ -411,27 +380,21 @@ public async ValueTask SubmitShareV2Async(StratumConnection worker, strin var jobId = request[1]; var nonce = request[2]; - EthereumJob job; - // stale? - lock(jobLock) - { - // look up job by id - if(!validJobs.TryGetValue(jobId, out job)) - throw new StratumException(StratumError.MinusOne, "stale share"); - } + if(jobId != currentJob.Id) + throw new StratumException(StratumError.MinusOne, "stale share"); // assemble full-nonce var fullNonceHex = context.ExtraNonce1 + nonce; - return await SubmitShareAsync(worker, context, job, fullNonceHex, ct); + return await SubmitShareAsync(worker, context, context.Worker, currentJob, fullNonceHex, ct); } private async ValueTask SubmitShareAsync(StratumConnection worker, - EthereumWorkerContext context, EthereumJob job, string nonce, CancellationToken ct) + EthereumWorkerContext context, string workerName, EthereumJob job, string nonce, CancellationToken ct) { // validate & process - var (share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, nonce, ethash, ct); + var (share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, workerName, nonce, ethash, ct); // enrich share with common data share.PoolId = poolConfig.Id; diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 2c72e13a4..f846b42f2 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -14,6 +14,7 @@ using Miningcore.Persistence; using Miningcore.Persistence.Model; using Miningcore.Persistence.Repositories; +using Miningcore.Rpc; using Miningcore.Time; using Miningcore.Util; using Newtonsoft.Json; diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs index de94faaee..25332c01a 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPool.cs @@ -38,7 +38,7 @@ public EthereumPool(IComponentContext ctx, private EthereumJobManager manager; private EthereumCoinTemplate coin; - #region // Protcol V2 handlers + #region // Protocol V2 handlers - https://github.com/nicehash/Specifications/blob/master/EthereumStratum_NiceHash_v1.0.0.txt private async Task OnSubscribeAsync(StratumConnection connection, Timestamped tsRequest) { @@ -55,6 +55,8 @@ private async Task OnSubscribeAsync(StratumConnection connection, Timestamped(data, request.Id); + + if(context.IsNicehash) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + await connection.RespondAsync(response); // setup worker context context.IsSubscribed = true; - context.UserAgent = requestParams.FirstOrDefault()?.Trim(); } private async Task OnAuthorizeAsync(StratumConnection connection, Timestamped tsRequest) @@ -170,7 +181,7 @@ private async Task OnSubmitAsync(StratumConnection connection, Timestamped tsRequest) { @@ -332,7 +347,7 @@ private async Task SendWork(EthereumWorkerContext context, StratumConnection con await connection.RespondAsync(parameters, requestId); } - #endregion // Protcol V1 handlers + #endregion // Protocol V1 handlers #region Overrides @@ -387,6 +402,14 @@ protected override WorkerContextBase CreateWorkerContext() return new EthereumWorkerContext(); } + private static string GetWorkerNameFromV1Request(JsonRpcRequest request, EthereumWorkerContext context) + { + if(request.Extra?.TryGetValue(EthereumConstants.RpcRequestWorkerPropertyName, out var tmp) == true && tmp is string workerNameValue) + return workerNameValue; + + return context.Worker; + } + protected virtual Task OnNewJobAsync() { var currentJobParams = manager.GetJobParamsForStratum(); @@ -406,7 +429,7 @@ protected virtual Task OnNewJobAsync() switch(context.ProtocolVersion) { case 1: - await SendWork(context, connection, null); + await SendWork(context, connection, 0); break; case 2: @@ -478,7 +501,7 @@ protected override async Task OnRequestAsync(StratumConnection connection, break; case EthereumStratumMethods.SubmitHashrate: - // just ignore this + await connection.RespondAsync(true, request.Id); break; default: @@ -510,14 +533,12 @@ protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, // apply immediately and notify client var context = connection.ContextAs(); - if(context.HasPendingDifficulty) + if(context.ApplyPendingDifficulty()) { - context.ApplyPendingDifficulty(); - switch(context.ProtocolVersion) { case 1: - await SendWork(context, connection, null); + await SendWork(context, connection, 0); break; case 2: diff --git a/src/Miningcore/Blockchain/JobManagerBase.cs b/src/Miningcore/Blockchain/JobManagerBase.cs index cba01cd3f..58ffdc288 100644 --- a/src/Miningcore/Blockchain/JobManagerBase.cs +++ b/src/Miningcore/Blockchain/JobManagerBase.cs @@ -37,7 +37,7 @@ protected JobManagerBase(IComponentContext ctx, IMessageBus messageBus) protected abstract void ConfigureDaemons(); - protected virtual async Task StartDaemonAsync(CancellationToken ct) + protected async Task StartDaemonAsync(CancellationToken ct) { while(!await AreDaemonsHealthyAsync(ct)) { @@ -50,7 +50,7 @@ protected virtual async Task StartDaemonAsync(CancellationToken ct) while(!await AreDaemonsConnectedAsync(ct)) { - logger.Info(() => "Waiting for daemons to connect to peers ..."); + logger.Info(() => "Waiting for daemon to connect to peers ..."); await Task.Delay(TimeSpan.FromSeconds(10), ct); } @@ -75,7 +75,7 @@ protected IObservable BtStreamSubscribe(ZmqPubSubEndpointConfig config) .Select(x => x.Payload); } - protected virtual void OnBlockFound() + protected void OnBlockFound() { blockFoundSubject.OnNext(Unit.Default); } diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs index fd7fd6b1f..ac7ecd24c 100644 --- a/src/Miningcore/Configuration/ClusterConfig.cs +++ b/src/Miningcore/Configuration/ClusterConfig.cs @@ -222,6 +222,32 @@ public partial class EquihashNetworkParams public bool PayFoundersReward { get; set; } public bool PayFundingStream { get; set; } + // zencash fonder reward + public bool vOuts { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public decimal vPercentFoundersReward { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string vTreasuryRewardAddress { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public decimal vPercentTreasuryReward { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string vSecureNodesRewardAddress { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public decimal percentSecureNodesReward { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public string vSuperNodesRewardAddress { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + public decimal percentSuperNodesReward { get; set; } + + // zencash founder reward + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] public decimal PercentFoundersReward { get; set; } @@ -291,6 +317,78 @@ public enum CryptonightHashType { [EnumMember(Value = "randomx")] RandomX, + + [EnumMember(Value = "randomarq")] + RandomARQ, + + [EnumMember(Value = "cn0")] + Crytonight0, + + [EnumMember(Value = "cn1")] + Crytonight1, + + [EnumMember(Value = "cn2")] + Crytonight2, + + [EnumMember(Value = "cn-half")] + CrytonightHalf, + + [EnumMember(Value = "cn-double")] + CrytonightDouble, + + [EnumMember(Value = "cn-r")] + CrytonightR, + + [EnumMember(Value = "cn-rto")] + CrytonightRTO, + + [EnumMember(Value = "cn-rwz")] + CrytonightRWZ, + + [EnumMember(Value = "cn-zls")] + CrytonightZLS, + + [EnumMember(Value = "cn-ccx")] + CrytonightCCX, + + [EnumMember(Value = "cn-gpu")] + CrytonightGPU, + + [EnumMember(Value = "cn-fast")] + CrytonightFast, + + [EnumMember(Value = "cn-xao")] + CrytonightXAO, + + [EnumMember(Value = "gr")] + Ghostrider, + + [EnumMember(Value = "cn_lite0")] + CrytonightLite0, + + [EnumMember(Value = "cn_lite1")] + CrytonightLite1, + + [EnumMember(Value = "cn_heavy")] + CrytonightHeavy, + + [EnumMember(Value = "cn_heavy_xhv")] + CrytonightHeavyXHV, + + [EnumMember(Value = "cn_heavy_tube")] + CrytonightHeavyTube, + + [EnumMember(Value = "cn_pico")] + CrytonightPico, + + [EnumMember(Value = "argon_chukwa")] + ArgonCHUKWA, + + [EnumMember(Value = "argon_chukwa_v2")] + ArgonCHUKWAV2, + + [EnumMember(Value = "argon_wrkz")] + ArgonWRKZ, } public partial class CryptonoteCoinTemplate : CoinTemplate diff --git a/src/Miningcore/GitVersion.yml b/src/Miningcore/GitVersion.yml new file mode 100644 index 000000000..277877951 --- /dev/null +++ b/src/Miningcore/GitVersion.yml @@ -0,0 +1,8 @@ +assembly-versioning-scheme: MajorMinorPatch +mode: ContinuousDelivery +branches: + develop: + regex: ^dev(elop)?(ment)?|oliverw$ +ignore: + sha: [] +merge-message-formats: {} diff --git a/src/Miningcore/JsonRpc/JsonRpcRequest.cs b/src/Miningcore/JsonRpc/JsonRpcRequest.cs index 469442140..5beb3a486 100644 --- a/src/Miningcore/JsonRpc/JsonRpcRequest.cs +++ b/src/Miningcore/JsonRpc/JsonRpcRequest.cs @@ -41,10 +41,13 @@ public JsonRpcRequest(string method, T parameters, object id) [JsonProperty("id")] public object Id { get; set; } + [JsonExtensionData] + public IDictionary Extra { get; set; } + public TParam ParamsAs() where TParam : class { - if(Params is JToken) - return ((JToken) Params)?.ToObject(); + if(Params is JToken token) + return token.ToObject(); return (TParam) Params; } diff --git a/src/Miningcore/JsonRpc/JsonRpcResponse.cs b/src/Miningcore/JsonRpc/JsonRpcResponse.cs index 2cf5e5bf6..15a0d2fe1 100644 --- a/src/Miningcore/JsonRpc/JsonRpcResponse.cs +++ b/src/Miningcore/JsonRpc/JsonRpcResponse.cs @@ -50,16 +50,19 @@ public JsonRpcResponse(JsonRpcError ex, object id, object result) [JsonProperty(PropertyName = "result", NullValueHandling = NullValueHandling.Ignore)] public object Result { get; set; } - [JsonProperty(PropertyName = "error")] + [JsonProperty(PropertyName = "error", NullValueHandling = NullValueHandling.Ignore)] public JsonRpcError Error { get; set; } [JsonProperty(PropertyName = "id", NullValueHandling = NullValueHandling.Ignore)] public object Id { get; set; } + [JsonExtensionData] + public IDictionary Extra { get; set; } + public TParam ResultAs() where TParam : class { - if(Result is JToken) - return ((JToken) Result)?.ToObject(); + if(Result is JToken token) + return token.ToObject(); return (TParam) Result; } diff --git a/src/Miningcore/Mining/PoolBase.cs b/src/Miningcore/Mining/PoolBase.cs index 40e912c26..68314388e 100644 --- a/src/Miningcore/Mining/PoolBase.cs +++ b/src/Miningcore/Mining/PoolBase.cs @@ -98,7 +98,7 @@ protected override void OnConnect(StratumConnection connection, IPEndPoint ipEnd { var context = CreateWorkerContext(); var poolEndpoint = poolConfig.Ports[ipEndPoint.Port]; - context.Init(poolConfig, poolEndpoint.Difficulty, poolConfig.EnableInternalStratum == true ? poolEndpoint.VarDiff : null, clock); + context.Init(poolEndpoint.Difficulty, poolConfig.EnableInternalStratum == true ? poolEndpoint.VarDiff : null, clock); connection.SetContext(context); // varDiff setup diff --git a/src/Miningcore/Mining/WorkerContextBase.cs b/src/Miningcore/Mining/WorkerContextBase.cs index a73224f1b..65ca74642 100644 --- a/src/Miningcore/Mining/WorkerContextBase.cs +++ b/src/Miningcore/Mining/WorkerContextBase.cs @@ -48,12 +48,7 @@ public string UserAgent public bool IsNicehash { get; private set; } - /// - /// True if there's a difficulty update queued for this worker - /// - public bool HasPendingDifficulty => pendingDifficulty.HasValue; - - public void Init(PoolConfig poolConfig, double difficulty, VarDiffConfig varDiffConfig, IMasterClock clock) + public void Init(double difficulty, VarDiffConfig varDiffConfig, IMasterClock clock) { Difficulty = difficulty; LastActivity = clock.Now; diff --git a/src/Miningcore/Miningcore.csproj b/src/Miningcore/Miningcore.csproj index c2be0551c..f8e5bf71e 100644 --- a/src/Miningcore/Miningcore.csproj +++ b/src/Miningcore/Miningcore.csproj @@ -124,6 +124,7 @@ + diff --git a/src/Miningcore/Native/RandomARQ.cs b/src/Miningcore/Native/RandomARQ.cs new file mode 100644 index 000000000..71c4d5c91 --- /dev/null +++ b/src/Miningcore/Native/RandomARQ.cs @@ -0,0 +1,312 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Miningcore.Contracts; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Notifications.Messages; +using NLog; + +// ReSharper disable UnusedMember.Global +// ReSharper disable InconsistentNaming + +namespace Miningcore.Native; + +public static unsafe class RandomARQ +{ + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + internal static IMessageBus messageBus; + + #region VM managment + + internal static readonly Dictionary>>> realms = new(); + private static readonly byte[] empty = new byte[32]; + + #endregion // VM managment + + [DllImport("librandomarq", EntryPoint = "randomx_get_flags", CallingConvention = CallingConvention.Cdecl)] + private static extern RandomX.randomx_flags randomx_get_flags(); + + [DllImport("librandomarq", EntryPoint = "randomx_alloc_cache", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_alloc_cache(RandomX.randomx_flags flags); + + [DllImport("librandomarq", EntryPoint = "randomx_init_cache", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_init_cache(IntPtr cache, IntPtr key, int keysize); + + [DllImport("librandomarq", EntryPoint = "randomx_release_cache", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_release_cache(IntPtr cache); + + [DllImport("librandomarq", EntryPoint = "randomx_alloc_dataset", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_alloc_dataset(RandomX.randomx_flags flags); + + [DllImport("librandomarq", EntryPoint = "randomx_dataset_item_count", CallingConvention = CallingConvention.Cdecl)] + private static extern ulong randomx_dataset_item_count(); + + [DllImport("librandomarq", EntryPoint = "randomx_init_dataset", CallingConvention = CallingConvention.Cdecl)] + private static extern void randomx_init_dataset(IntPtr dataset, IntPtr cache, ulong startItem, ulong itemCount); + + [DllImport("librandomarq", EntryPoint = "randomx_get_dataset_memory", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_get_dataset_memory(IntPtr dataset); + + [DllImport("librandomarq", EntryPoint = "randomx_release_dataset", CallingConvention = CallingConvention.Cdecl)] + private static extern void randomx_release_dataset(IntPtr dataset); + + [DllImport("librandomarq", EntryPoint = "randomx_create_vm", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_create_vm(RandomX.randomx_flags flags, IntPtr cache, IntPtr dataset); + + [DllImport("librandomarq", EntryPoint = "randomx_vm_set_cache", CallingConvention = CallingConvention.Cdecl)] + private static extern void randomx_vm_set_cache(IntPtr machine, IntPtr cache); + + [DllImport("librandomarq", EntryPoint = "randomx_vm_set_dataset", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr randomx_vm_set_dataset(IntPtr machine, IntPtr dataset); + + [DllImport("librandomarq", EntryPoint = "randomx_destroy_vm", CallingConvention = CallingConvention.Cdecl)] + private static extern void randomx_destroy_vm(IntPtr machine); + + [DllImport("librandomarq", EntryPoint = "randomx_calculate_hash", CallingConvention = CallingConvention.Cdecl)] + private static extern void randomx_calculate_hash(IntPtr machine, byte* input, int inputSize, byte* output); + + public class GenContext + { + public DateTime LastAccess { get; set; } = DateTime.Now; + public int VmCount { get; init; } + } + + public class RxDataSet : IDisposable + { + private IntPtr dataset = IntPtr.Zero; + + public void Dispose() + { + if(dataset != IntPtr.Zero) + { + randomx_release_dataset(dataset); + dataset = IntPtr.Zero; + } + } + + public IntPtr Init(ReadOnlySpan key, RandomX.randomx_flags flags, IntPtr cache) + { + dataset = randomx_alloc_dataset(flags); + + var itemCount = randomx_dataset_item_count(); + randomx_init_dataset(dataset, cache, 0, itemCount); + + return dataset; + } + } + + public class RxVm : IDisposable + { + private IntPtr cache = IntPtr.Zero; + private IntPtr vm = IntPtr.Zero; + private RxDataSet ds; + + public void Dispose() + { + if(vm != IntPtr.Zero) + { + randomx_destroy_vm(vm); + vm = IntPtr.Zero; + } + + ds?.Dispose(); + + if(cache != IntPtr.Zero) + { + randomx_release_cache(cache); + cache = IntPtr.Zero; + } + } + + public void Init(ReadOnlySpan key, RandomX.randomx_flags flags) + { + var ds_ptr = IntPtr.Zero; + + // alloc cache + cache = randomx_alloc_cache(flags); + + // init cache + fixed(byte* key_ptr = key) + { + randomx_init_cache(cache, (IntPtr) key_ptr, key.Length); + } + + // Enable fast-mode? (requires 2GB+ memory per VM) + if((flags & RandomX.randomx_flags.RANDOMX_FLAG_FULL_MEM) != 0) + { + ds = new RxDataSet(); + ds_ptr = ds.Init(key, flags, cache); + + // cache is no longer needed in fast-mode + randomx_release_cache(cache); + cache = IntPtr.Zero; + } + + vm = randomx_create_vm(flags, cache, ds_ptr); + } + + public void CalculateHash(ReadOnlySpan data, Span result) + { + fixed (byte* input = data) + { + fixed (byte* output = result) + { + randomx_calculate_hash(vm, input, data.Length, output); + } + } + } + } + + public static void WithLock(Action action) + { + lock(realms) + { + action(); + } + } + + public static void CreateSeed(string realm, string seedHex, + RandomX.randomx_flags? flagsOverride = null, RandomX.randomx_flags? flagsAdd = null, int vmCount = 1) + { + lock(realms) + { + if(!realms.TryGetValue(realm, out var seeds)) + { + seeds = new Dictionary>>(); + + realms[realm] = seeds; + } + + if(!seeds.TryGetValue(seedHex, out var seed)) + { + var flags = flagsOverride ?? randomx_get_flags(); + + if(flagsAdd.HasValue) + flags |= flagsAdd.Value; + + if (vmCount == -1) + vmCount = Environment.ProcessorCount; + + seed = CreateSeed(realm, seedHex, flags, vmCount); + + seeds[seedHex] = seed; + } + } + } + + private static Tuple> CreateSeed(string realm, string seedHex, RandomX.randomx_flags flags, int vmCount) + { + var vms = new BlockingCollection(); + + var seed = new Tuple>(new GenContext + { + VmCount = vmCount + }, vms); + + void createVm(int index) + { + var start = DateTime.Now; + logger.Info(() => $"Creating VM {realm}@{index + 1} [{flags}], hash {seedHex} ..."); + + var vm = new RxVm(); + vm.Init(seedHex.HexToByteArray(), flags); + + vms.Add(vm); + + logger.Info(() => $"Created VM {realm}@{index + 1} in {DateTime.Now - start}"); + }; + + Parallel.For(0, vmCount, createVm); + + return seed; + } + + public static void DeleteSeed(string realm, string seedHex) + { + Tuple> seed; + + lock(realms) + { + if(!realms.TryGetValue(realm, out var seeds)) + return; + + if(!seeds.Remove(seedHex, out seed)) + return; + } + + // dispose all VMs + var (ctx, col) = seed; + var remaining = ctx.VmCount; + + while (remaining > 0) + { + var vm = col.Take(); + + logger.Info($"Disposing VM {ctx.VmCount - remaining} for realm {realm} and key {seedHex}"); + vm.Dispose(); + + remaining--; + } + } + + public static Tuple> GetSeed(string realm, string seedHex) + { + lock(realms) + { + if(!realms.TryGetValue(realm, out var seeds)) + return null; + + if(!seeds.TryGetValue(seedHex, out var seed)) + return null; + + return seed; + } + } + + public static void CalculateHash(string realm, string seedHex, ReadOnlySpan data, Span result) + { + Contract.Requires(result.Length >= 32, $"{nameof(result)} must be greater or equal 32 bytes"); + + var sw = Stopwatch.StartNew(); + var success = false; + + var (ctx, seedVms) = GetSeed(realm, seedHex); + + if(ctx != null) + { + RxVm vm = null; + + try + { + // lease a VM + vm = seedVms.Take(); + + vm.CalculateHash(data, result); + + ctx.LastAccess = DateTime.Now; + success = true; + + messageBus?.SendTelemetry("RandomARQ", TelemetryCategory.Hash, sw.Elapsed, true); + } + + catch(Exception ex) + { + logger.Error(() => ex.Message); + } + + finally + { + // return it + if(vm != null) + seedVms.Add(vm); + } + } + + if(!success) + { + // clear result on failure + empty.CopyTo(result); + } + } +} diff --git a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs index 8d351e250..4f33cfd4e 100644 --- a/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs +++ b/src/Miningcore/Persistence/Postgres/Repositories/ShareRepository.cs @@ -124,6 +124,23 @@ public async Task GetHashAccumulationBetweenCreatedAsync(ID .ToArray(); } + public async Task[]> GetAccumulatedUserAgentShareDifficultyBetweenCreatedAsync( + IDbConnection con, string poolId, DateTime start, DateTime end, bool byVersion = false) + { + logger.LogInvoke(new object[] { poolId }); + + const string query = "SELECT SUM(difficulty) AS value, REGEXP_REPLACE(useragent, '/.+', '') AS key FROM shares " + + "WHERE poolid = @poolId AND created > @start AND created < @end " + + "GROUP BY key ORDER BY value DESC"; + + const string queryByVersion = "SELECT SUM(difficulty) AS value, useragent AS key FROM shares " + + "WHERE poolid = @poolId AND created > @start AND created < @end " + + "GROUP BY key ORDER BY value DESC"; + + return (await con.QueryAsync>(!byVersion ? query : queryByVersion, new { poolId, start, end })) + .ToArray(); + } + public async Task GetRecentyUsedIpAddresses(IDbConnection con, IDbTransaction tx, string poolId, string miner) { logger.LogInvoke(new object[] { poolId }); diff --git a/src/Miningcore/Persistence/Repositories/IShareRepository.cs b/src/Miningcore/Persistence/Repositories/IShareRepository.cs index 16d7e4f63..fe7ee0502 100644 --- a/src/Miningcore/Persistence/Repositories/IShareRepository.cs +++ b/src/Miningcore/Persistence/Repositories/IShareRepository.cs @@ -14,5 +14,6 @@ public interface IShareRepository Task DeleteSharesByMinerAsync(IDbConnection con, IDbTransaction tx, string poolId, string miner); Task GetAccumulatedShareDifficultyBetweenCreatedAsync(IDbConnection con, string poolId, DateTime start, DateTime end); Task GetHashAccumulationBetweenCreatedAsync(IDbConnection con, string poolId, DateTime start, DateTime end); + Task[]> GetAccumulatedUserAgentShareDifficultyBetweenCreatedAsync(IDbConnection con, string poolId, DateTime start, DateTime end, bool byVersion = false); Task GetRecentyUsedIpAddresses(IDbConnection con, IDbTransaction tx, string poolId, string miner); } diff --git a/src/Miningcore/Program.cs b/src/Miningcore/Program.cs index 68c476bdf..40aadf70f 100644 --- a/src/Miningcore/Program.cs +++ b/src/Miningcore/Program.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Net; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using AspNetCoreRateLimit; @@ -56,6 +57,8 @@ // ReSharper disable AssignNullToNotNullAttribute // ReSharper disable PossibleNullReferenceException +[assembly: InternalsVisibleToAttribute("Miningcore.Tests")] + namespace Miningcore; public class Program : BackgroundService @@ -756,6 +759,9 @@ private static async Task PreFlightChecks(IServiceProvider services) // Configure RandomX RandomX.messageBus = messageBus; + + // Configure RandomARQ + RandomARQ.messageBus = messageBus; } private static async Task ConfigurePostgresCompatibilityOptions(IServiceProvider services) diff --git a/src/Miningcore/Rest/SimpleRestClient.cs b/src/Miningcore/Rest/SimpleRestClient.cs index bdcc907c1..89f528e4a 100644 --- a/src/Miningcore/Rest/SimpleRestClient.cs +++ b/src/Miningcore/Rest/SimpleRestClient.cs @@ -42,7 +42,7 @@ protected virtual void PrepareRequest(HttpRequestMessage request, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true, - NumberHandling = JsonNumberHandling.AllowReadingFromString + NumberHandling = JsonNumberHandling.AllowReadingFromString, }; public class ResponseContent : IDisposable diff --git a/src/Miningcore/JsonRpc/RpcClient.cs b/src/Miningcore/Rpc/RpcClient.cs similarity index 99% rename from src/Miningcore/JsonRpc/RpcClient.cs rename to src/Miningcore/Rpc/RpcClient.cs index 3b686dcb5..b6c5da90d 100644 --- a/src/Miningcore/JsonRpc/RpcClient.cs +++ b/src/Miningcore/Rpc/RpcClient.cs @@ -4,10 +4,10 @@ using System.Net.WebSockets; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; using System.Text; using Miningcore.Configuration; using Miningcore.Extensions; +using Miningcore.JsonRpc; using Miningcore.Messaging; using Miningcore.Notifications.Messages; using Miningcore.Util; @@ -17,7 +17,7 @@ using ZeroMQ; using Contract = Miningcore.Contracts.Contract; -namespace Miningcore.JsonRpc; +namespace Miningcore.Rpc; /// /// JsonRpc interface to blockchain node diff --git a/src/Miningcore/JsonRpc/RpcRequest.cs b/src/Miningcore/Rpc/RpcRequest.cs similarity index 90% rename from src/Miningcore/JsonRpc/RpcRequest.cs rename to src/Miningcore/Rpc/RpcRequest.cs index eb819c866..1bc6c9d60 100644 --- a/src/Miningcore/JsonRpc/RpcRequest.cs +++ b/src/Miningcore/Rpc/RpcRequest.cs @@ -1,4 +1,4 @@ -namespace Miningcore.JsonRpc; +namespace Miningcore.Rpc; public record RpcRequest { diff --git a/src/Miningcore/JsonRpc/RpcResponse.cs b/src/Miningcore/Rpc/RpcResponse.cs similarity index 56% rename from src/Miningcore/JsonRpc/RpcResponse.cs rename to src/Miningcore/Rpc/RpcResponse.cs index 135bbc86e..723c8c25f 100644 --- a/src/Miningcore/JsonRpc/RpcResponse.cs +++ b/src/Miningcore/Rpc/RpcResponse.cs @@ -1,3 +1,5 @@ -namespace Miningcore.JsonRpc; +using Miningcore.JsonRpc; + +namespace Miningcore.Rpc; public record RpcResponse(T Response, JsonRpcError Error = null); diff --git a/src/Miningcore/Stratum/StratumConnection.cs b/src/Miningcore/Stratum/StratumConnection.cs index 37632b1e4..83f2715f7 100644 --- a/src/Miningcore/Stratum/StratumConnection.cs +++ b/src/Miningcore/Stratum/StratumConnection.cs @@ -182,7 +182,7 @@ public ValueTask RespondAsync(T payload, object id) return RespondAsync(new JsonRpcResponse(payload, id)); } - public ValueTask RespondErrorAsync(StratumError code, string message, object id, object result = null, object data = null) + public ValueTask RespondErrorAsync(StratumError code, string message, object id, object result = null) { return RespondAsync(new JsonRpcResponse(new JsonRpcError((int) code, message, null), id, result)); } @@ -338,8 +338,6 @@ private async Task ProcessSendQueueAsync(CancellationToken ct) private async Task SendMessage(object msg, CancellationToken ct) { - logger.Debug(() => $"[{ConnectionId}] Sending: {JsonConvert.SerializeObject(msg)}"); - var buffer = ArrayPool.Shared.Rent(MaxOutboundRequestLength); try @@ -350,6 +348,8 @@ private async Task SendMessage(object msg, CancellationToken ct) var cb = SerializeMessage(msg, buffer); + logger.Debug(() => $"[{ConnectionId}] Sending: {StratumConstants.Encoding.GetString(buffer.AsSpan(0, cb))}"); + await networkStream.WriteAsync(buffer, 0, cb, ctsTimeout.Token); await networkStream.FlushAsync(ctsTimeout.Token); } diff --git a/src/Miningcore/Stratum/StratumServer.cs b/src/Miningcore/Stratum/StratumServer.cs index 9ea166589..bdd77403c 100644 --- a/src/Miningcore/Stratum/StratumServer.cs +++ b/src/Miningcore/Stratum/StratumServer.cs @@ -81,7 +81,7 @@ static StratumServer() protected IBanManager banManager; protected ILogger logger; - public Task RunAsync(CancellationToken ct, params StratumEndpoint[] endpoints) + protected Task RunAsync(CancellationToken ct, params StratumEndpoint[] endpoints) { Contract.RequiresNonNull(endpoints, nameof(endpoints)); @@ -154,7 +154,7 @@ private void AcceptConnection(Socket socket, StratumEndpoint port, X509Certifica }, ex=> logger.Error(ex)), ct); } - protected virtual void RegisterConnection(StratumConnection connection) + protected void RegisterConnection(StratumConnection connection) { var result = connections.TryAdd(connection.ConnectionId, connection); Debug.Assert(result); @@ -162,7 +162,7 @@ protected virtual void RegisterConnection(StratumConnection connection) PublishTelemetry(TelemetryCategory.Connections, TimeSpan.Zero, true, connections.Count); } - protected virtual void UnregisterConnection(StratumConnection connection) + protected void UnregisterConnection(StratumConnection connection) { var result = connections.TryRemove(connection.ConnectionId, out _); Debug.Assert(result); @@ -187,7 +187,7 @@ protected async Task OnRequestAsync(StratumConnection connection, JsonRpcRequest await OnRequestAsync(connection, new Timestamped(request, clock.Now), ct); } - protected virtual void OnConnectionError(StratumConnection connection, Exception ex) + protected void OnConnectionError(StratumConnection connection, Exception ex) { if(ex is AggregateException) ex = ex.InnerException; @@ -259,14 +259,14 @@ protected virtual void OnConnectionError(StratumConnection connection, Exception UnregisterConnection(connection); } - protected virtual void OnConnectionComplete(StratumConnection connection) + protected void OnConnectionComplete(StratumConnection connection) { logger.Debug(() => $"[{connection.ConnectionId}] Received EOF"); UnregisterConnection(connection); } - protected virtual void CloseConnection(StratumConnection connection) + protected void CloseConnection(StratumConnection connection) { Contract.RequiresNonNull(connection, nameof(connection)); diff --git a/src/Miningcore/Util/BigRational.cs b/src/Miningcore/Util/BigRational.cs index 3bfb9243e..5855aa81d 100644 --- a/src/Miningcore/Util/BigRational.cs +++ b/src/Miningcore/Util/BigRational.cs @@ -145,9 +145,9 @@ int IComparable.CompareTo(object obj) { if(obj == null) return 1; - if(obj is not BigRational) + if(obj is not BigRational rational) throw new ArgumentException("Argument must be of type BigRational", "obj"); - return Compare(this, (BigRational) obj); + return Compare(this, rational); } // IComparable diff --git a/src/Miningcore/Util/ScheduledSubject.cs b/src/Miningcore/Util/ScheduledSubject.cs index f422306da..8a942a41b 100644 --- a/src/Miningcore/Util/ScheduledSubject.cs +++ b/src/Miningcore/Util/ScheduledSubject.cs @@ -56,7 +56,7 @@ public IDisposable Subscribe(IObserver observer) public void Dispose() { - if(_subject is IDisposable) - ((IDisposable) _subject).Dispose(); + if(_subject is IDisposable disposable) + disposable.Dispose(); } } diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index 5d85c5fe4..e36cb1bea 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -1278,7 +1278,15 @@ "zsoemTfqjicem2QVU8cgBHquKb1o9JR5p4Z", "zt339oiGL6tTgc9Q71f5g1sFTZf6QiXrRUr" ], - "treasuryRewardAddressChangeInterval": 50000 + "treasuryRewardAddressChangeInterval": 50000, + "vOuts": true, + "vPercentFoundersReward": 40, + "vPercentTreasuryReward": 20, + "vTreasuryRewardAddress": "zszpcLB6C5B8QvfDbF2dYWXsrpac5DL9WRk", + "percentSecureNodesReward": 10.0, + "vSecureNodesRewardAddress": "zsxWnyDbU8pk2Vp98Uvkx5Nh33RFzqnCpWN", + "percentSuperNodesReward": 10.0, + "vSuperNodesRewardAddress": "zsnL6pKdzvZ1BPVzALUoqw2KsY966XFs5CE" }, "test": { "diff1": "0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -1351,7 +1359,15 @@ "zrRBQ5heytPMN5nY3ssPf3cG4jocXeD8fm1", "zrRBQ5heytPMN5nY3ssPf3cG4jocXeD8fm1" ], - "treasuryRewardAddressChangeInterval": 10000 + "treasuryRewardAddressChangeInterval": 10000, + "vOuts": true, + "vPercentFoundersReward": 40, + "vPercentTreasuryReward": 20, + "vTreasuryRewardAddress": "zrFzxutppvxEdjyu4QNjogBMjtC1py9Hp1S", + "percentSecureNodesReward": 10.0, + "vSecureNodesRewardAddress": "zrS7QUB2eDbbKvyP43VJys3t7RpojW8GdxH", + "percentSuperNodesReward": 10.0, + "vSuperNodesRewardAddress": "zrFr5HVm7woVq3oFzkMEdJdbfBchfPAPDsP" }, "regtest": { "diff1": "0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", @@ -1369,14 +1385,22 @@ "percentTreasuryReward": 12, "treasuryRewardStartBlockHeight": 139200, "treasuryRewardAddresses": [ "zrKmSdqZKZjnARd5e8FfRg4v1m74X7twxGa" ], - "treasuryRewardAddressChangeInterval": 100 + "treasuryRewardAddressChangeInterval": 100, + "vOuts": true, + "vPercentFoundersReward": 40, + "vPercentTreasuryReward": 20, + "vTreasuryRewardAddress": "zrKmSdqZKZjnARd5e8FfRg4v1m74X7twxGa", + "percentSecureNodesReward": 10.0, + "vSecureNodesRewardAddress": "zrKmSdqZKZjnARd5e8FfRg4v1m74X7twxGa", + "percentSuperNodesReward": 10.0, + "vSuperNodesRewardAddress": "zrKmSdqZKZjnARd5e8FfRg4v1m74X7twxGa" } }, "usesZCashAddressFormat": true, "useBitcoinPayoutHandler": false, "explorerBlockLink": "http://explorer.zensystem.io/block/$hash$", - "explorerTxLink": "http://explorer.zensystem.io/transactions/{0}", - "explorerAccountLink": "http://explorer.zensystem.io/accounts/{0}" + "explorerTxLink": "http://explorer.zensystem.io/tx/{0}", + "explorerAccountLink": "http://explorer.zensystem.io/address/{0}" }, "bitcoin-gold": { "name": "Bitcoin Gold", diff --git a/src/Native/librandomarq/Makefile b/src/Native/librandomarq/Makefile new file mode 100644 index 000000000..cd22a218b --- /dev/null +++ b/src/Native/librandomarq/Makefile @@ -0,0 +1,17 @@ +# Create shared library librandomx.so from static library librandomx.a using --whole-archive + +CC = g++ +LDFLAGS = -shared -pthread -L. -Wl,-whole-archive librandomx.a -Wl,-no-whole-archive +LDLIBS = -lstdc++ -lgcc -lc +TARGET = librandomarq.so + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +.PHONY: clean + +clean: + find . -name '*.o' -exec rm -r {} \; + rm -f librandomarq.so