2015-04-29 17:03:08 +00:00
|
|
|
// Copyright (c) 2011-2015 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 <QCoreApplication>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QLocale>
|
|
|
|
#include <QVector>
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
#include <Common/Util.h>
|
|
|
|
#include <Wallet/WalletErrors.h>
|
|
|
|
#include <Wallet/LegacyKeysImporter.h>
|
2015-04-29 17:03:08 +00:00
|
|
|
|
|
|
|
#include "NodeAdapter.h"
|
|
|
|
#include "Settings.h"
|
|
|
|
#include "WalletAdapter.h"
|
|
|
|
|
|
|
|
namespace WalletGui {
|
|
|
|
|
|
|
|
const quint32 MSECS_IN_HOUR = 60 * 60 * 1000;
|
|
|
|
const quint32 MSECS_IN_MINUTE = 60 * 1000;
|
|
|
|
|
|
|
|
const quint32 LAST_BLOCK_INFO_UPDATING_INTERVAL = 1 * MSECS_IN_MINUTE;
|
|
|
|
const quint32 LAST_BLOCK_INFO_WARNING_INTERVAL = 1 * MSECS_IN_HOUR;
|
|
|
|
|
|
|
|
WalletAdapter& WalletAdapter::instance() {
|
|
|
|
static WalletAdapter inst;
|
|
|
|
return inst;
|
|
|
|
}
|
|
|
|
|
|
|
|
WalletAdapter::WalletAdapter() : QObject(), m_wallet(nullptr), m_mutex(), m_isBackupInProgress(false),
|
|
|
|
m_isSynchronized(false), m_newTransactionsNotificationTimer(),
|
|
|
|
m_lastWalletTransactionId(std::numeric_limits<quint64>::max()) {
|
|
|
|
connect(this, &WalletAdapter::walletInitCompletedSignal, this, &WalletAdapter::onWalletInitCompleted, Qt::QueuedConnection);
|
|
|
|
connect(this, &WalletAdapter::walletSendTransactionCompletedSignal, this, &WalletAdapter::onWalletSendTransactionCompleted, Qt::QueuedConnection);
|
|
|
|
connect(this, &WalletAdapter::updateBlockStatusTextSignal, this, &WalletAdapter::updateBlockStatusText, Qt::QueuedConnection);
|
|
|
|
connect(this, &WalletAdapter::updateBlockStatusTextWithDelaySignal, this, &WalletAdapter::updateBlockStatusTextWithDelay, Qt::QueuedConnection);
|
|
|
|
connect(&m_newTransactionsNotificationTimer, &QTimer::timeout, this, &WalletAdapter::notifyAboutLastTransaction);
|
|
|
|
connect(this, &WalletAdapter::walletSynchronizationProgressUpdatedSignal, this, [&]() {
|
|
|
|
if (!m_newTransactionsNotificationTimer.isActive()) {
|
|
|
|
m_newTransactionsNotificationTimer.start();
|
|
|
|
}
|
|
|
|
}, Qt::QueuedConnection);
|
|
|
|
|
|
|
|
connect(this, &WalletAdapter::walletSynchronizationCompletedSignal, this, [&]() {
|
|
|
|
m_newTransactionsNotificationTimer.stop();
|
|
|
|
notifyAboutLastTransaction();
|
|
|
|
}, Qt::QueuedConnection);
|
|
|
|
|
|
|
|
m_newTransactionsNotificationTimer.setInterval(500);
|
|
|
|
}
|
|
|
|
|
|
|
|
WalletAdapter::~WalletAdapter() {
|
|
|
|
}
|
|
|
|
|
|
|
|
QString WalletAdapter::getAddress() const {
|
|
|
|
try {
|
|
|
|
return m_wallet == nullptr ? QString() : QString::fromStdString(m_wallet->getAddress());
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 WalletAdapter::getActualBalance() const {
|
|
|
|
try {
|
|
|
|
return m_wallet == nullptr ? 0 : m_wallet->actualBalance();
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 WalletAdapter::getPendingBalance() const {
|
|
|
|
try {
|
|
|
|
return m_wallet == nullptr ? 0 : m_wallet->pendingBalance();
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::open(const QString& _password) {
|
|
|
|
Q_ASSERT(m_wallet == nullptr);
|
|
|
|
Settings::instance().setEncrypted(!_password.isEmpty());
|
|
|
|
Q_EMIT walletStateChangedSignal(tr("Opening wallet"));
|
|
|
|
|
|
|
|
m_wallet = NodeAdapter::instance().createWallet();
|
|
|
|
m_wallet->addObserver(this);
|
|
|
|
|
|
|
|
if (QFile::exists(Settings::instance().getWalletFile())) {
|
|
|
|
if (Settings::instance().getWalletFile().endsWith(".keys")) {
|
|
|
|
if(!importLegacyWallet(_password)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (openFile(Settings::instance().getWalletFile(), true)) {
|
|
|
|
try {
|
|
|
|
m_wallet->initAndLoad(m_file, _password.toStdString());
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
closeFile();
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Settings::instance().setEncrypted(false);
|
|
|
|
try {
|
|
|
|
m_wallet->initAndGenerate("");
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::isOpen() const {
|
|
|
|
return m_wallet != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::importLegacyWallet(const QString &_password) {
|
|
|
|
QString fileName = Settings::instance().getWalletFile();
|
|
|
|
Settings::instance().setEncrypted(!_password.isEmpty());
|
|
|
|
try {
|
|
|
|
fileName.replace(fileName.lastIndexOf(".keys"), 5, ".wallet");
|
|
|
|
if (!openFile(fileName, false)) {
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
CryptoNote::importLegacyKeys(Settings::instance().getWalletFile().toStdString(), _password.toStdString(), m_file);
|
2015-04-29 17:03:08 +00:00
|
|
|
closeFile();
|
|
|
|
Settings::instance().setWalletFile(fileName);
|
|
|
|
return true;
|
|
|
|
} catch (std::system_error& _err) {
|
|
|
|
closeFile();
|
2015-09-18 12:37:30 +00:00
|
|
|
if (_err.code().value() == CryptoNote::error::WRONG_PASSWORD) {
|
2015-04-29 17:03:08 +00:00
|
|
|
Settings::instance().setEncrypted(true);
|
|
|
|
Q_EMIT openWalletWithPasswordSignal(!_password.isEmpty());
|
|
|
|
}
|
|
|
|
} catch (std::runtime_error& _err) {
|
|
|
|
closeFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::close() {
|
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
save(true, true);
|
|
|
|
lock();
|
|
|
|
m_wallet->removeObserver(this);
|
|
|
|
m_isSynchronized = false;
|
|
|
|
m_newTransactionsNotificationTimer.stop();
|
|
|
|
m_lastWalletTransactionId = std::numeric_limits<quint64>::max();
|
|
|
|
Q_EMIT walletCloseCompletedSignal();
|
|
|
|
QCoreApplication::processEvents();
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::save(bool _details, bool _cache) {
|
|
|
|
return save(Settings::instance().getWalletFile() + ".temp", _details, _cache);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::save(const QString& _file, bool _details, bool _cache) {
|
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
if (openFile(_file, false)) {
|
|
|
|
try {
|
|
|
|
m_wallet->save(m_file, _details, _cache);
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
closeFile();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Q_EMIT walletStateChangedSignal(tr("Saving data"));
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::backup(const QString& _file) {
|
|
|
|
if (save(_file.endsWith(".wallet") ? _file : _file + ".wallet", true, false)) {
|
|
|
|
m_isBackupInProgress = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 WalletAdapter::getTransactionCount() const {
|
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
|
|
|
return m_wallet->getTransactionCount();
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 WalletAdapter::getTransferCount() const {
|
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
|
|
|
return m_wallet->getTransferCount();
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
bool WalletAdapter::getTransaction(CryptoNote::TransactionId& _id, CryptoNote::WalletLegacyTransaction& _transaction) {
|
2015-04-29 17:03:08 +00:00
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
|
|
|
return m_wallet->getTransaction(_id, _transaction);
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
bool WalletAdapter::getTransfer(CryptoNote::TransferId& _id, CryptoNote::WalletLegacyTransfer& _transfer) {
|
2015-04-29 17:03:08 +00:00
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
|
|
|
return m_wallet->getTransfer(_id, _transfer);
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
void WalletAdapter::sendTransaction(const QVector<CryptoNote::WalletLegacyTransfer>& _transfers, quint64 _fee, const QString& _paymentId, quint64 _mixin) {
|
2015-04-29 17:03:08 +00:00
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
|
|
|
lock();
|
|
|
|
m_wallet->sendTransaction(_transfers.toStdVector(), _fee, NodeAdapter::instance().convertPaymentId(_paymentId), _mixin, 0);
|
|
|
|
Q_EMIT walletStateChangedSignal(tr("Sending transaction"));
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::changePassword(const QString& _oldPassword, const QString& _newPassword) {
|
|
|
|
Q_CHECK_PTR(m_wallet);
|
|
|
|
try {
|
2015-09-18 12:37:30 +00:00
|
|
|
if (m_wallet->changePassword(_oldPassword.toStdString(), _newPassword.toStdString()).value() == CryptoNote::error::WRONG_PASSWORD) {
|
2015-04-29 17:03:08 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch (std::system_error&) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Settings::instance().setEncrypted(!_newPassword.isEmpty());
|
|
|
|
return save(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::setWalletFile(const QString& _path) {
|
|
|
|
Q_ASSERT(m_wallet == nullptr);
|
|
|
|
Settings::instance().setWalletFile(_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::initCompleted(std::error_code _error) {
|
|
|
|
if (m_file.is_open()) {
|
|
|
|
closeFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT walletInitCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::onWalletInitCompleted(int _error, const QString& _errorText) {
|
|
|
|
switch(_error) {
|
|
|
|
case 0: {
|
|
|
|
Q_EMIT walletActualBalanceUpdatedSignal(m_wallet->actualBalance());
|
|
|
|
Q_EMIT walletPendingBalanceUpdatedSignal(m_wallet->pendingBalance());
|
|
|
|
Q_EMIT updateWalletAddressSignal(QString::fromStdString(m_wallet->getAddress()));
|
|
|
|
Q_EMIT reloadWalletTransactionsSignal();
|
|
|
|
Q_EMIT walletStateChangedSignal(tr("Ready"));
|
|
|
|
QTimer::singleShot(5000, this, SLOT(updateBlockStatusText()));
|
|
|
|
if (!QFile::exists(Settings::instance().getWalletFile())) {
|
|
|
|
save(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2015-09-18 12:37:30 +00:00
|
|
|
case CryptoNote::error::WRONG_PASSWORD:
|
2015-04-29 17:03:08 +00:00
|
|
|
Q_EMIT openWalletWithPasswordSignal(Settings::instance().isEncrypted());
|
|
|
|
Settings::instance().setEncrypted(true);
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
break;
|
|
|
|
default: {
|
|
|
|
delete m_wallet;
|
|
|
|
m_wallet = nullptr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::saveCompleted(std::error_code _error) {
|
|
|
|
if (!_error && !m_isBackupInProgress) {
|
|
|
|
closeFile();
|
|
|
|
renameFile(Settings::instance().getWalletFile() + ".temp", Settings::instance().getWalletFile());
|
|
|
|
Q_EMIT walletStateChangedSignal(tr("Ready"));
|
|
|
|
Q_EMIT updateBlockStatusTextWithDelaySignal();
|
|
|
|
} else if (m_isBackupInProgress) {
|
|
|
|
m_isBackupInProgress = false;
|
|
|
|
closeFile();
|
|
|
|
} else {
|
|
|
|
closeFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT walletSaveCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
void WalletAdapter::synchronizationProgressUpdated(uint32_t _current, uint32_t _total) {
|
2015-04-29 17:03:08 +00:00
|
|
|
m_isSynchronized = false;
|
|
|
|
Q_EMIT walletStateChangedSignal(QString("%1 %2/%3").arg(tr("Synchronizing")).arg(_current).arg(_total));
|
|
|
|
Q_EMIT walletSynchronizationProgressUpdatedSignal(_current, _total);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::synchronizationCompleted(std::error_code _error) {
|
|
|
|
if (!_error) {
|
|
|
|
m_isSynchronized = true;
|
|
|
|
Q_EMIT updateBlockStatusTextSignal();
|
|
|
|
Q_EMIT walletSynchronizationCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::actualBalanceUpdated(uint64_t _actual_balance) {
|
|
|
|
Q_EMIT walletActualBalanceUpdatedSignal(_actual_balance);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::pendingBalanceUpdated(uint64_t _pending_balance) {
|
|
|
|
Q_EMIT walletPendingBalanceUpdatedSignal(_pending_balance);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::externalTransactionCreated(CryptoNote::TransactionId _transactionId) {
|
|
|
|
if (!m_isSynchronized) {
|
|
|
|
m_lastWalletTransactionId = _transactionId;
|
|
|
|
} else {
|
|
|
|
Q_EMIT walletTransactionCreatedSignal(_transactionId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::sendTransactionCompleted(CryptoNote::TransactionId _transaction_id, std::error_code _error) {
|
|
|
|
unlock();
|
|
|
|
Q_EMIT walletSendTransactionCompletedSignal(_transaction_id, _error.value(), QString::fromStdString(_error.message()));
|
|
|
|
Q_EMIT updateBlockStatusTextWithDelaySignal();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::onWalletSendTransactionCompleted(CryptoNote::TransactionId _transactionId, int _error, const QString& _errorText) {
|
|
|
|
if (_error) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-18 12:37:30 +00:00
|
|
|
CryptoNote::WalletLegacyTransaction transaction;
|
2015-04-29 17:03:08 +00:00
|
|
|
if (!this->getTransaction(_transactionId, transaction)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transaction.transferCount == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_EMIT walletTransactionCreatedSignal(_transactionId);
|
|
|
|
|
|
|
|
save(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::transactionUpdated(CryptoNote::TransactionId _transactionId) {
|
|
|
|
Q_EMIT walletTransactionUpdatedSignal(_transactionId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::lock() {
|
|
|
|
m_mutex.lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::unlock() {
|
|
|
|
m_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WalletAdapter::openFile(const QString& _file, bool _readOnly) {
|
|
|
|
lock();
|
|
|
|
m_file.open(_file.toStdString(), std::ios::binary | (_readOnly ? std::ios::in : (std::ios::out | std::ios::trunc)));
|
|
|
|
if (!m_file.is_open()) {
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_file.is_open();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::closeFile() {
|
|
|
|
m_file.close();
|
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::notifyAboutLastTransaction() {
|
|
|
|
if (m_lastWalletTransactionId != std::numeric_limits<quint64>::max()) {
|
|
|
|
Q_EMIT walletTransactionCreatedSignal(m_lastWalletTransactionId);
|
|
|
|
m_lastWalletTransactionId = std::numeric_limits<quint64>::max();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::renameFile(const QString& _oldName, const QString& _newName) {
|
|
|
|
Q_ASSERT(QFile::exists(_oldName));
|
|
|
|
QFile::remove(_newName);
|
|
|
|
QFile::rename(_oldName, _newName);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::updateBlockStatusText() {
|
|
|
|
if (m_wallet == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QDateTime currentTime = QDateTime::currentDateTimeUtc();
|
|
|
|
const QDateTime blockTime = NodeAdapter::instance().getLastLocalBlockTimestamp();
|
|
|
|
quint64 blockAge = blockTime.msecsTo(currentTime);
|
|
|
|
const QString warningString = blockTime.msecsTo(currentTime) < LAST_BLOCK_INFO_WARNING_INTERVAL ? "" :
|
|
|
|
QString(" Warning: last block was received %1 hours %2 minutes ago").arg(blockAge / MSECS_IN_HOUR).arg(blockAge % MSECS_IN_HOUR / MSECS_IN_MINUTE);
|
|
|
|
Q_EMIT walletStateChangedSignal(QString(tr("Wallet synchronized. Height: %1 | Time (UTC): %2%3")).
|
|
|
|
arg(NodeAdapter::instance().getLastLocalBlockHeight()).
|
|
|
|
arg(QLocale(QLocale::English).toString(blockTime, "dd MMM yyyy, HH:mm:ss")).
|
|
|
|
arg(warningString));
|
|
|
|
|
|
|
|
QTimer::singleShot(LAST_BLOCK_INFO_UPDATING_INTERVAL, this, SLOT(updateBlockStatusText()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletAdapter::updateBlockStatusTextWithDelay() {
|
|
|
|
QTimer::singleShot(5000, this, SLOT(updateBlockStatusText()));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|