void TransfersConsumer::processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx,
  const std::vector<TransactionOutputInformationIn>& transfers, const std::vector<uint32_t>& globalIdxs, bool& contains, bool& updated) {

  TransactionInformation subscribtionTxInfo;
  contains = sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo);
  updated = false;

  if (contains) {
    if (subscribtionTxInfo.blockHeight == WALLET_UNCONFIRMED_TRANSACTION_HEIGHT && blockInfo.height != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) {
      try {
        // pool->blockchain
        sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs);
        updated = true;
      } catch (...) {
          m_logger(ERROR, BRIGHT_RED) << "markTransactionConfirmed failed, throw MarkTransactionConfirmedException";
          throw MarkTransactionConfirmedException(tx.getTransactionHash());
      }
    } else {
      assert(subscribtionTxInfo.blockHeight == blockInfo.height);
    }
  } else {
    updated = sub.addTransaction(blockInfo, tx, transfers);
    contains = updated;
  }
}
void TransfersConsumer::processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info) {
  std::vector<TransactionOutputInformationIn> emptyOutputs;
  std::vector<ITransfersContainer*> transactionContainers;

  m_logger(TRACE) << "Process transaction, block " << blockInfo.height << ", transaction index " << blockInfo.transactionIndex << ", hash " << tx.getTransactionHash();
  bool someContainerUpdated = false;
  for (auto& kv : m_subscriptions) {
    auto it = info.outputs.find(kv.first);
    auto& subscriptionOutputs = (it == info.outputs.end()) ? emptyOutputs : it->second;

    bool containerContainsTx;
    bool containerUpdated;
    processOutputs(blockInfo, *kv.second, tx, subscriptionOutputs, info.globalIdxs, containerContainsTx, containerUpdated);
    someContainerUpdated = someContainerUpdated || containerUpdated;
    if (containerContainsTx) {
      transactionContainers.emplace_back(&kv.second->getContainer());
    }
  }

  if (someContainerUpdated) {
    m_logger(TRACE) << "Transaction updated some containers, hash " << tx.getTransactionHash();
    m_observerManager.notify(&IBlockchainConsumerObserver::onTransactionUpdated, this, tx.getTransactionHash(), transactionContainers);
  } else {
    m_logger(TRACE) << "Transaction doesn't updated any container, hash " << tx.getTransactionHash();
  }
}
std::error_code createTransfers(
  const AccountKeys& account,
  const TransactionBlockInfo& blockInfo,
  const ITransactionReader& tx,
  const std::vector<uint32_t>& outputs,
  const std::vector<uint32_t>& globalIdxs,
  std::vector<TransactionOutputInformationIn>& transfers) {

  auto txPubKey = tx.getTransactionPublicKey();

  for (auto idx : outputs) {

    if (idx >= tx.getOutputCount()) {
      return std::make_error_code(std::errc::argument_out_of_domain);
    }

    auto outType = tx.getOutputType(size_t(idx));

    if (
      outType != TransactionTypes::OutputType::Key) {
      continue;
    }

    TransactionOutputInformationIn info;

    info.type = outType;
    info.transactionPublicKey = txPubKey;
    info.outputInTransaction = idx;
    info.globalOutputIndex = (blockInfo.height == WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) ?
      UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX : globalIdxs[idx];

    if (outType == TransactionTypes::OutputType::Key) {
      uint64_t amount;
      KeyOutput out;
      tx.getOutput(idx, out, amount);

      CryptoNote::KeyPair in_ephemeral;
      CryptoNote::generate_key_image_helper(
        account,
        txPubKey,
        idx,
        in_ephemeral,
        info.keyImage);

      assert(out.key == reinterpret_cast<const PublicKey&>(in_ephemeral.publicKey));

      info.amount = amount;
      info.outputKey = out.key;

    }

    transfers.push_back(info);
  }

  return std::error_code();
}
std::error_code TransfersConsumer::preprocessOutputs(const BlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info) {
  std::unordered_map<PublicKey, std::vector<uint32_t>> outputs;
  findMyOutputs(tx, m_viewSecret, m_spendKeys, outputs);

  if (outputs.empty()) {
    return std::error_code();
  }

  std::error_code errorCode;
  auto txHash = tx.getTransactionHash();
  if (blockInfo.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
    errorCode = getGlobalIndices(reinterpret_cast<const Hash&>(txHash), info.globalIdxs);
    if (errorCode) {
      return errorCode;
    }
  }

  for (const auto& kv : outputs) {
    auto it = m_subscriptions.find(kv.first);
    if (it != m_subscriptions.end()) {
      auto& transfers = info.outputs[kv.first];
      errorCode = createTransfers(it->second->getKeys(), blockInfo, tx, kv.second, info.globalIdxs, transfers);
      if (errorCode) {
        return errorCode;
      }
    }
  }

  return std::error_code();
}
cryptonote::Transaction convertTx(ITransactionReader& tx) {
  auto blob = tx.getTransactionData();
  cryptonote::blobdata data(reinterpret_cast<const char*>(blob.data()), blob.size());
  cryptonote::Transaction oldTx;
  cryptonote::parse_and_validate_tx_from_blob(data, oldTx); // ignore return code
  return oldTx;
}
void TransfersSubscription::addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx,
                                           const std::vector<TransactionOutputInformationIn>& transfersList) {
  bool added = transfers.addTransaction(blockInfo, tx, transfersList);
  if (added) {
    m_observerManager.notify(&ITransfersObserver::onTransactionUpdated, this, tx.getTransactionHash());
  }
}
void TransfersSubscription::addTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx,
                                           const std::vector<TransactionOutputInformationIn>& transfers,
                                           std::vector<std::string>&& messages) {

  bool added = m_transfers.addTransaction(blockInfo, tx, transfers, std::move(messages));
  if (added) {
    m_observerManager.notify(&ITransfersObserver::onTransactionUpdated, this, tx.getTransactionHash());
  }
}
std::error_code submitTransaction(INode& node, ITransactionReader& tx) {
  auto data = tx.getTransactionData();

  CryptoNote::Transaction outTx;
  fromBinaryArray(outTx, data);


  LOG_DEBUG("Submitting transaction " + Common::toHex(tx.getTransactionHash().data, 32));

  std::promise<std::error_code> result;
  node.relayTransaction(outTx, [&result](std::error_code ec) { result.set_value(ec); });
  auto err = result.get_future().get();

  if (err) {
    LOG_DEBUG("Error: " + err.message());
  } else {
    LOG_DEBUG("Submitted successfully");
  }

  return err;
}
std::error_code submitTransaction(INode& node, ITransactionReader& tx) {
  auto data = tx.getTransactionData();

  cryptonote::blobdata txblob(data.data(), data.data() + data.size());
  cryptonote::Transaction outTx;
  cryptonote::parse_and_validate_tx_from_blob(txblob, outTx);

  LOG_DEBUG("Submitting transaction " + bin2str(tx.getTransactionHash()));

  std::promise<std::error_code> result;
  node.relayTransaction(outTx, [&result](std::error_code ec) { result.set_value(ec); });
  auto err = result.get_future().get();

  if (err) {
    LOG_DEBUG("Error: " + err.message());
  } else {
    LOG_DEBUG("Submitted successfully");
  }

  return err;
}
void TransfersConsumer::processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx,
  const std::vector<TransactionOutputInformationIn>& transfers, const std::vector<uint32_t>& globalIdxs, bool& contains, bool& updated) {

  TransactionInformation subscribtionTxInfo;
  contains = sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo);
  updated = false;

  if (contains) {
    if (subscribtionTxInfo.blockHeight == WALLET_UNCONFIRMED_TRANSACTION_HEIGHT && blockInfo.height != WALLET_UNCONFIRMED_TRANSACTION_HEIGHT) {
      // pool->blockchain
      sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs);
      updated = true;
    } else {
      assert(subscribtionTxInfo.blockHeight == blockInfo.height);
    }
  } else {
    auto messages = get_messages_from_extra(tx.getExtra(), tx.getTransactionPublicKey(), &sub.getKeys().spendSecretKey);
    updated = sub.addTransaction(blockInfo, tx, transfers, std::move(messages));
    contains = updated;
  }
}
std::error_code TransfersConsumer::processOutputs(const BlockInfo& blockInfo, TransfersSubscription& sub, 
  const ITransactionReader& tx, const std::vector<TransactionOutputInformationIn>& transfers, const std::vector<uint32_t>& globalIdxs) {

  if (blockInfo.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
    TransactionInformation subscribtionTxInfo;
    int64_t txBalance;
    if (sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo, txBalance)) {
      if (subscribtionTxInfo.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
        // pool->blockchain
        sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs);
        return std::error_code();
      } else {
        // - Subscription already has this transaction as confirmed, so why are we here?
        // - Because, for instance, some another subscription doesn't have this transactions, so it is given to us again.
        return std::error_code();
      }
    }
  }
  
  sub.addTransaction(blockInfo, tx, transfers);
  return std::error_code();
}
  std::error_code submitTransaction(ITransactionReader& tx) {
    auto data = tx.getTransactionData();

    cryptonote::blobdata txblob(data.data(), data.data() + data.size());
    cryptonote::Transaction outTx;
    cryptonote::parse_and_validate_tx_from_blob(txblob, outTx);

    std::promise<std::error_code> result;
    m_node.relayTransaction(outTx, [&result](std::error_code ec) {
      std::promise<std::error_code> detachedPromise = std::move(result);
      detachedPromise.set_value(ec);
    });
    return result.get_future().get();
  }
bool TransfersSubscription::addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx,
                                           const std::vector<TransactionOutputInformationIn>& transfersList,
                                           std::vector<std::string>&& messages) {
  std::vector<TransactionOutputInformation> unlockedTransfers;

  bool added = transfers.addTransaction(blockInfo, tx, transfersList, std::move(messages), &unlockedTransfers);
  if (added) {
    m_observerManager.notify(&ITransfersObserver::onTransactionUpdated, this, tx.getTransactionHash());
  }

  if (!unlockedTransfers.empty()) {
    m_observerManager.notify(&ITransfersObserver::onTransfersUnlocked, this, unlockedTransfers);
  }

  return added;
}
std::error_code TransfersConsumer::preprocessOutputs(const BlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info) {
  findMyOutputs(tx, m_viewSecret, m_spendKeys, info.outputs);

  std::error_code errorCode;
  if (!info.outputs.empty()) {
    auto txHash = tx.getTransactionHash();
    if (blockInfo.height != UNCONFIRMED_TRANSACTION_HEIGHT) {
      errorCode = getGlobalIndices(reinterpret_cast<const crypto::hash&>(txHash), info.globalIdxs);
      if (errorCode) {
        return errorCode;
      }
    }
  }

  return std::error_code();
}
std::error_code BlockchainSynchronizer::doAddUnconfirmedTransaction(const ITransactionReader& transaction) {
  std::unique_lock<std::mutex> lk(m_consumersMutex);

  std::error_code ec;
  auto addIt = m_consumers.begin();
  for (; addIt != m_consumers.end(); ++addIt) {
    ec = addIt->first->addUnconfirmedTransaction(transaction);
    if (ec) {
      break;
    }
  }

  if (ec) {
    auto transactionHash = transaction.getTransactionHash();
    for (auto rollbackIt = m_consumers.begin(); rollbackIt != addIt; ++rollbackIt) {
      rollbackIt->first->removeUnconfirmedTransaction(transactionHash);
    }
  }

  return ec;
}
std::error_code TransfersConsumer::processOutputs(const BlockInfo& blockInfo, TransfersSubscription& sub, 
  const ITransactionReader& tx, const std::vector<uint32_t>& outputs, const std::vector<uint64_t>& globalIdxs) {

  if (blockInfo.height != UNCONFIRMED_TRANSACTION_HEIGHT) {
    TransactionInformation subscribtionTxInfo;
    int64_t txBalance;
    if (sub.getContainer().getTransactionInformation(tx.getTransactionHash(), subscribtionTxInfo, txBalance)) {
      if (subscribtionTxInfo.blockHeight == UNCONFIRMED_TRANSACTION_HEIGHT) {
        // pool->blockchain
        sub.markTransactionConfirmed(blockInfo, tx.getTransactionHash(), globalIdxs);
        return std::error_code();
      } else {
        // - Subscription already has this transaction as confirmed, so why are we here?
        // - Because, for instance, some another subscription doesn't have this transactions, so it is given to us again.
        return std::error_code();
      }
    }
  }
  
  std::vector<TransactionOutputInformationIn> transfers;

  auto txPubKey = tx.getTransactionPublicKey();

  for (auto idx : outputs) {

    if (idx >= tx.getOutputCount()) {
      return std::make_error_code(std::errc::argument_out_of_domain);
    }

    auto outType = tx.getOutputType(size_t(idx));

    if (
      outType != TransactionTypes::OutputType::Key &&
      outType != TransactionTypes::OutputType::Multisignature) {
      continue;
    }

    TransactionOutputInformationIn info;

    info.type = outType;
    info.transactionPublicKey = txPubKey;
    info.outputInTransaction = idx;
    info.globalOutputIndex =
      (blockInfo.height == UNCONFIRMED_TRANSACTION_HEIGHT) ?
      UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX :
      globalIdxs[idx];

    if (outType == TransactionTypes::OutputType::Key) {
      TransactionTypes::OutputKey out;
      tx.getOutput(idx, out);

      cryptonote::KeyPair in_ephemeral;
      cryptonote::generate_key_image_helper(
        reinterpret_cast<const cryptonote::account_keys&>(sub.getKeys()),
        reinterpret_cast<const crypto::public_key&>(txPubKey),
        idx,
        in_ephemeral,
        reinterpret_cast<crypto::key_image&>(info.keyImage));

      assert(out.key == reinterpret_cast<const PublicKey&>(in_ephemeral.pub));

      info.amount = out.amount;
      info.outputKey = out.key;

    } else if (outType == TransactionTypes::OutputType::Multisignature) {
      TransactionTypes::OutputMultisignature out;
      tx.getOutput(idx, out);

      info.amount = out.amount;
      info.requiredSignatures = out.requiredSignatures;
    }

    transfers.push_back(info);
  }

  sub.addTransaction(blockInfo, tx, transfers);

  return std::error_code();
}