From fcf764e03c3758e753b43723a2e36237f251ce71 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Tue, 16 Mar 2021 04:17:01 +0100 Subject: [PATCH 1/2] Multibroadcast transactions --- src/appcontext.cpp | 29 +++ src/appcontext.h | 4 + src/dialog/txconfadvdialog.cpp | 2 +- src/libwalletqt/Wallet.cpp | 9 +- src/mainwindow.cpp | 2 +- src/settings.cpp | 4 + src/settings.ui | 395 +++++++++++++++++---------------- src/utils/config.cpp | 3 +- src/utils/config.h | 3 +- src/utils/nodes.cpp | 4 + src/utils/nodes.h | 6 +- src/widgets/nodewidget.cpp | 2 +- src/wizard/WalletWizard.cpp | 3 +- 13 files changed, 260 insertions(+), 206 deletions(-) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index afa53d0..a8361e6 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -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 to: %1").arg(address); + rpc->setDaemonAddress(address); + rpc->sendRawTransaction(txData); + } + } +} + void AppContext::onWalletOpened(Wallet *wallet) { auto state = wallet->status(); if (state != Wallet::Status_Ok) { diff --git a/src/appcontext.h b/src/appcontext.h index 5e185e4..33ebe16 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -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" @@ -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); diff --git a/src/dialog/txconfadvdialog.cpp b/src/dialog/txconfadvdialog.cpp index cc67e4d..13dfb63 100644 --- a/src/dialog/txconfadvdialog.cpp +++ b/src/dialog/txconfadvdialog.cpp @@ -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(); } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 0140207..c1ed8e9 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1286,11 +1286,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; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a9df680..7e0e03c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -751,7 +751,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto break; } case QDialog::Accepted: - m_ctx->currentWallet->commitTransactionAsync(tx); + m_ctx->commitTransaction(tx); break; } diff --git a/src/settings.cpp b/src/settings.cpp index 295f2f2..cf7d293 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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::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()); diff --git a/src/settings.ui b/src/settings.ui index 46b1311..1892866 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -23,214 +23,225 @@ General - - - + + + + + + + Preferred fiat currency: + + + + + + + + USD + + + + + EUR + + + + + RUB + + + + + GBP + + + + + AUD + + + + + CAD + + + + + CHF + + + + + CNY + + + + + CZK + + + + + JPY + + + + + KRW + + + + + MXN + + + + + NZD + + + + + SEK + + + + + THB + + + + + TRY + + + + + ZAR + + + + + + + + Appearance: + + + + + + + + + + Block explorer: + + + + + + + + exploremonero.com + + + + + xmrchain.net + + + + + moneroblocks.info + + + + + blockchair.com + + + + + + + + Reddit frontend: + + + + + + + + old.reddit.com + + + + + reddit.com + + + + + teddit.net + + + + + + + + Amount precision: + + + + + + + + + + Date format: + + + + + + + + + + Time format: + + + + + + + + + + - Preferred fiat currency: + Multibroadcast outgoing transactions - - - - - USD - - - - - EUR - - - - - RUB - - - - - GBP - - - - - AUD - - - - - CAD - - - - - CHF - - - - - CNY - - - - - CZK - - - - - JPY - - - - - KRW - - - - - MXN - - - - - NZD - - - - - SEK - - - - - THB - - - - - TRY - - - - - ZAR - - - - - - - - Appearance: - - - - - - - - - - Block explorer: - - - - - - - - exploremonero.com - - - - - xmrchain.net - - - - - moneroblocks.info - - - - - blockchair.com - - - - - + Warn before opening external link - + Hide balance - - - - Reddit frontend: - - - - - - - - old.reddit.com - - - - - reddit.com - - - - - teddit.net - - - - - - - - Amount precision: - - - - - - - - - - Date format: - - - - - - - - - - Time format: - - - - - - diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 6bd659e..ce14f97 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -50,7 +50,8 @@ static const QHash 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}} }; diff --git a/src/utils/config.h b/src/utils/config.h index 6398d8f..1b0b11e 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -54,7 +54,8 @@ public: amountPrecision, portableMode, dateFormat, - timeFormat + timeFormat, + multiBroadcast }; ~Config() override; diff --git a/src/utils/nodes.cpp b/src/utils/nodes.cpp index f75c520..ade3921 100644 --- a/src/utils/nodes.cpp +++ b/src/utils/nodes.cpp @@ -376,6 +376,10 @@ QList Nodes::customNodes() { return m_customNodes; } +QList Nodes::websocketNodes() { + return m_websocketNodes; +} + FeatherNode Nodes::connection() { return m_connection; } diff --git a/src/utils/nodes.h b/src/utils/nodes.h index 97c8ccb..cc87b97 100644 --- a/src/utils/nodes.h +++ b/src/utils/nodes.h @@ -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 customNodes(); + QList websocketNodes(); NodeModel *modelWebsocket; NodeModel *modelCustom; diff --git a/src/widgets/nodewidget.cpp b/src/widgets/nodewidget.cpp index 4fe5056..1f18e89 100644 --- a/src/widgets/nodewidget.cpp +++ b/src/widgets/nodewidget.cpp @@ -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() { diff --git a/src/wizard/WalletWizard.cpp b/src/wizard/WalletWizard.cpp index 2f12bcb..d375dad 100644 --- a/src/wizard/WalletWizard.cpp +++ b/src/wizard/WalletWizard.cpp @@ -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, ""); }); } From 401ae0971f81c0aeb43e0fe24dae284659cdb2d7 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 24 Mar 2021 02:37:54 +0100 Subject: [PATCH 2/2] Misc fixes --- CMakeLists.txt | 2 +- Dockerfile | 4 +- Dockerfile.windows | 4 +- monero | 2 +- src/appcontext.cpp | 53 +--- src/appcontext.h | 3 +- src/assets/restore_heights_monero_mainnet.txt | 55 ++++ .../restore_heights_monero_stagenet.txt | 87 ++++++ src/dialog/debuginfodialog.cpp | 8 +- src/historywidget.cpp | 6 +- src/libwalletqt/Wallet.cpp | 168 +++++----- src/libwalletqt/Wallet.h | 290 ++++++++---------- src/libwalletqt/WalletListenerImpl.cpp | 1 + src/libwalletqt/WalletManager.cpp | 23 ++ src/libwalletqt/WalletManager.h | 6 + src/main.cpp | 8 +- src/mainwindow.cpp | 102 +++--- src/mainwindow.h | 8 +- src/model/HistoryView.cpp | 4 + src/model/HistoryView.h | 1 + src/sendwidget.cpp | 2 +- src/sendwidget.ui | 3 + src/utils/config.cpp | 39 ++- src/utils/keysfiles.cpp | 7 +- src/utils/nodes.cpp | 2 +- src/utils/txfiathistory.cpp | 2 +- src/utils/utils.cpp | 20 ++ src/wizard/WalletWizard.cpp | 6 + 28 files changed, 555 insertions(+), 361 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 265599a..f86ee5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Dockerfile b/Dockerfile index 850729d..4c7d61b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ diff --git a/Dockerfile.windows b/Dockerfile.windows index 75b3e0b..e64839a 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -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 \ diff --git a/monero b/monero index 52acbb6..4132797 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit 52acbb68c1a4cc67ee539325a8febc2c144596c4 +Subproject commit 41327974116dedccc2f9709d8ad3a8a1f591faed diff --git a/src/appcontext.cpp b/src/appcontext.cpp index a8361e6..34ef1e0 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -287,7 +287,7 @@ void AppContext::onMultiBroadcast(PendingTransaction *tx) { if (!node.online) continue; QString address = node.as_url(); - qDebug() << QString("Relaying to: %1").arg(address); + qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address); rpc->setDaemonAddress(address); rpc->sendRawTransaction(txData); } @@ -363,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") { @@ -507,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; + } } } } @@ -719,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 @@ -788,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"; diff --git a/src/appcontext.h b/src/appcontext.h index 33ebe16..9500a03 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -54,7 +54,7 @@ public: QString walletPassword = ""; NetworkType::Type networkType; - QMap heights; + QMap heights; QMap restoreHeights; PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low; QString seedLanguage = "English"; // 14 word `monero-seed` only has English @@ -138,7 +138,6 @@ signals: void blockchainSync(int height, int target); void refreshSync(int height, int target); void synchronized(); - void blockHeightWSUpdated(QMap heights); void walletRefreshed(); void walletOpened(); void walletCreatedError(const QString &msg); diff --git a/src/assets/restore_heights_monero_mainnet.txt b/src/assets/restore_heights_monero_mainnet.txt index 60e7353..345106d 100644 --- a/src/assets/restore_heights_monero_mainnet.txt +++ b/src/assets/restore_heights_monero_mainnet.txt @@ -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 \ No newline at end of file diff --git a/src/assets/restore_heights_monero_stagenet.txt b/src/assets/restore_heights_monero_stagenet.txt index c28a4a4..4525fec 100644 --- a/src/assets/restore_heights_monero_stagenet.txt +++ b/src/assets/restore_heights_monero_stagenet.txt @@ -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 \ No newline at end of file diff --git a/src/dialog/debuginfodialog.cpp b/src/dialog/debuginfodialog.cpp index e9b5add..56bc0b1 100644 --- a/src/dialog/debuginfodialog.cpp +++ b/src/dialog/debuginfodialog.cpp @@ -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"; } diff --git a/src/historywidget.cpp b/src/historywidget.cpp index 941601e..9558ffa 100644 --- a/src/historywidget.cpp +++ b/src/historywidget.cpp @@ -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() diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index c1ed8e9..45ed1fe 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -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] { - quint64 daemonHeight; - QPair> daemonHeightFuture = m_scheduler.run([this, &daemonHeight] { - daemonHeight = daemonBlockChainHeight(); - }); - if (!daemonHeightFuture.first) - { - return; - } + // daemonHeight and targetHeight will be 0 if call to get_info fails - quint64 targetHeight; + quint64 daemonHeight; + QPair> daemonHeightFuture = m_scheduler.run([this, &daemonHeight] { + daemonHeight = m_walletImpl->daemonBlockChainHeight();; + }); + if (!daemonHeightFuture.first) + { + 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 = 0; + if (success) { QPair> 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 subaddr_indices; Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count, static_cast(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 &dst_addr, const QVector &amount, PendingTransaction::Priority priority) { + pauseRefresh(); + std::vector dests; for (auto &addr : dst_addr) { dests.push_back(addr.toStdString()); @@ -619,6 +641,8 @@ PendingTransaction* Wallet::createTransactionMultiDest(const QVector &d // TODO: remove mixin count Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast(priority)); PendingTransaction * result = new PendingTransaction(ptImpl); + + startRefresh(); return result; } @@ -638,11 +662,15 @@ void Wallet::createTransactionMultiDestAsync(const QVector &dst_addr, c PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id, quint32 mixin_count, PendingTransaction::Priority priority) { + pauseRefresh(); + std::set subaddr_indices; Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( dst_addr.toStdString(), payment_id.toStdString(), Monero::optional(), mixin_count, static_cast(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(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 = ""; @@ -1313,7 +1332,12 @@ void Wallet::startRefreshThread() if (elapsed >= refreshInterval || m_refreshNow) { m_refreshNow = false; - refresh(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(); } } @@ -1328,9 +1352,7 @@ void Wallet::startRefreshThread() } void Wallet::onRefreshed(bool success) { - if (success) { - setConnectionStatus(ConnectionStatus_Connected); - } else { + if (!success) { setConnectionStatus(ConnectionStatus_Disconnected); } } \ No newline at end of file diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 58fd655..8af1833 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -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& output_indeces, quint64 height, quint64 timestamp, bool miner_tx, bool pool, bool double_spend_seen); + bool importTransaction(const QString& txid, const QVector& 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 &dst_addr, const QVector &amount, + PendingTransaction * createTransactionMultiDest(const QVector &dst_addr, const QVector &amount, PendingTransaction::Priority priority); //! creates async multi-destination transaction - Q_INVOKABLE void createTransactionMultiDestAsync(const QVector &dst_addr, const QVector &amount, + void createTransactionMultiDestAsync(const QVector &dst_addr, const QVector &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 &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 &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 ¬e); - 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 checkSpendProof(const QString &txid, const QString &message, const QString &signature) const; + bool setUserNote(const QString &txid, const QString ¬e); + 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 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 &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 &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,17 +497,13 @@ 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; + mutable bool m_initialized; uint32_t m_currentSubaddressAccount; Subaddress * m_subaddress; mutable SubaddressModel * m_subaddressModel; @@ -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; diff --git a/src/libwalletqt/WalletListenerImpl.cpp b/src/libwalletqt/WalletListenerImpl.cpp index 7acde93..a92f2db 100644 --- a/src/libwalletqt/WalletListenerImpl.cpp +++ b/src/libwalletqt/WalletListenerImpl.cpp @@ -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()); } diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index a6d9536..970589d 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -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(); + }); +} \ No newline at end of file diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 6f5efab..42aa54e 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -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 m_currentWallet; PassphraseReceiver * m_passphraseReceiver; QMutex m_mutex_passphraseReceiver; + QString m_proxyAddress; + mutable QMutex m_proxyMutex; FutureScheduler m_scheduler; }; diff --git a/src/main.cpp b/src/main.cpp index c7fd814..1df7390 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7e0e03c..8ea79f9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #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 &address) { @@ -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; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 09b77a7..f4c6146 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -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); }; diff --git a/src/model/HistoryView.cpp b/src/model/HistoryView.cpp index c3a99bd..ec0f411 100644 --- a/src/model/HistoryView.cpp +++ b/src/model/HistoryView.cpp @@ -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; } diff --git a/src/model/HistoryView.h b/src/model/HistoryView.h index 63ba555..69c200a 100644 --- a/src/model/HistoryView.h +++ b/src/model/HistoryView.h @@ -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; diff --git a/src/sendwidget.cpp b/src/sendwidget.cpp index fc4e04a..7196cab 100644 --- a/src/sendwidget.cpp +++ b/src/sendwidget.cpp @@ -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."); diff --git a/src/sendwidget.ui b/src/sendwidget.ui index 17de2ad..566459d 100644 --- a/src/sendwidget.ui +++ b/src/sendwidget.ui @@ -50,6 +50,9 @@ Qt::ScrollBarAlwaysOff + + true + QPlainTextEdit::NoWrap diff --git a/src/utils/config.cpp b/src/utils/config.cpp index ce14f97..f5cf220 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -111,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"); @@ -136,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) diff --git a/src/utils/keysfiles.cpp b/src/utils/keysfiles.cpp index addd896..4c78d10 100644 --- a/src/utils/keysfiles.cpp +++ b/src/utils/keysfiles.cpp @@ -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(); diff --git a/src/utils/nodes.cpp b/src/utils/nodes.cpp index ade3921..45f8281 100644 --- a/src/utils/nodes.cpp +++ b/src/utils/nodes.cpp @@ -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 diff --git a/src/utils/txfiathistory.cpp b/src/utils/txfiathistory.cpp index 0f902af..6a4eaab 100644 --- a/src/utils/txfiathistory.cpp +++ b/src/utils/txfiathistory.cpp @@ -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(); } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 1da6683..6464d70 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -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) diff --git a/src/wizard/WalletWizard.cpp b/src/wizard/WalletWizard.cpp index d375dad..08f8f2a 100644 --- a/src/wizard/WalletWizard.cpp +++ b/src/wizard/WalletWizard.cpp @@ -88,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);