diff --git a/CMakeLists.txt b/CMakeLists.txt index e5dc35f8..03830d49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ set(CMAKE_SKIP_INSTALL_RULES ON) set(CMAKE_SKIP_PACKAGE_ALL_DEPENDENCY ON) set(CMAKE_SUPPRESS_REGENERATION ON) enable_testing() +# copy CTestCustom.cmake to build dir to disable long running tests in 'make test' +configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}) project(Bytecoin) diff --git a/CTestCustom.cmake b/CTestCustom.cmake new file mode 100644 index 00000000..088261fa --- /dev/null +++ b/CTestCustom.cmake @@ -0,0 +1,11 @@ +set(CTEST_CUSTOM_TESTS_IGNORE + CoreTests + IntegrationTestLibrary + TestGenerator + CryptoTests + IntegrationTests + NodeRpcProxyTests + PerformanceTests + TransfersTests + ) + diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index fa94c070..359fdaf1 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,8 @@ +Release notes 1.0.7 + +- Fusion transactions support +- Various simplewallet improvements + Release notes 1.0.6 - High-level API update diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index 9bb8d360..b7cfc4e4 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -62,6 +62,10 @@ const uint64_t CRYPTONOTE_MEMPOOL_TX_LIVETIME = 60 * 60 * 24; const uint64_t CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME = 60 * 60 * 24 * 7; //seconds, one week const uint64_t CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL = 7; // CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL * CRYPTONOTE_MEMPOOL_TX_LIVETIME = time to forget tx +const size_t FUSION_TX_MAX_SIZE = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE * 30 / 100; +const size_t FUSION_TX_MIN_INPUT_COUNT = 6; +const size_t FUSION_TX_MIN_IN_OUT_COUNT_RATIO = 3; + const uint64_t UPGRADE_HEIGHT = 546602; const unsigned UPGRADE_VOTING_THRESHOLD = 90; // percent const size_t UPGRADE_VOTING_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks @@ -156,7 +160,8 @@ const CheckpointData CHECKPOINTS[] = { {785500, "de1a487d70964d25ed6f7de196866f357a293e867ee81313e7fd0352d0126bdd"}, {789000, "acef490bbccce3b7b7ae8554a414f55413fbf4ca1472c6359b126a4439bd9f01"}, {796000, "04e387a00d35db21d4d93d04040b31f22573972a7e61d72cc07d0ab69bcb9c44"}, - {800000, "d7fa4eea02e5ce60b949136569c0ea7ac71ea46e0065311054072ac415560b86"} + {800000, "d7fa4eea02e5ce60b949136569c0ea7ac71ea46e0065311054072ac415560b86"}, + {804000, "bcc8b3782499aae508c40d5587d1cc5d68281435ea9bfc6804a262047f7b934d"} }; } // CryptoNote diff --git a/src/CryptoNoteCore/CryptoNoteBasic.h b/src/CryptoNoteCore/CryptoNoteBasic.h index 2230a309..4d3558fb 100755 --- a/src/CryptoNoteCore/CryptoNoteBasic.h +++ b/src/CryptoNoteCore/CryptoNoteBasic.h @@ -23,6 +23,7 @@ namespace CryptoNote { const Crypto::Hash NULL_HASH = boost::value_initialized(); const Crypto::PublicKey NULL_PUBLIC_KEY = boost::value_initialized(); + const Crypto::SecretKey NULL_SECRET_KEY = boost::value_initialized(); KeyPair generateKeyPair(); diff --git a/src/CryptoNoteCore/Currency.cpp b/src/CryptoNoteCore/Currency.cpp index 73590e89..fac0ae59 100755 --- a/src/CryptoNoteCore/Currency.cpp +++ b/src/CryptoNoteCore/Currency.cpp @@ -209,6 +209,52 @@ bool Currency::constructMinerTx(uint32_t height, size_t medianSize, uint64_t alr return true; } +bool Currency::isFusionTransaction(const Transaction& transaction, uint64_t inputAmount, size_t size) const { + assert(getInputAmount(transaction) == inputAmount); + assert(getObjectBinarySize(transaction) == size); + + if (size > fusionTxMaxSize()) { + return false; + } + + if (transaction.inputs.size() < fusionTxMinInputCount()) { + return false; + } + + if (transaction.inputs.size() < transaction.outputs.size() * fusionTxMinInOutCountRatio()) { + return false; + } + + std::vector expectedOutputsAmounts; + expectedOutputsAmounts.reserve(transaction.outputs.size()); + decomposeAmount(inputAmount, defaultDustThreshold(), expectedOutputsAmounts); + assert(!expectedOutputsAmounts.empty()); + + if (expectedOutputsAmounts.size() != transaction.outputs.size()) { + return false; + } + + std::sort(expectedOutputsAmounts.begin(), expectedOutputsAmounts.end()); + + if (expectedOutputsAmounts.front() <= defaultDustThreshold()) { + return false; + } + + auto it1 = expectedOutputsAmounts.begin(); + auto it2 = transaction.outputs.begin(); + for (; it1 != expectedOutputsAmounts.end(); ++it1, ++it2) { + if (*it1 != it2->amount) { + return false; + } + } + + return true; +} + +bool Currency::isFusionTransaction(const Transaction& transaction) const { + return isFusionTransaction(transaction, getInputAmount(transaction), getObjectBinarySize(transaction)); +} + std::string Currency::accountAddressAsString(const AccountBase& account) const { return getAccountAddressAsStr(m_publicAddressBase58Prefix, account.getAccountKeys().address); } @@ -240,6 +286,16 @@ std::string Currency::formatAmount(uint64_t amount) const { return s; } +std::string Currency::formatAmount(int64_t amount) const { + std::string s = formatAmount(static_cast(std::abs(amount))); + + if (amount < 0) { + s.insert(0, "-"); + } + + return s; +} + bool Currency::parseAmount(const std::string& str, uint64_t& amount) const { std::string strAmount = str; boost::algorithm::trim(strAmount); @@ -422,6 +478,10 @@ CurrencyBuilder::CurrencyBuilder(Logging::ILogger& log) : m_currency(log) { mempoolTxFromAltBlockLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME); numberOfPeriodsToForgetTxDeletedFromPool(parameters::CRYPTONOTE_NUMBER_OF_PERIODS_TO_FORGET_TX_DELETED_FROM_POOL); + fusionTxMaxSize(parameters::FUSION_TX_MAX_SIZE); + fusionTxMinInputCount(parameters::FUSION_TX_MIN_INPUT_COUNT); + fusionTxMinInOutCountRatio(parameters::FUSION_TX_MIN_IN_OUT_COUNT_RATIO); + upgradeHeight(parameters::UPGRADE_HEIGHT); upgradeVotingThreshold(parameters::UPGRADE_VOTING_THRESHOLD); upgradeVotingWindow(parameters::UPGRADE_VOTING_WINDOW); diff --git a/src/CryptoNoteCore/Currency.h b/src/CryptoNoteCore/Currency.h index 7394e48f..c687c6ef 100755 --- a/src/CryptoNoteCore/Currency.h +++ b/src/CryptoNoteCore/Currency.h @@ -72,6 +72,10 @@ public: uint64_t mempoolTxFromAltBlockLiveTime() const { return m_mempoolTxFromAltBlockLiveTime; } uint64_t numberOfPeriodsToForgetTxDeletedFromPool() const { return m_numberOfPeriodsToForgetTxDeletedFromPool; } + size_t fusionTxMaxSize() const { return m_fusionTxMaxSize; } + size_t fusionTxMinInputCount() const { return m_fusionTxMinInputCount; } + size_t fusionTxMinInOutCountRatio() const { return m_fusionTxMinInOutCountRatio; } + uint64_t upgradeHeight() const { return m_upgradeHeight; } unsigned int upgradeVotingThreshold() const { return m_upgradeVotingThreshold; } size_t upgradeVotingWindow() const { return m_upgradeVotingWindow; } @@ -99,11 +103,15 @@ public: uint64_t fee, const AccountPublicAddress& minerAddress, Transaction& tx, const BinaryArray& extraNonce = BinaryArray(), size_t maxOuts = 1, bool penalizeFee = false) const; + bool isFusionTransaction(const Transaction& transaction) const; + bool isFusionTransaction(const Transaction& transaction, uint64_t inputAmount, size_t size) const; + std::string accountAddressAsString(const AccountBase& account) const; std::string accountAddressAsString(const AccountPublicAddress& accountPublicAddress) const; bool parseAccountAddressString(const std::string& str, AccountPublicAddress& addr) const; std::string formatAmount(uint64_t amount) const; + std::string formatAmount(int64_t amount) const; bool parseAmount(const std::string& str, uint64_t& amount) const; difficulty_type nextDifficulty(std::vector timestamps, std::vector cumulativeDifficulties) const; @@ -159,6 +167,10 @@ private: uint64_t m_mempoolTxFromAltBlockLiveTime; uint64_t m_numberOfPeriodsToForgetTxDeletedFromPool; + size_t m_fusionTxMaxSize; + size_t m_fusionTxMinInputCount; + size_t m_fusionTxMinInOutCountRatio; + uint64_t m_upgradeHeight; unsigned int m_upgradeVotingThreshold; size_t m_upgradeVotingWindow; @@ -228,6 +240,10 @@ public: CurrencyBuilder& mempoolTxFromAltBlockLiveTime(uint64_t val) { m_currency.m_mempoolTxFromAltBlockLiveTime = val; return *this; } CurrencyBuilder& numberOfPeriodsToForgetTxDeletedFromPool(uint64_t val) { m_currency.m_numberOfPeriodsToForgetTxDeletedFromPool = val; return *this; } + CurrencyBuilder& fusionTxMaxSize(size_t val) { m_currency.m_fusionTxMaxSize = val; return *this; } + CurrencyBuilder& fusionTxMinInputCount(size_t val) { m_currency.m_fusionTxMinInputCount = val; return *this; } + CurrencyBuilder& fusionTxMinInOutCountRatio(size_t val) { m_currency.m_fusionTxMinInOutCountRatio = val; return *this; } + CurrencyBuilder& upgradeHeight(uint64_t val) { m_currency.m_upgradeHeight = val; return *this; } CurrencyBuilder& upgradeVotingThreshold(unsigned int val); CurrencyBuilder& upgradeVotingWindow(size_t val) { m_currency.m_upgradeVotingWindow = val; return *this; } diff --git a/src/CryptoNoteCore/TransactionPool.cpp b/src/CryptoNoteCore/TransactionPool.cpp index e4d1552c..564599eb 100644 --- a/src/CryptoNoteCore/TransactionPool.cpp +++ b/src/CryptoNoteCore/TransactionPool.cpp @@ -110,7 +110,6 @@ namespace CryptoNote { m_fee_index(boost::get<1>(m_transactions)), logger(log, "txpool") { } - //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const Transaction &tx, /*const Crypto::Hash& tx_prefix_hash,*/ const Crypto::Hash &id, size_t blobSize, tx_verification_context& tvc, bool keptByBlock) { if (!check_inputs_types_supported(tx)) { @@ -134,7 +133,8 @@ namespace CryptoNote { } const uint64_t fee = inputs_amount - outputs_amount; - if (!keptByBlock && fee < m_currency.minimumFee()) { + bool isFusionTransaction = fee == 0 && m_currency.isFusionTransaction(tx, inputs_amount, blobSize); + if (!keptByBlock && !isFusionTransaction && fee < m_currency.minimumFee()) { logger(INFO) << "transaction fee is not enough: " << m_currency.formatAmount(fee) << ", minimum fee: " << m_currency.formatAmount(m_currency.minimumFee()); tvc.m_verifivation_failed = true; @@ -212,10 +212,7 @@ namespace CryptoNote { } tvc.m_added_to_pool = true; - - if (inputsValid && fee > 0) - tvc.m_should_be_relayed = true; - + tvc.m_should_be_relayed = inputsValid && (fee > 0 || isFusionTransaction); tvc.m_verifivation_failed = true; if (!addTransactionInputs(id, tx, keptByBlock)) @@ -360,10 +357,24 @@ namespace CryptoNote { BlockTemplate blockTemplate; + for (auto it = m_fee_index.rbegin(); it != m_fee_index.rend() && it->fee == 0; ++it) { + const auto& txd = *it; + + if (m_currency.fusionTxMaxSize() < total_size + txd.blobSize) { + continue; + } + + TransactionCheckInfo checkInfo(txd); + if (is_transaction_ready_to_go(txd.tx, checkInfo) && blockTemplate.addTransaction(txd.id, txd.tx)) { + total_size += txd.blobSize; + } + } + for (auto i = m_fee_index.begin(); i != m_fee_index.end(); ++i) { const auto& txd = *i; - if (max_total_size < total_size + txd.blobSize) { + size_t blockSizeLimit = (txd.fee == 0) ? median_size : max_total_size; + if (blockSizeLimit < total_size + txd.blobSize) { continue; } @@ -374,7 +385,7 @@ namespace CryptoNote { m_fee_index.modify(i, [&checkInfo](TransactionCheckInfo& item) { item = checkInfo; }); - + if (ready && blockTemplate.addTransaction(txd.id, txd.tx)) { total_size += txd.blobSize; fee += txd.fee; diff --git a/src/PaymentGate/WalletService.cpp b/src/PaymentGate/WalletService.cpp index 17607514..9525fda6 100755 --- a/src/PaymentGate/WalletService.cpp +++ b/src/PaymentGate/WalletService.cpp @@ -428,7 +428,7 @@ std::error_code WalletService::getTransactionByTransferId(size_t transferId, siz return std::make_error_code(std::errc::argument_out_of_domain); } - auto nextTxId = std::lower_bound(transfersIndices.begin(), transfersIndices.end(), transferId); + auto nextTxId = std::upper_bound(transfersIndices.begin(), transfersIndices.end(), transferId); transactionId = (nextTxId - transfersIndices.begin()) - 1; return std::error_code(); @@ -454,13 +454,15 @@ std::error_code WalletService::getTransaction(size_t txId, bool& found, Transact logger(Logging::DEBUGGING) << "getTransaction request came"; found = false; + try { - auto tx = wallet->getTransaction(txId); if (txId + 1 >= transfersIndices.size()) { logger(Logging::WARNING) << "Unable to get transaction " << txId << ": argument out of domain."; return std::make_error_code(std::errc::argument_out_of_domain); } + auto tx = wallet->getTransaction(txId); + fillTransactionRpcInfo(txId, tx, rpcInfo); found = true; diff --git a/src/Platform/Windows/System/ErrorMessage.cpp b/src/Platform/Windows/System/ErrorMessage.cpp old mode 100644 new mode 100755 diff --git a/src/SimpleWallet/SimpleWallet.cpp b/src/SimpleWallet/SimpleWallet.cpp index 99ee6bb2..1f87036a 100644 --- a/src/SimpleWallet/SimpleWallet.cpp +++ b/src/SimpleWallet/SimpleWallet.cpp @@ -17,8 +17,10 @@ #include "SimpleWallet.h" +#include #include #include +#include #include #include #include @@ -31,6 +33,7 @@ #include "Common/CommandLine.h" #include "Common/SignalHandler.h" +#include "Common/StringTools.h" #include "Common/PathTools.h" #include "Common/Util.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" @@ -350,9 +353,74 @@ std::string tryToOpenWalletOrLoadKeysOrThrow(LoggerRef& logger, std::unique_ptr< } } +std::string makeCenteredString(size_t width, const std::string& text) { + if (text.size() >= width) { + return text; + } + size_t offset = (width - text.size() + 1) / 2; + return std::string(offset, ' ') + text + std::string(width - text.size() - offset, ' '); } +const size_t TIMESTAMP_MAX_WIDTH = 19; +const size_t HASH_MAX_WIDTH = 64; +const size_t TOTAL_AMOUNT_MAX_WIDTH = 20; +const size_t FEE_MAX_WIDTH = 14; +const size_t BLOCK_MAX_WIDTH = 7; +const size_t UNLOCK_TIME_MAX_WIDTH = 11; + +void printListTransfersHeader(LoggerRef& logger) { + std::string header = makeCenteredString(TIMESTAMP_MAX_WIDTH, "timestamp (UTC)") + " "; + header += makeCenteredString(HASH_MAX_WIDTH, "hash") + " "; + header += makeCenteredString(TOTAL_AMOUNT_MAX_WIDTH, "total amount") + " "; + header += makeCenteredString(FEE_MAX_WIDTH, "fee") + " "; + header += makeCenteredString(BLOCK_MAX_WIDTH, "block") + " "; + header += makeCenteredString(UNLOCK_TIME_MAX_WIDTH, "unlock time"); + + logger(INFO) << header; + logger(INFO) << std::string(header.size(), '-'); +} + +void printListTransfersItem(LoggerRef& logger, const WalletLegacyTransaction& txInfo, IWalletLegacy& wallet, const Currency& currency) { + std::vector extraVec = Common::asBinaryArray(txInfo.extra); + + Crypto::Hash paymentId; + std::string paymentIdStr = (getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId != NULL_HASH ? Common::podToHex(paymentId) : ""); + + char timeString[TIMESTAMP_MAX_WIDTH + 1]; + time_t timestamp = static_cast(txInfo.timestamp); + if (std::strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", std::gmtime(×tamp)) == 0) { + throw std::runtime_error("time buffer is too small"); + } + + std::string rowColor = txInfo.totalAmount < 0 ? MAGENTA : GREEN; + logger(INFO, rowColor) + << std::setw(TIMESTAMP_MAX_WIDTH) << timeString + << " " << std::setw(HASH_MAX_WIDTH) << Common::podToHex(txInfo.hash) + << " " << std::setw(TOTAL_AMOUNT_MAX_WIDTH) << currency.formatAmount(txInfo.totalAmount) + << " " << std::setw(FEE_MAX_WIDTH) << currency.formatAmount(txInfo.fee) + << " " << std::setw(BLOCK_MAX_WIDTH) << txInfo.blockHeight + << " " << std::setw(UNLOCK_TIME_MAX_WIDTH) << txInfo.unlockTime; + + if (!paymentIdStr.empty()) { + logger(INFO, rowColor) << "payment ID: " << paymentIdStr; + } + + if (txInfo.totalAmount < 0) { + if (txInfo.transferCount > 0) { + logger(INFO, rowColor) << "transfers:"; + for (TransferId id = txInfo.firstTransferId; id < txInfo.firstTransferId + txInfo.transferCount; ++id) { + WalletLegacyTransfer tr; + wallet.getTransfer(id, tr); + logger(INFO, rowColor) << tr.address << " " << std::setw(TOTAL_AMOUNT_MAX_WIDTH) << currency.formatAmount(tr.amount); + } + } + } + + logger(INFO, rowColor) << " "; //just to make logger print one endline +} + +} std::string simple_wallet::get_commands_str() { @@ -795,6 +863,8 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args } bool simple_wallet::listTransfers(const std::vector& args) { + bool haveTransfers = false; + size_t transactionsCount = m_wallet->getTransactionCount(); for (size_t trantransactionNumber = 0; trantransactionNumber < transactionsCount; ++trantransactionNumber) { WalletLegacyTransaction txInfo; @@ -803,37 +873,18 @@ bool simple_wallet::listTransfers(const std::vector& args) { continue; } - std::string paymentIdStr = ""; - std::vector extraVec; - extraVec.reserve(txInfo.extra.size()); - std::for_each(txInfo.extra.begin(), txInfo.extra.end(), [&extraVec](const char el) { extraVec.push_back(el); }); - - Crypto::Hash paymentId; - paymentIdStr = (getPaymentIdFromTxExtra(extraVec, paymentId) && paymentId != NULL_HASH ? Common::podToHex(paymentId) : ""); - - std::string address = "-"; - if (txInfo.totalAmount < 0) { - if (txInfo.transferCount > 0) - { - WalletLegacyTransfer tr; - m_wallet->getTransfer(txInfo.firstTransferId, tr); - address = tr.address; - } + if (!haveTransfers) { + printListTransfersHeader(logger); + haveTransfers = true; } - logger(INFO, txInfo.totalAmount < 0 ? MAGENTA : GREEN) - << txInfo.timestamp - << ", " << (txInfo.totalAmount < 0 ? "OUTPUT" : "INPUT") - << ", " << Common::podToHex(txInfo.hash) - << ", " << (txInfo.totalAmount < 0 ? "-" : "") << m_currency.formatAmount(std::abs(txInfo.totalAmount)) - << ", " << m_currency.formatAmount(txInfo.fee) - << ", " << (paymentIdStr.empty() ? std::string("-") : paymentIdStr) - << ", " << address - << ", " << txInfo.blockHeight - << ", " << txInfo.unlockTime; + printListTransfersItem(logger, txInfo, *m_wallet, m_currency); + } + + if (!haveTransfers) { + success_msg_writer() << "No transfers"; } - if (transactionsCount == 0) success_msg_writer() << "No transfers"; return true; } diff --git a/src/Transfers/TransfersConsumer.cpp b/src/Transfers/TransfersConsumer.cpp index f68437c4..059570f1 100755 --- a/src/Transfers/TransfersConsumer.cpp +++ b/src/Transfers/TransfersConsumer.cpp @@ -162,7 +162,7 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH assert(blocks); struct Tx { - BlockInfo blockInfo; + TransactionBlockInfo blockInfo; const ITransactionReader* tx; }; @@ -193,7 +193,7 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH continue; } - BlockInfo blockInfo; + TransactionBlockInfo blockInfo; blockInfo.height = startHeight + i; blockInfo.timestamp = block->timestamp; blockInfo.transactionIndex = 0; // position in block @@ -282,7 +282,7 @@ bool TransfersConsumer::onNewBlocks(const CompleteBlock* blocks, uint32_t startH } std::error_code TransfersConsumer::onPoolUpdated(const std::vector>& addedTransactions, const std::vector& deletedTransactions) { - BlockInfo unconfirmedBlockInfo; + TransactionBlockInfo unconfirmedBlockInfo; unconfirmedBlockInfo.timestamp = 0; unconfirmedBlockInfo.height = WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT; std::error_code processingError; @@ -325,7 +325,7 @@ void TransfersConsumer::getKnownPoolTxIds(std::vector& ids) { std::error_code createTransfers( const AccountKeys& account, - const BlockInfo& blockInfo, + const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const std::vector& outputs, const std::vector& globalIdxs, @@ -388,7 +388,7 @@ std::error_code createTransfers( return std::error_code(); } -std::error_code TransfersConsumer::preprocessOutputs(const BlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info) { +std::error_code TransfersConsumer::preprocessOutputs(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info) { std::unordered_map> outputs; findMyOutputs(tx, m_viewSecret, m_spendKeys, outputs); @@ -419,7 +419,7 @@ std::error_code TransfersConsumer::preprocessOutputs(const BlockInfo& blockInfo, return std::error_code(); } -std::error_code TransfersConsumer::processTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx) { +std::error_code TransfersConsumer::processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx) { PreprocessInfo info; auto ec = preprocessOutputs(blockInfo, tx, info); if (ec) { @@ -430,7 +430,7 @@ std::error_code TransfersConsumer::processTransaction(const BlockInfo& blockInfo } -std::error_code TransfersConsumer::processTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info) { +std::error_code TransfersConsumer::processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info) { std::error_code errorCode; std::vector emptyOutputs; for (auto& kv : m_subscriptions) { @@ -447,7 +447,7 @@ std::error_code TransfersConsumer::processTransaction(const BlockInfo& blockInfo -std::error_code TransfersConsumer::processOutputs(const BlockInfo& blockInfo, TransfersSubscription& sub, +std::error_code TransfersConsumer::processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, const std::vector& transfers, const std::vector& globalIdxs) { if (blockInfo.height != WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT) { diff --git a/src/Transfers/TransfersConsumer.h b/src/Transfers/TransfersConsumer.h index e8166bec..0463d421 100755 --- a/src/Transfers/TransfersConsumer.h +++ b/src/Transfers/TransfersConsumer.h @@ -65,10 +65,10 @@ private: std::vector globalIdxs; }; - std::error_code preprocessOutputs(const BlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info); - std::error_code processTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx); - std::error_code processTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info); - std::error_code processOutputs(const BlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, + std::error_code preprocessOutputs(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, PreprocessInfo& info); + std::error_code processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx); + std::error_code processTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const PreprocessInfo& info); + std::error_code processOutputs(const TransactionBlockInfo& blockInfo, TransfersSubscription& sub, const ITransactionReader& tx, const std::vector& outputs, const std::vector& globalIdxs); std::error_code getGlobalIndices(const Crypto::Hash& transactionHash, std::vector& outsGlobalIndices); diff --git a/src/Transfers/TransfersContainer.cpp b/src/Transfers/TransfersContainer.cpp index 6823ea1f..cdb6f96e 100755 --- a/src/Transfers/TransfersContainer.cpp +++ b/src/Transfers/TransfersContainer.cpp @@ -172,7 +172,7 @@ TransfersContainer::TransfersContainer(const Currency& currency, size_t transact m_transactionSpendableAge(transactionSpendableAge) { } -bool TransfersContainer::addTransaction(const BlockInfo& block, const ITransactionReader& tx, +bool TransfersContainer::addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx, const std::vector& transfers) { std::unique_lock lock(m_mutex); @@ -201,7 +201,7 @@ bool TransfersContainer::addTransaction(const BlockInfo& block, const ITransacti /** * \pre m_mutex is locked. */ -void TransfersContainer::addTransaction(const BlockInfo& block, const ITransactionReader& tx) { +void TransfersContainer::addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx) { auto txHash = tx.getTransactionHash(); TransactionInformation txInfo; @@ -226,7 +226,7 @@ void TransfersContainer::addTransaction(const BlockInfo& block, const ITransacti /** * \pre m_mutex is locked. */ -bool TransfersContainer::addTransactionOutputs(const BlockInfo& block, const ITransactionReader& tx, +bool TransfersContainer::addTransactionOutputs(const TransactionBlockInfo& block, const ITransactionReader& tx, const std::vector& transfers) { bool outputsAdded = false; @@ -281,7 +281,7 @@ bool TransfersContainer::addTransactionOutputs(const BlockInfo& block, const ITr /** * \pre m_mutex is locked. */ -bool TransfersContainer::addTransactionInputs(const BlockInfo& block, const ITransactionReader& tx) { +bool TransfersContainer::addTransactionInputs(const TransactionBlockInfo& block, const ITransactionReader& tx) { bool inputsAdded = false; for (size_t i = 0; i < tx.getInputCount(); ++i) { @@ -365,7 +365,7 @@ bool TransfersContainer::deleteUnconfirmedTransaction(const Hash& transactionHas } } -bool TransfersContainer::markTransactionConfirmed(const BlockInfo& block, const Hash& transactionHash, +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"); @@ -479,7 +479,7 @@ void TransfersContainer::deleteTransactionTransfers(const Hash& transactionHash) /** * \pre m_mutex is locked. */ -void TransfersContainer::copyToSpent(const BlockInfo& block, const ITransactionReader& tx, size_t inputIndex, +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); diff --git a/src/Transfers/TransfersContainer.h b/src/Transfers/TransfersContainer.h index 1f08b144..81e1474d 100755 --- a/src/Transfers/TransfersContainer.h +++ b/src/Transfers/TransfersContainer.h @@ -107,7 +107,7 @@ struct TransactionOutputInformationEx : public TransactionOutputInformationIn { }; -struct BlockInfo { +struct TransactionBlockInfo { uint32_t height; uint64_t timestamp; uint32_t transactionIndex; @@ -120,7 +120,7 @@ struct BlockInfo { }; struct SpentTransactionOutput : TransactionOutputInformationEx { - BlockInfo spendingBlock; + TransactionBlockInfo spendingBlock; Crypto::Hash spendingTransactionHash; uint32_t inputInTransaction; @@ -153,9 +153,9 @@ public: TransfersContainer(const CryptoNote::Currency& currency, size_t transactionSpendableAge); - bool addTransaction(const BlockInfo& block, const ITransactionReader& tx, const std::vector& transfers); + bool addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx, const std::vector& transfers); bool deleteUnconfirmedTransaction(const Crypto::Hash& transactionHash); - bool markTransactionConfirmed(const BlockInfo& block, const Crypto::Hash& transactionHash, const std::vector& globalIndices); + bool markTransactionConfirmed(const TransactionBlockInfo& block, const Crypto::Hash& transactionHash, const std::vector& globalIndices); std::vector detach(uint32_t height); bool advanceHeight(uint32_t height); @@ -258,17 +258,17 @@ private: > SpentTransfersMultiIndex; private: - void addTransaction(const BlockInfo& block, const ITransactionReader& tx); - bool addTransactionOutputs(const BlockInfo& block, const ITransactionReader& tx, + void addTransaction(const TransactionBlockInfo& block, const ITransactionReader& tx); + bool addTransactionOutputs(const TransactionBlockInfo& block, const ITransactionReader& tx, const std::vector& transfers); - bool addTransactionInputs(const BlockInfo& block, const ITransactionReader& tx); + bool addTransactionInputs(const TransactionBlockInfo& block, const ITransactionReader& tx); void deleteTransactionTransfers(const Crypto::Hash& transactionHash); bool isSpendTimeUnlocked(uint64_t unlockTime) const; bool isIncluded(const TransactionOutputInformationEx& info, uint32_t flags) const; static bool isIncluded(TransactionTypes::OutputType type, uint32_t state, uint32_t flags); void updateTransfersVisibility(const Crypto::KeyImage& keyImage); - void copyToSpent(const BlockInfo& block, const ITransactionReader& tx, size_t inputIndex, const TransactionOutputInformationEx& output); + void copyToSpent(const TransactionBlockInfo& block, const ITransactionReader& tx, size_t inputIndex, const TransactionOutputInformationEx& output); private: TransactionMultiIndex m_transactions; diff --git a/src/Transfers/TransfersSubscription.cpp b/src/Transfers/TransfersSubscription.cpp index 7f5f4f44..2f1e3784 100755 --- a/src/Transfers/TransfersSubscription.cpp +++ b/src/Transfers/TransfersSubscription.cpp @@ -52,7 +52,7 @@ const AccountKeys& TransfersSubscription::getKeys() const { return subscription.keys; } -void TransfersSubscription::addTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx, +void TransfersSubscription::addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const std::vector& transfersList) { bool added = transfers.addTransaction(blockInfo, tx, transfersList); if (added) { @@ -73,7 +73,7 @@ void TransfersSubscription::deleteUnconfirmedTransaction(const Hash& transaction m_observerManager.notify(&ITransfersObserver::onTransactionDeleted, this, transactionHash); } -void TransfersSubscription::markTransactionConfirmed(const BlockInfo& block, const Hash& transactionHash, +void TransfersSubscription::markTransactionConfirmed(const TransactionBlockInfo& block, const Hash& transactionHash, const std::vector& globalIndices) { transfers.markTransactionConfirmed(block, transactionHash, globalIndices); m_observerManager.notify(&ITransfersObserver::onTransactionUpdated, this, transactionHash); diff --git a/src/Transfers/TransfersSubscription.h b/src/Transfers/TransfersSubscription.h index 0dda4132..f8a46861 100755 --- a/src/Transfers/TransfersSubscription.h +++ b/src/Transfers/TransfersSubscription.h @@ -32,11 +32,11 @@ public: void onError(const std::error_code& ec, uint32_t height); bool advanceHeight(uint32_t height); const AccountKeys& getKeys() const; - void addTransaction(const BlockInfo& blockInfo, const ITransactionReader& tx, + void addTransaction(const TransactionBlockInfo& blockInfo, const ITransactionReader& tx, const std::vector& transfers); void deleteUnconfirmedTransaction(const Crypto::Hash& transactionHash); - void markTransactionConfirmed(const BlockInfo& block, const Crypto::Hash& transactionHash, const std::vector& globalIndices); + void markTransactionConfirmed(const TransactionBlockInfo& block, const Crypto::Hash& transactionHash, const std::vector& globalIndices); // ITransfersSubscription virtual AccountPublicAddress getAddress() override; diff --git a/src/Wallet/WalletErrors.h b/src/Wallet/WalletErrors.h index 346638fa..1a39710d 100644 --- a/src/Wallet/WalletErrors.h +++ b/src/Wallet/WalletErrors.h @@ -40,7 +40,8 @@ enum WalletErrorCodes { TX_CANCELLED, OPERATION_CANCELLED, TX_TRANSFER_IMPOSSIBLE, - WRONG_VERSION + WRONG_VERSION, + FEE_TOO_SMALL }; // custom category: @@ -72,7 +73,8 @@ public: case WRONG_STATE: return "The wallet is in wrong state (maybe loading or saving), try again later"; case OPERATION_CANCELLED: return "The operation you've requested has been cancelled"; case TX_TRANSFER_IMPOSSIBLE: return "Transaction transfer impossible"; - case WRONG_VERSION: return "Wrong version"; + case WRONG_VERSION: return "Wrong version"; + case FEE_TOO_SMALL: return "Transaction fee is too small"; default: return "Unknown error"; } } diff --git a/src/Wallet/WalletGreen.cpp b/src/Wallet/WalletGreen.cpp index e6125444..57931cf5 100755 --- a/src/Wallet/WalletGreen.cpp +++ b/src/Wallet/WalletGreen.cpp @@ -539,6 +539,10 @@ size_t WalletGreen::doTransfer(std::vector&& wallets, throw std::system_error(make_error_code(error::ZERO_DESTINATION)); } + if (fee < m_currency.minimumFee()) { + throw std::system_error(make_error_code(error::FEE_TOO_SMALL)); + } + validateAddresses(destinations, m_currency); uint64_t neededMoney = countNeededMoney(destinations, fee); diff --git a/src/WalletLegacy/WalletLegacySerializer.cpp b/src/WalletLegacy/WalletLegacySerializer.cpp index 1be62e60..0b1d94cc 100755 --- a/src/WalletLegacy/WalletLegacySerializer.cpp +++ b/src/WalletLegacy/WalletLegacySerializer.cpp @@ -137,14 +137,15 @@ void WalletLegacySerializer::deserialize(std::istream& stream, const std::string MemoryInputStream decryptedStream(plain.data(), plain.size()); CryptoNote::BinaryInputStreamSerializer serializer(decryptedStream); - try - { - loadKeys(serializer); - throwIfKeysMissmatch(account.getAccountKeys().viewSecretKey, account.getAccountKeys().address.viewPublicKey); + loadKeys(serializer); + throwIfKeysMissmatch(account.getAccountKeys().viewSecretKey, account.getAccountKeys().address.viewPublicKey); + + if (account.getAccountKeys().spendSecretKey != NULL_SECRET_KEY) { throwIfKeysMissmatch(account.getAccountKeys().spendSecretKey, account.getAccountKeys().address.spendPublicKey); - } - catch (std::exception&) { - throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); + } else { + if (!Crypto::check_key(account.getAccountKeys().address.spendPublicKey)) { + throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); + } } bool detailsSaved; diff --git a/src/version.h.in b/src/version.h.in index 88e14bd0..81443d33 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "@VERSION@" -#define PROJECT_VERSION "1.0.6.1" -#define PROJECT_VERSION_BUILD_NO "550" +#define PROJECT_VERSION "1.0.7" +#define PROJECT_VERSION_BUILD_NO "564" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" diff --git a/tests/UnitTests/TestCurrency.cpp b/tests/UnitTests/TestCurrency.cpp new file mode 100644 index 00000000..8fd37f15 --- /dev/null +++ b/tests/UnitTests/TestCurrency.cpp @@ -0,0 +1,115 @@ +// 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 "gtest/gtest.h" + +#include "crypto/crypto.h" +#include "CryptoNoteCore/Currency.h" +#include "CryptoNoteCore/TransactionApi.h" +#include "Logging/ConsoleLogger.h" + +#include "TransactionApiHelpers.h" + +using namespace CryptoNote; + +namespace { +const size_t TEST_FUSION_TX_MAX_SIZE = 6000; +const size_t TEST_FUSION_TX_MIN_INPUT_COUNT = 6; +const size_t TEST_FUSION_TX_MIN_IN_OUT_COUNT_RATIO = 3; +const uint64_t TEST_DUST_THRESHOLD = UINT64_C(1000000); +const uint64_t TEST_AMOUNT = 370 * TEST_DUST_THRESHOLD; + +class Currency_isFusionTransactionTest : public ::testing::Test { +public: + Currency_isFusionTransactionTest() : + m_currency(CurrencyBuilder(m_logger). + defaultDustThreshold(TEST_DUST_THRESHOLD). + fusionTxMaxSize(TEST_FUSION_TX_MAX_SIZE). + fusionTxMinInputCount(TEST_FUSION_TX_MIN_INPUT_COUNT). + fusionTxMinInOutCountRatio(TEST_FUSION_TX_MIN_IN_OUT_COUNT_RATIO). + currency()) { + } + +protected: + Logging::ConsoleLogger m_logger; + Currency m_currency; +}; +} + +TEST_F(Currency_isFusionTransactionTest, succeedsOnFusionTransaction) { + auto tx = FusionTransactionBuilder(m_currency, TEST_AMOUNT).buildTx(); + ASSERT_TRUE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, succeedsIfFusionTransactionSizeEqMaxSize) { + FusionTransactionBuilder builder(m_currency, TEST_AMOUNT); + auto tx = builder.createFusionTransactionBySize(m_currency.fusionTxMaxSize()); + ASSERT_EQ(m_currency.fusionTxMaxSize(), getObjectBinarySize(tx)); + ASSERT_TRUE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfFusionTransactionSizeGreaterThanMaxSize) { + FusionTransactionBuilder builder(m_currency, TEST_AMOUNT); + auto tx = builder.createFusionTransactionBySize(m_currency.fusionTxMaxSize() + 1); + ASSERT_EQ(m_currency.fusionTxMaxSize() + 1, getObjectBinarySize(tx)); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionInputsCountIsNotEnought) { + FusionTransactionBuilder builder(m_currency, TEST_AMOUNT); + builder.setInputCount(m_currency.fusionTxMinInputCount() - 1); + auto tx = builder.buildTx(); + ASSERT_EQ(m_currency.fusionTxMinInputCount() - 1, tx.inputs.size()); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionInputOutputCountRatioIsLessThenNecessary) { + FusionTransactionBuilder builder(m_currency, 3710 * m_currency.defaultDustThreshold()); + auto tx = builder.buildTx(); + ASSERT_EQ(3, tx.outputs.size()); + ASSERT_GT(tx.outputs.size() * m_currency.fusionTxMinInOutCountRatio(), tx.inputs.size()); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasNotExponentialOutput) { + FusionTransactionBuilder builder(m_currency, TEST_AMOUNT); + builder.setFirstOutput(TEST_AMOUNT); + auto tx = builder.buildTx(); + ASSERT_EQ(1, tx.outputs.size()); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasOutputsWithTheSameExponent) { + FusionTransactionBuilder builder(m_currency, 130 * m_currency.defaultDustThreshold()); + builder.setFirstOutput(70 * m_currency.defaultDustThreshold()); + auto tx = builder.buildTx(); + ASSERT_EQ(2, tx.outputs.size()); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionHasDustOutput) { + FusionTransactionBuilder builder(m_currency, 37 * m_currency.defaultDustThreshold() / 10); + auto tx = builder.buildTx(); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} + +TEST_F(Currency_isFusionTransactionTest, failsIfTransactionFeeIsNotZero) { + FusionTransactionBuilder builder(m_currency, 370 * m_currency.defaultDustThreshold()); + builder.setFee(70 * m_currency.defaultDustThreshold()); + auto tx = builder.buildTx(); + ASSERT_FALSE(m_currency.isFusionTransaction(tx)); +} diff --git a/tests/UnitTests/TestTransfersContainer.cpp b/tests/UnitTests/TestTransfersContainer.cpp index 5ebb9583..7ebdbc81 100755 --- a/tests/UnitTests/TestTransfersContainer.cpp +++ b/tests/UnitTests/TestTransfersContainer.cpp @@ -50,8 +50,8 @@ namespace { protected: - BlockInfo blockInfo(uint32_t height) const { - return BlockInfo{ height, 1000000 }; + TransactionBlockInfo blockInfo(uint32_t height) const { + return TransactionBlockInfo{ height, 1000000 }; } std::unique_ptr addTransaction(uint32_t height = WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, @@ -155,7 +155,7 @@ TEST_F(TransfersContainer_addTransaction, addingTransactionTwiceCausesException) TEST_F(TransfersContainer_addTransaction, addingTwoIdenticalUnconfirmedMultisignatureOutputsDoesNotCauseException) { - CryptoNote::BlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx1; tx1.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -182,7 +182,7 @@ TEST_F(TransfersContainer_addTransaction, addingTwoIdenticalUnconfirmedMultisign } TEST_F(TransfersContainer_addTransaction, addingConfirmedMultisignatureOutputIdenticalAnotherUnspentOuputCausesException) { - CryptoNote::BlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder tx1; tx1.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -209,7 +209,7 @@ TEST_F(TransfersContainer_addTransaction, addingConfirmedMultisignatureOutputIde } TEST_F(TransfersContainer_addTransaction, addingConfirmedMultisignatureOutputIdenticalAnotherSpentOuputCausesException) { - CryptoNote::BlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder tx1; tx1.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); auto outInfo1 = tx1.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); @@ -217,14 +217,14 @@ TEST_F(TransfersContainer_addTransaction, addingConfirmedMultisignatureOutputIde // Spend output { - CryptoNote::BlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx2; tx2.addTestMultisignatureInput(TEST_OUTPUT_AMOUNT, outInfo1); ASSERT_TRUE(container.addTransaction(blockInfo2, *tx2.build(), std::vector())); } { - CryptoNote::BlockInfo blockInfo3{ TEST_BLOCK_HEIGHT + 3, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo3{ TEST_BLOCK_HEIGHT + 3, 1000000 }; TestTransactionBuilder tx3; tx3.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); auto outInfo3 = tx3.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); @@ -240,7 +240,7 @@ TEST_F(TransfersContainer_addTransaction, addingConfirmedMultisignatureOutputIde } TEST_F(TransfersContainer_addTransaction, addingConfirmedBlockAndUnconfirmedOutputCausesException) { - CryptoNote::BlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder tx; tx.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -252,7 +252,7 @@ TEST_F(TransfersContainer_addTransaction, addingConfirmedBlockAndUnconfirmedOutp } TEST_F(TransfersContainer_addTransaction, addingUnconfirmedBlockAndConfirmedOutputCausesException) { - CryptoNote::BlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx; tx.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -264,7 +264,7 @@ TEST_F(TransfersContainer_addTransaction, addingUnconfirmedBlockAndConfirmedOutp } TEST_F(TransfersContainer_addTransaction, handlesAddingUnconfirmedOutputToKey) { - CryptoNote::BlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder txbuilder; txbuilder.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -306,7 +306,7 @@ TEST_F(TransfersContainer_addTransaction, handlesAddingUnconfirmedOutputToKey) { } TEST_F(TransfersContainer_addTransaction, handlesAddingConfirmedOutputToKey) { - CryptoNote::BlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder txbuilder; txbuilder.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -350,7 +350,7 @@ TEST_F(TransfersContainer_addTransaction, handlesAddingConfirmedOutputToKey) { } TEST_F(TransfersContainer_addTransaction, addingEmptyTransactionOuptutsDoesNotChaingeContainer) { - CryptoNote::BlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder builder; builder.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); @@ -559,7 +559,7 @@ TEST_F(TransfersContainer_deleteUnconfirmedTransaction, deleteUnconfirmedSpendin auto tx = spendingTx.build(); { - CryptoNote::BlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; ASSERT_TRUE(container.addTransaction(blockInfo, *tx, {})); } @@ -669,20 +669,20 @@ TEST_F(TransfersContainer_markTransactionConfirmed, confirmationTxWithNoOutputs) TEST_F(TransfersContainer_markTransactionConfirmed, confirmingMultisignatureOutputIdenticalAnotherUnspentOuputCausesException) { // Add tx1 - CryptoNote::BlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder tx1; tx1.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); auto outInfo1 = tx1.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); ASSERT_TRUE(container.addTransaction(blockInfo1, *tx1.build(), {outInfo1})); // Spend output, add tx2 - CryptoNote::BlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx2; tx2.addTestMultisignatureInput(TEST_OUTPUT_AMOUNT, outInfo1); ASSERT_TRUE(container.addTransaction(blockInfo2, *tx2.build(), {})); // Add tx3 - CryptoNote::BlockInfo blockInfo3{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo3{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx3; tx3.addTestInput(TEST_OUTPUT_AMOUNT + 1, account); auto outInfo3 = tx3.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX); @@ -703,7 +703,7 @@ TEST_F(TransfersContainer_markTransactionConfirmed, confirmingMultisignatureOutp } TEST_F(TransfersContainer_markTransactionConfirmed, confirmingMultisignatureOutputIdenticalAnotherSpentOuputCausesException) { - CryptoNote::BlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo1{ TEST_BLOCK_HEIGHT, 1000000 }; TestTransactionBuilder tx1; tx1.addTestInput(TEST_OUTPUT_AMOUNT + 1); auto outInfo1 = tx1.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, TEST_TRANSACTION_OUTPUT_GLOBAL_INDEX); @@ -711,7 +711,7 @@ TEST_F(TransfersContainer_markTransactionConfirmed, confirmingMultisignatureOutp container.advanceHeight(TEST_BLOCK_HEIGHT + TEST_TRANSACTION_SPENDABLE_AGE); - CryptoNote::BlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; + CryptoNote::TransactionBlockInfo blockInfo2{ WALLET_LEGACY_UNCONFIRMED_TRANSACTION_HEIGHT, 1000000 }; TestTransactionBuilder tx2; tx2.addTestInput(TEST_OUTPUT_AMOUNT + 1); auto outInfo2 = tx2.addTestMultisignatureOutput(TEST_OUTPUT_AMOUNT, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX); diff --git a/tests/UnitTests/TestTransfersContainerKeyImage.cpp b/tests/UnitTests/TestTransfersContainerKeyImage.cpp index 710348ec..f423864c 100755 --- a/tests/UnitTests/TestTransfersContainerKeyImage.cpp +++ b/tests/UnitTests/TestTransfersContainerKeyImage.cpp @@ -62,8 +62,8 @@ namespace { return getOutputs(flags).size(); } - BlockInfo blockInfo(uint32_t height) const { - return BlockInfo{ height, 1000000 }; + TransactionBlockInfo blockInfo(uint32_t height) const { + return TransactionBlockInfo{ height, 1000000 }; } TestTransactionBuilder createTransactionWithFixedKey() { @@ -82,7 +82,7 @@ namespace { auto outInfo = tx.addTestKeyOutput(amount, outputIndex, account); auto finalTx = tx.build(); - EXPECT_TRUE(container.addTransaction(BlockInfo{ height, 1000000, txIndex }, *finalTx, { outInfo })); + EXPECT_TRUE(container.addTransaction(TransactionBlockInfo{ height, 1000000, txIndex }, *finalTx, { outInfo })); return finalTx; } diff --git a/tests/UnitTests/TestTransfersSubscription.cpp b/tests/UnitTests/TestTransfersSubscription.cpp index d2990d16..72e14b8a 100755 --- a/tests/UnitTests/TestTransfersSubscription.cpp +++ b/tests/UnitTests/TestTransfersSubscription.cpp @@ -58,7 +58,7 @@ public: auto tx = std::shared_ptr(b1.build().release()); std::vector outputs = { outInfo }; - sub.addTransaction(BlockInfo{ height, 100000 }, *tx, outputs); + sub.addTransaction(TransactionBlockInfo{ height, 100000 }, *tx, outputs); return tx; } @@ -87,7 +87,7 @@ TEST_F(TransfersSubscriptionTest, addTransaction) { // this transaction should not be added, so no notification auto tx = createTransaction(); addTestInput(*tx, 20000); - sub.addTransaction(BlockInfo{ 2, 100000 }, *tx, {}); + sub.addTransaction(TransactionBlockInfo{ 2, 100000 }, *tx, {}); ASSERT_EQ(2, sub.getContainer().transactionsCount()); ASSERT_EQ(2, observer.updated.size()); @@ -142,7 +142,7 @@ TEST_F(TransfersSubscriptionTest, markTransactionConfirmed) { ASSERT_EQ(1, sub.getContainer().transactionsCount()); ASSERT_EQ(1, observer.updated.size()); // added - sub.markTransactionConfirmed(BlockInfo{ 10, 100000 }, txHash, { 1 }); + sub.markTransactionConfirmed(TransactionBlockInfo{ 10, 100000 }, txHash, { 1 }); ASSERT_EQ(2, observer.updated.size()); // added + updated ASSERT_EQ(txHash, observer.updated[0]); diff --git a/tests/UnitTests/TestWallet.cpp b/tests/UnitTests/TestWallet.cpp index 593c6309..02a038bb 100755 --- a/tests/UnitTests/TestWallet.cpp +++ b/tests/UnitTests/TestWallet.cpp @@ -107,7 +107,8 @@ public: currency(CryptoNote::CurrencyBuilder(logger).currency()), generator(currency), node(generator), - alice(dispatcher, currency, node) + alice(dispatcher, currency, node), + FEE(currency.minimumFee()) { } virtual void SetUp() override; @@ -161,7 +162,7 @@ protected: std::string aliceAddress; const uint64_t SENT = 1122334455; - const uint64_t FEE = 10000; + const uint64_t FEE; const std::string RANDOM_ADDRESS = "2634US2FAz86jZT73YmM8u5GPCknT2Wxj8bUCKivYKpThFhF2xsjygMGxbxZzM42zXhKUhym6Yy6qHHgkuWtruqiGkDpX6m"; }; @@ -391,14 +392,13 @@ TEST_F(WalletApi, transferFromTwoAddresses) { bob.initialize("pass2"); std::string bobAddress = bob.createAddress(); - const uint64_t fee = 10000; - const uint64_t sent = 2 * TEST_BLOCK_REWARD - 10 * fee; + const uint64_t sent = 2 * TEST_BLOCK_REWARD - 10 * FEE; auto bobPrev = bob.getPendingBalance(); auto alicePendingPrev = alice.getPendingBalance(); auto aliceActualPrev = alice.getActualBalance(); - sendMoney(bobAddress, sent, fee); + sendMoney(bobAddress, sent, FEE); node.updateObservers(); @@ -409,7 +409,7 @@ TEST_F(WalletApi, transferFromTwoAddresses) { ASSERT_EQ(0, bob.getActualBalance()); ASSERT_EQ(sent, bob.getPendingBalance()); - ASSERT_EQ(2 * TEST_BLOCK_REWARD - sent - fee, alice.getActualBalance() + alice.getPendingBalance()); + ASSERT_EQ(2 * TEST_BLOCK_REWARD - sent - FEE, alice.getActualBalance() + alice.getPendingBalance()); bob.shutdown(); wait(100); @@ -1039,11 +1039,11 @@ TEST_F(WalletApi, hybridTxTransfer) { } TEST_F(WalletApi, doubleSpendJustSentOut) { - generator.getSingleOutputTransaction(parseAddress(aliceAddress), 1000000); + generator.getSingleOutputTransaction(parseAddress(aliceAddress), SENT + FEE); unlockMoney(); - sendMoney(RANDOM_ADDRESS, 1000, 100); - ASSERT_ANY_THROW(sendMoney(RANDOM_ADDRESS, 1000, 100)); + sendMoney(RANDOM_ADDRESS, SENT, FEE); + ASSERT_ANY_THROW(sendMoney(RANDOM_ADDRESS, SENT, FEE)); } TEST_F(WalletApi, syncAfterLoad) { @@ -1139,3 +1139,9 @@ TEST_F(WalletApi, DISABLED_loadTest) { wallet.shutdown(); wait(100); } + +TEST_F(WalletApi, transferSmallFeeTransactionThrows) { + generateAndUnlockMoney(); + + ASSERT_ANY_THROW(sendMoneyToRandomAddressFrom(alice.getAddress(0), SENT, currency.minimumFee() - 1)); +} diff --git a/tests/UnitTests/TestWalletLegacy.cpp b/tests/UnitTests/TestWalletLegacy.cpp index 5e8943a0..bdc9d56a 100644 --- a/tests/UnitTests/TestWalletLegacy.cpp +++ b/tests/UnitTests/TestWalletLegacy.cpp @@ -1818,3 +1818,26 @@ TEST_F(WalletLegacyApi, outdatedUnconfirmedTransactionDeletedOnLoad) { wallet.removeObserver(&walletObserver); wallet.shutdown(); } + +TEST_F(WalletLegacyApi, walletLoadsNullSpendSecretKey) { + CryptoNote::AccountKeys accountKeys; + + Crypto::generate_keys(accountKeys.address.spendPublicKey, accountKeys.spendSecretKey); + Crypto::generate_keys(accountKeys.address.viewPublicKey, accountKeys.viewSecretKey); + accountKeys.spendSecretKey = CryptoNote::NULL_SECRET_KEY; + + alice->initWithKeys(accountKeys, "pass"); + WaitWalletSync(aliceWalletObserver.get()); + + std::stringstream data; + alice->save(data); + WaitWalletSave(aliceWalletObserver.get()); + + alice->shutdown(); + + alice->initAndLoad(data, "pass"); + WaitWalletSync(aliceWalletObserver.get()); + + ASSERT_EQ(std::error_code(), aliceWalletObserver->loadResult); + alice->shutdown(); +} diff --git a/tests/UnitTests/TransactionApiHelpers.cpp b/tests/UnitTests/TransactionApiHelpers.cpp index f6443bce..e58a80a7 100755 --- a/tests/UnitTests/TransactionApiHelpers.cpp +++ b/tests/UnitTests/TransactionApiHelpers.cpp @@ -43,6 +43,10 @@ PublicKey TestTransactionBuilder::getTransactionPublicKey() const { return tx->getTransactionPublicKey(); } +void TestTransactionBuilder::appendExtra(const BinaryArray& extraData) { + tx->appendExtra(extraData); +} + void TestTransactionBuilder::setUnlockTime(uint64_t time) { tx->setUnlockTime(time); } @@ -133,7 +137,7 @@ size_t TestTransactionBuilder::addFakeMultisignatureInput(uint64_t amount, uint3 MultisignatureInput input; input.amount = amount; input.outputIndex = globalOutputIndex; - input.signatureCount = signatureCount; + input.signatureCount = static_cast(signatureCount); size_t idx = tx->addInput(input); std::vector accs; @@ -221,7 +225,108 @@ std::unique_ptr TestTransactionBuilder::build() { return std::move(tx); } - Crypto::Hash TestTransactionBuilder::getTransactionHash() const { return transactionHash; } + +FusionTransactionBuilder::FusionTransactionBuilder(const Currency& currency, uint64_t amount) : + m_currency(currency), + m_amount(amount), + m_firstOutput(0), + m_fee(0), + m_extraSize(0), + m_inputCount(currency.fusionTxMinInputCount()) { +} + +uint64_t FusionTransactionBuilder::getAmount() const { + return m_amount; +} + +void FusionTransactionBuilder::setAmount(uint64_t val) { + m_amount = val; +} + +uint64_t FusionTransactionBuilder::getFirstOutput() const { + return m_firstOutput; +} + +void FusionTransactionBuilder::setFirstOutput(uint64_t val) { + m_firstOutput = val; +} + +uint64_t FusionTransactionBuilder::getFee() const { + return m_fee; +} + +void FusionTransactionBuilder::setFee(uint64_t val) { + m_fee = val; +} + +size_t FusionTransactionBuilder::getExtraSize() const { + return m_extraSize; +} + +void FusionTransactionBuilder::setExtraSize(size_t val) { + m_extraSize = val; +} + +size_t FusionTransactionBuilder::getInputCount() const { + return m_inputCount; +} + +void FusionTransactionBuilder::setInputCount(size_t val) { + m_inputCount = val; +} + +std::unique_ptr FusionTransactionBuilder::buildReader() const { + assert(m_inputCount > 0); + + TestTransactionBuilder builder; + + if (m_extraSize != 0) { + builder.appendExtra(BinaryArray(m_extraSize, 0)); + } + + builder.addTestInput(m_amount - (m_inputCount - 1) * m_currency.defaultDustThreshold()); + for (size_t i = 0; i < m_inputCount - 1; ++i) { + builder.addTestInput(m_currency.defaultDustThreshold()); + } + + AccountPublicAddress address = generateAddress(); + std::vector outputAmounts; + assert(m_amount >= m_firstOutput + m_fee); + decomposeAmount(m_amount - m_firstOutput - m_fee, m_currency.defaultDustThreshold(), outputAmounts); + std::sort(outputAmounts.begin(), outputAmounts.end()); + + if (m_firstOutput != 0) { + builder.addOutput(m_firstOutput, address); + } + + for (auto outAmount : outputAmounts) { + builder.addOutput(outAmount, address); + } + + return builder.build(); +} + +Transaction FusionTransactionBuilder::buildTx() const { + return convertTx(*buildReader()); +} + +Transaction FusionTransactionBuilder::createFusionTransactionBySize(size_t targetSize) { + auto tx = buildReader(); + + size_t realSize = tx->getTransactionData().size(); + if (realSize < targetSize) { + setExtraSize(targetSize - realSize); + tx = buildReader(); + + realSize = tx->getTransactionData().size(); + if (realSize > targetSize) { + setExtraSize(getExtraSize() - 1); + tx = buildReader(); + } + } + + return convertTx(*tx); +} diff --git a/tests/UnitTests/TransactionApiHelpers.h b/tests/UnitTests/TransactionApiHelpers.h index c7c9e23e..d12bf8f4 100644 --- a/tests/UnitTests/TransactionApiHelpers.h +++ b/tests/UnitTests/TransactionApiHelpers.h @@ -116,59 +116,93 @@ namespace { namespace CryptoNote { - class TestTransactionBuilder { - public: +class TestTransactionBuilder { +public: - TestTransactionBuilder(); - TestTransactionBuilder(const BinaryArray& txTemplate, const Crypto::SecretKey& secretKey); + TestTransactionBuilder(); + TestTransactionBuilder(const BinaryArray& txTemplate, const Crypto::SecretKey& secretKey); - PublicKey getTransactionPublicKey() const; - void setUnlockTime(uint64_t time); + PublicKey getTransactionPublicKey() const; + void appendExtra(const BinaryArray& extraData); + void setUnlockTime(uint64_t time); - // inputs - size_t addTestInput(uint64_t amount, const AccountKeys& senderKeys = generateAccountKeys()); - size_t addTestInput(uint64_t amount, std::vector gouts, const AccountKeys& senderKeys = generateAccountKeys()); - void addTestMultisignatureInput(uint64_t amount, const TransactionOutputInformation& t); - size_t addFakeMultisignatureInput(uint64_t amount, uint32_t globalOutputIndex, size_t signatureCount); - void addInput(const AccountKeys& senderKeys, const TransactionOutputInformation& t); + // inputs + size_t addTestInput(uint64_t amount, const AccountKeys& senderKeys = generateAccountKeys()); + size_t addTestInput(uint64_t amount, std::vector gouts, const AccountKeys& senderKeys = generateAccountKeys()); + void addTestMultisignatureInput(uint64_t amount, const TransactionOutputInformation& t); + size_t addFakeMultisignatureInput(uint64_t amount, uint32_t globalOutputIndex, size_t signatureCount); + void addInput(const AccountKeys& senderKeys, const TransactionOutputInformation& t); - // outputs - TransactionOutputInformationIn addTestKeyOutput(uint64_t amount, uint32_t globalOutputIndex, const AccountKeys& senderKeys = generateAccountKeys()); - TransactionOutputInformationIn addTestMultisignatureOutput(uint64_t amount, uint32_t globalOutputIndex); - TransactionOutputInformationIn addTestMultisignatureOutput(uint64_t amount, std::vector& addresses, uint32_t globalOutputIndex); - size_t addOutput(uint64_t amount, const AccountPublicAddress& to); - size_t addOutput(uint64_t amount, const KeyOutput& out); - size_t addOutput(uint64_t amount, const MultisignatureOutput& out); + // outputs + TransactionOutputInformationIn addTestKeyOutput(uint64_t amount, uint32_t globalOutputIndex, const AccountKeys& senderKeys = generateAccountKeys()); + TransactionOutputInformationIn addTestMultisignatureOutput(uint64_t amount, uint32_t globalOutputIndex); + TransactionOutputInformationIn addTestMultisignatureOutput(uint64_t amount, std::vector& addresses, uint32_t globalOutputIndex); + size_t addOutput(uint64_t amount, const AccountPublicAddress& to); + size_t addOutput(uint64_t amount, const KeyOutput& out); + size_t addOutput(uint64_t amount, const MultisignatureOutput& out); - // final step - std::unique_ptr build(); + // final step + std::unique_ptr build(); - // get built transaction hash (call only after build) - Crypto::Hash getTransactionHash() const; + // get built transaction hash (call only after build) + Crypto::Hash getTransactionHash() const; - private: +private: - void derivePublicKey(const AccountKeys& reciever, const Crypto::PublicKey& srcTxKey, size_t outputIndex, PublicKey& ephemeralKey) { - Crypto::KeyDerivation derivation; - Crypto::generate_key_derivation(srcTxKey, reinterpret_cast(reciever.viewSecretKey), derivation); - Crypto::derive_public_key(derivation, outputIndex, - reinterpret_cast(reciever.address.spendPublicKey), - reinterpret_cast(ephemeralKey)); - } + void derivePublicKey(const AccountKeys& reciever, const Crypto::PublicKey& srcTxKey, size_t outputIndex, PublicKey& ephemeralKey) { + Crypto::KeyDerivation derivation; + Crypto::generate_key_derivation(srcTxKey, reinterpret_cast(reciever.viewSecretKey), derivation); + Crypto::derive_public_key(derivation, outputIndex, + reinterpret_cast(reciever.address.spendPublicKey), + reinterpret_cast(ephemeralKey)); + } - struct MsigInfo { - PublicKey transactionKey; - size_t outputIndex; - std::vector accounts; - }; - - std::unordered_map> keys; - std::unordered_map msigInputs; - - std::unique_ptr tx; - Crypto::Hash transactionHash; + struct MsigInfo { + PublicKey transactionKey; + size_t outputIndex; + std::vector accounts; }; + std::unordered_map> keys; + std::unordered_map msigInputs; + + std::unique_ptr tx; + Crypto::Hash transactionHash; +}; + +class FusionTransactionBuilder { +public: + FusionTransactionBuilder(const Currency& currency, uint64_t amount); + + uint64_t getAmount() const; + void setAmount(uint64_t val); + + uint64_t getFirstOutput() const; + void setFirstOutput(uint64_t val); + + uint64_t getFee() const; + void setFee(uint64_t val); + + size_t getExtraSize() const; + void setExtraSize(size_t val); + + size_t getInputCount() const; + void setInputCount(size_t val); + + std::unique_ptr buildReader() const; + Transaction buildTx() const; + + Transaction createFusionTransactionBySize(size_t targetSize); + +private: + const Currency& m_currency; + uint64_t m_amount; + uint64_t m_firstOutput; + uint64_t m_fee; + size_t m_extraSize; + size_t m_inputCount; +}; + } namespace CryptoNote { diff --git a/tests/UnitTests/TransactionPool.cpp b/tests/UnitTests/TransactionPool.cpp index 967856b2..fa89195e 100755 --- a/tests/UnitTests/TransactionPool.cpp +++ b/tests/UnitTests/TransactionPool.cpp @@ -31,6 +31,8 @@ #include #include +#include "TransactionApiHelpers.h" + using namespace CryptoNote; using namespace CryptoNote; @@ -656,3 +658,161 @@ TEST_F(tx_pool, RecentlyDeletedTxInfoIsSerializedAndDeserialized) { ASSERT_EQ(1, pool->get_transactions_count()); } + +TEST_F(tx_pool, TxPoolAcceptsValidFusionTransaction) { + TransactionValidator validator; + FakeTimeProvider timeProvider; + std::unique_ptr pool(new tx_memory_pool(currency, validator, timeProvider, logger)); + ASSERT_TRUE(pool->init(m_configDir.string())); + + FusionTransactionBuilder builder(currency, 10 * currency.defaultDustThreshold()); + auto tx = builder.buildTx(); + tx_verification_context tvc = boost::value_initialized(); + + ASSERT_TRUE(pool->add_tx(tx, tvc, false)); + ASSERT_TRUE(tvc.m_added_to_pool); + ASSERT_TRUE(tvc.m_should_be_relayed); + ASSERT_FALSE(tvc.m_verifivation_failed); + ASSERT_FALSE(tvc.m_verifivation_impossible); +} + +TEST_F(tx_pool, TxPoolDoesNotAcceptInvalidFusionTransaction) { + TransactionValidator validator; + FakeTimeProvider timeProvider; + std::unique_ptr pool(new tx_memory_pool(currency, validator, timeProvider, logger)); + ASSERT_TRUE(pool->init(m_configDir.string())); + + FusionTransactionBuilder builder(currency, 10 * currency.defaultDustThreshold()); + builder.setInputCount(currency.fusionTxMinInputCount() - 1); + auto tx = builder.buildTx(); + tx_verification_context tvc = boost::value_initialized(); + + ASSERT_FALSE(pool->add_tx(tx, tvc, false)); + ASSERT_FALSE(tvc.m_added_to_pool); + ASSERT_FALSE(tvc.m_should_be_relayed); + ASSERT_TRUE(tvc.m_verifivation_failed); + ASSERT_FALSE(tvc.m_verifivation_impossible); +} + +namespace { + +const size_t TEST_FUSION_TX_COUNT_PER_BLOCK = 3; +const size_t TEST_TX_COUNT_UP_TO_MEDIAN = 10; +const size_t TEST_MAX_TX_COUNT_PER_BLOCK = TEST_TX_COUNT_UP_TO_MEDIAN * 125 / 100; +const size_t TEST_TRANSACTION_SIZE = 2000; +const size_t TEST_FUSION_TX_MAX_SIZE = TEST_FUSION_TX_COUNT_PER_BLOCK * TEST_TRANSACTION_SIZE; +const size_t TEST_MEDIAN_SIZE = TEST_TX_COUNT_UP_TO_MEDIAN * TEST_TRANSACTION_SIZE; + +Transaction createTestFusionTransaction(const Currency& currency) { + FusionTransactionBuilder builder(currency, 30 * currency.defaultDustThreshold()); + return builder.createFusionTransactionBySize(TEST_TRANSACTION_SIZE); +} + +Transaction createTestOrdinaryTransactionWithExtra(const Currency& currency, size_t extraSize) { + TestTransactionBuilder builder; + if (extraSize != 0) { + builder.appendExtra(BinaryArray(extraSize, 0)); + } + + builder.addTestInput(100 * currency.minimumFee()); + builder.addTestKeyOutput(99 * currency.minimumFee(), 0); + return convertTx(*builder.build()); +} + +Transaction createTestOrdinaryTransaction(const Currency& currency) { + auto tx = createTestOrdinaryTransactionWithExtra(currency, 0); + size_t realSize = getObjectBinarySize(tx); + if (realSize < TEST_TRANSACTION_SIZE) { + size_t extraSize = TEST_TRANSACTION_SIZE - realSize; + tx = createTestOrdinaryTransactionWithExtra(currency, extraSize); + + realSize = getObjectBinarySize(tx); + if (realSize > TEST_TRANSACTION_SIZE) { + extraSize -= realSize - TEST_TRANSACTION_SIZE; + tx = createTestOrdinaryTransactionWithExtra(currency, extraSize); + } + } + + return tx; +} + +class TxPool_FillBlockTemplate : public tx_pool { +public: + TxPool_FillBlockTemplate() : + tx_pool() { + currency = CryptoNote::CurrencyBuilder(logger).fusionTxMaxSize(TEST_FUSION_TX_MAX_SIZE).blockGrantedFullRewardZone(TEST_MEDIAN_SIZE).currency(); + } + + void doTest(size_t poolOrdinaryTxCount, size_t poolFusionTxCount, size_t expectedBlockOrdinaryTxCount, size_t expectedBlockFusionTxCount) { + TransactionValidator validator; + FakeTimeProvider timeProvider; + std::unique_ptr pool(new tx_memory_pool(currency, validator, timeProvider, logger)); + ASSERT_TRUE(pool->init(m_configDir.string())); + + std::unordered_map ordinaryTxs; + for (size_t i = 0; i < poolOrdinaryTxCount; ++i) { + auto tx = createTestOrdinaryTransaction(currency); + ordinaryTxs.emplace(getObjectHash(tx), std::move(tx)); + } + + std::unordered_map fusionTxs; + for (size_t i = 0; i < poolFusionTxCount; ++i) { + auto tx = createTestFusionTransaction(currency); + fusionTxs.emplace(getObjectHash(tx), std::move(tx)); + } + + for (auto pair : ordinaryTxs) { + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool->add_tx(pair.second, tvc, false)); + } + + for (auto pair : fusionTxs) { + tx_verification_context tvc = boost::value_initialized(); + ASSERT_TRUE(pool->add_tx(pair.second, tvc, false)); + } + + Block block; + size_t totalSize; + uint64_t totalFee; + ASSERT_TRUE(pool->fill_block_template(block, currency.blockGrantedFullRewardZone(), std::numeric_limits::max(), 0, totalSize, totalFee)); + + size_t fusionTxCount = 0; + size_t ordinaryTxCount = 0; + for (auto txHash : block.transactionHashes) { + if (fusionTxs.count(txHash) > 0) { + ++fusionTxCount; + } else { + ++ordinaryTxCount; + } + } + + ASSERT_EQ(expectedBlockOrdinaryTxCount, ordinaryTxCount); + ASSERT_EQ(expectedBlockFusionTxCount, fusionTxCount); + } +}; + +} + +TEST_F(TxPool_FillBlockTemplate, TxPoolAddsFusionTransactionsToBlockTemplateNoMoreThanLimit) { + ASSERT_NO_FATAL_FAILURE(doTest(TEST_MAX_TX_COUNT_PER_BLOCK, + TEST_MAX_TX_COUNT_PER_BLOCK, + TEST_MAX_TX_COUNT_PER_BLOCK - TEST_FUSION_TX_COUNT_PER_BLOCK, + TEST_FUSION_TX_COUNT_PER_BLOCK)); +} + +TEST_F(TxPool_FillBlockTemplate, TxPoolAddsFusionTransactionsUpToMedianAfterOrdinaryTransactions) { + static_assert(TEST_MAX_TX_COUNT_PER_BLOCK > 2, "TEST_MAX_TX_COUNT_PER_BLOCK > 2"); + ASSERT_NO_FATAL_FAILURE(doTest(2, TEST_MAX_TX_COUNT_PER_BLOCK, 2, TEST_TX_COUNT_UP_TO_MEDIAN - 2)); +} + +TEST_F(TxPool_FillBlockTemplate, TxPoolAddsFusionTransactionsUpToMedianIfThereAreNoOrdinaryTransactions) { + ASSERT_NO_FATAL_FAILURE(doTest(0, TEST_MAX_TX_COUNT_PER_BLOCK, 0, TEST_TX_COUNT_UP_TO_MEDIAN)); +} + +TEST_F(TxPool_FillBlockTemplate, TxPoolContinuesToAddOrdinaryTransactionsUpTo125PerCentOfMedianAfterAddingFusionTransactions) { + size_t fusionTxCount = TEST_FUSION_TX_COUNT_PER_BLOCK - 1; + ASSERT_NO_FATAL_FAILURE(doTest(TEST_MAX_TX_COUNT_PER_BLOCK, + fusionTxCount, + TEST_MAX_TX_COUNT_PER_BLOCK - fusionTxCount, + fusionTxCount)); +}