danicoin/tests/UnitTests/TransactionPool.cpp
2016-01-18 15:33:29 +00:00

807 lines
26 KiB
C++
Executable file

// Copyright (c) 2011-2016 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "gtest/gtest.h"
#include <algorithm>
#include <boost/filesystem/operations.hpp>
#include "CryptoNoteCore/Account.h"
#include "CryptoNoteCore/CryptoNoteFormatUtils.h"
#include "CryptoNoteCore/CryptoNoteTools.h"
#include "CryptoNoteCore/Currency.h"
#include "CryptoNoteCore/TransactionExtra.h"
#include "CryptoNoteCore/TransactionPool.h"
#include <Logging/ConsoleLogger.h>
#include <Logging/LoggerGroup.h>
#include "TransactionApiHelpers.h"
using namespace CryptoNote;
using namespace CryptoNote;
class TransactionValidator : public CryptoNote::ITransactionValidator {
virtual bool checkTransactionInputs(const CryptoNote::Transaction& tx, BlockInfo& maxUsedBlock) override {
return true;
}
virtual bool checkTransactionInputs(const CryptoNote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed) override {
return true;
}
virtual bool haveSpentKeyImages(const CryptoNote::Transaction& tx) override {
return false;
}
virtual bool checkTransactionSize(size_t blobSize) override {
return true;
}
};
class FakeTimeProvider : public ITimeProvider {
public:
FakeTimeProvider(time_t currentTime = time(nullptr))
: timeNow(currentTime) {}
time_t timeNow;
virtual time_t now() override { return timeNow; }
};
class TestTransactionGenerator {
public:
TestTransactionGenerator(const CryptoNote::Currency& currency, size_t ringSize) :
m_currency(currency),
m_ringSize(ringSize),
m_miners(ringSize),
m_miner_txs(ringSize),
m_public_keys(ringSize),
m_public_key_ptrs(ringSize)
{
rv_acc.generate();
}
bool createSources() {
size_t real_source_idx = m_ringSize / 2;
std::vector<TransactionSourceEntry::OutputEntry> output_entries;
for (uint32_t i = 0; i < m_ringSize; ++i)
{
m_miners[i].generate();
if (!m_currency.constructMinerTx(0, 0, 0, 2, 0, m_miners[i].getAccountKeys().address, m_miner_txs[i])) {
return false;
}
KeyOutput tx_out = boost::get<KeyOutput>(m_miner_txs[i].outputs[0].target);
output_entries.push_back(std::make_pair(i, tx_out.key));
m_public_keys[i] = tx_out.key;
m_public_key_ptrs[i] = &m_public_keys[i];
}
m_source_amount = m_miner_txs[0].outputs[0].amount;
TransactionSourceEntry source_entry;
source_entry.amount = m_source_amount;
source_entry.realTransactionPublicKey = getTransactionPublicKeyFromExtra(m_miner_txs[real_source_idx].extra);
source_entry.realOutputIndexInTransaction = 0;
source_entry.outputs.swap(output_entries);
source_entry.realOutput = real_source_idx;
m_sources.push_back(source_entry);
m_realSenderKeys = m_miners[real_source_idx].getAccountKeys();
return true;
}
void construct(uint64_t amount, uint64_t fee, size_t outputs, Transaction& tx) {
std::vector<TransactionDestinationEntry> destinations;
uint64_t amountPerOut = (amount - fee) / outputs;
for (size_t i = 0; i < outputs; ++i) {
destinations.push_back(TransactionDestinationEntry(amountPerOut, rv_acc.getAccountKeys().address));
}
constructTransaction(m_realSenderKeys, m_sources, destinations, std::vector<uint8_t>(), tx, 0, m_logger);
}
std::vector<AccountBase> m_miners;
std::vector<Transaction> m_miner_txs;
std::vector<TransactionSourceEntry> m_sources;
std::vector<Crypto::PublicKey> m_public_keys;
std::vector<const Crypto::PublicKey*> m_public_key_ptrs;
Logging::LoggerGroup m_logger;
const CryptoNote::Currency& m_currency;
const size_t m_ringSize;
AccountKeys m_realSenderKeys;
uint64_t m_source_amount;
AccountBase rv_acc;
};
class tx_pool : public ::testing::Test {
public:
tx_pool() :
currency(CryptoNote::CurrencyBuilder(logger).currency()) {}
protected:
virtual void SetUp() override {
m_configDir = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("test_data_%%%%%%%%%%%%");
}
virtual void TearDown() override {
boost::system::error_code ignoredErrorCode;
boost::filesystem::remove_all(m_configDir, ignoredErrorCode);
}
protected:
Logging::ConsoleLogger logger;
CryptoNote::Currency currency;
boost::filesystem::path m_configDir;
};
namespace
{
static const size_t textMaxCumulativeSize = std::numeric_limits<size_t>::max();
void GenerateTransaction(const CryptoNote::Currency& currency, Transaction& tx, uint64_t fee, size_t outputs) {
TestTransactionGenerator txGenerator(currency, 1);
txGenerator.createSources();
txGenerator.construct(txGenerator.m_source_amount, fee, outputs, tx);
}
template <typename Validator, typename TimeProvider>
class TestPool : public tx_memory_pool {
public:
Validator validator;
TimeProvider timeProvider;
TestPool(const CryptoNote::Currency& currency, Logging::ILogger& logger) :
tx_memory_pool(currency, validator, timeProvider, logger) {}
};
class TxTestBase {
public:
TxTestBase(size_t ringSize) :
m_currency(CryptoNote::CurrencyBuilder(m_logger).currency()),
txGenerator(m_currency, ringSize),
pool(m_currency, validator, m_time, m_logger)
{
txGenerator.createSources();
}
void construct(uint64_t fee, size_t outputs, Transaction& tx) {
txGenerator.construct(txGenerator.m_source_amount, fee, outputs, tx);
}
Logging::ConsoleLogger m_logger;
CryptoNote::Currency m_currency;
CryptoNote::RealTimeProvider m_time;
TestTransactionGenerator txGenerator;
TransactionValidator validator;
tx_memory_pool pool;
};
void InitBlock(Block& bl, uint8_t majorVersion = BLOCK_MAJOR_VERSION_1) {
bl.majorVersion = majorVersion;
bl.minorVersion = 0;
bl.nonce = 0;
bl.timestamp = time(0);
bl.previousBlockHash = NULL_HASH;
}
}
TEST_F(tx_pool, add_one_tx)
{
TxTestBase test(1);
Transaction tx;
test.construct(test.m_currency.minimumFee(), 1, tx);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(test.pool.add_tx(tx, tvc, false));
ASSERT_FALSE(tvc.m_verifivation_failed);
};
TEST_F(tx_pool, take_tx)
{
TxTestBase test(1);
Transaction tx;
test.construct(test.m_currency.minimumFee(), 1, tx);
auto txhash = getObjectHash(tx);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(test.pool.add_tx(tx, tvc, false));
ASSERT_FALSE(tvc.m_verifivation_failed);
Transaction txOut;
size_t blobSize;
uint64_t fee = 0;
ASSERT_TRUE(test.pool.take_tx(txhash, txOut, blobSize, fee));
ASSERT_EQ(fee, test.m_currency.minimumFee());
ASSERT_EQ(tx, txOut);
};
TEST_F(tx_pool, double_spend_tx)
{
TxTestBase test(1);
Transaction tx, tx_double;
test.construct(test.m_currency.minimumFee(), 1, tx);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(test.pool.add_tx(tx, tvc, false));
ASSERT_FALSE(tvc.m_verifivation_failed);
test.txGenerator.rv_acc.generate(); // generate new receiver address
test.construct(test.m_currency.minimumFee(), 1, tx_double);
ASSERT_FALSE(test.pool.add_tx(tx_double, tvc, false));
ASSERT_TRUE(tvc.m_verifivation_failed);
}
TEST_F(tx_pool, fillblock_same_fee)
{
TestPool<TransactionValidator, RealTimeProvider> pool(currency, logger);
uint64_t fee = currency.minimumFee();
std::unordered_map<Crypto::Hash, std::unique_ptr<Transaction>> transactions;
// generate transactions
for (int i = 1; i <= 50; ++i) {
TestTransactionGenerator txGenerator(currency, 1);
txGenerator.createSources();
std::unique_ptr<Transaction> txptr(new Transaction);
Transaction& tx = *txptr;
txGenerator.construct(txGenerator.m_source_amount, fee, i, tx);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
transactions[getObjectHash(tx)] = std::move(txptr);
}
Block bl;
InitBlock(bl);
size_t totalSize = 0;
uint64_t txFee = 0;
uint64_t median = 5000;
ASSERT_TRUE(pool.fill_block_template(bl, median, textMaxCumulativeSize, 0, totalSize, txFee));
ASSERT_TRUE(totalSize <= 2 * median);
// now, check that the block is opimally filled
// if fee is fixed, transactions with smaller number of outputs should be included
size_t maxOuts = 0;
for (auto& th : bl.transactionHashes) {
auto iter = transactions.find(th);
ASSERT_TRUE(iter != transactions.end());
size_t txouts = iter->second->outputs.size();
if (txouts > maxOuts)
maxOuts = txouts;
}
ASSERT_TRUE(maxOuts <= bl.transactionHashes.size());
}
TEST_F(tx_pool, fillblock_same_size)
{
TestPool<TransactionValidator, RealTimeProvider> pool(currency, logger);
const uint64_t fee = currency.minimumFee();
const size_t totalTransactions = 50;
std::unordered_map<Crypto::Hash, std::unique_ptr<Transaction>> transactions;
// generate transactions
for (int i = 0; i <= totalTransactions; ++i) {
TestTransactionGenerator txGenerator(currency, 1);
txGenerator.createSources();
std::unique_ptr<Transaction> txptr(new Transaction);
Transaction& tx = *txptr;
// interleave fee and fee*2
txGenerator.construct(txGenerator.m_source_amount, fee + (fee * (i&1)), 1, tx);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
transactions[getObjectHash(tx)] = std::move(txptr);
}
Block bl;
InitBlock(bl);
size_t totalSize = 0;
uint64_t txFee = 0;
uint64_t median = 5000;
ASSERT_TRUE(pool.fill_block_template(bl, median, textMaxCumulativeSize, 0, totalSize, txFee));
ASSERT_TRUE(totalSize <= 2 * median);
// check that fill_block_template prefers transactions with double fee
size_t doubleFee = 0;
for (auto& th : bl.transactionHashes) {
auto iter = transactions.find(th);
ASSERT_TRUE(iter != transactions.end());
if (get_tx_fee(*iter->second) > fee)
++doubleFee;
}
ASSERT_TRUE(doubleFee == std::min(bl.transactionHashes.size(), totalTransactions / 2));
}
TEST_F(tx_pool, cleanup_stale_tx)
{
TestPool<TransactionValidator, FakeTimeProvider> pool(currency, logger);
const uint64_t fee = currency.minimumFee();
time_t startTime = pool.timeProvider.now();
for (int i = 0; i < 3; ++i) {
Transaction tx;
GenerateTransaction(currency, tx, fee, 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false)); // main chain
ASSERT_TRUE(tvc.m_added_to_pool);
pool.timeProvider.timeNow += 60 * 60 * 2; // add 2 hours
}
for (int i = 0; i < 5; ++i) {
Transaction tx;
GenerateTransaction(currency, tx, fee, 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, true)); // alternative chain
ASSERT_TRUE(tvc.m_added_to_pool);
pool.timeProvider.timeNow += 60 * 60 * 2; // add 2 hours
}
ASSERT_EQ(8, pool.get_transactions_count());
pool.timeProvider.timeNow = startTime + currency.mempoolTxLiveTime() + 3*60*60;
pool.on_idle(); // 2 transactions should be removed
ASSERT_EQ(6, pool.get_transactions_count());
pool.timeProvider.timeNow = startTime + currency.mempoolTxFromAltBlockLiveTime() + (3*2+3) * 60 * 60;
pool.on_idle(); // all transactions from main chain and 2 transactions from altchain should be removed
ASSERT_EQ(3, pool.get_transactions_count());
}
TEST_F(tx_pool, add_tx_after_cleanup)
{
TestPool<TransactionValidator, FakeTimeProvider> pool(currency, logger);
const uint64_t fee = currency.minimumFee();
time_t startTime = pool.timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, fee, 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false)); // main chain
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t cleanupTime = startTime + currency.mempoolTxLiveTime() + 1;
pool.timeProvider.timeNow = cleanupTime;
pool.on_idle();
pool.timeProvider.timeNow = cleanupTime + currency.numberOfPeriodsToForgetTxDeletedFromPool() * currency.mempoolTxLiveTime() + 1;
pool.on_idle();
ASSERT_EQ(0, pool.get_transactions_count());
// add again
ASSERT_TRUE(pool.add_tx(tx, tvc, false)); // main chain
ASSERT_TRUE(tvc.m_added_to_pool);
ASSERT_EQ(1, pool.get_transactions_count());
}
TEST_F(tx_pool, RecentlyDeletedTransactionCannotBeAddedToTxPoolAgain) {
TestPool<TransactionValidator, FakeTimeProvider> pool(currency, logger);
uint64_t startTime = pool.timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
pool.timeProvider.timeNow = deleteTime;
pool.on_idle();
ASSERT_EQ(0, pool.get_transactions_count());
// Try to add tx again
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_FALSE(tvc.m_added_to_pool);
ASSERT_FALSE(tvc.m_should_be_relayed);
ASSERT_FALSE(tvc.m_verifivation_failed);
ASSERT_FALSE(tvc.m_verifivation_impossible);
ASSERT_EQ(0, pool.get_transactions_count());
}
TEST_F(tx_pool, RecentlyDeletedTransactionCanBeAddedAgainAfterSomeTime) {
TestPool<TransactionValidator, FakeTimeProvider> pool(currency, logger);
uint64_t startTime = pool.timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
pool.timeProvider.timeNow = deleteTime;
pool.on_idle();
ASSERT_EQ(0, pool.get_transactions_count());
uint64_t forgetDeletedTxTime = deleteTime + currency.numberOfPeriodsToForgetTxDeletedFromPool() * currency.mempoolTxLiveTime() + 1;
pool.timeProvider.timeNow = forgetDeletedTxTime;
pool.on_idle();
// Try to add tx again
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);
ASSERT_EQ(1, pool.get_transactions_count());
}
TEST_F(tx_pool, RecentlyDeletedTransactionCanBeAddedToTxPoolIfItIsReceivedInBlock) {
TestPool<TransactionValidator, FakeTimeProvider> pool(currency, logger);
uint64_t startTime = pool.timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool.add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
pool.timeProvider.timeNow = deleteTime;
pool.on_idle();
ASSERT_EQ(0, pool.get_transactions_count());
// Try to add tx again
ASSERT_TRUE(pool.add_tx(tx, tvc, true));
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);
ASSERT_EQ(1, pool.get_transactions_count());
}
TEST_F(tx_pool, OldTransactionIsDeletedDuringTxPoolInitialization) {
TransactionValidator validator;
FakeTimeProvider timeProvider;
std::unique_ptr<tx_memory_pool> pool(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
uint64_t startTime = timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool->add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
ASSERT_TRUE(pool->deinit());
pool.reset();
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
timeProvider.timeNow = deleteTime;
pool.reset(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
ASSERT_EQ(0, pool->get_transactions_count());
}
TEST_F(tx_pool, TransactionThatWasDeletedLongAgoIsForgottenDuringTxPoolInitialization) {
TransactionValidator validator;
FakeTimeProvider timeProvider;
std::unique_ptr<tx_memory_pool> pool(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
uint64_t startTime = timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool->add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
timeProvider.timeNow = deleteTime;
pool->on_idle();
ASSERT_EQ(0, pool->get_transactions_count());
ASSERT_TRUE(pool->deinit());
pool.reset();
uint64_t forgetDeletedTxTime = deleteTime + currency.numberOfPeriodsToForgetTxDeletedFromPool() * currency.mempoolTxLiveTime() + 1;
timeProvider.timeNow = forgetDeletedTxTime;
pool.reset(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
// Try to add tx again
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);
ASSERT_EQ(1, pool->get_transactions_count());
}
TEST_F(tx_pool, RecentlyDeletedTxInfoIsSerializedAndDeserialized) {
TransactionValidator validator;
FakeTimeProvider timeProvider;
std::unique_ptr<tx_memory_pool> pool(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
uint64_t startTime = timeProvider.now();
Transaction tx;
GenerateTransaction(currency, tx, currency.minimumFee(), 1);
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
ASSERT_TRUE(pool->add_tx(tx, tvc, false));
ASSERT_TRUE(tvc.m_added_to_pool);
uint64_t deleteTime = startTime + currency.mempoolTxLiveTime() + 1;
timeProvider.timeNow = deleteTime;
pool->on_idle();
ASSERT_EQ(0, pool->get_transactions_count());
ASSERT_TRUE(pool->deinit());
pool.reset(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
uint64_t timeBeforeCleanupDeletedTx = deleteTime + currency.numberOfPeriodsToForgetTxDeletedFromPool() * currency.mempoolTxLiveTime();
timeProvider.timeNow = timeBeforeCleanupDeletedTx;
pool->on_idle();
ASSERT_TRUE(pool->add_tx(tx, tvc, false));
ASSERT_FALSE(tvc.m_added_to_pool);
ASSERT_FALSE(tvc.m_should_be_relayed);
ASSERT_FALSE(tvc.m_verifivation_failed);
ASSERT_FALSE(tvc.m_verifivation_impossible);
ASSERT_EQ(0, pool->get_transactions_count());
timeProvider.timeNow = timeBeforeCleanupDeletedTx + 61;
pool->on_idle();
// Try to add tx again
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);
ASSERT_EQ(1, pool->get_transactions_count());
}
TEST_F(tx_pool, TxPoolAcceptsValidFusionTransaction) {
TransactionValidator validator;
FakeTimeProvider timeProvider;
std::unique_ptr<tx_memory_pool> 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<tx_verification_context>();
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<tx_memory_pool> 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<tx_verification_context>();
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 = 2 * TEST_TX_COUNT_UP_TO_MEDIAN;
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_MINER_TX_BLOB_RESERVED_SIZE = 600;
const size_t TEST_MEDIAN_SIZE = TEST_TX_COUNT_UP_TO_MEDIAN * TEST_TRANSACTION_SIZE + TEST_MINER_TX_BLOB_RESERVED_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<tx_memory_pool> pool(new tx_memory_pool(currency, validator, timeProvider, logger));
ASSERT_TRUE(pool->init(m_configDir.string()));
std::unordered_map<Crypto::Hash, Transaction> ordinaryTxs;
for (size_t i = 0; i < poolOrdinaryTxCount; ++i) {
auto tx = createTestOrdinaryTransaction(currency);
ordinaryTxs.emplace(getObjectHash(tx), std::move(tx));
}
std::unordered_map<Crypto::Hash, Transaction> 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<tx_verification_context>();
ASSERT_TRUE(pool->add_tx(pair.second, tvc, false));
}
for (auto pair : fusionTxs) {
tx_verification_context tvc = boost::value_initialized<tx_verification_context>();
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<size_t>::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));
}