Merge pull request 'Multibroadcast transactions' (#345) from tobtoht/feather:wizard_redesign into master

Reviewed-on: https://git.featherwallet.org/feather/feather/pulls/345
This commit is contained in:
tobtoht 2021-03-24 01:42:50 +00:00
commit a73d12a14f
34 changed files with 814 additions and 566 deletions

View file

@ -29,7 +29,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "52acbb68c1a4cc67ee539325a8febc2c144596c4")
set(MONERO_HEAD "41327974116dedccc2f9709d8ad3a8a1f591faed")
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)

View file

@ -42,9 +42,9 @@ RUN git clone -b v1.2.11 --depth 1 https://github.com/madler/zlib && \
make -j$THREADS install && \
rm -rf $(pwd)
RUN git clone -b tor-0.4.5.6 --depth 1 https://git.torproject.org/tor.git && \
RUN git clone -b tor-0.4.5.7 --depth 1 https://git.torproject.org/tor.git && \
cd tor && \
git reset --hard b36a00e9a9d3eb4b2949951afaa72e45fb7e68cd && \
git reset --hard 83f895c015de55201e5f226f84a866f30f5ee14b && \
./autogen.sh && \
./configure \
--disable-asciidoc \

View file

@ -146,9 +146,9 @@ RUN wget https://github.com/libevent/libevent/releases/download/release-2.1.11-s
make -j$THREADS install && \
rm -rf $(pwd)
RUN git clone -b tor-0.4.5.6 --depth 1 https://git.torproject.org/tor.git && \
RUN git clone -b tor-0.4.5.7 --depth 1 https://git.torproject.org/tor.git && \
cd tor && \
git reset --hard b36a00e9a9d3eb4b2949951afaa72e45fb7e68cd && \
git reset --hard 83f895c015de55201e5f226f84a866f30f5ee14b && \
./autogen.sh && \
./configure --host=x86_64-w64-mingw32 \
--disable-asciidoc \

2
monero

@ -1 +1 @@
Subproject commit 52acbb68c1a4cc67ee539325a8febc2c144596c4
Subproject commit 41327974116dedccc2f9709d8ad3a8a1f591faed

View file

@ -265,6 +265,35 @@ void AppContext::onAmountPrecisionChanged(int precision) {
model->amountPrecision = precision;
}
void AppContext::commitTransaction(PendingTransaction *tx) {
// Nodes - even well-connected, properly configured ones - consistently fail to relay transactions
// To mitigate transactions failing we just send the transaction to every node we know about over Tor
if (config()->get(Config::multiBroadcast).toBool()) {
this->onMultiBroadcast(tx);
}
this->currentWallet->commitTransactionAsync(tx);
}
void AppContext::onMultiBroadcast(PendingTransaction *tx) {
UtilsNetworking *net = new UtilsNetworking(this->network, this);
DaemonRpc *rpc = new DaemonRpc(this, net, "");
int count = tx->txCount();
for (int i = 0; i < count; i++) {
QString txData = tx->signedTxToHex(i);
for (const auto& node: this->nodes->websocketNodes()) {
if (!node.online) continue;
QString address = node.as_url();
qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address);
rpc->setDaemonAddress(address);
rpc->sendRawTransaction(txData);
}
}
}
void AppContext::onWalletOpened(Wallet *wallet) {
auto state = wallet->status();
if (state != Wallet::Status_Ok) {
@ -334,35 +363,13 @@ void AppContext::setWindowTitle(bool mining) {
void AppContext::onWSMessage(const QJsonObject &msg) {
QString cmd = msg.value("cmd").toString();
if(cmd == "blockheights") {
auto heights = msg.value("data").toObject();
auto mainnet = heights.value("mainnet").toInt();
auto stagenet = heights.value("stagenet").toInt();
auto changed = false;
if (cmd == "blockheights") {
QJsonObject data = msg.value("data").toObject();
int mainnet = data.value("mainnet").toInt();
int stagenet = data.value("stagenet").toInt();
if(!this->heights.contains("mainnet")) {
this->heights["mainnet"] = mainnet;
changed = true;
}
else {
if (mainnet > this->heights["mainnet"]) {
this->heights["mainnet"] = mainnet;
changed = true;
}
}
if(!this->heights.contains("stagenet")) {
this->heights["stagenet"] = stagenet;
changed = true;
}
else {
if (stagenet > this->heights["stagenet"]) {
this->heights["stagenet"] = stagenet;
changed = true;
}
}
if(changed)
emit blockHeightWSUpdated(this->heights);
this->heights[NetworkType::MAINNET] = mainnet;
this->heights[NetworkType::STAGENET] = stagenet;
}
else if(cmd == "nodes") {
@ -478,15 +485,16 @@ void AppContext::onWSCCS(const QJsonArray &ccs_data) {
}
void AppContext::createConfigDirectory(const QString &dir) {
QString config_dir_tor = QString("%1%2").arg(dir).arg("tor");
QString config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
QString config_dir_tor = QString("%1/%2").arg(dir).arg("tor");
QString config_dir_tordata = QString("%1/%2").arg(dir).arg("tor/data");
QStringList createDirs({dir, config_dir_tor, config_dir_tordata});
for(const auto &d: createDirs) {
if(!Utils::dirExists(d)) {
qDebug() << QString("Creating directory: %1").arg(d);
if (!QDir().mkpath(d))
throw std::runtime_error("Could not create directory " + d.toStdString());
if (!QDir().mkpath(d)) {
qCritical() << "Could not create directory " << d;
}
}
}
}
@ -690,14 +698,14 @@ void AppContext::onWalletRefreshed(bool success) {
}
qDebug() << "Wallet refresh status: " << success;
this->currentWallet->refreshHeightAsync();
}
void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) {
// Called whenever a new block gets scanned by the wallet
this->syncStatusUpdated(blockheight, targetHeight);
if (this->currentWallet->synchronized()) {
if (!this->currentWallet) return;
if (this->currentWallet->isSynchronized()) {
this->currentWallet->coins()->refreshUnlocked();
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
// Todo: only refresh tx confirmations
@ -759,7 +767,7 @@ void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, con
void AppContext::storeWallet() {
// Do not store a synchronizing wallet: store() is NOT thread safe and may crash the wallet
if (this->currentWallet == nullptr || !this->currentWallet->synchronized())
if (this->currentWallet == nullptr || !this->currentWallet->isSynchronized())
return;
qDebug() << "Storing wallet";

View file

@ -18,6 +18,7 @@
#include "utils/wsclient.h"
#include "utils/txfiathistory.h"
#include "utils/FeatherSeed.h"
#include "utils/daemonrpc.h"
#include "widgets/RedditPost.h"
#include "widgets/CCSEntry.h"
#include "utils/RestoreHeightLookup.h"
@ -53,7 +54,7 @@ public:
QString walletPassword = "";
NetworkType::Type networkType;
QMap<QString, int> heights;
QMap<NetworkType::Type, int> heights;
QMap<NetworkType::Type, RestoreHeightLookup*> restoreHeights;
PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low;
QString seedLanguage = "English"; // 14 word `monero-seed` only has English
@ -66,6 +67,7 @@ public:
WSClient *ws;
XmRig *XMRig;
Nodes *nodes;
DaemonRpc *daemonRpc;
static Prices *prices;
static WalletKeysFilesModel *wallets;
static double balance;
@ -81,6 +83,7 @@ public:
void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = "");
void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false);
void createWalletFinish(const QString &password);
void commitTransaction(PendingTransaction *tx);
void syncStatusUpdated(quint64 height, quint64 target);
void updateBalance();
void initTor();
@ -105,6 +108,7 @@ public slots:
void onSetRestoreHeight(quint64 height);
void onPreferredFiatCurrencyChanged(const QString &symbol);
void onAmountPrecisionChanged(int precision);
void onMultiBroadcast(PendingTransaction *tx);
private slots:
void onWSNodes(const QJsonArray &nodes);
@ -134,7 +138,6 @@ signals:
void blockchainSync(int height, int target);
void refreshSync(int height, int target);
void synchronized();
void blockHeightWSUpdated(QMap<QString, int> heights);
void walletRefreshed();
void walletOpened();
void walletCreatedError(const QString &msg);

View file

@ -1455,3 +1455,58 @@
1599426969:2181000
1599604157:2182500
1599786817:2184000
1599969318:2185500
1600147688:2187000
1600327569:2188500
1600510218:2190000
1600688674:2191500
1600867919:2193000
1601047108:2194500
1601226981:2196000
1601404059:2197500
1601584064:2199000
1601762823:2200500
1601946046:2202000
1602123016:2203500
1602302020:2205000
1602480047:2206500
1602640101:2208000
1602834791:2209500
1603025260:2211000
1603202279:2212500
1603383969:2214000
1603566322:2215500
1603746737:2217000
1603927129:2218500
1604112554:2220000
1604289877:2221500
1604474365:2223000
1604653260:2224500
1604830135:2226000
1605007197:2227500
1605185169:2229000
1605372544:2230500
1605551973:2232000
1605734501:2233500
1605910469:2235000
1606091151:2236500
1606276021:2238000
1606452893:2239500
1606627629:2241000
1606811187:2242500
1606991795:2244000
1607172363:2245500
1607353315:2247000
1607530844:2248500
1607716100:2250000
1607890493:2251500
1608067069:2253000
1608249924:2254500
1608429171:2256000
1608613290:2257500
1608791286:2259000
1608972563:2260500
1609151786:2262000
1609331787:2263500
1609513572:2265000
1609692129:2266500

View file

@ -442,3 +442,90 @@
1599636188:661500
1599809533:663000
1600001474:664500
1600168506:666000
1600360911:667500
1600585502:669000
1600723074:670500
1600989712:672000
1601126870:673500
1601342006:675000
1601531952:676500
1601712860:678000
1601888075:679500
1602072633:681000
1602210804:682500
1602390469:684000
1602588519:685500
1602759479:687000
1602934491:688500
1603134321:690000
1603296351:691500
1603486625:693000
1603689061:694500
1603841411:696000
1604017061:697500
1604195275:699000
1604379244:700500
1604562487:702000
1604737907:703500
1604923764:705000
1605103115:706500
1605286071:708000
1605470071:709500
1605639278:711000
1605823578:712500
1606010900:714000
1606188630:715500
1606366871:717000
1606530937:718500
1606743518:720000
1606899734:721500
1607074620:723000
1607295957:724500
1607480999:726000
1607660787:727500
1607840859:729000
1608022934:730500
1608202441:732000
1608382317:733500
1608563014:735000
1608760731:736500
1608941535:738000
1609125279:739500
1609300581:741000
1609479221:742500
1609663165:744000
1609842526:745500
1610017591:747000
1610178062:748500
1610393422:750000
1610574236:751500
1610755343:753000
1610932059:754500
1611119735:756000
1611303782:757500
1611475083:759000
1611663782:760500
1611847193:762000
1611996841:763500
1612304524:765000
1612476234:766500
1612666606:768000
1612877927:769500
1613072065:771000
1613256943:772500
1613446398:774000
1613622490:775500
1613798350:777000
1614039651:778500
1614217602:780000
1614432073:781500
1614557574:783000
1614817212:784500
1615014204:786000
1615200863:787500
1615390786:789000
1615573554:790500
1615731420:792000
1615933315:793500
1616104918:795000

View file

@ -47,7 +47,7 @@ void DebugInfoDialog::updateInfo() {
ui->label_daemonHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainHeight()));
ui->label_targetHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainTargetHeight()));
ui->label_restoreHeight->setText(QString::number(m_ctx->currentWallet->getWalletCreationHeight()));
ui->label_synchronized->setText(m_ctx->currentWallet->synchronized() ? "True" : "False");
ui->label_synchronized->setText(m_ctx->currentWallet->isSynchronized() ? "True" : "False");
auto node = m_ctx->nodes->connection();
ui->label_remoteNode->setText(node.full);
@ -75,12 +75,14 @@ QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
switch (status) {
case Wallet::ConnectionStatus_Disconnected:
return "Disconnected";
case Wallet::ConnectionStatus_Connected:
return "Connected";
case Wallet::ConnectionStatus_WrongVersion:
return "Daemon wrong version";
case Wallet::ConnectionStatus_Connecting:
return "Connecting";
case Wallet::ConnectionStatus_Synchronizing:
return "Synchronizing";
case Wallet::ConnectionStatus_Synchronized:
return "Synchronized";
default:
return "Unknown";
}

View file

@ -172,7 +172,7 @@ void TxConfAdvDialog::signedQrCode() {
void TxConfAdvDialog::broadcastTransaction() {
if (m_tx == nullptr) return;
m_ctx->currentWallet->commitTransactionAsync(m_tx);
m_ctx->commitTransaction(m_tx);
QDialog::accept();
}

View file

@ -84,7 +84,11 @@ void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet
m_wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt();
// Load view state
ui->history->setViewState(QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()));
QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray());
if (!historyViewState.isEmpty()) {
ui->history->setViewState(historyViewState);
}
}
void HistoryWidget::resetModel()

View file

@ -18,10 +18,6 @@
#include "utils/ScopeGuard.h"
namespace {
static const int DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS = 5;
static const int DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS = 30;
static const int WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS = 5;
static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] = "feather.subaddress_account";
}
@ -78,10 +74,6 @@ void Wallet::refreshingSet(bool value)
}
}
void Wallet::setConnectionTimeout(int timeout) {
m_connectionTimeout = timeout;
}
void Wallet::setConnectionStatus(ConnectionStatus value)
{
if (m_connectionStatus == value)
@ -102,6 +94,17 @@ void Wallet::setConnectionStatus(ConnectionStatus value)
}
}
void Wallet::onNewBlock(uint64_t walletHeight) {
// Called whenever a new block gets scanned by the wallet
quint64 daemonHeight = m_daemonBlockChainTargetHeight;
if (walletHeight < (daemonHeight - 1)) {
setConnectionStatus(ConnectionStatus_Synchronizing);
} else {
setConnectionStatus(ConnectionStatus_Synchronized);
}
}
QString Wallet::getProxyAddress() const
{
QMutexLocker locker(&m_proxyMutex);
@ -127,9 +130,21 @@ void Wallet::setProxyAddress(QString address)
bool Wallet::synchronized() const
{
// Misleading: this will return true if wallet has synchronized at least once even if it isn't currently synchronized
return m_walletImpl->synchronized();
}
bool Wallet::isSynchronized() const
{
return connectionStatus() == ConnectionStatus_Synchronized;
}
bool Wallet::isConnected() const
{
auto status = connectionStatus();
return status == ConnectionStatus_Synchronizing || status == ConnectionStatus_Synchronized;
}
QString Wallet::errorString() const
{
return QString::fromStdString(m_walletImpl->errorString());
@ -211,7 +226,6 @@ bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 uppe
}
emit proxyAddressChanged();
setTrustedDaemon(trustedDaemon);
setTrustedDaemon(trustedDaemon);
return true;
}
@ -239,7 +253,6 @@ void Wallet::initAsync(
{
emit walletCreationHeightChanged();
qDebug() << "init async finished - starting refresh";
refreshHeightAsync();
startRefresh();
}
});
@ -249,6 +262,12 @@ void Wallet::initAsync(
}
}
bool Wallet::setDaemon(const QString &daemonAddress)
{
qDebug() << "setDaemon: " + daemonAddress;
return m_walletImpl->setDaemon(daemonAddress.toStdString(), m_daemonUsername.toStdString(), m_daemonPassword.toStdString(), m_useSSL);
}
bool Wallet::isHwBacked() const
{
return m_walletImpl->getDeviceType() != Monero::Wallet::Device_Software;
@ -380,37 +399,53 @@ void Wallet::deviceShowAddressAsync(quint32 accountIndex, quint32 addressIndex,
});
}
void Wallet::refreshHeightAsync()
bool Wallet::refreshHeights()
{
m_scheduler.run([this] {
// daemonHeight and targetHeight will be 0 if call to get_info fails
quint64 daemonHeight;
QPair<bool, QFuture<void>> daemonHeightFuture = m_scheduler.run([this, &daemonHeight] {
daemonHeight = daemonBlockChainHeight();
daemonHeight = m_walletImpl->daemonBlockChainHeight();;
});
if (!daemonHeightFuture.first)
{
return;
return false;
}
// We must wait here for get_info to return, otherwise targetHeight will get cache value of 0
daemonHeightFuture.second.waitForFinished();
bool success = daemonHeight > 0;
quint64 targetHeight;
quint64 targetHeight = 0;
if (success) {
QPair<bool, QFuture<void>> targetHeightFuture = m_scheduler.run([this, &targetHeight] {
targetHeight = daemonBlockChainTargetHeight();
targetHeight = m_walletImpl->daemonBlockChainTargetHeight();
});
if (!targetHeightFuture.first)
{
return;
return false;
}
quint64 walletHeight = blockChainHeight();
daemonHeightFuture.second.waitForFinished();
targetHeightFuture.second.waitForFinished();
if (daemonHeight > 0 && targetHeight > 0) {
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
qDebug() << "Setting connection status from refreshHeightAsync";
setConnectionStatus(ConnectionStatus_Connected);
}
});
m_daemonBlockChainHeight = daemonHeight;
m_daemonBlockChainTargetHeight = targetHeight;
success = (daemonHeight > 0 && targetHeight > 0);
if (success) {
quint64 walletHeight = blockChainHeight();
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
if (walletHeight < (daemonHeight - 1)) {
setConnectionStatus(ConnectionStatus_Synchronizing);
} else {
setConnectionStatus(ConnectionStatus_Synchronized);
}
} else {
setConnectionStatus(ConnectionStatus_Disconnected);
}
return success;
}
quint64 Wallet::blockChainHeight() const
@ -420,32 +455,13 @@ quint64 Wallet::blockChainHeight() const
quint64 Wallet::daemonBlockChainHeight() const
{
// cache daemon blockchain height for some time (60 seconds by default)
if (m_daemonBlockChainHeight == 0
|| m_daemonBlockChainHeightTime.elapsed() / 1000 > m_daemonBlockChainHeightTtl)
{
m_daemonBlockChainHeight = m_walletImpl->daemonBlockChainHeight();
m_daemonBlockChainHeightTime.restart();
}
// Can not block UI
return m_daemonBlockChainHeight;
}
quint64 Wallet::daemonBlockChainTargetHeight() const
{
if (m_daemonBlockChainTargetHeight <= 1
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl)
{
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
// Target height is set to 0 if daemon is synced.
// Use current height from daemon when target height < current height
if (m_daemonBlockChainTargetHeight < m_daemonBlockChainHeight){
m_daemonBlockChainTargetHeight = m_daemonBlockChainHeight;
}
m_daemonBlockChainTargetHeightTime.restart();
}
// Can not block UI
return m_daemonBlockChainTargetHeight;
}
@ -584,11 +600,15 @@ PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QSt
quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority)
{
pauseRefresh();
std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl,0);
startRefresh();
return result;
}
@ -606,6 +626,8 @@ void Wallet::createTransactionAsync(const QString &dst_addr, const QString &paym
PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority)
{
pauseRefresh();
std::vector<std::string> dests;
for (auto &addr : dst_addr) {
dests.push_back(addr.toStdString());
@ -619,6 +641,8 @@ PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &d
// TODO: remove mixin count
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast<Monero::PendingTransaction::Priority>(priority));
PendingTransaction * result = new PendingTransaction(ptImpl);
startRefresh();
return result;
}
@ -638,11 +662,15 @@ void Wallet::createTransactionMultiDestAsync(const QVector<QString> &dst_addr, c
PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority)
{
pauseRefresh();
std::set<uint32_t> subaddr_indices;
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction(
dst_addr.toStdString(), payment_id.toStdString(), Monero::optional<uint64_t>(), mixin_count,
static_cast<Monero::PendingTransaction::Priority>(priority), currentSubaddressAccount(), subaddr_indices);
PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh();
return result;
}
@ -660,9 +688,13 @@ void Wallet::createTransactionAllAsync(const QString &dst_addr, const QString &p
PendingTransaction *Wallet::createTransactionSingle(const QString &key_image, const QString &dst_addr, const size_t outputs,
PendingTransaction::Priority priority)
{
pauseRefresh();
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionSingle(key_image.toStdString(), dst_addr.toStdString(),
outputs, static_cast<Monero::PendingTransaction::Priority>(priority));
PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh();
return result;
}
@ -678,8 +710,12 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin
PendingTransaction *Wallet::createSweepUnmixableTransaction()
{
pauseRefresh();
Monero::PendingTransaction * ptImpl = m_walletImpl->createSweepUnmixableTransaction();
PendingTransaction * result = new PendingTransaction(ptImpl, this);
startRefresh();
return result;
}
@ -870,16 +906,6 @@ QString Wallet::integratedAddress(const QString &paymentId) const
return QString::fromStdString(m_walletImpl->integratedAddress(paymentId.toStdString()));
}
QString Wallet::paymentId() const
{
return m_paymentId;
}
void Wallet::setPaymentId(const QString &paymentId)
{
m_paymentId = paymentId;
}
QString Wallet::getCacheAttribute(const QString &key) const {
return QString::fromStdString(m_walletImpl->getCacheAttribute(key.toStdString()));
}
@ -1221,11 +1247,8 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent)
, m_addressBook(nullptr)
, m_addressBookModel(nullptr)
, m_daemonBlockChainHeight(0)
, m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS)
, m_daemonBlockChainTargetHeight(0)
, m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS)
, m_connectionStatus(Wallet::ConnectionStatus_Disconnected)
, m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS)
, m_disconnected(true)
, m_currentSubaddressAccount(0)
, m_subaddress(nullptr)
@ -1248,11 +1271,7 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent)
m_walletImpl->setListener(m_walletListener);
m_currentSubaddressAccount = getCacheAttribute(ATTRIBUTE_SUBADDRESS_ACCOUNT).toUInt();
// start cache timers
m_connectionStatusTime.start();
m_daemonBlockChainHeightTime.start();
m_daemonBlockChainTargetHeightTime.start();
m_initialized = false;
m_connectionStatusRunning = false;
m_daemonUsername = "";
m_daemonPassword = "";
@ -1286,11 +1305,10 @@ Wallet::~Wallet()
//Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl);
if(status() == Status_Critical)
qDebug("Not storing wallet cache");
// Don't store on wallet close for now
// else if( m_walletImpl->store(""))
// qDebug("Wallet cache stored successfully");
// else
// qDebug("Error storing wallet cache");
else if( m_walletImpl->store(""))
qDebug("Wallet cache stored successfully");
else
qDebug("Error storing wallet cache");
delete m_walletImpl;
m_walletImpl = NULL;
delete m_walletListener;
@ -1314,7 +1332,12 @@ void Wallet::startRefreshThread()
if (elapsed >= refreshInterval || m_refreshNow)
{
m_refreshNow = false;
// Don't call refresh function if we don't have the daemon and target height
// We do this to prevent to UI from getting confused about the amount of blocks that are still remaining
bool haveHeights = refreshHeights();
if (haveHeights) {
refresh(false);
}
last = std::chrono::steady_clock::now();
}
}
@ -1329,9 +1352,7 @@ void Wallet::startRefreshThread()
}
void Wallet::onRefreshed(bool success) {
if (success) {
setConnectionStatus(ConnectionStatus_Connected);
} else {
if (!success) {
setConnectionStatus(ConnectionStatus_Disconnected);
}
}

View file

@ -74,37 +74,9 @@ struct TxProofResult {
class Wallet : public QObject, public PassprasePrompter
{
Q_OBJECT
Q_PROPERTY(bool disconnected READ disconnected NOTIFY disconnectedChanged)
Q_PROPERTY(bool refreshing READ refreshing NOTIFY refreshingChanged)
Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
Q_PROPERTY(Status status READ status)
Q_PROPERTY(NetworkType::Type nettype READ nettype)
Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus)
Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount NOTIFY currentSubaddressAccountChanged)
Q_PROPERTY(bool synchronized READ synchronized)
Q_PROPERTY(QString errorString READ errorString)
Q_PROPERTY(TransactionHistory * history READ history)
Q_PROPERTY(QString paymentId READ paymentId WRITE setPaymentId)
Q_PROPERTY(TransactionHistoryProxyModel * historyModel READ historyModel NOTIFY historyModelChanged)
Q_PROPERTY(QString path READ path)
Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel)
Q_PROPERTY(AddressBook * addressBook READ addressBook NOTIFY addressBookChanged)
Q_PROPERTY(SubaddressModel * subaddressModel READ subaddressModel)
Q_PROPERTY(Subaddress * subaddress READ subaddress)
Q_PROPERTY(SubaddressAccountModel * subaddressAccountModel READ subaddressAccountModel)
Q_PROPERTY(SubaddressAccount * subaddressAccount READ subaddressAccount)
Q_PROPERTY(bool viewOnly READ viewOnly)
Q_PROPERTY(QString secretViewKey READ getSecretViewKey)
Q_PROPERTY(QString publicViewKey READ getPublicViewKey)
Q_PROPERTY(QString secretSpendKey READ getSecretSpendKey)
Q_PROPERTY(QString publicSpendKey READ getPublicSpendKey)
Q_PROPERTY(QString daemonLogPath READ getDaemonLogPath CONSTANT)
Q_PROPERTY(QString proxyAddress READ getProxyAddress WRITE setProxyAddress NOTIFY proxyAddressChanged)
Q_PROPERTY(quint64 walletCreationHeight READ getWalletCreationHeight WRITE setWalletCreationHeight NOTIFY walletCreationHeightChanged)
public:
enum Status {
Status_Ok = Monero::Wallet::Status_Ok,
Status_Error = Monero::Wallet::Status_Error,
@ -115,9 +87,10 @@ public:
enum ConnectionStatus {
ConnectionStatus_Disconnected = Monero::Wallet::ConnectionStatus_Disconnected,
ConnectionStatus_Connected = Monero::Wallet::ConnectionStatus_Connected,
ConnectionStatus_WrongVersion = Monero::Wallet::ConnectionStatus_WrongVersion,
ConnectionStatus_Connecting
ConnectionStatus_Connecting = 9,
ConnectionStatus_Synchronizing = 10,
ConnectionStatus_Synchronized = 11
};
Q_ENUM(ConnectionStatus)
@ -132,7 +105,7 @@ public:
QString getSeedLanguage() const;
//! set seed language
Q_INVOKABLE void setSeedLanguage(const QString &lang);
void setSeedLanguage(const QString &lang);
//! returns last operation's status
Status status() const;
@ -143,32 +116,37 @@ public:
//! returns true if wallet was ever synchronized
bool synchronized() const;
//! returns true if wallet is currently synchronized
bool isSynchronized() const;
//! return true if wallet is connected to a node
bool isConnected() const;
//! returns last operation's error message
QString errorString() const;
//! changes the password using existing parameters (path, seed, seed lang)
Q_INVOKABLE bool setPassword(const QString &password);
bool setPassword(const QString &password);
//! get current wallet password
Q_INVOKABLE QString getPassword();
QString getPassword();
//! returns wallet's public address
Q_INVOKABLE QString address(quint32 accountIndex, quint32 addressIndex) const;
QString address(quint32 accountIndex, quint32 addressIndex) const;
//! returns the subaddress index of the address
Q_INVOKABLE SubaddressIndex subaddressIndex(const QString &address) const;
SubaddressIndex subaddressIndex(const QString &address) const;
//! returns wallet file's path
QString path() const;
//! saves wallet to the file by given path
//! empty path stores in current location
Q_INVOKABLE void store(const QString &path = "");
// Q_INVOKABLE void storeAsync(const QJSValue &callback, const QString &path = "");
void store(const QString &path = "");
// void storeAsync(const QJSValue &callback, const QString &path = "");
//! initializes wallet asynchronously
Q_INVOKABLE void initAsync(
void initAsync(
const QString &daemonAddress,
bool trustedDaemon = false,
quint64 upperTransactionLimit = 0,
@ -177,167 +155,167 @@ public:
quint64 restoreHeight = 0,
const QString &proxyAddress = "");
bool setDaemon(const QString &daemonAddress);
// Set daemon rpc user/pass
Q_INVOKABLE void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");
void setDaemonLogin(const QString &daemonUsername = "", const QString &daemonPassword = "");
//! create a view only wallet
Q_INVOKABLE bool createViewOnly(const QString &path, const QString &password) const;
bool createViewOnly(const QString &path, const QString &password) const;
//! connects to daemon
Q_INVOKABLE bool connectToDaemon();
//! set connect to daemon timeout
Q_INVOKABLE void setConnectionTimeout(int timeout);
bool connectToDaemon();
//! indicates if daemon is trusted
Q_INVOKABLE void setTrustedDaemon(bool arg);
void setTrustedDaemon(bool arg);
//! indicates if ssl should be used to connect to daemon
Q_INVOKABLE void setUseSSL(bool ssl);
void setUseSSL(bool ssl);
//! returns balance
Q_INVOKABLE quint64 balance() const;
Q_INVOKABLE quint64 balance(quint32 accountIndex) const;
Q_INVOKABLE quint64 balanceAll() const;
quint64 balance() const;
quint64 balance(quint32 accountIndex) const;
quint64 balanceAll() const;
//! returns unlocked balance
Q_INVOKABLE quint64 unlockedBalance() const;
Q_INVOKABLE quint64 unlockedBalance(quint32 accountIndex) const;
Q_INVOKABLE quint64 unlockedBalanceAll() const;
quint64 unlockedBalance() const;
quint64 unlockedBalance(quint32 accountIndex) const;
quint64 unlockedBalanceAll() const;
//! account/address management
quint32 currentSubaddressAccount() const;
Q_INVOKABLE void switchSubaddressAccount(quint32 accountIndex);
Q_INVOKABLE void addSubaddressAccount(const QString& label);
Q_INVOKABLE quint32 numSubaddressAccounts() const;
Q_INVOKABLE quint32 numSubaddresses(quint32 accountIndex) const;
Q_INVOKABLE void addSubaddress(const QString& label);
Q_INVOKABLE QString getSubaddressLabel(quint32 accountIndex, quint32 addressIndex) const;
Q_INVOKABLE void setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, const QString &label);
Q_INVOKABLE void deviceShowAddressAsync(quint32 accountIndex, quint32 addressIndex, const QString &paymentId);
void switchSubaddressAccount(quint32 accountIndex);
void addSubaddressAccount(const QString& label);
quint32 numSubaddressAccounts() const;
quint32 numSubaddresses(quint32 accountIndex) const;
void addSubaddress(const QString& label);
QString getSubaddressLabel(quint32 accountIndex, quint32 addressIndex) const;
void setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, const QString &label);
void deviceShowAddressAsync(quint32 accountIndex, quint32 addressIndex, const QString &paymentId);
//! hw-device backed wallets
Q_INVOKABLE bool isHwBacked() const;
Q_INVOKABLE bool isLedger() const;
Q_INVOKABLE bool isTrezor() const;
bool isHwBacked() const;
bool isLedger() const;
bool isTrezor() const;
//! returns if view only wallet
Q_INVOKABLE bool viewOnly() const;
bool viewOnly() const;
//! return true if deterministic keys
Q_INVOKABLE bool isDeterministic() const;
bool isDeterministic() const;
Q_INVOKABLE void refreshHeightAsync();
//! refresh daemon blockchain and target height
bool refreshHeights();
//! export/import key images
Q_INVOKABLE bool exportKeyImages(const QString& path, bool all = false);
Q_INVOKABLE bool importKeyImages(const QString& path);
bool exportKeyImages(const QString& path, bool all = false);
bool importKeyImages(const QString& path);
//! export/import outputs
Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false);
Q_INVOKABLE bool importOutputs(const QString& path);
bool exportOutputs(const QString& path, bool all = false);
bool importOutputs(const QString& path);
//! import a transaction
Q_INVOKABLE bool importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen);
bool importTransaction(const QString& txid, const QVector<quint64>& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen);
Q_INVOKABLE QString printBlockchain();
Q_INVOKABLE QString printTransfers();
Q_INVOKABLE QString printPayments();
Q_INVOKABLE QString printUnconfirmedPayments();
Q_INVOKABLE QString printConfirmedTransferDetails();
Q_INVOKABLE QString printUnconfirmedTransferDetails();
Q_INVOKABLE QString printPubKeys();
Q_INVOKABLE QString printTxNotes();
Q_INVOKABLE QString printSubaddresses();
Q_INVOKABLE QString printSubaddressLabels();
Q_INVOKABLE QString printAdditionalTxKeys();
Q_INVOKABLE QString printAttributes();
Q_INVOKABLE QString printKeyImages();
Q_INVOKABLE QString printAccountTags();
Q_INVOKABLE QString printTxKeys();
Q_INVOKABLE QString printAddressBook();
Q_INVOKABLE QString printScannedPoolTxs();
QString printBlockchain();
QString printTransfers();
QString printPayments();
QString printUnconfirmedPayments();
QString printConfirmedTransferDetails();
QString printUnconfirmedTransferDetails();
QString printPubKeys();
QString printTxNotes();
QString printSubaddresses();
QString printSubaddressLabels();
QString printAdditionalTxKeys();
QString printAttributes();
QString printKeyImages();
QString printAccountTags();
QString printTxKeys();
QString printAddressBook();
QString printScannedPoolTxs();
//! refreshes the wallet
Q_INVOKABLE bool refresh(bool historyAndSubaddresses = false);
bool refresh(bool historyAndSubaddresses = false);
// pause/resume refresh
Q_INVOKABLE void startRefresh();
Q_INVOKABLE void pauseRefresh();
void startRefresh();
void pauseRefresh();
//! returns current wallet's block height
//! (can be less than daemon's blockchain height when wallet sync in progress)
Q_INVOKABLE quint64 blockChainHeight() const;
quint64 blockChainHeight() const;
//! returns daemon's blockchain height
Q_INVOKABLE quint64 daemonBlockChainHeight() const;
quint64 daemonBlockChainHeight() const;
//! returns daemon's blockchain target height
Q_INVOKABLE quint64 daemonBlockChainTargetHeight() const;
quint64 daemonBlockChainTargetHeight() const;
//! creates transaction
Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id,
PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id,
quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority);
//! creates async transaction
Q_INVOKABLE void createTransactionAsync(const QString &dst_addr, const QString &payment_id,
void createTransactionAsync(const QString &dst_addr, const QString &payment_id,
quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority);
//! creates multi-destination transaction
Q_INVOKABLE PendingTransaction * createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction * createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority);
//! creates async multi-destination transaction
Q_INVOKABLE void createTransactionMultiDestAsync(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
void createTransactionMultiDestAsync(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority);
//! creates transaction with all outputs
Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id,
PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority);
//! creates async transaction with all outputs
Q_INVOKABLE void createTransactionAllAsync(const QString &dst_addr, const QString &payment_id,
void createTransactionAllAsync(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority);
//! creates transaction with single input
Q_INVOKABLE PendingTransaction * createTransactionSingle(const QString &key_image, const QString &dst_addr,
PendingTransaction * createTransactionSingle(const QString &key_image, const QString &dst_addr,
size_t outputs, PendingTransaction::Priority priority);
//! creates async transaction with single input
Q_INVOKABLE void createTransactionSingleAsync(const QString &key_image, const QString &dst_addr,
void createTransactionSingleAsync(const QString &key_image, const QString &dst_addr,
size_t outputs, PendingTransaction::Priority priority);
//! creates sweep unmixable transaction
Q_INVOKABLE PendingTransaction * createSweepUnmixableTransaction();
PendingTransaction * createSweepUnmixableTransaction();
//! creates async sweep unmixable transaction
Q_INVOKABLE void createSweepUnmixableTransactionAsync();
void createSweepUnmixableTransactionAsync();
//! Sign a transfer from file
Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName);
UnsignedTransaction * loadTxFile(const QString &fileName);
//! Load an unsigned transaction from a base64 encoded string
Q_INVOKABLE UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx);
UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx);
//! Load a signed transaction from file
Q_INVOKABLE PendingTransaction * loadSignedTxFile(const QString &fileName);
PendingTransaction * loadSignedTxFile(const QString &fileName);
//! Submit a transfer from file
Q_INVOKABLE bool submitTxFile(const QString &fileName) const;
bool submitTxFile(const QString &fileName) const;
//! asynchronous transaction commit
Q_INVOKABLE void commitTransactionAsync(PendingTransaction * t);
void commitTransactionAsync(PendingTransaction * t);
//! deletes transaction and frees memory
Q_INVOKABLE void disposeTransaction(PendingTransaction * t);
void disposeTransaction(PendingTransaction * t);
//! deletes unsigned transaction and frees memory
Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);
void disposeTransaction(UnsignedTransaction * t);
// Q_INVOKABLE void estimateTransactionFeeAsync(const QString &destination,
// void estimateTransactionFeeAsync(const QString &destination,
// quint64 amount,
// PendingTransaction::Priority priority,
// const QJSValue &callback);
@ -376,46 +354,41 @@ public:
CoinsModel *coinsModel() const;
//! generate payment id
Q_INVOKABLE QString generatePaymentId() const;
QString generatePaymentId() const;
//! integrated address
Q_INVOKABLE QString integratedAddress(const QString &paymentId) const;
QString integratedAddress(const QString &paymentId) const;
//! signing a message
Q_INVOKABLE QString signMessage(const QString &message, bool filename = false, const QString &address = "") const;
QString signMessage(const QString &message, bool filename = false, const QString &address = "") const;
//! verify a signed message
Q_INVOKABLE bool verifySignedMessage(const QString &message, const QString &address, const QString &signature, bool filename = false) const;
bool verifySignedMessage(const QString &message, const QString &address, const QString &signature, bool filename = false) const;
//! Parse URI
Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error);
//! saved payment id
QString paymentId() const;
void setPaymentId(const QString &paymentId);
bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error);
//! Namespace your cacheAttribute keys to avoid collisions
Q_INVOKABLE bool setCacheAttribute(const QString &key, const QString &val);
Q_INVOKABLE QString getCacheAttribute(const QString &key) const;
bool setCacheAttribute(const QString &key, const QString &val);
QString getCacheAttribute(const QString &key) const;
Q_INVOKABLE bool setUserNote(const QString &txid, const QString &note);
Q_INVOKABLE QString getUserNote(const QString &txid) const;
Q_INVOKABLE QString getTxKey(const QString &txid) const;
//Q_INVOKABLE void getTxKeyAsync(const QString &txid, const QJSValue &callback);
Q_INVOKABLE QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address);
Q_INVOKABLE TxProof getTxProof(const QString &txid, const QString &address, const QString &message) const;
// Q_INVOKABLE void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback);
//Q_INVOKABLE QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
Q_INVOKABLE TxProofResult checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
Q_INVOKABLE TxProof getSpendProof(const QString &txid, const QString &message) const;
// Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback);
Q_INVOKABLE QPair<bool, bool> checkSpendProof(const QString &txid, const QString &message, const QString &signature) const;
bool setUserNote(const QString &txid, const QString &note);
QString getUserNote(const QString &txid) const;
QString getTxKey(const QString &txid) const;
//void getTxKeyAsync(const QString &txid, const QJSValue &callback);
QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address);
TxProof getTxProof(const QString &txid, const QString &address, const QString &message) const;
// void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback);
//QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
TxProofResult checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature);
TxProof getSpendProof(const QString &txid, const QString &message) const;
// void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback);
QPair<bool, bool> checkSpendProof(const QString &txid, const QString &message, const QString &signature) const;
// Rescan spent outputs
Q_INVOKABLE bool rescanSpent();
bool rescanSpent();
// check if fork rules should be used
Q_INVOKABLE bool useForkRules(quint8 version, quint64 earlyBlocks = 0) const;
bool useForkRules(quint8 version, quint64 earlyBlocks = 0) const;
//! Get wallet keys
QString getSecretViewKey() const {return QString::fromStdString(m_walletImpl->secretViewKey());}
@ -427,30 +400,29 @@ public:
void setWalletCreationHeight(quint64 height);
QString getDaemonLogPath() const;
QString getWalletLogPath() const;
// Blackalled outputs
Q_INVOKABLE bool blackballOutput(const QString &amount, const QString &offset);
Q_INVOKABLE bool blackballOutputs(const QList<QString> &outputs, bool add);
Q_INVOKABLE bool blackballOutputs(const QString &filename, bool add);
Q_INVOKABLE bool unblackballOutput(const QString &amount, const QString &offset);
bool blackballOutput(const QString &amount, const QString &offset);
bool blackballOutputs(const QList<QString> &outputs, bool add);
bool blackballOutputs(const QString &filename, bool add);
bool unblackballOutput(const QString &amount, const QString &offset);
// Rings
Q_INVOKABLE QString getRing(const QString &key_image);
Q_INVOKABLE QString getRings(const QString &txid);
Q_INVOKABLE bool setRing(const QString &key_image, const QString &ring, bool relative);
QString getRing(const QString &key_image);
QString getRings(const QString &txid);
bool setRing(const QString &key_image, const QString &ring, bool relative);
// key reuse mitigation options
Q_INVOKABLE void segregatePreForkOutputs(bool segregate);
Q_INVOKABLE void segregationHeight(quint64 height);
Q_INVOKABLE void keyReuseMitigation2(bool mitigation);
void segregatePreForkOutputs(bool segregate);
void segregationHeight(quint64 height);
void keyReuseMitigation2(bool mitigation);
// Passphrase entry for hardware wallets
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
virtual void onWalletPassphraseNeeded(bool on_device) override;
Q_INVOKABLE quint64 getBytesReceived() const;
Q_INVOKABLE quint64 getBytesSent() const;
quint64 getBytesReceived() const;
quint64 getBytesSent() const;
// TODO: setListenter() when it implemented in API
signals:
@ -510,6 +482,8 @@ private:
void setProxyAddress(QString address);
void startRefreshThread();
void onNewBlock(uint64_t height);
private:
friend class WalletManager;
friend class WalletListenerImpl;
@ -523,15 +497,11 @@ private:
QString m_paymentId;
AddressBook * m_addressBook;
mutable AddressBookModel * m_addressBookModel;
mutable QElapsedTimer m_daemonBlockChainHeightTime;
mutable quint64 m_daemonBlockChainHeight;
int m_daemonBlockChainHeightTtl;
mutable QElapsedTimer m_daemonBlockChainTargetHeightTime;
mutable quint64 m_daemonBlockChainTargetHeight;
int m_daemonBlockChainTargetHeightTtl;
mutable ConnectionStatus m_connectionStatus;
int m_connectionStatusTtl;
mutable QElapsedTimer m_connectionStatusTime;
bool m_disconnected;
mutable bool m_initialized;
uint32_t m_currentSubaddressAccount;
@ -542,8 +512,6 @@ private:
Coins * m_coins;
mutable CoinsModel * m_coinsModel;
QMutex m_asyncMutex;
QMutex m_connectionStatusMutex;
bool m_connectionStatusRunning;
QString m_daemonUsername;
QString m_daemonPassword;
QString m_proxyAddress;

View file

@ -32,6 +32,7 @@ void WalletListenerImpl::unconfirmedMoneyReceived(const std::string &txId, uint6
void WalletListenerImpl::newBlock(uint64_t height)
{
// qDebug() << __FUNCTION__;
m_wallet->onNewBlock(height);
emit m_wallet->newBlock(height, m_wallet->daemonBlockChainTargetHeight());
}

View file

@ -481,3 +481,26 @@ void WalletManager::onPassphraseEntered(const QString &passphrase, bool enter_on
m_passphraseReceiver->onPassphraseEntered(passphrase, enter_on_device, entry_abort);
}
}
QString WalletManager::proxyAddress() const
{
QMutexLocker locker(&m_proxyMutex);
return m_proxyAddress;
}
void WalletManager::setProxyAddress(QString address)
{
m_scheduler.run([this, address] {
{
QMutexLocker locker(&m_proxyMutex);
if (!m_pimpl->setProxy(address.toStdString()))
{
qCritical() << "Failed to set proxy address" << address;
}
m_proxyAddress = std::move(address);
}
emit proxyAddressChanged();
});
}

View file

@ -172,6 +172,9 @@ public:
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
virtual void onWalletPassphraseNeeded(bool on_device) override;
QString proxyAddress() const;
void setProxyAddress(QString address);
signals:
void walletOpened(Wallet * wallet);
@ -186,6 +189,7 @@ signals:
const QString &firstSigner,
const QString &secondSigner) const;
void miningStatus(bool isMining) const;
void proxyAddressChanged() const;
public slots:
private:
@ -202,6 +206,8 @@ private:
QPointer<Wallet> m_currentWallet;
PassphraseReceiver * m_passphraseReceiver;
QMutex m_mutex_passphraseReceiver;
QString m_proxyAddress;
mutable QMutex m_proxyMutex;
FutureScheduler m_scheduler;
};

View file

@ -102,9 +102,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
if(cliMode) {
QCoreApplication cli_app(argc, argv);
QCoreApplication::setApplicationName("feather");
QCoreApplication::setOrganizationDomain("featherwallet.org");
QCoreApplication::setOrganizationName("featherwallet.org");
QCoreApplication::setApplicationName("FeatherWallet");
auto *ctx = new AppContext(&parser);
@ -136,9 +134,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
QApplication app(argc, argv);
QApplication::setApplicationName("feather");
QApplication::setOrganizationDomain("featherwallet.org");
QApplication::setOrganizationName("featherwallet.org");
QApplication::setApplicationName("FeatherWallet");
parser.process(app); // Parse again for --help and --version

View file

@ -7,6 +7,7 @@
#include <QCoreApplication>
#include <QSystemTrayIcon>
#include <QFileDialog>
#include <QDesktopWidget>
#include "mainwindow.h"
#include "dialog/txconfdialog.h"
@ -37,6 +38,12 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
pMainWindow = this;
ui->setupUi(this);
// Preload icons for better performance
m_statusDisconnected = QIcon(":/assets/images/status_disconnected.svg");
m_statusConnecting = QIcon(":/assets/images/status_lagging.svg");
m_statusSynchronizing = QIcon(":/assets/images/status_waiting.svg");
m_statusSynchronized = QIcon(":/assets/images/status_connected.svg");
m_windowSettings = new Settings(this);
m_aboutDialog = new AboutDialog(this);
m_windowCalc = new CalcWindow(this);
@ -244,7 +251,11 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
if(config()->get(Config::warnOnAlpha).toBool()) {
QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs "
"you encounter on our Git repository, IRC or on /r/FeatherWallet.";
QMessageBox::warning(this, "Beta Warning", warning);
QMessageBox warningMb(this);
warningMb.setWindowTitle("Beta Warning");
warningMb.setText(warning);
this->centerWidget(warningMb);
warningMb.exec();
config()->set(Config::warnOnAlpha, false);
}
@ -360,10 +371,14 @@ void MainWindow::initMain() {
return;
}
this->setEnabled(false);
this->setEnabled(true);
this->show();
m_wizard = this->createWizard(WalletWizard::Page_Menu);
m_wizard->show();
// wizard won't spawn on top of MainWindow without this dumb pattern
this->setEnabled(false);
m_wizard->setEnabled(true);
this->touchbarShowWizard();
}
@ -658,7 +673,6 @@ void MainWindow::setStatusText(const QString &text, bool override, int timeout)
void MainWindow::onSynchronized() {
this->updateNetStats();
this->setStatusText("Synchronized");
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
}
void MainWindow::onBlockchainSync(int height, int target) {
@ -679,34 +693,32 @@ void MainWindow::onConnectionStatusChanged(int status)
// Update connection info in status bar.
QString statusIcon;
QString statusMsg;
QIcon *icon;
switch(status){
case Wallet::ConnectionStatus_Disconnected:
statusIcon = ":/assets/images/status_disconnected.svg";
icon = &m_statusDisconnected;
this->setStatusText("Disconnected");
break;
case Wallet::ConnectionStatus_Connected:
if (m_ctx->currentWallet->synchronized()) {
statusIcon = ":/assets/images/status_connected.svg";
} else {
statusIcon = ":/assets/images/status_waiting.svg";
}
break;
case Wallet::ConnectionStatus_Connecting:
statusIcon = ":/assets/images/status_lagging.svg";
this->setStatusText("Connecting to daemon");
icon = &m_statusConnecting;
this->setStatusText("Connecting to node");
break;
case Wallet::ConnectionStatus_WrongVersion:
statusIcon = ":/assets/images/status_disconnected.svg";
this->setStatusText("Incompatible daemon");
icon = &m_statusDisconnected;
this->setStatusText("Incompatible node");
break;
case Wallet::ConnectionStatus_Synchronizing:
icon = &m_statusSynchronizing;
break;
case Wallet::ConnectionStatus_Synchronized:
icon = &m_statusSynchronized;
break;
default:
statusIcon = ":/assets/images/status_disconnected.svg";
icon = &m_statusDisconnected;
break;
}
m_statusBtnConnectionStatusIndicator->setIcon(QIcon(statusIcon));
m_statusBtnConnectionStatusIndicator->setIcon(*icon);
}
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
@ -751,7 +763,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
break;
}
case QDialog::Accepted:
m_ctx->currentWallet->commitTransactionAsync(tx);
m_ctx->commitTransaction(tx);
break;
}
@ -854,20 +866,13 @@ void MainWindow::showSeedDialog() {
void MainWindow::showConnectionStatusDialog() {
auto status = m_ctx->currentWallet->connectionStatus();
bool synchronized = m_ctx->currentWallet->synchronized();
bool synchronized = m_ctx->currentWallet->isSynchronized();
QString statusMsg;
switch(status){
case Wallet::ConnectionStatus_Disconnected:
statusMsg = "Wallet is disconnected from daemon.";
break;
case Wallet::ConnectionStatus_Connected: {
auto node = m_ctx->nodes->connection();
statusMsg = QString("Wallet is connected to %1").arg(node.full);
if (synchronized)
statusMsg += " and synchronized";
break;
}
case Wallet::ConnectionStatus_Connecting: {
auto node = m_ctx->nodes->connection();
statusMsg = QString("Wallet is connecting to %1").arg(node.full);
@ -876,6 +881,15 @@ void MainWindow::showConnectionStatusDialog() {
case Wallet::ConnectionStatus_WrongVersion:
statusMsg = "Wallet is connected to incompatible daemon.";
break;
case Wallet::ConnectionStatus_Synchronizing: {
auto node = m_ctx->nodes->connection();
statusMsg = QString("Wallet is connected to %1").arg(node.full);
}
case Wallet::ConnectionStatus_Synchronized: {
if (synchronized)
statusMsg += " and synchronized";
break;
}
default:
statusMsg = "Unknown connection status (this should never happen).";
}
@ -961,28 +975,6 @@ void MainWindow::menuWalletCloseClicked() {
m_ctx->closeWallet(true, true);
}
void MainWindow::menuWalletOpenClicked() {
auto walletPath = config()->get(Config::walletPath).toString();
if(walletPath.isEmpty())
walletPath = m_ctx->defaultWalletDir;
QString path = QFileDialog::getOpenFileName(this, "Select your wallet file", walletPath, "Wallet file (*.keys)");
if(path.isEmpty()) return;
QFileInfo infoPath(path);
if(!infoPath.isReadable()) {
QMessageBox::warning(this, "Cannot read wallet file", "Permission error.");
return;
}
if(path == m_ctx->walletPath) {
QMessageBox::warning(this, "Wallet already opened", "Please open another wallet.");
return;
}
m_ctx->closeWallet(false);
m_ctx->onOpenWallet(path, "");
}
void MainWindow::menuAboutClicked() {
m_aboutDialog->show();
}
@ -1318,7 +1310,7 @@ void MainWindow::updateNetStats() {
return;
}
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Connected && m_ctx->currentWallet->synchronized()) {
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Synchronized) {
m_statusLabelNetStats->setText("");
return;
}
@ -1357,6 +1349,16 @@ void MainWindow::bringToFront() {
activateWindow();
}
void MainWindow::centerWidget(QWidget &w) {
QScreen *s = QGuiApplication::primaryScreen();
const QRect sr = s->geometry();
const QRect wr({}, w.frameSize().boundedTo(sr.size()));
w.resize(wr.size());
w.move(sr.center() - wr.center());
}
MainWindow::~MainWindow() {
delete ui;
}

View file

@ -38,6 +38,7 @@
#include "dialog/keysdialog.h"
#include "dialog/aboutdialog.h"
#include "dialog/restoredialog.h"
#include "libwalletqt/Wallet.h"
namespace Ui {
class MainWindow;
@ -117,7 +118,6 @@ public slots:
void onWalletCreatedError(const QString &err);
void onWalletCreated(Wallet *wallet);
void menuWalletCloseClicked();
void menuWalletOpenClicked();
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
void onViewOnBlockExplorer(const QString &txid);
void onResendTransaction(const QString &txid);
@ -173,6 +173,7 @@ private:
void showBalanceDialog();
QString statusDots();
void bringToFront();
void centerWidget(QWidget &w);
WalletWizard *createWizard(WalletWizard::Page startPage);
@ -223,6 +224,11 @@ private:
bool m_statusOverrideActive = false;
QTimer m_txTimer;
QIcon m_statusDisconnected;
QIcon m_statusConnecting;
QIcon m_statusSynchronizing;
QIcon m_statusSynchronized;
private slots:
void menuToggleTabVisible(const QString &key);
};

View file

@ -85,6 +85,8 @@ void HistoryView::setSearchMode(bool mode) {
if (mode) {
header()->showSection(TransactionHistoryModel::TxID);
} else {
header()->hideSection(TransactionHistoryModel::TxID);
}
}
@ -99,6 +101,8 @@ bool HistoryView::setViewState(const QByteArray& state)
header()->setSortIndicator(-1, Qt::AscendingOrder);
bool status = header()->restoreState(state);
m_columnsNeedRelayout = state.isEmpty();
m_showTxidColumn = !header()->isSectionHidden(TransactionHistoryModel::TxID);
return status;
}

View file

@ -39,6 +39,7 @@ private:
TransactionHistoryProxyModel* m_model;
bool m_inSearchMode = false;
bool m_columnsNeedRelayout = true;
bool m_showTxidColumn = false;
QMenu* m_headerMenu;
QActionGroup* m_columnActions;

View file

@ -100,7 +100,7 @@ void SendWidget::fillAddress(const QString &address) {
}
void SendWidget::sendClicked() {
if (m_ctx->currentWallet->connectionStatus() != Wallet::ConnectionStatus_Connected) {
if (!m_ctx->currentWallet->isConnected()) {
QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n"
"Wallet is not connected to a node.\n"
"Go to File -> Settings -> Node to manually connect to a node.");

View file

@ -50,6 +50,9 @@
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>

View file

@ -20,6 +20,9 @@ Settings::Settings(QWidget *parent) :
ui->tabWidget->setTabVisible(4, false);
connect(ui->btnCopyToClipboard, &QPushButton::clicked, this, &Settings::copyToClipboard);
connect(ui->checkBox_multiBroadcast, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::multiBroadcast, toggled);
});
connect(ui->checkBox_externalLink, &QCheckBox::clicked, this, &Settings::checkboxExternalLinkWarn);
connect(ui->checkBox_hideBalance, &QCheckBox::toggled, [this](bool toggled){
config()->set(Config::hideBalance, toggled);
@ -34,6 +37,7 @@ Settings::Settings(QWidget *parent) :
connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
// setup checkboxes
ui->checkBox_multiBroadcast->setChecked(config()->get(Config::multiBroadcast).toBool());
ui->checkBox_externalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool());
ui->checkBox_hideBalance->setChecked(config()->get(Config::hideBalance).toBool());

View file

@ -23,7 +23,9 @@
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -161,20 +163,6 @@
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="checkBox_externalLink">
<property name="text">
<string>Warn before opening external link</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="checkBox_hideBalance">
<property name="text">
<string>Hide balance</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
@ -232,6 +220,29 @@
<widget class="QComboBox" name="comboBox_timeFormat"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBox_multiBroadcast">
<property name="text">
<string>Multibroadcast outgoing transactions</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_externalLink">
<property name="text">
<string>Warn before opening external link</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_hideBalance">
<property name="text">
<string>Hide balance</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_node">
<attribute name="title">

View file

@ -50,7 +50,8 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}},
{Config::amountPrecision, {QS("amountPrecision"), 4}},
{Config::dateFormat, {QS("dateFormat"), "yyyy-MM-dd"}},
{Config::timeFormat, {QS("timeFormat"), "HH:mm"}}
{Config::timeFormat, {QS("timeFormat"), "HH:mm"}},
{Config::multiBroadcast, {QS("multiBroadcast"), true}}
};
@ -110,23 +111,8 @@ Config::Config(QObject* parent)
{
QDir configDir = Config::defaultConfigDir();
QString portablePath = QCoreApplication::applicationDirPath().append("/%1");
if (QFile::exists(portablePath.arg(".portable"))) {
init(portablePath.arg("feather_data/settings.json"));
return;
}
bool isTails = TailsOS::detect();
if (isTails) { // #if defined(PORTABLE)
QString appImagePath = qgetenv("APPIMAGE");
QFileInfo appImageDir(appImagePath);
QDir portablePath(appImageDir.absoluteDir().path() + "/feather_data");
if (portablePath.mkpath(".")) {
configDir = portablePath;
} else {
qCritical() << "Unable to create portable directory: " << portablePath.path();
}
if (!QDir().mkpath(configDir.path())) {
qWarning() << "Unable to create config path: " << configDir.path();
}
QString configPath = configDir.filePath("settings.json");
@ -135,6 +121,26 @@ Config::Config(QObject* parent)
}
QDir Config::defaultConfigDir() {
QString portablePath = QCoreApplication::applicationDirPath().append("/%1");
if (QFile::exists(portablePath.arg(".portable"))) {
return portablePath.arg("feather_data");
}
if (TailsOS::detect()) {
QString path = []{
QString appImagePath = qgetenv("APPIMAGE");
if (appImagePath.isEmpty()) {
qDebug() << "Not an appimage, using currentPath()";
return QDir::currentPath() + "/.feather/.config/feather";
}
QFileInfo appImageDir(appImagePath);
return appImageDir.absoluteDir().path() + "/.feather/.config/feather";
}();
return QDir(path);
}
#if defined(Q_OS_WIN)
return QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
#elif defined(Q_OS_MACOS)

View file

@ -54,7 +54,8 @@ public:
amountPrecision,
portableMode,
dateFormat,
timeFormat
timeFormat,
multiBroadcast
};
~Config() override;

View file

@ -69,7 +69,12 @@ void WalletKeysFilesModel::refresh() {
void WalletKeysFilesModel::updateDirectories() {
this->walletDirectories.clear();
this->walletDirectories << m_ctx->defaultWalletDir; // TODO
QDir defaultWalletDir = QDir(Utils::defaultWalletDir());
QString walletDir = defaultWalletDir.path();
defaultWalletDir.cdUp();
QString walletDirRoot = defaultWalletDir.path();
this->walletDirectories << walletDir;
this->walletDirectories << walletDirRoot;
auto walletPath = config()->get(Config::walletPath).toString();
if(!walletPath.isEmpty() && Utils::fileExists(walletPath)) {
QDir d = QFileInfo(walletPath).absoluteDir();

View file

@ -183,7 +183,7 @@ void Nodes::autoConnect(bool forceReconnect) {
this->connectToNode(node);
return;
}
else if (status == Wallet::ConnectionStatus_Connected && m_connection.isConnecting) {
else if ((status == Wallet::ConnectionStatus_Synchronizing || status == Wallet::ConnectionStatus_Synchronized) && m_connection.isConnecting) {
qInfo() << QString("Node connected to %1").arg(m_connection.address);
// set current connection object
@ -376,6 +376,10 @@ QList<FeatherNode> Nodes::customNodes() {
return m_customNodes;
}
QList<FeatherNode> Nodes::websocketNodes() {
return m_websocketNodes;
}
FeatherNode Nodes::connection() {
return m_connection;
}

View file

@ -69,8 +69,8 @@ struct FeatherNode {
return QString("%1%2").arg(auth).arg(this->address);
}
QString as_url() {
return QString("%1://%2/get_info").arg(this->isHttps ? "https": "http",this->full);
QString as_url() const {
return QString("%1://%2").arg(this->isHttps ? "https": "http",this->full);
}
bool operator == (const FeatherNode &other) const {
@ -88,7 +88,9 @@ public:
NodeSource source();
FeatherNode connection();
QList<FeatherNode> customNodes();
QList<FeatherNode> websocketNodes();
NodeModel *modelWebsocket;
NodeModel *modelCustom;

View file

@ -11,7 +11,7 @@ TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirecto
QObject(parent),
m_genesis_timestamp(genesis_timestamp),
m_configDirectory(configDirectory) {
m_databasePath = QString("%1fiatHistory.db").arg(configDirectory);
m_databasePath = QString("%1/fiatHistory.db").arg(configDirectory);
this->loadDatabase();
}

View file

@ -481,6 +481,26 @@ bool Utils::isTorsocks() {
}
QString Utils::defaultWalletDir() {
QString portablePath = QCoreApplication::applicationDirPath().append("/%1");
if (QFile::exists(portablePath.arg(".portable"))) {
return portablePath.arg("feather_data/wallets");
}
if (TailsOS::detect()) {
QString path = []{
QString appImagePath = qgetenv("APPIMAGE");
if (appImagePath.isEmpty()) {
qDebug() << "Not an appimage, using currentPath()";
return QDir::currentPath() + "/.feather/Monero/wallets";
}
QFileInfo appImageDir(appImagePath);
return appImageDir.absoluteDir().path() + "/.feather/Monero/wallets";
}();
return path;
}
#if defined(Q_OS_LINUX) or defined(Q_OS_MAC)
return QString("%1/Monero/wallets").arg(QDir::homePath());
#elif defined(Q_OS_WIN)

View file

@ -94,7 +94,7 @@ void NodeWidget::onContextConnect() {
void NodeWidget::onContextStatusURL() {
FeatherNode node = this->selectedNode();
if (!node.full.isEmpty())
Utils::externalLinkWarning(this, node.as_url());
Utils::externalLinkWarning(this, QString("%1/get_info").arg(node.as_url()));
}
void NodeWidget::onContextNodeCopy() {

View file

@ -69,8 +69,7 @@ WalletWizard::WalletWizard(AppContext *ctx, WalletWizard::Page startPage, QWidge
});
connect(openWalletPage, &PageOpenWallet::openWallet, [=](const QString &path){
const auto walletPassword = this->field("walletPassword").toString();
emit openWallet(path, walletPassword);
emit openWallet(path, "");
});
}
@ -89,6 +88,12 @@ void WalletWizard::createWallet() {
auto seed = FeatherSeed(m_ctx->restoreHeights[m_ctx->networkType], QString::fromStdString(globals::coinName), m_ctx->seedLanguage, m_wizardFields.seed.split(" "));
if (m_wizardFields.mode == WizardMode::CreateWallet && m_ctx->heights.contains(m_ctx->networkType)) {
int restoreHeight = m_ctx->heights[m_ctx->networkType];
qInfo() << "New wallet, setting restore height to latest blockheight: " << restoreHeight;
seed.setRestoreHeight(restoreHeight);
}
if (m_wizardFields.mode == WizardMode::RestoreFromSeed && m_wizardFields.seedType == SeedType::MONERO)
seed.setRestoreHeight(m_wizardFields.restoreHeight);