// Returns the number of empty and full blocks, based on PoW field public List <int> getBlocksCount() { int empty_blocks = 0; int full_blocks = 0; ulong lastBlockNum = Node.blockChain.getLastBlockNum(); ulong oldestRedactedBlock = 0; if (lastBlockNum > ConsensusConfig.getRedactedWindowSize()) { oldestRedactedBlock = lastBlockNum - ConsensusConfig.getRedactedWindowSize(); } for (ulong i = lastBlockNum; i > oldestRedactedBlock; i--) { Block block = Node.blockChain.getBlock(i, false, false); if (block == null) { continue; } if (block.powField == null) { empty_blocks++; } else { full_blocks++; } } List <int> result = new List <int>(); result.Add(empty_blocks); result.Add(full_blocks); return(result); }
// Reverts redaction for a single block private bool unredactChain() { lock (blocks) { int redacted_window_size = (int)ConsensusConfig.getRedactedWindowSize(getLastBlockVersion()); if (blocks.Count() == redacted_window_size) { Logging.warn("Won't unredact chain, block count is already correct."); return(false); } while (blocks.Count() < redacted_window_size) { if (lastBlockNum < (ulong)redacted_window_size) { return(false); } ulong block_num_to_unredact = lastBlockNum - (ulong)blocks.Count(); Block b = getBlock(block_num_to_unredact, true, true); lock (blocksDictionary) { if (blocksDictionary.ContainsKey(block_num_to_unredact)) { Logging.warn("Won't unredact chain, block #{0} is already in memory.", block_num_to_unredact); return(false); } } if (!TransactionPool.unredactTransactionsForBlock(b)) { TransactionPool.redactTransactionsForBlock(b); return(false); } blocks.Insert(0, b); lock (blocksDictionary) { blocksDictionary.Add(block_num_to_unredact, b); } if (b.powField != null) { increaseSolvedBlocksCount(); } Logging.info("UNREDACTED block #{0} to keep the chain length appropriate.", block_num_to_unredact); } } return(true); }
public static void processPendingTransactions() { // TODO TODO improve to include failed transactions ulong last_block_height = IxianHandler.getLastBlockHeight(); lock (PendingTransactions.pendingTransactions) { long cur_time = Clock.getTimestamp(); List <PendingTransaction> tmp_pending_transactions = new List <PendingTransaction>(PendingTransactions.pendingTransactions); int idx = 0; foreach (var entry in tmp_pending_transactions) { Transaction t = entry.transaction; long tx_time = entry.addedTimestamp; if (t.applied != 0) { PendingTransactions.pendingTransactions.RemoveAll(x => x.transaction.id.SequenceEqual(t.id)); continue; } // if transaction expired, remove it from pending transactions if (last_block_height > ConsensusConfig.getRedactedWindowSize() && t.blockHeight < last_block_height - ConsensusConfig.getRedactedWindowSize()) { ActivityStorage.updateStatus(t.id, ActivityStatus.Error, 0); PendingTransactions.pendingTransactions.RemoveAll(x => x.transaction.id.SequenceEqual(t.id)); continue; } if (cur_time - tx_time > 40) // if the transaction is pending for over 40 seconds, resend { CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.transactionData, t.getBytes(), null); entry.addedTimestamp = cur_time; entry.confirmedNodeList.Clear(); } if (entry.confirmedNodeList.Count() >= 3) // if we get transaction from 3 nodes, we can consider it as confirmed { if (entry.messageId != null) { StreamProcessor.confirmMessage(entry.messageId); } continue; } if (cur_time - tx_time > 20) // if the transaction is pending for over 20 seconds, send inquiry { CoreProtocolMessage.broadcastGetTransaction(t.id, 0, null, false); } idx++; } } }
public static void addTransactionToActivityStorage(Transaction transaction) { Activity activity = null; int type = -1; IxiNumber value = transaction.amount; List <byte[]> wallet_list = null; byte[] wallet = null; byte[] primary_address = (new Address(transaction.pubKey)).address; if (IxianHandler.getWalletStorage().isMyAddress(primary_address)) { wallet = primary_address; type = (int)ActivityType.TransactionSent; if (transaction.type == (int)Transaction.Type.PoWSolution) { type = (int)ActivityType.MiningReward; value = ConsensusConfig.calculateMiningRewardForBlock(BitConverter.ToUInt64(transaction.data, 0)); } } else { wallet_list = IxianHandler.getWalletStorage().extractMyAddressesFromAddressList(transaction.toList); if (wallet_list != null) { type = (int)ActivityType.TransactionReceived; if (transaction.type == (int)Transaction.Type.StakingReward) { type = (int)ActivityType.StakingReward; } } } if (type != -1) { int status = (int)ActivityStatus.Pending; if (transaction.applied > 0) { status = (int)ActivityStatus.Final; } if (wallet_list != null) { foreach (var entry in wallet_list) { activity = new Activity(IxianHandler.getWalletStorage().getSeedHash(), Base58Check.Base58CheckEncoding.EncodePlain(entry), Base58Check.Base58CheckEncoding.EncodePlain(primary_address), transaction.toList, type, transaction.id, transaction.toList[entry].ToString(), transaction.timeStamp, status, transaction.applied, Transaction.txIdV8ToLegacy(transaction.id)); ActivityStorage.insertActivity(activity); } } else if (wallet != null) { activity = new Activity(IxianHandler.getWalletStorage().getSeedHash(), Base58Check.Base58CheckEncoding.EncodePlain(wallet), Base58Check.Base58CheckEncoding.EncodePlain(primary_address), transaction.toList, type, transaction.id, value.ToString(), transaction.timeStamp, status, transaction.applied, Transaction.txIdV8ToLegacy(transaction.id)); ActivityStorage.insertActivity(activity); } } }
public static void processPendingTransactions() { // TODO TODO improve to include failed transactions ulong last_block_height = IxianHandler.getLastBlockHeight(); lock (PendingTransactions.pendingTransactions) { long cur_time = Clock.getTimestamp(); List <PendingTransaction> tmp_pending_transactions = new List <PendingTransaction>(PendingTransactions.pendingTransactions); int idx = 0; foreach (var entry in tmp_pending_transactions) { Transaction t = entry.transaction; long tx_time = entry.addedTimestamp; if (t.applied != 0) { PendingTransactions.pendingTransactions.RemoveAll(x => x.transaction.id.SequenceEqual(t.id)); continue; } // if transaction expired, remove it from pending transactions if (last_block_height > ConsensusConfig.getRedactedWindowSize() && t.blockHeight < last_block_height - ConsensusConfig.getRedactedWindowSize()) { Console.WriteLine("Error sending the transaction {0}", Transaction.txIdV8ToLegacy(t.id)); PendingTransactions.pendingTransactions.RemoveAll(x => x.transaction.id.SequenceEqual(t.id)); continue; } if (cur_time - tx_time > 40) // if the transaction is pending for over 40 seconds, resend { CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.transactionData, t.getBytes(), null); entry.addedTimestamp = cur_time; entry.confirmedNodeList.Clear(); } if (entry.confirmedNodeList.Count() > 3) // already received 3+ feedback { continue; } if (cur_time - tx_time > 20) // if the transaction is pending for over 20 seconds, send inquiry { CoreProtocolMessage.broadcastGetTransaction(Transaction.txIdV8ToLegacy(t.id), 0); } idx++; } } }
private ulong getLowestBlockNum() { ulong lowestBlockNum = 1; if (Config.fullStorageDataVerification) { return(lowestBlockNum); } ulong syncToBlock = wsSyncConfirmedBlockNum; if (syncToBlock > ConsensusConfig.getRedactedWindowSize()) { lowestBlockNum = syncToBlock - ConsensusConfig.getRedactedWindowSize(); } return(lowestBlockNum); }
public static void processPendingTransactions() { // TODO TODO improve to include failed transactions ulong last_block_height = IxianHandler.getLastBlockHeight(); lock (PendingTransactions.pendingTransactions) { long cur_time = Clock.getTimestamp(); List <object[]> tmp_pending_transactions = new List <object[]>(PendingTransactions.pendingTransactions); int idx = 0; foreach (var entry in tmp_pending_transactions) { Transaction t = (Transaction)entry[0]; long tx_time = (long)entry[1]; if ((int)entry[2] > 3) // already received 3+ feedback { continue; } if (t.applied != 0) { PendingTransactions.pendingTransactions.RemoveAll(x => ((Transaction)x[0]).id.SequenceEqual(t.id)); continue; } // if transaction expired, remove it from pending transactions if (last_block_height > ConsensusConfig.getRedactedWindowSize() && t.blockHeight < last_block_height - ConsensusConfig.getRedactedWindowSize()) { Console.WriteLine("Error processing the transaction {0}", Encoding.UTF8.GetBytes(t.id)); PendingTransactions.pendingTransactions.RemoveAll(x => ((Transaction)x[0]).id.SequenceEqual(t.id)); continue; } if (cur_time - tx_time > 40) // if the transaction is pending for over 40 seconds, resend { CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.newTransaction, t.getBytes(), null); PendingTransactions.pendingTransactions[idx][1] = cur_time; } idx++; } } }
public void checkActiveBlockSolved() { if (currentBlockNum > 0) { if (currentBlockNum > ConsensusConfig.getRedactedWindowSize() && currentBlockNum + ConsensusConfig.getRedactedWindowSize() - 100 <= Node.blockChain.getLastBlockNum()) { blockFound = false; } else { Block tmpBlock = Node.blockChain.getBlock(currentBlockNum, false, false); if (tmpBlock == null || tmpBlock.powField != null) { blockFound = false; } } } lock (activePoolBlockLock) { if (activePoolBlock != null) { if (activePoolBlock.blockNum > ConsensusConfig.getRedactedWindowSize() && activePoolBlock.blockNum + ConsensusConfig.getRedactedWindowSize() - 100 <= Node.blockChain.getLastBlockNum()) { activePoolBlock = null; } else { Block tmpBlock = Node.blockChain.getBlock(activePoolBlock.blockNum, false, false); if (tmpBlock == null || tmpBlock.powField != null) { activePoolBlock = null; } } } } }
// Start the node public void start(bool verboseConsoleOutput) { char node_type = 'M'; // TODO TODO TODO TODO change this to 'W' or 'C' after the upgrade if (Config.disableMiner) { node_type = 'M'; } // Check if we're in worker-only mode if (Config.workerOnly) { // Enable miner Config.disableMiner = false; node_type = 'W'; CoreConfig.simultaneousConnectedNeighbors = 4; } // Generate presence list PresenceList.init(IxianHandler.publicIP, Config.serverPort, node_type); // Initialize storage Storage.prepareStorage(); ActivityStorage.prepareStorage(); // Initialize the block chain blockChain = new BlockChain(); //runDiffTests(); //return; // Create the block processor and sync blockProcessor = new BlockProcessor(); blockSync = new BlockSync(); if (Config.devInsertFromJson) { Console.WriteLine("Inserting from JSON"); devInsertFromJson(); Program.noStart = true; return; } if (Config.apiBinds.Count == 0) { Config.apiBinds.Add("http://localhost:" + Config.apiPort + "/"); } // Start the HTTP JSON API server apiServer = new APIServer(Config.apiBinds, Config.apiUsers, Config.apiAllowedIps); if (IXICore.Platform.onMono() == false && !Config.disableWebStart) { System.Diagnostics.Process.Start(Config.apiBinds[0]); } miner = new Miner(); // Start the network queue NetworkQueue.start(); // prepare stats screen ConsoleHelpers.verboseConsoleOutput = verboseConsoleOutput; Logging.consoleOutput = verboseConsoleOutput; Logging.flush(); if (ConsoleHelpers.verboseConsoleOutput == false) { statsConsoleScreen.clearScreen(); } // Distribute genesis funds IxiNumber genesisFunds = new IxiNumber(Config.genesisFunds); // Check if this is a genesis node if (genesisFunds > (long)0) { Logging.info(String.Format("Genesis {0} specified. Starting operation.", genesisFunds)); distributeGenesisFunds(genesisFunds); genesisNode = true; blockProcessor.resumeOperation(); serverStarted = true; if (!isMasterNode()) { Logging.info("Network server is not enabled in modes other than master node."); } else { NetworkServer.beginNetworkOperations(); } } else { if (File.Exists(Config.genesisFile)) { Block genesis = new Block(Crypto.stringToHash(File.ReadAllText(Config.genesisFile))); blockChain.setGenesisBlock(genesis); } ulong lastLocalBlockNum = Meta.Storage.getLastBlockNum(); if (lastLocalBlockNum > 6) { lastLocalBlockNum = lastLocalBlockNum - 6; } if (Config.lastGoodBlock > 0 && Config.lastGoodBlock < lastLocalBlockNum) { lastLocalBlockNum = Config.lastGoodBlock; } if (lastLocalBlockNum > 0) { Block b = blockChain.getBlock(lastLocalBlockNum, true); if (b != null) { ConsensusConfig.minRedactedWindowSize = ConsensusConfig.getRedactedWindowSize(b.version); ConsensusConfig.redactedWindowSize = ConsensusConfig.getRedactedWindowSize(b.version); } } if (Config.recoverFromFile) { Block b = Meta.Storage.getBlock(lastLocalBlockNum); blockSync.onHelloDataReceived(b.blockNum, b.blockChecksum, b.version, b.walletStateChecksum, b.getSignatureCount(), lastLocalBlockNum); } else { ulong blockNum = WalletStateStorage.restoreWalletState(lastLocalBlockNum); if (blockNum > 0) { Block b = blockChain.getBlock(blockNum, true); if (b != null) { blockSync.onHelloDataReceived(blockNum, b.blockChecksum, b.version, b.walletStateChecksum, b.getSignatureCount(), lastLocalBlockNum); } else { walletState.clear(); } } else { blockSync.lastBlockToReadFromStorage = lastLocalBlockNum; } // Start the server for ping purposes serverStarted = true; if (!isMasterNode()) { Logging.info("Network server is not enabled in modes other than master node."); } else { NetworkServer.beginNetworkOperations(); } // Start the network client manager NetworkClientManager.start(); } } PresenceList.startKeepAlive(); TLC = new ThreadLiveCheck(); // Start the maintenance thread maintenanceThread = new Thread(performMaintenance); maintenanceThread.Name = "Node_Maintenance_Thread"; maintenanceThread.Start(); }
public bool revertLastBlock(bool blacklist = true, bool legacy_dual_revert = true) { if (lastBlockNum == 1) { Logging.error("Cannot revert block #1."); return(false); } Block block_to_revert = lastBlock; ulong block_num_to_revert = block_to_revert.blockNum; if (!Node.walletState.canRevertTransaction(block_num_to_revert)) { Logging.error("Cannot revert block #" + block_num_to_revert + ", WSJ transaction is missing."); return(false); } if (reorgBlockStart == 0) { reorgBlockStart = block_num_to_revert; } // Re-org blockchain for max 7 blocks if (block_num_to_revert + 7 < reorgBlockStart) { Logging.error("Cannot revert block #" + block_num_to_revert + ", blockchain re-org started on " + reorgBlockStart); return(false); } Logging.info("Reverting block #" + block_num_to_revert); if (blacklist) { Node.blockProcessor.blacklistBlock(block_to_revert); } removeBlock(block_num_to_revert); lastBlock = getBlock(block_num_to_revert - 1, true, true); lastBlockVersion = lastBlock.version; lastBlockReceivedTime = lastBlock.timestamp; lastBlockNum = lastBlock.blockNum; if (lastSuperBlockNum == block_num_to_revert) { Block super_block = block_to_revert; lastSuperBlockNum = super_block.lastSuperBlockNum; lastSuperBlockChecksum = super_block.lastSuperBlockChecksum; } ConsensusConfig.redactedWindowSize = ConsensusConfig.getRedactedWindowSize(lastBlockVersion); ConsensusConfig.minRedactedWindowSize = ConsensusConfig.getRedactedWindowSize(lastBlockVersion); // edge case for first block of block_version 3 if (lastBlockVersion == 3 && getBlock(lastBlockNum - 1, true, true).version == 2) { Node.walletState.setCachedBlockVersion(2); } else { Node.walletState.setCachedBlockVersion(lastBlock.version); } unredactChain(); Node.walletState.revertTransaction(block_num_to_revert); revertBlockTransactions(block_to_revert); Node.blockProcessor.resetSuperBlockCache(); if (lastBlock.version >= BlockVer.v5 && lastBlock.lastSuperBlockChecksum == null) { if (lastBlock.version >= BlockVer.v8) { if (!Node.walletState.calculateWalletStateDeltaChecksum(lastBlock.blockNum).SequenceEqual(lastBlock.walletStateChecksum)) { Logging.error("Fatal error occured: Delta Wallet state is incorrect after reverting block #{0} - Block's WS Checksum: {1}, WS Checksum: {2}, Wallets: {3}", block_num_to_revert, Crypto.hashToString(lastBlock.walletStateChecksum), Crypto.hashToString(Node.walletState.calculateWalletStateDeltaChecksum(lastBlock.blockNum)), Node.walletState.numWallets); Node.stop(); return(false); } } else if (legacy_dual_revert) { revertLastBlock(false, false); return(true); } } else { if (!Node.walletState.calculateWalletStateChecksum().SequenceEqual(lastBlock.walletStateChecksum)) { Logging.error("Fatal error occured: Wallet state is incorrect after reverting block #{0} - Block's WS Checksum: {1}, WS Checksum: {2}, Wallets: {3}", block_num_to_revert, Crypto.hashToString(lastBlock.walletStateChecksum), Crypto.hashToString(Node.walletState.calculateWalletStateChecksum()), Node.walletState.numWallets); Node.stop(); return(false); } } return(true); }
public bool appendBlock(Block b, bool add_to_storage = true) { lock (blocks) { if (blocks.Count > 0) { Block prev_block = blocks[blocks.Count - 1]; // check for invalid block appending if (b.blockNum != prev_block.blockNum + 1) { Logging.warn(String.Format("Attempting to add non-sequential block #{0} after block #{1}.", b.blockNum, prev_block.blockNum)); return(false); } if (!b.lastBlockChecksum.SequenceEqual(prev_block.blockChecksum)) { Logging.error(String.Format("Attempting to add a block #{0} with invalid lastBlockChecksum!", b.blockNum)); return(false); } if (b.signatureFreezeChecksum != null && blocks.Count > 5 && !blocks[blocks.Count - 5].calculateSignatureChecksum().SequenceEqual(b.signatureFreezeChecksum)) { Logging.error(String.Format("Attempting to add a block #{0} with invalid sigFreezeChecksum!", b.blockNum)); return(false); } } lastBlock = b; lastBlockNum = b.blockNum; if (b.version != lastBlockVersion) { lastBlockVersion = b.version; } if (b.lastSuperBlockChecksum != null || b.blockNum == 1) { pendingSuperBlocks.Remove(b.blockNum); lastSuperBlockNum = b.blockNum; lastSuperBlockChecksum = b.blockChecksum; } // special case when we are starting up and have an empty chain if (blocks.Count == 0) { blocks.Add(b); lock (blocksDictionary) { blocksDictionary.Add(b.blockNum, b); } Node.storage.insertBlock(b); return(true); } blocks.Add(b); lock (blocksDictionary) { blocksDictionary.Add(b.blockNum, b); } if (reorgBlockStart <= b.blockNum) { reorgBlockStart = 0; } } if (add_to_storage) { // Add block to storage Node.storage.insertBlock(b); } ConsensusConfig.redactedWindowSize = ConsensusConfig.getRedactedWindowSize(b.version); ConsensusConfig.minRedactedWindowSize = ConsensusConfig.getRedactedWindowSize(b.version); redactChain(); lock (blocks) { if (blocks.Count > 30) { Block tmp_block = getBlock(b.blockNum - 30); if (tmp_block != null) { TransactionPool.compactTransactionsForBlock(tmp_block); tmp_block.compact(); } } compactBlockSigs(b); } // Cleanup transaction pool TransactionPool.performCleanup(); lastBlockReceivedTime = Clock.getTimestamp(); return(true); }
// Returns the most recent block without a PoW flag in the redacted blockchain private void searchForBlock() { lock (solvedBlocks) { List <ulong> tmpSolvedBlocks = new List <ulong>(solvedBlocks); foreach (ulong blockNum in tmpSolvedBlocks) { Block b = Node.blockChain.getBlock(blockNum, false, false); if (b == null || b.powField != null) { solvedBlocks.Remove(blockNum); } } } Block candidate_block = null; List <Block> blockList = null; int block_offset = 1; if (Node.blockChain.Count > (long)ConsensusConfig.getRedactedWindowSize()) { block_offset = 1000; } if (searchMode == BlockSearchMode.lowestDifficulty) { blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => x.difficulty).ToList(); } else if (searchMode == BlockSearchMode.randomLowestDifficulty) { Random rnd = new Random(); blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => x.difficulty).Skip(rnd.Next(500)).ToList(); } else if (searchMode == BlockSearchMode.latestBlock) { blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderByDescending(x => x.blockNum).ToList(); } else if (searchMode == BlockSearchMode.random) { Random rnd = new Random(); blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => rnd.Next()).ToList(); } // Check if the block list exists if (blockList == null) { Logging.error("No block list found while searching. Likely an incorrect miner block search mode."); return; } // Go through each block in the list foreach (Block block in blockList) { if (block.powField == null) { ulong solved = 0; lock (solvedBlocks) { solved = solvedBlocks.Find(x => x == block.blockNum); } // Check if this block is in the solved list if (solved > 0) { // Do nothing at this point } else { // Block is not solved, select it candidate_block = block; break; } } } if (candidate_block == null) { // No blocks with empty PoW field found, wait a bit Thread.Sleep(1000); return; } currentBlockNum = candidate_block.blockNum; currentBlockDifficulty = candidate_block.difficulty; currentBlockVersion = candidate_block.version; currentHashCeil = getHashCeilFromDifficulty(currentBlockDifficulty); activeBlock = candidate_block; byte[] block_checksum = activeBlock.blockChecksum; byte[] solver_address = Node.walletStorage.getPrimaryAddress(); activeBlockChallenge = new byte[block_checksum.Length + solver_address.Length]; System.Buffer.BlockCopy(block_checksum, 0, activeBlockChallenge, 0, block_checksum.Length); System.Buffer.BlockCopy(solver_address, 0, activeBlockChallenge, block_checksum.Length, solver_address.Length); blockFound = true; return; }
// Static function used by the getMiningBlock API call public static Block getMiningBlock(BlockSearchMode searchMode) { Block candidate_block = null; lock (activePoolBlockLock) { if (activePoolBlock != null) { return(activePoolBlock); } } List <Block> blockList = null; int block_offset = 1; if (Node.blockChain.Count >= (long)ConsensusConfig.getRedactedWindowSize()) { block_offset = 1000; } if (searchMode == BlockSearchMode.lowestDifficulty) { blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => x.difficulty).ToList(); } else if (searchMode == BlockSearchMode.randomLowestDifficulty) { Random rnd = new Random(); blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => x.difficulty).Skip(rnd.Next(200)).ToList(); } else if (searchMode == BlockSearchMode.latestBlock) { blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderByDescending(x => x.blockNum).ToList(); } else if (searchMode == BlockSearchMode.random) { Random rnd = new Random(); blockList = Node.blockChain.getBlocks(block_offset, (int)Node.blockChain.Count - block_offset).Where(x => x.powField == null).OrderBy(x => rnd.Next()).ToList(); } // Check if the block list exists if (blockList == null) { Logging.error("No block list found while searching."); return(null); } // Go through each block in the list foreach (Block block in blockList) { if (block.powField == null) { ulong solved = 0; lock (solvedBlocks) { solved = solvedBlocks.Find(x => x == block.blockNum); } // Check if this block is in the solved list if (solved > 0) { // Do nothing at this point } else { // Block is not solved, select it candidate_block = block; if (searchMode == BlockSearchMode.random || searchMode == BlockSearchMode.randomLowestDifficulty) { lock (activePoolBlockLock) { activePoolBlock = candidate_block; } } break; } } } return(candidate_block); }