Exemple #1
0
        protected PoolBase(IComponentContext ctx,
                           JsonSerializerSettings serializerSettings,
                           IConnectionFactory cf,
                           IStatsRepository statsRepo,
                           IMapper mapper,
                           IMasterClock clock,
                           IMessageBus messageBus,
                           WebhookNotificationService notificationService) : base(ctx, clock)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));
            Assertion.RequiresNonNull(serializerSettings, nameof(serializerSettings));
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(statsRepo, nameof(statsRepo));
            Assertion.RequiresNonNull(mapper, nameof(mapper));
            Assertion.RequiresNonNull(clock, nameof(clock));
            Assertion.RequiresNonNull(messageBus, nameof(messageBus));
            Assertion.RequiresNonNull(notificationService, nameof(notificationService));

            this.serializerSettings = serializerSettings;
            this.cf                  = cf;
            this.statsRepo           = statsRepo;
            this.mapper              = mapper;
            this.messageBus          = messageBus;
            this.notificationService = notificationService;
        }
        public RewardRecorder(IConnectionFactory cf, IMapper mapper,
                              JsonSerializerSettings jsonSerializerSettings,
                              IShareRepository shareRepo, IBlockRepository blockRepo,
                              IMasterClock clock,
                              IMessageBus messageBus,
                              WebhookNotificationService notificationService)
        {
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(mapper, nameof(mapper));
            Assertion.RequiresNonNull(shareRepo, nameof(shareRepo));
            Assertion.RequiresNonNull(blockRepo, nameof(blockRepo));
            Assertion.RequiresNonNull(jsonSerializerSettings, nameof(jsonSerializerSettings));
            Assertion.RequiresNonNull(clock, nameof(clock));
            Assertion.RequiresNonNull(messageBus, nameof(messageBus));
            Assertion.RequiresNonNull(notificationService, nameof(notificationService));

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

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

            BuildFaultHandlingPolicy();
        }
        protected PayoutHandlerBase(IConnectionFactory cf, IMapper mapper,
                                    IShareRepository shareRepo,
                                    IBlockRepository blockRepo,
                                    IBalanceRepository balanceRepo,
                                    IPaymentRepository paymentRepo,
                                    IMasterClock clock,
                                    WebhookNotificationService notificationService)
        {
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(mapper, nameof(mapper));
            Assertion.RequiresNonNull(shareRepo, nameof(shareRepo));
            Assertion.RequiresNonNull(blockRepo, nameof(blockRepo));
            Assertion.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Assertion.RequiresNonNull(paymentRepo, nameof(paymentRepo));
            Assertion.RequiresNonNull(clock, nameof(clock));
            Assertion.RequiresNonNull(notificationService, nameof(notificationService));

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

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

            this.endPoints = endPoints;


            httpClients = endPoints.ToDictionary(endpoint => endpoint, endpoint =>
            {
                var handler = new HttpClientHandler
                {
                    Credentials            = new NetworkCredential(endpoint.User, endpoint.Password),
                    PreAuthenticate        = true,
                    AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
                };

                if (endpoint.Ssl && !endpoint.ValidateCert)
                {
                    handler.ClientCertificateOptions = ClientCertificateOption.Manual;
                    handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true;
                }

                return(new HttpClient(handler));
            });
        }
Exemple #5
0
        protected StratumServer(IComponentContext ctx, IMasterClock clock)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));
            Assertion.RequiresNonNull(clock, nameof(clock));

            this.ctx   = ctx;
            this.clock = clock;
        }
Exemple #6
0
        public virtual void Configure(PoolConfig poolConfig, XPoolConfig clusterConfig)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));
            Assertion.RequiresNonNull(clusterConfig, nameof(clusterConfig));

            logger             = LogUtil.GetPoolScopedLogger(typeof(PoolBase), poolConfig);
            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;
        }
Exemple #7
0
        public void Start(XPoolConfig clusterConfig)
        {
            Assertion.RequiresNonNull(clusterConfig, nameof(clusterConfig));
            this.clusterConfig = clusterConfig;

            logger.Info(() => $"Launching ...");
            StartApi(clusterConfig);
            StartAdminApi(clusterConfig);
        }
Exemple #8
0
        public virtual void Configure(PoolConfig poolConfig, XPoolConfig clusterConfig)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));
            Assertion.RequiresNonNull(clusterConfig, nameof(clusterConfig));

            logger             = LogUtil.GetPoolScopedLogger(typeof(JobManagerBase <TJob>), poolConfig);
            this.poolConfig    = poolConfig;
            this.clusterConfig = clusterConfig;

            ConfigureDaemons();
        }
        public DaemonClient(JsonSerializerSettings serializerSettings)
        {
            Assertion.RequiresNonNull(serializerSettings, nameof(serializerSettings));

            this.serializerSettings = serializerSettings;

            serializer = new JsonSerializer
            {
                ContractResolver = serializerSettings.ContractResolver
            };
        }
Exemple #10
0
        public async Task StartAsync(CancellationToken ct)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));

            logger.Info(() => $"[{LogCat}] Launching ...");

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

            logger.Info(() => $"[{LogCat}] Online");
        }
        public bool ValidateAddress(string address)
        {
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(address), $"{nameof(address)} must not be empty");

            if (EthereumConstants.ZeroHashPattern.IsMatch(address) ||
                !EthereumConstants.ValidAddressPattern.IsMatch(address))
            {
                return(false);
            }

            return(true);
        }
        public async Task <DaemonResponse <JToken>[]> ExecuteBatchAnyAsync(params DaemonCmd[] batch)
        {
            Assertion.RequiresNonNull(batch, nameof(batch));

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

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

            var taskFirstCompleted = await Task.WhenAny(tasks);

            var result = MapDaemonBatchResponse(0, taskFirstCompleted);

            return(result);
        }
        public async Task <DaemonResponse <TResponse> > ExecuteCmdSingleAsync <TResponse>(string method, object payload = null,
                                                                                          JsonSerializerSettings payloadJsonSerializerSettings = null)
            where TResponse : class
        {
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty");

            logger.LogInvoke(new[] { method });

            var   task = BuildRequestTask(endPoints.First(), method, payload, payloadJsonSerializerSettings);
            await task;

            var result = MapDaemonResponse <TResponse>(0, task);

            return(result);
        }
        public async Task <DaemonResponse <TResponse> > ExecuteCmdAnyAsync <TResponse>(string method, object payload = null,
                                                                                       JsonSerializerSettings payloadJsonSerializerSettings = null, bool throwOnError = false)
            where TResponse : class
        {
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty");

            logger.LogInvoke(new[] { method });

            var tasks = endPoints.Select(endPoint => BuildRequestTask(endPoint, method, payload, payloadJsonSerializerSettings)).ToArray();

            var taskFirstCompleted = await Task.WhenAny(tasks);

            var result = MapDaemonResponse <TResponse>(0, taskFirstCompleted, throwOnError);

            return(result);
        }
        public async Task <Share> SubmitShareAsync(StratumClient worker,
                                                   string[] request, double stratumDifficulty, double stratumDifficultyBase)
        {
            Assertion.RequiresNonNull(worker, nameof(worker));
            Assertion.RequiresNonNull(request, nameof(request));

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


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


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


            var(share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, nonce, ethash);


            share.PoolId            = poolConfig.Id;
            share.NetworkDifficulty = BlockchainStats.NetworkDifficulty;
            share.Source            = clusterConfig.ClusterName;
            share.Created           = clock.Now;


            if (share.IsBlockCandidate)
            {
                logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight}");

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

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

            return(share);
        }
Exemple #16
0
        public RestfulApiServer(
            IMapper mapper,
            IConnectionFactory cf,
            IBlockRepository blocksRepo,
            IPaymentRepository paymentsRepo,
            IStatsRepository statsRepo,
            IMasterClock clock,
            IMessageBus messageBus)
        {
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(statsRepo, nameof(statsRepo));
            Assertion.RequiresNonNull(blocksRepo, nameof(blocksRepo));
            Assertion.RequiresNonNull(paymentsRepo, nameof(paymentsRepo));
            Assertion.RequiresNonNull(mapper, nameof(mapper));
            Assertion.RequiresNonNull(clock, nameof(clock));
            Assertion.RequiresNonNull(messageBus, nameof(messageBus));

            this.cf           = cf;
            this.statsRepo    = statsRepo;
            this.blocksRepo   = blocksRepo;
            this.paymentsRepo = paymentsRepo;
            this.mapper       = mapper;
            this.clock        = clock;

            messageBus.Listen <BlockNotification>().Subscribe(OnBlockNotification);

            requestMap = new Dictionary <Regex, Func <HttpContext, Match, Task> >
            {
                { new Regex("^/api/pools$", RegexOptions.Compiled), GetPoolInfosAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/performance$", RegexOptions.Compiled), GetPoolPerformanceAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/miners$", RegexOptions.Compiled), PagePoolMinersAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/blocks$", RegexOptions.Compiled), PagePoolBlocksPagedAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/payments$", RegexOptions.Compiled), PagePoolPaymentsAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)$", RegexOptions.Compiled), GetPoolInfoAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/miners/(?<address>[^/]+)/payments$", RegexOptions.Compiled), PageMinerPaymentsAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/miners/(?<address>[^/]+)/balancechanges$", RegexOptions.Compiled), PageMinerBalanceChangesAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/miners/(?<address>[^/]+)/performance$", RegexOptions.Compiled), GetMinerPerformanceAsync },
                { new Regex("^/api/pools/(?<poolId>[^/]+)/miners/(?<address>[^/]+)$", RegexOptions.Compiled), GetMinerInfoAsync },
            };

            requestMapAdmin = new Dictionary <Regex, Func <HttpContext, Match, Task> >
            {
                { new Regex("^/api/admin/forcegc$", RegexOptions.Compiled), HandleForceGcAsync },
                { new Regex("^/api/admin/stats/gc$", RegexOptions.Compiled), HandleGcStatsAsync },
            };
        }
        public PPLNSPaymentScheme(IConnectionFactory cf,
                                  IShareRepository shareRepo,
                                  IBlockRepository blockRepo,
                                  IBalanceRepository balanceRepo)
        {
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(shareRepo, nameof(shareRepo));
            Assertion.RequiresNonNull(blockRepo, nameof(blockRepo));
            Assertion.RequiresNonNull(balanceRepo, nameof(balanceRepo));

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

            BuildFaultHandlingPolicy();
        }
        public BitcoinPayoutHandler(
            IComponentContext ctx,
            IConnectionFactory cf,
            IMapper mapper,
            IShareRepository shareRepo,
            IBlockRepository blockRepo,
            IBalanceRepository balanceRepo,
            IPaymentRepository paymentRepo,
            IMasterClock clock,
            WebhookNotificationService notificationService) :
            base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, notificationService)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));
            Assertion.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Assertion.RequiresNonNull(paymentRepo, nameof(paymentRepo));

            this.ctx = ctx;
        }
        public EthereumJobManager(
            IComponentContext ctx,
            WebhookNotificationService notificationService,
            IMasterClock clock,
            JsonSerializerSettings serializerSettings) :
            base(ctx)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));
            Assertion.RequiresNonNull(notificationService, nameof(notificationService));
            Assertion.RequiresNonNull(clock, nameof(clock));

            this.clock = clock;
            this.notificationService = notificationService;

            serializer = new JsonSerializer
            {
                ContractResolver = serializerSettings.ContractResolver
            };
        }
Exemple #20
0
        public virtual async Task StartAsync(CancellationToken ct)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));

            logger.Info(() => $"[{LogCat}] Launching ...");

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

                InitStats();

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

                    StartListeners(poolConfig.Id, ipEndpoints);
                }

                logger.Info(() => $"[{LogCat}] Online");
                OutputPoolInfo();
            }

            catch (PoolStartupAbortException)
            {
                throw;
            }

            catch (TaskCanceledException)
            {
                throw;
            }

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

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

            extraPoolConfig = poolConfig.Extra.SafeExtensionDataAs <BitcoinDaemonEndpointConfigExtra>();
            extraPoolPaymentProcessingConfig = poolConfig.PaymentProcessing.Extra.SafeExtensionDataAs <BitcoinPoolPaymentProcessingConfigExtra>();
            coinProperties = BitcoinProperties.GetCoinProperties(poolConfig.Coin.Type, poolConfig.Coin.Algorithm);

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

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

            daemon = new DaemonClient(jsonSerializerSettings);
            daemon.Configure(poolConfig.Daemons);

            return(Task.FromResult(true));
        }
Exemple #22
0
        public StatsRecorder(IComponentContext ctx,
                             IMasterClock clock,
                             IConnectionFactory cf,
                             IMapper mapper,
                             IShareRepository shareRepo,
                             IStatsRepository statsRepo)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));
            Assertion.RequiresNonNull(clock, nameof(clock));
            Assertion.RequiresNonNull(cf, nameof(cf));
            Assertion.RequiresNonNull(mapper, nameof(mapper));
            Assertion.RequiresNonNull(shareRepo, nameof(shareRepo));
            Assertion.RequiresNonNull(statsRepo, nameof(statsRepo));

            this.ctx       = ctx;
            this.clock     = clock;
            this.cf        = cf;
            this.mapper    = mapper;
            this.shareRepo = shareRepo;
            this.statsRepo = statsRepo;

            BuildFaultHandlingPolicy();
        }
Exemple #23
0
        public virtual (Share Share, string BlockHex) ProcessShare(StratumClient worker,
                                                                   string extraNonce2, string nTime, string nonce)
        {
            Assertion.RequiresNonNull(worker, nameof(worker));
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty");
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(nTime), $"{nameof(nTime)} must not be empty");
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty");

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

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

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

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

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

            return(ProcessShareInternal(worker, extraNonce2, nTimeInt, nonceInt));
        }
        public async Task <DaemonResponse <TResponse>[]> ExecuteCmdAllAsync <TResponse>(string method,
                                                                                        object payload = null, JsonSerializerSettings payloadJsonSerializerSettings = null)
            where TResponse : class
        {
            Assertion.Requires <ArgumentException>(!string.IsNullOrEmpty(method), $"{nameof(method)} must not be empty");

            logger.LogInvoke(new[] { method });

            var tasks = endPoints.Select(endPoint => BuildRequestTask(endPoint, method, payload, payloadJsonSerializerSettings)).ToArray();

            try
            {
                await Task.WhenAll(tasks);
            }

            catch (Exception)
            {
            }

            var results = tasks.Select((x, i) => MapDaemonResponse <TResponse>(i, x))
                          .ToArray();

            return(results);
        }
        public double?Update(VarDiffContext ctx, double difficulty, bool isIdleUpdate)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));

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

                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);                  var sinceLast = ts - ctx.LastTs.Value;

                var timeTotal = ctx.TimeBuffer.Sum();
                var timeCount = ctx.TimeBuffer.Size;
                var avg       = (timeTotal + sinceLast) / (timeCount + 1);

                if (!isIdleUpdate)
                {
                    ctx.TimeBuffer.PushBack(sinceLast);
                    ctx.LastTs = ts;
                }

                if (ts - ctx.LastRtc < options.RetargetTime || avg >= tMin && avg <= tMax)
                {
                    return(null);
                }

                var newDiff = difficulty * options.TargetTime / avg;

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

                if (newDiff < minDiff)
                {
                    newDiff = minDiff;
                }
                if (newDiff > maxDiff)
                {
                    newDiff = maxDiff;
                }

                if (newDiff < difficulty || newDiff > difficulty)
                {
                    ctx.LastRtc    = ts;
                    ctx.LastUpdate = clock.Now;

                    ctx.TimeBuffer = new CircularDoubleBuffer(bufferSize);

                    return(newDiff);
                }
            }

            return(null);
        }
Exemple #26
0
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));
            Assertion.RequiresNonNull(blocks, nameof(blocks));

            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++)
            {
                var page = blocks
                           .Skip(i * pageSize)
                           .Take(pageSize)
                           .ToArray();

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

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

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

                    var mixHash = block.TransactionConfirmationData.Split(":").First();
                    var nonce   = block.TransactionConfirmationData.Split(":").Last();

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

                    if (blockInfo.Miner == poolConfig.Address)
                    {
                        var match = blockInfo.SealFields[0].Substring(4) == mixHash.Substring(2) &&
                                    blockInfo.SealFields[1].Substring(4) == nonce.Substring(2);

                        if (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations)
                        {
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.Reward = GetBaseBlockReward(chainType, block.BlockHeight);
                            if (extraConfig?.KeepUncles == false)
                            {
                                block.Reward += blockInfo.Uncles.Length * (block.Reward / 32);
                            }
                            if (extraConfig?.KeepTransactionFees == false && blockInfo.Transactions?.Length > 0)
                            {
                                block.Reward += await GetTxRewardAsync(blockInfo);
                            }
                            logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}");
                        }

                        continue;
                    }

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

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

                    foreach (var blockInfo2 in blockInfo2s)
                    {
                        if (blockInfo2.Uncles.Length > 0)
                        {
                            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(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 => x.Miner == poolConfig.Address);

                            if (uncle != null)
                            {
                                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)}");
                                }

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

                                break;
                            }
                        }
                    }

                    if (block.Status == BlockStatus.Pending && block.ConfirmationProgress > 0.75)
                    {
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;
                    }
                }
            }

            return(result.ToArray());
        }
Exemple #27
0
        protected JobManagerBase(IComponentContext ctx)
        {
            Assertion.RequiresNonNull(ctx, nameof(ctx));

            this.ctx = ctx;
        }
Exemple #28
0
        public override async Task PayoutAsync(Balance[] balances)
        {
            Assertion.RequiresNonNull(balances, nameof(balances));

            if (supportsNativeShielding)
            {
                await ShieldCoinbaseAsync();
            }
            else
            {
                await ShieldCoinbaseEmulatedAsync();
            }

            var didUnlockWallet = false;

            var pageSize  = 50;
            var pageCount = (int)Math.Ceiling(balances.Length / (double)pageSize);

            for (var i = 0; i < pageCount; i++)
            {
                didUnlockWallet = false;

                var page = balances
                           .Skip(i * pageSize)
                           .Take(pageSize)
                           .ToArray();

                var amounts = page
                              .Where(x => x.Amount > 0)
                              .Select(x => new ZSendManyRecipient {
                    Address = x.Address, Amount = Math.Round(x.Amount, 8)
                })
                              .ToList();

                if (amounts.Count == 0)
                {
                    return;
                }

                var pageAmount = amounts.Sum(x => x.Amount);

                var balanceResult = await daemon.ExecuteCmdSingleAsync <object>(ZCashCommands.ZGetBalance, new object[]
                {
                    poolExtraConfig.ZAddress, ZMinConfirmations,
                });

                if (balanceResult.Error != null || (decimal)(double)balanceResult.Response - TransferFee < pageAmount)
                {
                    logger.Info(() => $"[{LogCategory}] Insufficient shielded balance for payment of {FormatAmount(pageAmount)}");
                    return;
                }

                logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(pageAmount)} to {page.Length} addresses");

                var args = new object[]
                {
                    poolExtraConfig.ZAddress, amounts, ZMinConfirmations, TransferFee
                };

tryTransfer:
                var result = await daemon.ExecuteCmdSingleAsync <string>(ZCashCommands.ZSendMany, args);

                if (result.Error == null)
                {
                    var operationId = result.Response;

                    if (string.IsNullOrEmpty(operationId))
                    {
                        logger.Error(() => $"[{LogCategory}] {ZCashCommands.ZSendMany} did not return a operation id!");
                    }
                    else
                    {
                        logger.Info(() => $"[{LogCategory}] Tracking payout operation id: {operationId}");

                        var continueWaiting = true;

                        while (continueWaiting)
                        {
                            var operationResultResponse = await daemon.ExecuteCmdSingleAsync <ZCashAsyncOperationStatus[]>(
                                ZCashCommands.ZGetOperationResult, new object[] { new object[] { operationId } });

                            if (operationResultResponse.Error == null &&
                                operationResultResponse.Response?.Any(x => x.OperationId == operationId) == true)
                            {
                                var operationResult = operationResultResponse.Response.First(x => x.OperationId == operationId);

                                if (!Enum.TryParse(operationResult.Status, true, out ZOperationStatus status))
                                {
                                    logger.Error(() => $"Unrecognized operation status: {operationResult.Status}");
                                    break;
                                }

                                switch (status)
                                {
                                case ZOperationStatus.Success:
                                    var txId = operationResult.Result?.Value <string>("txid") ?? string.Empty;
                                    logger.Info(() => $"[{LogCategory}] {ZCashCommands.ZSendMany} completed with transaction id: {txId}");

                                    PersistPayments(page, txId);
                                    NotifyPayoutSuccess(poolConfig.Id, page, new[] { txId }, null);

                                    continueWaiting = false;
                                    continue;

                                case ZOperationStatus.Cancelled:
                                case ZOperationStatus.Failed:
                                    logger.Error(() => $"{ZCashCommands.ZSendMany} failed: {operationResult.Error.Message} code {operationResult.Error.Code}");
                                    NotifyPayoutFailure(poolConfig.Id, page, $"{ZCashCommands.ZSendMany} failed: {operationResult.Error.Message} code {operationResult.Error.Code}", null);

                                    continueWaiting = false;
                                    continue;
                                }
                            }

                            logger.Info(() => $"[{LogCategory}] Waiting for completion: {operationId}");
                            await Task.Delay(TimeSpan.FromSeconds(10));
                        }
                    }
                }

                else
                {
                    if (result.Error.Code == (int)BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                    {
                        if (!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword))
                        {
                            logger.Info(() => $"[{LogCategory}] Unlocking wallet");

                            var unlockResult = await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletPassphrase, new[]
                            {
                                (object)extraPoolPaymentProcessingConfig.WalletPassword,
                                (object)5
                            });

                            if (unlockResult.Error == null)
                            {
                                didUnlockWallet = true;
                                goto tryTransfer;
                            }

                            else
                            {
                                logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                                NotifyPayoutFailure(poolConfig.Id, page, $"{BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}", null);
                                break;
                            }
                        }

                        else
                        {
                            logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds.");
                            NotifyPayoutFailure(poolConfig.Id, page, $"Wallet is locked but walletPassword was not configured. Unable to send funds.", null);
                            break;
                        }
                    }

                    else
                    {
                        logger.Error(() => $"[{LogCategory}] {ZCashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                        NotifyPayoutFailure(poolConfig.Id, page, $"{ZCashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
                    }
                }
            }

            logger.Info(() => $"[{LogCategory}] Locking wallet");
            await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletLock);
        }
        public virtual async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));
            Assertion.RequiresNonNull(blocks, nameof(blocks));

            var pageSize  = 100;
            var pageCount = (int)Math.Ceiling(blocks.Length / (double)pageSize);
            var result    = new List <Block>();

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

                var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction,
                                                               new[] { block.TransactionConfirmationData })).ToArray();

                var results = await daemon.ExecuteBatchAnyAsync(batch);

                for (var j = 0; j < results.Length; j++)
                {
                    var cmdResult = results[j];

                    var transactionInfo = cmdResult.Response?.ToObject <Transaction>();
                    var block           = page[j];

                    if (cmdResult.Error != null)
                    {
                        if (cmdResult.Error.Code == -5)
                        {
                            block.Status = BlockStatus.Orphaned;
                            result.Add(block);
                        }

                        else
                        {
                            logger.Warn(() => $"[{LogCategory}] Daemon reports error '{cmdResult.Error.Message}' (Code {cmdResult.Error.Code}) for transaction {page[j].TransactionConfirmationData}");
                        }
                    }

                    else if (transactionInfo?.Details == null || transactionInfo.Details.Length == 0)
                    {
                        block.Status = BlockStatus.Orphaned;
                        result.Add(block);
                    }

                    else
                    {
                        switch (transactionInfo.Details[0].Category)
                        {
                        case "immature":
                            var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations;
                            block.ConfirmationProgress = Math.Min(1.0d, (double)transactionInfo.Confirmations / minConfirmations);
                            result.Add(block);
                            break;

                        case "generate":
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            result.Add(block);

                            logger.Info(() => $"[{LogCategory}] Unlocked block {block.BlockHeight} worth {FormatAmount(block.Reward)}");
                            break;

                        default:
                            logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned. Category: {transactionInfo.Details[0].Category}");

                            block.Status = BlockStatus.Orphaned;
                            block.Reward = 0;
                            result.Add(block);
                            break;
                        }
                    }
                }
            }

            return(result.ToArray());
        }
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Assertion.RequiresNonNull(balances, nameof(balances));

            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 8));

            if (amounts.Count == 0)
            {
                return;
            }

            logger.Info(() => $"[{LogCategory}] Paying out {FormatAmount(balances.Sum(x => x.Amount))} to {balances.Length} addresses");

            object[] args;

            if (extraPoolPaymentProcessingConfig?.MinersPayTxFees == true)
            {
                var comment          = (poolConfig.PoolName ?? clusterConfig.ClusterName ?? "MiningCore").Trim() + " Payment";
                var subtractFeesFrom = amounts.Keys.ToArray();

                args = new object[]
                {
                    string.Empty, amounts, 1, comment, subtractFeesFrom
                };
            }

            else
            {
                args = new object[]
                {
                    string.Empty, amounts,
                };
            }

            var didUnlockWallet = false;

tryTransfer:
            var result = await daemon.ExecuteCmdSingleAsync <string>(BitcoinCommands.SendMany, args, new JsonSerializerSettings());

            if (result.Error == null)
            {
                if (didUnlockWallet)
                {
                    logger.Info(() => $"[{LogCategory}] Locking wallet");
                    await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletLock);
                }

                var txId = result.Response;

                if (string.IsNullOrEmpty(txId))
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} did not return a transaction id!");
                }
                else
                {
                    logger.Info(() => $"[{LogCategory}] Payout transaction id: {txId}");
                }

                PersistPayments(balances, txId);

                NotifyPayoutSuccess(poolConfig.Id, balances, new[] { txId }, null);
            }

            else
            {
                if (result.Error.Code == (int)BitcoinRPCErrorCode.RPC_WALLET_UNLOCK_NEEDED && !didUnlockWallet)
                {
                    if (!string.IsNullOrEmpty(extraPoolPaymentProcessingConfig?.WalletPassword))
                    {
                        logger.Info(() => $"[{LogCategory}] Unlocking wallet");

                        var unlockResult = await daemon.ExecuteCmdSingleAsync <JToken>(BitcoinCommands.WalletPassphrase, new[]
                        {
                            (object)extraPoolPaymentProcessingConfig.WalletPassword,
                            (object)5
                        });

                        if (unlockResult.Error == null)
                        {
                            didUnlockWallet = true;
                            goto tryTransfer;
                        }

                        else
                        {
                            logger.Error(() => $"[{LogCategory}] {BitcoinCommands.WalletPassphrase} returned error: {result.Error.Message} code {result.Error.Code}");
                        }
                    }

                    else
                    {
                        logger.Error(() => $"[{LogCategory}] Wallet is locked but walletPassword was not configured. Unable to send funds.");
                    }
                }

                else
                {
                    logger.Error(() => $"[{LogCategory}] {BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}");

                    NotifyPayoutFailure(poolConfig.Id, balances, $"{BitcoinCommands.SendMany} returned error: {result.Error.Message} code {result.Error.Code}", null);
                }
            }
        }