示例#1
0
        private async Task <EthereumBlockTemplate> GetBlockTemplateGethAsync()
        {
            logger.LogInvoke();

            var commands = new[]
            {
                new DaemonCmd(EC.GetWork),
                new DaemonCmd(EC.GetBlockByNumber, new[] { (object)"latest", true })
            };

            var results = await daemon.ExecuteBatchAnyAsync(logger, commands);

            if (results.Any(x => x.Error != null))
            {
                logger.Warn(() => $"Error(s) refreshing blocktemplate: {results.First(x => x.Error != null).Error.Message}");
                return(null);
            }

            // extract results
            var work  = results[0].Response.ToObject <string[]>();
            var block = results[1].Response.ToObject <Block>();

            // append blockheight (parity returns this as 4th element in the getWork response, geth does not)
            if (work.Length < 4)
            {
                var currentHeight = block.Height.Value;
                work = work.Concat(new[] { (currentHeight + 1).ToStringHexWithPrefix() }).ToArray();
            }

            var result = AssembleBlockTemplate(work);

            return(result);
        }
示例#2
0
        private async Task <(bool Accepted, string CoinbaseTransaction)> SubmitBlockAsync(BitcoinShare share)
        {
            // execute command batch
            var results = await daemon.ExecuteBatchAnyAsync(
                hasSubmitBlockMethod
                ?new DaemonCmd(BitcoinCommands.SubmitBlock, new[] { share.BlockHex })
                : new DaemonCmd(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = share.BlockHex }),
                new DaemonCmd(BitcoinCommands.GetBlock, new[] { share.BlockHash }));

            // did submission succeed?
            var submitResult = results[0];
            var submitError  = submitResult.Error?.Message ?? submitResult.Response?.ToString();

            if (!string.IsNullOrEmpty(submitError))
            {
                logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} submission failed with: {submitError}");
                return(false, null);
            }

            // was it accepted?
            var acceptResult = results[1];
            var block        = acceptResult.Response?.ToObject <Blocks>();
            var accepted     = acceptResult.Error == null && block?.Hash == share.BlockHash;

            return(accepted, block?.Transactions.FirstOrDefault());
        }
示例#3
0
        private async Task UpdateNetworkStatsAsync()
        {
            logger.LogInvoke(LogCat);

            try
            {
                var results = await daemon.ExecuteBatchAnyAsync(
                    new DaemonCmd(BitcoinCommands.GetMiningInfo),
                    new DaemonCmd(BitcoinCommands.GetNetworkInfo)
                    );

                if (results.Any(x => x.Error != null))
                {
                    var errors = results.Where(x => x.Error != null).ToArray();

                    if (errors.Any())
                    {
                        logger.Warn(() => $"[{LogCat}] Error(s) refreshing network stats: {string.Join(", ", errors.Select(y => y.Error.Message))}");
                    }
                }

                var miningInfoResponse  = results[0].Response.ToObject <MiningInfo>();
                var networkInfoResponse = results[1].Response.ToObject <NetworkInfo>();

                BlockchainStats.NetworkHashrate = miningInfoResponse.NetworkHashps;
                BlockchainStats.ConnectedPeers  = networkInfoResponse.Connections;
            }

            catch (Exception e)
            {
                logger.Error(e);
            }
        }
示例#4
0
        private async Task <DaemonResponses.Block[]> FetchBlocks(Dictionary <long, DaemonResponses.Block> blockCache, params long[] blockHeights)
        {
            var cacheMisses = blockHeights.Where(x => !blockCache.ContainsKey(x)).ToArray();

            if (cacheMisses.Any())
            {
                var blockBatch = cacheMisses.Select(height => new DaemonCmd(AionCommands.GetBlockByNumber,
                                                                            new[]
                {
                    (object)height.ToStringHexWithPrefix(),
                    true
                })).ToArray();

                var tmp = await daemon.ExecuteBatchAnyAsync(blockBatch);

                var transformed = tmp
                                  .Where(x => x.Error == null && x.Response != null)
                                  .Select(x => x.Response?.ToObject <DaemonResponses.Block>())
                                  .Where(x => x != null)
                                  .ToArray();

                foreach (var block in transformed)
                {
                    blockCache[(long)block.Height.Value] = block;
                }
            }

            return(blockHeights.Select(x => blockCache[x]).ToArray());
        }
示例#5
0
        private async Task <EthereumBlockTemplate> GetBlockTemplateAsync()
        {
            logger.LogInvoke(LogCat);

            var commands = new[]
            {
                new DaemonCmd(EC.GetBlockByNumber, new[] { (object)"pending", true }),
                new DaemonCmd(EC.GetWork),
            };

            var results = await daemon.ExecuteBatchAnyAsync(commands);

            if (results.Any(x => x.Error != null))
            {
                var errors = results.Where(x => x.Error != null)
                             .ToArray();

                if (errors.Any())
                {
                    logger.Warn(() => $"[{LogCat}] Error(s) refreshing blocktemplate: {string.Join(", ", errors.Select(y => y.Error.Message))})");
                    return(null);
                }
            }

            // extract results
            var block  = results[0].Response.ToObject <Block>();
            var work   = results[1].Response.ToObject <string[]>();
            var result = AssembleBlockTemplate(block, work);

            return(result);
        }
示例#6
0
        private async Task <EthereumBlockTemplate> GetBlockTemplateAsync()
        {
            logger.LogInvoke(LogCat);

            var commands = new[]
            {
                new DaemonCmd(EC.GetBlockByNumber, new[] { (object)"pending", true }),
                new DaemonCmd(EC.GetWork),
            };

            var results = await daemon.ExecuteBatchAnyAsync(commands);

            if (results.Any(x => x.Error != null))
            {
                var errors = results.Where(x => x.Error != null)
                             .ToArray();

                if (errors.Any())
                {
                    logger.Warn(() => $"[{LogCat}] Error(s) refreshing blocktemplate: {string.Join(", ", errors.Select(y => y.Error.Message))})");
                    return(null);
                }
            }

            // extract results
            var block = results[0].Response.ToObject <Block>();
            var work  = results[1].Response.ToObject <string[]>();

            // only parity returns the 4th element (block height)
            if (work.Length < 3)
            {
                logger.Error(() => $"[{LogCat}] Error(s) refreshing blocktemplate: getWork did not return blockheight. Are you really connected to a Parity daemon?");
                return(null);
            }

            // make sure block matches work
            var height = work[3].IntegralFromHex <ulong>();

            if (height != block.Height)
            {
                logger.Debug(() => $"[{LogCat}] Discarding block template update as getWork result is not related to pending block");
                return(null);
            }

            var result = new EthereumBlockTemplate
            {
                Header     = work[0],
                Seed       = work[1],
                Target     = BigInteger.Parse("0" + work[2].Substring(2), NumberStyles.HexNumber),
                Difficulty = block.Difficulty.IntegralFromHex <ulong>(),
                Height     = block.Height.Value,
                ParentHash = block.ParentHash,
            };

            return(result);
        }
示例#7
0
        private async Task <EthereumBlockTemplate> GetBlockTemplateAsync()
        {
            var commands = new[]
            {
                new DaemonCmd(EC.GetBlockByNumber, new[] { (object)"pending", true }),
                new DaemonCmd(EC.GetWork),
            };

            var results = await daemon.ExecuteBatchAnyAsync(commands);

            if (results.Any(x => x.Error != null))
            {
                var errors = results.Where(x => x.Error != null)
                             .ToArray();

                if (errors.Any())
                {
                    logger.Warn(() => $"[{LogCat}] Error(s) refreshing blocktemplate: {string.Join(", ", errors.Select(y => y.Error.Message))})");
                    return(null);
                }
            }

            // extract results
            var block = results[0].Response.ToObject <Block>();
            var work  = results[1].Response.ToObject <string[]>();

            var result = new EthereumBlockTemplate
            {
                Header     = work[0].HexToByteArray(),
                Seed       = work[1].HexToByteArray(),
                Target     = new BigInteger(work[2].HexToByteArray().ToReverseArray()), // BigInteger.Parse(work[2], NumberStyles.HexNumber | NumberStyles.AllowHexSpecifier)
                Difficulty = block.Difficulty.IntegralFromHex <ulong>(),
                Height     = block.Height.Value,
                ParentHash = block.ParentHash,
            };

            return(result);
        }
示例#8
0
        private async Task UpdateNetworkStatsAsync()
        {
            logger.LogInvoke(LogCat);

            try
            {
                var commands = new[]
                {
                    new DaemonCmd(EC.GetPeerCount),
                };

                var results = await daemon.ExecuteBatchAnyAsync(commands);

                if (results.Any(x => x.Error != null))
                {
                    var errors = results.Where(x => x.Error != null)
                                 .ToArray();

                    if (errors.Any())
                    {
                        logger.Warn(() => $"[{LogCat}] Error(s) refreshing network stats: {string.Join(", ", errors.Select(y => y.Error.Message))})");
                    }
                }

                // extract results
                var peerCount = results[0].Response.ToObject <string>().IntegralFromHex <int>();

                BlockchainStats.NetworkHashrate = 0; // TODO
                BlockchainStats.ConnectedPeers  = peerCount;
            }

            catch (Exception e)
            {
                logger.Error(e);
            }
        }
        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];

                    // 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))
                    {
                        // mature?
                        if (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations)
                        {
                            var blockHashResponses = await daemon.ExecuteCmdAllAsync <DaemonResponses.Block>(logger, EC.GetBlockByNumber, new[] { (object)block.BlockHeight.ToStringHexWithPrefix(), true });

                            var blockHash = blockHashResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash;

                            block.Hash   = blockHash;
                            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)
                                {
                                    var blockHashUncleResponses = await daemon.ExecuteCmdAllAsync <DaemonResponses.Block>(logger, EC.GetBlockByNumber, new[] { (object)uncle.Height.Value.ToStringHexWithPrefix(), true });

                                    var blockHashUncle = blockHashUncleResponses.First(x => x.Error == null && x.Response?.Hash != null).Response.Hash;

                                    block.Hash   = blockHashUncle;
                                    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 > 0.75)
                    {
                        // we've lost this one
                        block.Hash   = "0x0";
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;

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

            return(result.ToArray());
        }
示例#10
0
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Assertion.RequiresNonNull(poolConfig, nameof(poolConfig));
            Assertion.RequiresNonNull(blocks, nameof(blocks));

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

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

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

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

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

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

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

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

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

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

                        continue;
                    }

                    var heightMin = block.BlockHeight - BlockSearchOffset;
                    var heightMax = Math.Min(block.BlockHeight + BlockSearchOffset, latestBlockHeight);
                    var range     = new List <long>();

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

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

                    foreach (var blockInfo2 in blockInfo2s)
                    {
                        if (blockInfo2.Uncles.Length > 0)
                        {
                            var uncleBatch = blockInfo2.Uncles.Select((x, index) => new DaemonCmd(EC.GetUncleByBlockNumberAndIndex,
                                                                                                  new[] { blockInfo2.Height.Value.ToStringHexWithPrefix(), index.ToStringHexWithPrefix() }))
                                             .ToArray();

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

                            var uncleResponses = await daemon.ExecuteBatchAnyAsync(uncleBatch);

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

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

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

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

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

                                break;
                            }
                        }
                    }

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

            return(result.ToArray());
        }
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

            await DetectChainAsync();

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

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

                        // mature?
                        if (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations)
                        {
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.Reward = GetBaseBlockReward(block.BlockHeight); // base reward

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

                        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(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)
                            {
                                // mature?
                                if (latestBlockHeight - uncle.Height.Value >= EthereumConstants.MinConfimations)
                                {
                                    block.Status = BlockStatus.Confirmed;
                                    block.ConfirmationProgress = 1;
                                    block.Reward      = GetUncleReward(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)
                    {
                        // we've lost this one
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;
                    }
                }
            }

            return(result.ToArray());
        }
        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());
        }
示例#13
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, EthCommands.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++)
                {
                    logger.Info(() => $"Blocks count to process : {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();
                    logger.Debug(() => $"** TransactionData: {block.TransactionConfirmationData}");


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

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


                    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
                        logger.Info(() => $"** Ethereum Deamon is Parity : {isParity}");

                        // is the block mined by us?
                        bool match = false;
                        if (isParity)
                        {
                            match = isParity ? true : blockInfo.SealFields[0].Substring(2) == mixHash && blockInfo.SealFields[1].Substring(2) == nonce;
                            logger.Debug(() => $"** Parity mixHash : {blockInfo.SealFields[0].Substring(2)} =?= {mixHash}");
                            logger.Debug(() => $"** Parity nonce   : {blockInfo.SealFields[1].Substring(2)} =?= {nonce}");
                        }
                        else
                        {
                            if (blockInfo.MixHash == mixHash && blockInfo.Nonce == nonce)
                            {
                                match = true;
                                logger.Debug(() => $"** Geth mixHash : {blockInfo.MixHash} =?= {mixHash}");
                                logger.Debug(() => $"** Geth nonce   : {blockInfo.Nonce} =?= {nonce}");
                                logger.Debug(() => $"** (MIXHASH_NONCE) Is the Block mined by us? {match}");
                            }
                            if (blockInfo.Miner == poolConfig.Address)
                            {
                                //match = true;
                                logger.Debug(() => $"Is the block mined by us? Yes if equal: {blockInfo.Miner} =?= {poolConfig.Address}");
                                logger.Debug(() => $"** (WALLET_MATCH) Is the Block mined by us? {match}");
                                logger.Debug(() => $"** Possible Uncle or Orphan block found");
                            }
                        }

                        // 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(EthCommands.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 > 0.75)
                    {
                        // we've lost this one
                        block.Status = BlockStatus.Orphaned;
                        block.Reward = 0;

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

            return(result.ToArray());
        }
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(blocks, nameof(blocks));

            await DetectChainAsync();

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

                // get latest block
                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;

                // build command batch (block.TransactionConfirmationData is the hash of the blocks coinbase transaction)
                var blockBatch = page.Select(block => new DaemonCmd(EC.GetBlockByNumber,
                                                                    new[]
                {
                    (object)block.BlockHeight.ToStringHexWithPrefix(),
                    true
                })).ToArray();

                // execute batch
                var responses = await daemon.ExecuteBatchAnyAsync(blockBatch);

                for (var j = 0; j < responses.Length; j++)
                {
                    var blockResponse = responses[j];

                    var blockInfo = blockResponse.Response?.ToObject <DaemonResponses.Block>();
                    var block     = page[j];

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

                    // check error
                    if (blockResponse.Error != null)
                    {
                        logger.Warn(() => $"[{LogCategory}] Daemon reports error '{blockResponse.Error.Message}' (Code {blockResponse.Error.Code}) for block {page[j].BlockHeight}. Will retry.");
                        continue;
                    }

                    // missing details with no error
                    if (blockInfo == null)
                    {
                        logger.Warn(() => $"[{LogCategory}] Daemon returned null result for block {page[j].BlockHeight}. Will retry.");
                        continue;
                    }

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

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

                        // mature?
                        if (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations)
                        {
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            block.Reward = GetBaseBlockReward(block.BlockHeight);         // base reward

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

                        continue;
                    }

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

                        var uncleResponses = await daemon.ExecuteBatchAnyAsync(uncleBatch);

                        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)
                        {
                            // mature?
                            if (latestBlockHeight - block.BlockHeight >= EthereumConstants.MinConfimations)
                            {
                                block.Status = BlockStatus.Confirmed;
                                block.ConfirmationProgress = 1;
                                block.Reward = GetUncleReward(uncle.Height.Value, block.BlockHeight);

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

                            continue;
                        }
                    }

                    if (block.ConfirmationProgress > 0.75)
                    {
                        // we've lost this one
                        block.Status = BlockStatus.Orphaned;
                    }
                }
            }

            return(result.ToArray());
        }
示例#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.SendMessage(new BlockConfirmationProgressNotification(block.ConfirmationProgress, poolConfig.Id, block.BlockHeight, coin.Symbol));
                            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)}");

                            // build explorer link
                            string explorerLink = null;
                            if (coin.ExplorerBlockLinks.TryGetValue(!string.IsNullOrEmpty(block.Type) ? block.Type : "block", out var blockInfobaseUrl))
                            {
                                if (blockInfobaseUrl.Contains(CoinMetaData.BlockHeightPH))
                                {
                                    explorerLink = blockInfobaseUrl.Replace(CoinMetaData.BlockHeightPH, block.BlockHeight.ToString(CultureInfo.InvariantCulture));
                                }
                                else if (blockInfobaseUrl.Contains(CoinMetaData.BlockHashPH) && !string.IsNullOrEmpty(block.Hash))
                                {
                                    explorerLink = blockInfobaseUrl.Replace(CoinMetaData.BlockHashPH, block.Hash);
                                }
                            }

                            messageBus.SendMessage(new BlockUnlockedNotification(block.Status, poolConfig.Id,
                                                                                 block.BlockHeight, block.Hash, block.Miner, coin.Symbol, explorerLink));
                            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.SendMessage(new BlockUnlockedNotification(block.Status, poolConfig.Id,
                                                                                 block.BlockHeight, block.Hash, block.Miner, coin.Symbol, null));
                            break;
                        }
                    }
                }
            }

            return(result.ToArray());
        }
示例#16
0
        public virtual async Task <Block[]> ClassifyBlocksAsync(IMiningPool pool, Block[] blocks, CancellationToken ct)
        {
            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>();
            int minConfirmations;

            if (coin is BitcoinTemplate bitcoinTemplate)
            {
                minConfirmations = extraPoolConfig?.MinimumConfirmations ?? bitcoinTemplate.CoinbaseMinConfimations ?? BitcoinConstants.CoinbaseMinConfimations;
            }
            else
            {
                minConfirmations = extraPoolConfig?.MinimumConfirmations ?? BitcoinConstants.CoinbaseMinConfimations;
            }

            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, ct, 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);

                            logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to daemon error {cmdResult.Error.Code}");

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

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

                        logger.Info(() => $"[{LogCategory}] Block {block.BlockHeight} classified as orphaned due to missing tx details");
                    }

                    else
                    {
                        switch (transactionInfo.Details[0].Category)
                        {
                        case "immature":
                            // update progress
                            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());
        }
示例#17
0
        public async Task <Block[]> ClassifyBlocksAsync(Block[] blocks)
        {
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.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++)
            {
                // 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(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;
                            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;
                        result.Add(block);
                    }

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

                        case "generate":
                            // matured and spendable coinbase transaction
                            block.Status = BlockStatus.Confirmed;
                            block.ConfirmationProgress = 1;
                            result.Add(block);

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

                        default:
                            block.Status = BlockStatus.Orphaned;
                            result.Add(block);
                            break;
                        }
                    }
                }
            }

            return(result.ToArray());
        }