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 virtual async Task OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { 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 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); }
private async Task OnSuggestDifficultyAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); // acknowledge await client.RespondAsync(true, request.Id); try { var requestedDiff = (double)Convert.ChangeType(request.Params, TypeCode.Double); // client may suggest higher-than-base difficulty, but not a lower one var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port]; if (requestedDiff > poolEndpoint.Difficulty) { context.SetDifficulty(requestedDiff); await client.NotifyAsync(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty }); logger.Info(() => $"[{client.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner"); } } catch (Exception ex) { logger.Error(ex, () => $"Unable to convert suggested difficulty {request.Params}"); } }
protected async Task OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); if (request.Id == null) { throw new StratumException(StratumError.MinusOne, "missing request id"); } 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; }
private async Task OnConfigureMiningAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <BitcoinWorkerContext>(); var requestParams = request.ParamsAs <JToken[]>(); var extensions = requestParams[0].ToObject <string[]>(); var extensionParams = requestParams[1].ToObject <Dictionary <string, JToken> >(); var result = new Dictionary <string, object>(); foreach (var extension in extensions) { switch (extension) { case BitcoinStratumExtensions.VersionRolling: ConfigureVersionRolling(client, context, extensionParams, result); break; case BitcoinStratumExtensions.MinimumDiff: ConfigureMinimumDiff(client, context, extensionParams, result); break; } } await client.RespondAsync(result, 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); } }
private async Task OnSubmitHashrateAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); if (request.Id == null) { throw new StratumException(StratumError.Other, "missing request id"); } // Dummy command, just predend like you did something with it and send true to keep the miner happy await client.RespondAsync(true, request.Id); }
private async Task OnSubmitLoginAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); if (request.Id == null) { throw new StratumException(StratumError.MinusOne, "missing request id"); } context.IsSubscribed = true; var requestParams = request.ParamsAs <string[]>(); // setup worker context var workerValue = requestParams?.Length > 0 ? requestParams[0] : "0"; 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() : "0"; // assumes that workerName is an address context.IsAuthorized = !string.IsNullOrEmpty(minerName) && manager.ValidateAddress(minerName); context.Miner = minerName.ToLower(); context.Worker = workerName; context.IsNiceHashClient = false; // 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); logger.Info(() => $"[{client.ConnectionId}] Setting static difficulty of {staticDiff.Value}"); } await EnsureInitialWorkSent(client); // log association logger.Info(() => $"[{client.ConnectionId}] Authorized Stratum-Proxy Worker {workerValue}"); }
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}"); }
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(); }
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}"); } }
private async Task OnGetWorkAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest) { var request = tsRequest.Value; var context = client.ContextAs <EthereumWorkerContext>(); if (request.Id == null) { throw new StratumException(StratumError.Other, "missing request id"); } object[] newJobParams = (object[])currentJobParams; var header = newJobParams[2]; var seed = newJobParams[1]; var target = EthereumUtils.GetTargetHex(new BigInteger(context.Difficulty * EthereumConstants.StratumDiffFactor)); await client.RespondAsync(new object[] { header, seed, target }, request.Id); context.IsInitialWorkSent = true; await EnsureInitialWorkSent(client); }
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 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.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) { throw new StratumException(StratumError.MinusOne, "invalid login"); } // validate payment Id if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != CryptonoteConstants.PaymentIdHexLength) { throw new StratumException(StratumError.MinusOne, "invalid payment 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); 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 logger.Info(() => $"[{client.ConnectionId}] Authorized worker {loginRequest.Login}"); }
private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest, CancellationToken ct) { var request = tsRequest.Value; var context = client.ContextAs <CryptonoteWorkerContext>(); 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 request var submitRequest = request.ParamsAs <CryptonoteSubmitShareRequest>(); // validate worker if (client.ConnectionId != submitRequest?.WorkerId || !context.IsAuthorized) { throw new StratumException(StratumError.MinusOne, "unauthorized"); } // recognize activity context.LastActivity = clock.Now; CryptonoteWorkerJob 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, ct); await client.RespondAsync(new CryptonoteResponseBase(), 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 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); } }
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); } }