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