Example #1
0
        private async Task <(Share Share, string nonce, string solution, string headerHash, string nTime)> ProcessShareInternal(
            StratumClient worker, string nonce, string nTime, string solution)
        {
            var context       = worker.GetContextAs <AionWorkerContext>();
            var solutionBytes = solution.HexToByteArray();

            // serialize block-header
            var headerBytes = SerializeHeader(nonce);

            // verify solution
            if (!equihash.Verify210(headerBytes, solutionBytes))
            {
                throw new StratumException(StratumError.Other, "invalid solution");
            }

            // hash block-header
            var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray();
            var headerHash          = headerHasher.Digest(headerSolutionBytes);
            var headerHashReversed  = headerHash.ToReverseArray();
            var headerBigInt        = headerHashReversed.ToBigInteger();
            var target = new BigInteger(blockTarget.ToBytes());

            var isBlockCandidate = target > headerBigInt;

            // calc share-diff
            var stratumDifficulty = context.Difficulty > Difficulty ? Difficulty : context.Difficulty;
            var shareDiff         = stratumDifficulty;
            var ratio             = shareDiff / stratumDifficulty;

            var sentTargetInt  = new uint256(AionUtils.diffToTarget(context.Difficulty).HexToByteArray().ReverseArray());
            var sentTarget     = new BigInteger(sentTargetInt.ToBytes());
            var isLowDiffShare = sentTarget <= headerBigInt;

            if (isLowDiffShare)
            {
                // Check if matched a previous varDiff before retarget
                if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                {
                    var prevSentTargetInt = new uint256(AionUtils.diffToTarget(context.PreviousDifficulty.Value).HexToByteArray().ReverseArray());
                    var prevSentTargetBi  = new BigInteger(prevSentTargetInt.ToBytes());
                    if (prevSentTargetBi <= headerBigInt)
                    {
                        stratumDifficulty = context.PreviousDifficulty.Value;
                    }
                }
                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            var result = new Share
            {
                BlockHeight                 = (long)BlockTemplate.Height,
                IpAddress                   = worker.RemoteEndpoint?.Address?.ToString(),
                Miner                       = context.MinerName,
                Worker                      = context.WorkerName,
                UserAgent                   = context.UserAgent,
                NetworkDifficulty           = Difficulty,
                Difficulty                  = stratumDifficulty,
                IsBlockCandidate            = isBlockCandidate,
                TransactionConfirmationData = headerHash.ToHexString(),
            };

            if (isBlockCandidate)
            {
                result.BlockReward = AionUtils.calculateReward((long)BlockTemplate.Height);
                result.BlockHash   = headerHashReversed.ToHexString();
            }

            return(result, nonce, solution, BlockTemplate.HeaderHash, nTime);
        }
Example #2
0
        public (Share Share, string BlobHex, string BlobHash) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker)
        {
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty");
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty");
            Contract.Requires <ArgumentException>(workerExtraNonce != 0, $"{nameof(workerExtraNonce)} must not be empty");

            var context = worker.ContextAs <MoneroWorkerContext>();

            // validate nonce
            if (!MoneroConstants.RegexValidNonce.IsMatch(nonce))
            {
                throw new StratumException(StratumError.MinusOne, "malformed nonce");
            }

            // clone template
            using (var blob = new PooledArraySegment <byte>(blobTemplate.Length))
            {
                Buffer.BlockCopy(blobTemplate, 0, blob.Array, 0, blobTemplate.Length);

                // inject extranonce
                var extraNonceBytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian());
                Buffer.BlockCopy(extraNonceBytes, 0, blob.Array, (int)BlockTemplate.ReservedOffset, extraNonceBytes.Length);

                // inject nonce
                var nonceBytes = nonce.HexToByteArray();
                Buffer.BlockCopy(nonceBytes, 0, blob.Array, MoneroConstants.BlobNonceOffset, nonceBytes.Length);

                // convert
                var blobConverted = LibCryptonote.ConvertBlob(blob.Array, blobTemplate.Length);
                if (blobConverted == null)
                {
                    throw new StratumException(StratumError.MinusOne, "malformed blob");
                }

                // hash it
                using (var hashSeg = hashSlow(blobConverted))
                {
                    var hash = hashSeg.ToHexString();
                    if (hash != workerHash)
                    {
                        throw new StratumException(StratumError.MinusOne, "bad hash");
                    }

                    // check difficulty
                    var headerValue       = hashSeg.ToBigInteger();
                    var shareDiff         = (double)new BigRational(MoneroConstants.Diff1b, headerValue);
                    var stratumDifficulty = context.Difficulty;
                    var ratio             = shareDiff / stratumDifficulty;
                    var isBlockCandidate  = shareDiff >= BlockTemplate.Difficulty;

                    // test if share meets at least workers current difficulty
                    if (!isBlockCandidate && ratio < 0.99)
                    {
                        // check if share matched the previous difficulty from before a vardiff retarget
                        if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                        {
                            ratio = shareDiff / context.PreviousDifficulty.Value;

                            if (ratio < 0.99)
                            {
                                throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                            }

                            // use previous difficulty
                            stratumDifficulty = context.PreviousDifficulty.Value;
                        }

                        else
                        {
                            throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                        }
                    }

                    using (var blockHash = ComputeBlockHash(blobConverted))
                    {
                        var result = new Share
                        {
                            BlockHeight      = BlockTemplate.Height,
                            IsBlockCandidate = isBlockCandidate,
                            BlockHash        = blockHash.ToHexString(),
                            Difficulty       = stratumDifficulty,
                        };

                        var blobHex  = blob.ToHexString();
                        var blobHash = blockHash.ToHexString();

                        return(result, blobHex, blobHash);
                    }
                }
            }
        }
Example #3
0
        protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string extraNonce2, uint nTime, uint nonce)
        {
            var context     = worker.ContextAs <BitcoinWorkerContext>();
            var extraNonce1 = context.ExtraNonce1;

            // build coinbase
            var coinbase     = SerializeCoinbase(extraNonce1, extraNonce2);
            var coinbaseHash = coinbaseHasher.Digest(coinbase);

            // hash block-header
            var headerBytes = SerializeHeader(coinbaseHash, nTime, nonce);
            var headerHash  = headerHasher.Digest(headerBytes, (ulong)nTime);
            var headerValue = new uint256(headerHash);

            // calc share-diff
            var shareDiff         = (double)new BigRational(BitcoinConstants.Diff1, headerHash.ToBigInteger()) * shareMultiplier;
            var stratumDifficulty = context.Difficulty;
            var ratio             = shareDiff / stratumDifficulty;

            // check if the share meets the much harder block difficulty (block candidate)
            var isBlockCandidate = headerValue <= blockTargetValue;

            // test if share meets at least workers current difficulty
            if (!isBlockCandidate && ratio < 0.99)
            {
                // check if share matched the previous difficulty from before a vardiff retarget
                if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                {
                    ratio = shareDiff / context.PreviousDifficulty.Value;

                    if (ratio < 0.99)
                    {
                        throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                    }

                    // use previous difficulty
                    stratumDifficulty = context.PreviousDifficulty.Value;
                }

                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            var result = new Share
            {
                BlockHeight       = BlockTemplate.Height,
                NetworkDifficulty = Difficulty * shareMultiplier,
                Difficulty        = stratumDifficulty,
            };

            if (isBlockCandidate)
            {
                result.IsBlockCandidate = true;
                result.BlockReward      = rewardToPool.ToDecimal(MoneyUnit.BTC);
                result.BlockHash        = blockHasher.Digest(headerBytes, nTime).ToHexString();

                var blockBytes = SerializeBlock(headerBytes, coinbase);
                var blockHex   = blockBytes.ToHexString();

                return(result, blockHex);
            }

            return(result, null);
        }
Example #4
0
        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);
            }
        }
Example #5
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);
            }
        }
Example #6
0
        public override async Task <Share> SubmitShareAsync(StratumClient worker, object submission,
                                                            double stratumDifficultyBase, CancellationToken ct)
        {
            Contract.RequiresNonNull(worker, nameof(worker));
            Contract.RequiresNonNull(submission, nameof(submission));

            logger.LogInvoke(new[] { worker.ConnectionId });

            if (!(submission is object[] submitParams))
            {
                throw new StratumException(StratumError.Other, "invalid params");
            }

            var context = worker.ContextAs <BitcoinWorkerContext>();

            // extract params
            var workerValue = (submitParams[0] as string)?.Trim();
            var jobId       = submitParams[1] as string;
            var nTime       = submitParams[2] as string;
            var extraNonce2 = submitParams[3] as string;
            var solution    = submitParams[4] as string;

            if (string.IsNullOrEmpty(workerValue))
            {
                throw new StratumException(StratumError.Other, "missing or invalid workername");
            }

            if (string.IsNullOrEmpty(solution))
            {
                throw new StratumException(StratumError.Other, "missing or invalid solution");
            }

            EquihashJob job;

            lock (jobLock)
            {
                job = validJobs.FirstOrDefault(x => x.JobId == jobId);
            }

            if (job == null)
            {
                throw new StratumException(StratumError.JobNotFound, "job not found");
            }

            // extract worker/miner/payoutid
            var split      = workerValue.Split('.');
            var minerName  = split[0];
            var workerName = split.Length > 1 ? split[1] : null;

            // validate & process
            var(share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, solution);

            // if block candidate, submit & check if accepted by network
            if (share.IsBlockCandidate)
            {
                logger.Info(() => $"Submitting block {share.BlockHeight} [{share.BlockHash}]");

                var acceptResponse = await SubmitBlockAsync(share, blockHex);

                // is it still a block candidate?
                share.IsBlockCandidate = acceptResponse.Accepted;

                if (share.IsBlockCandidate)
                {
                    logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash}] submitted by {minerName}");

                    blockSubmissionSubject.OnNext(Unit.Default);

                    // persist the coinbase transaction-hash to allow the payment processor
                    // to verify later on that the pool has received the reward for the block
                    share.TransactionConfirmationData = acceptResponse.CoinbaseTx;
                }

                else
                {
                    // clear fields that no longer apply
                    share.TransactionConfirmationData = null;
                }
            }

            // enrich share with common data
            share.PoolId            = poolConfig.Id;
            share.IpAddress         = worker.RemoteEndpoint.Address.ToString();
            share.Miner             = minerName;
            share.Worker            = workerName;
            share.UserAgent         = context.UserAgent;
            share.Source            = clusterConfig.ClusterName;
            share.NetworkDifficulty = job.Difficulty;
            share.Difficulty        = share.Difficulty;
            share.Created           = clock.Now;

            return(share);
        }
Example #7
0
        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);
            }
        }
Example #8
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++;
                UpdateVarDiff(client);
            }

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

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

                // banning
                ConsiderBan(client, context, poolConfig.Banning);
            }
        }
Example #9
0
        public async ValueTask <Share> SubmitShareAsync(StratumClient worker, string[] request, CancellationToken ct)
        {
            Contract.RequiresNonNull(worker, nameof(worker));
            Contract.RequiresNonNull(request, nameof(request));

            logger.LogInvoke(new[] { worker.ConnectionId });
            var context = worker.ContextAs <EthereumWorkerContext>();

            // var miner = request[0];
            // var jobId = request[1];
            // var nonce = request[2];
            EthereumJob job;

            string miner, jobId, nonce = string.Empty;

            if (context.IsNiceHashClient)
            {
                jobId = request[1];
                nonce = request[2];
                miner = request[0];

                lock (jobLock)
                {
                    var jobResult = validJobs.Where(x => x.Value.Id == jobId).FirstOrDefault();
                    if (jobResult.Value == null)
                    {
                        throw new StratumException(StratumError.MinusOne, "stale share");
                    }
                    job = jobResult.Value;
                }
            }

            else
            {
                jobId = request[1];
                nonce = request[0];

                lock (jobLock)
                {
                    var jobResult = validJobs.Where(x => x.Value.BlockTemplate.Header == jobId).FirstOrDefault();
                    if (jobResult.Value == null)
                    {
                        throw new StratumException(StratumError.MinusOne, "stale share");
                    }
                    job = jobResult.Value;
                }
            }

            // validate & process
            var(share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, nonce, ethash, ct);

            // enrich share with common data
            share.PoolId            = poolConfig.Id;
            share.NetworkDifficulty = BlockchainStats.NetworkDifficulty;
            share.Source            = clusterConfig.ClusterName;
            share.Created           = clock.UtcNow;

            // if block candidate, submit & check if accepted by network
            if (share.IsBlockCandidate)
            {
                logger.Info(() => $"Submitting block {share.BlockHeight}");

                share.IsBlockCandidate = await SubmitBlockAsync(share, fullNonceHex, headerHash, mixHash);

                if (share.IsBlockCandidate)
                {
                    logger.Info(() => $"Daemon accepted block {share.BlockHeight} submitted by {context.Miner}");
                }
            }

            return(share);
        }
Example #10
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));

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

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

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

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

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

                // banning
                if (poolConfig.Banning?.Enabled == true)
                {
                    ConsiderBan(client, client.Context, poolConfig.Banning);
                }
            }
        }
Example #11
0
        public async Task <EthereumShare> ProcessShareAsync(StratumClient <EthereumWorkerContext> worker, string nonce, EthashFull ethash)
        {
            // duplicate nonce?
            lock (workerNonces)
            {
                RegisterNonce(worker, nonce);
            }

            // assemble full-nonce
            var fullNonceHex = worker.Context.ExtraNonce1 + nonce;
            var fullNonce    = ulong.Parse(fullNonceHex, NumberStyles.HexNumber);

            // get dag for block
            var dag = await ethash.GetDagAsync(BlockTemplate.Height);

            // compute
            if (!dag.Compute(BlockTemplate.Header.HexToByteArray(), fullNonce, out var mixDigest, out var resultBytes))
            {
                throw new StratumException(StratumError.MinusOne, "bad hash");
            }

            // test if share meets at least workers current difficulty
            var resultValue       = BigInteger.Parse("0" + resultBytes.ToHexString(), NumberStyles.HexNumber);
            var shareDiff         = (double)BigInteger.Divide(EthereumConstants.BigMaxValue, resultValue) / EthereumConstants.Pow2x32;
            var stratumDifficulty = worker.Context.Difficulty;
            var ratio             = shareDiff / stratumDifficulty;
            var isBlockCandidate  = resultValue.CompareTo(BlockTemplate.Target) <= 0;

            if (!isBlockCandidate && ratio < 0.99)
            {
                // check if share matched the previous difficulty from before a vardiff retarget
                if (worker.Context.VarDiff?.LastUpdate != null && worker.Context.PreviousDifficulty.HasValue)
                {
                    ratio = shareDiff / worker.Context.PreviousDifficulty.Value;

                    if (ratio < 0.99)
                    {
                        throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                    }

                    // use previous difficulty
                    stratumDifficulty = worker.Context.PreviousDifficulty.Value;
                }

                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            // create share
            var share = new EthereumShare
            {
                BlockHeight      = (long)BlockTemplate.Height,
                IpAddress        = worker.RemoteEndpoint?.Address?.ToString(),
                Miner            = worker.Context.MinerName,
                Worker           = worker.Context.WorkerName,
                UserAgent        = worker.Context.UserAgent,
                FullNonceHex     = "0x" + fullNonceHex,
                HeaderHash       = BlockTemplate.Header,
                MixHash          = mixDigest.ToHexString(true),
                IsBlockCandidate = isBlockCandidate,
                Difficulty       = stratumDifficulty * EthereumConstants.Pow2x32,
            };

            if (share.IsBlockCandidate)
            {
                share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{share.FullNonceHex}";
            }

            return(share);
        }
        public async Task <(Share Share, string FullNonceHex, string HeaderHash, string MixHash)> ProcessShareAsync(StratumClient worker, string nonce, EthashFull ethash)
        {
            // duplicate nonce?
            //lock (workerNonces)
            //{
            //    RegisterNonce(worker, nonce);
            //}

            // assemble full-nonce
            var context      = worker.GetContextAs <EthereumWorkerContext>();
            var fullNonceHex = nonce.StartsWith("0x") ? nonce.Substring(2) : nonce;

            if (context.IsNiceHashClient && !string.IsNullOrEmpty(context.ExtraNonce1))
            {
                fullNonceHex = context.ExtraNonce1 + fullNonceHex;
            }

            if (!ulong.TryParse(fullNonceHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var fullNonce))
            {
                throw new StratumException(StratumError.Other, "bad nonce " + fullNonceHex);
            }

            // get dag for block
            var dag = await ethash.GetDagAsync(BlockTemplate.Height, logger);

            // compute
            if (!dag.Compute(logger, BlockTemplate.Header.HexToByteArray(), fullNonce, out var mixDigest, out var resultBytes))
            {
                throw new StratumException(StratumError.Other, "bad hash");
            }

            resultBytes.ReverseArray();

            // test if share meets at least workers current difficulty
            var resultValue       = new uint256(resultBytes);
            var resultValueBig    = resultBytes.ToBigInteger();
            var shareDiff         = (double)BigInteger.Divide(EthereumConstants.BigMaxValue, resultValueBig);
            var stratumDifficulty = context.Difficulty * EthereumConstants.StratumDiffFactor;
            var ratio             = shareDiff / stratumDifficulty;
            var isBlockCandidate  = resultValue <= blockTarget;

            if (!isBlockCandidate && ratio < 0.98)
            {
                throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
            }

            // create share
            var share = new Share
            {
                BlockHeight      = (long)BlockTemplate.Height,
                IpAddress        = worker.RemoteEndpoint?.Address?.ToString(),
                Miner            = context.MinerName,
                Worker           = context.WorkerName,
                UserAgent        = context.UserAgent,
                IsBlockCandidate = isBlockCandidate,
                Difficulty       = stratumDifficulty,
                BlockHash        = mixDigest.ToHexString(true)
            };

            if (share.IsBlockCandidate)
            {
                var headerHash = BlockTemplate.Header;
                var mixHash    = mixDigest.ToHexString(true);

                share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{nonce}";

                return(share, fullNonceHex, headerHash, mixHash);
            }

            return(share, null, null, null);
        }
Example #13
0
        private (Share Share, string nonce, string solution, string headerHash, string nTime) ProcessShareInternal(
            StratumClient worker, string nonce, string nTime, string solution)
        {
            var context       = worker.ContextAs <AionWorkerContext>();
            var solutionBytes = solution.HexToByteArray();
            // serialize block-header
            var headerBytes = SerializeHeader(nonce);

            // verify solution
            if (!equihash.Verify(headerBytes, solutionBytes))
            {
                throw new StratumException(StratumError.Other, "invalid solution");
            }

            // hash block-header
            var         headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray();
            Span <byte> headerHash          = stackalloc byte[32];

            headerHasher.Digest(headerSolutionBytes, headerHash);
            var headerHashReversed = headerHash.ToNewReverseArray();
            var headerValue        = headerHashReversed.ToBigInteger();
            var target             = new BigInteger(blockTarget.ToBytes());

            var isBlockCandidate = target > headerValue;

            logger.Debug(() => $"context.Difficulty:{context.Difficulty} Difficulty: {Difficulty}");
            // calc share-diff
            var stratumDifficulty = context.Difficulty > Difficulty ? Difficulty : context.Difficulty;
            var shareDiff         = stratumDifficulty;
            var ratio             = shareDiff / stratumDifficulty;

            var sentTargetInt  = new uint256(AionUtils.diffToTarget(context.Difficulty).HexToReverseByteArray());
            var sentTarget     = new BigInteger(sentTargetInt.ToBytes());
            var isLowDiffShare = sentTarget <= headerValue;

            if (isLowDiffShare)
            {
                throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
            }

            var result = new Share
            {
                BlockHeight                 = (long)BlockTemplate.Height,
                IpAddress                   = worker.RemoteEndpoint?.Address?.ToString(),
                Miner                       = context.MinerName,
                Worker                      = context.WorkerName,
                UserAgent                   = context.UserAgent,
                NetworkDifficulty           = Difficulty,
                Difficulty                  = stratumDifficulty,
                IsBlockCandidate            = isBlockCandidate,
                TransactionConfirmationData = headerHash.ToHexString(),
            };

            if (isBlockCandidate)
            {
                // result.BlockReward = AionUtils.calculateReward((long) BlockTemplate.Height);
                result.BlockHash = headerHashReversed.ToHexString();
            }

            return(result, nonce, solution, BlockTemplate.HeaderHash, nTime);
        }
Example #14
0
        private (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string nonce,
                                                                    uint nTime, string solution)
        {
            var context       = worker.ContextAs <BitcoinWorkerContext>();
            var solutionBytes = (Span <byte>)solution.HexToByteArray();

            // serialize block-header
            var headerBytes = SerializeHeader(nTime, nonce);

            // verify solution
            if (!solver.Verify(headerBytes, solutionBytes.Slice(networkParams.SolutionPreambleSize)))
            {
                throw new StratumException(StratumError.Other, "invalid solution");
            }

            // concat header and solution
            Span <byte> headerSolutionBytes = stackalloc byte[headerBytes.Length + solutionBytes.Length];

            headerBytes.CopyTo(headerSolutionBytes);
            solutionBytes.CopyTo(headerSolutionBytes.Slice(headerBytes.Length));

            // hash block-header
            Span <byte> headerHash = stackalloc byte[32];

            headerHasher.Digest(headerSolutionBytes, headerHash, (ulong)nTime);
            var headerValue = new uint256(headerHash);

            // calc share-diff
            var shareDiff         = (double)new BigRational(networkParams.Diff1BValue, headerHash.ToBigInteger());
            var stratumDifficulty = context.Difficulty;
            var ratio             = shareDiff / stratumDifficulty;

            // check if the share meets the much harder block difficulty (block candidate)
            var isBlockCandidate = headerValue <= blockTargetValue;

            // test if share meets at least workers current difficulty
            if (!isBlockCandidate && ratio < 0.99)
            {
                // check if share matched the previous difficulty from before a vardiff retarget
                if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                {
                    ratio = shareDiff / context.PreviousDifficulty.Value;

                    if (ratio < 0.99)
                    {
                        throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                    }

                    // use previous difficulty
                    stratumDifficulty = context.PreviousDifficulty.Value;
                }

                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            var result = new Share
            {
                BlockHeight       = BlockTemplate.Height,
                NetworkDifficulty = Difficulty,
                Difficulty        = stratumDifficulty,
            };

            if (isBlockCandidate)
            {
                var headerHashReversed = headerHash.ToNewReverseArray();

                result.IsBlockCandidate = true;
                result.BlockReward      = rewardToPool.ToDecimal(MoneyUnit.BTC);
                result.BlockHash        = headerHashReversed.ToHexString();

                var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes);
                var blockHex   = blockBytes.ToHexString();

                return(result, blockHex);
            }

            return(result, null);
        }
Example #15
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);
                }
            }
        }
Example #16
0
 public void PrepareWorker(StratumClient <EthereumWorkerContext> client)
 {
     client.Context.ExtraNonce1 = extraNonceProvider.Next();
 }
Example #17
0
        protected override void OnConnect(StratumClient client)
        {
            // update stats
            lock (clients)
            {
                poolStats.ConnectedMiners = clients.Count;
            }

            // client setup
            var context = CreateClientContext();

            var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port];

            context.Init(poolConfig, poolEndpoint.Difficulty, poolEndpoint.VarDiff, clock);
            client.SetContext(context);

            // varDiff setup
            if (context.VarDiff != null)
            {
                // get or create manager
                lock (varDiffManagers)
                {
                    if (!varDiffManagers.TryGetValue(poolEndpoint, out var varDiffManager))
                    {
                        varDiffManager = new VarDiffManager(poolEndpoint.VarDiff, clock);
                        varDiffManagers[poolEndpoint] = varDiffManager;
                    }
                }

                // wire updates
                lock (context.VarDiff)
                {
                    context.VarDiff.Subscription = Shares
                                                   .Where(x => x.Item1 == client)
                                                   .Timestamp()
                                                   .Select(x => x.Timestamp.ToUnixTimeMilliseconds())
                                                   .Buffer(TimeSpan.FromSeconds(poolEndpoint.VarDiff.RetargetTime), VarDiffSampleCount)
                                                   .Subscribe(timestamps =>
                    {
                        try
                        {
                            VarDiffManager varDiffManager;

                            lock (varDiffManagers)
                            {
                                varDiffManager = varDiffManagers[poolEndpoint];
                            }

                            var newDiff = varDiffManager.Update(context, timestamps, client.ConnectionId, logger);

                            if (newDiff.HasValue)
                            {
                                logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] VarDiff update to {Math.Round(newDiff.Value, 2)}");

                                OnVarDiffUpdate(client, newDiff.Value);
                            }
                        }

                        catch (Exception ex)
                        {
                            logger.Error(ex);
                        }
                    });
                }
            }

            // expect miner to establish communication within a certain time
            EnsureNoZombieClient(client);
        }
Example #18
0
 public ClientShare(StratumClient client, IShare share)
 {
     Client = client;
     Share  = share;
 }
Example #19
0
        protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string extraNonce,
                                                                              uint nTime, string nonce, string solution)
        {
            var context         = worker.ContextAs <BitcoinWorkerContext>();
            var solutionBytes   = solution.HexToByteArray();
            var extraNonceBytes = extraNonce.HexToByteArray();
            var nonceInt        = uint.Parse(nonce, NumberStyles.HexNumber);

            // serialize block-header
            var headerBytes = SerializeHeader(nTime, extraNonceBytes, nonceInt);

            // verify solution
            if (!equihash.Verify(headerBytes, solutionBytes))
            {
                throw new StratumException(StratumError.Other, "invalid solution");
            }

            // hash block-header
            var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray();
            var headerHash          = headerHasher.Digest(headerSolutionBytes);
            var headerValue         = new uint256(headerHash);

            // calc share-diff
            double shareDiff         = (double)new BigRational(chainConfig.Diff1b, headerHash.ToBigInteger());
            var    stratumDifficulty = context.Difficulty;
            var    ratio             = shareDiff / stratumDifficulty;

            // check if the share meets the much harder block difficulty (block candidate)
            var isBlockCandidate = headerValue < blockTarget.ToUInt256();

            // test if share meets at least workers current difficulty
            if (!isBlockCandidate && ratio < 0.99)
            {
                // check if share matched the previous difficulty from before a vardiff retarget
                if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                {
                    ratio = shareDiff / context.PreviousDifficulty.Value;

                    if (ratio < 0.99)
                    {
                        throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                    }

                    // use previous difficulty
                    stratumDifficulty = context.PreviousDifficulty.Value;
                }

                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            var result = new Share
            {
                BlockHeight       = BlockHeader.Height,
                NetworkDifficulty = Difficulty,
                Difficulty        = stratumDifficulty,
            };

            if (isBlockCandidate)
            {
                result.IsBlockCandidate = true;
                result.BlockHash        = headerValue.ToString();

                var blockHex = headerSolutionBytes.ToHexString();

                return(result, blockHex);
            }

            return(result, null);
        }
Example #20
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}");
        }
Example #21
0
        private async Task OnLoginAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.ContextAs <CryptonoteWorkerContext>();

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

            var loginRequest = request.ParamsAs <CryptonoteLoginRequest>();

            if (string.IsNullOrEmpty(loginRequest?.Login))
            {
                throw new StratumException(StratumError.MinusOne, "missing login");
            }

            // extract worker/miner/paymentid
            var split = loginRequest.Login.Split('.');

            context.Miner     = split[0].Trim();
            context.Worker    = split.Length > 1 ? split[1].Trim() : "0";
            context.UserAgent = loginRequest.UserAgent?.Trim();

            var addressToValidate = context.Miner;

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

            if (index != -1)
            {
                var paymentId = context.Miner.Substring(index + 1).Trim();

                // validate
                if (!string.IsNullOrEmpty(paymentId) && paymentId.Length != CryptonoteConstants.PaymentIdHexLength)
                {
                    throw new StratumException(StratumError.MinusOne, "invalid payment id");
                }

                // re-append to address
                addressToValidate = context.Miner.Substring(0, index).Trim();
                context.Miner     = addressToValidate + PayoutConstants.PayoutInfoSeperator + paymentId;
            }

            // validate login
            var result = manager.ValidateAddress(addressToValidate);

            if (!result)
            {
                throw new StratumException(StratumError.MinusOne, "invalid login");
            }

            context.IsSubscribed = result;
            context.IsAuthorized = result;

            // extract control vars from password
            var passParts  = loginRequest.Password?.Split(PasswordControlVarsSeparator);
            var staticDiff = GetStaticDiffFromPassparts(passParts);

            if (staticDiff.HasValue &&
                (context.VarDiff != null && staticDiff.Value >= context.VarDiff.Config.MinDiff ||
                 context.VarDiff == null && staticDiff.Value > context.Difficulty))
            {
                context.VarDiff = null; // disable vardiff
                context.SetDifficulty(staticDiff.Value);

                logger.Info(() => $"[{client.ConnectionId}] Setting static difficulty of {staticDiff.Value}");
            }

            // respond
            var loginResponse = new CryptonoteLoginResponse
            {
                Id  = client.ConnectionId,
                Job = CreateWorkerJob(client)
            };

            await client.RespondAsync(loginResponse, request.Id);

            // log association
            if (!string.IsNullOrEmpty(context.Worker))
            {
                logger.Info(() => $"[{client.ConnectionId}] Authorized worker {context.Worker}@{context.Miner}");
            }
            else
            {
                logger.Info(() => $"[{client.ConnectionId}] Authorized miner {context.Miner}");
            }
        }
Example #22
0
        protected virtual void OnVarDiffUpdate(StratumClient client, double newDiff)
        {
            var context = client.GetContextAs <WorkerContextBase>();

            context.EnqueueNewDifficulty(newDiff);
        }
Example #23
0
        private async Task OnSubmitAsync(StratumClient client, Timestamped <JsonRpcRequest> tsRequest)
        {
            var request = tsRequest.Value;
            var context = client.GetContextAs <AionWorkerContext>();

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

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

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

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

                // check request
                var submitRequest = request.ParamsAs <string[]>();

                if (submitRequest.Length != 5 || submitRequest.Any(string.IsNullOrEmpty))
                {
                    throw new StratumException(StratumError.MinusOne, "malformed PoW result");
                }

                // recognize activity
                context.LastActivity = clock.Now;

                var poolEndpoint = poolConfig.Ports[client.PoolEndpoint.Port];
                try
                {
                    var share = await manager.SubmitShareAsync(client, submitRequest, context.Difficulty, poolEndpoint.Difficulty);

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

                    EnsureInitialWorkSent(client);

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

                    // update pool stats
                    if (share.IsBlockCandidate)
                    {
                        poolStats.LastPoolBlockTime = clock.Now;
                    }
                }
                catch (MiningCore.Stratum.StratumException ex)
                {
                    logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] Exception occured: {ex.Message}");
                    throw ex;
                }

                // update client stats
                context.Stats.ValidShares++;
                UpdateVarDiff(client);
            } catch (StratumException ex) {
                client.RespondError(ex.Code, ex.Message, request.Id, false);

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

                // banning
                if (context.Stats.InvalidShares > ((poolConfig.Banning.CheckThreshold / 2) - 10) &&
                    context.Stats.InvalidShares < ((poolConfig.Banning.CheckThreshold / 2) + 10))
                {
                    if (!context.IsInitialWorkSent && context.IsAuthorized)
                    {
                        EnsureInitialWorkSent(client);
                    }
                    else if (context.IsAuthorized)
                    {
                        OnNewJob(currentJobParams);
                    }
                    else
                    {
                        DisconnectClient(client);
                    }
                }
                else
                {
                    ConsiderBan(client, context, poolConfig.Banning);
                }
            }
        }
Example #24
0
        public void PrepareWorker(StratumClient client)
        {
            var context = client.ContextAs <EthereumWorkerContext>();

            context.ExtraNonce1 = extraNonceProvider.Next();
        }
Example #25
0
        protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string nonce,
                                                            uint nTime, string solution)
        {
            var context       = worker.GetContextAs <BitcoinWorkerContext>();
            var solutionBytes = solution.HexToByteArray();

            // serialize block-header
            var headerBytes = SerializeHeader(nTime, nonce); // 144 bytes (doesn't contain soln)

            // verify solution
            if (!equihash.Verify(headerBytes, solutionBytes.Skip(3).ToArray())) // skip preamble (3 bytes)
            {
                throw new StratumException(StratumError.Other, "invalid solution");
            }

            // hash block-header
            var headerSolutionBytes = headerBytes.Concat(solutionBytes).ToArray();
            var headerHash          = headerHasher.Digest(headerSolutionBytes, (ulong)nTime);
            var headerHashReversed  = headerHash.ToReverseArray();
            var headerValue         = new uint256(headerHash);

            // calc share-diff
            var shareDiff         = (double)new BigRational(coinbaseTxConfig.Diff1b, headerHash.ToBigInteger()) * shareMultiplier;
            var stratumDifficulty = context.Difficulty;
            var ratio             = shareDiff / stratumDifficulty;

            // check if the share meets the much harder block difficulty (block candidate)
            var isBlockCandidate = headerValue <= blockTargetValue;

            // test if share meets at least workers current difficulty
            if (!isBlockCandidate && ratio < 0.99)
            {
                // check if share matched the previous difficulty from before a vardiff retarget
                if (context.VarDiff?.LastUpdate != null && context.PreviousDifficulty.HasValue)
                {
                    ratio = shareDiff / context.PreviousDifficulty.Value;

                    if (ratio < 0.99)
                    {
                        throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                    }

                    // use previous difficulty
                    stratumDifficulty = context.PreviousDifficulty.Value;
                }

                else
                {
                    throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})");
                }
            }

            var result = new BitcoinShare
            {
                BlockHeight      = BlockTemplate.Height,
                IsBlockCandidate = isBlockCandidate,
                Difficulty       = stratumDifficulty
            };

            if (isBlockCandidate)
            {
                var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes);
                result.BlockHex    = blockBytes.ToHexString();
                result.BlockHash   = headerHashReversed.ToHexString();
                result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC);
            }

            return(result);
        }
Example #26
0
        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;
            }
        }
Example #27
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].Trim();
            context.WorkerName = split.Length > 1 ? split[1].Trim() : null;
            context.UserAgent  = loginRequest.UserAgent.Trim();

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

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

            // validate login
            var result = manager.ValidateAddress(context.MinerName);

            context.IsSubscribed = result;
            context.IsAuthorized = result;

            if (!context.IsAuthorized)
            {
                client.RespondError(StratumError.MinusOne, "invalid login", request.Id);
                return;
            }

            // validate payment Id
            if (!string.IsNullOrEmpty(context.PaymentId) && context.PaymentId.Length != MoneroConstants.PaymentIdHexLength)
            {
                client.RespondError(StratumError.MinusOne, "invalid payment id", request.Id);
                return;
            }

            // respond
            var loginResponse = new MoneroLoginResponse
            {
                Id  = client.ConnectionId,
                Job = CreateWorkerJob(client)
            };

            client.Respond(loginResponse, request.Id);

            // log association
            logger.Info(() => $"[{LogCat}] [{client.ConnectionId}] = {loginRequest.Login} = {client.RemoteEndpoint.Address}");
        }