private async Task OnGetJob(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <MoneroWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.MinusOne, "missing request id", request.Id); return; } var getJobRequest = request.ParamsAs <MoneroGetJobRequest>(); // validate worker if (client.ConnectionId != getJobRequest?.WorkerId || !context.IsAuthorized) { await client.RespondErrorAsync(StratumError.MinusOne, "unauthorized", request.Id); return; } // respond var job = CreateWorkerJob(client); await client.RespondAsync(job, request.Id); }
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.GetTransactions: //OnGetTransactions(client, tsRequest); // ignored break; case BitcoinStratumMethods.ExtraNonceSubscribe: // 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 override async Task OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.Other, "missing request id", request.Id); return; } var requestParams = request.ParamsAs <string[]>(); var data = new object[] { client.ConnectionId, } .Concat(manager.GetSubscriberData(client)) .ToArray(); await client.RespondAsync(data, request.Id); // setup worker context context.IsSubscribed = true; context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; }
protected override async Task OnRequestAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; switch (request.Method) { case EthereumStratumMethods.Subscribe: await OnSubscribeAsync(client, tsRequest); break; case EthereumStratumMethods.Authorize: await OnAuthorizeAsync(client, tsRequest); break; case EthereumStratumMethods.SubmitShare: await OnSubmitAsync(client, tsRequest); break; case EthereumStratumMethods.ExtraNonceSubscribe: //await client.RespondError(StratumError.Other, "not supported", request.Id, false); break; default: logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); await client.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); break; } }
private async Task OnSuggestTargetAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.Other, "missing request id", request.Id); return; } var requestParams = request.ParamsAs <string[]>(); var target = requestParams.FirstOrDefault(); if (!string.IsNullOrEmpty(target)) { if (System.Numerics.BigInteger.TryParse(target, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var targetBig)) { var newDiff = (double)new BigRational(chainConfig.Diff1b, targetBig); var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; if (newDiff >= poolEndpoint.Difficulty) { context.EnqueueNewDifficulty(newDiff); context.ApplyPendingDifficulty(); await client.NotifyAsync(ZCashStratumMethods.SetTarget, new object[] { EncodeTarget(context.Difficulty) }); } else { await client.RespondErrorAsync(StratumError.Other, "suggested difficulty too low", request.Id); } } else { await client.RespondErrorAsync(StratumError.Other, "invalid target", request.Id); } } else { await client.RespondErrorAsync(StratumError.Other, "invalid target", request.Id); } }
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); } }
protected override async Task OnRequestAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; var context = client.ContextAs <CryptonoteWorkerContext>(); logger.Trace(() => $"[{client.ConnectionId}] RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); try { switch (request.Method) { case CryptonoteStratumMethods.Login: await OnLoginAsync(client, tsRequest); break; case CryptonoteStratumMethods.GetJob: await OnGetJobAsync(client, tsRequest); break; case CryptonoteStratumMethods.Submit: await OnSubmitAsync(client, tsRequest, ct); break; case CryptonoteStratumMethods.KeepAlive: // recognize activity context.LastActivity = clock.UtcNow; break; default: logger.Info(() => $"[{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); } }
private async Task OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.Other, "missing request id", request.Id); return; } var requestParams = request.ParamsAs <string[]>(); if (requestParams == null || requestParams.Length < 2 || requestParams.Any(string.IsNullOrEmpty)) { await client.RespondErrorAsync(StratumError.MinusOne, "invalid request", request.Id); return; } manager.PrepareWorker(client); var data = new object[] { new object[] { EthereumStratumMethods.MiningNotify, client.ConnectionId, EthereumConstants.EthereumStratumVersion }, context.ExtraNonce1 } .ToArray(); await client.RespondAsync(data, request.Id); // setup worker context context.IsSubscribed = true; context.UserAgent = requestParams[0].Trim(); }
private async Task OnAuthorizeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.Other, "missing request id", request.Id); return; } 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 workerParts = workerValue?.Split('.'); var minerName = workerParts?.Length > 0 ? workerParts[0].Trim() : null; var workerName = workerParts?.Length > 1 ? workerParts[1].Trim() : null; // assumes that workerName is an address context.IsAuthorized = !string.IsNullOrEmpty(minerName) && manager.ValidateAddress(minerName); context.MinerName = minerName; context.WorkerName = workerName; // respond await client.RespondAsync(context.IsAuthorized, request.Id); // 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); } await EnsureInitialWorkSent(client); // log association logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}"); }
protected async Task OnGetTransactions(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; try { var transactions = manager.GetTransactions(client, request.ParamsAs <object[]>()); await client.RespondAsync(transactions, request.Id); } catch (StratumException ex) { await client.RespondErrorAsync(ex.Code, ex.Message, request.Id, false); } catch (Exception ex) { logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}"); } }
protected virtual async Task OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; if (request.Id == null) { await client.RespondErrorAsync(StratumError.Other, "missing request id", request.Id); return; } var context = client.ContextAs <BitcoinWorkerContext>(); var requestParams = request.ParamsAs <string[]>(); var data = new object[] { new object[] { new object[] { BitcoinStratumMethods.SetDifficulty, client.ConnectionId }, new object[] { BitcoinStratumMethods.MiningNotify, client.ConnectionId } } } .Concat(manager.GetSubscriberData(client)) .ToArray(); await client.RespondAsync(data, request.Id); // setup worker context context.IsSubscribed = true; context.UserAgent = requestParams?.Length > 0 ? requestParams[0].Trim() : null; // send intial update await client.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); await client.NotifyAsync(BitcoinStratumMethods.MiningNotify, currentJobParams); }
protected override async Task OnRequestAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <MoneroWorkerContext>(); switch (request.Method) { case MoneroStratumMethods.Login: await OnLoginAsync(client, tsRequest); break; case MoneroStratumMethods.GetJob: await OnGetJob(client, tsRequest); break; case MoneroStratumMethods.Submit: await OnSubmitAsync(client, tsRequest); break; case MoneroStratumMethods.KeepAlive: // recognize activity context.LastActivity = clock.Now; break; default: logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}"); await client.RespondErrorAsync(StratumError.Other, $"Unsupported request {request.Method}", request.Id); break; } }
protected override async Task OnRequestAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; try { switch (request.Method) { #region EthereumStratum/1.0.0 case EthereumStratumMethods.Subscribe: await OnSubscribeAsync(client, tsRequest); break; case EthereumStratumMethods.Authorize: await OnAuthorizeAsync(client, tsRequest); break; case EthereumStratumMethods.SubmitShare: await OnSubmitAsync(client, tsRequest, ct); break; case EthereumStratumMethods.ExtraNonceSubscribe: await client.RespondErrorAsync(StratumError.Other, "not supported", request.Id, false); break; #endregion #region Stratum-Proxy case EthereumStratumMethods.SubmitLogin: await OnSubmitLoginAsync(client, tsRequest); break; case EthereumStratumMethods.GetWork: await OnGetWorkAsync(client, tsRequest); break; case EthereumStratumMethods.SubmitHasrate: await OnSubmitHashrateAsync(client, tsRequest); break; case EthereumStratumMethods.SubmitWork: await OnSubmitAsync(client, tsRequest, ct); break; #endregion 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); } }
private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); 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 != 3 || 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]; var share = await manager.SubmitShareAsync(client, submitRequest, context.Difficulty, poolEndpoint.Difficulty); 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(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 3)}"); await EnsureInitialWorkSent(client); // update pool stats if (share.IsBlockCandidate) { poolStats.LastPoolBlockTime = clock.Now; } // update client stats context.Stats.ValidShares++; await UpdateVarDiffAsync(client); } catch (StratumException ex) { await client.RespondErrorAsync(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(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}"); // banning ConsiderBan(client, context, poolConfig.Banning); } }
private async Task OnLoginAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <MoneroWorkerContext>(); if (request.Id == null) { await client.RespondErrorAsync(StratumError.MinusOne, "missing request id", request.Id); return; } var loginRequest = request.ParamsAs <MoneroLoginRequest>(); if (string.IsNullOrEmpty(loginRequest?.Login)) { await client.RespondErrorAsync(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) { await client.RespondErrorAsync(StratumError.MinusOne, "invalid login", request.Id); return; } // validate payment Id if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != MoneroConstants.PaymentIdHexLength) { await client.RespondErrorAsync(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) }; await client.RespondAsync(loginResponse, request.Id); // log association logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {loginRequest.Login} = {client.RemoteEndpoint.Address}"); }
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(() => $"[{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 || !context.IsAuthorized) { throw new StratumException(StratumError.MinusOne, "unauthorized"); } // recognize activity context.LastActivity = clock.Now; MoneroWorkerJob job; lock (context) { var jobId = submitRequest?.JobId; if (string.IsNullOrEmpty(jobId) || (job = 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); await client.RespondAsync(new MoneroResponseBase(), request.Id); // publish messageBus.SendMessage(new ClientShare(client, share)); // telemetry PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true); 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++; await UpdateVarDiffAsync(client); } catch (StratumException ex) { await client.RespondErrorAsync(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(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Message}"); // banning ConsiderBan(client, context, poolConfig.Banning); } }