private async Task ShowDaemonSyncProgressAsync() { var infos = await daemon.ExecuteCmdAllAsync<JToken>(EC.GetSyncState); var firstValidResponse = infos.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; if (firstValidResponse != null) { // eth_syncing returns false if not synching if (firstValidResponse.Type == JTokenType.Boolean) return; var syncStates = infos.Where(x => x.Error == null && x.Response != null && firstValidResponse.Type == JTokenType.Object) .Select(x => x.Response.ToObject<SyncState>()) .ToArray(); if (syncStates.Any()) { // get peer count var response = await daemon.ExecuteCmdAllAsync<string>(EC.GetPeerCount); var validResponses = response.Where(x => x.Error == null && x.Response != null).ToArray(); var peerCount = validResponses.Any() ? validResponses.Max(x => x.Response.IntegralFromHex<uint>()) : 0; if (syncStates.Any(x => x.HighestBlock != 0)) { var lowestHeight = syncStates.Min(x => x.CurrentBlock); var totalBlocks = syncStates.Max(x => x.HighestBlock); var percent = (double) lowestHeight / totalBlocks * 100; logger.Info(() => $"[{LogCat}] Daemons have downloaded {percent:0.00}% of blockchain from {peerCount} peers"); } } } }
protected virtual async Task ShowDaemonSyncProgressAsync() { if (hasLegacyDaemon) { await ShowDaemonSyncProgressLegacyAsync(); return; } var infos = await daemon.ExecuteCmdAllAsync <BlockchainInfo>(BitcoinCommands.GetBlockchainInfo); if (infos.Length > 0) { var blockCount = infos .Max(x => x.Response?.Blocks); if (blockCount.HasValue) { var peerInfo = await daemon.ExecuteCmdAnyAsync <PeerInfo[]>(BitcoinCommands.GetPeerInfo); var peers = peerInfo.Response; if (peers != null && peers.Length > 0) { var totalBlocks = peers.Max(x => x.StartingHeight); var percent = totalBlocks > 0 ? (double)blockCount / totalBlocks * 100 : 0; logger.Info(() => $"[{LogCat}] Daemons have downloaded {percent:0.00}% of blockchain from {peers.Length} peers"); } } } }
private async Task ShowDaemonSyncProgressAsync() { var infos = await daemon.ExecuteCmdAllAsync <Info>(BitcoinCommands.GetInfo); if (infos.Length > 0) { var blockCount = infos .Max(x => x.Response?.Blocks); if (blockCount.HasValue) { // get list of peers and their highest block height to compare to ours var peerInfo = await daemon.ExecuteCmdAnyAsync <PeerInfo[]>(BitcoinCommands.GetPeerInfo); var peers = peerInfo.Response; if (peers != null && peers.Length > 0) { var totalBlocks = peers.Max(x => x.StartingHeight); var percent = totalBlocks > 0 ? (double)blockCount / totalBlocks * 100 : 0; logger.Info(() => $"[{LogCat}] Daemons have downloaded {percent:0.00}% of blockchain from {peers.Length} peers"); } } } }
protected override async Task <bool> AreDaemonsHealthy() { // test daemons var responses = await daemon.ExecuteCmdAllAsync <GetInfoResponse>(MC.GetInfo); if (responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) .Select(x => (DaemonClientException)x.Error.InnerException) .Any(x => x.Code == HttpStatusCode.Unauthorized)) { logger.ThrowLogPoolStartupException($"Daemon reports invalid credentials", LogCat); } if (!responses.All(x => x.Error == null)) { return(false); } // test wallet daemons var responses2 = await walletDaemon.ExecuteCmdAllAsync <object>(MWC.GetAddress); if (responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) .Select(x => (DaemonClientException)x.Error.InnerException) .Any(x => x.Code == HttpStatusCode.Unauthorized)) { logger.ThrowLogPoolStartupException($"Wallet-Daemon reports invalid credentials", LogCat); } return(responses2.All(x => x.Error == null)); }
protected override async Task <bool> AreDaemonsHealthyAsync() { // test daemons var responses = await daemon.ExecuteCmdAllAsync <GetInfoResponse>(logger, CryptonoteCommands.GetInfo); if (responses.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) .Select(x => (DaemonClientException)x.Error.InnerException) .Any(x => x.Code == HttpStatusCode.Unauthorized)) { logger.ThrowLogPoolStartupException($"Daemon reports invalid credentials"); } if (!responses.All(x => x.Error == null)) { return(false); } if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) { // test wallet daemons var responses2 = await walletDaemon.ExecuteCmdAllAsync <object>(logger, CryptonoteWalletCommands.GetAddress); if (responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) .Select(x => (DaemonClientException)x.Error.InnerException) .Any(x => x.Code == HttpStatusCode.Unauthorized)) { logger.ThrowLogPoolStartupException($"Wallet-Daemon reports invalid credentials"); } return(responses2.All(x => x.Error == null)); } return(true); }
private async Task ShowDaemonSyncProgressAsync() { var infos = await daemon.ExecuteCmdAllAsync<GetInfoResponse>(MC.GetInfo); var firstValidResponse = infos.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; if (firstValidResponse != null) { var lowestHeight = infos.Where(x => x.Error == null && x.Response != null) .Min(x => x.Response.Height); var totalBlocks = firstValidResponse.TargetHeight; var percent = (double) lowestHeight / totalBlocks * 100; logger.Info(() => $"[{LogCat}] Daemons have downloaded {percent:0.00}% of blockchain from {firstValidResponse.OutgoingConnectionsCount} peers"); } }
private async Task ShowDaemonSyncProgressAsync() { var responses = await daemon.ExecuteCmdAllAsync <object>(logger, EC.GetSyncState); var firstValidResponse = responses.FirstOrDefault(x => x.Error == null && x.Response != null)?.Response; if (firstValidResponse != null) { // eth_syncing returns false if not synching if (firstValidResponse is bool) { return; } var syncStates = responses.Where(x => x.Error == null && x.Response != null && firstValidResponse is JObject) .Select(x => ((JObject)x.Response).ToObject <SyncState>()) .ToArray(); if (syncStates.Any()) { // get peer count var response = await daemon.ExecuteCmdAllAsync <string>(logger, EC.GetPeerCount); var validResponses = response.Where(x => x.Error == null && x.Response != null).ToArray(); var peerCount = validResponses.Any() ? validResponses.Max(x => x.Response.IntegralFromHex <uint>()) : 0; if (syncStates.Any(x => x.WarpChunksAmount != 0)) { var warpChunkAmount = syncStates.Min(x => x.WarpChunksAmount); var warpChunkProcessed = syncStates.Max(x => x.WarpChunksProcessed); var percent = (double)warpChunkProcessed / warpChunkAmount * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of warp-chunks from {peerCount} peers"); } else if (syncStates.Any(x => x.HighestBlock != 0)) { var lowestHeight = syncStates.Min(x => x.CurrentBlock); var totalBlocks = syncStates.Max(x => x.HighestBlock); var percent = (double)lowestHeight / totalBlocks * 100; logger.Info(() => $"Daemons have downloaded {percent:0.00}% of blockchain from {peerCount} peers"); } } } }
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) { 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 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 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>(AionCommands.GetBlockByNumber, new[] { (object)"latest", true }); if (!latestBlockResponses.Any(x => x.Error == null && x.Response?.Height != null)) { break; } 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) / extraConfig.MinimumConfirmations); result.Add(block); // is it block mined by us? if (blockInfo.Miner == poolConfig.Address) { // mature? if (latestBlockHeight - block.BlockHeight >= (ulong)extraConfig.MinimumConfirmations) { block.Status = BlockStatus.Confirmed; block.ConfirmationProgress = 1; block.Reward = AionUtils.calculateReward((long)block.BlockHeight); 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; } 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 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 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 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()); }