From 64e8d16d039533f3f5480f7677b18a20ec505384 Mon Sep 17 00:00:00 2001 From: ceedii Date: Fri, 26 Apr 2024 14:01:00 +0000 Subject: [PATCH] Add support for more KAS forks (BGA, HTN, CAS, NTL, NXL, SDR) Fix block classification issue in the Ethereum family when the node is not connected to the network Fix Nicehash support for ALPH --- .../Blockchain/Alephium/AlephiumPool.cs | 68 ++++++++++++-- .../Ethereum/EthereumPayoutHandler.cs | 66 ++++++++----- .../Blockchain/Kaspa/KaspaConstants.cs | 56 ++++++++++- .../Blockchain/Kaspa/KaspaJobManager.cs | 44 ++++++++- src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs | 62 +++++++++++- .../Configuration/ClusterConfigExtensions.cs | 4 + .../Crypto/Hashing/Algorithms/FishHash.cs | 7 +- .../Hashing/Algorithms/FishHashKarlsen.cs | 7 +- src/Miningcore/Native/Multihash.cs | 5 +- src/Miningcore/coins.json | 94 ++++++++++++++++++- src/Native/libmultihash/fishhash/fishhash.cpp | 61 ++++++++++-- src/Native/libmultihash/fishhash/fishhash.h | 3 +- 12 files changed, 422 insertions(+), 55 deletions(-) diff --git a/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs b/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs index 07b988eaf..416394329 100644 --- a/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs +++ b/src/Miningcore/Blockchain/Alephium/AlephiumPool.cs @@ -64,10 +64,21 @@ protected virtual async Task OnHelloAsync(StratumConnection connection, Timestam blockchainStats.NodeVersion }; - await connection.RespondAsync(data, request.Id); - context.UserAgent = requestParams.FirstOrDefault()?.Trim(); + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(data, request.Id); + + if(context.IsNicehash) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + await connection.RespondAsync(response); + logger.Info(() => $"[{connection.ConnectionId}] Hello {context.UserAgent}"); } @@ -81,8 +92,6 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time var context = connection.ContextAs(); var requestParams = request.ParamsAs(); - await connection.RespondAsync(connection.ConnectionId, request.Id); - // setup worker context context.IsSubscribed = true; // If the user agent was set by a mining.hello, we don't want to overwrite that (to match actual protocol) @@ -90,6 +99,19 @@ protected virtual async Task OnSubscribeAsync(StratumConnection connection, Time { context.UserAgent = requestParams.FirstOrDefault()?.Trim(); } + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(connection.ConnectionId, request.Id); + + if(context.IsNicehash) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + await connection.RespondAsync(response); } protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Timestamped tsRequest, CancellationToken ct) @@ -122,12 +144,27 @@ protected virtual async Task OnAuthorizeAsync(StratumConnection connection, Time { // setup worker context context.IsSubscribed = true; - context.UserAgent = requestParams.FirstOrDefault()?.Trim(); + // If the user agent was set by a mining.hello, we don't want to overwrite that (to match actual protocol) + if (string.IsNullOrEmpty(context.UserAgent)) + { + context.UserAgent = requestParams?.Length > 2 ? requestParams[2] : requestParams.FirstOrDefault()?.Trim(); + } } - + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(context.IsAuthorized, request.Id); + + if(context.IsNicehash) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + // respond - await connection.RespondAsync(context.IsAuthorized, request.Id); - + await connection.RespondAsync(response); + // send extranonce await connection.NotifyAsync(AlephiumStratumMethods.SetExtraNonce, manager.GetSubscriberData(connection)); @@ -216,7 +253,20 @@ protected virtual async Task OnSubmitAsync(StratumConnection connection, Timesta // submit var share = await manager.SubmitShareAsync(connection, requestParams, ct); - await connection.RespondAsync(true, request.Id); + + // Nicehash's stupid validator insists on "error" property present + // in successful responses which is a violation of the JSON-RPC spec + // [Respect the goddamn standards Nicehack :(] + var response = new JsonRpcResponse(true, request.Id); + + if(context.IsNicehash) + { + response.Extra = new Dictionary(); + response.Extra["error"] = null; + } + + // respond + await connection.RespondAsync(response); // publish messageBus.SendMessage(share); diff --git a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs index 32f2c1c1b..8156ca0fa 100644 --- a/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs +++ b/src/Miningcore/Blockchain/Ethereum/EthereumPayoutHandler.cs @@ -79,6 +79,11 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, Contract.RequiresNonNull(poolConfig); Contract.RequiresNonNull(blocks); + // ensure we have enough peers + var enoughPeers = await EnsureDaemonsSynchedAsync(ct); + if(!enoughPeers) + return blocks; + var coin = poolConfig.Template.As(); var pageSize = 100; var pageCount = (int) Math.Ceiling(blocks.Length / (double) pageSize); @@ -250,6 +255,7 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, block.Hash = uncle.Hash; block.Reward = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value); + block.BlockHeight = uncle.Height.Value; // There is a rare case-scenario where an Uncle has a block reward of zero // We must handle it carefully otherwise payout will be stuck forever @@ -257,7 +263,6 @@ public async Task ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, { block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; - block.BlockHeight = uncle.Height.Value; } else { @@ -307,16 +312,10 @@ public override async Task UpdateBlockRewardBalancesAsync(IDbConnection public async Task PayoutAsync(IMiningPool pool, Balance[] balances, CancellationToken ct) { - // ensure we have peers - var infoResponse = await rpcClient.ExecuteAsync(logger, EC.GetPeerCount, ct); - - if((networkType == EthereumNetworkType.Main || extraPoolConfig?.ChainTypeOverride == "Classic" || extraPoolConfig?.ChainTypeOverride == "Mordor" || networkType == EthereumNetworkType.MainPow || extraPoolConfig?.ChainTypeOverride == "Ubiq" || extraPoolConfig?.ChainTypeOverride == "EtherOne" || extraPoolConfig?.ChainTypeOverride == "Pink" || extraPoolConfig?.ChainTypeOverride == "OctaSpace" || extraPoolConfig?.ChainTypeOverride == "OctaSpaceTestnet" || extraPoolConfig?.ChainTypeOverride == "Hypra") && - (infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) || - infoResponse.Response.IntegralFromHex() < EthereumConstants.MinPayoutPeerCount)) - { - logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peers (4 required)"); + // ensure we have enough peers + var enoughPeers = await EnsureDaemonsSynchedAsync(ct); + if(!enoughPeers) return; - } var txHashes = new List(); @@ -347,6 +346,22 @@ public double AdjustBlockEffort(double effort) #endregion // IPayoutHandler + private async Task EnsureDaemonsSynchedAsync(CancellationToken ct) + { + // ensure we have enough peers + var infoResponse = await rpcClient.ExecuteAsync(logger, EC.GetPeerCount, ct); + + if((networkType == EthereumNetworkType.Main || extraPoolConfig?.ChainTypeOverride == "Classic" || extraPoolConfig?.ChainTypeOverride == "Mordor" || networkType == EthereumNetworkType.MainPow || extraPoolConfig?.ChainTypeOverride == "Ubiq" || extraPoolConfig?.ChainTypeOverride == "EtherOne" || extraPoolConfig?.ChainTypeOverride == "Pink" || extraPoolConfig?.ChainTypeOverride == "OctaSpace" || extraPoolConfig?.ChainTypeOverride == "OctaSpaceTestnet" || extraPoolConfig?.ChainTypeOverride == "Hypra") && + (infoResponse.Error != null || string.IsNullOrEmpty(infoResponse.Response) || + infoResponse.Response.IntegralFromHex() < EthereumConstants.MinPayoutPeerCount)) + { + logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peer(s) ({EthereumConstants.MinPayoutPeerCount} required)"); + return false; + } + + return true; + } + private async Task FetchBlocks(Dictionary blockCache, CancellationToken ct, params long[] blockHeights) { var cacheMisses = blockHeights.Where(x => !blockCache.ContainsKey(x)).ToArray(); @@ -491,30 +506,37 @@ internal static decimal GetUncleReward(GethChainType chainType, ulong uheight, u case GethChainType.Mordor: double heightEraLength = height / EthereumClassicConstants.EraLength; double era = Math.Floor(heightEraLength); - if (era == 0) { - reward *= uheight + 8 - height; + + if(era == 0) + { + if(uheight != height) + reward *= uheight + 8 - height; + reward /= 8m; } - else { + else + { reward /= 32m; } - + break; case GethChainType.Ubiq: - reward = (uheight == height) ? reward / 2 : (uheight + 2 - height) * reward / 2; - // blocks older than the previous block are not rewarded - if (reward < 0) - reward = 0; - + if(uheight != height) + reward *= uheight + 2 - height; + + reward /= 2m; + break; case GethChainType.Hypra: reward = 0.1m; - + break; default: - reward *= uheight + 8 - height; + if(uheight != height) + reward *= uheight + 8 - height; + reward /= 8m; - + break; } diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs b/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs index 31dc9301a..13d7b8ebc 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaConstants.cs @@ -6,6 +6,24 @@ namespace Miningcore.Blockchain.Kaspa; +public static class BugnaConstants +{ + // List of BGA prefixes: https://github.com/bugnanetwork/bugnad/blob/master/util/address.go + public const string ChainPrefixDevnet = "bugnadev"; + public const string ChainPrefixSimnet = "bugnasim"; + public const string ChainPrefixTestnet = "bugnatest"; + public const string ChainPrefixMainnet = "bugna"; +} + +public static class HoosatConstants +{ + // List of HTN prefixes: https://github.com/Hoosat-Oy/HTND/blob/master/util/address.go + public const string ChainPrefixDevnet = "htndev"; + public const string ChainPrefixSimnet = "hoosatsim"; + public const string ChainPrefixTestnet = "hoosattest"; + public const string ChainPrefixMainnet = "hoosat"; +} + public static class KaspaConstants { public const string WalletDaemonCategory = "wallet"; @@ -57,6 +75,15 @@ public static class KaspaConstants public const int Blake2bSize256 = 32; } +public static class KaspaClassicConstants +{ + // List of CAS prefixes: https://github.com/kaspaclassic/caspad/blob/main/util/address.go + public const string ChainPrefixDevnet = "caspadev"; + public const string ChainPrefixSimnet = "pyrinsim"; + public const string ChainPrefixTestnet = "pyrintest"; + public const string ChainPrefixMainnet = "cas"; +} + public static class KarlsencoinConstants { // List of KLS prefixes: https://github.com/karlsen-network/karlsend/blob/master/util/address.go @@ -66,7 +93,25 @@ public static class KarlsencoinConstants public const string ChainPrefixMainnet = "karlsen"; public const long FishHashForkHeightTestnet = 0; - public const long FishHashForkHeightMainnet = 999999999999; + public const long FishHashPlusForkHeightTestnet = 6000000; +} + +public static class NautilusConstants +{ + // List of NTL prefixes: https://github.com/Nautilus-Network/nautiliad/blob/master/util/address.go + public const string ChainPrefixDevnet = "nautiliadev"; + public const string ChainPrefixSimnet = "nautilussim"; + public const string ChainPrefixTestnet = "nautilustest"; + public const string ChainPrefixMainnet = "nautilus"; +} + +public static class NexelliaConstants +{ + // List of NXL prefixes: https://github.com/Nexellia-Network/nexelliad/blob/master/util/address.go + public const string ChainPrefixDevnet = "nexelliadev"; + public const string ChainPrefixSimnet = "nexelliasim"; + public const string ChainPrefixTestnet = "nexelliatest"; + public const string ChainPrefixMainnet = "nexellia"; } public static class PyrinConstants @@ -80,6 +125,15 @@ public static class PyrinConstants public const long Blake3ForkHeight = 1484741; } +public static class SedraCoinConstants +{ + // List of HTN prefixes: https://github.com/sedracoin/sedrad/blob/main/util/address.go + public const string ChainPrefixDevnet = "sedradev"; + public const string ChainPrefixSimnet = "sedrasim"; + public const string ChainPrefixTestnet = "sedratest"; + public const string ChainPrefixMainnet = "sedra"; +} + public enum KaspaBech32Prefix { Unknown = 0, diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs b/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs index c175d08f1..65602ccc8 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaJobManager.cs @@ -215,6 +215,22 @@ private KaspaJob CreateJob(long blockHeight) { switch(coin.Symbol) { + case "CAS": + case "HTN": + if(customBlockHeaderHasher is not Blake3) + { + string coinbaseBlockHash = KaspaConstants.CoinbaseBlockHash; + byte[] hashBytes = Encoding.UTF8.GetBytes(coinbaseBlockHash.PadRight(32, '\0')).Take(32).ToArray(); + customBlockHeaderHasher = new Blake3(hashBytes); + } + + if(customCoinbaseHasher is not Blake3) + customCoinbaseHasher = new Blake3(); + + if(customShareHasher is not Blake3) + customShareHasher = new Blake3(); + + return new PyrinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); case "KLS": var karlsenNetwork = network.ToLower(); @@ -224,7 +240,21 @@ private KaspaJob CreateJob(long blockHeight) if(customCoinbaseHasher is not Blake3) customCoinbaseHasher = new Blake3(); - if ((karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashForkHeightTestnet)) + if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashPlusForkHeightTestnet) + { + logger.Debug(() => $"fishHashPlusHardFork activated"); + + if(customShareHasher is not FishHashKarlsen) + { + var started = DateTime.Now; + logger.Debug(() => $"Generating light cache"); + + customShareHasher = new FishHashKarlsen(true); + + logger.Debug(() => $"Done generating light cache after {DateTime.Now - started}"); + } + } + else if(karlsenNetwork == "testnet" && blockHeight >= KarlsencoinConstants.FishHashForkHeightTestnet) { logger.Debug(() => $"fishHashHardFork activated"); @@ -242,6 +272,18 @@ private KaspaJob CreateJob(long blockHeight) if(customShareHasher is not CShake256) customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash)); + return new KarlsencoinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); + case "NTL": + case "NXL": + if(customBlockHeaderHasher is not Blake2b) + customBlockHeaderHasher = new Blake2b(Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseBlockHash)); + + if(customCoinbaseHasher is not Blake3) + customCoinbaseHasher = new Blake3(); + + if(customShareHasher is not CShake256) + customShareHasher = new CShake256(null, Encoding.UTF8.GetBytes(KaspaConstants.CoinbaseHeavyHash)); + return new KarlsencoinJob(customBlockHeaderHasher, customCoinbaseHasher, customShareHasher); case "PYI": if(blockHeight >= PyrinConstants.Blake3ForkHeight) diff --git a/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs b/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs index 2e988ad64..b32b92a8f 100644 --- a/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs +++ b/src/Miningcore/Blockchain/Kaspa/KaspaUtils.cs @@ -354,6 +354,36 @@ public KaspaAddressUtility(string coinSymbol = "KAS") // Build address pattern based on network type and coin symbol switch(this.CoinSymbol) { + case "BGA": + this.stringsToBech32Prefixes = new Dictionary + { + { BugnaConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { BugnaConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { BugnaConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { BugnaConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + + break; + case "CAS": + this.stringsToBech32Prefixes = new Dictionary + { + { KaspaClassicConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { KaspaClassicConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { KaspaClassicConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { KaspaClassicConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + + break; + case "HTN": + this.stringsToBech32Prefixes = new Dictionary + { + { HoosatConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { HoosatConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { HoosatConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { HoosatConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + + break; case "KLS": this.stringsToBech32Prefixes = new Dictionary { @@ -362,7 +392,27 @@ public KaspaAddressUtility(string coinSymbol = "KAS") { KarlsencoinConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, { KarlsencoinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, }; - + + break; + case "NTL": + this.stringsToBech32Prefixes = new Dictionary + { + { NautilusConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { NautilusConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { NautilusConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { NautilusConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + + break; + case "NXL": + this.stringsToBech32Prefixes = new Dictionary + { + { NexelliaConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { NexelliaConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { NexelliaConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { NexelliaConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + break; case "PYI": this.stringsToBech32Prefixes = new Dictionary @@ -373,6 +423,16 @@ public KaspaAddressUtility(string coinSymbol = "KAS") { PyrinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, }; + break; + case "SDR": + this.stringsToBech32Prefixes = new Dictionary + { + { SedraCoinConstants.ChainPrefixMainnet, KaspaBech32Prefix.KaspaMain }, + { SedraCoinConstants.ChainPrefixDevnet, KaspaBech32Prefix.KaspaDev }, + { SedraCoinConstants.ChainPrefixTestnet, KaspaBech32Prefix.KaspaTest }, + { SedraCoinConstants.ChainPrefixSimnet, KaspaBech32Prefix.KaspaSim }, + }; + break; default: this.stringsToBech32Prefixes = new Dictionary diff --git a/src/Miningcore/Configuration/ClusterConfigExtensions.cs b/src/Miningcore/Configuration/ClusterConfigExtensions.cs index 1da7175b4..519466ffc 100644 --- a/src/Miningcore/Configuration/ClusterConfigExtensions.cs +++ b/src/Miningcore/Configuration/ClusterConfigExtensions.cs @@ -259,7 +259,11 @@ public override string GetAlgorithmName() switch(Symbol) { case "KLS": + case "NTL": + case "NXL": return "Karlsenhash"; + case "CAS": + case "HTN": case "PYI": return "Pyrinhash"; default: diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs b/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs index b0e97dbce..2705e3835 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/FishHash.cs @@ -6,11 +6,14 @@ namespace Miningcore.Crypto.Hashing.Algorithms; [Identifier("fishhash")] public unsafe class FishHash : IHashAlgorithm { + private bool enableFishHashPlus = false; private IntPtr handle = IntPtr.Zero; private readonly object genLock = new(); - public FishHash(bool fullContext = false, uint threads = 4) + public FishHash(bool enableFishHashPlus = false, bool fullContext = false, uint threads = 4) { + this.enableFishHashPlus = enableFishHashPlus; + lock(genLock) { this.handle = Multihash.fishhashGetContext(fullContext); @@ -28,7 +31,7 @@ public void Digest(ReadOnlySpan data, Span result, params object[] e { fixed (byte* output = result) { - Multihash.fishhash(output, this.handle, input, (uint) data.Length); + Multihash.fishhash(output, this.handle, input, (uint) data.Length, this.enableFishHashPlus); } } } diff --git a/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs b/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs index dc216872e..01e6e867e 100644 --- a/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs +++ b/src/Miningcore/Crypto/Hashing/Algorithms/FishHashKarlsen.cs @@ -6,11 +6,14 @@ namespace Miningcore.Crypto.Hashing.Algorithms; [Identifier("fishhashkarlsen")] public unsafe class FishHashKarlsen : IHashAlgorithm { + private bool enableFishHashPlus = false; private IntPtr handle = IntPtr.Zero; private readonly object genLock = new(); - public FishHashKarlsen(bool fullContext = false, uint threads = 4) + public FishHashKarlsen(bool enableFishHashPlus = false, bool fullContext = false, uint threads = 4) { + this.enableFishHashPlus = enableFishHashPlus; + lock(genLock) { this.handle = Multihash.fishhashGetContext(fullContext); @@ -33,7 +36,7 @@ public void Digest(ReadOnlySpan data, Span result, params object[] e var seed = new Multihash.Fishhash_hash512(); seed.bytes = seedBytes.ToArray(); - mixHash = Multihash.fishhashKernel(this.handle, ref seed); + mixHash = (this.enableFishHashPlus) ? Multihash.fishhashplusKernel(this.handle, ref seed) : Multihash.fishhashKernel(this.handle, ref seed); fixed(byte* input = mixHash.bytes) { diff --git a/src/Miningcore/Native/Multihash.cs b/src/Miningcore/Native/Multihash.cs index e3e3cb570..f8f0774f3 100644 --- a/src/Miningcore/Native/Multihash.cs +++ b/src/Miningcore/Native/Multihash.cs @@ -213,12 +213,15 @@ public static unsafe class Multihash [DllImport("libmultihash", EntryPoint = "fishhash_kernel", CallingConvention = CallingConvention.Cdecl)] public static extern Fishhash_hash256 fishhashKernel(IntPtr context, ref Fishhash_hash512 seed); + + [DllImport("libmultihash", EntryPoint = "fishhashplus_kernel", CallingConvention = CallingConvention.Cdecl)] + public static extern Fishhash_hash256 fishhashplusKernel(IntPtr context, ref Fishhash_hash512 seed); [DllImport("libmultihash", EntryPoint = "fishhash_prebuild_dataset", CallingConvention = CallingConvention.Cdecl)] public static extern void fishhashPrebuildDataset(IntPtr context, uint number_threads = 1); [DllImport("libmultihash", EntryPoint = "fishhash_hash", CallingConvention = CallingConvention.Cdecl)] - public static extern void fishhash(void* output, IntPtr context, byte* input, uint inputLength); + public static extern void fishhash(void* output, IntPtr context, byte* input, uint inputLength, bool enableFishHashPlus = false); [StructLayout(LayoutKind.Explicit)] public struct Fishhash_hash256 diff --git a/src/Miningcore/coins.json b/src/Miningcore/coins.json index 42bfd0ba5..640001642 100644 --- a/src/Miningcore/coins.json +++ b/src/Miningcore/coins.json @@ -2420,9 +2420,9 @@ "hasCommunityAddress": true, "shareMultiplier": 1, "progpower": "meowpow", - "explorerBlockLink": "https://explorer.mewccrypto.com/block/$hash$", - "explorerTxLink": "https://explorer.mewccrypto.com/tx/{0}", - "explorerAccountLink": "https://explorer.mewccrypto.com/address/{0}" + "explorerBlockLink": "https://mewc.cryptoscope.io/block/?blockhash=$hash$", + "explorerTxLink": "https://mewc.cryptoscope.io/tx/?txid={0}", + "explorerAccountLink": "https://mewc.cryptoscope.io/address/?address={0}" }, "aipg": { "name": "AI Power Grid", @@ -4880,6 +4880,34 @@ "explorerTxLink": "https://explorer.alephium.org/transactions/{0}", "explorerAccountLink": "https://explorer.alephium.org/addresses/{0}" }, + "bugna": { + "name": "Bugna Network", + "canonicalName": "Bugna Network", + "symbol": "BGA", + "family": "kaspa", + "website": "https://bugna.org/", + "market": "", + "twitter": "https://twitter.com/bugnanetwork", + "telegram": "", + "discord": "https://discord.com/invite/YxDF2EMKTq", + "explorerBlockLink": "https://explorer.bugna.org/blocks/$hash$", + "explorerTxLink": "https://explorer.bugna.org/txs/{0}", + "explorerAccountLink": "https://explorer.bugna.org/addresses/{0}" + }, + "hoosat": { + "name": "Hoosat", + "canonicalName": "Hoosat", + "symbol": "HTN", + "family": "kaspa", + "website": "https://network.hoosat.fi/", + "market": "", + "twitter": "https://twitter.com/HoosatNetwork", + "telegram": "https://t.me/HoosatNetwork", + "discord": "https://discord.com/invite/NzENEkxEqY", + "explorerBlockLink": "https://explorer.hoosat.fi/blocks/$hash$", + "explorerTxLink": "https://explorer.hoosat.fi/txs/{0}", + "explorerAccountLink": "https://explorer.hoosat.fi/addresses/{0}" + }, "kaspa": { "name": "Kaspa", "canonicalName": "Kaspa", @@ -4894,13 +4922,27 @@ "explorerTxLink": "https://explorer.kaspa.org/txs/{0}", "explorerAccountLink": "https://explorer.kaspa.org/addresses/{0}" }, + "kaspaclassic": { + "name": "Kaspa Classic", + "canonicalName": "Kaspa Classic", + "symbol": "CAS", + "family": "kaspa", + "website": "https://kaspaclassic.com/", + "market": "", + "twitter": "https://twitter.com/kaspaclassic", + "telegram": "https://t.me/kaspaclassic", + "discord": "https://discord.com/invite/kaspaclassic1", + "explorerBlockLink": "https://explorer.kaspa-classic.online/blocks/$hash$", + "explorerTxLink": "https://explorer.kaspa-classic.online/txs/{0}", + "explorerAccountLink": "https://explorer.kaspa-classic.online/addresses/{0}" + }, "karlsencoin": { "name": "Karlsencoin", "canonicalName": "Karlsencoin", "symbol": "KLS", "family": "kaspa", "website": "https://karlsencoin.com/", - "market": "", + "market": "https://coinmarketcap.com/currencies/karlsen/", "twitter": "https://twitter.com/karlsennetwork", "telegram": "https://t.me/KarlsenNetwork", "discord": "https://discord.com/invite/NasfjsEm", @@ -4908,18 +4950,60 @@ "explorerTxLink": "https://explorer.karlsencoin.com/txs/{0}", "explorerAccountLink": "https://explorer.karlsencoin.com/addresses/{0}" }, + "nautilus-network": { + "name": "Nautilus-Network", + "canonicalName": "Nautilus-Network", + "symbol": "NTL", + "family": "kaspa", + "website": "https://nautilus-network.net/", + "market": "", + "twitter": "https://twitter.com/nautilusNTL", + "telegram": "", + "discord": "https://discord.com/invite/qWcUUgww4d", + "explorerBlockLink": "https://explorer.nautilus-network.net/blocks/$hash$", + "explorerTxLink": "https://explorer.nautilus-network.net/txs/{0}", + "explorerAccountLink": "https://explorer.nautilus-network.net/addresses/{0}" + }, + "nexellia": { + "name": "Nexellia Network", + "canonicalName": "Nexellia Network", + "symbol": "NXL", + "family": "kaspa", + "website": "https://nexell-ia.net/", + "market": "", + "twitter": "https://twitter.com/nexellia", + "telegram": "https://t.me/NexelliaNetwork", + "discord": "https://discord.com/invite/MHn4wStC4h", + "explorerBlockLink": "https://explorer.nexell-ia.net/blocks/$hash$", + "explorerTxLink": "https://explorer.nexell-ia.net/txs/{0}", + "explorerAccountLink": "https://explorer.nexell-ia.net/addresses/{0}" + }, "pyrin": { "name": "Pyrin", "canonicalName": "Pyrin", "symbol": "PYI", "family": "kaspa", "website": "https://pyrin.network/", - "market": "", + "market": "https://coinmarketcap.com/currencies/pyrin/", "twitter": "https://twitter.com/pyrin_network", "telegram": "https://t.me/pyrin_network", "discord": "https://discord.gg/QQgWRntF", "explorerBlockLink": "https://explorer.pyrin.network/blocks/$hash$", "explorerTxLink": "https://explorer.pyrin.network/txs/{0}", "explorerAccountLink": "https://explorer.pyrin.network/addresses/{0}" + }, + "sedracoin": { + "name": "Sedracoin", + "canonicalName": "Sedracoin", + "symbol": "SDR", + "family": "kaspa", + "website": "https://sedracoin.com/", + "market": "https://www.coingecko.com/en/coins/sedra-coin", + "twitter": "https://twitter.com/SedraCoin", + "telegram": "https://t.me/OfficialSedraCoin", + "discord": "https://discord.gg/jWHrkKMSEr", + "explorerBlockLink": "https://explorer.sedracoin.com/blocks/$hash$", + "explorerTxLink": "https://explorer.sedracoin.com/txs/{0}", + "explorerAccountLink": "https://explorer.sedracoin.com/addresses/{0}" } } diff --git a/src/Native/libmultihash/fishhash/fishhash.cpp b/src/Native/libmultihash/fishhash/fishhash.cpp index de3213f75..76a51e4a7 100644 --- a/src/Native/libmultihash/fishhash/fishhash.cpp +++ b/src/Native/libmultihash/fishhash/fishhash.cpp @@ -141,16 +141,57 @@ namespace FishHash { for (uint32_t i = 0; i < num_dataset_accesses; ++i) { // Calculate new fetching indexes - uint32_t mixGroup[8]; - for (uint32_t c=0; c<8; c++) { - mixGroup[c] = (mix.word32s[4*c + 0] ^ mix.word32s[4*c + 1] ^ mix.word32s[4*c + 2] ^ mix.word32s[4*c + 3]); + const uint32_t p0 = mix.word32s[0] % index_limit; + const uint32_t p1 = mix.word32s[4] % index_limit; + const uint32_t p2 = mix.word32s[8] % index_limit; + + fishhash_hash1024 fetch0 = lookup(*ctx, p0); + fishhash_hash1024 fetch1 = lookup(*ctx, p1); + fishhash_hash1024 fetch2 = lookup(*ctx, p2); + + // Modify fetch1 and fetch2 + for (size_t j = 0; j < 32; ++j) { + fetch1.word32s[j] = fnv1(mix.word32s[j], fetch1.word32s[j]); + fetch2.word32s[j] = mix.word32s[j] ^ fetch2.word32s[j]; } + + // Final computation of new mix + for (size_t j = 0; j < 16; ++j) + mix.word64s[j] = fetch0.word64s[j] * fetch1.word64s[j] + fetch2.word64s[j]; + } + // Collapse the result into 32 bytes + fishhash_hash256 mix_hash; + static constexpr size_t num_words = sizeof(mix) / sizeof(uint32_t); + 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 mix_hash; + } + + fishhash_hash256 fishhashplus_kernel( const fishhash_context * ctx, const fishhash_hash512 seed) noexcept { + const uint32_t index_limit = static_cast(ctx->full_dataset_num_items); + const uint32_t seed_init = seed.word32s[0]; + + fishhash_hash1024 mix{seed, seed}; + + for (uint32_t i = 0; i < num_dataset_accesses; ++i) { + + // Calculate new fetching indexes + uint32_t mixGroup[8]; + for (uint32_t c=0; c<8; c++) { + mixGroup[c] = (mix.word32s[4*c + 0] ^ mix.word32s[4*c + 1] ^ mix.word32s[4*c + 2] ^ mix.word32s[4*c + 3]); + } + + + uint32_t p0 = (mixGroup[0] ^ mixGroup[3] ^ mixGroup[6]) % index_limit; + uint32_t p1 = (mixGroup[1] ^ mixGroup[4] ^ mixGroup[7]) % index_limit; + uint32_t p2 = (mixGroup[2] ^ mixGroup[5] ^ i) % index_limit; - uint32_t p0 = (mixGroup[0] ^ mixGroup[3] ^ mixGroup[6]) % index_limit; - uint32_t p1 = (mixGroup[1] ^ mixGroup[4] ^ mixGroup[7]) % index_limit; - uint32_t p2 = (mixGroup[2] ^ mixGroup[5] ^ i) % index_limit; - fishhash_hash1024 fetch0 = lookup(*ctx, p0); fishhash_hash1024 fetch1 = lookup(*ctx, p1); fishhash_hash1024 fetch2 = lookup(*ctx, p2); @@ -179,15 +220,15 @@ namespace FishHash { return mix_hash; } - void fishhash_hash(uint8_t * output, const fishhash_context * ctx, const uint8_t * header, uint64_t header_size) noexcept { + void fishhash_hash(uint8_t * output, const fishhash_context * ctx, const uint8_t * header, uint64_t header_size, bool enable_fishhashplus) noexcept { fishhash_hash512 seed; blake3_hasher hasher; blake3_hasher_init(&hasher); blake3_hasher_update(&hasher, header, header_size); blake3_hasher_finalize(&hasher, seed.bytes, 64); - - const fishhash_hash256 mix_hash = fishhash_kernel(ctx, seed); + + const fishhash_hash256 mix_hash = enable_fishhashplus ? fishhashplus_kernel(ctx, seed): fishhash_kernel(ctx, seed); uint8_t final_data[sizeof(seed) + sizeof(mix_hash)]; std::memcpy(&final_data[0], seed.bytes, sizeof(seed)); diff --git a/src/Native/libmultihash/fishhash/fishhash.h b/src/Native/libmultihash/fishhash/fishhash.h index 25959df9a..962a3571d 100644 --- a/src/Native/libmultihash/fishhash/fishhash.h +++ b/src/Native/libmultihash/fishhash/fishhash.h @@ -62,8 +62,9 @@ namespace FishHash { EXPORT struct fishhash_context* fishhash_get_context(bool full = false) NOEXCEPT; EXPORT union fishhash_hash256 fishhash_kernel( const fishhash_context * ctx, const fishhash_hash512 seed) NOEXCEPT; + EXPORT union fishhash_hash256 fishhashplus_kernel( const fishhash_context * ctx, const fishhash_hash512 seed) NOEXCEPT; EXPORT void fishhash_prebuild_dataset(fishhash_context * ctx, uint32_t numThreads = 1) NOEXCEPT; - EXPORT void fishhash_hash(uint8_t * output, const fishhash_context * ctx, const uint8_t * header, uint64_t header_size) NOEXCEPT; + EXPORT void fishhash_hash(uint8_t * output, const fishhash_context * ctx, const uint8_t * header, uint64_t header_size, bool enable_fishhashplus = false) NOEXCEPT; } #ifdef __cplusplus