private std::error_code preprocessOutputs(TransactionBlockInfo blockInfo, ITransactionReader tx, PreprocessInfo info)
        {
            Dictionary <PublicKey, List <uint> > outputs = new Dictionary <PublicKey, List <uint> >();

            try
            {
                GlobalMembers.findMyOutputs(tx, m_viewSecret, m_spendKeys, outputs);
            }
            catch (System.Exception e)
            {
                m_logger.functorMethod(WARNING, BRIGHT_RED) << "Failed to process transaction: " << e.Message << ", transaction hash " << Common.GlobalMembers.podToHex(tx.GetTransactionHash());
                return(std::error_code());
            }

            if (outputs.Count == 0)
            {
                return(std::error_code());
            }

            std::error_code errorCode = new std::error_code();
            var             txHash    = tx.GetTransactionHash();

            if (blockInfo.height != GlobalMembers.WALLET_UNCONFIRMED_TRANSACTION_HEIGHT)
            {
//C++ TO C# CONVERTER TODO TASK: There is no equivalent to 'reinterpret_cast' in C#:
//C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created:
//ORIGINAL LINE: errorCode = getGlobalIndices(reinterpret_cast<const Hash&>(txHash), info.globalIdxs);
                errorCode.CopyFrom(getGlobalIndices(reinterpret_cast <const Hash&>(txHash), info.globalIdxs));
                if (errorCode != null)
                {
                    return(errorCode);
                }
            }

            foreach (var kv in outputs)
            {
                var it = m_subscriptions.find(kv.first);
//C++ TO C# CONVERTER TODO TASK: Iterators are only converted within the context of 'while' and 'for' loops:
                if (it != m_subscriptions.end())
                {
                    auto transfers = info.outputs[kv.first];
//C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created:
//ORIGINAL LINE: errorCode = createTransfers(it->second->getKeys(), blockInfo, tx, kv.second, info.globalIdxs, transfers, m_logger);
//C++ TO C# CONVERTER TODO TASK: Iterators are only converted within the context of 'while' and 'for' loops:
                    errorCode.CopyFrom(CryptoNote.GlobalMembers.createTransfers(it.second.getKeys(), blockInfo, tx, kv.second, info.globalIdxs, transfers, m_logger.functorMethod));
                    if (errorCode != null)
                    {
                        return(errorCode);
                    }
                }
            }

            return(std::error_code());
        }
        public override std::error_code onPoolUpdated(List <std::unique_ptr <ITransactionReader> > addedTransactions, List <Hash> deletedTransactions)
        {
            TransactionBlockInfo unconfirmedBlockInfo = new TransactionBlockInfo();

            unconfirmedBlockInfo.timestamp = 0;
            unconfirmedBlockInfo.height    = GlobalMembers.WALLET_UNCONFIRMED_TRANSACTION_HEIGHT;

            std::error_code processingError = new std::error_code();

            foreach (var cryptonoteTransaction in addedTransactions)
            {
                m_poolTxs.emplace(cryptonoteTransaction.getTransactionHash());
//C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created:
//ORIGINAL LINE: processingError = processTransaction(unconfirmedBlockInfo, *cryptonoteTransaction.get());
                processingError.CopyFrom(processTransaction(unconfirmedBlockInfo, *cryptonoteTransaction.get()));
                if (processingError != null)
                {
                    foreach (var sub in m_subscriptions)
                    {
                        sub.second.onError(processingError, GlobalMembers.WALLET_UNCONFIRMED_TRANSACTION_HEIGHT);
                    }

                    return(processingError);
                }
            }

            foreach (var deletedTxHash in deletedTransactions)
            {
                m_poolTxs.erase(deletedTxHash);

                m_observerManager.notify(IBlockchainConsumerObserver.onTransactionDeleteBegin, this, deletedTxHash);
                foreach (var sub in m_subscriptions)
                {
//C++ TO C# CONVERTER TODO TASK: There is no equivalent to 'reinterpret_cast' in C#:
                    sub.second.deleteUnconfirmedTransaction(*reinterpret_cast <const Hash>(deletedTxHash));
                }

                m_observerManager.notify(IBlockchainConsumerObserver.onTransactionDeleteEnd, this, deletedTxHash);
            }

            return(std::error_code());
        }
        public override uint onNewBlocks(CompleteBlock[] blocks, uint startHeight, uint count)
        {
            Debug.Assert(blocks);
            Debug.Assert(count > 0);

//C++ TO C# CONVERTER TODO TASK: C# does not allow declaring types within methods:
//	struct Tx
//	{
//	  TransactionBlockInfo blockInfo;
//	  const ITransactionReader* tx;
//	  bool isLastTransactionInBlock;
//	};

//C++ TO C# CONVERTER TODO TASK: C# does not allow declaring types within methods:
//	struct PreprocessedTx : Tx, PreprocessInfo
//	{
//	};

            List <PreprocessedTx> preprocessedTransactions = new List <PreprocessedTx>();
            object preprocessedTransactionsMutex           = new object();

            uint workers = std::thread.hardware_concurrency();

            if (workers == 0)
            {
                workers = 2;
            }

            BlockingQueue <Tx> inputQueue = new BlockingQueue <Tx>(workers * 2);

            std::atomic <bool> stopProcessing  = new std::atomic <bool>(false);
            std::atomic <uint> emptyBlockCount = new std::atomic <uint>(0);

//C++ TO C# CONVERTER TODO TASK: Lambda expressions cannot be assigned to 'var':
            var pushingThread = std::async(std::launch.async, () =>
            {
                for (uint i = 0; i < count && stopProcessing == null; ++i)
                {
                    auto block = blocks[i].block;

                    if (!block.is_initialized())
                    {
                        ++emptyBlockCount;
                        continue;
                    }

                    // filter by syncStartTimestamp
                    if (m_syncStart.timestamp != 0 && block.timestamp < m_syncStart.timestamp)
                    {
                        ++emptyBlockCount;
                        continue;
                    }

                    TransactionBlockInfo blockInfo = new TransactionBlockInfo();
                    blockInfo.height           = startHeight + i;
                    blockInfo.timestamp        = block.timestamp;
                    blockInfo.transactionIndex = 0; // position in block

                    foreach (var tx in blocks[i].transactions)
                    {
                        var pubKey = tx.GetTransactionPublicKey();
                        if (pubKey == NULL_PUBLIC_KEY)
                        {
                            ++blockInfo.transactionIndex;
                            continue;
                        }

                        bool isLastTransactionInBlock = blockInfo.transactionIndex + 1 == blocks[i].transactions.size();
                        Tx item = new Tx(blockInfo, tx.get(), isLastTransactionInBlock);
                        inputQueue.push(new Tx(item));
                        ++blockInfo.transactionIndex;
                    }
                }

                inputQueue.close();
            });

//C++ TO C# CONVERTER TODO TASK: Lambda expressions cannot be assigned to 'var':
            var processingFunction = () =>
            {
                Tx item            = new Tx();
                std::error_code ec = new std::error_code();
                while (stopProcessing == null && inputQueue.pop(ref item))
                {
                    PreprocessedTx output = new PreprocessedTx();
                    (Tx)output = item;

                    ec.CopyFrom(preprocessOutputs(item.blockInfo, *item.tx, output));
                    if (ec != null)
                    {
                        stopProcessing = true;
                        break;
                    }

                    lock (preprocessedTransactionsMutex)
                    {
                        preprocessedTransactions.Add(std::move(output));
                    }
                }
                return(ec);
            };

            List <std::future <std::error_code> > processingThreads = new List <std::future <std::error_code> >();

            for (uint i = 0; i < workers; ++i)
            {
                processingThreads.Add(std::async(std::launch.async, processingFunction));
            }

            std::error_code processingError = new std::error_code();

            foreach (var f in processingThreads)
            {
                try
                {
                    std::error_code ec = f.get();
                    if (processingError == null && ec != null)
                    {
//C++ TO C# CONVERTER TODO TASK: The following line was determined to be a copy assignment (rather than a reference assignment) - this should be verified and a 'CopyFrom' method should be created:
//ORIGINAL LINE: processingError = ec;
                        processingError.CopyFrom(ec);
                    }
                }
                catch (std::system_error e)
                {
                    processingError = e.code();
                }
                catch (System.Exception)
                {
                    processingError = std::make_error_code(std::errc.operation_canceled);
                }
            }

            if (processingError != null)
            {
                forEachSubscription((TransfersSubscription sub) =>
                {
                    sub.onError(processingError, startHeight);
                });

                return(0);
            }

//C++ TO C# CONVERTER TODO TASK: The following line was determined to contain a copy constructor call - this should be verified and a copy constructor should be created:
//ORIGINAL LINE: ClassicVector<Crypto::Hash> blockHashes = getBlockHashes(blocks, count);
            List <Crypto.Hash> blockHashes = GlobalMembers.getBlockHashes(new CryptoNote.CompleteBlock(blocks), count);

            m_observerManager.notify(IBlockchainConsumerObserver.onBlocksAdded, this, blockHashes);

            // sort by block height and transaction index in block
//C++ TO C# CONVERTER TODO TASK: The 'Compare' parameter of std::sort produces a boolean value, while the .NET Comparison parameter produces a tri-state result:
//ORIGINAL LINE: std::sort(preprocessedTransactions.begin(), preprocessedTransactions.end(), [](const PreprocessedTx& a, const PreprocessedTx& b)
            preprocessedTransactions.Sort((PreprocessedTx a, PreprocessedTx b) =>
            {
                return(std::tie(a.blockInfo.height, a.blockInfo.transactionIndex) < std::tie(b.blockInfo.height, b.blockInfo.transactionIndex));
            });

            uint processedBlockCount = (uint)emptyBlockCount;

            try
            {
                foreach (var tx in preprocessedTransactions)
                {
                    processTransaction(tx.blockInfo, *tx.tx, tx);

                    if (tx.isLastTransactionInBlock)
                    {
                        ++processedBlockCount;
                        m_logger.functorMethod(TRACE) << "Processed block " << (int)processedBlockCount << " of " << (int)count << ", last processed block index " << tx.blockInfo.height << ", hash " << blocks[processedBlockCount - 1].blockHash;

                        var newHeight = startHeight + processedBlockCount - 1;
//C++ TO C# CONVERTER TODO TASK: Only lambda expressions having all locals passed by reference can be converted to C#:
//ORIGINAL LINE: forEachSubscription([newHeight](TransfersSubscription& sub)
                        forEachSubscription((TransfersSubscription sub) =>
                        {
                            sub.advanceHeight(newHeight);
                        });
                    }
                }
            }
            catch (MarkTransactionConfirmedException e)
            {
                m_logger.functorMethod(ERROR, BRIGHT_RED) << "Failed to process block transactions: failed to confirm transaction " << e.getTxHash() << ", remove this transaction from all containers and transaction pool";
                forEachSubscription((TransfersSubscription sub) =>
                {
                    sub.deleteUnconfirmedTransaction(e.getTxHash());
                });

                m_poolTxs.erase(e.getTxHash());
            }
            catch (System.Exception e)
            {
                m_logger.functorMethod(ERROR, BRIGHT_RED) << "Failed to process block transactions, exception: " << e.Message;
            }
            catch
            {
                m_logger.functorMethod(ERROR, BRIGHT_RED) << "Failed to process block transactions, unknown exception";
            }

            if (processedBlockCount < count)
            {
                uint detachIndex = startHeight + processedBlockCount;
                m_logger.functorMethod(ERROR, BRIGHT_RED) << "Not all block transactions are processed, fully processed block count: " << (int)processedBlockCount << " of " << (int)count << ", last processed block hash " << (processedBlockCount > 0 ? blocks[processedBlockCount - 1].blockHash : GlobalMembers.NULL_HASH) << ", detach block index " << (int)detachIndex << " to remove partially processed block";
//C++ TO C# CONVERTER TODO TASK: Only lambda expressions having all locals passed by reference can be converted to C#:
//ORIGINAL LINE: forEachSubscription([detachIndex](TransfersSubscription& sub)
                forEachSubscription((TransfersSubscription sub) =>
                {
                    sub.onBlockchainDetach(detachIndex);
                });
            }

            return(processedBlockCount);
        }