diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index cb0ab527a4..d086381865 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -84,6 +84,7 @@ QT_MOC_CPP = \ qt/moc_peertablemodel.cpp \ qt/moc_paymentserver.cpp \ qt/moc_qrdialog.cpp \ + qt/moc_qrimagewidget.cpp \ qt/moc_qvalidatedlineedit.cpp \ qt/moc_qvaluecombobox.cpp \ qt/moc_receivecoinsdialog.cpp \ @@ -167,6 +168,7 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/qrdialog.h \ + qt/qrimagewidget.h \ qt/qvalidatedlineedit.h \ qt/qvaluecombobox.h \ qt/receivecoinsdialog.h \ @@ -266,6 +268,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ qt/qrdialog.cpp \ + qt/qrimagewidget.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ qt/recentrequeststablemodel.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 69c549578d..d6be286007 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -152,6 +152,7 @@ public: consensus.DIP0003EnforcementHeight = 1047200; consensus.DIP0003EnforcementHash = uint256S("000000000000002d1734087b4c5afc3133e4e1c3e1a89218f62bcd9bb3d17f81"); consensus.DIP0008Height = 1088640; // 00000000000000112e41e4b3afda8b233b8cc07c532d2eac5de097b68358c43e + consensus.MinBIP9WarningHeight = 1090656; // dip8 activation height + miner confirmation window consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -371,6 +372,7 @@ public: consensus.DIP0003EnforcementHeight = 7300; consensus.DIP0003EnforcementHash = uint256S("00000055ebc0e974ba3a3fb785c5ad4365a39637d4df168169ee80d313612f8f"); consensus.DIP0008Height = 78800; // 000000000e9329d964d80e7dab2e704b43b6bd2b91fea1e9315d38932e55fb55 + consensus.MinBIP9WarningHeight = 80816; // dip8 activation height + miner confirmation window consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -563,6 +565,7 @@ public: consensus.DIP0003EnforcementHeight = 2; // DIP0003 activated immediately on devnet consensus.DIP0003EnforcementHash = uint256(); consensus.DIP0008Height = 2; // DIP0008 activated immediately on devnet + consensus.MinBIP9WarningHeight = 2018; // dip8 activation height + miner confirmation window consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -798,6 +801,7 @@ public: consensus.DIP0003EnforcementHeight = 500; consensus.DIP0003EnforcementHash = uint256(); consensus.DIP0008Height = 432; + consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes diff --git a/src/consensus/params.h b/src/consensus/params.h index 1416938c5e..8d350c9820 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -85,6 +85,9 @@ struct Params { uint256 DIP0003EnforcementHash; /** Block height at which DIP0008 becomes active */ int DIP0008Height; + /** Don't warn about unknown BIP 9 activations below this height. + * This prevents us from warning about the CSV and DIP activations. */ + int MinBIP9WarningHeight; /** * Minimum blocks including miner confirmation of the total of nMinerConfirmationWindow blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. diff --git a/src/init.cpp b/src/init.cpp index 06a1459e46..c9bc43907d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1469,7 +1469,7 @@ bool AppInitParameterInteraction() if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) { return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", ""))); } - // High fee check is done afterward in WalletParameterInteraction() + // High fee check is done afterward in CWallet::CreateWalletFromFile() ::minRelayTxFee = CFeeRate(n); } else if (incrementalRelayFee > ::minRelayTxFee) { // Allow only setting incrementalRelayFee to control both diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index 58fe832452..eb0578afe6 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -117,7 +117,7 @@ public: ensurePolished(); const QFontMetrics fm(fontMetrics()); int h = 0; - int w = fm.width(BitcoinUnits::format(BitcoinUnits::DASH, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); + int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnits::DASH, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways)); w += 2; // cursor blinking space w += GUIUtil::dashThemeActive() ? 24 : 0; // counteract padding from css return QSize(w, h); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 14c7c86c9c..fba897e92b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -46,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -89,7 +89,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } #ifdef ENABLE_WALLET @@ -489,6 +489,8 @@ void BitcoinGUI::createActions() for (const std::pair& i : m_wallet_controller->listWalletDir()) { const std::string& path = i.first; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + // Menu items remove single &. Single & are shown when && is in the string, but only the first occurrence. So replace only the first & with && + name.replace(name.indexOf(QChar('&')), 1, QString("&&")); QAction* action = m_open_wallet_menu->addAction(name); if (i.second) { @@ -1324,7 +1326,7 @@ void BitcoinGUI::updateWidth() continue; } QFontMetrics fm(button->font()); - nWidthWidestButton = std::max(nWidthWidestButton, fm.width(button->text())); + nWidthWidestButton = std::max(nWidthWidestButton, GUIUtil::TextWidth(fm, button->text())); ++nButtonsVisible; } // Add 30 per button as padding and use minimum 980 which is the minimum required to show all tab's contents @@ -1990,7 +1992,7 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl() : const QFontMetrics fm(GUIUtil::getFontNormal()); for (const BitcoinUnits::Unit unit : units) { - max_width = qMax(max_width, fm.width(BitcoinUnits::name(unit))); + max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::name(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); diff --git a/src/qt/forms/receiverequestdialog.ui b/src/qt/forms/receiverequestdialog.ui index dbe966b241..9f896ee3b1 100644 --- a/src/qt/forms/receiverequestdialog.ui +++ b/src/qt/forms/receiverequestdialog.ui @@ -127,7 +127,7 @@ QRImageWidget QLabel -
qt/receiverequestdialog.h
+
qt/qrimagewidget.h
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index b2ca24ae8d..03f314937b 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -32,12 +32,6 @@ static const bool DEFAULT_SPLASHSCREEN = true; */ static const int TOOLTIP_WRAP_THRESHOLD = 80; -/* Maximum allowed URI length */ -static const int MAX_URI_LENGTH = 255; - -/* QRCodeDialog -- size of exported QR Code image */ -#define QR_IMAGE_SIZE 300 - /* Number of frames in spinner animation */ #define SPINNER_FRAMES 90 diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 0aa06dd1ab..8597073c13 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -51,7 +51,6 @@ #include #include #include -#include #include #include #include @@ -1832,7 +1831,7 @@ qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal m while(font_size >= minPointSize) { font.setPointSizeF(font_size); QFontMetrics fm(font); - if (fm.width(text) < width) { + if (TextWidth(fm, text) < width) { break; } font_size -= 0.5; @@ -1864,7 +1863,7 @@ void PolishProgressDialog(QProgressDialog* dialog) { #ifdef Q_OS_MAC // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. - const int margin = dialog->fontMetrics().width("X"); + const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); dialog->show(); #else @@ -1872,4 +1871,13 @@ void PolishProgressDialog(QProgressDialog* dialog) #endif } +int TextWidth(const QFontMetrics& fm, const QString& text) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + return fm.horizontalAdvance(text); +#else + return fm.width(text); +#endif +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index dd6b4a9d89..425fe7791d 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -467,6 +467,14 @@ namespace GUIUtil // Fix known bugs in QProgressDialog class. void PolishProgressDialog(QProgressDialog* dialog); + + /** + * Returns the distance in pixels appropriate for drawing a subsequent character after text. + * + * In Qt 5.12 and before the QFontMetrics::width() is used and it is deprecated since Qt 13.0. + * In Qt 5.11 the QFontMetrics::horizontalAdvance() was introduced. + */ + int TextWidth(const QFontMetrics& fm, const QString& text); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 6f8f4c1a79..ad8cca2fd5 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -497,7 +497,7 @@ void OptionsDialog::updateWidth() continue; } QFontMetrics fm(button->font()); - nWidthWidestButton = std::max(nWidthWidestButton, fm.width(button->text())); + nWidthWidestButton = std::max(nWidthWidestButton, GUIUtil::TextWidth(fm, button->text())); ++nButtonsVisible; } // Add 10 per button as padding and use minimum 585 which is what we used in css before diff --git a/src/qt/qrdialog.cpp b/src/qt/qrdialog.cpp index d70ca30c68..6fc8b45d87 100644 --- a/src/qt/qrdialog.cpp +++ b/src/qt/qrdialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp new file mode 100644 index 0000000000..2ad72c0256 --- /dev/null +++ b/src/qt/qrimagewidget.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_CONFIG_H) +#include /* for USE_QRCODE */ +#endif + +#ifdef USE_QRCODE +#include +#endif + +QRImageWidget::QRImageWidget(QWidget *parent): + QLabel(parent), contextMenu(nullptr) +{ + contextMenu = new QMenu(this); + QAction *saveImageAction = new QAction(tr("&Save Image..."), this); + connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); + contextMenu->addAction(saveImageAction); + QAction *copyImageAction = new QAction(tr("&Copy Image"), this); + connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); + contextMenu->addAction(copyImageAction); +} + +bool QRImageWidget::setQR(const QString& data, const QString& text) +{ +#ifdef USE_QRCODE + setText(""); + if (data.isEmpty()) return false; + + // limit length + if (data.length() > MAX_URI_LENGTH) { + setText(tr("Resulting URI too long, try to reduce the text for label / message.")); + return false; + } + + QRcode *code = QRcode_encodeString(data.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + + if (!code) { + setText(tr("Error encoding URI into QR Code.")); + return false; + } + + QImage qrImage = QImage(code->width + 6, code->width + 6, QImage::Format_RGB32); + qrImage.fill(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET)); + unsigned char *p = code->data; + for (int y = 0; y < code->width; y++) + { + for (int x = 0; x < code->width; x++) + { + qrImage.setPixel(x + 3, y + 3, ((*p & 1) ? GUIUtil::getThemedQColor(GUIUtil::ThemedColor::QR_PIXEL).rgb() : GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET).rgb())); + p++; + } + } + QRcode_free(code); + + // Create the image with respect to the device pixel ratio + int qrAddrImageWidth = QR_IMAGE_SIZE; + int qrAddrImageHeight = QR_IMAGE_SIZE + 20; + qreal scale = qApp->devicePixelRatio(); + QImage qrAddrImage = QImage(qrAddrImageWidth * scale, qrAddrImageHeight * scale, QImage::Format_RGB32); + qrAddrImage.setDevicePixelRatio(scale); + QPainter painter(&qrAddrImage); + + // Fill the whole image with border color + qrAddrImage.fill(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BORDER_WIDGET)); + + // Create a 2px/2px smaller rect and fill it with background color to keep the 1px border with the border color + QRect paddedRect = QRect(1, 1, qrAddrImageWidth - 2, qrAddrImageHeight - 2); + painter.fillRect(paddedRect, GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET)); + painter.drawImage(2, 2, qrImage.scaled(QR_IMAGE_SIZE - 4, QR_IMAGE_SIZE - 4)); + + // calculate ideal font size + QFont font = GUIUtil::getFontNormal(); + qreal font_size = GUIUtil::calculateIdealFontSize((paddedRect.width() - 20), text, font); + font.setPointSizeF(font_size); + + // paint the address + painter.setFont(font); + painter.setPen(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::QR_PIXEL)); + paddedRect.setHeight(QR_IMAGE_SIZE + 3); + painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, text); + painter.end(); + + setPixmap(QPixmap::fromImage(qrAddrImage)); + + return true; +#else + setText(tr("QR code support not available.")); + return false; +#endif +} + +QImage QRImageWidget::exportImage() +{ + if(!pixmap()) + return QImage(); + return pixmap()->toImage(); +} + +void QRImageWidget::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton && pixmap()) + { + event->accept(); + QMimeData *mimeData = new QMimeData; + mimeData->setImageData(exportImage()); + + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->exec(); + } else { + QLabel::mousePressEvent(event); + } +} + +void QRImageWidget::saveImage() +{ + if(!pixmap()) + return; + QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); + if (!fn.isEmpty()) + { + exportImage().save(fn); + } +} + +void QRImageWidget::copyImage() +{ + if(!pixmap()) + return; + QApplication::clipboard()->setImage(exportImage()); +} + +void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) +{ + if(!pixmap()) + return; + contextMenu->exec(event->globalPos()); +} diff --git a/src/qt/qrimagewidget.h b/src/qt/qrimagewidget.h new file mode 100644 index 0000000000..1a5c904707 --- /dev/null +++ b/src/qt/qrimagewidget.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_QRIMAGEWIDGET_H +#define BITCOIN_QT_QRIMAGEWIDGET_H + +#include +#include + +/* Maximum allowed URI length */ +static const int MAX_URI_LENGTH = 255; + +/* Size of exported QR Code image */ +static const int QR_IMAGE_SIZE = 300; + +QT_BEGIN_NAMESPACE +class QMenu; +QT_END_NAMESPACE + +/* Label widget for QR code. This image can be dragged, dropped, copied and saved + * to disk. + */ +class QRImageWidget : public QLabel +{ + Q_OBJECT + +public: + explicit QRImageWidget(QWidget *parent = nullptr); + bool setQR(const QString& data, const QString& text = ""); + QImage exportImage(); + +public Q_SLOTS: + void saveImage(); + void copyImage(); + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void contextMenuEvent(QContextMenuEvent *event) override; + +private: + QMenu *contextMenu; +}; + +#endif // BITCOIN_QT_QRIMAGEWIDGET_H diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index a516380e41..46ef6689ec 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -6,85 +6,17 @@ #include #include -#include #include #include +#include #include -#include -#include -#include -#include #include #if defined(HAVE_CONFIG_H) #include /* for USE_QRCODE */ #endif -#ifdef USE_QRCODE -#include -#endif - -QRImageWidget::QRImageWidget(QWidget *parent): - QLabel(parent), contextMenu(nullptr) -{ - contextMenu = new QMenu(this); - QAction *saveImageAction = new QAction(tr("&Save Image..."), this); - connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); - contextMenu->addAction(saveImageAction); - QAction *copyImageAction = new QAction(tr("&Copy Image"), this); - connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); - contextMenu->addAction(copyImageAction); -} - -QImage QRImageWidget::exportImage() -{ - if(!pixmap()) - return QImage(); - return pixmap()->toImage(); -} - -void QRImageWidget::mousePressEvent(QMouseEvent *event) -{ - if(event->button() == Qt::LeftButton && pixmap()) - { - event->accept(); - QMimeData *mimeData = new QMimeData; - mimeData->setImageData(exportImage()); - - QDrag *drag = new QDrag(this); - drag->setMimeData(mimeData); - drag->exec(); - } else { - QLabel::mousePressEvent(event); - } -} - -void QRImageWidget::saveImage() -{ - if(!pixmap()) - return; - QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); - if (!fn.isEmpty()) - { - exportImage().save(fn); - } -} - -void QRImageWidget::copyImage() -{ - if(!pixmap()) - return; - QApplication::clipboard()->setImage(exportImage()); -} - -void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) -{ - if(!pixmap()) - return; - contextMenu->exec(event->globalPos()); -} - ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), @@ -153,62 +85,9 @@ void ReceiveRequestDialog::update() } ui->outUri->setText(html); -#ifdef USE_QRCODE - ui->lblQRCode->setText(""); - if(!uri.isEmpty()) - { - // limit URI length - if (uri.length() > MAX_URI_LENGTH) - { - ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce the text for label / message.")); - } else { - QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); - if (!code) - { - ui->lblQRCode->setText(tr("Error encoding URI into QR Code.")); - return; - } - QImage qrImage = QImage(code->width + 6, code->width + 6, QImage::Format_RGB32); - qrImage.fill(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET)); - unsigned char *p = code->data; - for (int y = 0; y < code->width; y++) - { - for (int x = 0; x < code->width; x++) - { - qrImage.setPixel(x + 3, y + 3, ((*p & 1) ? GUIUtil::getThemedQColor(GUIUtil::ThemedColor::QR_PIXEL).rgb() : GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET).rgb())); - p++; - } - } - QRcode_free(code); - // Create the image with respect to the device pixel ratio - int qrAddrImageWidth = QR_IMAGE_SIZE; - int qrAddrImageHeight = QR_IMAGE_SIZE + 20; - qreal scale = qApp->devicePixelRatio(); - QImage qrAddrImage = QImage(qrAddrImageWidth * scale, qrAddrImageHeight * scale, QImage::Format_RGB32); - qrAddrImage.setDevicePixelRatio(scale); - QPainter painter(&qrAddrImage); - // Fill the whole image with border color - qrAddrImage.fill(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BORDER_WIDGET)); - // Create a 2px/2px smaller rect and fill it with background color to keep the 1px border with the border color - QRect paddedRect = QRect(1, 1, qrAddrImageWidth - 2, qrAddrImageHeight - 2); - painter.fillRect(paddedRect, GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET)); - painter.drawImage(2, 2, qrImage.scaled(QR_IMAGE_SIZE - 4, QR_IMAGE_SIZE - 4)); - // calculate ideal font size - QFont font = GUIUtil::getFontNormal(); - qreal font_size = GUIUtil::calculateIdealFontSize((paddedRect.width() - 20), info.address, font); - font.setPointSizeF(font_size); - // paint the address - painter.setFont(font); - painter.setPen(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::QR_PIXEL)); - paddedRect.setHeight(QR_IMAGE_SIZE + 3); - painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address); - painter.end(); - - ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); - ui->btnSaveAs->setEnabled(true); - } + if (ui->lblQRCode->setQR(uri, info.address)) { + ui->btnSaveAs->setEnabled(true); } -#endif } void ReceiveRequestDialog::on_btnCopyURI_clicked() diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index 45bbeafbea..c7195d7596 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -8,41 +8,11 @@ #include #include -#include -#include -#include namespace Ui { class ReceiveRequestDialog; } -QT_BEGIN_NAMESPACE -class QMenu; -QT_END_NAMESPACE - -/* Label widget for QR code. This image can be dragged, dropped, copied and saved - * to disk. - */ -class QRImageWidget : public QLabel -{ - Q_OBJECT - -public: - explicit QRImageWidget(QWidget *parent = nullptr); - QImage exportImage(); - -public Q_SLOTS: - void saveImage(); - void copyImage(); - -protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void contextMenuEvent(QContextMenuEvent *event) override; - -private: - QMenu *contextMenu; -}; - class ReceiveRequestDialog : public QDialog { Q_OBJECT diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 63f87b743f..54195c2ee1 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -31,12 +31,12 @@ #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -466,7 +466,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags QSettings settings; if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) { // Restore failed (perhaps missing setting), center the window - move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); + move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center()); } QChar nonbreaking_hyphen(8209); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 64610f88d4..2e1a479d5a 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -9,22 +9,22 @@ #include -#include -#include #include #include #include #include #include +#include +#include #include #include #include #include #include -#include #include +#include SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) : @@ -89,7 +89,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw fontBold.setPointSize(50 * fontFactor); pixPaint.setFont(fontBold); QFontMetrics fm = pixPaint.fontMetrics(); - int titleTextWidth = fm.width(titleText); + int titleTextWidth = GUIUtil::TextWidth(fm, titleText); if (titleTextWidth > width * 0.8) { fontFactor = 0.75; } @@ -97,14 +97,14 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw fontBold.setPointSize(50 * fontFactor); pixPaint.setFont(fontBold); fm = pixPaint.fontMetrics(); - titleTextWidth = fm.width(titleText); + titleTextWidth = GUIUtil::TextWidth(fm, titleText); int titleTextHeight = fm.height(); pixPaint.drawText((width / 2) - (titleTextWidth / 2), titleTextHeight + paddingTop, titleText); fontNormal.setPointSize(16 * fontFactor); pixPaint.setFont(fontNormal); fm = pixPaint.fontMetrics(); - int versionTextWidth = fm.width(versionText); + int versionTextWidth = GUIUtil::TextWidth(fm, versionText); pixPaint.drawText((width / 2) - (versionTextWidth / 2), titleTextHeight + paddingTop + titleVersionVSpace, versionText); // draw additional text if special network @@ -112,7 +112,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw fontBold.setPointSize(10 * fontFactor); pixPaint.setFont(fontBold); fm = pixPaint.fontMetrics(); - int titleAddTextWidth = fm.width(titleAddText); + int titleAddTextWidth = GUIUtil::TextWidth(fm, titleAddText); // Draw the badge background with the network-specific color QRect badgeRect = QRect(width - titleAddTextWidth - 20, 5, width, fm.height() + 10); QColor badgeColor = networkStyle->getBadgeColor(); @@ -128,7 +128,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw QRect r(QPoint(), QSize(width, height)); resize(r.size()); setFixedSize(r.size()); - move(QApplication::desktop()->screenGeometry().center() - r.center()); + move(QGuiApplication::primaryScreen()->geometry().center() - r.center()); subscribeToCoreSignals(); installEventFilter(this); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 1cd10add04..d82bfe1651 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include @@ -70,7 +71,7 @@ void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) { QMessageBox box(parent); box.setWindowTitle(tr("Close wallet")); - box.setText(tr("Are you sure you wish to close wallet %1?").arg(wallet_model->getDisplayName())); + box.setText(tr("Are you sure you wish to close wallet %1?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName()))); box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.")); box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); box.setDefaultButton(QMessageBox::Yes); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 2ae9074689..f096403930 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -229,9 +229,9 @@ void WalletView::processNewTransaction(const QModelIndex& parent, int start, int qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong(); QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString(); QString address = ttm->data(index, TransactionTableModel::AddressRole).toString(); - QString label = ttm->data(index, TransactionTableModel::LabelRole).toString(); + QString label = GUIUtil::HtmlEscape(ttm->data(index, TransactionTableModel::LabelRole).toString()); - Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, walletModel->getWalletName()); + Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName())); } void WalletView::gotoGovernancePage() diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 7427f37390..96f85b6422 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -40,32 +40,46 @@ bool EqualDescriptor(std::string a, std::string b) return a == b; } -std::string MaybeUseHInsteadOfApostrophy(std::string ret) +std::string UseHInsteadOfApostrophe(const std::string& desc) { - if (InsecureRandBool()) { - while (true) { - auto it = ret.find("'"); - if (it != std::string::npos) { - ret[it] = 'h'; - if (ret.size() > 9 && ret[ret.size() - 9] == '#') ret = ret.substr(0, ret.size() - 9); // Changing apostrophe to h breaks the checksum - } else { - break; - } - } + std::string ret = desc; + while (true) { + auto it = ret.find('\''); + if (it == std::string::npos) break; + ret[it] = 'h'; + } + + // GetDescriptorChecksum returns "" if the checksum exists but is bad. + // Switching apostrophes with 'h' breaks the checksum if it exists - recalculate it and replace the broken one. + if (GetDescriptorChecksum(ret) == "") { + ret = ret.substr(0, desc.size() - 9); + ret += std::string("#") + GetDescriptorChecksum(ret); } return ret; } const std::set> ONLY_EMPTY{{}}; -void Check(const std::string& prv, const std::string& pub, int flags, const std::vector>& scripts, const std::set>& paths = ONLY_EMPTY) +void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector>& scripts, const std::set>& paths = ONLY_EMPTY, + bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false) { FlatSigningProvider keys_priv, keys_pub; std::set> left_paths = paths; + std::unique_ptr parse_priv; + std::unique_ptr parse_pub; // Check that parsing succeeds. - std::unique_ptr parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv); - std::unique_ptr parse_pub = Parse(MaybeUseHInsteadOfApostrophy(pub), keys_pub); + if (replace_apostrophe_with_h_in_prv) { + parse_priv = Parse(UseHInsteadOfApostrophe(prv), keys_priv); + } else { + parse_priv = Parse(prv, keys_priv); + } + if (replace_apostrophe_with_h_in_pub) { + parse_pub = Parse(UseHInsteadOfApostrophe(pub), keys_pub); + } else { + parse_pub = Parse(pub, keys_pub); + } + BOOST_CHECK(parse_priv); BOOST_CHECK(parse_pub); @@ -164,6 +178,32 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } +void Check(const std::string& prv, const std::string& pub, int flags, const std::vector>& scripts, const std::set>& paths = ONLY_EMPTY) +{ + bool found_apostrophes_in_prv = false; + bool found_apostrophes_in_pub = false; + + // Do not replace apostrophes with 'h' in prv and pub + DoCheck(prv, pub, flags, scripts, paths); + + // Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv + if (prv.find('\'') != std::string::npos) { + found_apostrophes_in_prv = true; + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); + } + + // Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub + if (pub.find('\'') != std::string::npos) { + found_apostrophes_in_pub = true; + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); + } + + // Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both + if (found_apostrophes_in_prv && found_apostrophes_in_pub) { + DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); + } +} + } BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup) diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index c03400cb8f..bc6670f4cb 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -249,4 +249,82 @@ BOOST_AUTO_TEST_CASE(merkle_test) } } + +BOOST_AUTO_TEST_CASE(merkle_test_empty_block) +{ + bool mutated = false; + CBlock block; + uint256 root = BlockMerkleRoot(block, &mutated); + + BOOST_CHECK_EQUAL(root.IsNull(), true); + BOOST_CHECK_EQUAL(mutated, false); +} + +BOOST_AUTO_TEST_CASE(merkle_test_oneTx_block) +{ + bool mutated = false; + CBlock block; + + block.vtx.resize(1); + CMutableTransaction mtx; + mtx.nLockTime = 0; + block.vtx[0] = MakeTransactionRef(std::move(mtx)); + uint256 root = BlockMerkleRoot(block, &mutated); + BOOST_CHECK_EQUAL(root, block.vtx[0]->GetHash()); + BOOST_CHECK_EQUAL(mutated, false); +} + +BOOST_AUTO_TEST_CASE(merkle_test_OddTxWithRepeatedLastTx_block) +{ + bool mutated; + CBlock block, blockWithRepeatedLastTx; + + block.vtx.resize(3); + + for (std::size_t pos = 0; pos < block.vtx.size(); pos++) { + CMutableTransaction mtx; + mtx.nLockTime = pos; + block.vtx[pos] = MakeTransactionRef(std::move(mtx)); + } + + blockWithRepeatedLastTx = block; + blockWithRepeatedLastTx.vtx.push_back(blockWithRepeatedLastTx.vtx.back()); + + uint256 rootofBlock = BlockMerkleRoot(block, &mutated); + BOOST_CHECK_EQUAL(mutated, false); + + uint256 rootofBlockWithRepeatedLastTx = BlockMerkleRoot(blockWithRepeatedLastTx, &mutated); + BOOST_CHECK_EQUAL(rootofBlock, rootofBlockWithRepeatedLastTx); + BOOST_CHECK_EQUAL(mutated, true); +} + +BOOST_AUTO_TEST_CASE(merkle_test_LeftSubtreeRightSubtree) +{ + CBlock block, leftSubtreeBlock, rightSubtreeBlock; + + block.vtx.resize(4); + std::size_t pos; + for (pos = 0; pos < block.vtx.size(); pos++) { + CMutableTransaction mtx; + mtx.nLockTime = pos; + block.vtx[pos] = MakeTransactionRef(std::move(mtx)); + } + + for (pos = 0; pos < block.vtx.size() / 2; pos++) + leftSubtreeBlock.vtx.push_back(block.vtx[pos]); + + for (pos = block.vtx.size() / 2; pos < block.vtx.size(); pos++) + rightSubtreeBlock.vtx.push_back(block.vtx[pos]); + + uint256 root = BlockMerkleRoot(block); + uint256 rootOfLeftSubtree = BlockMerkleRoot(leftSubtreeBlock); + uint256 rootOfRightSubtree = BlockMerkleRoot(rightSubtreeBlock); + std::vector leftRight; + leftRight.push_back(rootOfLeftSubtree); + leftRight.push_back(rootOfRightSubtree); + uint256 rootOfLR = ComputeMerkleRoot(leftRight); + + BOOST_CHECK_EQUAL(root, rootOfLR); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/threadnames.cpp b/src/util/threadnames.cpp index 11f68e2c19..1baee81905 100644 --- a/src/util/threadnames.cpp +++ b/src/util/threadnames.cpp @@ -57,7 +57,7 @@ static void SetInternalName(std::string name) { } void util::ThreadRename(std::string&& name) { - SetThreadName(("dash-" + name).c_str()); + SetThreadName(("d-" + name).c_str()); SetInternalName(std::move(name)); } diff --git a/src/validation.cpp b/src/validation.cpp index 1d5f35dcae..204936b824 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1895,7 +1895,8 @@ public: bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { - return ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && + return pindex->nHeight >= params.MinBIP9WarningHeight && + ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && ((pindex->nVersion >> bit) & 1) != 0 && ((ComputeBlockVersion(pindex->pprev, params) >> bit) & 1) == 0; } diff --git a/src/wallet/load.h b/src/wallet/load.h index 9bb6f2166e..7420d81b16 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -16,8 +16,8 @@ class Chain; } // namespace interfaces //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. -//! This function will perform salvage on the wallet if requested, as long as only one wallet is -//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (WalletInit::ParameterInteraction() forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). bool VerifyWallets(interfaces::Chain& chain, const std::vector& wallet_files); //! Load wallet databases. diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 30f1f38031..7d1851c62f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4885,7 +4885,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b std::shared_ptr CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { - const std::string& walletFile = WalletDataFilePath(location.GetPath()).string(); + const std::string walletFile = WalletDataFilePath(location.GetPath()).string(); // needed to restore wallet transaction meta data after -zapwallettxes std::vector vWtx; diff --git a/test/functional/data/wallets/high_minversion/.walletlock b/test/functional/data/wallets/high_minversion/.walletlock new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional/data/wallets/high_minversion/db.log b/test/functional/data/wallets/high_minversion/db.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional/data/wallets/high_minversion/wallet.dat b/test/functional/data/wallets/high_minversion/wallet.dat new file mode 100644 index 0000000000..99ab809263 Binary files /dev/null and b/test/functional/data/wallets/high_minversion/wallet.dat differ diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 6075ef0867..97050708c0 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -18,7 +18,29 @@ from test_framework.util import ( wait_until, ) from test_framework.mininode import P2PInterface -from test_framework.messages import CAddress, msg_addr, NODE_NETWORK +from test_framework.messages import ( + CAddress, + msg_addr, + NODE_NETWORK, + NODE_GETUTXO,NODE_BLOOM, + NODE_NETWORK_LIMITED, +) + +def assert_net_servicesnames(servicesflag, servicenames): + """Utility that checks if all flags are correctly decoded in + `getpeerinfo` and `getnetworkinfo`. + + :param servicesflag: The services as an integer. + :param servicesnames: The list of decoded services names, as strings. + """ + if servicesflag & NODE_NETWORK: + assert "NETWORK" in servicenames + if servicesflag & NODE_GETUTXO: + assert "GETUTXO" in servicenames + if servicesflag & NODE_BLOOM: + assert "BLOOM" in servicenames + if servicesflag & NODE_NETWORK_LIMITED: + assert "NETWORK_LIMITED" in servicenames class NetTest(BitcoinTestFramework): def set_test_params(self): @@ -38,7 +60,7 @@ class NetTest(BitcoinTestFramework): self._test_connection_count() self._test_getnettotals() - self._test_getnetworkinginfo() + self._test_getnetworkinfo() self._test_getaddednodeinfo() self._test_getpeerinfo() self._test_getnodeaddresses() @@ -77,7 +99,7 @@ class NetTest(BitcoinTestFramework): assert_greater_than_or_equal(after['bytesrecv_per_msg'].get('pong', 0), before['bytesrecv_per_msg'].get('pong', 0) + 32) assert_greater_than_or_equal(after['bytessent_per_msg'].get('ping', 0), before['bytessent_per_msg'].get('ping', 0) + 32) - def _test_getnetworkinginfo(self): + def _test_getnetworkinfo(self): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) @@ -92,6 +114,11 @@ class NetTest(BitcoinTestFramework): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) + # check the `servicesnames` field + network_info = [node.getnetworkinfo() for node in self.nodes] + for info in network_info: + assert_net_servicesnames(int(info["localservices"]), info["localservicesnames"]) + def _test_getaddednodeinfo(self): assert_equal(self.nodes[0].getaddednodeinfo(), []) # add a node (node2) to node0 @@ -110,6 +137,9 @@ class NetTest(BitcoinTestFramework): # the address bound to on one side will be the source address for the other node assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr']) assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) + # check the `servicesnames` field + for info in peer_info: + assert_net_servicesnames(int(info[0]["services"]), info[0]["servicesnames"]) def _test_getnodeaddresses(self): self.nodes[0].add_p2p_connection(P2PInterface()) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 69717604cc..4876eb3fda 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -44,7 +44,7 @@ COIN = 100000000 # 1 btc in satoshis BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out NODE_NETWORK = (1 << 0) -# NODE_GETUTXO = (1 << 1) +NODE_GETUTXO = (1 << 1) NODE_BLOOM = (1 << 2) NODE_COMPACT_FILTERS = (1 << 6) NODE_NETWORK_LIMITED = (1 << 10) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index d9ad02ab15..6a01d88b9f 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -68,7 +68,9 @@ def assert_raises_message(exc, message, fun, *args, **kwds): raise AssertionError("Use assert_raises_rpc_error() to test RPC failures") except exc as e: if message is not None and message not in e.error['message']: - raise AssertionError("Expected substring not found:" + e.error['message']) + raise AssertionError( + "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( + message, e.error['message'])) except Exception as e: raise AssertionError("Unexpected exception raised: " + type(e).__name__) else: @@ -128,7 +130,9 @@ def try_rpc(code, message, fun, *args, **kwds): if (code is not None) and (code != e.error["code"]): raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"]) if (message is not None) and (message not in e.error['message']): - raise AssertionError("Expected substring not found:" + e.error['message']) + raise AssertionError( + "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( + message, e.error['message'])) return True except Exception as e: raise AssertionError("Unexpected exception raised: " + type(e).__name__) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 5db3b0b660..7dde96dbca 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -17,6 +17,8 @@ from test_framework.util import ( assert_raises_rpc_error, ) +FEATURE_LATEST = 120200 + class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): @@ -27,6 +29,13 @@ class MultiWalletTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def add_options(self, parser): + parser.add_argument( + '--data_wallets_dir', + default=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/wallets/'), + help='Test data with wallet directories (default: %(default)s)', + ) + def run_test(self): node = self.nodes[0] @@ -328,6 +337,22 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) + # Fail to load if wallet is downgraded + shutil.copytree(os.path.join(self.options.data_wallets_dir, 'high_minversion'), wallet_dir('high_minversion')) + self.restart_node(0, extra_args=['-upgradewallet={}'.format(FEATURE_LATEST)]) + assert {'name': 'high_minversion'} in self.nodes[0].listwalletdir()['wallets'] + self.log.info("Fail -upgradewallet that results in downgrade") + assert_raises_rpc_error( + -4, + "Wallet loading failed.", + lambda: self.nodes[0].loadwallet(filename='high_minversion'), + ) + self.stop_node( + i=0, + expected_stderr='Error: Error loading {}: Wallet requires newer version of Dash Core'.format( + wallet_dir('high_minversion', 'wallet.dat')), + ) + if __name__ == '__main__': MultiWalletTest().main()