// 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 <future>
#include <chrono>
#include <array>

#include "EventWaiter.h"
#include "INode.h"
#include "WalletLegacy/WalletLegacy.h"
#include "WalletLegacy/WalletHelper.h"
#include "CryptoNoteCore/Account.h"
#include "CryptoNoteCore/Currency.h"
#include "CryptoNote.h"

#include "INodeStubs.h"
#include "TestBlockchainGenerator.h"
#include <Logging/ConsoleLogger.h>

class TrivialWalletObserver : public CryptoNote::IWalletLegacyObserver
{
public:
  TrivialWalletObserver() : actualBalance(0), pendingBalance(0) {}

  bool waitForSyncEnd() {
    return synced.wait_for(std::chrono::milliseconds(3000));
  }

  bool waitForSendEnd(std::error_code& ec) {
    if (!sent.wait_for(std::chrono::milliseconds(5000))) return false;
    ec = sendResult;
    return true;
  }

  bool waitForSaveEnd(std::error_code& ec) {
    if (!saved.wait_for(std::chrono::milliseconds(5000))) return false;
    ec = saveResult;
    return true;
  }

  bool waitForLoadEnd(std::error_code& ec) {
    if (!loaden.wait_for(std::chrono::milliseconds(5000))) return false;
    ec = loadResult;
    return true;
  }

  virtual void synchronizationCompleted(std::error_code result) override {
    synced.notify();
  }

  virtual void sendTransactionCompleted(CryptoNote::TransactionId transactionId, std::error_code result) override {
    sendResult = result;
    sent.notify();
  }

  virtual void saveCompleted(std::error_code result) override {
    saveResult = result;
    saved.notify();
  }

  virtual void initCompleted(std::error_code result) override {
    loadResult = result;
    loaden.notify();
  }

  virtual void actualBalanceUpdated(uint64_t actualBalance) override {
    //    std::cout << "actual balance: " << actualBalance << std::endl;
    this->actualBalance = actualBalance;
  }

  virtual void pendingBalanceUpdated(uint64_t pendingBalance) override {
//    std::cout << "pending balance: " << pendingBalance << std::endl;
    this->pendingBalance = pendingBalance;
  }

  std::error_code sendResult;
  std::error_code saveResult;
  std::error_code loadResult;

  std::atomic<uint64_t> actualBalance;
  std::atomic<uint64_t> pendingBalance;

  EventWaiter synced;
  EventWaiter saved;
  EventWaiter loaden;
  EventWaiter sent;
};

struct SaveOnInitWalletObserver: public CryptoNote::IWalletLegacyObserver {
  SaveOnInitWalletObserver(CryptoNote::WalletLegacy* wallet) : wallet(wallet) {
  };
  virtual ~SaveOnInitWalletObserver() {}

  virtual void initCompleted(std::error_code result) override {
    wallet->save(stream, true, true);
  }

  CryptoNote::WalletLegacy* wallet;
  std::stringstream stream;
};

static const uint64_t TEST_BLOCK_REWARD = 70368744177663;

CryptoNote::TransactionId TransferMoney(CryptoNote::WalletLegacy& from, CryptoNote::WalletLegacy& to, int64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "") {
  CryptoNote::WalletLegacyTransfer transfer;
  transfer.amount = amount;
  transfer.address = to.getAddress();

  return from.sendTransaction(transfer, fee, extra, mixIn);
}

void WaitWalletSync(TrivialWalletObserver* observer) {
  ASSERT_TRUE(observer->waitForSyncEnd());
}

void WaitWalletSend(TrivialWalletObserver* observer) {
  std::error_code ec;
  ASSERT_TRUE(observer->waitForSendEnd(ec));
}

void WaitWalletSend(TrivialWalletObserver* observer, std::error_code& ec) {
  ASSERT_TRUE(observer->waitForSendEnd(ec));
}

void WaitWalletSave(TrivialWalletObserver* observer) {
  std::error_code ec;

  ASSERT_TRUE(observer->waitForSaveEnd(ec));
  EXPECT_FALSE(ec);
}

void WaitWalletLoad(TrivialWalletObserver* observer) {
  std::error_code ec;
  
  ASSERT_TRUE(observer->waitForLoadEnd(ec));
  EXPECT_FALSE(ec);  
}

class WalletLegacyApi : public ::testing::Test
{
public:
  WalletLegacyApi() : m_currency(CryptoNote::CurrencyBuilder(m_logger).currency()), generator(m_currency) {
  }

  void SetUp() override;

protected:
  void prepareAliceWallet();
  void prepareBobWallet();
  void prepareCarolWallet();

  void GetOneBlockReward(CryptoNote::WalletLegacy& wallet);
  void GetOneBlockReward(CryptoNote::WalletLegacy& wallet, const CryptoNote::Currency& currency, TestBlockchainGenerator& blockchainGenerator);
  void GetOneBlockRewardAndUnlock(CryptoNote::WalletLegacy& wallet, TrivialWalletObserver& observer, INodeTrivialRefreshStub& node,
                                  const CryptoNote::Currency& currency, TestBlockchainGenerator& blockchainGenerator);

  void TestSendMoney(int64_t transferAmount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "");
  void performTransferWithErrorTx(const std::array<int64_t, 5>& amounts, uint64_t fee);


  Logging::ConsoleLogger m_logger;
  CryptoNote::Currency m_currency;

  TestBlockchainGenerator generator;

  std::shared_ptr<TrivialWalletObserver> aliceWalletObserver;
  std::shared_ptr<INodeTrivialRefreshStub> aliceNode;
  std::shared_ptr<CryptoNote::WalletLegacy> alice;

  std::shared_ptr<TrivialWalletObserver> bobWalletObserver;
  std::shared_ptr<INodeTrivialRefreshStub> bobNode;
  std::shared_ptr<CryptoNote::WalletLegacy> bob;

  std::shared_ptr<TrivialWalletObserver> carolWalletObserver;
  std::shared_ptr<INodeTrivialRefreshStub> carolNode;
  std::shared_ptr<CryptoNote::WalletLegacy> carol;
};

void WalletLegacyApi::SetUp() {
  prepareAliceWallet();
  generator.generateEmptyBlocks(3);
}

void WalletLegacyApi::prepareAliceWallet() {
  decltype(aliceNode) newNode(new INodeTrivialRefreshStub(generator));

  alice.reset(new CryptoNote::WalletLegacy(m_currency, *newNode));
  aliceNode = newNode;

  aliceWalletObserver.reset(new TrivialWalletObserver());
  alice->addObserver(aliceWalletObserver.get());
}

void WalletLegacyApi::prepareBobWallet() {
  bobNode.reset(new INodeTrivialRefreshStub(generator));
  bobWalletObserver.reset(new TrivialWalletObserver());

  bob.reset(new CryptoNote::WalletLegacy(m_currency, *bobNode));
  bob->addObserver(bobWalletObserver.get());
}

void WalletLegacyApi::prepareCarolWallet() {
  carolNode.reset(new INodeTrivialRefreshStub(generator));
  carolWalletObserver.reset(new TrivialWalletObserver());

  carol.reset(new CryptoNote::WalletLegacy(m_currency, *carolNode));
  carol->addObserver(carolWalletObserver.get());
}

void WalletLegacyApi::GetOneBlockReward(CryptoNote::WalletLegacy& wallet) {
  GetOneBlockReward(wallet, m_currency, generator);
}

void WalletLegacyApi::GetOneBlockReward(CryptoNote::WalletLegacy& wallet, const CryptoNote::Currency& currency, TestBlockchainGenerator& blockchainGenerator) {
  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(currency.parseAccountAddressString(wallet.getAddress(), address));
  blockchainGenerator.getBlockRewardForAddress(address);
}

void WalletLegacyApi::GetOneBlockRewardAndUnlock(CryptoNote::WalletLegacy& wallet, TrivialWalletObserver& observer, INodeTrivialRefreshStub& node,
                                                 const CryptoNote::Currency& currency, TestBlockchainGenerator& blockchainGenerator) {
  GetOneBlockReward(wallet, currency, blockchainGenerator);
  blockchainGenerator.generateEmptyBlocks(10);
  node.updateObservers();
  WaitWalletSync(&observer);
}

void WalletLegacyApi::performTransferWithErrorTx(const std::array<int64_t, 5>& amounts, uint64_t fee) {
  std::vector<CryptoNote::WalletLegacyTransfer> trs;
  CryptoNote::WalletLegacyTransfer tr;
  tr.address = bob->getAddress();
  tr.amount = amounts[0];
  trs.push_back(tr);

  tr.address = bob->getAddress();
  tr.amount = amounts[1];
  trs.push_back(tr);

  tr.address = carol->getAddress();
  tr.amount = amounts[2];
  trs.push_back(tr);

  aliceNode->setNextTransactionError();
  alice->sendTransaction(trs, fee);

  std::error_code result;
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), result));
  ASSERT_NE(result.value(), 0);

  trs.clear();

  tr.address = bob->getAddress();
  tr.amount = amounts[3];
  trs.push_back(tr);

  tr.address = carol->getAddress();
  tr.amount = amounts[4];
  trs.push_back(tr);

  alice->sendTransaction(trs, fee);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);
}

void WalletLegacyApi::TestSendMoney(int64_t transferAmount, uint64_t fee, uint64_t mixIn, const std::string& extra) {
  prepareBobWallet();

  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));
  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));

  //unlock Alice's money
  generator.generateEmptyBlocks(10);
  uint64_t expectedBalance = TEST_BLOCK_REWARD;

  aliceNode->updateObservers();

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(0, alice->pendingBalance());
  EXPECT_EQ(expectedBalance, alice->actualBalance());

  EXPECT_EQ(expectedBalance, aliceWalletObserver->actualBalance);
  EXPECT_EQ(0, aliceWalletObserver->pendingBalance);

  bob->initAndGenerate("pass2");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));
  ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, transferAmount, fee, mixIn, ""));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  EXPECT_EQ(0, bob->pendingBalance());
  EXPECT_EQ(transferAmount, bob->actualBalance());

  EXPECT_EQ(0, alice->pendingBalance());
  EXPECT_EQ(expectedBalance - transferAmount - fee, alice->actualBalance());

  alice->shutdown();
  bob->shutdown();
}

void WaitWalletLoad(TrivialWalletObserver* observer, std::error_code& ec) {
  ASSERT_TRUE(observer->waitForLoadEnd(ec));
}

TEST_F(WalletLegacyApi, initAndSave) {
  SaveOnInitWalletObserver saveOnInit(alice.get());
  alice->addObserver(&saveOnInit);
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));
  alice->shutdown();
}

TEST_F(WalletLegacyApi, refreshWithMoney) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(alice->actualBalance(), 0);
  ASSERT_EQ(alice->pendingBalance(), 0);

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getBlockRewardForAddress(address);

  aliceNode->updateObservers();

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(alice->actualBalance(), 0);
  EXPECT_EQ(alice->pendingBalance(), TEST_BLOCK_REWARD);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, initWithMoney) {
  std::stringstream archive;

  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));
  alice->save(archive, true, true);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  ASSERT_EQ(alice->actualBalance(), 0);
  ASSERT_EQ(alice->pendingBalance(), 0);

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));

  alice->shutdown();

  generator.getBlockRewardForAddress(address);

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(alice->actualBalance(), 0);
  EXPECT_EQ(alice->pendingBalance(), TEST_BLOCK_REWARD);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, TransactionsAndTransfersAfterSend) {
  prepareBobWallet();
  prepareCarolWallet();

  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(alice->getTransactionCount(), 0);
  EXPECT_EQ(alice->getTransferCount(), 0);

  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));

  //unblock Alice's money
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(alice->getTransactionCount(), 1);

  bob->initAndGenerate("pass2");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  uint64_t fee = 100000;
  int64_t amount1 = 1230000;
  ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount1, fee, 0));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  int64_t amount2 = 1234500;
  ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount2, fee, 0));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));
  
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  int64_t amount3 = 1234567;
  ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *bob, amount3, fee, 0));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  carol->initAndGenerate("pass3");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get()));

  int64_t amount4 = 1020304;
  ASSERT_NO_FATAL_FAILURE(TransferMoney(*alice, *carol, amount4, fee, 0));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  EXPECT_EQ(alice->getTransactionCount(), 5);

  CryptoNote::WalletLegacyTransaction tx;

  //Transaction with id = 0 is tested in getTransactionSuccess
  ASSERT_TRUE(alice->getTransaction(1, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount1 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.isCoinbase, false);
  EXPECT_EQ(tx.firstTransferId, 0);
  EXPECT_EQ(tx.transferCount, 1);

  ASSERT_TRUE(alice->getTransaction(2, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount2 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.isCoinbase, false);
  EXPECT_EQ(tx.firstTransferId, 1);
  EXPECT_EQ(tx.transferCount, 1);

  ASSERT_TRUE(alice->getTransaction(3, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount3 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.isCoinbase, false);
  EXPECT_EQ(tx.firstTransferId, 2);
  EXPECT_EQ(tx.transferCount, 1);

  ASSERT_TRUE(alice->getTransaction(4, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount4 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.isCoinbase, false);
  EXPECT_EQ(tx.firstTransferId, 3);
  EXPECT_EQ(tx.transferCount, 1);

  //Now checking transfers
  CryptoNote::WalletLegacyTransfer tr;
  ASSERT_TRUE(alice->getTransfer(0, tr));
  EXPECT_EQ(tr.amount, amount1);
  EXPECT_EQ(tr.address, bob->getAddress());

  ASSERT_TRUE(alice->getTransfer(1, tr));
  EXPECT_EQ(tr.amount, amount2);
  EXPECT_EQ(tr.address, bob->getAddress());

  ASSERT_TRUE(alice->getTransfer(2, tr));
  EXPECT_EQ(tr.amount, amount3);
  EXPECT_EQ(tr.address, bob->getAddress());

  ASSERT_TRUE(alice->getTransfer(3, tr));
  EXPECT_EQ(tr.amount, amount4);
  EXPECT_EQ(tr.address, carol->getAddress());

  EXPECT_EQ(alice->findTransactionByTransferId(0), 1);
  EXPECT_EQ(alice->findTransactionByTransferId(1), 2);
  EXPECT_EQ(alice->findTransactionByTransferId(2), 3);
  EXPECT_EQ(alice->findTransactionByTransferId(3), 4);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, saveAndLoadCacheDetails) {
  prepareBobWallet();
  prepareCarolWallet();

  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));

  //unblock Alice's money
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  bob->initAndGenerate("pass2");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  carol->initAndGenerate("pass3");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get()));

  uint64_t fee = 1000000;
  int64_t amount1 = 1234567;
  int64_t amount2 = 1020304;
  int64_t amount3 = 2030405;

  std::vector<CryptoNote::WalletLegacyTransfer> trs;
  CryptoNote::WalletLegacyTransfer tr;
  tr.address = bob->getAddress();
  tr.amount = amount1;
  trs.push_back(tr);

  tr.address = bob->getAddress();
  tr.amount = amount2;
  trs.push_back(tr);

  alice->sendTransaction(trs, fee, "", 0, 0);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  trs.clear();
  tr.address = carol->getAddress();
  tr.amount = amount3;
  trs.push_back(tr);

  alice->sendTransaction(trs, fee, "", 0, 0);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  std::stringstream archive;
  alice->save(archive, true, true);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  auto prevActualBalance = alice->actualBalance();
  auto prevPendingBalance = alice->pendingBalance();

  alice->shutdown();

  prepareAliceWallet();

  alice->initAndLoad(archive, "pass");
  std::error_code ec;

  WaitWalletLoad(aliceWalletObserver.get(), ec);
  ASSERT_FALSE(ec);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(alice->getTransactionCount(), 3);
  ASSERT_EQ(alice->getTransferCount(), 3);

  EXPECT_EQ(prevActualBalance, alice->actualBalance());
  EXPECT_EQ(prevPendingBalance, alice->pendingBalance());

  CryptoNote::WalletLegacyTransaction tx;
  ASSERT_TRUE(alice->getTransaction(1, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount1 + amount2 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.firstTransferId, 0);
  EXPECT_EQ(tx.transferCount, 2);

  ASSERT_TRUE(alice->getTransaction(2, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amount3 + fee));
  EXPECT_EQ(tx.fee, fee);
  EXPECT_EQ(tx.firstTransferId, 2);
  EXPECT_EQ(tx.transferCount, 1);

  ASSERT_TRUE(alice->getTransfer(0, tr));
  EXPECT_EQ(tr.address, bob->getAddress());
  EXPECT_EQ(tr.amount, amount1);

  ASSERT_TRUE(alice->getTransfer(1, tr));
  EXPECT_EQ(tr.address, bob->getAddress());
  EXPECT_EQ(tr.amount, amount2);

  ASSERT_TRUE(alice->getTransfer(2, tr));
  EXPECT_EQ(tr.address, carol->getAddress());
  EXPECT_EQ(tr.amount, amount3);

  EXPECT_EQ(alice->findTransactionByTransferId(0), 1);
  EXPECT_EQ(alice->findTransactionByTransferId(1), 1);
  EXPECT_EQ(alice->findTransactionByTransferId(2), 2);

  alice->shutdown();
  carol->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, sendMoneySuccessNoMixin) {
  ASSERT_NO_FATAL_FAILURE(TestSendMoney(10000000, 1000000, 0));
}

TEST_F(WalletLegacyApi, sendMoneySuccessWithMixin) {
  ASSERT_NO_FATAL_FAILURE(TestSendMoney(10000000, 1000000, 3));
}

TEST_F(WalletLegacyApi, getTransactionSuccess) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));
  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::WalletLegacyTransaction tx;

  ASSERT_EQ(alice->getTransactionCount(), 1);
  ASSERT_TRUE(alice->getTransaction(0, tx));

  EXPECT_EQ(tx.firstTransferId, CryptoNote::WALLET_LEGACY_INVALID_TRANSFER_ID);
  EXPECT_EQ(tx.transferCount, 0);
  EXPECT_EQ(tx.totalAmount, TEST_BLOCK_REWARD);
  EXPECT_EQ(tx.fee, 0);
  EXPECT_EQ(tx.isCoinbase, false);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, getTransactionFailure) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::WalletLegacyTransaction tx;

  ASSERT_EQ(alice->getTransactionCount(), 0);
  ASSERT_FALSE(alice->getTransaction(0, tx));

  alice->shutdown();
}

TEST_F(WalletLegacyApi, useNotInitializedObject) {
  EXPECT_THROW(alice->pendingBalance(), std::system_error);
  EXPECT_THROW(alice->actualBalance(), std::system_error);
  EXPECT_THROW(alice->getTransactionCount(), std::system_error);
  EXPECT_THROW(alice->getTransferCount(), std::system_error);
  EXPECT_THROW(alice->getAddress(), std::system_error);

  std::stringstream archive;
  EXPECT_THROW(alice->save(archive, true, true), std::system_error);

  EXPECT_THROW(alice->findTransactionByTransferId(1), std::system_error);

  CryptoNote::WalletLegacyTransaction tx;
  CryptoNote::WalletLegacyTransfer tr;
  EXPECT_THROW(alice->getTransaction(1, tx), std::system_error);
  EXPECT_THROW(alice->getTransfer(2, tr), std::system_error);

  tr.address = "lslslslslslsls";
  tr.amount = 1000000;
  EXPECT_THROW(alice->sendTransaction(tr, 300201), std::system_error);

  std::vector<CryptoNote::WalletLegacyTransfer> trs;
  trs.push_back(tr);
  EXPECT_THROW(alice->sendTransaction(trs, 329293), std::system_error);
}

TEST_F(WalletLegacyApi, sendWrongAmount) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::WalletLegacyTransfer tr;
  tr.address = "1234567890qwertasdfgzxcvbyuiophjklnm";
  tr.amount = 1;

  EXPECT_THROW(alice->sendTransaction(tr, 1), std::system_error);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, wrongPassword) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  std::stringstream archive;
  alice->save(archive, true, false);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  alice->shutdown();

  prepareAliceWallet();
  alice->initAndLoad(archive, "wrongpass");

  std::error_code result;
  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  EXPECT_EQ(result.value(), CryptoNote::error::WRONG_PASSWORD);
}

TEST_F(WalletLegacyApi, detachBlockchain) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));

  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  aliceNode->startAlternativeChain(3);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(0, alice->actualBalance());
  EXPECT_EQ(0, alice->pendingBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, saveAndLoad) {
  alice->initAndGenerate("pass");

  std::error_code result;
  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);

  std::stringstream archive;
  ASSERT_NO_FATAL_FAILURE(alice->save(archive));

  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);
}

TEST_F(WalletLegacyApi, saveAndLoadErroneousTxsCacheDetails) {
  prepareBobWallet();
  prepareCarolWallet();

  std::error_code result;

  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  carol->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get()));

  std::array<int64_t, 5> amounts;
  amounts[0] = 1234567;
  amounts[1] = 1345678;
  amounts[2] = 1456789;
  amounts[3] = 1567890;
  amounts[4] = 1678901;
  uint64_t fee = 10000;

  ASSERT_NO_FATAL_FAILURE(performTransferWithErrorTx(amounts, fee));

  std::stringstream archive;
  alice->save(archive);

  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);

  EXPECT_EQ(alice->getTransactionCount(), 2);
  EXPECT_EQ(alice->getTransferCount(), 2);

  CryptoNote::WalletLegacyTransaction tx;
  ASSERT_TRUE(alice->getTransaction(1, tx));
  EXPECT_EQ(tx.totalAmount, -static_cast<int64_t>(amounts[3] + amounts[4] + fee));
  EXPECT_EQ(tx.firstTransferId, 0);
  EXPECT_EQ(tx.transferCount, 2);

  CryptoNote::WalletLegacyTransfer tr;
  ASSERT_TRUE(alice->getTransfer(0, tr));
  EXPECT_EQ(tr.amount, amounts[3]);
  EXPECT_EQ(tr.address, bob->getAddress());

  ASSERT_TRUE(alice->getTransfer(1, tr));
  EXPECT_EQ(tr.amount, amounts[4]);
  EXPECT_EQ(tr.address, carol->getAddress());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, saveAndLoadErroneousTxsCacheNoDetails) {
  prepareBobWallet();
  prepareCarolWallet();

  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_NO_FATAL_FAILURE(GetOneBlockReward(*alice));
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  carol->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(carolWalletObserver.get()));

  std::array<int64_t, 5> amounts;
  amounts[0] = 1234567;
  amounts[1] = 1345678;
  amounts[2] = 1456789;
  amounts[3] = 1567890;
  amounts[4] = 1678901;
  uint64_t fee = 10000;

  ASSERT_NO_FATAL_FAILURE(performTransferWithErrorTx(amounts, fee));

  std::stringstream archive;
  alice->save(archive, false, true);

  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");

  std::error_code result;
  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);

  EXPECT_EQ(0, alice->getTransactionCount());
  EXPECT_EQ(0, alice->getTransferCount());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, mineSaveNoCacheNoDetailsRefresh) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getBlockRewardForAddress(address);
  generator.getBlockRewardForAddress(address);
  generator.getBlockRewardForAddress(address);

  aliceNode->updateObservers();

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  std::stringstream archive;
  alice->save(archive, false, false);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  alice->shutdown();

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get()));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD * 3, alice->pendingBalance());
  alice->shutdown();
}


TEST_F(WalletLegacyApi, sendMoneyToMyself) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getBlockRewardForAddress(address);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::TransactionId txId = TransferMoney(*alice, *alice, 100000000, 100);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD - 100, alice->actualBalance());
  ASSERT_EQ(0, alice->pendingBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, sendSeveralTransactions) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  for (int i = 0; i < 5; ++i) {
    GetOneBlockReward(*alice);
  }

  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  auto aliceBalance = alice->actualBalance();

  uint64_t sendAmount = 100000;
  uint64_t totalSentAmount = 0;
  size_t transactionCount = 0;
  
  for (int i = 0; i < 10 && alice->actualBalance() > sendAmount; ++i) {
    CryptoNote::WalletLegacyTransfer tr;
    tr.address = bob->getAddress();
    tr.amount = sendAmount;

    auto txId = alice->sendTransaction(tr, m_currency.minimumFee(), "", 1, 0);  
    ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);

    std::error_code sendResult;
    ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), sendResult));
    ASSERT_EQ(std::error_code(), sendResult);

    ++transactionCount;
    totalSentAmount += sendAmount;
  }

  generator.generateEmptyBlocks(10);

  bobNode->updateObservers();

  while (totalSentAmount != bob->actualBalance()) {
    ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));
  }

  EXPECT_EQ(transactionCount, bob->getTransactionCount());
  EXPECT_EQ(0, bob->pendingBalance());
  EXPECT_EQ(totalSentAmount, bob->actualBalance());

  uint64_t aliceTotalBalance = alice->actualBalance() + alice->pendingBalance();
  EXPECT_EQ(aliceBalance - transactionCount * (sendAmount + m_currency.minimumFee()), aliceTotalBalance);
}

TEST_F(WalletLegacyApi, balanceAfterFailedTransaction) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  auto actualBalance = alice->actualBalance();
  auto pendingBalance = alice->pendingBalance();

  uint64_t send = 11000000;
  uint64_t fee = m_currency.minimumFee();

  CryptoNote::WalletLegacyTransfer tr;
  tr.address = bob->getAddress();
  tr.amount = send;

  aliceNode->setNextTransactionError();

  alice->sendTransaction(tr, fee, "", 1, 0);
  generator.generateEmptyBlocks(1);

  ASSERT_EQ(actualBalance, alice->actualBalance());
  ASSERT_EQ(pendingBalance, alice->pendingBalance());

  alice->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, checkPendingBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  uint64_t startActualBalance = alice->actualBalance();
  int64_t sendAmount = 304050;
  uint64_t fee = m_currency.minimumFee();

  CryptoNote::WalletLegacyTransfer tr;
  tr.address = bob->getAddress();
  tr.amount = sendAmount;

  auto txId = alice->sendTransaction(tr, fee, "", 1, 0);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);

  std::error_code sendResult;
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), sendResult));
  ASSERT_EQ(std::error_code(), sendResult);

  uint64_t totalBalance = alice->actualBalance() + alice->pendingBalance();
  ASSERT_EQ(startActualBalance - sendAmount - fee, totalBalance);

  generator.generateEmptyBlocks(1);
  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  ASSERT_EQ(sendAmount, bob->actualBalance());
  ASSERT_EQ(0, bob->pendingBalance());

  alice->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, checkChange) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  uint64_t banknote = 1000000000;
  uint64_t sendAmount = 50000;
  uint64_t fee = m_currency.minimumFee();

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getSingleOutputTransaction(address, banknote);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::WalletLegacyTransfer tr;
  tr.address = bob->getAddress();
  tr.amount = sendAmount;

  auto txId = alice->sendTransaction(tr, fee, "", 1, 0);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);

  std::error_code sendResult;
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get(), sendResult));
  ASSERT_EQ(std::error_code(), sendResult);

  EXPECT_EQ(0, alice->actualBalance());
  EXPECT_EQ(banknote - sendAmount - fee, alice->pendingBalance());
}

TEST_F(WalletLegacyApi, checkBalanceAfterSend) {
  alice->initAndGenerate("pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  uint64_t banknote = 1000000000;

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));

  //Once wallet takes outputs in random fashion we don't know for sure which outputs will be taken.
  //In this case we generate controllable set of outs.
  generator.getSingleOutputTransaction(address, banknote);
  generator.getSingleOutputTransaction(address, banknote);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  const uint64_t sendAmount = 10000000;
  const uint64_t fee = 100;
  CryptoNote::TransactionId txId = TransferMoney(*alice, *alice, sendAmount, fee);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  ASSERT_EQ(banknote, alice->actualBalance());
  ASSERT_EQ(banknote - sendAmount - fee, alice->pendingBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, moneyInPoolDontAffectActualBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  uint64_t banknote = 1000000000;

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getSingleOutputTransaction(address, banknote);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  const uint64_t sendAmount = 10000000;
  const uint64_t fee = 100;
  aliceNode->setNextTransactionToPool();
  CryptoNote::TransactionId txId = TransferMoney(*alice, *bob, sendAmount, fee);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(0, alice->actualBalance());
  EXPECT_EQ(banknote - sendAmount - fee, alice->pendingBalance());

  alice->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, balanceAfterTransactionsPlacedInBlockchain) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  uint64_t banknote = 1000000000;

  CryptoNote::AccountPublicAddress address;
  ASSERT_TRUE(m_currency.parseAccountAddressString(alice->getAddress(), address));
  generator.getSingleOutputTransaction(address, banknote);
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  const uint64_t sendAmount = 10000000;
  const uint64_t fee = 100;
  aliceNode->setNextTransactionToPool();
  CryptoNote::TransactionId txId = TransferMoney(*alice, *bob, sendAmount, fee);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));
  generator.generateEmptyBlocks(10);

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  aliceNode->includeTransactionsFromPoolToBlock();
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  EXPECT_EQ(banknote - sendAmount - fee, alice->actualBalance());
  EXPECT_EQ(0, alice->pendingBalance());

  alice->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, checkMyMoneyInTxPool) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  uint64_t sendAmount = 8821902;
  uint64_t fee = 10000;

  aliceNode->setNextTransactionToPool();
  CryptoNote::TransactionId txId = TransferMoney(*alice, *bob, sendAmount, fee);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  EXPECT_EQ(0, bob->actualBalance());
  EXPECT_EQ(sendAmount, bob->pendingBalance());

  alice->shutdown();
  bob->shutdown();
}

TEST_F(WalletLegacyApi, initWithKeys) {
  CryptoNote::AccountKeys accountKeys;

  Crypto::generate_keys(accountKeys.address.spendPublicKey, accountKeys.spendSecretKey);
  Crypto::generate_keys(accountKeys.address.viewPublicKey, accountKeys.viewSecretKey);

  alice->initWithKeys(accountKeys, "pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get()));

  CryptoNote::AccountKeys keys;
  alice->getAccountKeys(keys);
  
  EXPECT_EQ(accountKeys.address.spendPublicKey, keys.address.spendPublicKey);
  EXPECT_EQ(accountKeys.spendSecretKey, keys.spendSecretKey);
  EXPECT_EQ(accountKeys.address.viewPublicKey, keys.address.viewPublicKey);
  EXPECT_EQ(accountKeys.viewSecretKey, keys.viewSecretKey);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, deleteTxFromPool) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  uint64_t sendAmount = 9748291;
  uint64_t fee = 10000;

  aliceNode->setNextTransactionToPool();
  CryptoNote::TransactionId txId = TransferMoney(*alice, *bob, sendAmount, fee);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));
  alice->shutdown();

  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  generator.clearTxPool();

  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  EXPECT_EQ(0, bob->actualBalance());
  EXPECT_EQ(0, bob->pendingBalance());

  bob->shutdown();
}

TEST_F(WalletLegacyApi, sendAfterFailedTransaction) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::WalletLegacyTransfer tr;
  tr.amount = 100000;
  tr.address = "wrong_address";

  EXPECT_THROW(alice->sendTransaction(tr, 1000, "", 2, 0), std::system_error);
  CryptoNote::TransactionId txId = TransferMoney(*alice, *alice, 100000, 100);
  ASSERT_NE(txId, CryptoNote::WALLET_LEGACY_INVALID_TRANSACTION_ID);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));
  alice->shutdown();
}

TEST_F(WalletLegacyApi, DISABLED_loadingBrokenCache) {
  alice->initAndGenerate("pass");

  std::error_code result;
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));
  ASSERT_EQ(result.value(), 0);

  std::stringstream archive;
  ASSERT_NO_FATAL_FAILURE(alice->save(archive, false, true));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));
  size_t sizeWithEmptyCache = archive.str().size();

  for (size_t i = 0; i < 3; ++i) {
    GetOneBlockReward(*alice);
  }
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();

  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  archive.str("");
  archive.clear();

  ASSERT_NO_FATAL_FAILURE(alice->save(archive, false, true));
  ASSERT_NO_FATAL_FAILURE(WaitWalletSave(aliceWalletObserver.get()));

  std::string state = archive.str();
  for (size_t i = sizeWithEmptyCache; i < state.size(); ++i) {
    state[i] = '\xff';
  }
  archive.str(state);

  prepareAliceWallet();
  alice->initAndLoad(archive, "pass");

  ASSERT_NO_FATAL_FAILURE(WaitWalletLoad(aliceWalletObserver.get(), result));
  ASSERT_EQ(result.value(), 0);
}

TEST_F(WalletLegacyApi, outcommingExternalTransactionTotalAmount) {
  class ExternalTxChecker : public CryptoNote::IWalletLegacyObserver {
  public:
    ExternalTxChecker(CryptoNote::WalletLegacy& wallet) : wallet(wallet), totalAmount(std::numeric_limits<int64_t>::max()) {
    }

    virtual void externalTransactionCreated(CryptoNote::TransactionId transactionId) override {
      CryptoNote::WalletLegacyTransaction txInfo;
      ASSERT_TRUE(wallet.getTransaction(transactionId, txInfo));
      totalAmount = txInfo.totalAmount;
    }

    CryptoNote::WalletLegacy& wallet;
    int64_t totalAmount;
  };

  std::stringstream walletData;

  alice->initAndGenerate("pass");
  WaitWalletSync(aliceWalletObserver.get());

  alice->save(walletData, false, false);
  WaitWalletSave(aliceWalletObserver.get());

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  WaitWalletSync(aliceWalletObserver.get());

  prepareBobWallet();
  bob->initAndGenerate("pass2");
  WaitWalletSync(bobWalletObserver.get());

  uint64_t sent = 10000000;
  uint64_t fee = 1000;

  CryptoNote::WalletLegacyTransfer tr;
  tr.amount = sent;
  tr.address = bob->getAddress();

  alice->sendTransaction(tr, fee);
  WaitWalletSend(aliceWalletObserver.get());

  bob->shutdown();
  alice->shutdown();

  CryptoNote::WalletLegacy wallet(m_currency, *aliceNode);

  ExternalTxChecker externalTransactionObserver(wallet);
  TrivialWalletObserver walletObserver;

  wallet.addObserver(&externalTransactionObserver);
  wallet.addObserver(&walletObserver);

  wallet.initAndLoad(walletData, "pass");
  WaitWalletSync(&walletObserver);

  ASSERT_EQ(-static_cast<int64_t>(sent + fee), externalTransactionObserver.totalAmount);
  wallet.shutdown();
}

TEST_F(WalletLegacyApi, shutdownAllowsInitializeWalletWithTheSameKeys) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::AccountKeys accountKeys;
  alice->getAccountKeys(accountKeys);

  alice->shutdown();
  alice->initWithKeys(accountKeys, "pass");

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(1, alice->getTransactionCount());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, shutdownAllowsInitializeWalletWithDifferentKeys) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->shutdown();
  alice->initAndGenerate("pass");

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(0, alice->getTransactionCount());

  alice->shutdown();
}

namespace {
class WalletSynchronizationProgressUpdatedObserver : public CryptoNote::IWalletLegacyObserver {
public:
  virtual void synchronizationProgressUpdated(uint32_t current, uint32_t /*total*/) override {
    m_current = current;
  }

  uint64_t m_current = 0;
};
}

TEST_F(WalletLegacyApi, shutdownDoesNotRemoveObservers) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  WalletSynchronizationProgressUpdatedObserver observer;
  CryptoNote::WalletHelper::IWalletRemoveObserverGuard observerGuard(*alice, observer);

  alice->shutdown();
  observer.m_current = 0;
  alice->initAndGenerate("pass");

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(5, observer.m_current);

  observerGuard.removeObserver();
  alice->shutdown();
}

namespace {
class WalletTransactionEventCounter : public CryptoNote::IWalletLegacyObserver {
public:
  virtual void externalTransactionCreated(CryptoNote::TransactionId /*transactionId*/) override {
    ++m_count;
  }

  virtual void transactionUpdated(CryptoNote::TransactionId /*transactionId*/) override {
    ++m_count;
  }

  size_t m_count = 0;
};
}

TEST_F(WalletLegacyApi, afterShutdownAndInitWalletDoesNotSendNotificationsRelatedToOldAddress) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  std::string aliceAddress1 = alice->getAddress();
  CryptoNote::AccountKeys accountKeys1;
  alice->getAccountKeys(accountKeys1);

  alice->shutdown();
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  std::string aliceAddress2 = alice->getAddress();

  alice->shutdown();
  alice->initWithKeys(accountKeys1, "pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  WalletTransactionEventCounter observer;
  CryptoNote::WalletHelper::IWalletRemoveObserverGuard observerGuard(*alice, observer);

  prepareBobWallet();
  bob->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));
  GetOneBlockReward(*bob);
  generator.generateEmptyBlocks(10);
  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  std::vector<CryptoNote::WalletLegacyTransfer> transfers;
  transfers.push_back({ aliceAddress1, TEST_BLOCK_REWARD / 10 });
  transfers.push_back({ aliceAddress2, TEST_BLOCK_REWARD / 5 });
  bob->sendTransaction(transfers, m_currency.minimumFee());
  std::error_code sendResult;
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(bobWalletObserver.get(), sendResult));

  generator.generateEmptyBlocks(1);
  bobNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(bobWalletObserver.get()));

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(1, observer.m_count);

  observerGuard.removeObserver();
  bob->shutdown();
  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetDoesNotChangeAddress) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  auto expectedAddress = alice->getAddress();
  alice->reset();
  ASSERT_EQ(expectedAddress, alice->getAddress());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetDoesNotChangeAccountKeys) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  CryptoNote::AccountKeys expectedAccountKeys;
  alice->getAccountKeys(expectedAccountKeys);

  alice->reset();

  CryptoNote::AccountKeys actualAccountKeys;
  alice->getAccountKeys(actualAccountKeys);

  ASSERT_EQ(expectedAccountKeys.address, actualAccountKeys.address);
  ASSERT_EQ(expectedAccountKeys.spendSecretKey, actualAccountKeys.spendSecretKey);
  ASSERT_EQ(expectedAccountKeys.viewSecretKey, actualAccountKeys.viewSecretKey);

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetDoesNotRemoveObservers) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  WalletSynchronizationProgressUpdatedObserver observer;
  CryptoNote::WalletHelper::IWalletRemoveObserverGuard observerGuard(*alice, observer);

  alice->reset();
  observer.m_current = 0;

  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(5, observer.m_current);

  observerGuard.removeObserver();
  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetDoesNotChangePassword) {
  std::string password = "password";
  std::string newPassword = "new_password";

  alice->initAndGenerate(password);
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->reset();
  ASSERT_TRUE(static_cast<bool>(alice->changePassword(newPassword, password)));
  ASSERT_FALSE(static_cast<bool>(alice->changePassword(password, newPassword)));

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetClearsPendingBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD, alice->pendingBalance());
  alice->reset();
  ASSERT_EQ(0, alice->pendingBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetClearsActualBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD, alice->actualBalance());
  alice->reset();
  ASSERT_EQ(0, alice->actualBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetClearsTransactionHistory) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(1, alice->getTransactionCount());
  alice->reset();
  ASSERT_EQ(0, alice->getTransactionCount());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetClearsTransfersHistory) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->sendTransaction({ alice->getAddress(), 100 }, m_currency.minimumFee());
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  ASSERT_EQ(1, alice->getTransferCount());
  alice->reset();
  ASSERT_EQ(0, alice->getTransferCount());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetAndSyncRestorePendingBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->reset();
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD, alice->pendingBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetAndSyncRestoreActualBalance) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->reset();
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(TEST_BLOCK_REWARD, alice->actualBalance());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetAndSyncRestoreTransactions) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->reset();
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(1, alice->getTransactionCount());

  alice->shutdown();
}

TEST_F(WalletLegacyApi, resetAndSyncDoNotRestoreTransfers) {
  alice->initAndGenerate("pass");
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  GetOneBlockReward(*alice);
  generator.generateEmptyBlocks(10);
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  alice->sendTransaction({ alice->getAddress(), 100 }, m_currency.minimumFee());
  ASSERT_NO_FATAL_FAILURE(WaitWalletSend(aliceWalletObserver.get()));

  alice->reset();
  aliceNode->updateObservers();
  ASSERT_NO_FATAL_FAILURE(WaitWalletSync(aliceWalletObserver.get()));

  ASSERT_EQ(0, alice->getTransferCount());

  alice->shutdown();
}

void generateWallet(CryptoNote::IWalletLegacy& wallet, TrivialWalletObserver& observer, const std::string& pass) {
  wallet.initAndGenerate(pass);
  WaitWalletSync(&observer);
}

TEST_F(WalletLegacyApi, outdatedUnconfirmedTransactionDeletedOnNewBlock) {
  const uint64_t TRANSACTION_MEMPOOL_TIME = 1;
  CryptoNote::Currency currency(CryptoNote::CurrencyBuilder(m_logger).mempoolTxLiveTime(TRANSACTION_MEMPOOL_TIME).currency());
  TestBlockchainGenerator blockchainGenerator(currency);
  INodeTrivialRefreshStub node(blockchainGenerator);
  CryptoNote::WalletLegacy wallet(currency, node);
  TrivialWalletObserver walletObserver;
  wallet.addObserver(&walletObserver);

  wallet.initAndGenerate("pass");
  WaitWalletSync(&walletObserver);

  GetOneBlockRewardAndUnlock(wallet, walletObserver, node, currency, blockchainGenerator);

  const std::string ADDRESS = "2634US2FAz86jZT73YmM8u5GPCknT2Wxj8bUCKivYKpThFhF2xsjygMGxbxZzM42zXhKUhym6Yy6qHHgkuWtruqiGkDpX6m";
  node.setNextTransactionToPool();
  auto id = wallet.sendTransaction({ADDRESS, static_cast<int64_t>(TEST_BLOCK_REWARD - m_currency.minimumFee())}, m_currency.minimumFee());
  WaitWalletSend(&walletObserver);

  node.cleanTransactionPool();
  std::this_thread::sleep_for(std::chrono::seconds(TRANSACTION_MEMPOOL_TIME));

  blockchainGenerator.generateEmptyBlocks(1);
  node.updateObservers();
  WaitWalletSync(&walletObserver);

  ASSERT_EQ(TEST_BLOCK_REWARD, wallet.actualBalance());

  CryptoNote::WalletLegacyTransaction transaction;
  ASSERT_TRUE(wallet.getTransaction(id, transaction));
  EXPECT_EQ(CryptoNote::WalletLegacyTransactionState::Deleted, transaction.state);

  wallet.removeObserver(&walletObserver);
  wallet.shutdown();
}

TEST_F(WalletLegacyApi, outdatedUnconfirmedTransactionDeletedOnLoad) {
  const uint64_t TRANSACTION_MEMPOOL_TIME = 1;
  CryptoNote::Currency currency(CryptoNote::CurrencyBuilder(m_logger).mempoolTxLiveTime(TRANSACTION_MEMPOOL_TIME).currency());
  TestBlockchainGenerator blockchainGenerator(currency);
  INodeTrivialRefreshStub node(blockchainGenerator);
  CryptoNote::WalletLegacy wallet(currency, node);
  TrivialWalletObserver walletObserver;
  wallet.addObserver(&walletObserver);

  wallet.initAndGenerate("pass");
  WaitWalletSync(&walletObserver);

  GetOneBlockRewardAndUnlock(wallet, walletObserver, node, currency, blockchainGenerator);

  const std::string ADDRESS = "2634US2FAz86jZT73YmM8u5GPCknT2Wxj8bUCKivYKpThFhF2xsjygMGxbxZzM42zXhKUhym6Yy6qHHgkuWtruqiGkDpX6m";
  node.setNextTransactionToPool();
  auto id = wallet.sendTransaction({ADDRESS, static_cast<int64_t>(TEST_BLOCK_REWARD - m_currency.minimumFee())}, m_currency.minimumFee());
  WaitWalletSend(&walletObserver);

  node.cleanTransactionPool();

  std::stringstream data;
  wallet.save(data);
  WaitWalletSave(&walletObserver);

  wallet.shutdown();

  std::this_thread::sleep_for(std::chrono::seconds(TRANSACTION_MEMPOOL_TIME));

  wallet.initAndLoad(data, "pass");
  WaitWalletSync(&walletObserver);

  ASSERT_EQ(TEST_BLOCK_REWARD, wallet.actualBalance());

  CryptoNote::WalletLegacyTransaction transaction;
  ASSERT_TRUE(wallet.getTransaction(id, transaction));
  EXPECT_EQ(CryptoNote::WalletLegacyTransactionState::Deleted, transaction.state);

  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();
}
