예제 #1
0
        public void Configure(DaemonEndpointConfig[] endPoints, string digestAuthRealm = null)
        {
            Contract.RequiresNonNull(endPoints, nameof(endPoints));
            Contract.Requires <ArgumentException>(endPoints.Length > 0, $"{nameof(endPoints)} must not be empty");

            this.endPoints = endPoints;

            // create one HttpClient instance per endpoint that carries the associated credentials
            httpClients = endPoints.ToDictionary(endpoint => endpoint, endpoint =>
            {
                if (string.IsNullOrEmpty(endpoint.User) || !endpoint.DigestAuth)
                {
                    return(defaultHttpClient);
                }

                return(new HttpClient(new SocketsHttpHandler
                {
                    AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,

                    Credentials = new NetworkCredential(endpoint.User, endpoint.Password),
                    PreAuthenticate = true,

                    SslOptions = new SslClientAuthenticationOptions
                    {
                        RemoteCertificateValidationCallback = ((sender, certificate, chain, errors) => true),
                    }
                }));
            });
        }
예제 #2
0
        public ShareRecorder(IConnectionFactory cf, IMapper mapper,
                             JsonSerializerSettings jsonSerializerSettings,
                             IShareRepository shareRepo, IBlockRepository blockRepo,
                             IMasterClock clock,
                             IMessageBus messageBus)
        {
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(mapper, nameof(mapper));
            Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
            Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
            Contract.RequiresNonNull(jsonSerializerSettings, nameof(jsonSerializerSettings));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.cf     = cf;
            this.mapper = mapper;
            this.jsonSerializerSettings = jsonSerializerSettings;
            this.clock      = clock;
            this.messageBus = messageBus;

            this.shareRepo = shareRepo;
            this.blockRepo = blockRepo;

            BuildFaultHandlingPolicy();
        }
예제 #3
0
        public CryptonoteJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, string jobId,
                             PoolConfig poolConfig, ClusterConfig clusterConfig, string prevHash)
        {
            Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate));
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig));
            Contract.RequiresNonNull(instanceId, nameof(instanceId));
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty");

            coin          = poolConfig.Template.As <CryptonoteCoinTemplate>();
            BlockTemplate = blockTemplate;
            PrepareBlobTemplate(instanceId);
            PrevHash = prevHash;

            switch (coin.Hash)
            {
            case CryptonightHashType.Normal:
                hashFunc = LibCryptonight.Cryptonight;
                break;

            case CryptonightHashType.Lite:
                hashFunc = LibCryptonight.CryptonightLight;
                break;

            case CryptonightHashType.Heavy:
                hashFunc = LibCryptonight.CryptonightHeavy;
                break;
            }
        }
        protected PayoutHandlerBase(
            IConnectionFactory cf,
            IMapper mapper,
            IShareRepository shareRepo,
            IBlockRepository blockRepo,
            IBalanceRepository balanceRepo,
            IPaymentRepository paymentRepo,
            IMasterClock clock,
            IMessageBus messageBus)
        {
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(mapper, nameof(mapper));
            Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
            Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.cf          = cf;
            this.mapper      = mapper;
            this.clock       = clock;
            this.shareRepo   = shareRepo;
            this.blockRepo   = blockRepo;
            this.balanceRepo = balanceRepo;
            this.paymentRepo = paymentRepo;
            this.messageBus  = messageBus;

            BuildFaultHandlingPolicy();
        }
        protected StratumServer(IComponentContext ctx, IMasterClock clock)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(clock, nameof(clock));

            this.ctx   = ctx;
            this.clock = clock;
        }
예제 #6
0
        protected JobManagerBase(IComponentContext ctx, IMessageBus messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.ctx        = ctx;
            this.messageBus = messageBus;
        }
        public virtual void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig));

            logger             = LogUtil.GetPoolScopedLogger(typeof(PoolBase), poolConfig);
            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;
        }
예제 #8
0
        public async Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));

            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;
            extraConfig        = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs <CryptonotePoolPaymentProcessingConfigExtra>();

            logger = LogUtil.GetPoolScopedLogger(typeof(CryptonotePayoutHandler), poolConfig);

            // configure standard daemon
            var jsonSerializerSettings = ctx.Resolve <JsonSerializerSettings>();

            var daemonEndpoints = poolConfig.Daemons
                                  .Where(x => string.IsNullOrEmpty(x.Category))
                                  .Select(x =>
            {
                if (string.IsNullOrEmpty(x.HttpPath))
                {
                    x.HttpPath = CryptonoteConstants.DaemonRpcLocation;
                }

                return(x);
            })
                                  .ToArray();

            daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id);
            daemon.Configure(daemonEndpoints);

            // configure wallet daemon
            var walletDaemonEndpoints = poolConfig.Daemons
                                        .Where(x => x.Category?.ToLower() == CryptonoteConstants.WalletDaemonCategory)
                                        .Select(x =>
            {
                if (string.IsNullOrEmpty(x.HttpPath))
                {
                    x.HttpPath = CryptonoteConstants.DaemonRpcLocation;
                }

                return(x);
            })
                                        .ToArray();

            walletDaemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id);
            walletDaemon.Configure(walletDaemonEndpoints);

            // detect network
            await GetNetworkTypeAsync();

            // detect transfer_split support
            var response = await walletDaemon.ExecuteCmdSingleAsync <TransferResponse>(logger, CryptonoteWalletCommands.TransferSplit);

            walletSupportsTransferSplit = response.Error.Code != CryptonoteConstants.MoneroRpcMethodNotFound;
        }
예제 #9
0
        public async ValueTask <Share> SubmitShareAsync(StratumClient worker,
                                                        CryptonoteSubmitShareRequest request, CryptonoteWorkerJob workerJob, double stratumDifficultyBase, CancellationToken ct)
        {
            Contract.RequiresNonNull(worker, nameof(worker));
            Contract.RequiresNonNull(request, nameof(request));

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

            var job = currentJob;

            if (workerJob.Height != job?.BlockTemplate.Height)
            {
                throw new StratumException(StratumError.MinusOne, "block expired");
            }

            // validate & process
            var(share, blobHex) = job.ProcessShare(request.Nonce, workerJob.ExtraNonce, request.Hash, worker);

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

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

                share.IsBlockCandidate = await SubmitBlockAsync(share, blobHex, share.BlockHash);

                if (share.IsBlockCandidate)
                {
                    logger.Info(() => $"Daemon accepted block {share.BlockHeight} [{share.BlockHash.Substring(0, 6)}] submitted by {context.Miner}");

                    OnBlockFound();

                    share.TransactionConfirmationData = share.BlockHash;
                }

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

            return(share);
        }
예제 #10
0
        public byte[] WithFirst(byte[] first)
        {
            Contract.RequiresNonNull(first, nameof(first));

            foreach (var step in Steps)
            {
                first = DoubleDigest(first.Concat(step)).ToArray();
            }

            return(first);
        }
예제 #11
0
        public async Task StartAsync(CancellationToken ct)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));

            logger.Info(() => $"Starting Job Manager ...");

            await StartDaemonAsync(ct);
            await EnsureDaemonsSynchedAsync(ct);
            await PostStartInitAsync(ct);

            logger.Info(() => $"Job Manager Online");
        }
        public virtual (Share Share, string BlockHex) ProcessShare(StratumClient worker,
                                                                   string extraNonce2, string nTime, string nonce, string versionBits = null)
        {
            Contract.RequiresNonNull(worker, nameof(worker));
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty");
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty");
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty");

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

            // validate nTime
            if (nTime.Length != 8)
            {
                throw new StratumException(StratumError.Other, "incorrect size of ntime");
            }

            var nTimeInt = uint.Parse(nTime, NumberStyles.HexNumber);

            if (nTimeInt < BlockTemplate.CurTime || nTimeInt > ((DateTimeOffset)clock.Now).ToUnixTimeSeconds() + 7200)
            {
                throw new StratumException(StratumError.Other, "ntime out of range");
            }

            // validate nonce
            if (nonce.Length != 8)
            {
                throw new StratumException(StratumError.Other, "incorrect size of nonce");
            }

            var nonceInt = uint.Parse(nonce, NumberStyles.HexNumber);

            // validate version-bits (overt ASIC boost)
            uint versionBitsInt = 0;

            if (context.VersionRollingMask.HasValue && versionBits != null)
            {
                versionBitsInt = uint.Parse(versionBits, NumberStyles.HexNumber);

                // enforce that only bits covered by current mask are changed by miner
                if ((versionBitsInt & ~context.VersionRollingMask.Value) != 0)
                {
                    throw new StratumException(StratumError.Other, "rolling-version mask violation");
                }
            }

            // dupe check
            if (!RegisterSubmit(context.ExtraNonce1, extraNonce2, nTime, nonce))
            {
                throw new StratumException(StratumError.DuplicateShare, "duplicate share");
            }

            return(ProcessShareInternal(worker, extraNonce2, nTimeInt, nonceInt, versionBitsInt));
        }
        public void Ban(IPAddress address, TimeSpan duration)
        {
            Contract.RequiresNonNull(address, nameof(address));
            Contract.Requires <ArgumentException>(duration.TotalMilliseconds > 0, $"{nameof(duration)} must not be empty");

            // don't ban 127.0.0.1
            if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback))
            {
                return;
            }

            cache.Set(address.ToString(), string.Empty, duration);
        }
예제 #14
0
        public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig));

            logger             = LogUtil.GetPoolScopedLogger(typeof(JobManagerBase <CryptonoteJob>), poolConfig);
            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;
            extraPoolConfig    = poolConfig.Extra.SafeExtensionDataAs <CryptonotePoolConfigExtra>();

            // extract standard daemon endpoints
            daemonEndpoints = poolConfig.Daemons
                              .Where(x => string.IsNullOrEmpty(x.Category))
                              .Select(x =>
            {
                if (string.IsNullOrEmpty(x.HttpPath))
                {
                    x.HttpPath = CryptonoteConstants.DaemonRpcLocation;
                }

                return(x);
            })
                              .ToArray();

            if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true)
            {
                // extract wallet daemon endpoints
                walletDaemonEndpoints = poolConfig.Daemons
                                        .Where(x => x.Category?.ToLower() == CryptonoteConstants.WalletDaemonCategory)
                                        .Select(x =>
                {
                    if (string.IsNullOrEmpty(x.HttpPath))
                    {
                        x.HttpPath = CryptonoteConstants.DaemonRpcLocation;
                    }

                    // cryptonote daemons only support digest auth
                    x.DigestAuth = true;

                    return(x);
                })
                                        .ToArray();

                if (walletDaemonEndpoints.Length == 0)
                {
                    logger.ThrowLogPoolStartupException("Wallet-RPC daemon is not configured (Daemon configuration for monero-pools require an additional entry of category \'wallet' pointing to the wallet daemon)");
                }
            }

            ConfigureDaemons();
        }
예제 #15
0
        /// <summary>
        /// Executes the requests against all configured demons and returns the first successful response array
        /// </summary>
        /// <returns></returns>
        public async Task <DaemonResponse <JToken>[]> ExecuteBatchAnyAsync(ILogger logger, params DaemonCmd[] batch)
        {
            Contract.RequiresNonNull(batch, nameof(batch));

            logger.LogInvoke(batch.Select(x => "\"" + x.Method + "\"").ToArray());

            var tasks = endPoints.Select(endPoint => BuildBatchRequestTask(logger, endPoint, batch)).ToArray();

            var taskFirstCompleted = await Task.WhenAny(tasks);

            var result = MapDaemonBatchResponse(0, taskFirstCompleted);

            return(result);
        }
예제 #16
0
        /// <summary>
        /// </summary>
        /// <example>
        /// python: http://runnable.com/U3jqtyYUmAUxtsSS/bitcoin-block-merkle-root-python
        /// nodejs: https://github.com/zone117x/node-stratum-pool/blob/master/lib/merkleTree.js#L9
        /// </example>
        /// <param name="hashList"></param>
        /// <returns></returns>
        private IList <byte[]> CalculateSteps(IEnumerable <byte[]> hashList)
        {
            Contract.RequiresNonNull(hashList, nameof(hashList));

            var steps = new List <byte[]>();

            var L = new List <byte[]> {
                null
            };

            L.AddRange(hashList);

            var startL = 2;
            var Ll     = L.Count;

            if (Ll > 1)
            {
                while (true)
                {
                    if (Ll == 1)
                    {
                        break;
                    }

                    steps.Add(L[1]);

                    if (Ll % 2 == 1)
                    {
                        L.Add(L[L.Count - 1]);
                    }

                    var Ld = new List <byte[]>();

                    //foreach (int i in Range.From(startL).To(Ll).WithStepSize(2))
                    for (var i = startL; i < Ll; i += 2)
                    {
                        Ld.Add(MerkleJoin(L[i], L[i + 1]));
                    }

                    L = new List <byte[]> {
                        null
                    };
                    L.AddRange(Ld);
                    Ll = L.Count;
                }
            }

            return(steps);
        }
예제 #17
0
        public SoloPaymentScheme(IConnectionFactory cf,
                                 IShareRepository shareRepo,
                                 IBlockRepository blockRepo,
                                 IBalanceRepository balanceRepo)
        {
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
            Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));

            this.cf          = cf;
            this.shareRepo   = shareRepo;
            this.blockRepo   = blockRepo;
            this.balanceRepo = balanceRepo;
        }
예제 #18
0
        public DaemonClient(JsonSerializerSettings serializerSettings, IMessageBus messageBus, string server, string poolId)
        {
            Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(poolId), $"{nameof(poolId)} must not be empty");

            this.serializerSettings = serializerSettings;
            this.messageBus         = messageBus;
            this.server             = server;
            this.poolId             = poolId;

            serializer = new JsonSerializer
            {
                ContractResolver = serializerSettings.ContractResolver
            };
        }
예제 #19
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;

            // stale?
            lock (jobLock)
            {
                if (!validJobs.TryGetValue(jobId, out job))
                {
                    throw new StratumException(StratumError.MinusOne, "stale share");
                }
            }

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

            // 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);
        }
        public PPLNSPaymentScheme(IConnectionFactory cf,
                                  IShareRepository shareRepo,
                                  IBlockRepository blockRepo,
                                  IBalanceRepository balanceRepo)
        {
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
            Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));

            this.cf          = cf;
            this.shareRepo   = shareRepo;
            this.blockRepo   = blockRepo;
            this.balanceRepo = balanceRepo;

            BuildFaultHandlingPolicy();
        }
예제 #21
0
        public CryptonoteJobManager(
            IComponentContext ctx,
            IMasterClock clock,
            IMessageBus messageBus) :
            base(ctx, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.clock = clock;

            using (var rng = RandomNumberGenerator.Create())
            {
                instanceId = new byte[CryptonoteConstants.InstanceIdSize];
                rng.GetNonZeroBytes(instanceId);
            }
        }
예제 #22
0
        public EthereumJobManager(
            IComponentContext ctx,
            IMasterClock clock,
            IMessageBus messageBus,
            JsonSerializerSettings serializerSettings) :
            base(ctx, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.clock = clock;

            serializer = new JsonSerializer
            {
                ContractResolver = serializerSettings.ContractResolver
            };
        }
예제 #23
0
        public EthereumPayoutHandler(
            IComponentContext ctx,
            IConnectionFactory cf,
            IMapper mapper,
            IShareRepository shareRepo,
            IBlockRepository blockRepo,
            IBalanceRepository balanceRepo,
            IPaymentRepository paymentRepo,
            IMasterClock clock,
            IMessageBus messageBus) :
            base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo));

            this.ctx = ctx;
        }
        public virtual async Task StartAsync(CancellationToken ct)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));

            logger.Info(() => $"Starting Pool ...");

            try
            {
                SetupBanning(clusterConfig);
                await SetupJobManager(ct);
                await InitStatsAsync();

                if (poolConfig.EnableInternalStratum == true)
                {
                    var ipEndpoints = poolConfig.Ports.Keys
                                      .Select(port => PoolEndpoint2IPEndpoint(port, poolConfig.Ports[port]))
                                      .ToArray();

                    StartListeners(ipEndpoints);
                }

                logger.Info(() => $"Pool Online");
                OutputPoolInfo();
            }

            catch (PoolStartupAbortException)
            {
                // just forward these
                throw;
            }

            catch (TaskCanceledException)
            {
                // just forward these
                throw;
            }

            catch (Exception ex)
            {
                logger.Error(ex);
                throw;
            }
        }
        public virtual Task ConfigureAsync(ClusterConfig clusterConfig, PoolConfig poolConfig)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));

            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;

            extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs <BitcoinDaemonEndpointConfigExtra>();
            extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs <BitcoinPoolPaymentProcessingConfigExtra>();

            logger = LogUtil.GetPoolScopedLogger(typeof(BitcoinPayoutHandler), poolConfig);

            var jsonSerializerSettings = ctx.Resolve <JsonSerializerSettings>();

            daemon = new DaemonClient(jsonSerializerSettings, messageBus, clusterConfig.ClusterName ?? poolConfig.PoolName, poolConfig.Id);
            daemon.Configure(poolConfig.Daemons);

            return(Task.FromResult(true));
        }
        public PayoutManager(IComponentContext ctx,
                             IConnectionFactory cf,
                             IBlockRepository blockRepo,
                             IShareRepository shareRepo,
                             IBalanceRepository balanceRepo,
                             IMessageBus messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(blockRepo, nameof(blockRepo));
            Contract.RequiresNonNull(shareRepo, nameof(shareRepo));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.ctx         = ctx;
            this.cf          = cf;
            this.blockRepo   = blockRepo;
            this.shareRepo   = shareRepo;
            this.balanceRepo = balanceRepo;
            this.messageBus  = messageBus;
        }
        protected PoolBase(IComponentContext ctx,
                           JsonSerializerSettings serializerSettings,
                           IConnectionFactory cf,
                           IStatsRepository statsRepo,
                           IMapper mapper,
                           IMasterClock clock,
                           IMessageBus messageBus) : base(ctx, clock)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(serializerSettings, nameof(serializerSettings));
            Contract.RequiresNonNull(cf, nameof(cf));
            Contract.RequiresNonNull(statsRepo, nameof(statsRepo));
            Contract.RequiresNonNull(mapper, nameof(mapper));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.serializerSettings = serializerSettings;
            this.cf         = cf;
            this.statsRepo  = statsRepo;
            this.mapper     = mapper;
            this.messageBus = messageBus;
        }
예제 #28
0
        public async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            var coin = poolConfig.Template.As <CryptonoteCoinTemplate>();

#if !DEBUG // ensure we have peers
            var infoResponse = await daemon.ExecuteCmdAnyAsync <GetInfoResponse>(logger, CNC.GetInfo);

            if (infoResponse.Error != null || infoResponse.Response == null ||
                infoResponse.Response.IncomingConnectionsCount + infoResponse.Response.OutgoingConnectionsCount < 3)
            {
                logger.Warn(() => $"[{LogCategory}] Payout aborted. Not enough peers (4 required)");
                return;
            }
#endif
            // validate addresses
            balances = balances
                       .Where(x =>
            {
                ExtractAddressAndPaymentId(x.Address, out var address, out var paymentId);

                var addressPrefix           = LibCryptonote.DecodeAddress(address);
                var addressIntegratedPrefix = LibCryptonote.DecodeIntegratedAddress(address);

                switch (networkType)
                {
                case CryptonoteNetworkType.Main:
                    if (addressPrefix != coin.AddressPrefix &&
                        addressIntegratedPrefix != coin.AddressPrefixIntegrated)
                    {
                        logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}");
                        return(false);
                    }

                    break;

                case CryptonoteNetworkType.Test:
                    if (addressPrefix != coin.AddressPrefixTestnet &&
                        addressIntegratedPrefix != coin.AddressPrefixIntegratedTestnet)
                    {
                        logger.Warn(() => $"[{LogCategory}] Excluding payment to invalid address {x.Address}");
                        return(false);
                    }

                    break;
                }

                return(true);
            })
                       .ToArray();

            // simple balances first
            var simpleBalances = balances
                                 .Where(x =>
            {
                ExtractAddressAndPaymentId(x.Address, out var address, out var paymentId);

                var hasPaymentId            = paymentId != null;
                var isIntegratedAddress     = false;
                var addressIntegratedPrefix = LibCryptonote.DecodeIntegratedAddress(address);

                switch (networkType)
                {
                case CryptonoteNetworkType.Main:
                    if (addressIntegratedPrefix == coin.AddressPrefixIntegrated)
                    {
                        isIntegratedAddress = true;
                    }
                    break;

                case CryptonoteNetworkType.Test:
                    if (addressIntegratedPrefix == coin.AddressPrefixIntegratedTestnet)
                    {
                        isIntegratedAddress = true;
                    }
                    break;
                }

                return(!hasPaymentId && !isIntegratedAddress);
            })
                                 .OrderByDescending(x => x.Amount)
                                 .ToArray();

            if (simpleBalances.Length > 0)
#if false
            { await PayoutBatch(simpleBalances); }
#else
            {
                var maxBatchSize = 15;  // going over 15 yields "sv/gamma are too large"
                var pageSize     = maxBatchSize;
                var pageCount    = (int)Math.Ceiling((double)simpleBalances.Length / pageSize);

                for (var i = 0; i < pageCount; i++)
                {
                    var page = simpleBalances
                               .Skip(i * pageSize)
                               .Take(pageSize)
                               .ToArray();

                    if (!await PayoutBatch(page))
                    {
                        break;
                    }
                }
            }
#endif
            // balances with paymentIds
            var minimumPaymentToPaymentId = extraConfig?.MinimumPaymentToPaymentId ?? poolConfig.PaymentProcessing.MinimumPayment;

            var paymentIdBalances = balances.Except(simpleBalances)
                                    .Where(x => x.Amount >= minimumPaymentToPaymentId)
                                    .ToArray();

            foreach (var balance in paymentIdBalances)
            {
                if (!await PayoutToPaymentId(balance))
                {
                    break;
                }
            }

            // save wallet
            await walletDaemon.ExecuteCmdSingleAsync <JToken>(logger, CryptonoteWalletCommands.Store);
        }
예제 #29
0
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

            var coin       = poolConfig.Template.As <EthereumCoinTemplate>();
            var pageSize   = 100;
            var pageCount  = (int)Math.Ceiling(blocks.Length / (double)pageSize);
            var blockCache = new Dictionary <long, DaemonResponses.Block>();
            var result     = new List <Block>();

            for (var i = 0; i < pageCount; i++)
            {
                // get a page full of blocks
                var page = blocks
                           .Skip(i * pageSize)
                           .Take(pageSize)
                           .ToArray();

                // get latest block
                var latestBlockResponses = await daemon.ExecuteCmdAllAsync <DaemonResponses.Block>(logger, EC.GetBlockByNumber, new[] { (object)"latest", true });

                var latestBlockHeight = latestBlockResponses.First(x => x.Error == null && x.Response?.Height != null).Response.Height.Value;

                // execute batch
                var blockInfos = await FetchBlocks(blockCache, page.Select(block => (long)block.BlockHeight).ToArray());

                for (var j = 0; j < blockInfos.Length; j++)
                {
                    var blockInfo = blockInfos[j];
                    var block     = page[j];

                    // extract confirmation data from stored block
                    var mixHash = block.TransactionConfirmationData.Split(":").First();
                    var nonce   = block.TransactionConfirmationData.Split(":").Last();

                    // update progress
                    block.ConfirmationProgress = Math.Min(1.0d, (double)(latestBlockHeight - block.BlockHeight) / EthereumConstants.MinConfimations);
                    result.Add(block);

                    messageBus.NotifyBlockConfirmationProgress(poolConfig.Id, block, coin);

                    // is it block mined by us?
                    if (string.Equals(blockInfo.Miner, poolConfig.Address, StringComparison.OrdinalIgnoreCase))
                    {
                        // additional check
                        // NOTE: removal of first character of both sealfields caused by
                        // https://github.com/paritytech/parity/issues/1090
                        var match = isParity ?
                                    true :
                                    blockInfo.SealFields[0].Substring(2) == mixHash &&
                                    blockInfo.SealFields[1].Substring(2) == nonce;

                        // mature?
                        if (match && (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations))
                        {
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.BlockHeight          = (ulong)blockInfo.Height;
                            block.Reward = GetBaseBlockReward(chainType, block.BlockHeight); // base reward
                            block.Type   = "block";

                            if (extraConfig?.KeepUncles == false)
                            {
                                block.Reward += blockInfo.Uncles.Length * (block.Reward / 32); // uncle rewards
                            }
                            if (extraConfig?.KeepTransactionFees == false && blockInfo.Transactions?.Length > 0)
                            {
                                block.Reward += await GetTxRewardAsync(blockInfo); // tx fees
                            }
                            logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}");

                            messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
                        }

                        continue;
                    }

                    // search for a block containing our block as an uncle by checking N blocks in either direction
                    var heightMin = block.BlockHeight - BlockSearchOffset;
                    var heightMax = Math.Min(block.BlockHeight + BlockSearchOffset, latestBlockHeight);
                    var range     = new List <long>();

                    for (var k = heightMin; k < heightMax; k++)
                    {
                        range.Add((long)k);
                    }

                    // execute batch
                    var blockInfo2s = await FetchBlocks(blockCache, range.ToArray());

                    foreach (var blockInfo2 in blockInfo2s)
                    {
                        // don't give up yet, there might be an uncle
                        if (blockInfo2.Uncles.Length > 0)
                        {
                            // fetch all uncles in a single RPC batch request
                            var uncleBatch = blockInfo2.Uncles.Select((x, index) => new DaemonCmd(EC.GetUncleByBlockNumberAndIndex,
                                                                                                  new[] { blockInfo2.Height.Value.ToStringHexWithPrefix(), index.ToStringHexWithPrefix() }))
                                             .ToArray();

                            logger.Info(() => $"[{LogCategory}] Fetching {blockInfo2.Uncles.Length} uncles for block {blockInfo2.Height}");

                            var uncleResponses = await daemon.ExecuteBatchAnyAsync(logger, uncleBatch);

                            logger.Info(() => $"[{LogCategory}] Fetched {uncleResponses.Count(x => x.Error == null && x.Response != null)} uncles for block {blockInfo2.Height}");

                            var uncle = uncleResponses.Where(x => x.Error == null && x.Response != null)
                                        .Select(x => x.Response.ToObject <DaemonResponses.Block>())
                                        .FirstOrDefault(x => string.Equals(x.Miner, poolConfig.Address, StringComparison.OrdinalIgnoreCase));

                            if (uncle != null)
                            {
                                // mature?
                                if (latestBlockHeight - uncle.Height.Value >= EthereumConstants.MinConfimations)
                                {
                                    block.Status = BlockStatus.Confirmed;
                                    block.ConfirmationProgress = 1;
                                    block.Reward      = GetUncleReward(chainType, uncle.Height.Value, blockInfo2.Height.Value);
                                    block.BlockHeight = uncle.Height.Value;
                                    block.Type        = EthereumConstants.BlockTypeUncle;

                                    logger.Info(() => $"[{LogCategory}] Unlocked uncle for block {blockInfo2.Height.Value} at height {uncle.Height.Value} worth {FormatAmount(block.Reward)}");

                                    messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
                                }

                                else
                                {
                                    logger.Info(() => $"[{LogCategory}] Got immature matching uncle for block {blockInfo2.Height.Value}. Will try again.");
                                }

                                break;
                            }
                        }
                    }

                    if (block.Status == BlockStatus.Pending && block.ConfirmationProgress > 1 * *)
                    {
                        // we've lost this one
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;

                        messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
                    }
                }
            }

            return(result.ToArray());
        }
        public double?Update(VarDiffContext ctx, double difficulty, bool isIdleUpdate)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));

            lock (ctx)
            {
                // Get Current Time
                var ts = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;

                // For the first time, won't change diff.
                if (!ctx.LastTs.HasValue)
                {
                    ctx.LastRtc    = ts;
                    ctx.LastTs     = ts;
                    ctx.TimeBuffer = new CircularDoubleBuffer(bufferSize);
                    return(null);
                }

                var minDiff   = options.MinDiff;
                var maxDiff   = options.MaxDiff ?? Math.Max(minDiff, double.MaxValue); // for regtest
                var sinceLast = ts - ctx.LastTs.Value;

                // Always calculate the time until now even there is no share submitted.
                var timeTotal = ctx.TimeBuffer.Sum();
                var timeCount = ctx.TimeBuffer.Size;
                var avg       = (timeTotal + sinceLast) / (timeCount + 1);

                // Once there is a share submitted, store the time into the buffer and update the last time.
                if (!isIdleUpdate)
                {
                    ctx.TimeBuffer.PushBack(sinceLast);
                    ctx.LastTs = ts;
                }

                // Check if we need to change the difficulty
                if (ts - ctx.LastRtc < options.RetargetTime || avg >= tMin && avg <= tMax)
                {
                    return(null);
                }

                // Possible New Diff
                var newDiff = difficulty * options.TargetTime / avg;

                // Max delta
                if (options.MaxDelta.HasValue && options.MaxDelta > 0)
                {
                    var delta = Math.Abs(newDiff - difficulty);

                    if (delta > options.MaxDelta)
                    {
                        if (newDiff > difficulty)
                        {
                            newDiff -= delta - options.MaxDelta.Value;
                        }
                        else if (newDiff < difficulty)
                        {
                            newDiff += delta - options.MaxDelta.Value;
                        }
                    }
                }

                // Clamp to valid range
                if (newDiff < minDiff)
                {
                    newDiff = minDiff;
                }
                if (newDiff > maxDiff)
                {
                    newDiff = maxDiff;
                }

                // RTC if the Diff is changed
                if (newDiff < difficulty || newDiff > difficulty)
                {
                    ctx.LastRtc    = ts;
                    ctx.LastUpdate = clock.Now;

                    // Due to change of diff, Buffer needs to be cleared
                    ctx.TimeBuffer = new CircularDoubleBuffer(bufferSize);

                    return(newDiff);
                }
            }

            return(null);
        }