private async Task <(Share Share, string nonce, string solution, string headerHash, string nTime)> ProcessShareInternal( StratumClient worker, string nonce, string nTime, string solution) { var context = worker.GetContextAs <AionWorkerContext>(); var solutionBytes = solution.HexToByteArray(); // serialize block-header var headerBytes = SerializeHeader(nonce); // verify solution if (!equihash.Verify210(headerBytes, solutionBytes)) { throw new StratumException(StratumError.Other, "invalid solution"); } // hash block-header var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray(); var headerHash = headerHasher.Digest(headerSolutionBytes); var headerHashReversed = headerHash.ToReverseArray(); var headerBigInt = headerHashReversed.ToBigInteger(); var target = new BigInteger(blockTarget.ToBytes()); var isBlockCandidate = target > headerBigInt; // calc share-diff var stratumDifficulty = context.Difficulty > Difficulty ? Difficulty : context.Difficulty; var shareDiff = stratumDifficulty; var ratio = shareDiff / stratumDifficulty; var sentTargetInt = new uint256(AionUtils.diffToTarget(context.Difficulty).HexToByteArray().ReverseArray()); var sentTarget = new BigInteger(sentTargetInt.ToBytes()); var isLowDiffShare = sentTarget <= headerBigInt; if (isLowDiffShare) { // Check if matched a previous varDiff before retarget if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue) { var prevSentTargetInt = new uint256(AionUtils.diffToTarget(context.PreviousDifficulty.Value).HexToByteArray().ReverseArray()); var prevSentTargetBi = new BigInteger(prevSentTargetInt.ToBytes()); if (prevSentTargetBi <= headerBigInt) { stratumDifficulty = context.PreviousDifficulty.Value; } } else { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } } var result = new Share { BlockHeight = (long)BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = context.MinerName, Worker = context.WorkerName, UserAgent = context.UserAgent, NetworkDifficulty = Difficulty, Difficulty = stratumDifficulty, IsBlockCandidate = isBlockCandidate, TransactionConfirmationData = headerHash.ToHexString(), }; if (isBlockCandidate) { result.BlockReward = AionUtils.calculateReward((long)BlockTemplate.Height); result.BlockHash = headerHashReversed.ToHexString(); } return(result, nonce, solution, BlockTemplate.HeaderHash, nTime); }
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 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); } } } }
protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string extraNonce2, uint nTime, uint nonce) { var context = worker.ContextAs <BitcoinWorkerContext>(); var extraNonce1 = context.ExtraNonce1; // build coinbase var coinbase = SerializeCoinbase(extraNonce1, extraNonce2); var coinbaseHash = coinbaseHasher.Digest(coinbase); // hash block-header var headerBytes = SerializeHeader(coinbaseHash, nTime, nonce); var headerHash = headerHasher.Digest(headerBytes, (ulong)nTime); var headerValue = new uint256(headerHash); // calc share-diff var shareDiff = (double)new BigRational(BitcoinConstants.Diff1, headerHash.ToBigInteger()) * shareMultiplier; var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = headerValue <= 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 * shareMultiplier, Difficulty = stratumDifficulty, }; if (isBlockCandidate) { result.IsBlockCandidate = true; result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC); result.BlockHash = blockHasher.Digest(headerBytes, nTime).ToHexString(); var blockBytes = SerializeBlock(headerBytes, coinbase); var blockHex = blockBytes.ToHexString(); return(result, blockHex); } return(result, null); }
protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; if (request.Id == null) { throw new StratumException(StratumError.MinusOne, "missing request id"); } var context = client.ContextAs <BitcoinWorkerContext>(); var requestParams = request.ParamsAs <string[]>(); 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 workerName is an address context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName, ct); context.Miner = minerName; context.Worker = workerName; if (context.IsAuthorized) { // respond await client.RespondAsync(context.IsAuthorized, request.Id); // log association logger.Info(() => $"[{client.ConnectionId}] Authorized worker {workerValue}"); // extract control vars from password var staticDiff = GetStaticDiffFromPassparts(passParts); 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(() => $"[{client.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); await client.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); } } else { // respond await client.RespondErrorAsync(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized); // issue short-time ban if unauthorized to prevent DDos on daemon (validateaddress RPC) logger.Info(() => $"[{client.ConnectionId}] Banning unauthorized worker for 60 sec"); banManager.Ban(client.RemoteEndpoint.Address, TimeSpan.FromSeconds(60)); DisconnectClient(client); } }
private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <MoneroWorkerContext>(); 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.Debug(() => $"[{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); return; } // check request var submitRequest = request.ParamsAs <MoneroSubmitShareRequest>(); // validate worker if (client.ConnectionId != submitRequest?.WorkerId || !context.IsAuthorized) { throw new StratumException(StratumError.MinusOne, "unauthorized"); } // recognize activity context.LastActivity = clock.Now; MoneroWorkerJob job; lock (context) { var jobId = submitRequest?.JobId; if ((job = context.FindJob(jobId)) == null) { throw new StratumException(StratumError.MinusOne, "invalid jobid"); } } // dupe check var nonceLower = submitRequest.Nonce.ToLower(); lock (job) { if (job.Submissions.Contains(nonceLower)) { throw new StratumException(StratumError.MinusOne, "duplicate share"); } job.Submissions.Add(nonceLower); } var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; var share = await manager.SubmitShareAsync(client, submitRequest, job, poolEndpoint.Difficulty); client.Respond(new MoneroResponseBase(), request.Id); // publish messageBus.SendMessage(new ClientShare(client, share)); // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); logger.Info(() => $"[{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.Now; } // update client stats context.Stats.ValidShares++; UpdateVarDiff(client); } catch (StratumException ex) { client.RespondError(ex.Code, ex.Message, request.Id, false); // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, false); // update client stats context.Stats.InvalidShares++; logger.Info(() => $"[{client.ConnectionId}] Share rejected: {ex.Message}"); // banning ConsiderBan(client, context, poolConfig.Banning); } }
public override async Task <Share> SubmitShareAsync(StratumClient worker, object submission, double stratumDifficultyBase, CancellationToken ct) { Contract.RequiresNonNull(worker, nameof(worker)); Contract.RequiresNonNull(submission, nameof(submission)); logger.LogInvoke(new[] { worker.ConnectionId }); if (!(submission is object[] submitParams)) { throw new StratumException(StratumError.Other, "invalid params"); } var context = worker.ContextAs <BitcoinWorkerContext>(); // extract params var workerValue = (submitParams[0] as string)?.Trim(); var jobId = submitParams[1] as string; var nTime = submitParams[2] as string; var extraNonce2 = submitParams[3] as string; var solution = submitParams[4] as string; if (string.IsNullOrEmpty(workerValue)) { throw new StratumException(StratumError.Other, "missing or invalid workername"); } if (string.IsNullOrEmpty(solution)) { throw new StratumException(StratumError.Other, "missing or invalid solution"); } EquihashJob job; lock (jobLock) { job = validJobs.FirstOrDefault(x => x.JobId == jobId); } if (job == null) { throw new StratumException(StratumError.JobNotFound, "job not found"); } // extract worker/miner/payoutid var split = workerValue.Split('.'); var minerName = split[0]; var workerName = split.Length > 1 ? split[1] : null; // validate & process var(share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, solution); // 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); // is it still a block candidate? share.IsBlockCandidate = acceptResponse.Accepted; if (share.IsBlockCandidate) { logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}"); blockSubmissionSubject.OnNext(Unit.Default); // 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; } } // enrich share with common data share.PoolId = poolConfig.Id; share.IpAddress = worker.RemoteEndpoint.Address.ToString(); share.Miner = minerName; share.Worker = workerName; share.UserAgent = context.UserAgent; share.Source = clusterConfig.ClusterName; share.NetworkDifficulty = job.Difficulty; share.Difficulty = share.Difficulty; share.Created = clock.Now; return(share); }
protected override async Task OnRequestAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; try { switch (request.Method) { case BitcoinStratumMethods.Subscribe: await OnSubscribeAsync(client, tsRequest); break; case BitcoinStratumMethods.Authorize: await OnAuthorizeAsync(client, tsRequest, ct); break; case BitcoinStratumMethods.SubmitShare: await OnSubmitAsync(client, tsRequest, ct); break; case BitcoinStratumMethods.SuggestDifficulty: await OnSuggestDifficultyAsync(client, tsRequest); break; case BitcoinStratumMethods.MiningConfigure: await OnConfigureMiningAsync(client, tsRequest); // ignored break; case BitcoinStratumMethods.ExtraNonceSubscribe: await client.RespondAsync(true, request.Id); break; case BitcoinStratumMethods.GetTransactions: // ignored break; case BitcoinStratumMethods.MiningMultiVersion: // ignored break; default: logger.Debug(() => $"[{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); await client.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); break; } } catch (StratumException ex) { await client.RespondErrorAsync(ex.Code, ex.Message, request.Id, false); } }
protected virtual async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.GetContextAs <BitcoinWorkerContext>(); 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.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); 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"); } // submit var requestParams = request.ParamsAs <string[]>(); var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; var share = await manager.SubmitShareAsync(client, requestParams, poolEndpoint.Difficulty); // success client.Respond(true, request.Id); shareSubject.OnNext(new ClientShare(client, share)); logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.Now; } // update client stats context.Stats.ValidShares++; UpdateVarDiff(client); } catch (StratumException ex) { client.RespondError(ex.Code, ex.Message, request.Id, false); // update client stats context.Stats.InvalidShares++; logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning ConsiderBan(client, context, poolConfig.Banning); } }
public async ValueTask <Share> SubmitShareAsync(StratumClient worker, string[] request, CancellationToken ct) { Contract.RequiresNonNull(worker, nameof(worker)); Contract.RequiresNonNull(request, nameof(request)); logger.LogInvoke(new[] { worker.ConnectionId }); var context = worker.ContextAs <EthereumWorkerContext>(); // var miner = request[0]; // var jobId = request[1]; // var nonce = request[2]; EthereumJob job; string miner, jobId, nonce = string.Empty; if (context.IsNiceHashClient) { jobId = request[1]; nonce = request[2]; miner = request[0]; lock (jobLock) { var jobResult = validJobs.Where(x => x.Value.Id == jobId).FirstOrDefault(); if (jobResult.Value == null) { throw new StratumException(StratumError.MinusOne, "stale share"); } job = jobResult.Value; } } else { jobId = request[1]; nonce = request[0]; lock (jobLock) { var jobResult = validJobs.Where(x => x.Value.BlockTemplate.Header == jobId).FirstOrDefault(); if (jobResult.Value == null) { throw new StratumException(StratumError.MinusOne, "stale share"); } job = jobResult.Value; } } // validate & process var(share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, nonce, ethash, ct); // enrich share with common data share.PoolId = poolConfig.Id; share.NetworkDifficulty = BlockchainStats.NetworkDifficulty; share.Source = clusterConfig.ClusterName; share.Created = clock.UtcNow; // if block candidate, submit & check if accepted by network if (share.IsBlockCandidate) { logger.Info(() => $"Submitting block {share.BlockHeight}"); share.IsBlockCandidate = await SubmitBlockAsync(share, fullNonceHex, headerHash, mixHash); if (share.IsBlockCandidate) { logger.Info(() => $"Daemon accepted block {share.BlockHeight} submitted by {context.Miner}"); } } return(share); }
private async Task OnSubmitAsync(StratumClient <EthereumWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; 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.UtcNow - tsRequest.Timestamp.UtcDateTime; if (requestAge > maxShareAge) { logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); return; } // validate worker if (!client.Context.IsAuthorized) { throw new StratumException(StratumError.UnauthorizedWorker, "Unauthorized worker"); } else if (!client.Context.IsSubscribed) { throw new StratumException(StratumError.NotSubscribed, "Not subscribed"); } // check request var submitRequest = request.ParamsAs <string[]>(); if (submitRequest.Length != 3 || submitRequest.Any(string.IsNullOrEmpty)) { throw new StratumException(StratumError.MinusOne, "malformed PoW result"); } // recognize activity client.Context.LastActivity = clock.UtcNow; var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; var share = await manager.SubmitShareAsync(client, submitRequest, client.Context.Difficulty, poolEndpoint.Difficulty); // success client.Respond(true, request.Id); shareSubject.OnNext(Tuple.Create((object)client, share)); logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.UtcNow; } // update client stats client.Context.Stats.ValidShares++; } catch (StratumException ex) { client.RespondError(ex.Code, ex.Message, request.Id, false); // update client stats client.Context.Stats.InvalidShares++; logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning if (poolConfig.Banning?.Enabled == true) { ConsiderBan(client, client.Context, poolConfig.Banning); } } }
public async Task <EthereumShare> ProcessShareAsync(StratumClient <EthereumWorkerContext> worker, string nonce, EthashFull ethash) { // duplicate nonce? lock (workerNonces) { RegisterNonce(worker, nonce); } // assemble full-nonce var fullNonceHex = worker.Context.ExtraNonce1 + nonce; var fullNonce = ulong.Parse(fullNonceHex, NumberStyles.HexNumber); // get dag for block var dag = await ethash.GetDagAsync(BlockTemplate.Height); // compute if (!dag.Compute(BlockTemplate.Header.HexToByteArray(), fullNonce, out var mixDigest, out var resultBytes)) { throw new StratumException(StratumError.MinusOne, "bad hash"); } // test if share meets at least workers current difficulty var resultValue = BigInteger.Parse("0" + resultBytes.ToHexString(), NumberStyles.HexNumber); var shareDiff = (double)BigInteger.Divide(EthereumConstants.BigMaxValue, resultValue) / EthereumConstants.Pow2x32; var stratumDifficulty = worker.Context.Difficulty; var ratio = shareDiff / stratumDifficulty; var isBlockCandidate = resultValue.CompareTo(BlockTemplate.Target) <= 0; if (!isBlockCandidate && ratio < 0.99) { // check if share matched the previous difficulty from before a vardiff retarget if (worker.Context.VarDiff?.LastUpdate != null && worker.Context.PreviousDifficulty.HasValue) { ratio = shareDiff / worker.Context.PreviousDifficulty.Value; if (ratio < 0.99) { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } // use previous difficulty stratumDifficulty = worker.Context.PreviousDifficulty.Value; } else { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } } // create share var share = new EthereumShare { BlockHeight = (long)BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = worker.Context.MinerName, Worker = worker.Context.WorkerName, UserAgent = worker.Context.UserAgent, FullNonceHex = "0x" + fullNonceHex, HeaderHash = BlockTemplate.Header, MixHash = mixDigest.ToHexString(true), IsBlockCandidate = isBlockCandidate, Difficulty = stratumDifficulty * EthereumConstants.Pow2x32, }; if (share.IsBlockCandidate) { share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{share.FullNonceHex}"; } return(share); }
public async Task <(Share Share, string FullNonceHex, string HeaderHash, string MixHash)> ProcessShareAsync(StratumClient worker, string nonce, EthashFull ethash) { // duplicate nonce? //lock (workerNonces) //{ // RegisterNonce(worker, nonce); //} // assemble full-nonce var context = worker.GetContextAs <EthereumWorkerContext>(); var fullNonceHex = nonce.StartsWith("0x") ? nonce.Substring(2) : nonce; if (context.IsNiceHashClient && !string.IsNullOrEmpty(context.ExtraNonce1)) { fullNonceHex = context.ExtraNonce1 + fullNonceHex; } if (!ulong.TryParse(fullNonceHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var fullNonce)) { throw new StratumException(StratumError.Other, "bad nonce " + fullNonceHex); } // get dag for block var dag = await ethash.GetDagAsync(BlockTemplate.Height, logger); // compute if (!dag.Compute(logger, BlockTemplate.Header.HexToByteArray(), fullNonce, out var mixDigest, out var resultBytes)) { throw new StratumException(StratumError.Other, "bad hash"); } resultBytes.ReverseArray(); // test if share meets at least workers current difficulty var resultValue = new uint256(resultBytes); var resultValueBig = resultBytes.ToBigInteger(); var shareDiff = (double)BigInteger.Divide(EthereumConstants.BigMaxValue, resultValueBig); var stratumDifficulty = context.Difficulty * EthereumConstants.StratumDiffFactor; var ratio = shareDiff / stratumDifficulty; var isBlockCandidate = resultValue <= blockTarget; if (!isBlockCandidate && ratio < 0.98) { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } // create share var share = new Share { BlockHeight = (long)BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = context.MinerName, Worker = context.WorkerName, UserAgent = context.UserAgent, IsBlockCandidate = isBlockCandidate, Difficulty = stratumDifficulty, BlockHash = mixDigest.ToHexString(true) }; if (share.IsBlockCandidate) { var headerHash = BlockTemplate.Header; var mixHash = mixDigest.ToHexString(true); share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{nonce}"; return(share, fullNonceHex, headerHash, mixHash); } return(share, null, null, null); }
private (Share Share, string nonce, string solution, string headerHash, string nTime) ProcessShareInternal( StratumClient worker, string nonce, string nTime, string solution) { var context = worker.ContextAs <AionWorkerContext>(); var solutionBytes = solution.HexToByteArray(); // serialize block-header var headerBytes = SerializeHeader(nonce); // verify solution if (!equihash.Verify(headerBytes, solutionBytes)) { throw new StratumException(StratumError.Other, "invalid solution"); } // hash block-header var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray(); Span <byte> headerHash = stackalloc byte[32]; headerHasher.Digest(headerSolutionBytes, headerHash); var headerHashReversed = headerHash.ToNewReverseArray(); var headerValue = headerHashReversed.ToBigInteger(); var target = new BigInteger(blockTarget.ToBytes()); var isBlockCandidate = target > headerValue; logger.Debug(() => $"context.Difficulty:{context.Difficulty} Difficulty: {Difficulty}"); // calc share-diff var stratumDifficulty = context.Difficulty > Difficulty ? Difficulty : context.Difficulty; var shareDiff = stratumDifficulty; var ratio = shareDiff / stratumDifficulty; var sentTargetInt = new uint256(AionUtils.diffToTarget(context.Difficulty).HexToReverseByteArray()); var sentTarget = new BigInteger(sentTargetInt.ToBytes()); var isLowDiffShare = sentTarget <= headerValue; if (isLowDiffShare) { throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } var result = new Share { BlockHeight = (long)BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = context.MinerName, Worker = context.WorkerName, UserAgent = context.UserAgent, NetworkDifficulty = Difficulty, Difficulty = stratumDifficulty, IsBlockCandidate = isBlockCandidate, TransactionConfirmationData = headerHash.ToHexString(), }; if (isBlockCandidate) { // result.BlockReward = AionUtils.calculateReward((long) BlockTemplate.Height); result.BlockHash = headerHashReversed.ToHexString(); } return(result, nonce, solution, BlockTemplate.HeaderHash, nTime); }
private (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string nonce, uint nTime, string solution) { var context = worker.ContextAs <BitcoinWorkerContext>(); var solutionBytes = (Span <byte>)solution.HexToByteArray(); // serialize block-header var headerBytes = SerializeHeader(nTime, nonce); // verify solution if (!solver.Verify(headerBytes, solutionBytes.Slice(networkParams.SolutionPreambleSize))) { throw new StratumException(StratumError.Other, "invalid solution"); } // concat header and solution Span <byte> headerSolutionBytes = stackalloc byte[headerBytes.Length + solutionBytes.Length]; headerBytes.CopyTo(headerSolutionBytes); solutionBytes.CopyTo(headerSolutionBytes.Slice(headerBytes.Length)); // hash block-header Span <byte> headerHash = stackalloc byte[32]; headerHasher.Digest(headerSolutionBytes, headerHash, (ulong)nTime); var headerValue = new uint256(headerHash); // calc share-diff var shareDiff = (double)new BigRational(networkParams.Diff1BValue, headerHash.ToBigInteger()); var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = headerValue <= 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, }; if (isBlockCandidate) { var headerHashReversed = headerHash.ToNewReverseArray(); result.IsBlockCandidate = true; result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC); result.BlockHash = headerHashReversed.ToHexString(); var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes); var blockHex = blockBytes.ToHexString(); return(result, blockHex); } return(result, null); }
private async Task OnSubmitAsync(StratumClient <MoneroWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; 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.UtcNow - tsRequest.Timestamp.UtcDateTime; if (requestAge > maxShareAge) { logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); return; } // check request var submitRequest = request.ParamsAs <MoneroSubmitShareRequest>(); // validate worker if (client.ConnectionId != submitRequest?.WorkerId || !client.Context.IsAuthorized) { throw new StratumException(StratumError.MinusOne, "unauthorized"); } // recognize activity client.Context.LastActivity = clock.UtcNow; MoneroWorkerJob job; lock (client.Context) { var jobId = submitRequest?.JobId; if (string.IsNullOrEmpty(jobId) || (job = client.Context.ValidJobs.FirstOrDefault(x => x.Id == jobId)) == null) { throw new StratumException(StratumError.MinusOne, "invalid jobid"); } } // dupe check var nonceLower = submitRequest.Nonce.ToLower(); lock (job) { if (job.Submissions.Contains(nonceLower)) { throw new StratumException(StratumError.MinusOne, "duplicate share"); } job.Submissions.Add(nonceLower); } var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; var share = await manager.SubmitShareAsync(client, submitRequest, job, poolEndpoint.Difficulty); // success client.Respond(new MoneroResponseBase(), request.Id); shareSubject.OnNext(Tuple.Create((object)client, share)); logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.UtcNow; } // update client stats client.Context.Stats.ValidShares++; } catch (StratumException ex) { client.RespondError(ex.Code, ex.Message, request.Id, false); // update client stats client.Context.Stats.InvalidShares++; logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Message}"); // banning if (poolConfig.Banning?.Enabled == true) { ConsiderBan(client, client.Context, poolConfig.Banning); } } }
public void PrepareWorker(StratumClient <EthereumWorkerContext> client) { client.Context.ExtraNonce1 = extraNonceProvider.Next(); }
protected override void OnConnect(StratumClient client) { // update stats lock (clients) { poolStats.ConnectedMiners = clients.Count; } // client setup var context = CreateClientContext(); var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; context.Init(poolConfig, poolEndpoint.Difficulty, poolEndpoint.VarDiff, clock); client.SetContext(context); // varDiff setup if (context.VarDiff != null) { // get or create manager lock (varDiffManagers) { if (!varDiffManagers.TryGetValue(poolEndpoint, out var varDiffManager)) { varDiffManager = new VarDiffManager(poolEndpoint.VarDiff, clock); varDiffManagers[poolEndpoint] = varDiffManager; } } // wire updates lock (context.VarDiff) { context.VarDiff.Subscription = Shares .Where(x => x.Item1 == client) .Timestamp() .Select(x => x.Timestamp.ToUnixTimeMilliseconds()) .Buffer(TimeSpan.FromSeconds(poolEndpoint.VarDiff.RetargetTime), VarDiffSampleCount) .Subscribe(timestamps => { try { VarDiffManager varDiffManager; lock (varDiffManagers) { varDiffManager = varDiffManagers[poolEndpoint]; } var newDiff = varDiffManager.Update(context, timestamps, client.ConnectionId, logger); if (newDiff.HasValue) { logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] VarDiff update to {Math.Round(newDiff.Value, 2)}"); OnVarDiffUpdate(client, newDiff.Value); } } catch (Exception ex) { logger.Error(ex); } }); } } // expect miner to establish communication within a certain time EnsureNoZombieClient(client); }
public ClientShare(StratumClient client, IShare share) { Client = client; Share = share; }
protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string extraNonce, uint nTime, string nonce, string solution) { var context = worker.ContextAs <BitcoinWorkerContext>(); var solutionBytes = solution.HexToByteArray(); var extraNonceBytes = extraNonce.HexToByteArray(); var nonceInt = uint.Parse(nonce, NumberStyles.HexNumber); // serialize block-header var headerBytes = SerializeHeader(nTime, extraNonceBytes, nonceInt); // verify solution if (!equihash.Verify(headerBytes, solutionBytes)) { throw new StratumException(StratumError.Other, "invalid solution"); } // hash block-header var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray(); var headerHash = headerHasher.Digest(headerSolutionBytes); var headerValue = new uint256(headerHash); // calc share-diff double shareDiff = (double)new BigRational(chainConfig.Diff1b, headerHash.ToBigInteger()); var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = headerValue < blockTarget.ToUInt256(); // 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 = BlockHeader.Height, NetworkDifficulty = Difficulty, Difficulty = stratumDifficulty, }; if (isBlockCandidate) { result.IsBlockCandidate = true; result.BlockHash = headerValue.ToString(); var blockHex = headerSolutionBytes.ToHexString(); return(result, blockHex); } return(result, null); }
private void OnLogin(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <MoneroWorkerContext>(); if (request.Id == null) { client.RespondError(StratumError.MinusOne, "missing request id", request.Id); return; } var loginRequest = request.ParamsAs <MoneroLoginRequest>(); if (string.IsNullOrEmpty(loginRequest?.Login)) { client.RespondError(StratumError.MinusOne, "missing login", request.Id); return; } // extract worker/miner/paymentid var split = loginRequest.Login.Split('.'); context.MinerName = split[0].Trim(); context.WorkerName = split.Length > 1 ? split[1].Trim() : null; context.UserAgent = loginRequest.UserAgent?.Trim(); var passParts = loginRequest.Password?.Split(PasswordControlVarsSeparator); // extract paymentid var index = context.MinerName.IndexOf('#'); if (index != -1) { context.PaymentId = context.MinerName.Substring(index + 1).Trim(); context.MinerName = context.MinerName.Substring(0, index).Trim(); } // validate login var result = manager.ValidateAddress(context.MinerName); context.IsSubscribed = result; context.IsAuthorized = result; if (!context.IsAuthorized) { client.RespondError(StratumError.MinusOne, "invalid login", request.Id); return; } // validate payment Id if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != MoneroConstants.PaymentIdHexLength) { client.RespondError(StratumError.MinusOne, "invalid payment id", request.Id); return; } // extract control vars from password var staticDiff = GetStaticDiffFromPassparts(passParts); 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); } // respond var loginResponse = new MoneroLoginResponse { Id = client.ConnectionId, Job = CreateWorkerJob(client) }; client.Respond(loginResponse, request.Id); // log association logger.Info(() => $"[{client.ConnectionId}] Authorized worker {loginRequest.Login}"); }
private async Task OnLoginAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <CryptonoteWorkerContext>(); if (request.Id == null) { throw new StratumException(StratumError.MinusOne, "missing request id"); } var loginRequest = request.ParamsAs <CryptonoteLoginRequest>(); if (string.IsNullOrEmpty(loginRequest?.Login)) { throw new StratumException(StratumError.MinusOne, "missing login"); } // extract worker/miner/paymentid var split = loginRequest.Login.Split('.'); context.Miner = split[0].Trim(); context.Worker = split.Length > 1 ? split[1].Trim() : "0"; context.UserAgent = loginRequest.UserAgent?.Trim(); var addressToValidate = context.Miner; // extract paymentid var index = context.Miner.IndexOf('#'); if (index != -1) { var paymentId = context.Miner.Substring(index + 1).Trim(); // validate if (!string.IsNullOrEmpty(paymentId) && paymentId.Length != CryptonoteConstants.PaymentIdHexLength) { throw new StratumException(StratumError.MinusOne, "invalid payment id"); } // re-append to address addressToValidate = context.Miner.Substring(0, index).Trim(); context.Miner = addressToValidate + PayoutConstants.PayoutInfoSeperator + paymentId; } // validate login var result = manager.ValidateAddress(addressToValidate); if (!result) { throw new StratumException(StratumError.MinusOne, "invalid login"); } context.IsSubscribed = result; context.IsAuthorized = result; // extract control vars from password var passParts = loginRequest.Password?.Split(PasswordControlVarsSeparator); var staticDiff = GetStaticDiffFromPassparts(passParts); 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(() => $"[{client.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); } // respond var loginResponse = new CryptonoteLoginResponse { Id = client.ConnectionId, Job = CreateWorkerJob(client) }; await client.RespondAsync(loginResponse, request.Id); // log association if (!string.IsNullOrEmpty(context.Worker)) { logger.Info(() => $"[{client.ConnectionId}] Authorized worker {context.Worker}@{context.Miner}"); } else { logger.Info(() => $"[{client.ConnectionId}] Authorized miner {context.Miner}"); } }
protected virtual void OnVarDiffUpdate(StratumClient client, double newDiff) { var context = client.GetContextAs <WorkerContextBase>(); context.EnqueueNewDifficulty(newDiff); }
private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.GetContextAs <AionWorkerContext>(); 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.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)"); return; } // validate worker if (!context.IsAuthorized) { throw new StratumException(StratumError.UnauthorizedWorker, "Unauthorized worker"); } else if (!context.IsSubscribed) { throw new StratumException(StratumError.NotSubscribed, "Not subscribed"); } // check request var submitRequest = request.ParamsAs <string[]>(); if (submitRequest.Length != 5 || submitRequest.Any(string.IsNullOrEmpty)) { throw new StratumException(StratumError.MinusOne, "malformed PoW result"); } // recognize activity context.LastActivity = clock.Now; var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; try { var share = await manager.SubmitShareAsync(client, submitRequest, context.Difficulty, poolEndpoint.Difficulty); // success client.Respond(true, request.Id); messageBus.SendMessage(new ClientShare(client, share)); EnsureInitialWorkSent(client); logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.Now; } } catch (MiningCore.Stratum.StratumException ex) { logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Exception occured: {ex.Message}"); throw ex; } // update client stats context.Stats.ValidShares++; UpdateVarDiff(client); } catch (StratumException ex) { client.RespondError(ex.Code, ex.Message, request.Id, false); // update client stats context.Stats.InvalidShares++; logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning if (context.Stats.InvalidShares > ((poolConfig.Banning.CheckThreshold / 2) - 10) && context.Stats.InvalidShares < ((poolConfig.Banning.CheckThreshold / 2) + 10)) { if (!context.IsInitialWorkSent && context.IsAuthorized) { EnsureInitialWorkSent(client); } else if (context.IsAuthorized) { OnNewJob(currentJobParams); } else { DisconnectClient(client); } } else { ConsiderBan(client, context, poolConfig.Banning); } } }
public void PrepareWorker(StratumClient client) { var context = client.ContextAs <EthereumWorkerContext>(); context.ExtraNonce1 = extraNonceProvider.Next(); }
protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string nonce, uint nTime, string solution) { var context = worker.GetContextAs <BitcoinWorkerContext>(); var solutionBytes = solution.HexToByteArray(); // serialize block-header var headerBytes = SerializeHeader(nTime, nonce); // 144 bytes (doesn't contain soln) // verify solution if (!equihash.Verify(headerBytes, solutionBytes.Skip(3).ToArray())) // skip preamble (3 bytes) { throw new StratumException(StratumError.Other, "invalid solution"); } // hash block-header var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray(); var headerHash = headerHasher.Digest(headerSolutionBytes, (ulong)nTime); var headerHashReversed = headerHash.ToReverseArray(); var headerValue = new uint256(headerHash); // calc share-diff var shareDiff = (double)new BigRational(coinbaseTxConfig.Diff1b, headerHash.ToBigInteger()) * shareMultiplier; var stratumDifficulty = context.Difficulty; var ratio = shareDiff / stratumDifficulty; // check if the share meets the much harder block difficulty (block candidate) var isBlockCandidate = headerValue <= 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 BitcoinShare { BlockHeight = BlockTemplate.Height, IsBlockCandidate = isBlockCandidate, Difficulty = stratumDifficulty }; if (isBlockCandidate) { var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes); result.BlockHex = blockBytes.ToHexString(); result.BlockHash = headerHashReversed.ToHexString(); result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC); } return(result); }
protected virtual async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); 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(() => $"[{client.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"); } // submit var requestParams = request.ParamsAs <string[]>(); var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; var share = await manager.SubmitShareAsync(client, requestParams, poolEndpoint.Difficulty, ct); await client.RespondAsync(true, request.Id); // publish messageBus.SendMessage(new ClientShare(client, share)); // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); logger.Info(() => $"[{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}"); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.Now; } // update client stats context.Stats.ValidShares++; await UpdateVarDiffAsync(client); } catch (StratumException ex) { // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, false); // update client stats context.Stats.InvalidShares++; logger.Info(() => $"[{client.ConnectionId}] Share rejected: {ex.Message}"); // banning ConsiderBan(client, context, poolConfig.Banning); throw; } }
private void OnLogin(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.GetContextAs <MoneroWorkerContext>(); if (request.Id == null) { client.RespondError(StratumError.MinusOne, "missing request id", request.Id); return; } var loginRequest = request.ParamsAs <MoneroLoginRequest>(); if (string.IsNullOrEmpty(loginRequest?.Login)) { client.RespondError(StratumError.MinusOne, "missing login", request.Id); return; } // extract worker/miner/paymentid var split = loginRequest.Login.Split('.'); context.MinerName = split[0].Trim(); context.WorkerName = split.Length > 1 ? split[1].Trim() : null; context.UserAgent = loginRequest.UserAgent.Trim(); // extract paymentid var index = context.MinerName.IndexOf('#'); if (index != -1) { context.PaymentId = context.MinerName.Substring(index + 1).Trim(); context.MinerName = context.MinerName.Substring(0, index).Trim(); } // validate login var result = manager.ValidateAddress(context.MinerName); context.IsSubscribed = result; context.IsAuthorized = result; if (!context.IsAuthorized) { client.RespondError(StratumError.MinusOne, "invalid login", request.Id); return; } // validate payment Id if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != MoneroConstants.PaymentIdHexLength) { client.RespondError(StratumError.MinusOne, "invalid payment id", request.Id); return; } // respond var loginResponse = new MoneroLoginResponse { Id = client.ConnectionId, Job = CreateWorkerJob(client) }; client.Respond(loginResponse, request.Id); // log association logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {loginRequest.Login} = {client.RemoteEndpoint.Address}"); }