public MoneroJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig)); Contract.RequiresNonNull(instanceId, nameof(instanceId)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty"); switch (poolConfig.Coin.Type) { case CoinType.AEON: hashSlow = LibCryptonote.CryptonightHashSlowLite; break; case CoinType.XMR: hashSlow = buf => { // PoW variant var variant = buf[0] >= 7 ? buf[0] - 6 : 0; return(LibCryptonote.CryptonightHashSlow(buf, variant)); }; break; default: hashSlow = buf => LibCryptonote.CryptonightHashSlow(buf, 0); break; } BlockTemplate = blockTemplate; PrepareBlobTemplate(instanceId); }
public void Ban(IPAddress address, TimeSpan duration) { Contract.RequiresNonNull(address, nameof(address)); Contract.Requires <ArgumentException>(duration.TotalMilliseconds > 0, $"{nameof(duration)} must not be empty"); cache.Set(address.ToString(), string.Empty, duration); }
/// <summary> /// Executes the request against all configured demons and returns their responses as an array /// </summary> /// <typeparam name="TResponse"></typeparam> /// <param name="method"></param> /// <param name="payload"></param> /// <returns></returns> public async Task <DaemonResponse <TResponse>[]> ExecuteCmdAllAsync <TResponse>(string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) where TResponse : class { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); logger.LogInvoke(new[] { method }); var tasks = endPoints.Select(endPoint => BuildRequestTask(endPoint, method, payload, payloadJsonSerializerSettings)).ToArray(); try { await Task.WhenAll(tasks); } catch (Exception) { // ignored } var results = tasks.Select((x, i) => MapDaemonResponse <TResponse>(i, x)) .ToArray(); return(results); }
public void Configure(DaemonEndpointConfig[] endPoints, string digestAuthRealm = null) { Contract.RequiresNonNull(endPoints, nameof(endPoints)); Contract.Requires <ArgumentException>(endPoints.Length > 0, $"{nameof(endPoints)} must not be empty"); this.endPoints = endPoints; // create one HttpClient instance per endpoint that carries the associated credentials httpClients = endPoints.ToDictionary(endpoint => endpoint, endpoint => { var handler = new HttpClientHandler { Credentials = new NetworkCredential(endpoint.User, endpoint.Password), PreAuthenticate = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }; if (endpoint.Ssl && !endpoint.ValidateCert) { handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true; } return(new HttpClient(handler)); }); }
public void RespondError(object id, int code, string message) { Contract.RequiresNonNull(id, nameof(id)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(message), $"{nameof(message)} must not be empty"); Respond(new JsonRpcResponse(new JsonRpcException(code, message, null), id)); }
public bool ValidateAddress(string address) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(address), $"{nameof(address)} must not be empty"); var addressPrefix = LibCryptonote.DecodeAddress(address); var addressIntegratedPrefix = LibCryptonote.DecodeIntegratedAddress(address); switch (networkType) { case MoneroNetworkType.Main: if (addressPrefix != MoneroConstants.AddressPrefix[poolConfig.Coin.Type] && addressIntegratedPrefix != MoneroConstants.AddressPrefixIntegrated[poolConfig.Coin.Type]) { return(false); } break; case MoneroNetworkType.Test: if (addressPrefix != MoneroConstants.AddressPrefixTestnet[poolConfig.Coin.Type] && addressIntegratedPrefix != MoneroConstants.AddressPrefixIntegratedTestnet[poolConfig.Coin.Type]) { return(false); } break; } return(true); }
public virtual void Init(TBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock, IDestination poolAddressDestination, BitcoinNetworkType networkType, bool isPoS, double shareMultiplier, IHashAlgorithm coinbaseHasher, IHashAlgorithm headerHasher, IHashAlgorithm blockHasher) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig)); Contract.RequiresNonNull(clock, nameof(clock)); Contract.RequiresNonNull(poolAddressDestination, nameof(poolAddressDestination)); Contract.RequiresNonNull(coinbaseHasher, nameof(coinbaseHasher)); Contract.RequiresNonNull(headerHasher, nameof(headerHasher)); Contract.RequiresNonNull(blockHasher, nameof(blockHasher)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty"); this.poolConfig = poolConfig; this.clusterConfig = clusterConfig; this.clock = clock; this.poolAddressDestination = poolAddressDestination; this.networkType = networkType; BlockTemplate = blockTemplate; JobId = jobId; Difficulty = new Target(new NBitcoin.BouncyCastle.Math.BigInteger(BlockTemplate.Target, 16)).Difficulty; extraNoncePlaceHolderLength = BitcoinExtraNonceProvider.PlaceHolderLength; this.isPoS = isPoS; this.shareMultiplier = shareMultiplier; this.coinbaseHasher = coinbaseHasher; this.headerHasher = headerHasher; this.blockHasher = blockHasher; blockTargetValue = BigInteger.Parse(BlockTemplate.Target, NumberStyles.HexNumber); previousBlockHashReversedHex = BlockTemplate.PreviousBlockhash .HexToByteArray() .ReverseByteOrder() .ToHexString(); BuildMerkleBranches(); BuildCoinbase(); jobParams = new object[] { JobId, previousBlockHashReversedHex, coinbaseInitialHex, coinbaseFinalHex, merkleBranchesHex, BlockTemplate.Version.ToStringHex8(), BlockTemplate.Bits, BlockTemplate.CurTime.ToStringHex8(), false }; }
public IObservable <PooledArraySegment <byte>[]> ZmqSubscribe(Dictionary <DaemonEndpointConfig, string> portMap, string topic, int numMsgSegments = 2) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(topic), $"{nameof(topic)} must not be empty"); logger.LogInvoke(new[] { topic }); return(Observable.Merge(portMap.Keys .Select(endPoint => ZmqSubscribeEndpoint(endPoint, portMap[endPoint], topic, numMsgSegments))) .Publish() .RefCount()); }
public MoneroJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig)); Contract.RequiresNonNull(instanceId, nameof(instanceId)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty"); BlockTemplate = blockTemplate; PrepareBlobTemplate(instanceId); }
public IObservable <PooledArraySegment <byte> > WebsocketSubscribe(Dictionary <DaemonEndpointConfig, int> portMap, string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); logger.LogInvoke(new[] { method }); return(Observable.Merge(portMap.Keys .Select(endPoint => WebsocketSubscribeEndpoint(endPoint, portMap[endPoint], method, payload, payloadJsonSerializerSettings))) .Publish() .RefCount()); }
public bool ValidateAddress(string address) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(address), $"{nameof(address)} must not be empty"); if (EthereumConstants.ZeroHashPattern.IsMatch(address) || !EthereumConstants.ValidAddressPattern.IsMatch(address)) { return(false); } return(true); }
public bool ValidateAddress(string address) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(address), $"{nameof(address)} must not be empty"); var addressBytes = address.HexToByteArray(); if (addressBytes.Length != EthereumConstants.AddressLength) { return(false); } return(true); }
/// <summary> /// Executes the request against all configured demons and returns the first successful response /// </summary> /// <typeparam name="TResponse"></typeparam> /// <param name="method"></param> /// <param name="payload"></param> /// <returns></returns> public async Task <DaemonResponse <TResponse> > ExecuteCmdSingleAsync <TResponse>(string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) where TResponse : class { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); var task = BuildRequestTask(endPoints.First(), method, payload, payloadJsonSerializerSettings); await task; var result = MapDaemonResponse <TResponse>(0, task); return(result); }
public void Ban(IPAddress address, TimeSpan duration) { Contract.RequiresNonNull(address, nameof(address)); Contract.Requires <ArgumentException>(duration.TotalMilliseconds > 0, $"{nameof(duration)} must not be empty"); // don't ban 127.0.0.1 if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback)) { return; } cache.Set(address.ToString(), string.Empty, duration); }
/// <summary> /// Executes the request against all configured demons and returns the first successful response /// </summary> /// <typeparam name="TResponse"></typeparam> /// <param name="method"></param> /// <param name="payload"></param> /// <returns></returns> public async Task <DaemonResponse <TResponse> > ExecuteCmdAnyAsync <TResponse>(string method, object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null) where TResponse : class { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); var tasks = endPoints.Select(endPoint => BuildRequestTask(endPoint, method, payload, payloadJsonSerializerSettings)).ToArray(); var taskFirstCompleted = await Task.WhenAny(tasks); var result = MapDaemonResponse <TResponse>(0, taskFirstCompleted); return(result); }
public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messageBus, string server, string poolId) { Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings)); Contract.RequiresNonNull(messageBus, nameof(messageBus)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(poolId), $"{nameof(poolId)} must not be empty"); this.serializerSettings = serializerSettings; this.messageBus = messageBus; this.server = server; this.poolId = poolId; serializer = new JsonSerializer { ContractResolver = serializerSettings.ContractResolver }; }
public bool ValidateAddress(string address) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(address), $"{nameof(address)} must not be empty"); if (address.Length != MoneroConstants.AddressLength) { return(false); } var addressPrefix = LibCryptonote.DecodeAddress(address); if (addressPrefix != poolAddressBase58Prefix) { return(false); } return(true); }
public void Configure(DaemonEndpointConfig[] endPoints, string rpcLocation = null, string digestAuthRealm = null) { Contract.RequiresNonNull(endPoints, nameof(endPoints)); Contract.Requires <ArgumentException>(endPoints.Length > 0, $"{nameof(endPoints)} must not be empty"); this.endPoints = endPoints; this.rpcLocation = rpcLocation; // create one HttpClient instance per endpoint that carries the associated credentials httpClients = endPoints.ToDictionary(endpoint => endpoint, endpoint => new HttpClient(new HttpClientHandler { Credentials = new NetworkCredential(endpoint.User, endpoint.Password), PreAuthenticate = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip })); }
public void Init(TBlockTemplate blockTemplate, string jobId, PoolConfig poolConfig, ClusterConfig clusterConfig, IDestination poolAddressDestination, BitcoinNetworkType networkType, BitcoinExtraNonceProvider extraNonceProvider, bool isPoS, double shareMultiplier, IHashAlgorithm coinbaseHasher, IHashAlgorithm headerHasher, IHashAlgorithm blockHasher) { Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate)); Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig)); Contract.RequiresNonNull(poolAddressDestination, nameof(poolAddressDestination)); Contract.RequiresNonNull(extraNonceProvider, nameof(extraNonceProvider)); Contract.RequiresNonNull(coinbaseHasher, nameof(coinbaseHasher)); Contract.RequiresNonNull(headerHasher, nameof(headerHasher)); Contract.RequiresNonNull(blockHasher, nameof(blockHasher)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty"); this.poolConfig = poolConfig; this.clusterConfig = clusterConfig; this.poolAddressDestination = poolAddressDestination; this.networkType = networkType; BlockTemplate = blockTemplate; JobId = jobId; extraNoncePlaceHolderLength = extraNonceProvider.PlaceHolder.Length; this.isPoS = isPoS; this.shareMultiplier = shareMultiplier; this.coinbaseHasher = coinbaseHasher; this.headerHasher = headerHasher; this.blockHasher = blockHasher; blockTargetValue = BigInteger.Parse(BlockTemplate.Target, NumberStyles.HexNumber); previousBlockHashReversedHex = BlockTemplate.PreviousBlockhash .HexToByteArray() .ReverseByteOrder() .ToHexString(); BuildMerkleBranches(); BuildCoinbase(); }
public virtual BitcoinShare ProcessShare(StratumClient worker, string extraNonce2, string nTime, string nonce) { Contract.RequiresNonNull(worker, nameof(worker)); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); var context = worker.GetContextAs <BitcoinWorkerContext>(); // validate nTime if (nTime.Length != 8) { throw new StratumException(StratumError.Other, "incorrect size of ntime"); } var nTimeInt = uint.Parse(nTime, NumberStyles.HexNumber); if (nTimeInt < BlockTemplate.CurTime || nTimeInt > ((DateTimeOffset)clock.Now).ToUnixTimeSeconds() + 7200) { throw new StratumException(StratumError.Other, "ntime out of range"); } // validate nonce if (nonce.Length != 8) { throw new StratumException(StratumError.Other, "incorrect size of nonce"); } var nonceInt = uint.Parse(nonce, NumberStyles.HexNumber); // dupe check if (!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce)) { throw new StratumException(StratumError.DuplicateShare, "duplicate share"); } return(ProcessShareInternal(worker, extraNonce2, nTimeInt, nonceInt)); }
public virtual BitcoinShare ProcessShare(string extraNonce1, string extraNonce2, string nTime, string nonce, double stratumDifficulty) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(extraNonce1), $"{nameof(extraNonce1)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); // validate nTime if (nTime.Length != 8) { throw new StratumException(StratumError.Other, "incorrect size of ntime"); } var nTimeInt = uint.Parse(nTime, NumberStyles.HexNumber); if (nTimeInt < BlockTemplate.CurTime || nTimeInt > DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 7200) { throw new StratumException(StratumError.Other, "ntime out of range"); } // validate nonce if (nonce.Length != 8) { throw new StratumException(StratumError.Other, "incorrect size of nonce"); } var nonceInt = uint.Parse(nonce, NumberStyles.HexNumber); // dupe check if (!RegisterSubmit(extraNonce1, extraNonce2, nTime, nonce)) { throw new StratumException(StratumError.DuplicateShare, "duplicate share"); } return(ProcessShareInternal(extraNonce1, extraNonce2, nTimeInt, nonceInt, stratumDifficulty)); }
public MoneroShare ProcessShare(string nonce, uint workerExtraNonce, string workerHash, double stratumDifficulty) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty"); Contract.Requires <ArgumentException>(extraNonce != 0, $"{nameof(extraNonce)} must not be empty"); // validate nonce if (!MoneroConstants.RegexValidNonce.IsMatch(nonce)) { throw new StratumException(StratumError.MinusOne, "malformed nonce"); } // clone template var blob = new byte[blobTemplate.Length]; Buffer.BlockCopy(blobTemplate, 0, blob, 0, blobTemplate.Length); // inject extranonce var extraNonceBytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian()); Buffer.BlockCopy(extraNonceBytes, 0, blob, (int)BlockTemplate.ReservedOffset, extraNonceBytes.Length); // inject nonce var nonceBytes = nonce.HexToByteArray(); Buffer.BlockCopy(nonceBytes, 0, blob, MoneroConstants.BlobNonceOffset, nonceBytes.Length); // convert var blobConverted = LibCryptonote.ConvertBlob(blob); if (blobConverted == null) { throw new StratumException(StratumError.MinusOne, "malformed blob"); } // hash it var hashBytes = LibCryptonote.CryptonightHashSlow(blobConverted); var hash = hashBytes.ToHexString(); if (hash != workerHash) { throw new StratumException(StratumError.MinusOne, "bad hash"); } // check difficulty var headerValue = new System.Numerics.BigInteger(hashBytes); var shareDiff = (double)new BigRational(MoneroConstants.Diff1b, headerValue); var ratio = shareDiff / stratumDifficulty; // test if share meets at least workers current difficulty if (ratio < 0.99) { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } // valid share, check if the share also meets the much harder block difficulty (block candidate) var isBlockCandidate = shareDiff >= BlockTemplate.Difficulty; var result = new MoneroShare { BlockHeight = BlockTemplate.Height, IsBlockCandidate = isBlockCandidate, BlobHex = blob.ToHexString(), BlobHash = ComputeBlockHash(blobConverted).ToHexString() }; return(result); }
public (Share Share, string BlobHex, string BlobHash) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty"); Contract.Requires <ArgumentException>(workerExtraNonce != 0, $"{nameof(workerExtraNonce)} must not be empty"); var context = worker.GetContextAs <MoneroWorkerContext>(); // validate nonce if (!MoneroConstants.RegexValidNonce.IsMatch(nonce)) { throw new StratumException(StratumError.MinusOne, "malformed nonce"); } // clone template using (var blob = new PooledArraySegment <byte>(blobTemplate.Length)) { Buffer.BlockCopy(blobTemplate, 0, blob.Array, 0, blobTemplate.Length); // inject extranonce var extraNonceBytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian()); Buffer.BlockCopy(extraNonceBytes, 0, blob.Array, (int)BlockTemplate.ReservedOffset, extraNonceBytes.Length); // inject nonce var nonceBytes = nonce.HexToByteArray(); Buffer.BlockCopy(nonceBytes, 0, blob.Array, MoneroConstants.BlobNonceOffset, nonceBytes.Length); // convert var blobConverted = LibCryptonote.ConvertBlob(blob.Array, blobTemplate.Length); if (blobConverted == null) { throw new StratumException(StratumError.MinusOne, "malformed blob"); } // hash it using (var hashSeg = hashSlow(blobConverted)) { var hash = hashSeg.ToHexString(); if (hash != workerHash) { throw new StratumException(StratumError.MinusOne, "bad hash"); } // check difficulty var headerValue = hashSeg.ToBigInteger(); var shareDiff = (double)new BigRational(MoneroConstants.Diff1b, headerValue); var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; var isBlockCandidate = shareDiff >= BlockTemplate.Difficulty; // 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})"); } } using (var blockHash = ComputeBlockHash(blobConverted)) { var result = new Share { BlockHeight = BlockTemplate.Height, IsBlockCandidate = isBlockCandidate, BlockHash = blockHash.ToHexString(), Difficulty = stratumDifficulty, }; var blobHex = blob.ToHexString(); var blobHash = blockHash.ToHexString(); return(result, blobHex, blobHash); } } } }
public void Notify <T>(string method, T payload) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty"); Notify(new JsonRpcRequest <T>(method, payload, null)); }
public (Share Share, string BlobHex, string BlobHash) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker) { Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty"); Contract.Requires <ArgumentException>(workerExtraNonce != 0, $"{nameof(workerExtraNonce)} must not be empty"); var context = worker.ContextAs <MoneroWorkerContext>(); // validate nonce if (!MoneroConstants.RegexValidNonce.IsMatch(nonce)) { throw new StratumException(StratumError.MinusOne, "malformed nonce"); } // clone template Span <byte> blob = stackalloc byte[blobTemplate.Length]; blobTemplate.CopyTo(blob); // inject extranonce var extraNonceBytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian()); extraNonceBytes.CopyTo(blob.Slice(BlockTemplate.ReservedOffset, extraNonceBytes.Length)); // inject nonce var nonceBytes = nonce.HexToByteArray(); nonceBytes.CopyTo(blob.Slice(MoneroConstants.BlobNonceOffset, nonceBytes.Length)); // convert var blobConverted = LibCryptonote.ConvertBlob(blob, blobTemplate.Length); if (blobConverted == null) { throw new StratumException(StratumError.MinusOne, "malformed blob"); } // hash it Span <byte> headerHash = stackalloc byte[32]; switch (coin) { case CoinType.AEON: LibCryptonight.CryptonightLight(blobConverted, headerHash, 0); break; case CoinType.XMR: var variant = blobConverted[0] >= 7 ? blobConverted[0] - 6 : 0; LibCryptonight.Cryptonight(blobConverted, headerHash, variant); break; default: LibCryptonight.Cryptonight(blobConverted, headerHash, 0); break; } var headerHashString = headerHash.ToHexString(); if (headerHashString != workerHash) { throw new StratumException(StratumError.MinusOne, "bad hash"); } // check difficulty var headerValue = headerHash.ToBigInteger(); var shareDiff = (double)new BigRational(MoneroConstants.Diff1b, headerValue); var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; var isBlockCandidate = shareDiff >= BlockTemplate.Difficulty; // 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})"); } } // Compute block hash Span <byte> blockHash = stackalloc byte[32]; ComputeBlockHash(blobConverted, blockHash); var result = new Share { BlockHeight = BlockTemplate.Height, IsBlockCandidate = isBlockCandidate, BlockHash = blockHash.ToHexString(), Difficulty = stratumDifficulty, }; var blobHex = blob.ToHexString(); var blobHash = blockHash.ToHexString(); return(result, blobHex, blobHash); }