danicoin/tests/transfers_tests/tests.cpp
2015-05-27 13:28:09 +01:00

521 lines
15 KiB
C++

// 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 <http://www.gnu.org/licenses/>.
#include "globals.h"
#include "cryptonote_core/account.h"
#include "cryptonote_core/cryptonote_format_utils.h"
#include "cryptonote_core/TransactionApi.h"
#include "transfers/TransfersSynchronizer.h"
#include "transfers/BlockchainSynchronizer.h"
#include <mutex>
#include <condition_variable>
#include <future>
#include <atomic>
using namespace CryptoNote;
template <size_t size>
std::string bin2str(const std::array<uint8_t, size>& data) {
std::string result;
result.resize(size * 2 + 1);
for (size_t i = 0; i < size; ++i) {
sprintf(&result[i * 2], "%02x", data[i]);
}
return result;
}
class WalletObserver : public IWalletObserver {
public:
virtual void actualBalanceUpdated(uint64_t actualBalance) {
std::cout << "Actual balance updated = " << currency.formatAmount(actualBalance) << std::endl;
m_actualBalance = actualBalance;
m_sem.notify();
}
virtual void sendTransactionCompleted(TransactionId transactionId, std::error_code result) {
std::cout << "Transaction sended, result = " << result << std::endl;
}
std::atomic<uint64_t> m_actualBalance;
Tests::Common::Semaphore m_sem;
};
class TransactionConsumer : public IBlockchainConsumer {
public:
TransactionConsumer() {
syncStart.timestamp = time(nullptr);
syncStart.height = 0;
}
virtual SynchronizationStart getSyncStart() override {
return syncStart;
}
virtual void onBlockchainDetach(uint64_t height) override {
std::lock_guard<std::mutex> lk(m_mutex);
auto it = m_transactions.lower_bound(height);
m_transactions.erase(it, m_transactions.end());
}
virtual bool onNewBlocks(const CompleteBlock* blocks, uint64_t startHeight, size_t count) override {
std::lock_guard<std::mutex> lk(m_mutex);
for(size_t i = 0; i < count; ++i) {
for (const auto& tx : blocks[i].transactions) {
m_transactions[startHeight + i].insert(tx->getTransactionHash());
}
}
m_cv.notify_all();
return true;
}
bool waitForTransaction(const Hash& txHash) {
std::unique_lock<std::mutex> lk(m_mutex);
while (!hasTransaction(txHash)) {
m_cv.wait(lk);
}
return true;
}
std::error_code onPoolUpdated(const std::vector<Transaction>& addedTransactions, const std::vector<crypto::hash>& deletedTransactions) override {
//stub
return std::error_code();
}
void getKnownPoolTxIds(std::vector<crypto::hash>& ids) override {
//stub
}
private:
bool hasTransaction(const Hash& txHash) {
for (const auto& kv : m_transactions) {
if (kv.second.count(txHash) > 0)
return true;
}
return false;
}
std::mutex m_mutex;
std::condition_variable m_cv;
std::map<uint64_t, std::set<Hash>> m_transactions;
SynchronizationStart syncStart;
};
class TransfersObserver : public ITransfersObserver {
public:
virtual void onTransactionUpdated(ITransfersSubscription* object, const Hash& transactionHash) override {
{
std::lock_guard<std::mutex> lk(m_mutex);
m_transfers.push_back(transactionHash);
auto key = object->getAddress().spendPublicKey;
std::string address = Common::toHex(&key, sizeof(key));
LOG_DEBUG("Transfer to " + address);
}
m_cv.notify_all();
}
bool waitTransfer() {
std::unique_lock<std::mutex> lk(m_mutex);
size_t prevSize = m_transfers.size();
while (m_transfers.size() == prevSize) {
m_cv.wait_for(lk, std::chrono::seconds(10));
}
return true;
}
bool waitTransactionTransfer(const Hash& transactionHash) {
std::unique_lock<std::mutex> lk(m_mutex);
while (!hasTransaction(transactionHash)) {
m_cv.wait_for(lk, std::chrono::seconds(10));
}
return true;
}
private:
bool hasTransaction(const Hash& transactionHash) {
return std::find(m_transfers.begin(), m_transfers.end(), transactionHash) != m_transfers.end();
}
std::mutex m_mutex;
std::condition_variable m_cv;
std::vector<Hash> m_transfers;
};
class AccountGroup {
public:
AccountGroup(ITransfersSynchronizer& sync) :
m_sync(sync) {}
void generateAccounts(size_t count) {
CryptoNote::account_base acc;
while (count--) {
acc.generate();
AccountSubscription sub;
sub.keys = reinterpret_cast<const AccountKeys&>(acc.get_keys());
sub.syncStart.timestamp = acc.get_createtime();
sub.syncStart.height = 0;
sub.transactionSpendableAge = 5;
m_accounts.push_back(sub);
m_addresses.push_back(currency.accountAddressAsString(acc));
}
}
void subscribeAll() {
m_observers.reset(new TransfersObserver[m_accounts.size()]);
for (size_t i = 0; i < m_accounts.size(); ++i) {
m_sync.addSubscription(m_accounts[i]).addObserver(&m_observers[i]);
}
}
std::vector<AccountAddress> getAddresses() {
std::vector<AccountAddress> addr;
for (const auto& acc : m_accounts) {
addr.push_back(acc.keys.address);
}
return addr;
}
ITransfersContainer& getTransfers(size_t idx) {
return m_sync.getSubscription(m_accounts[idx].keys.address)->getContainer();
}
std::vector<AccountSubscription> m_accounts;
std::vector<std::string> m_addresses;
ITransfersSynchronizer& m_sync;
std::unique_ptr<TransfersObserver[]> m_observers;
};
class MultisignatureTest : public TransfersTest {
public:
virtual void SetUp() override {
launchTestnet(2);
}
};
TEST_F(TransfersTest, base) {
uint64_t TRANSFER_AMOUNT;
currency.parseAmount("500000.5", TRANSFER_AMOUNT);
launchTestnet(2);
std::unique_ptr<CryptoNote::INode> node1;
std::unique_ptr<CryptoNote::INode> node2;
nodeDaemons[0]->makeINode(node1);
nodeDaemons[1]->makeINode(node2);
CryptoNote::account_base dstAcc;
dstAcc.generate();
AccountKeys dstKeys = reinterpret_cast<const AccountKeys&>(dstAcc.get_keys());
BlockchainSynchronizer blockSync(*node2.get(), currency.genesisBlockHash());
TransfersSyncronizer transferSync(currency, blockSync, *node2.get());
TransfersObserver transferObserver;
WalletObserver walletObserver;
AccountSubscription sub;
sub.syncStart.timestamp = 0;
sub.syncStart.height = 0;
sub.keys = dstKeys;
sub.transactionSpendableAge = 5;
ITransfersSubscription& transferSub = transferSync.addSubscription(sub);
ITransfersContainer& transferContainer = transferSub.getContainer();
transferSub.addObserver(&transferObserver);
std::unique_ptr<IWallet> wallet1;
makeWallet(wallet1, node1);
mineBlock(wallet1);
wallet1->addObserver(&walletObserver);
startMining(1);
while (wallet1->actualBalance() < TRANSFER_AMOUNT) {
walletObserver.m_sem.wait();
}
// start syncing and wait for a transfer
auto waitFuture = std::async(std::launch::async, [&transferObserver] { return transferObserver.waitTransfer(); });
blockSync.start();
Transfer transfer;
transfer.address = currency.accountAddressAsString(dstAcc);
transfer.amount = TRANSFER_AMOUNT;
wallet1->sendTransaction(transfer, currency.minimumFee());
auto result = waitFuture.get();
std::cout << "Received transfer: " << currency.formatAmount(transferContainer.balance(ITransfersContainer::IncludeAll)) << std::endl;
ASSERT_EQ(TRANSFER_AMOUNT, transferContainer.balance(ITransfersContainer::IncludeAll));
auto BACK_TRANSFER = TRANSFER_AMOUNT / 2;
stopMining();
blockSync.stop();
}
std::unique_ptr<ITransaction> createTransferToMultisignature(
ITransfersContainer& tc, // money source
uint64_t amount,
uint64_t fee,
const AccountKeys& senderKeys,
const std::vector<AccountAddress>& recipients,
uint32_t requiredSignatures) {
std::vector<TransactionOutputInformation> transfers;
tc.getOutputs(transfers, ITransfersContainer::IncludeAllUnlocked | ITransfersContainer::IncludeStateSoftLocked);
auto tx = createTransaction();
std::vector<std::pair<TransactionTypes::InputKeyInfo, TransactionTypes::KeyPair>> inputs;
uint64_t foundMoney = 0;
for (const auto& t : transfers) {
TransactionTypes::InputKeyInfo info;
info.amount = t.amount;
TransactionTypes::GlobalOutput globalOut;
globalOut.outputIndex = t.globalOutputIndex;
globalOut.targetKey = t.outputKey;
info.outputs.push_back(globalOut);
info.realOutput.outputInTransaction = t.outputInTransaction;
info.realOutput.transactionIndex = 0;
info.realOutput.transactionPublicKey = t.transactionPublicKey;
TransactionTypes::KeyPair kp;
tx->addInput(senderKeys, info, kp);
inputs.push_back(std::make_pair(info, kp));
foundMoney += info.amount;
if (foundMoney >= amount + fee) {
break;
}
}
// output to receiver
tx->addOutput(amount, recipients, requiredSignatures);
// change
uint64_t change = foundMoney - amount - fee;
if (change) {
tx->addOutput(change, senderKeys.address);
}
for (size_t inputIdx = 0; inputIdx < inputs.size(); ++inputIdx) {
tx->signInputKey(inputIdx, inputs[inputIdx].first, inputs[inputIdx].second);
}
return tx;
}
std::error_code submitTransaction(INode& node, ITransactionReader& tx) {
auto data = tx.getTransactionData();
CryptoNote::blobdata txblob(data.data(), data.data() + data.size());
CryptoNote::Transaction outTx;
CryptoNote::parse_and_validate_tx_from_blob(txblob, outTx);
LOG_DEBUG("Submitting transaction " + bin2str(tx.getTransactionHash()));
std::promise<std::error_code> result;
node.relayTransaction(outTx, [&result](std::error_code ec) { result.set_value(ec); });
auto err = result.get_future().get();
if (err) {
LOG_DEBUG("Error: " + err.message());
} else {
LOG_DEBUG("Submitted successfully");
}
return err;
}
std::unique_ptr<ITransaction> createTransferFromMultisignature(
AccountGroup& consilium, const AccountAddress& receiver, const Hash& txHash, uint64_t amount, uint64_t fee) {
auto& tc = consilium.getTransfers(0);
std::vector<TransactionOutputInformation> transfers = tc.getTransactionOutputs(txHash,
ITransfersContainer::IncludeTypeMultisignature |
ITransfersContainer::IncludeStateSoftLocked |
ITransfersContainer::IncludeStateUnlocked);
const TransactionOutputInformation& out = transfers[0];
auto tx = createTransaction();
TransactionTypes::InputMultisignature msigInput;
msigInput.amount = out.amount;
msigInput.outputIndex = out.globalOutputIndex;
msigInput.signatures = out.requiredSignatures;
tx->addInput(msigInput);
tx->addOutput(amount, receiver);
uint64_t change = out.amount - amount - fee;
tx->addOutput(change, consilium.getAddresses(), out.requiredSignatures);
for (size_t i = 0; i < out.requiredSignatures; ++i) {
tx->signInputMultisignature(0, out.transactionPublicKey, out.outputInTransaction, consilium.m_accounts[i].keys);
}
return tx;
}
TEST_F(MultisignatureTest, createMulitisignatureTransaction) {
std::unique_ptr<CryptoNote::INode> node1;
std::unique_ptr<CryptoNote::INode> node2;
nodeDaemons[0]->makeINode(node1);
nodeDaemons[1]->makeINode(node2);
BlockchainSynchronizer blockSync(*node2.get(), currency.genesisBlockHash());
TransfersSyncronizer transferSync(currency, blockSync, *node2.get());
// add transaction collector
TransactionConsumer txConsumer;
blockSync.addConsumer(&txConsumer);
AccountGroup sender(transferSync);
AccountGroup consilium(transferSync);
sender.generateAccounts(1);
sender.subscribeAll();
consilium.generateAccounts(3);
consilium.subscribeAll();
auto senderSubscription = transferSync.getSubscription(sender.m_accounts[0].keys.address);
auto& senderContainer = senderSubscription->getContainer();
blockSync.start();
// start mining for sender
nodeDaemons[0]->startMining(1, sender.m_addresses[0]);
// wait for incoming transfer
while (senderContainer.balance() == 0) {
sender.m_observers[0].waitTransfer();
auto unlockedBalance = senderContainer.balance(ITransfersContainer::IncludeAllUnlocked | ITransfersContainer::IncludeStateSoftLocked);
auto totalBalance = senderContainer.balance(ITransfersContainer::IncludeAll);
LOG_DEBUG("Balance: " + currency.formatAmount(unlockedBalance) + " (" + currency.formatAmount(totalBalance) + ")");
}
uint64_t fundBalance = 0;
for (int iteration = 1; iteration <= 3; ++iteration) {
LOG_DEBUG("***** Iteration " + std::to_string(iteration) + " ******");
auto sendAmount = senderContainer.balance() / 2;
LOG_DEBUG("Creating transaction with amount = " + currency.formatAmount(sendAmount));
auto tx2msig = createTransferToMultisignature(
senderContainer, sendAmount, currency.minimumFee(), sender.m_accounts[0].keys, consilium.getAddresses(), 3);
auto txHash = tx2msig->getTransactionHash();
auto err = submitTransaction(*node2, *tx2msig);
ASSERT_EQ(std::error_code(), err);
LOG_DEBUG("Waiting for transaction to be included in block...");
txConsumer.waitForTransaction(txHash);
LOG_DEBUG("Transaction in blockchain, waiting for observers to receive transaction...");
uint64_t expectedFundBalance = fundBalance + sendAmount;
// wait for consilium to receive the transfer
for (size_t i = 0; i < consilium.m_accounts.size(); ++i) {
auto& observer = consilium.m_observers[i];
observer.waitTransactionTransfer(txHash);
auto sub = transferSync.getSubscription(consilium.m_accounts[i].keys.address);
ASSERT_TRUE(sub != nullptr);
ASSERT_EQ(expectedFundBalance, sub->getContainer().balance(
ITransfersContainer::IncludeStateAll | ITransfersContainer::IncludeTypeMultisignature));
}
LOG_DEBUG("Creating transaction to spend multisignature output");
uint64_t returnAmount = sendAmount / 2;
auto spendMsigTx = createTransferFromMultisignature(
consilium, sender.m_accounts[0].keys.address, txHash, returnAmount, currency.minimumFee());
auto spendMsigTxHash = spendMsigTx->getTransactionHash();
err = submitTransaction(*node2, *spendMsigTx);
ASSERT_EQ(std::error_code(), err);
LOG_DEBUG("Waiting for transaction to be included in block...");
txConsumer.waitForTransaction(spendMsigTxHash);
LOG_DEBUG("Checking left balances");
// check that outputs were correctly marked as spent
uint64_t leftAmount = expectedFundBalance - returnAmount - currency.minimumFee();
for (size_t i = 0; i < consilium.m_accounts.size(); ++i) {
auto& observer = consilium.m_observers[i];
observer.waitTransactionTransfer(spendMsigTxHash);
ASSERT_EQ(leftAmount, consilium.getTransfers(i).balance(ITransfersContainer::IncludeAll));
}
fundBalance = leftAmount;
}
stopMining();
blockSync.stop();
LOG_DEBUG("Success!!!");
}