danicoin/tests/unit_tests/test_TransfersConsumer.cpp

905 lines
32 KiB
C++
Raw Normal View History

// Copyright (c) 2012-2014, 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 <http://www.gnu.org/licenses/>.
#include "gtest/gtest.h"
#include "cryptonote_core/TransactionApi.h"
#include "transfers/TransfersConsumer.h"
#include <algorithm>
#include <limits>
#include <transfers/CommonTypes.h>
#include <cryptonote_core/TransactionApi.h>
#include "INodeStubs.h"
#include "TransactionApiHelpers.h"
#include "TransfersObserver.h"
#include "TestBlockchainGenerator.h"
using namespace CryptoNote;
AccountSubscription getAccountSubscription(const AccountKeys& accountKeys) {
AccountSubscription subscription;
subscription.keys = accountKeys;
return subscription;
}
AccountKeys getAccountKeysWithViewKey(const PublicKey& publicViewKey, const SecretKey& secretViewKey) {
KeyPair viewKp;
viewKp.publicKey = publicViewKey;
viewKp.secretKey = secretViewKey;
AccountKeys accountKeys = accountKeysFromKeypairs(viewKp, generateKeys());
return accountKeys;
}
cryptonote::Transaction convertTx(ITransactionReader& tx) {
auto blob = tx.getTransactionData();
cryptonote::blobdata data(reinterpret_cast<const char*>(blob.data()), blob.size());
cryptonote::Transaction oldTx;
cryptonote::parse_and_validate_tx_from_blob(data, oldTx); // ignore return code
return oldTx;
}
class TransfersConsumerTest : public ::testing::Test {
public:
TransfersConsumerTest();
protected:
ITransfersSubscription& addSubscription(TransfersConsumer& consumer, const AccountKeys& acc, uint64_t height = 0,
uint64_t timestamp = 0, size_t age = 0)
{
AccountSubscription subscription = getAccountSubscription(acc);
subscription.syncStart.height = height;
subscription.syncStart.timestamp = timestamp;
subscription.transactionSpendableAge = age;
return consumer.addSubscription(subscription);
}
ITransfersSubscription& addSubscription(const AccountKeys& acc, uint64_t height = 0, uint64_t timestamp = 0, size_t age = 0) {
return addSubscription(m_consumer, acc, height, timestamp, age);
}
ITransfersSubscription& addSubscription(uint64_t height = 0, uint64_t timestamp = 0, size_t age = 0) {
return addSubscription(m_consumer, m_accountKeys, height, timestamp, age);
}
ITransfersSubscription& addSubscription(TransfersConsumer& consumer, uint64_t height = 0, uint64_t timestamp = 0, size_t age = 0) {
return addSubscription(consumer, m_accountKeys, height, timestamp, age);
}
AccountKeys generateAccount() {
return getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
}
cryptonote::Currency m_currency;
TestBlockchainGenerator m_generator;
INodeTrivialRefreshStub m_node;
AccountKeys m_accountKeys;
TransfersConsumer m_consumer;
};
TransfersConsumerTest::TransfersConsumerTest() :
m_currency(cryptonote::CurrencyBuilder().currency()),
m_generator(m_currency),
m_node(m_generator),
m_accountKeys(generateAccountKeys()),
m_consumer(m_currency, m_node, m_accountKeys.viewSecretKey)
{
}
bool amountFound(const std::vector<TransactionOutputInformation>& outs, uint64_t amount) {
return std::find_if(outs.begin(), outs.end(), [amount] (const TransactionOutputInformation& inf) { return inf.amount == amount; }) != outs.end();
}
AccountSubscription getAccountSubscriptionWithSyncStart(const AccountKeys& keys, uint64_t timestamp, uint64_t height) {
AccountSubscription subscription = getAccountSubscription(keys);
subscription.syncStart.timestamp = timestamp;
subscription.syncStart.height = height;
return subscription;
}
TEST_F(TransfersConsumerTest, addSubscription_Success) {
AccountSubscription subscription;
subscription.keys = m_accountKeys;
ITransfersSubscription& accountSubscription = m_consumer.addSubscription(subscription);
ASSERT_EQ(subscription.keys.address, accountSubscription.getAddress());
}
TEST_F(TransfersConsumerTest, addSubscription_WrongViewKey) {
AccountKeys accountKeys = generateAccountKeys();
AccountSubscription subscription = getAccountSubscription(accountKeys);
ASSERT_ANY_THROW(m_consumer.addSubscription(subscription));
}
TEST_F(TransfersConsumerTest, addSubscription_SameSubscription) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
ITransfersSubscription* ts1 = &m_consumer.addSubscription(subscription);
ITransfersSubscription* ts2 = &m_consumer.addSubscription(subscription);
ASSERT_EQ(ts1, ts2);
}
TEST_F(TransfersConsumerTest, removeSubscription_Success) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription);
ITransfersSubscription* ts = m_consumer.getSubscription(m_accountKeys.address);
ASSERT_NE(nullptr, ts);
m_consumer.removeSubscription(m_accountKeys.address);
ts = m_consumer.getSubscription(m_accountKeys.address);
ASSERT_EQ(nullptr, ts);
}
TEST_F(TransfersConsumerTest, removeSubscription_OneAddressLeft) {
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription1);
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
AccountSubscription subscription2 = getAccountSubscription(accountKeys);
m_consumer.addSubscription(subscription2);
ASSERT_FALSE(m_consumer.removeSubscription(subscription1.keys.address));
}
TEST_F(TransfersConsumerTest, removeSubscription_RemoveAllAddresses) {
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription1);
ASSERT_TRUE(m_consumer.removeSubscription(subscription1.keys.address));
}
TEST_F(TransfersConsumerTest, getSubscription_ReturnSameValueForSameAddress) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription);
ITransfersSubscription* ts1 = m_consumer.getSubscription(m_accountKeys.address);
ITransfersSubscription* ts2 = m_consumer.getSubscription(m_accountKeys.address);
ASSERT_EQ(ts1, ts2);
}
TEST_F(TransfersConsumerTest, getSubscription_ReturnNullForNonExistentAddr) {
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription1);
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
ASSERT_EQ(nullptr, m_consumer.getSubscription(accountKeys.address));
}
TEST_F(TransfersConsumerTest, getSubscriptions_Empty) {
std::vector<AccountAddress> subscriptions;
m_consumer.getSubscriptions(subscriptions);
ASSERT_TRUE(subscriptions.empty());
}
TEST_F(TransfersConsumerTest, getSubscriptions_TwoSubscriptions) {
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
m_consumer.addSubscription(subscription1);
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
AccountSubscription subscription2 = getAccountSubscription(accountKeys);
m_consumer.addSubscription(subscription2);
std::vector<AccountAddress> subscriptions;
m_consumer.getSubscriptions(subscriptions);
ASSERT_EQ(2, subscriptions.size());
ASSERT_NE(subscriptions.end(), std::find(subscriptions.begin(), subscriptions.end(), subscription1.keys.address));
ASSERT_NE(subscriptions.end(), std::find(subscriptions.begin(), subscriptions.end(), subscription2.keys.address));
}
TEST_F(TransfersConsumerTest, getSyncStart_Empty) {
auto syncStart = m_consumer.getSyncStart();
EXPECT_EQ(std::numeric_limits<uint64_t>::max(), syncStart.height);
EXPECT_EQ(std::numeric_limits<uint64_t>::max(), syncStart.timestamp);
}
TEST_F(TransfersConsumerTest, getSyncStart_OneSubscription) {
const uint64_t height = 1209384;
const uint64_t timestamp = 99284512;
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.height = height;
subscription.syncStart.timestamp = timestamp;
m_consumer.addSubscription(subscription);
auto sync = m_consumer.getSyncStart();
ASSERT_EQ(height, sync.height);
ASSERT_EQ(timestamp, sync.timestamp);
}
TEST_F(TransfersConsumerTest, getSyncStart_MinSyncSameSubscription) {
const uint64_t height = 1209384;
const uint64_t timestamp = 99284512;
const uint64_t minHeight = 120984;
const uint64_t minTimestamp = 9984512;
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
subscription1.syncStart.height = height;
subscription1.syncStart.timestamp = timestamp;
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
AccountSubscription subscription2 = getAccountSubscription(accountKeys);
subscription2.syncStart.height = minHeight;
subscription2.syncStart.timestamp = minTimestamp;
m_consumer.addSubscription(subscription1);
m_consumer.addSubscription(subscription2);
auto sync = m_consumer.getSyncStart();
ASSERT_EQ(minHeight, sync.height);
ASSERT_EQ(minTimestamp, sync.timestamp);
}
TEST_F(TransfersConsumerTest, getSyncStart_MinSyncDifferentSubscriptions) {
const uint64_t height = 1209384;
const uint64_t timestamp = 99284512;
const uint64_t minHeight = 120984;
const uint64_t minTimestamp = 9984512;
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
subscription1.syncStart.height = minHeight;
subscription1.syncStart.timestamp = timestamp;
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
AccountSubscription subscription2 = getAccountSubscription(accountKeys);
subscription2.syncStart.height = height;
subscription2.syncStart.timestamp = minTimestamp;
m_consumer.addSubscription(subscription1);
m_consumer.addSubscription(subscription2);
auto sync = m_consumer.getSyncStart();
ASSERT_EQ(minHeight, sync.height);
ASSERT_EQ(minTimestamp, sync.timestamp);
}
TEST_F(TransfersConsumerTest, getSyncStart_RemoveMinSyncSubscription) {
const uint64_t height = 1209384;
const uint64_t timestamp = 99284512;
const uint64_t minHeight = 120984;
const uint64_t minTimestamp = 9984512;
AccountSubscription subscription1 = getAccountSubscription(m_accountKeys);
subscription1.syncStart.height = height;
subscription1.syncStart.timestamp = timestamp;
AccountKeys accountKeys = getAccountKeysWithViewKey(m_accountKeys.address.viewPublicKey, m_accountKeys.viewSecretKey);
AccountSubscription subscription2 = getAccountSubscription(accountKeys);
subscription2.syncStart.height = minHeight;
subscription2.syncStart.timestamp = minTimestamp;
m_consumer.addSubscription(subscription1);
m_consumer.addSubscription(subscription2);
m_consumer.removeSubscription(subscription2.keys.address);
auto sync = m_consumer.getSyncStart();
ASSERT_EQ(height, sync.height);
ASSERT_EQ(timestamp, sync.timestamp);
}
TEST_F(TransfersConsumerTest, onBlockchainDetach) {
auto& container1 = addSubscription().getContainer();
auto keys = generateAccount();
auto& container2 = addSubscription(keys).getContainer();
std::shared_ptr<ITransaction> tx1 = createTransaction();
addTestInput(*tx1, 100);
addTestKeyOutput(*tx1, 50, 1, m_accountKeys);
std::shared_ptr<ITransaction> tx2 = createTransaction();
addTestInput(*tx1, 100);
addTestKeyOutput(*tx1, 50, 1, keys);
CompleteBlock blocks[3];
blocks[0].block = cryptonote::Block();
blocks[0].block->timestamp = 1233;
blocks[1].block = cryptonote::Block();
blocks[1].block->timestamp = 1234;
blocks[1].transactions.push_back(tx1);
blocks[2].block = cryptonote::Block();
blocks[2].block->timestamp = 1235;
blocks[2].transactions.push_back(tx2);
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 0, 3));
m_consumer.onBlockchainDetach(0);
std::vector<TransactionOutputInformation> trs;
container1.getOutputs(trs, ITransfersContainer::IncludeAll);
ASSERT_EQ(0, trs.size());
container2.getOutputs(trs, ITransfersContainer::IncludeAll);
ASSERT_EQ(0, trs.size());
}
TEST_F(TransfersConsumerTest, onNewBlocks_OneEmptyBlockOneFilled) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.height = 1;
subscription.syncStart.timestamp = 1234;
std::shared_ptr<ITransaction> ignoredTx(createTransaction());
addTestInput(*ignoredTx, 1000);
addTestKeyOutput(*ignoredTx, 123, 1, m_accountKeys);
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 2, m_accountKeys);
addTestKeyOutput(*tx, 850, 3, m_accountKeys);
CompleteBlock blocks[2];
blocks[0].transactions.push_back(ignoredTx);
blocks[1].block = cryptonote::Block();
blocks[1].block->timestamp = 1235;
blocks[1].transactions.push_back(tx);
ITransfersContainer& container = m_consumer.addSubscription(subscription).getContainer();
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 1, 2));
auto outs = container.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_TRUE(amountFound(outs, 850));
ASSERT_TRUE(amountFound(outs, 900));
auto ignoredOuts = container.getTransactionOutputs(ignoredTx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(0, ignoredOuts.size());
}
TEST_F(TransfersConsumerTest, onNewBlocks_DifferentTimestamps) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.timestamp = 12345;
subscription.syncStart.height = 12;
std::shared_ptr<ITransaction> ignoredTx(createTransaction());
addTestInput(*ignoredTx, 1000);
addTestKeyOutput(*ignoredTx, 123, 1, m_accountKeys);
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 2, m_accountKeys);
addTestKeyOutput(*tx, 850, 3, m_accountKeys);
CompleteBlock blocks[2];
blocks[0].transactions.push_back(ignoredTx);
blocks[0].block = cryptonote::Block();
blocks[0].block->timestamp = subscription.syncStart.timestamp - 1;
blocks[1].block = cryptonote::Block();
blocks[1].block->timestamp = subscription.syncStart.timestamp;
blocks[1].transactions.push_back(tx);
ITransfersContainer& container = m_consumer.addSubscription(subscription).getContainer();
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 2, 2));
auto ignoredOuts = container.getTransactionOutputs(ignoredTx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(0, ignoredOuts.size());
auto outs = container.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_TRUE(amountFound(outs, 850));
ASSERT_TRUE(amountFound(outs, 900));
}
TEST_F(TransfersConsumerTest, onNewBlocks_getTransactionOutsGlobalIndicesError) {
class INodeGlobalIndicesStub: public INodeDummyStub {
public:
virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash,
std::vector<uint64_t>& outsGlobalIndices, const Callback& callback) override {
callback(std::make_error_code(std::errc::operation_canceled));
};
};
INodeGlobalIndicesStub node;
TransfersConsumer consumer(m_currency, node, m_accountKeys.viewSecretKey);
auto subscription = getAccountSubscriptionWithSyncStart(m_accountKeys, 1234, 10);
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 2, m_accountKeys);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = subscription.syncStart.timestamp;
block.transactions.push_back(tx);
consumer.addSubscription(subscription);
ASSERT_FALSE(consumer.onNewBlocks(&block, subscription.syncStart.height, 1));
}
TEST_F(TransfersConsumerTest, onNewBlocks_updateHeight) {
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.timestamp = 2131;
subscription.syncStart.height = 32;
subscription.transactionSpendableAge = 5;
auto& container = m_consumer.addSubscription(subscription).getContainer();
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 0, m_accountKeys);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = subscription.syncStart.timestamp;
block.transactions.push_back(tx);
ASSERT_TRUE(m_consumer.onNewBlocks(&block, subscription.syncStart.height, 1));
ASSERT_EQ(900, container.balance(ITransfersContainer::IncludeAllLocked));
std::unique_ptr<CompleteBlock[]> blocks(new CompleteBlock[subscription.transactionSpendableAge]);
for (size_t i = 0; i < subscription.transactionSpendableAge; ++i) {
blocks[i].block = cryptonote::Block();
auto tr = createTransaction();
addTestInput(*tr, 1000);
addTestKeyOutput(*tr, 100, i + 1, generateAccountKeys());
}
ASSERT_TRUE(m_consumer.onNewBlocks(blocks.get(), subscription.syncStart.height + 1, subscription.transactionSpendableAge));
ASSERT_EQ(0, container.balance(ITransfersContainer::IncludeAllLocked));
ASSERT_EQ(900, container.balance(ITransfersContainer::IncludeAllUnlocked));
}
TEST_F(TransfersConsumerTest, onNewBlocks_DifferentSubscribers) {
auto& container1 = addSubscription().getContainer();
auto keys = generateAccount();
auto& container2 = addSubscription(keys).getContainer();
uint64_t amount1 = 900;
uint64_t amount2 = 850;
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, amount1, 0, m_accountKeys);
addTestKeyOutput(*tx, amount2, 1, keys);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(m_consumer.onNewBlocks(&block, 0, 1));
auto outs1 = container1.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outs1.size());
ASSERT_EQ(amount1, outs1[0].amount);
auto outs2 = container2.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outs2.size());
ASSERT_EQ(amount2, outs2[0].amount);
}
TEST_F(TransfersConsumerTest, onNewBlocks_MultisignatureTransaction) {
auto& container1 = addSubscription().getContainer();
auto keys = generateAccount();
auto keys2 = generateAccount();
auto keys3 = generateAccount();
uint64_t amount = 900;
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
tx->addOutput(amount, { m_accountKeys.address, keys.address, keys2.address } , 3);
tx->addOutput(800, { keys.address, keys2.address, keys3.address }, 3);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(m_consumer.onNewBlocks(&block, 0, 1));
auto outs1 = container1.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outs1.size());
ASSERT_EQ(amount, outs1[0].amount);
}
TEST_F(TransfersConsumerTest, onNewBlocks_getTransactionOutsGlobalIndicesIsProperlyCalled) {
class INodeGlobalIndicesStub: public INodeDummyStub {
public:
virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash,
std::vector<uint64_t>& outsGlobalIndices, const Callback& callback) override {
outsGlobalIndices.push_back(3);
hash = transactionHash;
callback(std::error_code());
};
crypto::hash hash;
};
INodeGlobalIndicesStub node;
TransfersConsumer consumer(m_currency, node, m_accountKeys.viewSecretKey);
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.height = 0;
subscription.syncStart.timestamp = 0;
consumer.addSubscription(subscription);
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 2, m_accountKeys);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(consumer.onNewBlocks(&block, 1, 1));
const CryptoNote::Hash &hash = tx->getTransactionHash();
const crypto::hash expectedHash = *reinterpret_cast<const crypto::hash*>(&hash);
ASSERT_EQ(expectedHash, node.hash);
}
TEST_F(TransfersConsumerTest, onNewBlocks_getTransactionOutsGlobalIndicesIsNotCalled) {
class INodeGlobalIndicesStub: public INodeDummyStub {
public:
INodeGlobalIndicesStub() : called(false) {};
virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash,
std::vector<uint64_t>& outsGlobalIndices, const Callback& callback) override {
outsGlobalIndices.push_back(3);
called = true;
callback(std::error_code());
};
bool called;
};
INodeGlobalIndicesStub node;
TransfersConsumer consumer(m_currency, node, m_accountKeys.viewSecretKey);
AccountSubscription subscription = getAccountSubscription(m_accountKeys);
subscription.syncStart.height = 0;
subscription.syncStart.timestamp = 0;
consumer.addSubscription(subscription);
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 900, 2, generateAccount());
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(consumer.onNewBlocks(&block, 1, 1));
ASSERT_FALSE(node.called);
}
TEST_F(TransfersConsumerTest, onNewBlocks_markTransactionConfirmed) {
auto& container = addSubscription().getContainer();
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 10000, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, m_accountKeys);
m_consumer.onPoolUpdated({convertTx(*tx)}, {});
auto lockedOuts = container.getTransactionOutputs(tx->getTransactionHash(),
ITransfersContainer::IncludeStateLocked | ITransfersContainer::IncludeTypeKey);
ASSERT_EQ(1, lockedOuts.size());
ASSERT_EQ(10000, lockedOuts[0].amount);
CompleteBlock blocks[2];
blocks[0].block = cryptonote::Block();
blocks[0].block->timestamp = 0;
blocks[0].transactions.push_back(tx);
blocks[1].block = cryptonote::Block();
blocks[1].block->timestamp = 0;
blocks[1].transactions.push_back(createTransaction());
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 0, 2));
auto softLockedOuts = container.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeKeyUnlocked);
ASSERT_EQ(1, softLockedOuts.size());
ASSERT_EQ(10000, softLockedOuts[0].amount);
}
class INodeGlobalIndexStub: public INodeDummyStub {
public:
virtual void getTransactionOutsGlobalIndices(const crypto::hash& transactionHash,
std::vector<uint64_t>& outsGlobalIndices, const Callback& callback) override {
outsGlobalIndices.push_back(globalIndex);
callback(std::error_code());
};
uint64_t globalIndex;
};
TEST_F(TransfersConsumerTest, onNewBlocks_checkTransactionOutputInformation) {
const uint64_t index = 2;
INodeGlobalIndexStub node;
TransfersConsumer consumer(m_currency, node, m_accountKeys.viewSecretKey);
node.globalIndex = index;
auto& container = addSubscription(consumer).getContainer();
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
auto out = addTestKeyOutput(*tx, 10000, index, m_accountKeys);
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(consumer.onNewBlocks(&block, 0, 1));
auto outs = container.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outs.size());
auto& o = outs[0];
ASSERT_EQ(out.type, o.type);
ASSERT_EQ(out.amount, o.amount);
ASSERT_EQ(out.outputKey, o.outputKey);
ASSERT_EQ(out.globalOutputIndex, o.globalOutputIndex);
ASSERT_EQ(out.outputInTransaction, o.outputInTransaction);
ASSERT_EQ(out.transactionPublicKey, o.transactionPublicKey);
}
TEST_F(TransfersConsumerTest, onNewBlocks_checkTransactionOutputInformationMultisignature) {
const uint64_t index = 2;
INodeGlobalIndexStub node;
TransfersConsumer consumer(m_currency, node, m_accountKeys.viewSecretKey);
node.globalIndex = index;
auto& container = addSubscription(consumer).getContainer();
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
uint32_t txIndex = tx->addOutput(300, { m_accountKeys.address, generateAccountKeys().address}, 2);
TransactionOutputInformation expectedOut;
expectedOut.type = TransactionTypes::OutputType::Multisignature;
expectedOut.amount = 300;
expectedOut.globalOutputIndex = index;
expectedOut.outputInTransaction = txIndex;
expectedOut.transactionPublicKey = tx->getTransactionPublicKey();
expectedOut.requiredSignatures = 2;
CompleteBlock block;
block.block = cryptonote::Block();
block.block->timestamp = 0;
block.transactions.push_back(tx);
ASSERT_TRUE(consumer.onNewBlocks(&block, 0, 1));
auto outs = container.getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outs.size());
auto& o = outs[0];
ASSERT_EQ(expectedOut.type, o.type);
ASSERT_EQ(expectedOut.amount, o.amount);
ASSERT_EQ(expectedOut.requiredSignatures, o.requiredSignatures);
ASSERT_EQ(expectedOut.globalOutputIndex, o.globalOutputIndex);
ASSERT_EQ(expectedOut.outputInTransaction, o.outputInTransaction);
ASSERT_EQ(expectedOut.transactionPublicKey, o.transactionPublicKey);
}
TEST_F(TransfersConsumerTest, onNewBlocks_checkTransactionInformation) {
auto& container = addSubscription().getContainer();
std::shared_ptr<ITransaction> tx(createTransaction());
addTestInput(*tx, 10000);
addTestKeyOutput(*tx, 1000, 2, m_accountKeys);
Hash paymentId = crypto::rand<Hash>();
uint64_t unlockTime = 10;
tx->setPaymentId(paymentId);
tx->setUnlockTime(unlockTime);
CompleteBlock blocks[2];
blocks[0].block = cryptonote::Block();
blocks[0].block->timestamp = 0;
blocks[0].transactions.push_back(createTransaction());
blocks[1].block = cryptonote::Block();
blocks[1].block->timestamp = 11;
blocks[1].transactions.push_back(tx);
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 0, 2));
TransactionInformation info;
int64_t balance;
ASSERT_TRUE(container.getTransactionInformation(tx->getTransactionHash(), info, balance));
ASSERT_EQ(tx->getTransactionHash(), info.transactionHash);
ASSERT_EQ(tx->getTransactionPublicKey(), info.publicKey);
ASSERT_EQ(1, info.blockHeight);
ASSERT_EQ(11, info.timestamp);
ASSERT_EQ(unlockTime, info.unlockTime);
ASSERT_EQ(10000, info.totalAmountIn);
ASSERT_EQ(1000, info.totalAmountOut);
ASSERT_EQ(paymentId, info.paymentId);
}
TEST_F(TransfersConsumerTest, onNewBlocks_manyBlocks) {
const size_t blocksCount = 1000;
const size_t txPerBlock = 10;
auto& container = addSubscription().getContainer();
std::vector<CompleteBlock> blocks(blocksCount);
uint64_t timestamp = 10000;
uint64_t expectedAmount = 0;
size_t expectedTransactions = 0;
uint64_t globalOut = 0;
size_t blockIdx = 0;
for (auto& b : blocks) {
b.block = cryptonote::Block();
b.block->timestamp = timestamp++;
if (++blockIdx % 10 == 0) {
for (size_t i = 0; i < txPerBlock; ++i) {
auto tx = createTransaction();
addTestInput(*tx, 10000);
if ((i % 3) == 0) {
addTestKeyOutput(*tx, 1000, ++globalOut, m_accountKeys);
addTestKeyOutput(*tx, 2000, ++globalOut, m_accountKeys);
expectedAmount += 3000;
++expectedTransactions;
}
b.transactions.push_back(std::move(tx));
}
}
}
ASSERT_TRUE(m_consumer.onNewBlocks(&blocks[0], 0, blocks.size()));
ASSERT_EQ(expectedTransactions, container.transactionsCount());
ASSERT_EQ(expectedAmount, container.balance(ITransfersContainer::IncludeAll));
}
TEST_F(TransfersConsumerTest, onPoolUpdated_addTransaction) {
auto& sub = addSubscription();
// construct tx
auto tx = createTransaction();
addTestInput(*tx, 10000);
auto out = addTestKeyOutput(*tx, 10000, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, m_accountKeys);
m_consumer.onPoolUpdated({convertTx(*tx)}, {});
auto outputs = sub.getContainer().getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outputs.size());
auto& o = outputs[0];
ASSERT_EQ(out.type, o.type);
ASSERT_EQ(out.amount, o.amount);
ASSERT_EQ(out.outputKey, o.outputKey);
ASSERT_EQ(UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, o.globalOutputIndex);
}
TEST_F(TransfersConsumerTest, onPoolUpdated_addTransactionMultisignature) {
auto& sub = addSubscription();
// construct tx with multisignature output
auto tx = createTransaction();
addTestInput(*tx, 10000);
auto addresses = { m_accountKeys.address, generateAccountKeys().address };
tx->addOutput(10000, addresses, 1);
m_consumer.onPoolUpdated({ convertTx(*tx) }, {});
auto outputs = sub.getContainer().getTransactionOutputs(tx->getTransactionHash(), ITransfersContainer::IncludeAll);
ASSERT_EQ(1, outputs.size());
auto& o = outputs[0];
TransactionTypes::OutputMultisignature out;
tx->getOutput(0, out);
ASSERT_EQ(TransactionTypes::OutputType::Multisignature, o.type);
ASSERT_EQ(out.amount, o.amount);
ASSERT_EQ(out.requiredSignatures, o.requiredSignatures);
ASSERT_EQ(UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, o.globalOutputIndex);
}
TEST_F(TransfersConsumerTest, onPoolUpdated_addTransactionDoesNotGetsGlobalIndices) {
auto& sub = addSubscription();
// construct tx
auto tx = createTransaction();
addTestInput(*tx, 10000);
auto out = addTestKeyOutput(*tx, 10000, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, m_accountKeys);
m_consumer.onPoolUpdated({ convertTx(*tx) }, {});
ASSERT_TRUE(m_node.calls_getTransactionOutsGlobalIndices.empty());
}
TEST_F(TransfersConsumerTest, onPoolUpdated_deleteTransaction) {
auto& sub = addSubscription();
TransfersObserver observer;
sub.addObserver(&observer);
std::vector<crypto::hash> deleted = {
crypto::rand<crypto::hash>(),
crypto::rand<crypto::hash>()
};
m_consumer.onPoolUpdated({}, deleted);
ASSERT_EQ(deleted.size(), observer.deleted.size());
ASSERT_EQ(reinterpret_cast<const Hash&>(deleted[0]), observer.deleted[0]);
ASSERT_EQ(reinterpret_cast<const Hash&>(deleted[1]), observer.deleted[1]);
}
TEST_F(TransfersConsumerTest, getKnownPoolTxIds_empty) {
auto& sub = addSubscription();
std::vector<crypto::hash> ids;
m_consumer.getKnownPoolTxIds(ids);
ASSERT_TRUE(ids.empty());
}
std::unique_ptr<ITransaction> createTransactionTo(const AccountKeys& to, uint64_t amountIn, uint64_t amountOut) {
auto tx = createTransaction();
addTestInput(*tx, amountIn);
addTestKeyOutput(*tx, amountOut, UNCONFIRMED_TRANSACTION_GLOBAL_OUTPUT_INDEX, to);
return tx;
}
TEST_F(TransfersConsumerTest, getKnownPoolTxIds_returnsUnconfirmed) {
auto acc1 = generateAccount();
auto acc2 = generateAccount();
auto& sub1 = addSubscription(acc1);
auto& sub2 = addSubscription(acc2);
std::vector<std::unique_ptr<ITransaction>> txs;
txs.push_back(createTransactionTo(acc1, 10000, 10000));
txs.push_back(createTransactionTo(acc1, 20000, 20000));
txs.push_back(createTransactionTo(acc2, 30000, 30000));
m_consumer.onPoolUpdated({ convertTx(*txs[0]), convertTx(*txs[1]), convertTx(*txs[2])}, {});
std::vector<crypto::hash> ids;
m_consumer.getKnownPoolTxIds(ids);
ASSERT_EQ(3, ids.size());
for (int i = 0; i < 3; ++i) {
auto txhash = txs[i]->getTransactionHash();
ASSERT_TRUE(std::find(ids.begin(), ids.end(), reinterpret_cast<const crypto::hash&>(txhash)) != ids.end());
}
}