// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers
//
// This file is part of Bytecoin.
//
// Bytecoin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Bytecoin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Bytecoin. If not, see .
#include "TransfersContainer.h"
#include "IWalletLegacy.h"
#include "Common/StdInputStream.h"
#include "Common/StdOutputStream.h"
#include "CryptoNoteCore/CryptoNoteFormatUtils.h"
#include "Serialization/BinaryInputStreamSerializer.h"
#include "Serialization/BinaryOutputStreamSerializer.h"
#include "Serialization/SerializationOverloads.h"
using namespace Common;
using namespace Crypto;
namespace CryptoNote {
void serialize(TransactionInformation& ti, CryptoNote::ISerializer& s) {
s(ti.transactionHash, "");
s(ti.publicKey, "");
serializeBlockHeight(s, ti.blockHeight, "");
s(ti.timestamp, "");
s(ti.unlockTime, "");
s(ti.totalAmountIn, "");
s(ti.totalAmountOut, "");
s(ti.extra, "");
s(ti.paymentId, "");
}
const uint32_t TRANSFERS_CONTAINER_STORAGE_VERSION = 0;
namespace {
template
class TransferIteratorList {
public:
TransferIteratorList(const TIterator& begin, const TIterator& end) : m_end(end) {
for (auto it = begin; it != end; ++it) {
m_list.emplace_back(it);
}
}
TransferIteratorList(TransferIteratorList&& other) {
m_list = std::move(other.m_list);
m_end = std::move(other.m_end);
}
TransferIteratorList& operator=(TransferIteratorList&& other) {
m_list = std::move(other.m_list);
m_end = std::move(other.m_end);
return *this;
}
void sort() {
std::sort(m_list.begin(), m_list.end(), &TransferIteratorList::lessTIterator);
}
TIterator findFirstByAmount(uint64_t amount) const {
auto listIt = std::find_if(m_list.begin(), m_list.end(), [amount](const TIterator& it) {
return it->amount == amount;
});
return listIt == m_list.end() ? m_end : *listIt;
}
TIterator minElement() const {
auto listIt = std::min_element(m_list.begin(), m_list.end(), &TransferIteratorList::lessTIterator);
return listIt == m_list.end() ? m_end : *listIt;
}
private:
static bool lessTIterator(const TIterator& it1, const TIterator& it2) {
return
(it1->blockHeight < it2->blockHeight) ||
(it1->blockHeight == it2->blockHeight && it1->transactionIndex < it2->transactionIndex);
}
private:
std::vector m_list;
TIterator m_end;
};
template
TransferIteratorList createTransferIteratorList(const std::pair& itPair) {
return TransferIteratorList(itPair.first, itPair.second);
}
}
SpentOutputDescriptor::SpentOutputDescriptor() :
m_type(TransactionTypes::OutputType::Invalid) {
}
SpentOutputDescriptor::SpentOutputDescriptor(const TransactionOutputInformationIn& transactionInfo) :
m_type(transactionInfo.type),
m_amount(0),
m_globalOutputIndex(0) {
if (m_type == TransactionTypes::OutputType::Key) {
m_keyImage = &transactionInfo.keyImage;
} else if (m_type == TransactionTypes::OutputType::Multisignature) {
m_amount = transactionInfo.amount;
m_globalOutputIndex = transactionInfo.globalOutputIndex;
} else {
assert(false);
}
}
SpentOutputDescriptor::SpentOutputDescriptor(const KeyImage* keyImage) {
assign(keyImage);
}
SpentOutputDescriptor::SpentOutputDescriptor(uint64_t amount, uint32_t globalOutputIndex) {
assign(amount, globalOutputIndex);
}
void SpentOutputDescriptor::assign(const KeyImage* keyImage) {
m_type = TransactionTypes::OutputType::Key;
m_keyImage = keyImage;
}
void SpentOutputDescriptor::assign(uint64_t amount, uint32_t globalOutputIndex) {
m_type = TransactionTypes::OutputType::Multisignature;
m_amount = amount;
m_globalOutputIndex = globalOutputIndex;
}
bool SpentOutputDescriptor::isValid() const {
return m_type != TransactionTypes::OutputType::Invalid;
}
bool SpentOutputDescriptor::operator==(const SpentOutputDescriptor& other) const {
if (m_type == TransactionTypes::OutputType::Key) {
return other.m_type == m_type && *other.m_keyImage == *m_keyImage;
} else if (m_type == TransactionTypes::OutputType::Multisignature) {
return other.m_type == m_type && other.m_amount == m_amount && other.m_globalOutputIndex == m_globalOutputIndex;
} else {
assert(false);
return false;
}
}
size_t SpentOutputDescriptor::hash() const {
if (m_type == TransactionTypes::OutputType::Key) {
static_assert(sizeof(size_t) < sizeof(*m_keyImage), "sizeof(size_t) < sizeof(*m_keyImage)");
return *reinterpret_cast(m_keyImage->data);
} else if (m_type == TransactionTypes::OutputType::Multisignature) {
size_t hashValue = boost::hash_value(m_amount);
boost::hash_combine(hashValue, m_globalOutputIndex);
return hashValue;
} else {
assert(false);
return 0;
}
}
TransfersContainer::TransfersContainer(const Currency& currency, size_t transactionSpendableAge) :
m_currentHeight(0),
m_currency(currency),
m_transactionSpendableAge(transactionSpendableAge) {
}
bool TransfersContainer::addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx,
const std::vector& transfers) {
std::unique_lock lock(m_mutex);
if (block.height < m_currentHeight) {
throw std::invalid_argument("Cannot add transaction from block < m_currentHeight");
}
if (m_transactions.count(tx.getTransactionHash()) > 0) {
throw std::invalid_argument("Transaction is already added");
}
bool added = addTransactionOutputs(block, tx, transfers);
added |= addTransactionInputs(block, tx);
if (added) {
addTransaction(block, tx);
}
if (block.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
m_currentHeight = block.height;
}
return added;
}
/**
* \pre m_mutex is locked.
*/
void TransfersContainer::addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx) {
auto txHash = tx.getTransactionHash();
TransactionInformation txInfo;
txInfo.blockHeight = block.height;
txInfo.timestamp = block.timestamp;
txInfo.transactionHash = txHash;
txInfo.unlockTime = tx.getUnlockTime();
txInfo.publicKey = tx.getTransactionPublicKey();
txInfo.totalAmountIn = tx.getInputTotalAmount();
txInfo.totalAmountOut = tx.getOutputTotalAmount();
txInfo.extra = tx.getExtra();
if (!tx.getPaymentId(txInfo.paymentId)) {
txInfo.paymentId = NULL_HASH;
}
auto result = m_transactions.emplace(std::move(txInfo));
(void)result; // Disable unused warning
assert(result.second);
}
/**
* \pre m_mutex is locked.
*/
bool TransfersContainer::addTransactionOutputs(const TransactionBlockInfo& block, const ITransactionReader& tx,
const std::vector& transfers) {
bool outputsAdded = false;
auto txHash = tx.getTransactionHash();
bool transactionIsUnconfimed = (block.height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
for (const auto& transfer : transfers) {
assert(transfer.outputInTransaction < tx.getOutputCount());
assert(transfer.type == tx.getOutputType(transfer.outputInTransaction));
assert(transfer.amount > 0);
bool transferIsUnconfirmed = (transfer.globalOutputIndex == UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX);
if (transactionIsUnconfimed != transferIsUnconfirmed) {
throw std::invalid_argument("Bad transfer's globalOutputIndex");
}
TransactionOutputInformationEx info;
static_cast(info) = transfer;
info.blockHeight = block.height;
info.transactionIndex = block.transactionIndex;
info.unlockTime = tx.getUnlockTime();
info.transactionHash = txHash;
info.visible = true;
if (transferIsUnconfirmed) {
auto result = m_unconfirmedTransfers.emplace(std::move(info));
(void)result; // Disable unused warning
assert(result.second);
} else {
if (info.type == TransactionTypes::OutputType::Multisignature) {
SpentOutputDescriptor descriptor(transfer);
if (m_availableTransfers.get().count(descriptor) > 0 ||
m_spentTransfers.get().count(descriptor) > 0) {
throw std::runtime_error("Transfer already exists");
}
}
auto result = m_availableTransfers.emplace(std::move(info));
(void)result; // Disable unused warning
assert(result.second);
}
if (info.type == TransactionTypes::OutputType::Key) {
updateTransfersVisibility(info.keyImage);
}
outputsAdded = true;
}
return outputsAdded;
}
/**
* \pre m_mutex is locked.
*/
bool TransfersContainer::addTransactionInputs(const TransactionBlockInfo& block, const ITransactionReader& tx) {
bool inputsAdded = false;
for (size_t i = 0; i < tx.getInputCount(); ++i) {
auto inputType = tx.getInputType(i);
if (inputType == TransactionTypes::InputType::Key) {
KeyInput input;
tx.getInput(i, input);
SpentOutputDescriptor descriptor(&input.keyImage);
auto spentRange = m_spentTransfers.get().equal_range(descriptor);
if (std::distance(spentRange.first, spentRange.second) > 0) {
throw std::runtime_error("Spending already spent transfer");
}
auto availableRange = m_availableTransfers.get().equal_range(descriptor);
auto unconfirmedRange = m_unconfirmedTransfers.get().equal_range(descriptor);
size_t availableCount = std::distance(availableRange.first, availableRange.second);
size_t unconfirmedCount = std::distance(unconfirmedRange.first, unconfirmedRange.second);
if (availableCount == 0) {
if (unconfirmedCount > 0) {
throw std::runtime_error("Spending unconfirmed transfer");
} else {
// This input doesn't spend any transfer from this container
continue;
}
}
auto& outputDescriptorIndex = m_availableTransfers.get();
auto availableOutputsRange = outputDescriptorIndex.equal_range(SpentOutputDescriptor(&input.keyImage));
auto iteratorList = createTransferIteratorList(availableOutputsRange);
iteratorList.sort();
auto spendingTransferIt = iteratorList.findFirstByAmount(input.amount);
if (spendingTransferIt == availableOutputsRange.second) {
throw std::runtime_error("Input has invalid amount, corresponding output isn't found");
}
assert(spendingTransferIt->keyImage == input.keyImage);
copyToSpent(block, tx, i, *spendingTransferIt);
// erase from available outputs
outputDescriptorIndex.erase(spendingTransferIt);
updateTransfersVisibility(input.keyImage);
inputsAdded = true;
} else if (inputType == TransactionTypes::InputType::Multisignature) {
MultisignatureInput input;
tx.getInput(i, input);
auto& outputDescriptorIndex = m_availableTransfers.get();
auto availableOutputIt = outputDescriptorIndex.find(SpentOutputDescriptor(input.amount, input.outputIndex));
if (availableOutputIt != outputDescriptorIndex.end()) {
copyToSpent(block, tx, i, *availableOutputIt);
// erase from available outputs
outputDescriptorIndex.erase(availableOutputIt);
inputsAdded = true;
}
} else {
assert(inputType == TransactionTypes::InputType::Generating);
}
}
return inputsAdded;
}
bool TransfersContainer::deleteUnconfirmedTransaction(const Hash& transactionHash) {
std::unique_lock lock(m_mutex);
auto it = m_transactions.find(transactionHash);
if (it == m_transactions.end()) {
return false;
} else if (it->blockHeight != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
return false;
} else {
deleteTransactionTransfers(it->transactionHash);
m_transactions.erase(it);
return true;
}
}
bool TransfersContainer::markTransactionConfirmed(const TransactionBlockInfo& block, const Hash& transactionHash,
const std::vector& globalIndices) {
if (block.height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
throw std::invalid_argument("Block height equals WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT");
}
std::unique_lock lock(m_mutex);
auto transactionIt = m_transactions.find(transactionHash);
if (transactionIt == m_transactions.end()) {
return false;
}
if (transactionIt->blockHeight != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
return false;
}
auto txInfo = *transactionIt;
txInfo.blockHeight = block.height;
txInfo.timestamp = block.timestamp;
m_transactions.replace(transactionIt, txInfo);
auto availableRange = m_unconfirmedTransfers.get().equal_range(transactionHash);
for (auto transferIt = availableRange.first; transferIt != availableRange.second; ) {
auto transfer = *transferIt;
assert(transfer.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
assert(transfer.globalOutputIndex == UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX);
if (transfer.outputInTransaction >= globalIndices.size()) {
throw std::invalid_argument("Not enough elements in globalIndices");
}
transfer.blockHeight = block.height;
transfer.transactionIndex = block.transactionIndex;
transfer.globalOutputIndex = globalIndices[transfer.outputInTransaction];
if (transfer.type == TransactionTypes::OutputType::Multisignature) {
SpentOutputDescriptor descriptor(transfer);
if (m_availableTransfers.get().count(descriptor) > 0 ||
m_spentTransfers.get().count(descriptor) > 0) {
// This exception breaks TransfersContainer consistency
throw std::runtime_error("Transfer already exists");
}
}
auto result = m_availableTransfers.emplace(std::move(transfer));
(void)result; // Disable unused warning
assert(result.second);
transferIt = m_unconfirmedTransfers.get().erase(transferIt);
if (transfer.type == TransactionTypes::OutputType::Key) {
updateTransfersVisibility(transfer.keyImage);
}
}
auto& spendingTransactionIndex = m_spentTransfers.get();
auto spentRange = spendingTransactionIndex.equal_range(transactionHash);
for (auto transferIt = spentRange.first; transferIt != spentRange.second; ++transferIt) {
auto transfer = *transferIt;
assert(transfer.spendingBlock.height == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
transfer.spendingBlock = block;
spendingTransactionIndex.replace(transferIt, transfer);
}
return true;
}
/**
* \pre m_mutex is locked.
*/
void TransfersContainer::deleteTransactionTransfers(const Hash& transactionHash) {
auto& spendingTransactionIndex = m_spentTransfers.get();
auto spentTransfersRange = spendingTransactionIndex.equal_range(transactionHash);
for (auto it = spentTransfersRange.first; it != spentTransfersRange.second;) {
assert(it->blockHeight != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
assert(it->globalOutputIndex != UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX);
auto result = m_availableTransfers.emplace(static_cast(*it));
assert(result.second);
it = spendingTransactionIndex.erase(it);
if (result.first->type == TransactionTypes::OutputType::Key) {
updateTransfersVisibility(result.first->keyImage);
}
}
auto unconfirmedTransfersRange = m_unconfirmedTransfers.get().equal_range(transactionHash);
for (auto it = unconfirmedTransfersRange.first; it != unconfirmedTransfersRange.second;) {
if (it->type == TransactionTypes::OutputType::Key) {
KeyImage keyImage = it->keyImage;
it = m_unconfirmedTransfers.get().erase(it);
updateTransfersVisibility(keyImage);
} else {
it = m_unconfirmedTransfers.get().erase(it);
}
}
auto& transactionTransfersIndex = m_availableTransfers.get();
auto transactionTransfersRange = transactionTransfersIndex.equal_range(transactionHash);
for (auto it = transactionTransfersRange.first; it != transactionTransfersRange.second;) {
if (it->type == TransactionTypes::OutputType::Key) {
KeyImage keyImage = it->keyImage;
it = transactionTransfersIndex.erase(it);
updateTransfersVisibility(keyImage);
} else {
it = transactionTransfersIndex.erase(it);
}
}
}
/**
* \pre m_mutex is locked.
*/
void TransfersContainer::copyToSpent(const TransactionBlockInfo& block, const ITransactionReader& tx, size_t inputIndex,
const TransactionOutputInformationEx& output) {
assert(output.blockHeight != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
assert(output.globalOutputIndex != UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX);
SpentTransactionOutput spentOutput;
static_cast(spentOutput) = output;
spentOutput.spendingBlock = block;
spentOutput.spendingTransactionHash = tx.getTransactionHash();
spentOutput.inputInTransaction = static_cast(inputIndex);
auto result = m_spentTransfers.emplace(std::move(spentOutput));
(void)result; // Disable unused warning
assert(result.second);
}
std::vector TransfersContainer::detach(uint32_t height) {
// This method expects that WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT is a big positive number
assert(height < WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT);
std::lock_guard lk(m_mutex);
std::vector deletedTransactions;
auto& spendingTransactionIndex = m_spentTransfers.get();
auto& blockHeightIndex = m_transactions.get<1>();
auto it = blockHeightIndex.end();
while (it != blockHeightIndex.begin()) {
--it;
bool doDelete = false;
if (it->blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
auto range = spendingTransactionIndex.equal_range(it->transactionHash);
for (auto spentTransferIt = range.first; spentTransferIt != range.second; ++spentTransferIt) {
if (spentTransferIt->blockHeight >= height) {
doDelete = true;
break;
}
}
} else if (it->blockHeight >= height) {
doDelete = true;
} else {
break;
}
if (doDelete) {
deleteTransactionTransfers(it->transactionHash);
deletedTransactions.emplace_back(it->transactionHash);
it = blockHeightIndex.erase(it);
}
}
// TODO: notification on detach
m_currentHeight = height == 0 ? 0 : height - 1;
return deletedTransactions;
}
namespace {
template
void updateVisibility(C& collection, const T& range, bool visible) {
for (auto it = range.first; it != range.second; ++it) {
auto updated = *it;
updated.visible = visible;
collection.replace(it, updated);
}
}
}
/**
* \pre m_mutex is locked.
*/
void TransfersContainer::updateTransfersVisibility(const KeyImage& keyImage) {
auto& unconfirmedIndex = m_unconfirmedTransfers.get();
auto& availableIndex = m_availableTransfers.get();
auto& spentIndex = m_spentTransfers.get();
SpentOutputDescriptor descriptor(&keyImage);
auto unconfirmedRange = unconfirmedIndex.equal_range(descriptor);
auto availableRange = availableIndex.equal_range(descriptor);
auto spentRange = spentIndex.equal_range(descriptor);
size_t unconfirmedCount = std::distance(unconfirmedRange.first, unconfirmedRange.second);
size_t availableCount = std::distance(availableRange.first, availableRange.second);
size_t spentCount = std::distance(spentRange.first, spentRange.second);
assert(spentCount == 0 || spentCount == 1);
if (spentCount > 0) {
updateVisibility(unconfirmedIndex, unconfirmedRange, false);
updateVisibility(availableIndex, availableRange, false);
updateVisibility(spentIndex, spentRange, true);
} else if (availableCount > 0) {
updateVisibility(unconfirmedIndex, unconfirmedRange, false);
updateVisibility(availableIndex, availableRange, false);
auto iteratorList = createTransferIteratorList(availableRange);
auto earliestTransferIt = iteratorList.minElement();
assert(earliestTransferIt != availableRange.second);
auto earliestTransfer = *earliestTransferIt;
earliestTransfer.visible = true;
availableIndex.replace(earliestTransferIt, earliestTransfer);
} else {
updateVisibility(unconfirmedIndex, unconfirmedRange, unconfirmedCount == 1);
}
}
bool TransfersContainer::advanceHeight(uint32_t height) {
std::lock_guard lk(m_mutex);
if (m_currentHeight <= height) {
m_currentHeight = height;
return true;
}
return false;
}
size_t TransfersContainer::transfersCount() {
std::lock_guard lk(m_mutex);
return m_unconfirmedTransfers.size() + m_availableTransfers.size() + m_spentTransfers.size();
}
size_t TransfersContainer::transactionsCount() {
std::lock_guard lk(m_mutex);
return m_transactions.size();
}
uint64_t TransfersContainer::balance(uint32_t flags) {
std::lock_guard lk(m_mutex);
uint64_t amount = 0;
for (const auto& t : m_availableTransfers) {
if (t.visible && isIncluded(t, flags)) {
amount += t.amount;
}
}
if ((flags & IncludeStateLocked) != 0) {
for (const auto& t : m_unconfirmedTransfers) {
if (t.visible && isIncluded(t.type, IncludeStateLocked, flags)) {
amount += t.amount;
}
}
}
return amount;
}
void TransfersContainer::getOutputs(std::vector& transfers, uint32_t flags) {
std::lock_guard lk(m_mutex);
for (const auto& t : m_availableTransfers) {
if (t.visible && isIncluded(t, flags)) {
transfers.push_back(t);
}
}
if ((flags & IncludeStateLocked) != 0) {
for (const auto& t : m_unconfirmedTransfers) {
if (t.visible && isIncluded(t.type, IncludeStateLocked, flags)) {
transfers.push_back(t);
}
}
}
}
bool TransfersContainer::getTransactionInformation(const Hash& transactionHash, TransactionInformation& info, int64_t& txBalance) {
std::lock_guard lk(m_mutex);
auto it = m_transactions.find(transactionHash);
if (it == m_transactions.end()) {
return false;
}
info = *it;
int64_t amountOut = 0;
if (info.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
auto unconfirmedOutputsRange = m_unconfirmedTransfers.get().equal_range(transactionHash);
for (auto it = unconfirmedOutputsRange.first; it != unconfirmedOutputsRange.second; ++it) {
amountOut += static_cast(it->amount);
}
} else {
auto availableOutputsRange = m_availableTransfers.get().equal_range(transactionHash);
for (auto it = availableOutputsRange.first; it != availableOutputsRange.second; ++it) {
amountOut += static_cast(it->amount);
}
auto spentOutputsRange = m_spentTransfers.get().equal_range(transactionHash);
for (auto it = spentOutputsRange.first; it != spentOutputsRange.second; ++it) {
amountOut += static_cast(it->amount);
}
}
int64_t amountIn = 0;
auto rangeInputs = m_spentTransfers.get().equal_range(transactionHash);
for (auto it = rangeInputs.first; it != rangeInputs.second; ++it) {
amountIn += static_cast(it->amount);
}
txBalance = amountOut - amountIn;
return true;
}
std::vector TransfersContainer::getTransactionOutputs(const Hash& transactionHash,
uint32_t flags) {
std::lock_guard lk(m_mutex);
std::vector result;
auto availableRange = m_availableTransfers.get().equal_range(transactionHash);
for (auto i = availableRange.first; i != availableRange.second; ++i) {
const auto& t = *i;
if (isIncluded(t, flags)) {
result.push_back(t);
}
}
if ((flags & IncludeStateLocked) != 0) {
auto unconfirmedRange = m_unconfirmedTransfers.get().equal_range(transactionHash);
for (auto i = unconfirmedRange.first; i != unconfirmedRange.second; ++i) {
if (isIncluded(i->type, IncludeStateLocked, flags)) {
result.push_back(*i);
}
}
}
return result;
}
void TransfersContainer::getUnconfirmedTransactions(std::vector& transactions) {
std::lock_guard lk(m_mutex);
transactions.clear();
for (auto& element : m_transactions) {
if (element.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) {
transactions.push_back(*reinterpret_cast(&element.transactionHash));
}
}
}
std::vector TransfersContainer::getSpentOutputs() {
std::lock_guard lk(m_mutex);
std::vector spentOutputs;
spentOutputs.reserve(m_spentTransfers.size());
for (const auto& o : m_spentTransfers) {
TransactionSpentOutputInformation spentOutput;
static_cast(spentOutput) = o;
spentOutput.spendingBlockHeight = o.spendingBlock.height;
spentOutput.timestamp = o.spendingBlock.timestamp;
spentOutput.spendingTransactionHash = o.spendingTransactionHash;
spentOutput.keyImage = o.keyImage;
spentOutput.inputInTransaction = o.inputInTransaction;
spentOutputs.push_back(spentOutput);
}
return spentOutputs;
}
void TransfersContainer::save(std::ostream& os) {
std::lock_guard lk(m_mutex);
StdOutputStream stream(os);
CryptoNote::BinaryOutputStreamSerializer s(stream);
s(const_cast(TRANSFERS_CONTAINER_STORAGE_VERSION), "version");
s(m_currentHeight, "height");
writeSequence(m_transactions.begin(), m_transactions.end(), "transactions", s);
writeSequence(m_unconfirmedTransfers.begin(), m_unconfirmedTransfers.end(), "unconfirmedTransfers", s);
writeSequence(m_availableTransfers.begin(), m_availableTransfers.end(), "availableTransfers", s);
writeSequence(m_spentTransfers.begin(), m_spentTransfers.end(), "spentTransfers", s);
}
void TransfersContainer::load(std::istream& in) {
std::lock_guard lk(m_mutex);
StdInputStream stream(in);
CryptoNote::BinaryInputStreamSerializer s(stream);
uint32_t version = 0;
s(version, "version");
if (version > TRANSFERS_CONTAINER_STORAGE_VERSION) {
throw std::runtime_error("Unsupported transfers storage version");
}
uint32_t currentHeight = 0;
TransactionMultiIndex transactions;
UnconfirmedTransfersMultiIndex unconfirmedTransfers;
AvailableTransfersMultiIndex availableTransfers;
SpentTransfersMultiIndex spentTransfers;
s(currentHeight, "height");
readSequence(std::inserter(transactions, transactions.end()), "transactions", s);
readSequence(std::inserter(unconfirmedTransfers, unconfirmedTransfers.end()), "unconfirmedTransfers", s);
readSequence(std::inserter(availableTransfers, availableTransfers.end()), "availableTransfers", s);
readSequence(std::inserter(spentTransfers, spentTransfers.end()), "spentTransfers", s);
m_currentHeight = currentHeight;
m_transactions = std::move(transactions);
m_unconfirmedTransfers = std::move(unconfirmedTransfers);
m_availableTransfers = std::move(availableTransfers);
m_spentTransfers = std::move(spentTransfers);
}
bool TransfersContainer::isSpendTimeUnlocked(uint64_t unlockTime) const {
if (unlockTime < m_currency.maxBlockHeight()) {
// interpret as block index
return m_currentHeight - 1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlockTime;
} else {
//interpret as time
uint64_t current_time = static_cast(time(NULL));
return current_time + m_currency.lockedTxAllowedDeltaSeconds() >= unlockTime;
}
return false;
}
bool TransfersContainer::isIncluded(const TransactionOutputInformationEx& info, uint32_t flags) const {
uint32_t state;
if (info.blockHeight == WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT || !isSpendTimeUnlocked(info.unlockTime)) {
state = IncludeStateLocked;
} else if (m_currentHeight < info.blockHeight + m_transactionSpendableAge) {
state = IncludeStateSoftLocked;
} else {
state = IncludeStateUnlocked;
}
return isIncluded(info.type, state, flags);
}
bool TransfersContainer::isIncluded(TransactionTypes::OutputType type, uint32_t state, uint32_t flags) {
return
// filter by type
(
((flags & IncludeTypeKey) != 0 && type == TransactionTypes::OutputType::Key) ||
((flags & IncludeTypeMultisignature) != 0 && type == TransactionTypes::OutputType::Multisignature)
)
&&
// filter by state
((flags & state) != 0);
}
}