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), } })); }); }
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(); }
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; }
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; }
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; }
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); }
public byte[] WithFirst(byte[] first) { Contract.RequiresNonNull(first, nameof(first)); foreach (var step in Steps) { first = DoubleDigest(first.Concat(step)).ToArray(); } return(first); }
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); }
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(); }
/// <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); }
/// <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); }
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; }
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 }; }
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(); }
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); } }
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 }; }
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; }
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); }
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); }