Beispiel #1
0
        protected override void OnSubscribe(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <BitcoinWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var requestParams = request.ParamsAs <string[]>();

            var data = new object[]
            {
                client.ConnectionId,
            }
            .Concat(manager.GetSubscriberData(client))
            .ToArray();

            client.Respond(data, request.Id);

            // setup worker context
            context.IsSubscribed = true;
            context.UserAgent    = requestParams?.Length > 0 ? requestParams[0].Trim() : null;
        }
Beispiel #2
0
        private void OnGetJob(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 getJobRequest = request.ParamsAs <MoneroGetJobRequest>();

            // validate worker
            if (client.ConnectionId != getJobRequest?.WorkerId || !context.IsAuthorized)
            {
                client.RespondError(StratumError.MinusOne, "unauthorized", request.Id);
                return;
            }

            // respond
            var job = CreateWorkerJob(client);

            client.Respond(job, request.Id);
        }
Beispiel #3
0
        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");
                }

                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;
                }

                context.LastActivity = clock.Now;

                if (!context.IsAuthorized)
                {
                    throw new StratumException(StratumError.UnauthorizedWorker, "Unauthorized worker");
                }
                else if (!context.IsSubscribed)
                {
                    throw new StratumException(StratumError.NotSubscribed, "Not subscribed");
                }

                var requestParams = request.ParamsAs <string[]>();
                var poolEndpoint  = poolConfig.Ports[client.PoolEndpoint.Port];

                var share = await manager.SubmitShareAsync(client, requestParams, poolEndpoint.Difficulty);

                client.Respond(true, request.Id);
                messageBus.SendMessage(new ClientShare(client, share));

                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}");

                if (share.IsBlockCandidate)
                {
                    poolStats.LastPoolBlockTime = clock.Now;
                }

                context.Stats.ValidShares++;
                UpdateVarDiff(client);
            }

            catch (StratumException ex)
            {
                client.RespondError(ex.Code, ex.Message, request.Id, false);

                context.Stats.InvalidShares++;
                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected: {ex.Code}");

                ConsiderBan(client, context, poolConfig.Banning);
            }
        }
Beispiel #4
0
        private void OnSuggestDifficulty(StratumClient <BitcoinWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            // acknowledge
            client.Respond(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)
                {
                    client.Context.SetDifficulty(requestedDiff);
                    client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { client.Context.Difficulty });

                    logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Difficulty set to {requestedDiff} as requested by miner");
                }
            }

            catch (Exception ex)
            {
                logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}");
            }
        }
Beispiel #5
0
        protected override async Task OnRequestAsync(StratumClient <EthereumWorkerContext> client,
                                                     Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            switch (request.Method)
            {
            case EthereumStratumMethods.Subscribe:
                OnSubscribe(client, tsRequest);
                break;

            case EthereumStratumMethods.Authorize:
                OnAuthorize(client, tsRequest);
                break;

            case EthereumStratumMethods.SubmitShare:
                await OnSubmitAsync(client, tsRequest);

                break;

            case EthereumStratumMethods.ExtraNonceSubscribe:
                client.Respond(true, request.Id);
                break;

            default:
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Unsupported RPC request: {JsonConvert.SerializeObject(request, serializerSettings)}");

                client.RespondError(StratumError.Other, $"Unsupported request {request.Method}", request.Id);
                break;
            }
        }
Beispiel #6
0
        protected virtual void OnSubscribe(StratumClient <BitcoinWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            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();

            client.Respond(data, request.Id);

            // setup worker context
            client.Context.IsSubscribed = true;
            client.Context.UserAgent    = requestParams?.Length > 0 ? requestParams[0].Trim() : null;

            // send intial update
            client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { client.Context.Difficulty });
            client.Notify(BitcoinStratumMethods.MiningNotify, currentJobParams);
        }
Beispiel #7
0
        private void OnAuthorize(StratumClient <EthereumWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(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;

            // extract worker/miner
            var split      = workerValue?.Split('.');
            var minerName  = split?.FirstOrDefault();
            var workerName = split?.LastOrDefault();

            // assumes that workerName is an address
            client.Context.IsAuthorized = manager.ValidateAddress(minerName);
            client.Context.MinerName    = minerName;
            client.Context.WorkerName   = workerName;
            client.Respond(client.Context.IsAuthorized, request.Id);

            // send intial update
            client.Notify(EthereumStratumMethods.SetDifficulty, new object[] { client.Context.Difficulty });
            client.Notify(EthereumStratumMethods.MiningNotify, currentJobParams);
        }
        private void OnSubscribe(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <EthereumWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var requestParams = request.ParamsAs <string[]>();

            manager.PrepareWorker(client);

            var data = new object[]
            {
                new object[]
                {
                    EthereumStratumMethods.MiningNotify,
                    client.ConnectionId,
                    EthereumConstants.EthereumStratumVersion
                },
                context.ExtraNonce1
            }
            .ToArray();

            client.Respond(data, request.Id);

            // setup worker context
            context.IsSubscribed     = true;
            context.IsNiceHashClient = true;
            //context.UserAgent = requestParams[0].Trim();
        }
Beispiel #9
0
        private void OnAuthorize(StratumClient <EthereumWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(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;

            // extract worker/miner
            var split      = workerValue?.Split('.');
            var minerName  = split?.FirstOrDefault()?.Trim();
            var workerName = split?.Skip(1).LastOrDefault()?.Trim();

            // assumes that workerName is an address
            client.Context.IsAuthorized = !string.IsNullOrEmpty(minerName) && manager.ValidateAddress(minerName);
            client.Context.MinerName    = minerName;
            client.Context.WorkerName   = workerName;

            // respond
            client.Respond(client.Context.IsAuthorized, request.Id);

            EnsureInitialWorkSent(client);

            // log association
            logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}");
        }
        private void OnGetWork(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <EthereumWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            object[] newJobParams = (object[])currentJobParams;
            var      header       = newJobParams[2];
            var      seed         = newJobParams[1];
            var      target       = EthereumUtils.GetTargetHex(new BigInteger(context.Difficulty * EthereumConstants.StratumDiffFactor));

            client.Respond(new object[] { header, seed, target }, request.Id);
            context.IsInitialWorkSent = true;

            var requestParams = request.ParamsAs <string[]>();
            var workerValue   = requestParams?.Length > 0 ? requestParams[0] : null;

            // log association
            if (!string.IsNullOrEmpty(context.WorkerName))
            {
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] recieved GetWork command for {context.MinerName}.{context.WorkerName} from {client.RemoteEndpoint.Address}");
            }
            else
            {
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] received GetWork command for {context.MinerName} from {client.RemoteEndpoint.Address}");
            }
        }
Beispiel #11
0
        protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var context       = client.GetContextAs <BitcoinWorkerContext>();
            var requestParams = request.ParamsAs <string[]>();
            var workerValue   = requestParams?.Length > 0 ? requestParams[0] : null;
            //var password = requestParams?.Length > 1 ? requestParams[1] : null;

            // extract worker/miner
            var split      = workerValue?.Split('.');
            var minerName  = split?.FirstOrDefault();
            var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty;

            // assumes that workerName is an address
            context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName);

            context.MinerName  = minerName;
            context.WorkerName = workerName;

            // respond
            client.Respond(context.IsAuthorized, request.Id);

            // log association
            logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}");
        }
Beispiel #12
0
        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];
            context.WorkerName = split.Length > 1 ? split[1] : null;
            context.UserAgent  = loginRequest.UserAgent;

            // extract paymentid
            var index = context.MinerName.IndexOf('#');

            if (index != -1)
            {
                context.PaymentId = context.MinerName.Substring(index + 1);
                context.MinerName = context.MinerName.Substring(0, index);
            }

            // 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;
            }

            // 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}");
        }
Beispiel #13
0
        protected virtual async Task OnAuthorizeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var context       = client.GetContextAs <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);

            var split      = workerValue?.Split('.');
            var minerName  = split?.FirstOrDefault()?.Trim();
            var workerName = split?.Skip(1).FirstOrDefault()?.Trim() ?? string.Empty;

            context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName);

            context.MinerName  = minerName;
            context.WorkerName = workerName;

            if (context.IsAuthorized)
            {
                client.Respond(context.IsAuthorized, request.Id);

                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}");

                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; context.SetDifficulty(staticDiff.Value);

                    client.Notify(BitcoinStratumMethods.SetDifficulty, new object[] { context.Difficulty });
                }
            }

            else
            {
                client.RespondError(StratumError.UnauthorizedWorker, "Authorization failed", request.Id, context.IsAuthorized);

                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Banning unauthorized worker for 60 sec");

                banManager.Ban(client.RemoteEndpoint.Address, TimeSpan.FromSeconds(60));

                DisconnectClient(client);
            }
        }
        private void OnSubmitLogin(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <EthereumWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var requestParams = request.ParamsAs <string[]>();

            if (requestParams == null || requestParams.Length < 1 || requestParams.Any(string.IsNullOrEmpty))
            {
                client.RespondError(StratumError.MinusOne, "invalid request", request.Id);
                return;
            }

            manager.PrepareWorker(client);
            client.Respond(true, request.Id);

            // setup worker context
            context.IsSubscribed = true;
            context.IsAuthorized = true;
            context.MinerName    = requestParams[0].Trim();

            var workerValue = requestParams?.Length > 0 ? requestParams[0] : null;

            // 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;

            if (!string.IsNullOrEmpty(minerName))
            {
                context.MinerName = minerName.ToLower();
            }
            if (!string.IsNullOrEmpty(workerName))
            {
                context.WorkerName = workerName;
            }

            // log association
            if (!string.IsNullOrEmpty(context.WorkerName))
            {
                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] recieved SubmitLogin command for {context.MinerName}.{context.WorkerName} from {client.RemoteEndpoint.Address}");
            }
            else
            {
                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] recieved SubmitLogin command for {context.MinerName} from {client.RemoteEndpoint.Address}");
            }
        }
Beispiel #15
0
        private async Task OnAuthorizeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.ContextAs <AionWorkerContext>();

            if (request.Id == null)
            {
                throw new StratumException(StratumError.MinusOne, "missing request id");
            }

            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;
            var minimumPayment = GetMinimumPaymentFromPassparts(passParts);

            // assumes that workerName is an address
            context.IsAuthorized = !string.IsNullOrEmpty(minerName) && await manager.ValidateAddressAsync(minerName);

            context.MinerName  = minerName;
            context.WorkerName = workerName;
            // respond
            // await client.RespondAsync(context.IsAuthorized, request.Id);
            client.Respond(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);
            }

            messageBus.SendMessage(new MinerInfo(poolConfig.Id,
                                                 context.MinerName, context.MinimumPayment = minimumPayment));

            EnsureInitialWorkSent(client);

            // log association
            logger.Info(() => $"[{client.ConnectionId}] Authorized worker {workerValue} mp {minimumPayment}");
        }
Beispiel #16
0
        private async void OnAuthorize(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <AionWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(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) && await manager.ValidateAddressAsync(minerName);

            context.MinerName  = minerName;
            context.WorkerName = workerName;

            // respond
            client.Respond(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);
            }

            EnsureInitialWorkSent(client);

            // log association
            logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {workerValue} = {client.RemoteEndpoint.Address}");
        }
        private void OnAuthorize(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <EthereumWorkerContext>();

            if (request.Id == null)
            {
                client.RespondError(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.ToLower();
            context.WorkerName       = workerName;
            context.IsNiceHashClient = true;

            // respond
            client.Respond(context.IsAuthorized, request.Id);

            // send the first job to the client
            EnsureInitialWorkSent(client);

            // log association
            if (!string.IsNullOrEmpty(context.WorkerName))
            {
                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] recieved Authorize command for {context.MinerName}.{context.WorkerName} from {client.RemoteEndpoint.Address}");
            }
            else
            {
                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] recieved Authorize command for {context.MinerName} from {client.RemoteEndpoint.Address}");
            }
        }
Beispiel #18
0
        protected void OnGetTransactions(StratumClient <BitcoinWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            try
            {
                var transactions = manager.GetTransactions(client, request.ParamsAs <object[]>());

                client.Respond(transactions, request.Id);
            }

            catch (StratumException ex)
            {
                client.RespondError(ex.Code, ex.Message, request.Id, false);
            }

            catch (Exception ex)
            {
                logger.Error(ex, () => $"[{LogCat}] Unable to convert suggested difficulty {request.Params}");
            }
        }
Beispiel #19
0
        private void OnSubscribe(StratumClient <EthereumWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            var requestParams = request.ParamsAs <string[]>();

            if (requestParams == null || requestParams.Length < 2 || requestParams.Any(string.IsNullOrEmpty))
            {
                client.RespondError(StratumError.MinusOne, "invalid request", request.Id);
                return;
            }

            manager.PrepareWorker(client);

            var data = new object[]
            {
                new object[]
                {
                    EthereumStratumMethods.MiningNotify,
                    client.ConnectionId,
                    EthereumConstants.EthereumStratumVersion
                },
                client.Context.ExtraNonce1
            }
            .ToArray();

            client.Respond(data, request.Id);

            // setup worker context
            client.Context.IsSubscribed = true;
            client.Context.UserAgent    = requestParams[0].Trim();
        }
Beispiel #20
0
        protected virtual async Task OnAuthorizeAsync(StratumClient <BitcoinWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(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;

            // extract worker/miner
            var split     = workerValue?.Split('.');
            var minerName = split?.FirstOrDefault();

            // assumes that workerName is an address
            client.Context.IsAuthorized = await manager.ValidateAddressAsync(minerName);

            client.Respond(client.Context.IsAuthorized, request.Id);
        }
Beispiel #21
0
        private void OnSubscribeAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.ContextAs <AionWorkerContext>();

            if (request.Id == null)
            {
                throw new StratumException(StratumError.Other, "missing request id");
            }

            var requestParams = request.ParamsAs <string[]>();

            if (requestParams == null || requestParams.Length < 2)
            {
                throw new StratumException(StratumError.MinusOne, "invalid request");
            }

            manager.PrepareWorker(client);
            var data = new object[]
            {
                new object[]
                {
                    AionStratumMethods.MiningNotify,
                    client.ConnectionId,
                    AionConstants.AionStratumVersion
                },
                context.ExtraNonce1
            }
            .ToArray();

            // await client.RespondAsync(data, request.Id);
            client.Respond(data, request.Id);

            // setup worker context
            context.IsSubscribed = true;
            context.UserAgent    = requestParams[0].Trim();
        }
        private void OnSubmitHashrate(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            // Dummy command, just predend like you did something with it and send true to keep the miner happy
            client.Respond(true, request.Id);

            var context = client.GetContextAs <EthereumWorkerContext>();

            if (!string.IsNullOrEmpty(context.WorkerName))
            {
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] received SubmitHashrate command for {context.MinerName}.{context.WorkerName} from {client.RemoteEndpoint.Address}");
            }
            else
            {
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] received SubmitHashrate command for {context.MinerName} from {client.RemoteEndpoint.Address}");
            }
        }
Beispiel #23
0
        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));

                EnsureInitialWorkSent(client);

                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 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);
                }
            }
        }
        private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <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);

                // success
                client.Respond(true, request.Id);
                messageBus.SendMessage(new ClientShare(client, share));

                // update pool stats
                if (share.IsBlockCandidate)
                {
                    poolStats.LastPoolBlockTime = clock.Now;
                }

                // update client stats
                context.Stats.ValidShares++;

                if (!string.IsNullOrEmpty(context.WorkerName))
                {
                    logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted for {context.MinerName}.{context.WorkerName} from {client.RemoteEndpoint.Address}. Diff: {Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 3)}. Shares: {context.Stats.ValidShares}");
                }
                else
                {
                    logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Share accepted for {context.MinerName} from {client.RemoteEndpoint.Address}. Diff: {Math.Round(share.Difficulty / EthereumConstants.Pow2x32, 3)}. Shares: {context.Stats.ValidShares}");
                }
            }

            catch (StratumException ex)
            {
                client.RespondError(ex.Code, ex.Message, request.Id, false);

                // update client stats
                context.Stats.InvalidShares++;

                if (!string.IsNullOrEmpty(context.WorkerName))
                {
                    logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected for {context.MinerName}.{context.WorkerName}: {ex.Code} - {ex.Message}. Invalid shares: {context.Stats.InvalidShares}");
                }
                else
                {
                    logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Share rejected for {context.MinerName}: {ex.Code} - {ex.Message}. Invalid shares: {context.Stats.InvalidShares}");
                }

                // banning
                ConsiderBan(client, context, poolConfig.Banning);
            }
        }
Beispiel #25
0
        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++;
            }

            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
                if (poolConfig.Banning?.Enabled == true && clusterConfig.Banning?.BanOnInvalidShares == true)
                {
                    ConsiderBan(client, context, poolConfig.Banning);
                }
            }
        }
Beispiel #26
0
        private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.ContextAs <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.Warn(() => $"[{client.ConnectionId}] Dropping stale share submission request (server overloaded?)");
                    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
                    // await client.RespondAsync(true, request.Id);
                    client.Respond(true, request.Id);
                    // publish
                    messageBus.SendMessage(new ClientShare(client, share));
                    // telemetry
                    PublishTelemetry(TelemetryCategory.Share, clock.Now - tsRequest.Timestamp.UtcDateTime, true);

                    logger.Debug(() => $"[{client.ConnectionId}] Share accepted: D={Math.Round(share.Difficulty, 3)}");
                    EnsureInitialWorkSent(client);

                    // update pool stats
                    if (share.IsBlockCandidate)
                    {
                        poolStats.LastPoolBlockTime = clock.Now;
                    }

                    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;
                }
            }
            catch (StratumException ex)
            {
                // await client.RespondErrorAsync(ex.Code, ex.Message, request.Id, false);
                client.RespondError(ex.Code, ex.Message, request.Id, false);
                messageBus.SendMessage(new InvalidShare
                {
                    PoolId  = poolConfig.Id,
                    Miner   = context.MinerName,
                    Worker  = context.WorkerName,
                    Created = clock.Now
                });
                // 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;
            }
        }
Beispiel #27
0
        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}");
        }
Beispiel #28
0
        private async Task OnSubmitAsync(StratumClient <BitcoinWorkerContext> client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;

            if (request.Id == null)
            {
                client.RespondError(StratumError.Other, "missing request id", request.Id);
                return;
            }

            // check age of submission (aged submissions are usually caused by high server load)
            var requestAge = DateTime.UtcNow - tsRequest.Timestamp.UtcDateTime;

            if (requestAge > maxShareAge)
            {
                logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Dropping stale share submission request (not client's fault)");
                return;
            }

            // check worker state
            client.Context.LastActivity = DateTime.UtcNow;

            if (!client.Context.IsAuthorized)
            {
                client.RespondError(StratumError.UnauthorizedWorker, "Unauthorized worker", request.Id);
            }
            else if (!client.Context.IsSubscribed)
            {
                client.RespondError(StratumError.NotSubscribed, "Not subscribed", request.Id);
            }
            else
            {
                UpdateVarDiff(client, manager.BlockchainStats.NetworkDifficulty, true);

                try
                {
                    // submit
                    var requestParams = request.ParamsAs <string[]>();
                    var poolEndpoint  = poolConfig.Ports[client.PoolEndpoint.Port];

                    var share = await manager.SubmitShareAsync(client, requestParams, client.Context.Difficulty,
                                                               poolEndpoint.Difficulty);

                    // success
                    client.Respond(true, request.Id);

                    // record it
                    shareSubject.OnNext(share);

                    // update pool stats
                    if (share.IsBlockCandidate)
                    {
                        poolStats.LastPoolBlockTime = DateTime.UtcNow;
                    }

                    // update client stats
                    client.Context.Stats.ValidShares++;

                    // telemetry
                    validSharesSubject.OnNext(share);
                }

                catch (StratumException ex)
                {
                    client.RespondError(ex.Code, ex.Message, request.Id, false);

                    // update client stats
                    client.Context.Stats.InvalidShares++;

                    // telemetry
                    invalidSharesSubject.OnNext(Unit.Default);

                    // banning
                    if (poolConfig.Banning?.Enabled == true)
                    {
                        ConsiderBan(client, client.Context, poolConfig.Banning);
                    }
                }
            }
        }
Beispiel #29
0
        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);
                }
            }
        }
Beispiel #30
0
        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);
            }
        }