std::error_code TestWalletLegacy::init() {
    CryptoNote::AccountBase walletAccount;
    walletAccount.generate();

    m_wallet->initWithKeys(walletAccount.getAccountKeys(), TEST_PASSWORD);
    m_synchronizationCompleted.wait();
    return m_lastSynchronizationResult;
}
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::AccountBase dstAcc;
  dstAcc.generate();

  AccountKeys dstKeys = reinterpret_cast<const AccountKeys&>(dstAcc.getAccountKeys());

  BlockchainSynchronizer blockSync(*node2.get(), currency.genesisBlockHash());
  TransfersSyncronizer transferSync(currency, blockSync, *node2.get());
  TransfersObserver transferObserver;
  WalletLegacyObserver 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);

  Tests::Common::TestWalletLegacy wallet1(m_dispatcher, m_currency, *node1);
  ASSERT_FALSE(static_cast<bool>(wallet1.init()));
  wallet1.wallet()->addObserver(&walletObserver);
  ASSERT_TRUE(mineBlocks(*nodeDaemons[0], wallet1.address(), 1));
  ASSERT_TRUE(mineBlocks(*nodeDaemons[0], wallet1.address(), currency.minedMoneyUnlockWindow()));
  wallet1.waitForSynchronizationToHeight(static_cast<uint32_t>(2 + currency.minedMoneyUnlockWindow()));

  // start syncing and wait for a transfer
  FutureGuard<bool> waitFuture(std::async(std::launch::async, [&transferObserver] { return transferObserver.waitTransfer(); }));
  Interrupter transferObserverInterrupter(transferObserver);
  blockSync.start();

  Hash txId;
  ASSERT_FALSE(static_cast<bool>(wallet1.sendTransaction(currency.accountAddressAsString(dstAcc), TRANSFER_AMOUNT, txId)));
  ASSERT_TRUE(mineBlocks(*nodeDaemons[0], wallet1.address(), 1));

  ASSERT_TRUE(waitFuture.get());
  transferObserverInterrupter.cancel();
  std::cout << "Received transfer: " << currency.formatAmount(transferContainer.balance(ITransfersContainer::IncludeAll)) << std::endl;

  ASSERT_EQ(TRANSFER_AMOUNT, transferContainer.balance(ITransfersContainer::IncludeAll));
  ASSERT_GT(transferContainer.getTransactionOutputs(txId, ITransfersContainer::IncludeAll).size(), 0);

  blockSync.stop();
}
  void generateAccounts(size_t count) {
    CryptoNote::AccountBase acc;

    while (count--) {
      acc.generate();

      AccountSubscription sub;
      sub.keys = reinterpret_cast<const AccountKeys&>(acc.getAccountKeys());
      sub.syncStart.timestamp = 0;
      sub.syncStart.height = 0;
      sub.transactionSpendableAge = TRANSACTION_SPENDABLE_AGE;

      m_accounts.push_back(sub);
      m_addresses.push_back(currency.accountAddressAsString(acc));
    }
  }
  void run() {
    if (m_config.daemons.empty()) {
      logger(ERROR, BRIGHT_RED) << "No daemons configured, exiting";
      return;
    }

    launchTestnet(m_nodeCount, Tests::Common::BaseFunctionalTests::Line);
    
    createWallets();
    
    miningTest();

    // create some address for mining
    CryptoNote::AccountBase stashAddress;
    stashAddress.generate();
    auto stashAddressStr = m_currency.accountAddressAsString(stashAddress);

    unlockMoney(stashAddressStr);

    std::vector<uint64_t> balances;
    for (auto& o : m_observers) {
      balances.push_back(o->totalBalance());
    }

    printBalances();

    // transfer money from each wallet to each other
    for (size_t i = 0; i < m_nodeCount; ++i) {
      auto& srcWallet = *m_wallets[i];
      for (size_t wi = 0; wi < m_nodeCount; ++wi) {
        if (i != wi) {
          CryptoNote::WalletLegacyTransfer transfer;
          transfer.address = m_wallets[wi]->getAddress();
          transfer.amount = (i * 1000 + wi * 100) * m_currency.coin();
          logger(INFO, BRIGHT_YELLOW) << "Sending from " << shortAddress(srcWallet.getAddress()) << " to " << shortAddress(transfer.address) << " amount = " << m_currency.formatAmount(transfer.amount);
          auto txid = srcWallet.sendTransaction(transfer, m_currency.minimumFee());

          balances[i] -= transfer.amount + m_currency.minimumFee();
          balances[wi] += transfer.amount;

          auto res = m_observers[i]->waitSendResult(txid);

          if (res) {
            logger(ERROR, BRIGHT_RED) << "Failed to send transaction: " << res.message();
            throw std::runtime_error("Failed to send transaction: " + res.message());
          }

          logger(INFO) << "Sent successfully";
        }
      }
    }

    nodeDaemons[0]->startMining(1, stashAddressStr);

    for (size_t i = 0; i < m_nodeCount; ++i) {
      uint64_t total;
      logger(INFO) << i << " Expected target balance: " << m_currency.formatAmount(balances[i]);

      while ((total = m_wallets[i]->pendingBalance() + m_wallets[i]->actualBalance()) != balances[i]) {
        logger(INFO) << i << " - total: " << m_currency.formatAmount(total) << ", waiting";
        m_observers[i]->waitTotalBalanceChange();
      }
    }

    nodeDaemons[0]->stopMining();

    printBalances();
  }