コード例 #1
0
        public CryptonoteJobManager(
            IComponentContext ctx,
            IMasterClock clock,
            IMessageBus messageBus) :
            base(ctx, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.clock = clock;

            using (var rng = RandomNumberGenerator.Create())
            {
                instanceId = new byte[CryptonoteConstants.InstanceIdSize];
                rng.GetNonZeroBytes(instanceId);
            }
        }
コード例 #2
0
        public EthereumPayoutHandler(
            IComponentContext ctx,
            IConnectionFactory cf,
            IMapper mapper,
            IShareRepository shareRepo,
            IBlockRepository blockRepo,
            IBalanceRepository balanceRepo,
            IPaymentRepository paymentRepo,
            IMasterClock clock,
            IMessageBus messageBus) :
            base(cf, mapper, shareRepo, blockRepo, balanceRepo, paymentRepo, clock, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(balanceRepo, nameof(balanceRepo));
            Contract.RequiresNonNull(paymentRepo, nameof(paymentRepo));

            this.ctx = ctx;
        }
コード例 #3
0
        public EthereumJobManager(
            IComponentContext ctx,
            IMasterClock clock,
            IMessageBus messageBus,
            JsonSerializerSettings serializerSettings) :
            base(ctx, messageBus)
        {
            Contract.RequiresNonNull(ctx, nameof(ctx));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(messageBus, nameof(messageBus));

            this.clock = clock;

            serializer = new JsonSerializer
            {
                ContractResolver = serializerSettings.ContractResolver
            };
        }
コード例 #4
0
        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;
            }
        }
コード例 #5
0
        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));
        }
コード例 #6
0
        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;
        }
コード例 #7
0
        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;
        }
コード例 #8
0
        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);
        }
コード例 #9
0
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

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

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

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

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

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

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

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

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

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

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

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

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

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

                        continue;
                    }

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

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

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

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

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

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

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

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

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

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

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

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

                                break;
                            }
                        }
                    }

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

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

            return(result.ToArray());
        }
コード例 #10
0
        public async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

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

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

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

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

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

                    break;

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

                    break;
                }

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

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

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

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

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

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

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

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

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

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

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

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

            var coin      = poolConfig.Template.As <CryptonoteCoinTemplate>();
            var pageSize  = 100;
            var pageCount = (int)Math.Ceiling(blocks.Length / (double)pageSize);
            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();

                // NOTE: monerod does not support batch-requests
                for (var j = 0; j < page.Length; j++)
                {
                    var block = page[j];

                    var rpcResult = await daemon.ExecuteCmdAnyAsync <GetBlockHeaderResponse>(logger,
                                                                                             CryptonoteCommands.GetBlockHeaderByHeight,
                                                                                             new GetBlockHeaderByHeightRequest
                    {
                        Height = block.BlockHeight
                    });

                    if (rpcResult.Error != null)
                    {
                        logger.Debug(() => $"[{LogCategory}] Daemon reports error '{rpcResult.Error.Message}' (Code {rpcResult.Error.Code}) for block {block.BlockHeight}");
                        continue;
                    }

                    if (rpcResult.Response?.BlockHeader == null)
                    {
                        logger.Debug(() => $"[{LogCategory}] Daemon returned no header for block {block.BlockHeight}");
                        continue;
                    }

                    var blockHeader = rpcResult.Response.BlockHeader;

                    // update progress
                    block.ConfirmationProgress = Math.Min(1.0d, (double)blockHeader.Depth / CryptonoteConstants.PayoutMinBlockConfirmations);
                    result.Add(block);

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

                    // orphaned?
                    if (blockHeader.IsOrphaned || blockHeader.Hash != block.TransactionConfirmationData)
                    {
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;

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

                    // matured and spendable?
                    if (blockHeader.Depth >= CryptonoteConstants.PayoutMinBlockConfirmations)
                    {
                        block.Status = BlockStatus.Confirmed;
                        block.ConfirmationProgress = 1;
                        block.Reward = ((decimal)blockHeader.Reward / coin.SmallestUnit) * coin.BlockrewardMultiplier;

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

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

            return(result.ToArray());
        }
コード例 #12
0
        public (Share Share, string BlobHex) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker)
        {
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty");
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty");
            Contract.Requires <ArgumentException>(workerExtraNonce != 0, $"{nameof(workerExtraNonce)} must not be empty");

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

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

            // clone template
            Span <byte> blob = stackalloc byte[blobTemplate.Length];

            blobTemplate.CopyTo(blob);

            // inject extranonce
            var extraNonceBytes = BitConverter.GetBytes(workerExtraNonce.ToBigEndian());

            extraNonceBytes.CopyTo(blob.Slice(BlockTemplate.ReservedOffset, extraNonceBytes.Length));

            // inject nonce
            var nonceBytes = nonce.HexToByteArray();

            nonceBytes.CopyTo(blob.Slice(CryptonoteConstants.BlobNonceOffset, nonceBytes.Length));

            // convert
            var blobConverted = LibCryptonote.ConvertBlob(blob, blobTemplate.Length);

            if (blobConverted == null)
            {
                throw new StratumException(StratumError.MinusOne, "malformed blob");
            }

            // determine variant
            CryptonightVariant variant = CryptonightVariant.VARIANT_0;

            if (coin.HashVariant != 0)
            {
                variant = (CryptonightVariant)coin.HashVariant;
            }
            else
            {
                switch (coin.Hash)
                {
                case CryptonightHashType.Normal:
                    variant = (blobConverted[0] >= 10) ? CryptonightVariant.VARIANT_4 :
                              ((blobConverted[0] >= 8) ? CryptonightVariant.VARIANT_2 :
                               ((blobConverted[0] == 7) ? CryptonightVariant.VARIANT_1 :
                                CryptonightVariant.VARIANT_0));
                    break;

                case CryptonightHashType.Lite:
                    variant = CryptonightVariant.VARIANT_1;
                    break;

                case CryptonightHashType.Heavy:
                    variant = CryptonightVariant.VARIANT_0;
                    break;
                }
            }

            // hash it
            Span <byte> headerHash = stackalloc byte[32];

            hashFunc(blobConverted, headerHash, variant, BlockTemplate.Height);

            var headerHashString = headerHash.ToHexString();

            if (headerHashString != workerHash)
            {
                throw new StratumException(StratumError.MinusOne, "bad hash");
            }

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

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

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

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

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

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

            if (isBlockCandidate)
            {
                // Compute block hash
                Span <byte> blockHash = stackalloc byte[32];
                ComputeBlockHash(blobConverted, blockHash);

                // Fill in block-relevant fields
                result.IsBlockCandidate = true;
                result.BlockHash        = blockHash.ToHexString();
            }

            return(result, blob.ToHexString());
        }
コード例 #13
0
        public void Init(BlockTemplate blockTemplate, string jobId,
                         PoolConfig poolConfig, BitcoinPoolConfigExtra extraPoolConfig,
                         ClusterConfig clusterConfig, IMasterClock clock,
                         IDestination poolAddressDestination, Network network,
                         bool isPoS, double shareMultiplier, IHashAlgorithm coinbaseHasher,
                         IHashAlgorithm headerHasher, IHashAlgorithm blockHasher)
        {
            Contract.RequiresNonNull(blockTemplate, nameof(blockTemplate));
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(poolAddressDestination, nameof(poolAddressDestination));
            Contract.RequiresNonNull(coinbaseHasher, nameof(coinbaseHasher));
            Contract.RequiresNonNull(headerHasher, nameof(headerHasher));
            Contract.RequiresNonNull(blockHasher, nameof(blockHasher));
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty");

            this.poolConfig             = poolConfig;
            coin                        = poolConfig.Template.As <BitcoinTemplate>();
            networkParams               = coin.GetNetwork(network.NetworkType);
            txVersion                   = coin.CoinbaseTxVersion;
            this.network                = network;
            this.clock                  = clock;
            this.poolAddressDestination = poolAddressDestination;
            BlockTemplate               = blockTemplate;
            JobId                       = jobId;
            Difficulty                  = new Target(new NBitcoin.BouncyCastle.Math.BigInteger(BlockTemplate.Target, 16)).Difficulty;
            extraNoncePlaceHolderLength = BitcoinConstants.ExtranoncePlaceHolderLength;
            this.isPoS                  = isPoS;
            this.shareMultiplier        = shareMultiplier;

            txComment = !string.IsNullOrEmpty(extraPoolConfig?.CoinbaseTxComment) ?
                        extraPoolConfig.CoinbaseTxComment : coin.CoinbaseTxComment;

            if (coin.HasMasterNodes)
            {
                masterNodeParameters = BlockTemplate.Extra.SafeExtensionDataAs <MasterNodeBlockTemplateExtra>();

                if (!string.IsNullOrEmpty(masterNodeParameters.CoinbasePayload))
                {
                    txVersion = 3;
                    var txType = 5;
                    txVersion = txVersion + ((uint)(txType << 16));
                }
            }

            if (coin.HasPayee)
            {
                payeeParameters = BlockTemplate.Extra.SafeExtensionDataAs <PayeeBlockTemplateExtra>();
            }

            this.coinbaseHasher = coinbaseHasher;
            this.headerHasher   = headerHasher;
            this.blockHasher    = blockHasher;

            if (!string.IsNullOrEmpty(BlockTemplate.Target))
            {
                blockTargetValue = new uint256(BlockTemplate.Target);
            }
            else
            {
                var tmp = new Target(BlockTemplate.Bits.HexToByteArray());
                blockTargetValue = tmp.ToUInt256();
            }

            previousBlockHashReversedHex = BlockTemplate.PreviousBlockhash
                                           .HexToByteArray()
                                           .ReverseByteOrder()
                                           .ToHexString();

            BuildMerkleBranches();
            BuildCoinbase();

            jobParams = new object[]
            {
                JobId,
                previousBlockHashReversedHex,
                coinbaseInitialHex,
                coinbaseFinalHex,
                merkleBranchesHex,
                BlockTemplate.Version.ToStringHex8(),
                BlockTemplate.Bits,
                BlockTemplate.CurTime.ToStringHex8(),
                false
            };
        }
コード例 #14
0
        public override async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // Shield first
            if (supportsNativeShielding)
            {
                await ShieldCoinbaseAsync();
            }
            else
            {
                await ShieldCoinbaseEmulatedAsync();
            }

            var didUnlockWallet = false;

            // send in batches with no more than 50 recipients to avoid running into tx size limits
            var pageSize  = 50;
            var pageCount = (int)Math.Ceiling(balances.Length / (double)pageSize);

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

                // get a page full of balances
                var page = balances
                           .Skip(i * pageSize)
                           .Take(pageSize)
                           .ToArray();

                // build args
                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);

                // check shielded balance
                var balanceResult = await daemon.ExecuteCmdSingleAsync <object>(logger, EquihashCommands.ZGetBalance, new object[]
                {
                    poolExtraConfig.ZAddress, // default account
                    ZMinConfirmations,        // only spend funds covered by this many confirmations
                });

                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, // default account
                    amounts,                  // addresses and associated amounts
                    ZMinConfirmations,        // only spend funds covered by this many confirmations
                    TransferFee
                };

                // send command
tryTransfer:
                var result = await daemon.ExecuteCmdSingleAsync <string>(logger, EquihashCommands.ZSendMany, args);

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

                    // check result
                    if (string.IsNullOrEmpty(operationId))
                    {
                        logger.Error(() => $"[{LogCategory}] {EquihashCommands.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[]>(logger,
                                                                                                                           EquihashCommands.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}] {EquihashCommands.ZSendMany} completed with transaction id: {txId}");

                                    await PersistPaymentsAsync(page, txId);

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

                                    continueWaiting = false;
                                    continue;

                                case ZOperationStatus.Cancelled:
                                case ZOperationStatus.Failed:
                                    logger.Error(() => $"{EquihashCommands.ZSendMany} failed: {operationResult.Error.Message} code {operationResult.Error.Code}");
                                    NotifyPayoutFailure(poolConfig.Id, page, $"{EquihashCommands.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>(logger, BitcoinCommands.WalletPassphrase, new[]
                            {
                                (object)extraPoolPaymentProcessingConfig.WalletPassword,
                                (object)5  // unlock for N seconds
                            });

                            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}] {EquihashCommands.ZSendMany} returned error: {result.Error.Message} code {result.Error.Code}");

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

            // lock wallet
            logger.Info(() => $"[{LogCategory}] Locking wallet");
            await daemon.ExecuteCmdSingleAsync <JToken>(logger, BitcoinCommands.WalletLock);
        }
コード例 #15
0
        public virtual async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

            var coin      = poolConfig.Template.As <CoinTemplate>();
            var pageSize  = 100;
            var pageCount = (int)Math.Ceiling(blocks.Length / (double)pageSize);
            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();

                // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction)
                var batch = page.Select(block => new DaemonCmd(BitcoinCommands.GetTransaction,
                                                               new[] { block.TransactionConfirmationData })).ToArray();

                // execute batch
                var results = await daemon.ExecuteBatchAnyAsync(logger, batch);

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

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

                    // check error
                    if (cmdResult.Error != null)
                    {
                        // Code -5 interpreted as "orphaned"
                        if (cmdResult.Error.Code == -5)
                        {
                            block.Status = BlockStatus.Orphaned;
                            block.Reward = 0;
                            result.Add(block);
                        }

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

                    // missing transaction details are interpreted as "orphaned"
                    else if (transactionInfo?.Details == null || transactionInfo.Details.Length == 0)
                    {
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;
                        result.Add(block);
                    }

                    else
                    {
                        switch (transactionInfo.Details[0].Category)
                        {
                        case "immature":
                            // update progress
                            var minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations;
                            block.ConfirmationProgress = Math.Min(1.0d, (double)transactionInfo.Confirmations / minConfirmations);
                            block.Reward = transactionInfo.Amount;      // update actual block-reward from coinbase-tx
                            result.Add(block);

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

                        case "generate":
                            // matured and spendable coinbase transaction
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.Reward = transactionInfo.Amount;      // update actual block-reward from coinbase-tx
                            result.Add(block);

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

                            messageBus.NotifyBlockUnlocked(poolConfig.Id, block, coin);
                            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);

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

            return(result.ToArray());
        }
コード例 #16
0
        public virtual async Task PayoutAsync(Balance[] balances)
        {
            Contract.RequiresNonNull(balances, nameof(balances));

            // build args
            var amounts = balances
                          .Where(x => x.Amount > 0)
                          .ToDictionary(x => x.Address, x => Math.Round(x.Amount, 4));

            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 ?? "PoolCore").Trim() + " Payment";
                var subtractFeesFrom = amounts.Keys.ToArray();

                if (!poolConfig.Template.As <BitcoinTemplate>().HasMasterNodes)
                {
                    args = new object[]
                    {
                        string.Empty,    // default account
                        amounts,         // addresses and associated amounts
                        1,               // only spend funds covered by this many confirmations
                        comment,         // tx comment
                        subtractFeesFrom // distribute transaction fee equally over all recipients
                    };
                }

                else
                {
                    args = new object[]
                    {
                        string.Empty,     // default account
                        amounts,          // addresses and associated amounts
                        1,                // only spend funds covered by this many confirmations
                        false,            // Whether to add confirmations to transactions locked via InstantSend
                        comment,          // tx comment
                        subtractFeesFrom, // distribute transaction fee equally over all recipients
                        false,            // use_is: Send this transaction as InstantSend
                        false,            // Use anonymized funds only
                    };
                }
            }

            else
            {
                args = new object[]
                {
                    string.Empty, // default account
                    amounts,      // addresses and associated amounts
                };
            }

            var didUnlockWallet = false;

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

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

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

                await PersistPaymentsAsync(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>(logger, BitcoinCommands.WalletPassphrase, new[]
                        {
                            (object)extraPoolPaymentProcessingConfig.WalletPassword,
                            (object)5  // unlock for N seconds
                        });

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