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)); }); }
protected StratumServer(IComponentContext ctx, IMasterClock clock) { Assertion.RequiresNonNull(ctx, nameof(ctx)); Assertion.RequiresNonNull(clock, nameof(clock)); this.ctx = ctx; this.clock = clock; }
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; }
public void Start(XPoolConfig clusterConfig) { Assertion.RequiresNonNull(clusterConfig, nameof(clusterConfig)); this.clusterConfig = clusterConfig; logger.Info(() => $"Launching ..."); StartApi(clusterConfig); StartAdminApi(clusterConfig); }
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 }; }
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); }
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 }; }
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)); }
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(); }
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); }
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()); }
protected JobManagerBase(IComponentContext ctx) { Assertion.RequiresNonNull(ctx, nameof(ctx)); this.ctx = ctx; }
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); } } }