diff --git a/.gitignore b/.gitignore index 0bf0b0de5..f767354a4 100644 --- a/.gitignore +++ b/.gitignore @@ -210,5 +210,4 @@ fabric.properties /*/*/*/*.a /*/*/*/*/*/*/*/*.o /*/*/*/*/*/*/*/*/*.o -/*/*/*/*/*/*/*/*/*/*.o -/*/*/*/*/*/*/*/*/*/*.o +/*/*/*/*/*/*/*/*/*/*.o \ No newline at end of file diff --git a/examples/ravencoin_pool.json b/examples/ravencoin_pool.json new file mode 100644 index 000000000..cac0d37df --- /dev/null +++ b/examples/ravencoin_pool.json @@ -0,0 +1,117 @@ +{ + "logging": { + "level": "info", + "enableConsoleLog": true, + "enableConsoleColors": true, + "logFile": "", + "apiLogFile": "", + "logBaseDirectory": "", + "perPoolLogFile": true + }, + "banning": { + "manager": "Integrated", + "banOnJunkReceive": true, + "banOnInvalidShares": true, + "banOnLoginFailure": true + }, + "notifications": { + "enabled": false, + "email": { + "host": "smtp.example.com", + "port": 465, + "user": "user", + "password": "password", + "fromAddress": "info@yourpool.org", + "fromName": "pool support" + }, + "admin": { + "enabled": true, + "emailAddress": "admin@yourpool.org", + "notifyBlockFound": false + } + }, + "persistence": { + "postgres": { + "host": "localhost", + "port": 5432, + "user": "miningcore", + "password": "miningcore", + "database": "miningcore" + } + }, + "paymentProcessing": { + "enabled": true, + "interval": 100 + }, + "shareRecoveryFile": "/app/recovered-shares.txt", + "api": { + "enabled": true, + "listenAddress": "0.0.0.0", + "port": 5000, + "metricsIpWhitelist": ["127.0.0.1"], + "adminIpWhitelist": ["127.0.0.1"], + "rateLimiting": { + "disabled": false, + "rules": [ + { + "Endpoint": "*", + "Period": "1s", + "Limit": 500 + } + ], + "ipWhitelist": ["127.0.0.1"] + } + }, + "nicehashClusterConfig": { + "enableAutoDiff": true + }, + "clusterName": "devel_1", + "pools": [ + { + "id": "rvn1", + "enabled": true, + "coin": "ravencoin", + "address": "mxR4ic4Q32giqfcYqQGxDteGhzkjgA2eX6", + "rewardRecipients": [ + { + "type": "op", + "address": "mwQo1fhe1sAYtywSPEXEp7vGxFS46qzsnB", + "percentage": 1 + } + ], + "blockRefreshInterval": 120, + "clientConnectionTimeout": 600, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 50 + }, + "ports": { + "4600": { + "name": "test", + "listenAddress": "*", + "difficulty": 1, + "varDiff": null + } + }, + "daemons": [ + { + "host": "127.0.0.1", + "port": 8766, + "user": "user", + "password": "passwd" + } + ], + "paymentProcessing": { + "enabled": true, + "minimumPayment": 10000, + "payoutScheme": "PPLNS", + "payoutSchemeConfig": { + "factor": 0.5 + } + } + } + ] + } + \ No newline at end of file diff --git a/src/.idea/.idea.Miningcore/.idea/indexLayout.xml b/src/.idea/.idea.Miningcore/.idea/indexLayout.xml index 7b08163ce..444ce609f 100644 --- a/src/.idea/.idea.Miningcore/.idea/indexLayout.xml +++ b/src/.idea/.idea.Miningcore/.idea/indexLayout.xml @@ -1,7 +1,9 @@ - + + ../../ravencoin + diff --git a/src/Miningcore/AutofacModule.cs b/src/Miningcore/AutofacModule.cs index ef2b595b9..db6c9032a 100644 --- a/src/Miningcore/AutofacModule.cs +++ b/src/Miningcore/AutofacModule.cs @@ -7,6 +7,7 @@ using Miningcore.Blockchain.Cryptonote; using Miningcore.Blockchain.Equihash; using Miningcore.Blockchain.Ethereum; +using Miningcore.Blockchain.Ravencoin; using Miningcore.Configuration; using Miningcore.Crypto; using Miningcore.Crypto.Hashing.Equihash; @@ -176,6 +177,11 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType(); builder.RegisterType(); + ////////////////////// + // Ravencoin + + builder.RegisterType(); + base.Load(builder); } } diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs index 0557ac076..41b9c45fa 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJob.cs @@ -243,10 +243,10 @@ protected virtual Transaction CreateOutputTransaction() if(coin.HasMasterNodes) rewardToPool = CreateMasternodeOutputs(tx, rewardToPool); - if (coin.HasFounderFee) + if(coin.HasFounderFee) rewardToPool = CreateFounderOutputs(tx, rewardToPool); - if (coin.HasMinerFund) + if(coin.HasMinerFund) rewardToPool = CreateMinerFundOutputs(tx, rewardToPool); // Remaining amount goes to pool @@ -492,10 +492,10 @@ protected virtual Money CreateMasternodeOutputs(Transaction tx, Money reward) protected virtual Money CreateFounderOutputs(Transaction tx, Money reward) { - if (founderParameters.Founder != null) + if(founderParameters.Founder != null) { Founder[] founders; - if (founderParameters.Founder.Type == JTokenType.Array) + if(founderParameters.Founder.Type == JTokenType.Array) founders = founderParameters.Founder.ToObject(); else founders = new[] { founderParameters.Founder.ToObject() }; @@ -529,7 +529,7 @@ protected virtual Money CreateMinerFundOutputs(Transaction tx, Money reward) { var payeeReward = minerFundParameters.MinimumValue; - if (!string.IsNullOrEmpty(minerFundParameters.Addresses?.FirstOrDefault())) + if(!string.IsNullOrEmpty(minerFundParameters.Addresses?.FirstOrDefault())) { var payeeAddress = BitcoinUtils.AddressToDestination(minerFundParameters.Addresses[0], network); tx.Outputs.Add(payeeReward, payeeAddress); @@ -612,10 +612,10 @@ public void Init(BlockTemplate blockTemplate, string jobId, if(coin.HasPayee) payeeParameters = BlockTemplate.Extra.SafeExtensionDataAs(); - if (coin.HasFounderFee) + if(coin.HasFounderFee) founderParameters = BlockTemplate.Extra.SafeExtensionDataAs(); - if (coin.HasMinerFund) + if(coin.HasMinerFund) minerFundParameters = BlockTemplate.Extra.SafeExtensionDataAs("coinbasetxn", "minerfund"); this.coinbaseHasher = coinbaseHasher; diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs index e48a86d5e..0fc25bdc4 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinJobManagerBase.cs @@ -262,7 +262,7 @@ protected async Task SubmitBlockAsync(Share share, string blockHex ? new RpcRequest(BitcoinCommands.SubmitBlock, new[] { blockHex }) : new RpcRequest(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }); - var batch = new [] + var batch = new[] { submitBlockRequest, new RpcRequest(BitcoinCommands.GetBlock, new[] { share.BlockHash }) @@ -466,17 +466,17 @@ protected override async Task PostStartInitAsync(CancellationToken ct) PostChainIdentifyConfigure(); // ensure pool owns wallet - if(validateAddressResponse is not {IsValid: true}) + if(validateAddressResponse is not { IsValid: true }) throw new PoolStartupException($"Daemon reports pool-address '{poolConfig.Address}' as invalid", poolConfig.Id); - isPoS = poolConfig.Template is BitcoinTemplate {IsPseudoPoS: true} || + isPoS = poolConfig.Template is BitcoinTemplate { IsPseudoPoS: true } || (difficultyResponse.Values().Any(x => x.Path == "proof-of-stake" && !difficultyResponse.Values().Any(x => x.Path == "proof-of-work"))); // Create pool address script from response if(!isPoS) { if(extraPoolConfig != null && extraPoolConfig.AddressType != BitcoinAddressType.Legacy) - logger.Info(()=> $"Interpreting pool address {poolConfig.Address} as type {extraPoolConfig?.AddressType.ToString()}"); + logger.Info(() => $"Interpreting pool address {poolConfig.Address} as type {extraPoolConfig?.AddressType.ToString()}"); poolAddressDestination = AddressToDestination(poolConfig.Address, extraPoolConfig?.AddressType); } @@ -488,8 +488,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // ensure pool owns wallet - if(validateAddressResponse is {IsMine: false} && addressInfoResponse is {IsMine: false}) - logger.Warn(()=> $"Daemon does not own pool-address '{poolConfig.Address}'"); + if(validateAddressResponse is { IsMine: false } && addressInfoResponse is { IsMine: false }) + logger.Warn(() => $"Daemon does not own pool-address '{poolConfig.Address}'"); } // update stats @@ -512,7 +512,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // Periodically update network stats Observable.Interval(TimeSpan.FromMinutes(10)) .Select(_ => Observable.FromAsync(() => - Guard(()=> !hasLegacyDaemon ? UpdateNetworkStatsAsync(ct) : UpdateNetworkStatsLegacyAsync(ct), + Guard(() => !hasLegacyDaemon ? UpdateNetworkStatsAsync(ct) : UpdateNetworkStatsLegacyAsync(ct), ex => logger.Error(ex)))) .Concat() .Subscribe(); @@ -573,7 +573,7 @@ public virtual async Task ValidateAddressAsync(string address, Cancellatio var result = await rpc.ExecuteAsync(logger, BitcoinCommands.ValidateAddress, ct, new[] { address }); - return result.Response is {IsValid: true}; + return result.Response is { IsValid: true }; } #endregion // API-Surface diff --git a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs index 691deb33f..fb23f32f9 100644 --- a/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Bitcoin/BitcoinPayoutHandler.cs @@ -15,13 +15,13 @@ using Miningcore.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using static Miningcore.Util.ActionUtils; using Block = Miningcore.Persistence.Model.Block; using Contract = Miningcore.Contracts.Contract; -using static Miningcore.Util.ActionUtils; namespace Miningcore.Blockchain.Bitcoin; -[CoinFamily(CoinFamily.Bitcoin)] +[CoinFamily(CoinFamily.Bitcoin, CoinFamily.Ravencoin)] public class BitcoinPayoutHandler : PayoutHandlerBase, IPayoutHandler { @@ -250,8 +250,8 @@ public virtual async Task PayoutAsync(IMiningPool pool, Balance[] balances, Canc var didUnlockWallet = false; - // send command - tryTransfer: + // send command + tryTransfer: var result = await rpcClient.ExecuteAsync(logger, BitcoinCommands.SendMany, ct, args); if(result.Error == null) @@ -336,7 +336,7 @@ await Guard(async () => // use a common id for all log entries related to this transfer var transferId = CorrelationIdGenerator.GetNextId(); - logger.Info(()=> $"[{LogCategory}] [{transferId}] Sending {FormatAmount(amount)} to {address}"); + logger.Info(() => $"[{LogCategory}] [{transferId}] Sending {FormatAmount(amount)} to {address}"); var result = await rpcClient.ExecuteAsync(logger, BitcoinCommands.SendToAddress, ct, new object[] { @@ -376,10 +376,10 @@ await Guard(async () => if(txFailures.Any()) { - var failureBalances = txFailures.Select(x=> new Balance { Amount = x.Item1.Value }).ToArray(); + var failureBalances = txFailures.Select(x => new Balance { Amount = x.Item1.Value }).ToArray(); var error = string.Join(", ", txFailures.Select(x => $"{x.Item1.Key} {FormatAmount(x.Item1.Value)}: {x.Item2.Message}")); - logger.Error(()=> $"[{LogCategory}] Failed to transfer the following balances: {error}"); + logger.Error(() => $"[{LogCategory}] Failed to transfer the following balances: {error}"); NotifyPayoutFailure(poolConfig.Id, failureBalances, error, null); } diff --git a/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs b/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs index c043a4044..ce3a7946d 100644 --- a/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs +++ b/src/Miningcore/Blockchain/Conceal/ConcealJobManager.cs @@ -1,4 +1,3 @@ -using static System.Array; using System.Globalization; using System.Reactive; using System.Reactive.Linq; @@ -25,8 +24,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NLog; -using Contract = Miningcore.Contracts.Contract; +using static System.Array; using static Miningcore.Util.ActionUtils; +using Contract = Miningcore.Contracts.Contract; namespace Miningcore.Blockchain.Conceal; @@ -145,7 +145,7 @@ private RpcResponse GetBlockTemplateFromJson(string js private async Task ShowDaemonSyncProgressAsync(CancellationToken ct) { var info = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); - + if(info.Status != "OK") { var lowestHeight = info.Height; @@ -163,7 +163,7 @@ private async Task UpdateNetworkStatsAsync(CancellationToken ct) { var coin = poolConfig.Template.As(); var info = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); - + if(info.Status != "OK") logger.Warn(() => $"Error(s) refreshing network stats..."); @@ -212,9 +212,9 @@ public override void Configure(PoolConfig pc, ClusterConfig cc) clusterConfig = cc; extraPoolConfig = pc.Extra.SafeExtensionDataAs(); coin = pc.Template.As(); - + var NetworkTypeOverride = !string.IsNullOrEmpty(extraPoolConfig?.NetworkTypeOverride) ? extraPoolConfig.NetworkTypeOverride : "testnet"; - + switch(NetworkTypeOverride.ToLower()) { case "mainnet": @@ -226,7 +226,7 @@ public override void Configure(PoolConfig pc, ClusterConfig cc) default: throw new PoolStartupException($"Unsupport net type '{NetworkTypeOverride}'", poolConfig.Id); } - + // extract standard daemon endpoints daemonEndpoints = pc.Daemons .Where(x => string.IsNullOrEmpty(x.Category)) @@ -363,7 +363,7 @@ private static JToken GetFrameAsJToken(byte[] frame) // find end of message type indicator var index = text.IndexOf(":"); - if (index == -1) + if(index == -1) return null; var json = text.Substring(index + 1); @@ -376,7 +376,7 @@ private static JToken GetFrameAsJToken(byte[] frame) protected override void ConfigureDaemons() { var jsonSerializerSettings = ctx.Resolve(); - + restClient = new SimpleRestClient(httpClientFactory, "http://" + daemonEndpoints.First().Host.ToString() + ":" + daemonEndpoints.First().Port.ToString() + "/"); rpc = new RpcClient(daemonEndpoints.First(), jsonSerializerSettings, messageBus, poolConfig.Id); @@ -390,7 +390,7 @@ protected override void ConfigureDaemons() protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) { logger.Debug(() => "Checking if conceald daemon is healthy..."); - + // test daemons var response = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); if(response.Status != "OK") @@ -398,13 +398,13 @@ protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) logger.Debug(() => $"conceald daemon did not responded..."); return false; } - + logger.Debug(() => $"{response.Status} - Incoming: {response.IncomingConnectionsCount} - Outgoing: {response.OutgoingConnectionsCount})"); - + if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { logger.Debug(() => "Checking if walletd daemon is healthy..."); - + // test wallet daemons //var response2 = await walletRpc.ExecuteAsync(logger, ConcealWalletCommands.GetAddress, ct); var request2 = new GetBalanceRequest @@ -413,7 +413,7 @@ protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) }; var response2 = await walletRpc.ExecuteAsync(logger, ConcealWalletCommands.GetBalance, ct, request2); - + if(response2.Error != null) logger.Debug(() => $"walletd daemon response: {response2.Error.Message} (Code {response2.Error.Code})"); @@ -426,15 +426,15 @@ protected override async Task AreDaemonsHealthyAsync(CancellationToken ct) protected override async Task AreDaemonsConnectedAsync(CancellationToken ct) { logger.Debug(() => "Checking if conceald daemon is connected..."); - + var response = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); - + if(response.Status != "OK") logger.Debug(() => $"conceald daemon is not connected..."); - + if(response.Status == "OK") logger.Debug(() => $"Peers connected - Incoming: {response.IncomingConnectionsCount} - Outgoing: {response.OutgoingConnectionsCount}"); - + return response.Status == "OK" && (response.OutgoingConnectionsCount + response.IncomingConnectionsCount) > 0; } @@ -442,7 +442,7 @@ protected override async Task AreDaemonsConnectedAsync(CancellationToken c protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); - + logger.Debug(() => "Checking if conceald daemon is synched..."); var syncPendingNotificationShown = false; @@ -457,11 +457,11 @@ protected override async Task EnsureDaemonsSynchedAsync(CancellationToken ct) var response = await rpc.ExecuteAsync(logger, ConcealCommands.GetBlockTemplate, ct, request); - + if(response.Error != null) logger.Debug(() => $"conceald daemon response: {response.Error.Message} (Code {response.Error.Code})"); - var isSynched = response.Error is not {Code: -9}; + var isSynched = response.Error is not { Code: -9 }; if(isSynched) { @@ -486,7 +486,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // coin config var coin = poolConfig.Template.As(); var infoResponse = await restClient.Get(ConcealConstants.DaemonRpcGetInfoLocation, ct); - + if(infoResponse.Status != "OK") throw new PoolStartupException($"Init RPC failed...", poolConfig.Id); @@ -516,7 +516,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(poolAddressBase58Prefix != coin.AddressPrefix) throw new PoolStartupException($"Invalid pool address prefix. Expected {coin.AddressPrefix}, got {poolAddressBase58Prefix}", poolConfig.Id); break; - + case ConcealNetworkType.Test: if(poolAddressBase58Prefix != coin.AddressPrefixTestnet) throw new PoolStartupException($"Invalid pool address prefix. Expected {coin.AddressPrefixTestnet}, got {poolAddressBase58Prefix}", poolConfig.Id); @@ -532,8 +532,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // Periodically update network stats Observable.Interval(TimeSpan.FromMinutes(1)) .Select(via => Observable.FromAsync(() => - Guard(()=> UpdateNetworkStatsAsync(ct), - ex=> logger.Error(ex)))) + Guard(() => UpdateNetworkStatsAsync(ct), + ex => logger.Error(ex)))) .Concat() .Subscribe(); @@ -607,7 +607,7 @@ protected virtual void SetupJobUpdates(CancellationToken ct) { var token = GetFrameAsJToken(msg[0].Read()); - if (token != null) + if(token != null) return token.Value("first_height").ToString(CultureInfo.InvariantCulture); // We just take the second frame's raw data and turn it into a hex string. diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs index e1a82b769..ac35801c5 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumJobManager.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Numerics; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using System.Text; @@ -14,19 +15,18 @@ using Miningcore.Extensions; using Miningcore.JsonRpc; using Miningcore.Messaging; +using Miningcore.Mining; using Miningcore.Notifications.Messages; +using Miningcore.Rpc; using Miningcore.Stratum; using Miningcore.Time; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NLog; +using static Miningcore.Util.ActionUtils; using Block = Miningcore.Blockchain.Ethereum.DaemonResponses.Block; using Contract = Miningcore.Contracts.Contract; using EC = Miningcore.Blockchain.Ethereum.EthCommands; -using static Miningcore.Util.ActionUtils; -using System.Reactive; -using Miningcore.Mining; -using Miningcore.Rpc; -using Newtonsoft.Json.Linq; namespace Miningcore.Blockchain.Ethereum; @@ -350,10 +350,10 @@ public override void Configure(PoolConfig pc, ClusterConfig cc) if(pc.EnableInternalStratum == true) { var coin = pc.Template.As(); - + // ensure dag location is configured string dagDir = null; - + if(!string.IsNullOrEmpty(extraPoolConfig?.DagDir)) { dagDir = Environment.ExpandEnvironmentVariables(extraPoolConfig.DagDir); @@ -466,13 +466,13 @@ private async Task SubmitShareAsync(StratumConnection worker, EthereumWorkerContext context, string workerName, EthereumJob job, string nonce, CancellationToken ct) { var coin = poolConfig.Template.As(); - + // validate & process switch(coin.Symbol) { case "ETC": var (shareEtchash, fullNonceHexEtchash, headerHashEtchash, mixHashEtchash) = await job.ProcessShareEtcHashAsync(worker, workerName, nonce, etchash, ct); - + // enrich share with common data shareEtchash.PoolId = poolConfig.Id; shareEtchash.NetworkDifficulty = BlockchainStats.NetworkDifficulty; @@ -493,11 +493,11 @@ private async Task SubmitShareAsync(StratumConnection worker, OnBlockFound(); } } - + return shareEtchash; case "UBIQ": var (shareUbqhash, fullNonceHexUbqhash, headerHashUbqhash, mixHashUbqhash) = await job.ProcessShareUbqHashAsync(worker, workerName, nonce, ubqhash, ct); - + // enrich share with common data shareUbqhash.PoolId = poolConfig.Id; shareUbqhash.NetworkDifficulty = BlockchainStats.NetworkDifficulty; @@ -518,11 +518,11 @@ private async Task SubmitShareAsync(StratumConnection worker, OnBlockFound(); } } - + return shareUbqhash; default: var (share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, workerName, nonce, ethash, ct); - + // enrich share with common data share.PoolId = poolConfig.Id; share.NetworkDifficulty = BlockchainStats.NetworkDifficulty; @@ -543,7 +543,7 @@ private async Task SubmitShareAsync(StratumConnection worker, OnBlockFound(); } } - + return share; } } @@ -641,8 +641,8 @@ protected override async Task PostStartInitAsync(CancellationToken ct) // Periodically update network stats Observable.Interval(TimeSpan.FromMinutes(10)) .Select(via => Observable.FromAsync(() => - Guard(()=> UpdateNetworkStatsAsync(ct), - ex=> logger.Error(ex)))) + Guard(() => UpdateNetworkStatsAsync(ct), + ex => logger.Error(ex)))) .Concat() .Subscribe(); @@ -658,7 +658,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) if(blockTemplate != null) { logger.Info(() => "Loading current DAG ..."); - + // setup dag file switch(coin.Symbol) { @@ -672,7 +672,7 @@ protected override async Task PostStartInitAsync(CancellationToken ct) await ethash.GetDagAsync(blockTemplate.Height, logger, ct); break; } - + logger.Info(() => "Loaded current DAG"); break; } @@ -698,7 +698,7 @@ protected virtual async Task SetupJobUpdates(CancellationToken ct) var endpointExtra = daemonEndpoints .Where(x => x.Extra.SafeExtensionDataAs() != null) - .Select(x=> Tuple.Create(x, x.Extra.SafeExtensionDataAs())) + .Select(x => Tuple.Create(x, x.Extra.SafeExtensionDataAs())) .FirstOrDefault(); if(endpointExtra?.Item2?.PortWs.HasValue == true) diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinConstants.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinConstants.cs new file mode 100644 index 000000000..8a810481a --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinConstants.cs @@ -0,0 +1,13 @@ +using System.Globalization; +using System.Numerics; + +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinConstants +{ + public const int EpochLength = 7500; + public static readonly Org.BouncyCastle.Math.BigInteger Diff1B = new Org.BouncyCastle.Math.BigInteger("00ff000000000000000000000000000000000000000000000000000000", 16); + public static readonly BigInteger Diff1 = BigInteger.Parse("00ff000000000000000000000000000000000000000000000000000000", NumberStyles.HexNumber); + public const int TargetPaddingLength = 32; + public const int ExtranoncePlaceHolderLength = 2; +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinExtraNonceProvider.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinExtraNonceProvider.cs new file mode 100644 index 000000000..a36aa095a --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinExtraNonceProvider.cs @@ -0,0 +1,8 @@ +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinExtraNonceProvider : ExtraNonceProviderBase +{ + public RavencoinExtraNonceProvider(string poolId, byte? clusterInstanceId) : base(poolId, RavencoinConstants.ExtranoncePlaceHolderLength, clusterInstanceId) + { + } +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinJob.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinJob.cs new file mode 100644 index 000000000..01e6f0bb5 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinJob.cs @@ -0,0 +1,265 @@ +using System.Globalization; +using System.Text; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Bitcoin.Configuration; +using Miningcore.Blockchain.Bitcoin.DaemonResponses; +using Miningcore.Configuration; +using Miningcore.Crypto; +using Miningcore.Crypto.Hashing.Kawpow; +using Miningcore.Extensions; +using Miningcore.Stratum; +using Miningcore.Time; +using Miningcore.Util; +using NBitcoin; +using NBitcoin.DataEncoders; +using NLog; +using Contract = Miningcore.Contracts.Contract; + +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinJobParams +{ + public ulong Height { get; init; } + public bool CleanJobs { get; set; } +} + +public class RavencoinJob : BitcoinJob +{ + private Cache kawpowHasher; + private new RavencoinJobParams jobParams; + + private byte[] SerializeHeader(Span coinbaseHash) + { + // build merkle-root + var merkleRoot = mt.WithFirst(coinbaseHash.ToArray()); + + // Build version + var version = BlockTemplate.Version; + +#pragma warning disable 618 + var blockHeader = new BlockHeader +#pragma warning restore 618 + { + Version = unchecked((int) version), + Bits = new Target(Encoders.Hex.DecodeData(BlockTemplate.Bits)), + HashPrevBlock = uint256.Parse(BlockTemplate.PreviousBlockhash), + HashMerkleRoot = new uint256(merkleRoot), + BlockTime = DateTimeOffset.FromUnixTimeSeconds(BlockTemplate.CurTime), + Nonce = BlockTemplate.Height + }; + + return blockHeader.ToBytes(); + } + + public (Share Share, string BlockHex) ProcessShareInternal(ILogger logger, + StratumConnection worker, ulong nonce, string inputHeaderHash, string mixHash) + { + var context = worker.ContextAs(); + var extraNonce1 = context.ExtraNonce1; + + // build coinbase + var coinbase = SerializeCoinbase(extraNonce1); + Span coinbaseHash = stackalloc byte[32]; + coinbaseHasher.Digest(coinbase, coinbaseHash); + + // hash block-header + var headerBytes = SerializeHeader(coinbaseHash); + Span headerHash = stackalloc byte[32]; + headerHasher.Digest(headerBytes, headerHash); + headerHash.Reverse(); + + var headerHashHex = headerHash.ToHexString(); + + if(headerHashHex != inputHeaderHash) + throw new StratumException(StratumError.MinusOne, $"bad header-hash"); + + if(!kawpowHasher.Compute(logger, (int) BlockTemplate.Height, headerHash.ToArray(), nonce, out var mixHashOut, out var resultBytes)) + throw new StratumException(StratumError.MinusOne, "bad hash"); + + if(mixHash != mixHashOut.ToHexString()) + throw new StratumException(StratumError.MinusOne, $"bad mix-hash"); + + resultBytes.ReverseInPlace(); + mixHashOut.ReverseInPlace(); + + var resultValue = new uint256(resultBytes); + var resultValueBig = resultBytes.AsSpan().ToBigInteger(); + // calc share-diff + var shareDiff = (double) new BigRational(RavencoinConstants.Diff1, resultValueBig) * shareMultiplier; + var stratumDifficulty = context.Difficulty; + var ratio = shareDiff / stratumDifficulty; + + // check if the share meets the much harder block difficulty (block candidate) + var isBlockCandidate = resultValue <= blockTargetValue; + + // test if share meets at least workers current difficulty + if(!isBlockCandidate && ratio < 0.99) + { + // check if share matched the previous difficulty from before a vardiff retarget + if(context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) + { + ratio = shareDiff / context.PreviousDifficulty.Value; + + if(ratio < 0.99) + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + + // use previous difficulty + stratumDifficulty = context.PreviousDifficulty.Value; + } + + else + throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); + } + + var result = new Share + { + BlockHeight = BlockTemplate.Height, + NetworkDifficulty = Difficulty, + Difficulty = stratumDifficulty / shareMultiplier, + }; + + if(!isBlockCandidate) + { + return (result, null); + } + + result.IsBlockCandidate = true; + result.BlockHash = resultBytes.ReverseInPlace().ToHexString(); + + var blockBytes = SerializeBlock(headerBytes, coinbase, nonce, mixHashOut); + var blockHex = blockBytes.ToHexString(); + + return (result, blockHex); + } + + private byte[] SerializeCoinbase(string extraNonce1) + { + var extraNonce1Bytes = extraNonce1.HexToByteArray(); + + using var stream = new MemoryStream(); + { + stream.Write(coinbaseInitial); + stream.Write(extraNonce1Bytes); + stream.Write(coinbaseFinal); + + return stream.ToArray(); + } + } + + private byte[] SerializeBlock(byte[] header, byte[] coinbase, ulong nonce, byte[] mixHash) + { + var rawTransactionBuffer = BuildRawTransactionBuffer(); + var transactionCount = (uint) BlockTemplate.Transactions.Length + 1; // +1 for prepended coinbase tx + + using var stream = new MemoryStream(); + { + var bs = new BitcoinStream(stream, true); + + bs.ReadWrite(ref header); + bs.ReadWrite(ref nonce); + bs.ReadWrite(ref mixHash); + bs.ReadWriteAsVarInt(ref transactionCount); + + bs.ReadWrite(ref coinbase); + bs.ReadWrite(ref rawTransactionBuffer); + + return stream.ToArray(); + } + } + + #region API-Surface + + public void Init(BlockTemplate blockTemplate, string jobId, + PoolConfig pc, BitcoinPoolConfigExtra extraPoolConfig, + ClusterConfig cc, IMasterClock clock, + IDestination poolAddressDestination, Network network, + bool isPoS, double shareMultiplier, IHashAlgorithm coinbaseHasher, + IHashAlgorithm headerHasher, Cache kawpowHasher) + { + Contract.RequiresNonNull(blockTemplate); + Contract.RequiresNonNull(pc); + Contract.RequiresNonNull(cc); + Contract.RequiresNonNull(clock); + Contract.RequiresNonNull(poolAddressDestination); + Contract.RequiresNonNull(coinbaseHasher); + Contract.RequiresNonNull(headerHasher); + Contract.RequiresNonNull(kawpowHasher); + Contract.Requires(!string.IsNullOrEmpty(jobId)); + + this.coin = pc.Template.As(); + this.txVersion = coin.CoinbaseTxVersion; + this.network = network; + this.clock = clock; + this.poolAddressDestination = poolAddressDestination; + this.BlockTemplate = blockTemplate; + this.JobId = jobId; + + var coinbaseString = !string.IsNullOrEmpty(cc.PaymentProcessing?.CoinbaseString) ? + cc.PaymentProcessing?.CoinbaseString.Trim() : "Miningcore"; + + if(!string.IsNullOrEmpty(coinbaseString)) + this.scriptSigFinalBytes = new Script(Op.GetPushOp(Encoding.UTF8.GetBytes(coinbaseString))).ToBytes(); + + this.Difficulty = new Target(System.Numerics.BigInteger.Parse(BlockTemplate.Target, NumberStyles.HexNumber)).Difficulty; + + this.extraNoncePlaceHolderLength = RavencoinConstants.ExtranoncePlaceHolderLength; + this.shareMultiplier = shareMultiplier; + + this.coinbaseHasher = coinbaseHasher; + this.headerHasher = headerHasher; + this.kawpowHasher = kawpowHasher; + + if(!string.IsNullOrEmpty(BlockTemplate.Target)) + this.blockTargetValue = new uint256(BlockTemplate.Target); + else + { + var tmp = new Target(BlockTemplate.Bits.HexToByteArray()); + this.blockTargetValue = tmp.ToUInt256(); + } + + BuildMerkleBranches(); + BuildCoinbase(); + + this.jobParams = new RavencoinJobParams + { + Height = BlockTemplate.Height, + CleanJobs = false + }; + } + + public new object GetJobParams(bool isNew) + { + jobParams.CleanJobs = isNew; + return jobParams; + } + + public void PrepareWorkerJob(RavencoinWorkerJob workerJob, out string headerHash) + { + workerJob.Job = this; + workerJob.Height = BlockTemplate.Height; + workerJob.Bits = BlockTemplate.Bits; + workerJob.SeedHash = kawpowHasher.SeedHash.ToHexString(); + headerHash = CreateHeaderHash(workerJob); + } + + private string CreateHeaderHash(RavencoinWorkerJob workerJob) + { + var headerHasher = coin.HeaderHasherValue; + var coinbaseHasher = coin.CoinbaseHasherValue; + var extraNonce1 = workerJob.ExtraNonce1; + + var coinbase = SerializeCoinbase(extraNonce1); + Span coinbaseHash = stackalloc byte[32]; + coinbaseHasher.Digest(coinbase, coinbaseHash); + + var headerBytes = SerializeHeader(coinbaseHash); + Span headerHash = stackalloc byte[32]; + headerHasher.Digest(headerBytes, headerHash); + headerHash.Reverse(); + + return headerHash.ToHexString(); + } + + + #endregion // API-Surface +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinJobManager.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinJobManager.cs new file mode 100644 index 000000000..984f16196 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinJobManager.cs @@ -0,0 +1,313 @@ +using Autofac; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Blockchain.Bitcoin.Configuration; +using Miningcore.Blockchain.Bitcoin.DaemonResponses; +using Miningcore.Configuration; +using Miningcore.Contracts; +using Miningcore.Crypto; +using Miningcore.Extensions; +using Miningcore.JsonRpc; +using Miningcore.Messaging; +using Miningcore.Rpc; +using Miningcore.Stratum; +using Miningcore.Time; +using Newtonsoft.Json; +using NLog; + +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinJobManager : BitcoinJobManagerBase +{ + public RavencoinJobManager( + IComponentContext ctx, + IMasterClock clock, + IMessageBus messageBus, + IExtraNonceProvider extraNonceProvider) : + base(ctx, clock, messageBus, extraNonceProvider) + { + } + + private RavencoinTemplate coin; + + private async Task> GetBlockTemplateAsync(CancellationToken ct) + { + var result = await rpc.ExecuteAsync(logger, + BitcoinCommands.GetBlockTemplate, ct, extraPoolConfig?.GBTArgs ?? (object) GetBlockTemplateParams()); + + return result; + } + + private RpcResponse GetBlockTemplateFromJson(string json) + { + var result = JsonConvert.DeserializeObject(json); + + return new RpcResponse(result!.ResultAs()); + } + + private static RavencoinJob CreateJob() + { + return new RavencoinJob(); + } + + private double ShareMultiplier => coin.ShareMultiplier; + + + protected override void PostChainIdentifyConfigure() + { + base.PostChainIdentifyConfigure(); + + if(poolConfig.EnableInternalStratum == false || coin.HeaderHasherValue is not IHashAlgorithmInit hashInit) + return; + + if(!hashInit.DigestInit(poolConfig)) + logger.Error(() => $"{hashInit.GetType().Name} initialization failed"); + } + + protected override async Task PostStartInitAsync(CancellationToken ct) + { + if(poolConfig.EnableInternalStratum == true) + { + // make sure we have a current light cache + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5)); + + do + { + var blockTemplate = await GetBlockTemplateAsync(ct); + + if(blockTemplate?.Response != null) + { + logger.Info(() => "Loading current light cache ..."); + + await coin.KawpowHasher.GetCacheAsync(logger, (int) blockTemplate.Response.Height); + + logger.Info(() => "Loaded current light cache"); + break; + } + + logger.Info(() => "Waiting for first valid block template"); + } while(await timer.WaitForNextTickAsync(ct)); + } + await base.PostStartInitAsync(ct); + } + + protected override async Task<(bool IsNew, bool Force)> UpdateJob(CancellationToken ct, bool forceUpdate, string via = null, string json = null) + { + try + { + if(forceUpdate) + lastJobRebroadcast = clock.Now; + + var response = string.IsNullOrEmpty(json) ? + await GetBlockTemplateAsync(ct) : + GetBlockTemplateFromJson(json); + + // may happen if daemon is currently not connected to peers + if(response.Error != null) + { + logger.Warn(() => $"Unable to update job. Daemon responded with: {response.Error.Message} Code {response.Error.Code}"); + return (false, forceUpdate); + } + + var blockTemplate = response.Response; + var job = currentJob; + + var isNew = job == null || + (blockTemplate != null && + (job.BlockTemplate?.PreviousBlockhash != blockTemplate.PreviousBlockhash || + blockTemplate.Height > job.BlockTemplate?.Height)); + + if(isNew) + messageBus.NotifyChainHeight(poolConfig.Id, blockTemplate.Height, poolConfig.Template); + + if(isNew || forceUpdate) + { + job = CreateJob(); + + var blockHeight = blockTemplate?.Height ?? currentJob.BlockTemplate.Height; + + var kawpowHasher = await coin.KawpowHasher.GetCacheAsync(logger, (int) blockHeight); + + job.Init(blockTemplate, NextJobId(), + poolConfig, extraPoolConfig, clusterConfig, clock, poolAddressDestination, network, isPoS, + ShareMultiplier, coin.CoinbaseHasherValue, coin.HeaderHasherValue, kawpowHasher); + + if(isNew) + { + if(via != null) + logger.Info(() => $"Detected new block {blockTemplate.Height} [{via}]"); + else + logger.Info(() => $"Detected new block {blockTemplate.Height}"); + + // update stats + BlockchainStats.LastNetworkBlockTime = clock.Now; + BlockchainStats.BlockHeight = blockTemplate.Height; + BlockchainStats.NetworkDifficulty = job.Difficulty; + BlockchainStats.NextNetworkTarget = blockTemplate.Target; + BlockchainStats.NextNetworkBits = blockTemplate.Bits; + } + + else + { + if(via != null) + logger.Debug(() => $"Template update {blockTemplate?.Height} [{via}]"); + else + logger.Debug(() => $"Template update {blockTemplate?.Height}"); + } + + currentJob = job; + } + + return (isNew, forceUpdate); + } + + catch(OperationCanceledException) + { + // ignored + } + + catch(Exception ex) + { + logger.Error(ex, () => $"Error during {nameof(UpdateJob)}"); + } + + return (false, forceUpdate); + } + + protected override object GetJobParamsForStratum(bool isNew) + { + var job = currentJob; + return job?.GetJobParams(isNew); + } + + #region API-Surface + + public override void Configure(PoolConfig pc, ClusterConfig cc) + { + coin = pc.Template.As(); + extraPoolConfig = pc.Extra.SafeExtensionDataAs(); + extraPoolPaymentProcessingConfig = pc.PaymentProcessing?.Extra?.SafeExtensionDataAs(); + + if(extraPoolConfig?.MaxActiveJobs.HasValue == true) + maxActiveJobs = extraPoolConfig.MaxActiveJobs.Value; + + hasLegacyDaemon = extraPoolConfig?.HasLegacyDaemon == true; + + if(pc.EnableInternalStratum == true) + { + coin.KawpowHasher.Setup(3); + } + + base.Configure(pc, cc); + } + + public object[] GetSubscriberData(StratumConnection worker) + { + Contract.RequiresNonNull(worker); + + var context = worker.ContextAs(); + + // assign unique ExtraNonce1 to worker (miner) + context.ExtraNonce1 = extraNonceProvider.Next(); + + // setup response data + var responseData = new object[] + { + context.ExtraNonce1, + }; + + return responseData; + } + + public void PrepareWorkerJob(RavencoinWorkerJob workerJob, out string headerHash) + { + headerHash = null; + + var job = currentJob; + + if(job != null) + { + lock(job) + { + job.PrepareWorkerJob(workerJob, out headerHash); + } + } + } + + public async ValueTask SubmitShareAsync(StratumConnection worker, object submission, + CancellationToken ct) + { + Contract.RequiresNonNull(worker); + Contract.RequiresNonNull(submission); + + if(submission is not object[] submitParams) + throw new StratumException(StratumError.Other, "invalid params"); + + var context = worker.ContextAs(); + + // extract params + var workerValue = (submitParams[0] as string)?.Trim(); + var jobId = submitParams[1] as string; + var nonce = (submitParams[2] as string)?.Substring(2); + var headerHash = (submitParams[3] as string)?.Substring(2); + var mixHash = (submitParams[4] as string)?.Substring(2); + + if(string.IsNullOrEmpty(workerValue)) + throw new StratumException(StratumError.Other, "missing or invalid workername"); + + RavencoinWorkerJob job; + + lock(context) + { + if((job = context.FindJob(jobId)) == null) + throw new StratumException(StratumError.MinusOne, "invalid jobid"); + } + + if(job == null) + throw new StratumException(StratumError.JobNotFound, "job not found"); + + + // validate & process + var (share, blockHex) = job.ProcessShare(logger, worker, nonce, headerHash, mixHash); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.IpAddress = worker.RemoteEndpoint.Address.ToString(); + share.Miner = context.Miner; + share.Worker = context.Worker; + share.UserAgent = context.UserAgent; + share.Source = clusterConfig.ClusterName; + share.Created = clock.Now; + + // if block candidate, submit & check if accepted by network + if(share.IsBlockCandidate) + { + logger.Info(() => $"Submitting block {share.BlockHeight} [{share.BlockHash}]"); + + var acceptResponse = await SubmitBlockAsync(share, blockHex, ct); + + // is it still a block candidate? + share.IsBlockCandidate = acceptResponse.Accepted; + + if(share.IsBlockCandidate) + { + logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {context.Miner}"); + + OnBlockFound(); + + // persist the coinbase transaction-hash to allow the payment processor + // to verify later on that the pool has received the reward for the block + share.TransactionConfirmationData = acceptResponse.CoinbaseTx; + } + + else + { + // clear fields that no longer apply + share.TransactionConfirmationData = null; + } + } + + return share; + } + + #endregion // API-Surface +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinPool.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinPool.cs new file mode 100644 index 000000000..70c675224 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinPool.cs @@ -0,0 +1,395 @@ +using System.Globalization; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Autofac; +using AutoMapper; +using Microsoft.IO; +using Miningcore.Blockchain.Bitcoin; +using Miningcore.Configuration; +using Miningcore.JsonRpc; +using Miningcore.Messaging; +using Miningcore.Mining; +using Miningcore.Nicehash; +using Miningcore.Notifications.Messages; +using Miningcore.Persistence; +using Miningcore.Persistence.Repositories; +using Miningcore.Stratum; +using Miningcore.Time; +using Newtonsoft.Json; +using static Miningcore.Util.ActionUtils; + +namespace Miningcore.Blockchain.Ravencoin; + +[CoinFamily(CoinFamily.Ravencoin)] +public class RavencoinPool : PoolBase +{ + public RavencoinPool(IComponentContext ctx, + JsonSerializerSettings serializerSettings, + IConnectionFactory cf, + IStatsRepository statsRepo, + IMapper mapper, + IMasterClock clock, + IMessageBus messageBus, + RecyclableMemoryStreamManager rmsm, + NicehashService nicehashService) : + base(ctx, serializerSettings, cf, statsRepo, mapper, clock, messageBus, rmsm, nicehashService) + { + } + + private RavencoinJobParams currentJobParams; + private long currentJobId; + private RavencoinJobManager manager; + private RavencoinTemplate coin; + + protected virtual async Task OnSubscribeAsync(StratumConnection connection, Timestamped tsRequest) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + var requestParams = request.ParamsAs(); + + var data = new object[] + { + new object[] + { + new object[] { RavencoinStratumMethods.SetDifficulty, connection.ConnectionId }, + new object[] { RavencoinStratumMethods.MiningNotify, connection.ConnectionId } + } + } + .Concat(manager.GetSubscriberData(connection)) + .ToArray(); + + await connection.RespondAsync(data, request.Id); + + // setup worker context + context.IsSubscribed = true; + context.UserAgent = requestParams.FirstOrDefault()?.Trim(); + + // Nicehash support + var nicehashDiff = await GetNicehashStaticMinDiff(context, coin.Name, coin.GetAlgorithmName()); + + if(nicehashDiff.HasValue) + { + logger.Info(() => $"[{connection.ConnectionId}] Nicehash detected. Using API supplied difficulty of {nicehashDiff.Value}"); + + context.VarDiff = null; // disable vardiff + context.SetDifficulty(nicehashDiff.Value); + } + + var minerJobParams = CreateWorkerJob(connection, currentJobParams.CleanJobs); + // send intial update + await connection.NotifyAsync(RavencoinStratumMethods.SetDifficulty, new object[] { RavencoinUtils.EncodeTarget(context.Difficulty) }); + await connection.NotifyAsync(RavencoinStratumMethods.MiningNotify, minerJobParams); + } + + protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + var context = connection.ContextAs(); + var requestParams = request.ParamsAs(); + var workerValue = requestParams?.Length > 0 ? requestParams[0] : null; + var password = requestParams?.Length > 1 ? requestParams[1] : null; + var passParts = password?.Split(PasswordControlVarsSeparator); + + // extract worker/miner + var split = workerValue?.Split('.'); + var minerName = split?.FirstOrDefault()?.Trim(); + var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; + + // assumes that minerName is an address + context.IsAuthorized = await manager.ValidateAddressAsync(minerName, ct); + context.Miner = minerName; + context.Worker = workerName; + + if(context.IsAuthorized) + { + // respond + await connection.RespondAsync(context.IsAuthorized, request.Id); + + // log association + logger.Info(() => $"[{connection.ConnectionId}] Authorized worker {workerValue}"); + + // extract control vars from password + var staticDiff = GetStaticDiffFromPassparts(passParts); + + // Static diff + if(staticDiff.HasValue && + (context.VarDiff != null && staticDiff.Value >= context.VarDiff.Config.MinDiff || + context.VarDiff == null && staticDiff.Value > context.Difficulty)) + { + context.VarDiff = null; // disable vardiff + context.SetDifficulty(staticDiff.Value); + + logger.Info(() => $"[{connection.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); + + await connection.NotifyAsync(RavencoinStratumMethods.SetDifficulty, new object[] { RavencoinUtils.EncodeTarget(context.Difficulty) }); + } + } + + else + { + await connection.RespondErrorAsync(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized); + + if(clusterConfig?.Banning?.BanOnLoginFailure is null or true) + { + // issue short-time ban if unauthorized to prevent DDos on daemon (validateaddress RPC) + logger.Info(() => $"[{connection.ConnectionId}] Banning unauthorized worker {minerName} for {loginFailureBanTimeout.TotalSeconds} sec"); + + banManager.Ban(connection.RemoteEndpoint.Address, loginFailureBanTimeout); + + Disconnect(connection); + } + } + } + + private object CreateWorkerJob(StratumConnection connection, bool update) + { + var context = connection.ContextAs(); + var job = new RavencoinWorkerJob(NextJobId(), context.ExtraNonce1); + + manager.PrepareWorkerJob(job, out var headerHash); + + var result = new object[] + { + job.Id, + headerHash, + job.SeedHash, + RavencoinUtils.EncodeTarget(context.Difficulty), + update, + job.Height, + job.Bits + }; + + // update context + lock(context) + { + context.AddJob(job); + } + + return result; + } + + private string NextJobId() + { + return Interlocked.Increment(ref currentJobId).ToString(CultureInfo.InvariantCulture); + } + + protected virtual async Task OnSubmitAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + var context = connection.ContextAs(); + + try + { + if(request.Id == null) + throw new StratumException(StratumError.MinusOne, "missing request id"); + + // check age of submission (aged submissions are usually caused by high server load) + var requestAge = clock.Now - tsRequest.Timestamp.UtcDateTime; + + if(requestAge > maxShareAge) + { + logger.Warn(() => $"[{connection.ConnectionId}] Dropping stale share submission request (server overloaded?)"); + return; + } + + // check worker state + context.LastActivity = clock.Now; + + // validate worker + if(!context.IsAuthorized) + throw new StratumException(StratumError.UnauthorizedWorker, "unauthorized worker"); + else if(!context.IsSubscribed) + throw new StratumException(StratumError.NotSubscribed, "not subscribed"); + + var requestParams = request.ParamsAs(); + + // submit + var share = await manager.SubmitShareAsync(connection, requestParams, ct); + await connection.RespondAsync(true, request.Id); + + // publish + messageBus.SendMessage(share); + + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); + + logger.Info(() => $"[{connection.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty * coin.ShareMultiplier, 3)}"); + + // update pool stats + if(share.IsBlockCandidate) + poolStats.LastPoolBlockTime = clock.Now; + + // update client stats + context.Stats.ValidShares++; + + await UpdateVarDiffAsync(connection, false, ct); + } + + catch(StratumException ex) + { + // telemetry + PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, false); + + // update client stats + context.Stats.InvalidShares++; + logger.Info(() => $"[{connection.ConnectionId}] Share rejected: {ex.Message} [{context.UserAgent}]"); + + // banning + ConsiderBan(connection, context, poolConfig.Banning); + + throw; + } + } + + protected virtual async Task OnNewJobAsync(object job) + { + logger.Info(() => $"Broadcasting jobs"); + + currentJobParams = job as RavencoinJobParams; + + await Guard(() => ForEachMinerAsync(async (connection, _) => + { + var context = connection.ContextAs(); + + var minerJobParams = CreateWorkerJob(connection, currentJobParams.CleanJobs); + + if(context.ApplyPendingDifficulty()) + await connection.NotifyAsync(RavencoinStratumMethods.SetDifficulty, new object[] { RavencoinUtils.EncodeTarget(context.Difficulty) }); + + // send job + await connection.NotifyAsync(RavencoinStratumMethods.MiningNotify, minerJobParams); + })); + } + + public override double HashrateFromShares(double shares, double interval) + { + var multiplier = BitcoinConstants.Pow2x32; + var result = shares * multiplier / interval; + + if(coin.HashrateMultiplier.HasValue) + result *= coin.HashrateMultiplier.Value; + + return result; + } + + public override double ShareMultiplier => coin.ShareMultiplier; + + #region Overrides + + public override void Configure(PoolConfig pc, ClusterConfig cc) + { + coin = pc.Template.As(); + + base.Configure(pc, cc); + } + + protected override async Task SetupJobManager(CancellationToken ct) + { + manager = ctx.Resolve( + new TypedParameter(typeof(IExtraNonceProvider), new RavencoinExtraNonceProvider(poolConfig.Id, clusterConfig.InstanceId))); + + manager.Configure(poolConfig, clusterConfig); + + await manager.StartAsync(ct); + + if(poolConfig.EnableInternalStratum == true) + { + disposables.Add(manager.Jobs + .Select(job => Observable.FromAsync(() => + Guard(() => OnNewJobAsync(job), + ex => logger.Debug(() => $"{nameof(OnNewJobAsync)}: {ex.Message}")))) + .Concat() + .Subscribe(_ => { }, ex => + { + logger.Debug(ex, nameof(OnNewJobAsync)); + })); + + // start with initial blocktemplate + await manager.Jobs.Take(1).ToTask(ct); + } + + else + { + // keep updating NetworkStats + disposables.Add(manager.Jobs.Subscribe()); + } + } + + protected override async Task InitStatsAsync(CancellationToken ct) + { + await base.InitStatsAsync(ct); + + blockchainStats = manager.BlockchainStats; + } + + protected override WorkerContextBase CreateWorkerContext() + { + return new RavencoinWorkerContext(); + } + + protected override async Task OnRequestAsync(StratumConnection connection, + Timestamped tsRequest, CancellationToken ct) + { + var request = tsRequest.Value; + + try + { + switch(request.Method) + { + case RavencoinStratumMethods.Subscribe: + await OnSubscribeAsync(connection, tsRequest); + break; + + case RavencoinStratumMethods.Authorize: + await OnAuthorizeAsync(connection, tsRequest, ct); + break; + + case RavencoinStratumMethods.SubmitShare: + await OnSubmitAsync(connection, tsRequest, ct); + break; + + case BitcoinStratumMethods.GetTransactions: + // ignored + break; + + case BitcoinStratumMethods.MiningMultiVersion: + // ignored + break; + + default: + logger.Debug(() => $"[{connection.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); + + await connection.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); + break; + } + } + + catch(StratumException ex) + { + await connection.RespondErrorAsync(ex.Code, ex.Message, request.Id, false); + } + } + + protected override async Task OnVarDiffUpdateAsync(StratumConnection connection, double newDiff, CancellationToken ct) + { + await base.OnVarDiffUpdateAsync(connection, newDiff, ct); + + if(connection.Context.ApplyPendingDifficulty()) + { + var minerJobParams = CreateWorkerJob(connection, currentJobParams.CleanJobs); + await connection.NotifyAsync(RavencoinStratumMethods.SetDifficulty, new object[] { RavencoinUtils.EncodeTarget(connection.Context.Difficulty) }); + await connection.NotifyAsync(RavencoinStratumMethods.MiningNotify, minerJobParams); + } + } + + #endregion // Overrides +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinStratumMethods.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinStratumMethods.cs new file mode 100644 index 000000000..39efa4f28 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinStratumMethods.cs @@ -0,0 +1,29 @@ +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinStratumMethods +{ + /// + /// Used to subscribe to work from a server, required before all other communication. + /// + public const string Subscribe = "mining.subscribe"; + + /// + /// Used to authorize a worker, required before any shares can be submitted. + /// + public const string Authorize = "mining.authorize"; + + /// + /// Used to push new work to the miner. Previous work should be aborted if Clean Jobs = true! + /// + public const string MiningNotify = "mining.notify"; + + /// + /// Used to submit shares + /// + public const string SubmitShare = "mining.submit"; + + /// + /// Used to signal the miner to stop submitting shares under the new difficulty. + /// + public const string SetDifficulty = "mining.set_target"; +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinUtils.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinUtils.cs new file mode 100644 index 000000000..035906ffd --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinUtils.cs @@ -0,0 +1,29 @@ +using Miningcore.Extensions; +using Org.BouncyCastle.Math; + +namespace Miningcore.Blockchain.Ravencoin; + +public static class RavencoinUtils +{ + public static string EncodeTarget(double difficulty) + { + string result; + var diff = BigInteger.ValueOf((long) (difficulty * 255d)); + var quotient = RavencoinConstants.Diff1B.Divide(diff).Multiply(BigInteger.ValueOf(255)); + var bytes = quotient.ToByteArray().AsSpan(); + Span padded = stackalloc byte[RavencoinConstants.TargetPaddingLength]; + + var padLength = RavencoinConstants.TargetPaddingLength - bytes.Length; + + if(padLength > 0) + { + bytes.CopyTo(padded.Slice(padLength, bytes.Length)); + result = padded.ToHexString(0, RavencoinConstants.TargetPaddingLength); + } + + else + result = bytes.ToHexString(0, RavencoinConstants.TargetPaddingLength); + + return result; + } +} \ No newline at end of file diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerContext.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerContext.cs new file mode 100644 index 000000000..a904e2fc2 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerContext.cs @@ -0,0 +1,36 @@ +using Miningcore.Mining; + +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinWorkerContext : WorkerContextBase +{ + /// + /// Usually a wallet address + /// + public string Miner { get; set; } + + /// + /// Arbitrary worker identififer for miners using multiple rigs + /// + public string Worker { get; set; } + + /// + /// Unique value assigned per worker + /// + public string ExtraNonce1 { get; set; } + + private List ValidJobs { get; } = new(); + + public void AddJob(RavencoinWorkerJob job) + { + ValidJobs.Insert(0, job); + + while(ValidJobs.Count > 4) + ValidJobs.RemoveAt(ValidJobs.Count - 1); + } + + public RavencoinWorkerJob FindJob(string jobId) + { + return ValidJobs.FirstOrDefault(x => x.Id == jobId); + } +} diff --git a/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerJob.cs b/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerJob.cs new file mode 100644 index 000000000..1ec403741 --- /dev/null +++ b/src/Miningcore/Blockchain/Ravencoin/RavencoinWorkerJob.cs @@ -0,0 +1,65 @@ +using System.Collections.Concurrent; +using System.Globalization; +using System.Text; +using Miningcore.Stratum; +using NLog; +using Contract = Miningcore.Contracts.Contract; + +namespace Miningcore.Blockchain.Ravencoin; + +public class RavencoinWorkerJob +{ + public RavencoinWorkerJob(string jobId, string extraNonce1) + { + Id = jobId; + ExtraNonce1 = extraNonce1; + } + + public string Id { get; } + public RavencoinJob Job { get; set; } + public uint Height { get; set; } + public string ExtraNonce1 { get; set; } + public string Bits { get; set; } + public string SeedHash { get; set; } + + private readonly ConcurrentDictionary submissions = new(StringComparer.OrdinalIgnoreCase); + + private bool RegisterSubmit(string nonce, string headerHash, string mixHash) + { + var key = new StringBuilder() + .Append(nonce) // lowercase as we don't want to accept case-sensitive values as valid. + .Append(headerHash) + .Append(mixHash) + .ToString(); + + return submissions.TryAdd(key, true); + } + + public (Share Share, string BlockHex) ProcessShare(ILogger logger, StratumConnection worker, string nonce, string headerHash, string mixHash) + { + Contract.RequiresNonNull(worker); + Contract.Requires(!string.IsNullOrEmpty(nonce)); + + var context = worker.ContextAs(); + + // mixHash + if(mixHash.Length != 64) + throw new StratumException(StratumError.Other, $"incorrect size of mixHash: {mixHash}"); + + // validate nonce + if(nonce.Length != 16) + throw new StratumException(StratumError.Other, $"incorrect size of nonce: {nonce}"); + + // check if nonce is within range + if(nonce.IndexOf(context.ExtraNonce1[0..4], StringComparison.OrdinalIgnoreCase) != 0) + throw new StratumException(StratumError.Other, $"nonce out of range: {nonce}"); + + // dupe check + if(!RegisterSubmit(nonce, headerHash, mixHash)) + throw new StratumException(StratumError.DuplicateShare, "duplicate share"); + + var nonceLong = ulong.Parse(nonce, NumberStyles.HexNumber); + + return Job.ProcessShareInternal(logger, worker, nonceLong, headerHash, mixHash); + } +} diff --git a/src/Miningcore/Configuration/ClusterConfig.cs b/src/Miningcore/Configuration/ClusterConfig.cs index 9392d47b4..e2ed4999f 100644 --- a/src/Miningcore/Configuration/ClusterConfig.cs +++ b/src/Miningcore/Configuration/ClusterConfig.cs @@ -2,6 +2,9 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using AspNetCoreRateLimit; +using Microsoft.AspNetCore.Identity; +using Miningcore.Crypto.Hashing.Kawpow; +using Miningcore.Native; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; @@ -33,6 +36,9 @@ public enum CoinFamily [EnumMember(Value = "ergo")] Ergo, + + [EnumMember(Value = "ravencoin")] + Ravencoin, } public abstract partial class CoinTemplate @@ -136,6 +142,7 @@ public abstract partial class CoinTemplate {CoinFamily.Cryptonote, typeof(CryptonoteCoinTemplate)}, {CoinFamily.Ethereum, typeof(EthereumCoinTemplate)}, {CoinFamily.Ergo, typeof(ErgoCoinTemplate)}, + {CoinFamily.Ravencoin, typeof(RavencoinTemplate)}, }; } @@ -224,6 +231,11 @@ public class BitcoinNetworkParams public string BlockSerializer { get; set; } } +public partial class RavencoinTemplate : BitcoinTemplate +{ + public EthashLight KawpowHasher { get; set; } +} + public enum EquihashSubfamily { [EnumMember(Value = "none")] diff --git a/src/Miningcore/Configuration/ClusterConfigExtensions.cs b/src/Miningcore/Configuration/ClusterConfigExtensions.cs index d7c783306..45df5c45b 100644 --- a/src/Miningcore/Configuration/ClusterConfigExtensions.cs +++ b/src/Miningcore/Configuration/ClusterConfigExtensions.cs @@ -84,6 +84,24 @@ public override string GetAlgorithmName() #endregion } +public partial class RavencoinTemplate +{ + public RavencoinTemplate() : base() + { + KawpowHasher = new Crypto.Hashing.Kawpow.EthashLight(); + } + + #region Overrides of CoinTemplate + + public override string GetAlgorithmName() + { + return KawpowHasher.AlgoName; + } + + #endregion +} + + public partial class EquihashCoinTemplate { public partial class EquihashNetworkParams @@ -152,11 +170,11 @@ public partial class ConcealCoinTemplate public override string GetAlgorithmName() { -// switch(Hash) -// { -// case CryptonightHashType.RandomX: -// return "RandomX"; -// } + // switch(Hash) + // { + // case CryptonightHashType.RandomX: + // return "RandomX"; + // } return Hash.ToString(); } @@ -170,11 +188,11 @@ public partial class CryptonoteCoinTemplate public override string GetAlgorithmName() { -// switch(Hash) -// { -// case CryptonightHashType.RandomX: -// return "RandomX"; -// } + // switch(Hash) + // { + // case CryptonightHashType.RandomX: + // return "RandomX"; + // } return Hash.ToString(); } diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/Sha256DT.cs b/src/Miningcore/Crypto/Hashing/Algorithms/Sha256DT.cs index be4824fe3..1ce6d9fa1 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/Sha256DT.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/Sha256DT.cs @@ -10,9 +10,9 @@ public void Digest(ReadOnlySpan data, Span result, params object[] e { Contract.Requires(result.Length >= 32); - fixed (byte* input = data) + fixed(byte* input = data) { - fixed (byte* output = result) + fixed(byte* output = result) { Multihash.sha256dt(input, output); } diff --git a/src/Miningcore/Crypto/Hashing/Kawpow/Cache.cs b/src/Miningcore/Crypto/Hashing/Kawpow/Cache.cs new file mode 100644 index 000000000..943f3277b --- /dev/null +++ b/src/Miningcore/Crypto/Hashing/Kawpow/Cache.cs @@ -0,0 +1,95 @@ +using System.Diagnostics; +using Miningcore.Blockchain.Ravencoin; +using Miningcore.Contracts; +using Miningcore.Extensions; +using Miningcore.Messaging; +using Miningcore.Native; +using Miningcore.Notifications.Messages; +using NLog; + +namespace Miningcore.Crypto.Hashing.Kawpow; + +public class Cache : IDisposable +{ + public Cache(int epoch) + { + Epoch = epoch; + LastUsed = DateTime.Now; + } + + private IntPtr handle = IntPtr.Zero; + private bool isGenerated = false; + private readonly object genLock = new(); + internal static IMessageBus messageBus; + public int Epoch { get; } + public byte[] SeedHash { get; set; } + public DateTime LastUsed { get; set; } + + public void Dispose() + { + if(handle != IntPtr.Zero) + { + KawPow.DestroyContext(handle); + handle = IntPtr.Zero; + } + } + + public async Task GenerateAsync(ILogger logger) + { + await Task.Run(() => + { + lock(genLock) + { + if(!isGenerated) + { + + var started = DateTime.Now; + logger.Debug(() => $"Generating cache for epoch {Epoch}"); + + handle = KawPow.CreateContext(Epoch); + + logger.Debug(() => $"Done generating cache for epoch {Epoch} after {DateTime.Now - started}"); + isGenerated = true; + + // get the seed hash for this epoch + var res = KawPow.calculate_epoch_seed(Epoch); + SeedHash = res.bytes; + logger.Info(() => $"Seed hash for epoch {Epoch} is {SeedHash.ToHexString()}"); + } + } + }); + } + + public unsafe bool Compute(ILogger logger, int blockNumber, byte[] hash, ulong nonce, out byte[] mixDigest, out byte[] result) + { + Contract.RequiresNonNull(hash); + + var sw = Stopwatch.StartNew(); + + mixDigest = null; + result = null; + + var value = new KawPow.Ethash_result(); + + var inputHash = new KawPow.Ethash_hash256(); + inputHash.bytes = hash; + + fixed(byte* input = hash) + { + value = KawPow.hash(handle, blockNumber, ref inputHash, nonce); + } + + if(value.final_hash.bytes == null) + { + logger.Error(() => $"KawPow.hash returned null"); + return false; + } + + mixDigest = value.mix_hash.bytes; + result = value.final_hash.bytes; + + messageBus?.SendTelemetry("Kawpow", TelemetryCategory.Hash, sw.Elapsed, true); + + return true; + } +} \ No newline at end of file diff --git a/src/Miningcore/Crypto/Hashing/Kawpow/EthashLight.cs b/src/Miningcore/Crypto/Hashing/Kawpow/EthashLight.cs new file mode 100644 index 000000000..232b02522 --- /dev/null +++ b/src/Miningcore/Crypto/Hashing/Kawpow/EthashLight.cs @@ -0,0 +1,86 @@ +using Miningcore.Blockchain.Ravencoin; +using NLog; + +namespace Miningcore.Crypto.Hashing.Kawpow; + +public class EthashLight : IDisposable +{ + public void Setup(int numCaches) + { + this.numCaches = numCaches; + } + + private int numCaches; // Maximum number of caches to keep before eviction (only init, don't modify) + private readonly object cacheLock = new(); + private readonly Dictionary caches = new(); + private Cache future; + public string AlgoName { get; } = "KawPow"; + + public void Dispose() + { + foreach(var value in caches.Values) + value.Dispose(); + } + + public async Task GetCacheAsync(ILogger logger, int block) + { + var epoch = block / RavencoinConstants.EpochLength; + Cache result; + + lock(cacheLock) + { + if(numCaches == 0) + numCaches = 3; + + if(!caches.TryGetValue(epoch, out result)) + { + // No cached cache, evict the oldest if the cache limit was reached + while(caches.Count >= numCaches) + { + var toEvict = caches.Values.OrderBy(x => x.LastUsed).First(); + var key = caches.First(pair => pair.Value == toEvict).Key; + var epochToEvict = toEvict.Epoch; + + logger.Info(() => $"Evicting cache for epoch {epochToEvict} in favour of epoch {epoch}"); + toEvict.Dispose(); + caches.Remove(key); + } + + // If we have the new cache pre-generated, use that, otherwise create a new one + if(future != null && future.Epoch == epoch) + { + logger.Debug(() => $"Using pre-generated cache for epoch {epoch}"); + + result = future; + future = null; + } + + else + { + logger.Info(() => $"No pre-generated cache available, creating new for epoch {epoch}"); + result = new Cache(epoch); + } + + caches[epoch] = result; + } + + // If we used up the future cache, or need a refresh, regenerate + else if(future == null || future.Epoch <= epoch) + { + logger.Info(() => $"Pre-generating cache for epoch {epoch + 1}"); + future = new Cache(epoch + 1); + +#pragma warning disable 4014 + future.GenerateAsync(logger); +#pragma warning restore 4014 + } + + result.LastUsed = DateTime.Now; + } + + // get/generate current one + await result.GenerateAsync(logger); + + return result; + } +} diff --git a/src/Miningcore/Native/KawPow.cs b/src/Miningcore/Native/KawPow.cs index c80643952..8d82d9f30 100644 --- a/src/Miningcore/Native/KawPow.cs +++ b/src/Miningcore/Native/KawPow.cs @@ -8,17 +8,20 @@ namespace Miningcore.Native; public static unsafe class KawPow { - [DllImport("libethash", EntryPoint = "ethash_create_epoch_context", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr CreateContext(int epoch_number); + [DllImport("libkawpow", EntryPoint = "ethash_create_epoch_context", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateContext(int epoch_number); - [DllImport("libethash", EntryPoint = "ethash_destroy_epoch_context", CallingConvention = CallingConvention.Cdecl)] - private static extern void DestroyContext(IntPtr context); + [DllImport("libkawpow", EntryPoint = "ethash_destroy_epoch_context", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyContext(IntPtr context); - [DllImport("libethash", EntryPoint = "hashext", CallingConvention = CallingConvention.Cdecl)] - private static extern Ethash_result hashext(IntPtr context, int block_number, ref Ethash_hash256 header_hash, ulong nonce, ref Ethash_hash256 mix_hash, ref Ethash_hash256 boundary1, ref Ethash_hash256 boundary2, out int retcode); + [DllImport("libkawpow", EntryPoint = "hash", CallingConvention = CallingConvention.Cdecl)] + public static extern Ethash_result hash(IntPtr context, int block_number, ref Ethash_hash256 header_hash, ulong nonce); + + [DllImport("libkawpow", EntryPoint = "ethash_calculate_epoch_seed", CallingConvention = CallingConvention.Cdecl)] + public static extern Ethash_hash256 calculate_epoch_seed(int epoch_number); [StructLayout(LayoutKind.Explicit)] - private struct Ethash_hash256 + public struct Ethash_hash256 { [FieldOffset(0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] @@ -26,7 +29,7 @@ private struct Ethash_hash256 } [StructLayout(LayoutKind.Sequential)] - private struct Ethash_result + public struct Ethash_result { public Ethash_hash256 final_hash;//32 public Ethash_hash256 mix_hash;//32 diff --git a/src/Miningcore/build-libs-linux.sh b/src/Miningcore/build-libs-linux.sh index 5af3bab26..2cd53744f 100755 --- a/src/Miningcore/build-libs-linux.sh +++ b/src/Miningcore/build-libs-linux.sh @@ -22,12 +22,14 @@ HAVE_AVX512F=$(../Native/check_cpu.sh avx512f && echo -DHAVE_AVX512F || echo) export HAVE_FEATURE="$HAVE_AES $HAVE_SSE2 $HAVE_SSE3 $HAVE_SSSE3 $HAVE_AVX $HAVE_AVX2 $HAVE_AVX512F" -(cd ../Native/libmultihash && make clean && make) && mv ../Native/libmultihash/libmultihash.so "$OutDir" -(cd ../Native/libetchash && make clean && make) && mv ../Native/libetchash/libetchash.so "$OutDir" -(cd ../Native/libethhash && make clean && make) && mv ../Native/libethhash/libethhash.so "$OutDir" -(cd ../Native/libubqhash && make clean && make) && mv ../Native/libubqhash/libubqhash.so "$OutDir" -(cd ../Native/libcryptonote && make clean && make) && mv ../Native/libcryptonote/libcryptonote.so "$OutDir" -(cd ../Native/libcryptonight && make clean && make) && mv ../Native/libcryptonight/libcryptonight.so "$OutDir" - -((cd /tmp && rm -rf RandomX && git clone https://github.com/tevador/RandomX && cd RandomX && git checkout tags/v1.1.10 && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomx && cp /tmp/RandomX/build/librandomx.a . && make clean && make) && mv ../Native/librandomx/librandomx.so "$OutDir") -((cd /tmp && rm -rf RandomARQ && git clone https://github.com/arqma/RandomARQ && cd RandomARQ && git checkout 14850620439045b319fa6398f5a164715c4a66ce && mkdir build && cd build && cmake -DARCH=native .. && make) && (cd ../Native/librandomarq && cp /tmp/RandomARQ/build/librandomx.a . && make clean && make) && mv ../Native/librandomarq/librandomarq.so "$OutDir") +(cd ../Native/libmultihash && make -j clean && make -j) && mv ../Native/libmultihash/libmultihash.so "$OutDir" +(cd ../Native/libetchash && make -j clean && make -j) && mv ../Native/libetchash/libetchash.so "$OutDir" +(cd ../Native/libethhash && make -j clean && make -j) && mv ../Native/libethhash/libethhash.so "$OutDir" +(cd ../Native/libubqhash && make -j clean && make -j) && mv ../Native/libubqhash/libubqhash.so "$OutDir" +(cd ../Native/libcryptonote && make -j clean && make -j) && mv ../Native/libcryptonote/libcryptonote.so "$OutDir" +(cd ../Native/libcryptonight && make -j clean && make -j) && mv ../Native/libcryptonight/libcryptonight.so "$OutDir" +(cd ../Native/libkawpow && make -j clean && make -j) && mv ../Native/libkawpow/libkawpow.so "$OutDir" + + +((cd /tmp && rm -rf RandomX && git clone https://github.com/tevador/RandomX && cd RandomX && git checkout tags/v1.1.10 && mkdir build && cd build && cmake -DARCH=native .. && make -j) && (cd ../Native/librandomx && cp /tmp/RandomX/build/librandomx.a . && make -j clean && make -j) && mv ../Native/librandomx/librandomx.so "$OutDir") +((cd /tmp && rm -rf RandomARQ && git clone https://github.com/arqma/RandomARQ && cd RandomARQ && git checkout 14850620439045b319fa6398f5a164715c4a66ce && mkdir build && cd build && cmake -DARCH=native .. && make -j) && (cd ../Native/librandomarq && cp /tmp/RandomARQ/build/librandomx.a . && make -j clean && make -j) && mv ../Native/librandomarq/librandomarq.so "$OutDir") diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index bc624a715..470b6c57e 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -9,7 +9,7 @@ "telegram": "https://t.me/VGC_5GCASH", "discord": "https://discord.gg/uXHvyBT", "coinbaseHasher": { - "hash": "sha256d" + "hash": "sha256d", }, "headerHasher": { "hash": "x16r-v2" @@ -60,9 +60,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "sha256d" - ], + "blockTemplateRpcExtraParams": ["sha256d"], "explorerBlockLink": "https://chainz.cryptoid.info/aur/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/aur/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/aur/address.dws?{0}.htm" @@ -82,10 +80,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -95,9 +90,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "scrypt" - ], + "blockTemplateRpcExtraParams": ["scrypt"], "shareMultiplier": 65536, "explorerBlockLink": "https://chainz.cryptoid.info/aur/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/aur/tx.dws?{0}.htm", @@ -127,9 +120,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "skein" - ], + "blockTemplateRpcExtraParams": ["skein"], "explorerBlockLink": "https://chainz.cryptoid.info/aur/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/aur/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/aur/address.dws?{0}.htm" @@ -158,9 +149,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "qubit" - ], + "blockTemplateRpcExtraParams": ["qubit"], "explorerBlockLink": "https://chainz.cryptoid.info/aur/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/aur/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/aur/address.dws?{0}.htm" @@ -190,9 +179,7 @@ ] }, "shareMultiplier": 4096, - "blockTemplateRpcExtraParams": [ - "groestl" - ], + "blockTemplateRpcExtraParams": ["groestl"], "explorerBlockLink": "https://chainz.cryptoid.info/aur/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/aur/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/aur/address.dws?{0}.htm" @@ -212,10 +199,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -273,10 +257,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -542,9 +523,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "sha256d" - ], + "blockTemplateRpcExtraParams": ["sha256d"], "hasFounderFee": true, "hasMasterNodes": true, "explorerBlockLink": "https://blocks.butcoin.tech/block/$hash$", @@ -566,10 +545,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -584,10 +560,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -707,9 +680,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "sha256d" - ], + "blockTemplateRpcExtraParams": ["sha256d"], "explorerBlockLink": "https://chainz.cryptoid.info/dgb/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/dgb/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/dgb/address.dws?{0}.htm" @@ -729,10 +700,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -743,9 +711,7 @@ ] }, "shareMultiplier": 65536, - "blockTemplateRpcExtraParams": [ - "scrypt" - ], + "blockTemplateRpcExtraParams": ["scrypt"], "explorerBlockLink": "https://chainz.cryptoid.info/dgb/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/dgb/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/dgb/address.dws?{0}.htm" @@ -774,9 +740,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "skein" - ], + "blockTemplateRpcExtraParams": ["skein"], "explorerBlockLink": "https://chainz.cryptoid.info/dgb/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/dgb/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/dgb/address.dws?{0}.htm" @@ -805,9 +769,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "qubit" - ], + "blockTemplateRpcExtraParams": ["qubit"], "explorerBlockLink": "https://chainz.cryptoid.info/dgb/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/dgb/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/dgb/address.dws?{0}.htm" @@ -837,9 +799,7 @@ ] }, "shareMultiplier": 256, - "blockTemplateRpcExtraParams": [ - "groestl" - ], + "blockTemplateRpcExtraParams": ["groestl"], "explorerBlockLink": "https://chainz.cryptoid.info/dgb/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/dgb/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/dgb/address.dws?{0}.htm" @@ -859,10 +819,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1094,18 +1051,14 @@ }, "headerHasher": { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] }, "blockHasher": { "hash": "reverse", "args": [ { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] } ] }, @@ -1128,10 +1081,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1146,10 +1096,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1173,10 +1120,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1191,10 +1135,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1372,10 +1313,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1390,10 +1328,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1510,10 +1445,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1528,10 +1460,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1582,18 +1511,14 @@ }, "headerHasher": { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] }, "blockHasher": { "hash": "reverse", "args": [ { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] } ] }, @@ -1645,10 +1570,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1707,10 +1629,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1725,10 +1644,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1780,10 +1696,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1798,10 +1711,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -1879,10 +1789,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -1897,10 +1804,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2008,10 +1912,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2026,10 +1927,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2083,10 +1981,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2101,10 +1996,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2170,9 +2062,7 @@ "hasFounderFee": true, "hasMasterNodes": true, "hasSmartNodes": true, - "foundersRewardAddress": [ - "RTtyQU6DoSuNWetT4WUem5qXP5jNYGpwat" - ], + "foundersRewardAddress": ["RTtyQU6DoSuNWetT4WUem5qXP5jNYGpwat"], "shareMultiplier": 65536, "explorerBlockLink": "https://explorer.raptoreum.com/block-height/$height$", "explorerTxLink": "https://explorer.raptoreum.com/tx/{0}", @@ -2203,9 +2093,7 @@ }, "hasFounderFee": true, "hasMasterNodes": true, - "foundersRewardAddress": [ - "GZBNAiLokyYvSwNLftReXuYbvdmzNoorGj" - ], + "foundersRewardAddress": ["GZBNAiLokyYvSwNLftReXuYbvdmzNoorGj"], "shareMultiplier": 65536, "explorerBlockLink": "https://explorer.globalsocialpost.com/block/$height$", "explorerTxLink": "https://explorer.globalsocialpost.com/tx/{0}", @@ -2223,29 +2111,49 @@ }, "blockHasher": { "hash": "reverse", - "args": [ { "hash": "sha512/256d" } ] + "args": [{ "hash": "sha512/256d" }] }, "explorerBlockLink": "https://explorer.radiantblockchain.org/block-height/$height$", "explorerTxLink": "https://explorer.radiantblockchain.org/tx/{0}", "explorerAccountLink": "https://explorer.radiantblockchain.org/address/{0}" }, + "ravencoin": { + "name": "Ravencoin", + "symbol": "RVN", + "family": "ravencoin", + "website": "https://www.bitcoin.org/", + "market": "https://coinmarketcap.com/currencies/bitcoin/", + "twitter": "https://twitter.com/bitcoin", + "telegram": "", + "discord": "", + "coinbaseHasher": { + "hash": "sha256d" + }, + "headerHasher": { + "hash": "sha256d" + }, + "shareMultiplier": 1, + "explorerBlockLink": "https://ravencoin.network/rvn/block/$hash$", + "explorerTxLink": "https://ravencoin.network/rvn/transaction/{0}", + "explorerAccountLink": "https://ravencoin.network/rvn/address/{0}" + }, "novo": { - "name": "Novo", - "symbol": "NOVO", - "family": "bitcoin", - "coinbaseHasher": { - "hash": "sha256d" - }, - "headerHasher": { - "hash": "sha256dt" - }, - "blockHasher": { - "hash": "reverse", - "args": [ { "hash": "sha256dt" } ] - }, - "explorerBlockLink": "https://explorer.novochain.ovh/block/$height$", - "explorerTxLink": "https://explorer.novochain.ovh/tx/{0}", - "explorerAccountLink": "https://explorer.novochain.ovh/address/{0}" + "name": "Novo", + "symbol": "NOVO", + "family": "bitcoin", + "coinbaseHasher": { + "hash": "sha256d" + }, + "headerHasher": { + "hash": "sha256dt" + }, + "blockHasher": { + "hash": "reverse", + "args": [{ "hash": "sha256dt" }] + }, + "explorerBlockLink": "https://explorer.novochain.ovh/block/$height$", + "explorerTxLink": "https://explorer.novochain.ovh/tx/{0}", + "explorerAccountLink": "https://explorer.novochain.ovh/address/{0}" }, "bitoreum": { "name": "Bitoreum", @@ -2273,9 +2181,7 @@ "hasFounderFee": true, "hasMasterNodes": true, "hasSmartNodes": true, - "foundersRewardAddress": [ - "BanxgMPcMpXnuWQ2ogfQqEkwwVtjhAhXBR" - ], + "foundersRewardAddress": ["BanxgMPcMpXnuWQ2ogfQqEkwwVtjhAhXBR"], "shareMultiplier": 65536, "explorerBlockLink": "https://explorer.bitoreum.org/block/$hash$", "explorerTxLink": "https://explorer.bitoreum.org/tx/{0}", @@ -2296,10 +2202,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2314,10 +2217,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2387,9 +2287,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "sha256d" - ], + "blockTemplateRpcExtraParams": ["sha256d"], "explorerBlockLink": "https://chainz.cryptoid.info/smly/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/smly/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/smly/address.dws?{0}.htm" @@ -2409,10 +2307,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2422,9 +2317,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "scrypt" - ], + "blockTemplateRpcExtraParams": ["scrypt"], "shareMultiplier": 65536, "explorerBlockLink": "https://chainz.cryptoid.info/smly/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/smly/tx.dws?{0}.htm", @@ -2454,9 +2347,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "skein" - ], + "blockTemplateRpcExtraParams": ["skein"], "explorerBlockLink": "https://chainz.cryptoid.info/smly/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/smly/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/smly/address.dws?{0}.htm" @@ -2485,9 +2376,7 @@ } ] }, - "blockTemplateRpcExtraParams": [ - "qubit" - ], + "blockTemplateRpcExtraParams": ["qubit"], "explorerBlockLink": "https://chainz.cryptoid.info/smly/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/smly/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/smly/address.dws?{0}.htm" @@ -2517,9 +2406,7 @@ ] }, "shareMultiplier": 4096, - "blockTemplateRpcExtraParams": [ - "groestl" - ], + "blockTemplateRpcExtraParams": ["groestl"], "explorerBlockLink": "https://chainz.cryptoid.info/smly/block.dws?$height$.htm", "explorerTxLink": "https://chainz.cryptoid.info/smly/tx.dws?{0}.htm", "explorerAccountLink": "https://chainz.cryptoid.info/smly/address.dws?{0}.htm" @@ -2539,18 +2426,14 @@ }, "headerHasher": { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] }, "blockHasher": { "hash": "reverse", "args": [ { "hash": "neoscrypt", - "args": [ - 0 - ] + "args": [0] } ] }, @@ -2714,10 +2597,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2732,10 +2612,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2818,10 +2695,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2880,10 +2754,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -2898,10 +2769,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -2932,17 +2800,12 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, "isPseudoPoS": true, - "blockTemplateRpcExtraParams": [ - "lyra" - ], + "blockTemplateRpcExtraParams": ["lyra"], "shareMultiplier": 256, "explorerBlockLink": "https://verge-blockchain.info/block/$hash$", "explorerTxLink": "https://verge-blockchain.info/tx/{0}", @@ -2963,27 +2826,19 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, "isPseudoPoS": true, - "blockTemplateRpcExtraParams": [ - "scrypt" - ], + "blockTemplateRpcExtraParams": ["scrypt"], "shareMultiplier": 65536, "explorerBlockLink": "https://verge-blockchain.info/block/$hash$", "explorerTxLink": "https://verge-blockchain.info/tx/{0}", @@ -3010,17 +2865,12 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, "isPseudoPoS": true, - "blockTemplateRpcExtraParams": [ - "x17" - ], + "blockTemplateRpcExtraParams": ["x17"], "explorerBlockLink": "https://verge-blockchain.info/block/$hash$", "explorerTxLink": "https://verge-blockchain.info/tx/{0}", "explorerAccountLink": "https://verge-blockchain.info/address/{0}" @@ -3046,17 +2896,12 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, "isPseudoPoS": true, - "blockTemplateRpcExtraParams": [ - "blake" - ], + "blockTemplateRpcExtraParams": ["blake"], "explorerBlockLink": "https://verge-blockchain.info/block/$hash$", "explorerTxLink": "https://verge-blockchain.info/tx/{0}", "explorerAccountLink": "https://verge-blockchain.info/address/{0}" @@ -3082,17 +2927,12 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, "isPseudoPoS": true, - "blockTemplateRpcExtraParams": [ - "groestl" - ], + "blockTemplateRpcExtraParams": ["groestl"], "explorerBlockLink": "https://verge-blockchain.info/block/$hash$", "explorerTxLink": "https://verge-blockchain.info/tx/{0}", "explorerAccountLink": "https://verge-blockchain.info/address/{0}" @@ -3140,10 +2980,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -3158,10 +2995,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -3185,10 +3019,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -3203,10 +3034,7 @@ "args": [ { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] } ] }, @@ -3258,10 +3086,7 @@ }, "headerHasher": { "hash": "scrypt", - "args": [ - 1024, - 1 - ] + "args": [1024, 1] }, "blockHasher": { "hash": "reverse", @@ -3410,11 +3235,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3425,11 +3246,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3440,11 +3257,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3473,11 +3286,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3488,11 +3297,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3503,11 +3308,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BgoldPoW" - ] + "args": [144, 5, "BgoldPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3535,11 +3336,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3550,11 +3347,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3565,11 +3358,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3597,11 +3386,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BethdPoW" - ] + "args": [144, 5, "BethdPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3612,11 +3397,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BethdPoW" - ] + "args": [144, 5, "BethdPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3627,11 +3408,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 144, - 5, - "BethdPoW" - ] + "args": [144, 5, "BethdPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3659,11 +3436,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 96, - 5, - "ZcashPoW" - ] + "args": [96, 5, "ZcashPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3674,11 +3447,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 96, - 5, - "ZcashPoW" - ] + "args": [96, 5, "ZcashPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3689,11 +3458,7 @@ "solutionPreambleSize": 1, "solver": { "hash": "equihash", - "args": [ - 96, - 5, - "ZcashPoW" - ] + "args": [96, 5, "ZcashPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3721,11 +3486,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "Verushash" - ] + "args": [200, 9, "Verushash"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false, @@ -3742,11 +3503,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "Verushash" - ] + "args": [200, 9, "Verushash"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -3757,11 +3514,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "Verushash" - ] + "args": [200, 9, "Verushash"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -3789,11 +3542,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "zcash-main", "payFoundersReward": true, @@ -3864,11 +3613,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "zcash-test", "payFoundersReward": true, @@ -3939,11 +3684,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "zcash-reg", "payFoundersReward": true, @@ -3976,11 +3717,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": false @@ -3991,11 +3728,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": false @@ -4006,11 +3739,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": false @@ -4038,11 +3767,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "main", "payFoundersReward": true, @@ -4115,11 +3840,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "testnet", "payFoundersReward": true, @@ -4192,11 +3913,7 @@ "solutionPreambleSize": 3, "solver": { "hash": "equihash", - "args": [ - 200, - 9, - "ZcashPoW" - ] + "args": [200, 9, "ZcashPoW"] }, "coinbaseTxNetwork": "regtest", "payFoundersReward": true, @@ -4244,7 +3961,7 @@ "explorerBlockLink": "https://www.exploremonero.com/block/$height$", "explorerTxLink": "https://www.exploremonero.com/transaction/{0}" }, - "conceal": { + "conceal": { "name": "Conceal", "canonicalName": "Conceal", "symbol": "CCX", diff --git a/src/Native/libkawpow/ethash/ethash.cpp b/src/Native/libkawpow/ethash/ethash.cpp index 436ded06b..9f3236535 100644 --- a/src/Native/libkawpow/ethash/ethash.cpp +++ b/src/Native/libkawpow/ethash/ethash.cpp @@ -20,425 +20,424 @@ namespace ethash { -// Internal constants: -constexpr static int light_cache_init_size = 1 << 24; -constexpr static int light_cache_growth = 1 << 17; -constexpr static int light_cache_rounds = 3; -constexpr static int full_dataset_init_size = 1 << 30; -constexpr static int full_dataset_growth = 1 << 23; -constexpr static int full_dataset_item_parents = 512; - -// Verify constants: -static_assert(sizeof(hash512) == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); -static_assert(sizeof(hash1024) == ETHASH_FULL_DATASET_ITEM_SIZE, ""); -static_assert(light_cache_item_size == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); -static_assert(full_dataset_item_size == ETHASH_FULL_DATASET_ITEM_SIZE, ""); - - -namespace -{ -using ::fnv1; + // Internal constants: + constexpr static int light_cache_init_size = 1 << 24; + constexpr static int light_cache_growth = 1 << 17; + constexpr static int light_cache_rounds = 3; + constexpr static int full_dataset_init_size = 1 << 30; + constexpr static int full_dataset_growth = 1 << 23; + constexpr static int full_dataset_item_parents = 512; + + // Verify constants: + static_assert(sizeof(hash512) == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); + static_assert(sizeof(hash1024) == ETHASH_FULL_DATASET_ITEM_SIZE, ""); + static_assert(light_cache_item_size == ETHASH_LIGHT_CACHE_ITEM_SIZE, ""); + static_assert(full_dataset_item_size == ETHASH_FULL_DATASET_ITEM_SIZE, ""); + + namespace + { + using ::fnv1; -inline hash512 fnv1(const hash512& u, const hash512& v) noexcept -{ - hash512 r; - for (size_t i = 0; i < sizeof(r) / sizeof(r.word32s[0]); ++i) - r.word32s[i] = fnv1(u.word32s[i], v.word32s[i]); - return r; -} + inline hash512 fnv1(const hash512 &u, const hash512 &v) noexcept + { + hash512 r; + for (size_t i = 0; i < sizeof(r) / sizeof(r.word32s[0]); ++i) + r.word32s[i] = fnv1(u.word32s[i], v.word32s[i]); + return r; + } -inline hash512 bitwise_xor(const hash512& x, const hash512& y) noexcept -{ - hash512 z; - for (size_t i = 0; i < sizeof(z) / sizeof(z.word64s[0]); ++i) - z.word64s[i] = x.word64s[i] ^ y.word64s[i]; - return z; -} -} // namespace - -int find_epoch_number(const hash256& seed) noexcept -{ - static constexpr int num_tries = 30000; // Divisible by 16. + inline hash512 bitwise_xor(const hash512 &x, const hash512 &y) noexcept + { + hash512 z; + for (size_t i = 0; i < sizeof(z) / sizeof(z.word64s[0]); ++i) + z.word64s[i] = x.word64s[i] ^ y.word64s[i]; + return z; + } + } // namespace - // Thread-local cache of the last search. - static thread_local int cached_epoch_number = 0; - static thread_local hash256 cached_seed = {}; + int find_epoch_number(const hash256 &seed) noexcept + { + static constexpr int num_tries = 30000; // Divisible by 16. - // Load from memory once (memory will be clobbered by keccak256()). - const uint32_t seed_part = seed.word32s[0]; - const int e = cached_epoch_number; - hash256 s = cached_seed; + // Thread-local cache of the last search. + static thread_local int cached_epoch_number = 0; + static thread_local hash256 cached_seed = {}; - if (s.word32s[0] == seed_part) - return e; + // Load from memory once (memory will be clobbered by keccak256()). + const uint32_t seed_part = seed.word32s[0]; + const int e = cached_epoch_number; + hash256 s = cached_seed; - // Try the next seed, will match for sequential epoch access. - s = keccak256(s); - if (s.word32s[0] == seed_part) - { - cached_seed = s; - cached_epoch_number = e + 1; - return e + 1; - } + if (s.word32s[0] == seed_part) + return e; - // Search for matching seed starting from epoch 0. - s = {}; - for (int i = 0; i < num_tries; ++i) - { + // Try the next seed, will match for sequential epoch access. + s = keccak256(s); if (s.word32s[0] == seed_part) { cached_seed = s; - cached_epoch_number = i; - return i; + cached_epoch_number = e + 1; + return e + 1; } - s = keccak256(s); - } - - return -1; -} + // Search for matching seed starting from epoch 0. + s = {}; + for (int i = 0; i < num_tries; ++i) + { + if (s.word32s[0] == seed_part) + { + cached_seed = s; + cached_epoch_number = i; + return i; + } + + s = keccak256(s); + } -namespace generic -{ -void build_light_cache( - hash_fn_512 hash_fn, hash512 cache[], int num_items, const hash256& seed) noexcept -{ - hash512 item = hash_fn(seed.bytes, sizeof(seed)); - cache[0] = item; - for (int i = 1; i < num_items; ++i) - { - item = hash_fn(item.bytes, sizeof(item)); - cache[i] = item; + return -1; } - for (int q = 0; q < light_cache_rounds; ++q) + namespace generic { - for (int i = 0; i < num_items; ++i) + void build_light_cache( + hash_fn_512 hash_fn, hash512 cache[], int num_items, const hash256 &seed) noexcept { - const uint32_t index_limit = static_cast(num_items); - - // Fist index: 4 first bytes of the item as little-endian integer. - const uint32_t t = le::uint32(cache[i].word32s[0]); - const uint32_t v = t % index_limit; - - // Second index. - const uint32_t w = static_cast(num_items + (i - 1)) % index_limit; + hash512 item = hash_fn(seed.bytes, sizeof(seed)); + cache[0] = item; + for (int i = 1; i < num_items; ++i) + { + item = hash_fn(item.bytes, sizeof(item)); + cache[i] = item; + } + + for (int q = 0; q < light_cache_rounds; ++q) + { + for (int i = 0; i < num_items; ++i) + { + const uint32_t index_limit = static_cast(num_items); + + // Fist index: 4 first bytes of the item as little-endian integer. + const uint32_t t = le::uint32(cache[i].word32s[0]); + const uint32_t v = t % index_limit; + + // Second index. + const uint32_t w = static_cast(num_items + (i - 1)) % index_limit; + + const hash512 x = bitwise_xor(cache[v], cache[w]); + cache[i] = hash_fn(x.bytes, sizeof(x)); + } + } + } - const hash512 x = bitwise_xor(cache[v], cache[w]); - cache[i] = hash_fn(x.bytes, sizeof(x)); + epoch_context_full *create_epoch_context( + build_light_cache_fn build_fn, int epoch_number, bool full) noexcept + { + static_assert(sizeof(epoch_context_full) < sizeof(hash512), "epoch_context too big"); + static constexpr size_t context_alloc_size = sizeof(hash512); + + const int light_cache_num_items = calculate_light_cache_num_items(epoch_number); + const int full_dataset_num_items = calculate_full_dataset_num_items(epoch_number); + const size_t light_cache_size = get_light_cache_size(light_cache_num_items); + const size_t full_dataset_size = + full ? static_cast(full_dataset_num_items) * sizeof(hash1024) : progpow::l1_cache_size; + + const size_t alloc_size = context_alloc_size + light_cache_size + full_dataset_size; + + char *const alloc_data = static_cast(std::calloc(1, alloc_size)); + if (!alloc_data) + return nullptr; // Signal out-of-memory by returning null pointer. + + hash512 *const light_cache = reinterpret_cast(alloc_data + context_alloc_size); + const hash256 epoch_seed = calculate_epoch_seed(epoch_number); + build_fn(light_cache, light_cache_num_items, epoch_seed); + + uint32_t *const l1_cache = + reinterpret_cast(alloc_data + context_alloc_size + light_cache_size); + + hash1024 *full_dataset = full ? reinterpret_cast(l1_cache) : nullptr; + + epoch_context_full *const context = new (alloc_data) epoch_context_full{ + epoch_number, + light_cache_num_items, + light_cache, + l1_cache, + full_dataset_num_items, + full_dataset, + }; + + auto *full_dataset_2048 = reinterpret_cast(l1_cache); + for (uint32_t i = 0; i < progpow::l1_cache_size / sizeof(full_dataset_2048[0]); ++i) + full_dataset_2048[i] = calculate_dataset_item_2048(*context, i); + return context; } + } // namespace generic + + void build_light_cache(hash512 cache[], int num_items, const hash256 &seed) noexcept + { + return generic::build_light_cache(keccak512, cache, num_items, seed); } -} -epoch_context_full* create_epoch_context( - build_light_cache_fn build_fn, int epoch_number, bool full) noexcept -{ - static_assert(sizeof(epoch_context_full) < sizeof(hash512), "epoch_context too big"); - static constexpr size_t context_alloc_size = sizeof(hash512); - - const int light_cache_num_items = calculate_light_cache_num_items(epoch_number); - const int full_dataset_num_items = calculate_full_dataset_num_items(epoch_number); - const size_t light_cache_size = get_light_cache_size(light_cache_num_items); - const size_t full_dataset_size = - full ? static_cast(full_dataset_num_items) * sizeof(hash1024) : - progpow::l1_cache_size; - - const size_t alloc_size = context_alloc_size + light_cache_size + full_dataset_size; - - char* const alloc_data = static_cast(std::calloc(1, alloc_size)); - if (!alloc_data) - return nullptr; // Signal out-of-memory by returning null pointer. - - hash512* const light_cache = reinterpret_cast(alloc_data + context_alloc_size); - const hash256 epoch_seed = calculate_epoch_seed(epoch_number); - build_fn(light_cache, light_cache_num_items, epoch_seed); - - uint32_t* const l1_cache = - reinterpret_cast(alloc_data + context_alloc_size + light_cache_size); - - hash1024* full_dataset = full ? reinterpret_cast(l1_cache) : nullptr; - - epoch_context_full* const context = new (alloc_data) epoch_context_full{ - epoch_number, - light_cache_num_items, - light_cache, - l1_cache, - full_dataset_num_items, - full_dataset, - }; + struct item_state + { + const hash512 *const cache; + const int64_t num_cache_items; + const uint32_t seed; - auto* full_dataset_2048 = reinterpret_cast(l1_cache); - for (uint32_t i = 0; i < progpow::l1_cache_size / sizeof(full_dataset_2048[0]); ++i) - full_dataset_2048[i] = calculate_dataset_item_2048(*context, i); - return context; -} -} // namespace generic + hash512 mix; -void build_light_cache(hash512 cache[], int num_items, const hash256& seed) noexcept -{ - return generic::build_light_cache(keccak512, cache, num_items, seed); -} + ALWAYS_INLINE item_state(const epoch_context &context, int64_t index) noexcept + : cache{context.light_cache}, + num_cache_items{context.light_cache_num_items}, + seed{static_cast(index)} + { + mix = cache[index % num_cache_items]; + mix.word32s[0] ^= le::uint32(seed); + mix = le::uint32s(keccak512(mix)); + } -struct item_state -{ - const hash512* const cache; - const int64_t num_cache_items; - const uint32_t seed; + ALWAYS_INLINE void update(uint32_t round) noexcept + { + static constexpr size_t num_words = sizeof(mix) / sizeof(uint32_t); + const uint32_t t = fnv1(seed ^ round, mix.word32s[round % num_words]); + const int64_t parent_index = t % num_cache_items; + mix = fnv1(mix, le::uint32s(cache[parent_index])); + } - hash512 mix; + ALWAYS_INLINE hash512 final() noexcept { return keccak512(le::uint32s(mix)); } + }; - ALWAYS_INLINE item_state(const epoch_context& context, int64_t index) noexcept - : cache{context.light_cache}, - num_cache_items{context.light_cache_num_items}, - seed{static_cast(index)} + hash512 calculate_dataset_item_512(const epoch_context &context, int64_t index) noexcept { - mix = cache[index % num_cache_items]; - mix.word32s[0] ^= le::uint32(seed); - mix = le::uint32s(keccak512(mix)); + item_state item0{context, index}; + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + item0.update(j); + return item0.final(); } - ALWAYS_INLINE void update(uint32_t round) noexcept + /// Calculates a full dataset item + /// + /// This consist of two 512-bit items produced by calculate_dataset_item_partial(). + /// Here the computation is done interleaved for better performance. + hash1024 calculate_dataset_item_1024(const epoch_context &context, uint32_t index) noexcept { - static constexpr size_t num_words = sizeof(mix) / sizeof(uint32_t); - const uint32_t t = fnv1(seed ^ round, mix.word32s[round % num_words]); - const int64_t parent_index = t % num_cache_items; - mix = fnv1(mix, le::uint32s(cache[parent_index])); - } + item_state item0{context, int64_t(index) * 2}; + item_state item1{context, int64_t(index) * 2 + 1}; - ALWAYS_INLINE hash512 final() noexcept { return keccak512(le::uint32s(mix)); } -}; - -hash512 calculate_dataset_item_512(const epoch_context& context, int64_t index) noexcept -{ - item_state item0{context, index}; - for (uint32_t j = 0; j < full_dataset_item_parents; ++j) - item0.update(j); - return item0.final(); -} - -/// Calculates a full dataset item -/// -/// This consist of two 512-bit items produced by calculate_dataset_item_partial(). -/// Here the computation is done interleaved for better performance. -hash1024 calculate_dataset_item_1024(const epoch_context& context, uint32_t index) noexcept -{ - item_state item0{context, int64_t(index) * 2}; - item_state item1{context, int64_t(index) * 2 + 1}; + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + { + item0.update(j); + item1.update(j); + } - for (uint32_t j = 0; j < full_dataset_item_parents; ++j) - { - item0.update(j); - item1.update(j); + return hash1024{{item0.final(), item1.final()}}; } - return hash1024{{item0.final(), item1.final()}}; -} + hash2048 calculate_dataset_item_2048(const epoch_context &context, uint32_t index) noexcept + { + item_state item0{context, int64_t(index) * 4}; + item_state item1{context, int64_t(index) * 4 + 1}; + item_state item2{context, int64_t(index) * 4 + 2}; + item_state item3{context, int64_t(index) * 4 + 3}; -hash2048 calculate_dataset_item_2048(const epoch_context& context, uint32_t index) noexcept -{ - item_state item0{context, int64_t(index) * 4}; - item_state item1{context, int64_t(index) * 4 + 1}; - item_state item2{context, int64_t(index) * 4 + 2}; - item_state item3{context, int64_t(index) * 4 + 3}; + for (uint32_t j = 0; j < full_dataset_item_parents; ++j) + { + item0.update(j); + item1.update(j); + item2.update(j); + item3.update(j); + } - for (uint32_t j = 0; j < full_dataset_item_parents; ++j) - { - item0.update(j); - item1.update(j); - item2.update(j); - item3.update(j); + return hash2048{{item0.final(), item1.final(), item2.final(), item3.final()}}; } - return hash2048{{item0.final(), item1.final(), item2.final(), item3.final()}}; -} - -namespace -{ -using lookup_fn = hash1024 (*)(const epoch_context&, uint32_t); + namespace + { + using lookup_fn = hash1024 (*)(const epoch_context &, uint32_t); -inline hash512 hash_seed(const hash256& header_hash, uint64_t nonce) noexcept -{ - nonce = le::uint64(nonce); - uint8_t init_data[sizeof(header_hash) + sizeof(nonce)]; - std::memcpy(&init_data[0], &header_hash, sizeof(header_hash)); - std::memcpy(&init_data[sizeof(header_hash)], &nonce, sizeof(nonce)); + inline hash512 hash_seed(const hash256 &header_hash, uint64_t nonce) noexcept + { + nonce = le::uint64(nonce); + uint8_t init_data[sizeof(header_hash) + sizeof(nonce)]; + std::memcpy(&init_data[0], &header_hash, sizeof(header_hash)); + std::memcpy(&init_data[sizeof(header_hash)], &nonce, sizeof(nonce)); - return keccak512(init_data, sizeof(init_data)); -} + return keccak512(init_data, sizeof(init_data)); + } -inline hash256 hash_final(const hash512& seed, const hash256& mix_hash) -{ - uint8_t final_data[sizeof(seed) + sizeof(mix_hash)]; - std::memcpy(&final_data[0], seed.bytes, sizeof(seed)); - std::memcpy(&final_data[sizeof(seed)], mix_hash.bytes, sizeof(mix_hash)); - return keccak256(final_data, sizeof(final_data)); -} - -inline hash256 hash_kernel( - const epoch_context& context, const hash512& seed, lookup_fn lookup) noexcept -{ - static constexpr size_t num_words = sizeof(hash1024) / sizeof(uint32_t); - const uint32_t index_limit = static_cast(context.full_dataset_num_items); - const uint32_t seed_init = le::uint32(seed.word32s[0]); + inline hash256 hash_final(const hash512 &seed, const hash256 &mix_hash) + { + uint8_t final_data[sizeof(seed) + sizeof(mix_hash)]; + std::memcpy(&final_data[0], seed.bytes, sizeof(seed)); + std::memcpy(&final_data[sizeof(seed)], mix_hash.bytes, sizeof(mix_hash)); + return keccak256(final_data, sizeof(final_data)); + } - hash1024 mix{{le::uint32s(seed), le::uint32s(seed)}}; + inline hash256 hash_kernel( + const epoch_context &context, const hash512 &seed, lookup_fn lookup) noexcept + { + static constexpr size_t num_words = sizeof(hash1024) / sizeof(uint32_t); + const uint32_t index_limit = static_cast(context.full_dataset_num_items); + const uint32_t seed_init = le::uint32(seed.word32s[0]); + + hash1024 mix{{le::uint32s(seed), le::uint32s(seed)}}; + + for (uint32_t i = 0; i < num_dataset_accesses; ++i) + { + const uint32_t p = fnv1(i ^ seed_init, mix.word32s[i % num_words]) % index_limit; + const hash1024 newdata = le::uint32s(lookup(context, p)); + + for (size_t j = 0; j < num_words; ++j) + mix.word32s[j] = fnv1(mix.word32s[j], newdata.word32s[j]); + } + + hash256 mix_hash; + for (size_t i = 0; i < num_words; i += 4) + { + const uint32_t h1 = fnv1(mix.word32s[i], mix.word32s[i + 1]); + const uint32_t h2 = fnv1(h1, mix.word32s[i + 2]); + const uint32_t h3 = fnv1(h2, mix.word32s[i + 3]); + mix_hash.word32s[i / 4] = h3; + } + + return le::uint32s(mix_hash); + } + } // namespace - for (uint32_t i = 0; i < num_dataset_accesses; ++i) + result hash(const epoch_context_full &context, const hash256 &header_hash, uint64_t nonce) noexcept { - const uint32_t p = fnv1(i ^ seed_init, mix.word32s[i % num_words]) % index_limit; - const hash1024 newdata = le::uint32s(lookup(context, p)); - - for (size_t j = 0; j < num_words; ++j) - mix.word32s[j] = fnv1(mix.word32s[j], newdata.word32s[j]); + static const auto lazy_lookup = [](const epoch_context &ctx, uint32_t index) noexcept + { + auto full_dataset = static_cast(ctx).full_dataset; + hash1024 &item = full_dataset[index]; + if (item.word64s[0] == 0) + { + // TODO: Copy elision here makes it thread-safe? + item = calculate_dataset_item_1024(ctx, index); + } + + return item; + }; + + const hash512 seed = hash_seed(header_hash, nonce); + const hash256 mix_hash = hash_kernel(context, seed, lazy_lookup); + return {hash_final(seed, mix_hash), mix_hash}; } - hash256 mix_hash; - for (size_t i = 0; i < num_words; i += 4) + search_result search_light(const epoch_context &context, const hash256 &header_hash, + const hash256 &boundary, uint64_t start_nonce, size_t iterations) noexcept { - const uint32_t h1 = fnv1(mix.word32s[i], mix.word32s[i + 1]); - const uint32_t h2 = fnv1(h1, mix.word32s[i + 2]); - const uint32_t h3 = fnv1(h2, mix.word32s[i + 3]); - mix_hash.word32s[i / 4] = h3; + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + { + result r = hash(context, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; + } + return {}; } - return le::uint32s(mix_hash); -} -} // namespace - -result hash(const epoch_context_full& context, const hash256& header_hash, uint64_t nonce) noexcept -{ - static const auto lazy_lookup = [](const epoch_context& ctx, uint32_t index) noexcept { - auto full_dataset = static_cast(ctx).full_dataset; - hash1024& item = full_dataset[index]; - if (item.word64s[0] == 0) + search_result search(const epoch_context_full &context, const hash256 &header_hash, + const hash256 &boundary, uint64_t start_nonce, size_t iterations) noexcept + { + const uint64_t end_nonce = start_nonce + iterations; + for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) { - // TODO: Copy elision here makes it thread-safe? - item = calculate_dataset_item_1024(ctx, index); + result r = hash(context, header_hash, nonce); + if (is_less_or_equal(r.final_hash, boundary)) + return {r, nonce}; } + return {}; + } +} // namespace ethash - return item; - }; - - const hash512 seed = hash_seed(header_hash, nonce); - const hash256 mix_hash = hash_kernel(context, seed, lazy_lookup); - return {hash_final(seed, mix_hash), mix_hash}; -} +using namespace ethash; -search_result search_light(const epoch_context& context, const hash256& header_hash, - const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept +extern "C" { - const uint64_t end_nonce = start_nonce + iterations; - for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + + ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) noexcept { - result r = hash(context, header_hash, nonce); - if (is_less_or_equal(r.final_hash, boundary)) - return {r, nonce}; + ethash_hash256 epoch_seed = {}; + for (int i = 0; i < epoch_number; ++i) + epoch_seed = ethash_keccak256_32(epoch_seed.bytes); + return epoch_seed; } - return {}; -} -search_result search(const epoch_context_full& context, const hash256& header_hash, - const hash256& boundary, uint64_t start_nonce, size_t iterations) noexcept -{ - const uint64_t end_nonce = start_nonce + iterations; - for (uint64_t nonce = start_nonce; nonce < end_nonce; ++nonce) + int ethash_calculate_light_cache_num_items(int epoch_number) noexcept { - result r = hash(context, header_hash, nonce); - if (is_less_or_equal(r.final_hash, boundary)) - return {r, nonce}; + static constexpr int item_size = sizeof(hash512); + static constexpr int num_items_init = light_cache_init_size / item_size; + static constexpr int num_items_growth = light_cache_growth / item_size; + static_assert( + light_cache_init_size % item_size == 0, "light_cache_init_size not multiple of item size"); + static_assert( + light_cache_growth % item_size == 0, "light_cache_growth not multiple of item size"); + + int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; + int num_items = ethash_find_largest_prime(num_items_upper_bound); + return num_items; } - return {}; -} -} // namespace ethash - -using namespace ethash; - -extern "C" { -ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) noexcept -{ - ethash_hash256 epoch_seed = {}; - for (int i = 0; i < epoch_number; ++i) - epoch_seed = ethash_keccak256_32(epoch_seed.bytes); - return epoch_seed; -} - -int ethash_calculate_light_cache_num_items(int epoch_number) noexcept -{ - static constexpr int item_size = sizeof(hash512); - static constexpr int num_items_init = light_cache_init_size / item_size; - static constexpr int num_items_growth = light_cache_growth / item_size; - static_assert( - light_cache_init_size % item_size == 0, "light_cache_init_size not multiple of item size"); - static_assert( - light_cache_growth % item_size == 0, "light_cache_growth not multiple of item size"); - - int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; - int num_items = ethash_find_largest_prime(num_items_upper_bound); - return num_items; -} - -int ethash_calculate_full_dataset_num_items(int epoch_number) noexcept -{ - static constexpr int item_size = sizeof(hash1024); - static constexpr int num_items_init = full_dataset_init_size / item_size; - static constexpr int num_items_growth = full_dataset_growth / item_size; - static_assert(full_dataset_init_size % item_size == 0, - "full_dataset_init_size not multiple of item size"); - static_assert( - full_dataset_growth % item_size == 0, "full_dataset_growth not multiple of item size"); - - int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; - int num_items = ethash_find_largest_prime(num_items_upper_bound); - return num_items; -} - -epoch_context* ethash_create_epoch_context(int epoch_number) noexcept -{ - return generic::create_epoch_context(build_light_cache, epoch_number, false); -} + int ethash_calculate_full_dataset_num_items(int epoch_number) noexcept + { + static constexpr int item_size = sizeof(hash1024); + static constexpr int num_items_init = full_dataset_init_size / item_size; + static constexpr int num_items_growth = full_dataset_growth / item_size; + static_assert(full_dataset_init_size % item_size == 0, + "full_dataset_init_size not multiple of item size"); + static_assert( + full_dataset_growth % item_size == 0, "full_dataset_growth not multiple of item size"); + + int num_items_upper_bound = num_items_init + epoch_number * num_items_growth; + int num_items = ethash_find_largest_prime(num_items_upper_bound); + return num_items; + } -epoch_context_full* ethash_create_epoch_context_full(int epoch_number) noexcept -{ - return generic::create_epoch_context(build_light_cache, epoch_number, true); -} + epoch_context *ethash_create_epoch_context(int epoch_number) noexcept + { + return generic::create_epoch_context(build_light_cache, epoch_number, false); + } -void ethash_destroy_epoch_context_full(epoch_context_full* context) noexcept -{ - ethash_destroy_epoch_context(context); -} + epoch_context_full *ethash_create_epoch_context_full(int epoch_number) noexcept + { + return generic::create_epoch_context(build_light_cache, epoch_number, true); + } -void ethash_destroy_epoch_context(epoch_context* context) noexcept -{ - context->~epoch_context(); - std::free(context); -} + void ethash_destroy_epoch_context_full(epoch_context_full *context) noexcept + { + ethash_destroy_epoch_context(context); + } + void ethash_destroy_epoch_context(epoch_context *context) noexcept + { + context->~epoch_context(); + std::free(context); + } -ethash_result ethash_hash( - const epoch_context* context, const hash256* header_hash, uint64_t nonce) noexcept -{ - const hash512 seed = hash_seed(*header_hash, nonce); - const hash256 mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); - return {hash_final(seed, mix_hash), mix_hash}; -} + ethash_result ethash_hash( + const epoch_context *context, const hash256 *header_hash, uint64_t nonce) noexcept + { + const hash512 seed = hash_seed(*header_hash, nonce); + const hash256 mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); + return {hash_final(seed, mix_hash), mix_hash}; + } -bool ethash_verify_final_hash(const hash256* header_hash, const hash256* mix_hash, uint64_t nonce, - const hash256* boundary) noexcept -{ - const hash512 seed = hash_seed(*header_hash, nonce); - return is_less_or_equal(hash_final(seed, *mix_hash), *boundary); -} + bool ethash_verify_final_hash(const hash256 *header_hash, const hash256 *mix_hash, uint64_t nonce, + const hash256 *boundary) noexcept + { + const hash512 seed = hash_seed(*header_hash, nonce); + return is_less_or_equal(hash_final(seed, *mix_hash), *boundary); + } -bool ethash_verify(const epoch_context* context, const hash256* header_hash, - const hash256* mix_hash, uint64_t nonce, const hash256* boundary) noexcept -{ - const hash512 seed = hash_seed(*header_hash, nonce); - if (!is_less_or_equal(hash_final(seed, *mix_hash), *boundary)) - return false; + bool ethash_verify(const epoch_context *context, const hash256 *header_hash, + const hash256 *mix_hash, uint64_t nonce, const hash256 *boundary) noexcept + { + const hash512 seed = hash_seed(*header_hash, nonce); + if (!is_less_or_equal(hash_final(seed, *mix_hash), *boundary)) + return false; - const hash256 expected_mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); - return is_equal(expected_mix_hash, *mix_hash); -} + const hash256 expected_mix_hash = hash_kernel(*context, seed, calculate_dataset_item_1024); + return is_equal(expected_mix_hash, *mix_hash); + } -} // extern "C" +} // extern "C" diff --git a/src/Native/libkawpow/ethash/ethash.h b/src/Native/libkawpow/ethash/ethash.h index 5d4230f6e..1b653ef8e 100644 --- a/src/Native/libkawpow/ethash/ethash.h +++ b/src/Native/libkawpow/ethash/ethash.h @@ -32,7 +32,8 @@ #endif #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif /** @@ -46,99 +47,91 @@ extern "C" { #define ETHASH_FULL_DATASET_ITEM_SIZE 128 #define ETHASH_NUM_DATASET_ACCESSES 64 - -struct ethash_epoch_context -{ - const int epoch_number; - const int light_cache_num_items; - const union ethash_hash512* const light_cache; - const uint32_t* const l1_cache; - const int full_dataset_num_items; -}; - - -struct ethash_epoch_context_full; - - -struct ethash_result -{ - union ethash_hash256 final_hash; - union ethash_hash256 mix_hash; -}; - - -/** - * Calculates the number of items in the light cache for given epoch. - * - * This function will search for a prime number matching the criteria given - * by the Ethash so the execution time is not constant. It takes ~ 0.01 ms. - * - * @param epoch_number The epoch number. - * @return The number items in the light cache. - */ -int ethash_calculate_light_cache_num_items(int epoch_number) NOEXCEPT; - - -/** - * Calculates the number of items in the full dataset for given epoch. - * - * This function will search for a prime number matching the criteria given - * by the Ethash so the execution time is not constant. It takes ~ 0.05 ms. - * - * @param epoch_number The epoch number. - * @return The number items in the full dataset. - */ -int ethash_calculate_full_dataset_num_items(int epoch_number) NOEXCEPT; - -/** - * Calculates the epoch seed hash. - * @param epoch_number The epoch number. - * @return The epoch seed hash. - */ -union ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) NOEXCEPT; - - -EXPORT struct ethash_epoch_context* ethash_create_epoch_context(int epoch_number) NOEXCEPT; - -/** - * Creates the epoch context with the full dataset initialized. - * - * The memory for the full dataset is only allocated and marked as "not-generated". - * The items of the full dataset are generated on the fly when hit for the first time. - * - * The memory allocated in the context MUST be freed with ethash_destroy_epoch_context_full(). - * - * @param epoch_number The epoch number. - * @return Pointer to the context or null in case of memory allocation failure. - */ -EXPORT struct ethash_epoch_context_full* ethash_create_epoch_context_full(int epoch_number) NOEXCEPT; - -EXPORT void ethash_destroy_epoch_context(struct ethash_epoch_context* context) NOEXCEPT; - -EXPORT void ethash_destroy_epoch_context_full(struct ethash_epoch_context_full* context) NOEXCEPT; - - -/** - * Get global shared epoch context. - */ -const struct ethash_epoch_context* ethash_get_global_epoch_context(int epoch_number) NOEXCEPT; - -/** - * Get global shared epoch context with full dataset initialized. - */ -const struct ethash_epoch_context_full* ethash_get_global_epoch_context_full( - int epoch_number) NOEXCEPT; - - -struct ethash_result ethash_hash(const struct ethash_epoch_context* context, - const union ethash_hash256* header_hash, uint64_t nonce) NOEXCEPT; - -bool ethash_verify(const struct ethash_epoch_context* context, - const union ethash_hash256* header_hash, const union ethash_hash256* mix_hash, uint64_t nonce, - const union ethash_hash256* boundary) NOEXCEPT; -bool ethash_verify_final_hash(const union ethash_hash256* header_hash, - const union ethash_hash256* mix_hash, uint64_t nonce, - const union ethash_hash256* boundary) NOEXCEPT; + struct ethash_epoch_context + { + const int epoch_number; + const int light_cache_num_items; + const union ethash_hash512 *const light_cache; + const uint32_t *const l1_cache; + const int full_dataset_num_items; + }; + + struct ethash_epoch_context_full; + + struct ethash_result + { + union ethash_hash256 final_hash; + union ethash_hash256 mix_hash; + }; + + /** + * Calculates the number of items in the light cache for given epoch. + * + * This function will search for a prime number matching the criteria given + * by the Ethash so the execution time is not constant. It takes ~ 0.01 ms. + * + * @param epoch_number The epoch number. + * @return The number items in the light cache. + */ + int ethash_calculate_light_cache_num_items(int epoch_number) NOEXCEPT; + + /** + * Calculates the number of items in the full dataset for given epoch. + * + * This function will search for a prime number matching the criteria given + * by the Ethash so the execution time is not constant. It takes ~ 0.05 ms. + * + * @param epoch_number The epoch number. + * @return The number items in the full dataset. + */ + int ethash_calculate_full_dataset_num_items(int epoch_number) NOEXCEPT; + + /** + * Calculates the epoch seed hash. + * @param epoch_number The epoch number. + * @return The epoch seed hash. + */ + EXPORT union ethash_hash256 ethash_calculate_epoch_seed(int epoch_number) NOEXCEPT; + + EXPORT struct ethash_epoch_context *ethash_create_epoch_context(int epoch_number) NOEXCEPT; + + /** + * Creates the epoch context with the full dataset initialized. + * + * The memory for the full dataset is only allocated and marked as "not-generated". + * The items of the full dataset are generated on the fly when hit for the first time. + * + * The memory allocated in the context MUST be freed with ethash_destroy_epoch_context_full(). + * + * @param epoch_number The epoch number. + * @return Pointer to the context or null in case of memory allocation failure. + */ + EXPORT struct ethash_epoch_context_full *ethash_create_epoch_context_full(int epoch_number) NOEXCEPT; + + EXPORT void ethash_destroy_epoch_context(struct ethash_epoch_context *context) NOEXCEPT; + + EXPORT void ethash_destroy_epoch_context_full(struct ethash_epoch_context_full *context) NOEXCEPT; + + /** + * Get global shared epoch context. + */ + const struct ethash_epoch_context *ethash_get_global_epoch_context(int epoch_number) NOEXCEPT; + + /** + * Get global shared epoch context with full dataset initialized. + */ + const struct ethash_epoch_context_full *ethash_get_global_epoch_context_full( + int epoch_number) NOEXCEPT; + + struct ethash_result ethash_hash(const struct ethash_epoch_context *context, + const union ethash_hash256 *header_hash, uint64_t nonce) NOEXCEPT; + + bool ethash_verify(const struct ethash_epoch_context *context, + const union ethash_hash256 *header_hash, const union ethash_hash256 *mix_hash, uint64_t nonce, + const union ethash_hash256 *boundary) NOEXCEPT; + bool ethash_verify_final_hash(const union ethash_hash256 *header_hash, + const union ethash_hash256 *mix_hash, uint64_t nonce, + const union ethash_hash256 *boundary) NOEXCEPT; #ifdef __cplusplus } diff --git a/src/Native/libkawpow/ethash/progpow.hpp b/src/Native/libkawpow/ethash/progpow.hpp index f1de8222e..59b07c3c6 100644 --- a/src/Native/libkawpow/ethash/progpow.hpp +++ b/src/Native/libkawpow/ethash/progpow.hpp @@ -27,42 +27,41 @@ namespace progpow { -using namespace ethash; // Include ethash namespace. + using namespace ethash; // Include ethash namespace. + /// The ProgPoW algorithm revision implemented as specified in the spec + /// https://github.com/ifdefelse/ProgPOW#change-history. + constexpr auto revision = "0.9.4"; -/// The ProgPoW algorithm revision implemented as specified in the spec -/// https://github.com/ifdefelse/ProgPOW#change-history. -constexpr auto revision = "0.9.4"; + constexpr int period_length = 3; + constexpr uint32_t num_regs = 32; + constexpr size_t num_lanes = 16; + constexpr int num_cache_accesses = 11; + constexpr int num_math_operations = 18; + constexpr size_t l1_cache_size = 16 * 1024; + constexpr size_t l1_cache_num_items = l1_cache_size / sizeof(uint32_t); -constexpr int period_length = 3; -constexpr uint32_t num_regs = 32; -constexpr size_t num_lanes = 16; -constexpr int num_cache_accesses = 11; -constexpr int num_math_operations = 18; -constexpr size_t l1_cache_size = 16 * 1024; -constexpr size_t l1_cache_num_items = l1_cache_size / sizeof(uint32_t); + result hashext(const epoch_context &context, int block_number, const hash256 &header_hash, + uint64_t nonce, const hash256 &mix_hash, const hash256 &boundary1, const hash256 &boundary2, int *retcode) noexcept; -extern "C" EXPORT result hashext(const epoch_context& context, int block_number, const hash256& header_hash, - uint64_t nonce, const hash256& mix_hash, const hash256& boundary1, const hash256& boundary2, int* retcode) noexcept; + extern "C" EXPORT result hash(const epoch_context &context, int block_number, const hash256 &header_hash, + uint64_t nonce) noexcept; -result hash(const epoch_context& context, int block_number, const hash256& header_hash, - uint64_t nonce) noexcept; + result hash(const epoch_context_full &context, int block_number, const hash256 &header_hash, + uint64_t nonce) noexcept; -result hash(const epoch_context_full& context, int block_number, const hash256& header_hash, - uint64_t nonce) noexcept; + extern "C" EXPORT bool verify(const epoch_context &context, int block_number, const hash256 &header_hash, + const hash256 &mix_hash, uint64_t nonce, const hash256 &boundary) noexcept; -extern "C" EXPORT bool verify(const epoch_context& context, int block_number, const hash256& header_hash, - const hash256& mix_hash, uint64_t nonce, const hash256& boundary) noexcept; + // bool light_verify(const char* str_header_hash, + // const char* str_mix_hash, const char* str_nonce, const char* str_boundary, char* str_final) noexcept; -//bool light_verify(const char* str_header_hash, -// const char* str_mix_hash, const char* str_nonce, const char* str_boundary, char* str_final) noexcept; + search_result search_light(const epoch_context &context, int block_number, + const hash256 &header_hash, const hash256 &boundary, uint64_t start_nonce, + size_t iterations) noexcept; -search_result search_light(const epoch_context& context, int block_number, - const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, - size_t iterations) noexcept; + search_result search(const epoch_context_full &context, int block_number, + const hash256 &header_hash, const hash256 &boundary, uint64_t start_nonce, + size_t iterations) noexcept; -search_result search(const epoch_context_full& context, int block_number, - const hash256& header_hash, const hash256& boundary, uint64_t start_nonce, - size_t iterations) noexcept; - -} // namespace progpow +} // namespace progpow