Merge g562 via wallet_warn_reuse_gui

This commit is contained in:
Luke Dashjr 2024-06-21 19:28:12 +00:00
commit 00cd83d47c
14 changed files with 309 additions and 24 deletions

View File

@ -129,6 +129,9 @@ public:
//! Display address on external signer //! Display address on external signer
virtual bool displayAddress(const CTxDestination& dest) = 0; virtual bool displayAddress(const CTxDestination& dest) = 0;
virtual bool checkAddressForUsage(const std::vector<std::string>& addresses) const = 0;
virtual bool findAddressUsage(const std::vector<std::string>& addresses, std::function<void(const std::string&, const WalletTx&, uint32_t)> callback) const = 0;
//! Lock coin. //! Lock coin.
virtual bool lockCoin(const COutPoint& output, const bool write_to_db) = 0; virtual bool lockCoin(const COutPoint& output, const bool write_to_db) = 0;

View File

@ -26,6 +26,8 @@ static const bool DEFAULT_SPLASHSCREEN = true;
/* Invalid field background style */ /* Invalid field background style */
#define STYLE_INVALID "border: 3px solid #FF8080" #define STYLE_INVALID "border: 3px solid #FF8080"
/* "Warning" field background style */
#define STYLE_INCORRECT "border: 3px solid #FFFF80"
/* Transaction list -- unconfirmed transaction */ /* Transaction list -- unconfirmed transaction */
#define COLOR_UNCONFIRMED QColor(128, 128, 128) #define COLOR_UNCONFIRMED QColor(128, 128, 128)

View File

@ -91,9 +91,19 @@ using namespace std::chrono_literals;
namespace GUIUtil { namespace GUIUtil {
QString dateStr(const QDate &date)
{
return QLocale::system().toString(date, QLocale::ShortFormat);
}
QString dateStr(qint64 nTime)
{
return dateStr(QDateTime::fromSecsSinceEpoch(nTime).date());
}
QString dateTimeStr(const QDateTime &date) QString dateTimeStr(const QDateTime &date)
{ {
return QLocale::system().toString(date.date(), QLocale::ShortFormat) + QString(" ") + date.toString("hh:mm"); return dateStr(date.date()) + QString(" ") + date.toString("hh:mm");
} }
QString dateTimeStr(qint64 nTime) QString dateTimeStr(qint64 nTime)

View File

@ -60,6 +60,8 @@ namespace GUIUtil
constexpr auto dialog_flags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; constexpr auto dialog_flags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
// Create human-readable string from date // Create human-readable string from date
QString dateStr(const QDate &datetime);
QString dateStr(qint64 nTime);
QString dateTimeStr(const QDateTime &datetime); QString dateTimeStr(const QDateTime &datetime);
QString dateTimeStr(qint64 nTime); QString dateTimeStr(qint64 nTime);

View File

@ -13,23 +13,35 @@ QValidatedLineEdit::QValidatedLineEdit(QWidget* parent)
connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid); connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid);
} }
QValidatedLineEdit::~QValidatedLineEdit()
{
delete m_warning_validator;
}
void QValidatedLineEdit::setText(const QString& text) void QValidatedLineEdit::setText(const QString& text)
{ {
QLineEdit::setText(text); QLineEdit::setText(text);
checkValidity(); checkValidity();
} }
void QValidatedLineEdit::setValid(bool _valid) void QValidatedLineEdit::setValid(bool _valid, bool with_warning)
{ {
if(_valid == this->valid) if(_valid == this->valid)
{ {
if (with_warning == m_has_warning || !valid) {
return; return;
} }
}
if(_valid) if(_valid)
{ {
m_has_warning = with_warning;
if (with_warning) {
setStyleSheet("QValidatedLineEdit { " STYLE_INCORRECT "}");
} else {
setStyleSheet(""); setStyleSheet("");
} }
}
else else
{ {
setStyleSheet("QValidatedLineEdit { " STYLE_INVALID "}"); setStyleSheet("QValidatedLineEdit { " STYLE_INVALID "}");
@ -82,13 +94,14 @@ void QValidatedLineEdit::setEnabled(bool enabled)
void QValidatedLineEdit::checkValidity() void QValidatedLineEdit::checkValidity()
{ {
const bool has_warning = checkWarning();
if (text().isEmpty()) if (text().isEmpty())
{ {
setValid(true); setValid(true);
} }
else if (hasAcceptableInput()) else if (hasAcceptableInput())
{ {
setValid(true); setValid(true, has_warning);
// Check contents on focus out // Check contents on focus out
if (checkValidator) if (checkValidator)
@ -96,7 +109,7 @@ void QValidatedLineEdit::checkValidity()
QString address = text(); QString address = text();
int pos = 0; int pos = 0;
if (checkValidator->validate(address, pos) == QValidator::Acceptable) if (checkValidator->validate(address, pos) == QValidator::Acceptable)
setValid(true); setValid(true, has_warning);
else else
setValid(false); setValid(false);
} }
@ -126,3 +139,28 @@ bool QValidatedLineEdit::isValid()
return valid; return valid;
} }
void QValidatedLineEdit::setWarningValidator(const QValidator *v)
{
delete m_warning_validator;
m_warning_validator = v;
checkValidity();
}
bool QValidatedLineEdit::checkWarning() const
{
if (m_warning_validator && !text().isEmpty()) {
QString address = text();
int pos = 0;
if (m_warning_validator->validate(address, pos) != QValidator::Acceptable) {
return true;
}
}
return false;
}
bool QValidatedLineEdit::hasWarning() const
{
return m_has_warning;
}

View File

@ -16,9 +16,12 @@ class QValidatedLineEdit : public QLineEdit
public: public:
explicit QValidatedLineEdit(QWidget *parent); explicit QValidatedLineEdit(QWidget *parent);
~QValidatedLineEdit();
void clear(); void clear();
void setCheckValidator(const QValidator *v); void setCheckValidator(const QValidator *v);
bool isValid(); bool isValid();
void setWarningValidator(const QValidator *);
bool hasWarning() const;
protected: protected:
void focusInEvent(QFocusEvent *evt) override; void focusInEvent(QFocusEvent *evt) override;
@ -27,10 +30,12 @@ protected:
private: private:
bool valid{true}; bool valid{true};
const QValidator* checkValidator{nullptr}; const QValidator* checkValidator{nullptr};
bool m_has_warning{false};
const QValidator *m_warning_validator{nullptr};
public Q_SLOTS: public Q_SLOTS:
void setText(const QString&); void setText(const QString&);
void setValid(bool valid); void setValid(bool valid, bool with_warning=false);
void setEnabled(bool enabled); void setEnabled(bool enabled);
Q_SIGNALS: Q_SIGNALS:
@ -39,6 +44,7 @@ Q_SIGNALS:
private Q_SLOTS: private Q_SLOTS:
void markValid(); void markValid();
void checkValidity(); void checkValidity();
bool checkWarning() const;
}; };
#endif // BITCOIN_QT_QVALIDATEDLINEEDIT_H #endif // BITCOIN_QT_QVALIDATEDLINEEDIT_H

View File

@ -483,11 +483,82 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
if (!PrepareSendText(question_string, informative_text, detailed_text)) return; if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
assert(m_current_transaction); assert(m_current_transaction);
bool have_warning = false;
for (int i = 0; i < ui->entries->count(); ++i) {
SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
if (entry && entry->hasPaytoWarning()) {
have_warning = true;
break;
}
}
if (have_warning) {
auto recipients = m_current_transaction->getRecipients();
struct prior_usage_info_t {
CAmount total_amount{0};
int num_txs{0};
qint64 tx_time_oldest;
qint64 tx_time_newest;
};
QMap<QString, prior_usage_info_t> prior_usage_info;
{
QStringList addresses;
for (const auto& recipient : recipients) {
addresses.append(recipient.address);
}
model->findAddressUsage(addresses, [&prior_usage_info](const QString& address, const interfaces::WalletTx& wtx, uint32_t output_index){
auto& info = prior_usage_info[address];
info.total_amount += wtx.tx->vout[output_index].nValue;
++info.num_txs;
if (info.num_txs == 1 || wtx.time < info.tx_time_oldest) {
info.tx_time_oldest = wtx.time;
}
if (info.num_txs == 1 || wtx.time > info.tx_time_newest) {
info.tx_time_newest = wtx.time;
}
});
}
QString reuse_question, reuse_details;
if (recipients.size() > 1) {
reuse_question = tr("You've already paid some of these addresses.");
} else {
reuse_question = tr("You've already paid this address.");
}
for (const auto& rcp : recipients) {
if (!prior_usage_info.contains(rcp.address)) continue;
if (!reuse_details.isEmpty()) reuse_details.append("\n\n");
const auto& rcp_prior_usage_info = prior_usage_info.value(rcp.address);
const QString label_and_address = rcp.label.isEmpty() ? rcp.address : (QString("'") + rcp.label + "' (" + rcp.address + ")");
if (rcp_prior_usage_info.num_txs == 1) {
//: %1 is an amount (eg, "1 BTC"); %2 is a Bitcoin address and its label; %3 is a date (eg, "2019-05-08")
reuse_details.append(tr("Sent %1 to %2 on %3").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp_prior_usage_info.total_amount), label_and_address, GUIUtil::dateStr(rcp_prior_usage_info.tx_time_newest)));
} else {
//: %1 is an amount (eg, "1 BTC"); %2 is a Bitcoin address and its label; %3 is the number of transactions; %4 and %5 are dates (eg, "2019-05-08"), earlier first
reuse_details.append(tr("Sent %1 to %2 across %3 transactions from %4 through %5").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp_prior_usage_info.total_amount), label_and_address, QString::number(rcp_prior_usage_info.num_txs), GUIUtil::dateStr(rcp_prior_usage_info.tx_time_oldest), GUIUtil::dateStr(rcp_prior_usage_info.tx_time_newest)));
}
}
reuse_question.append("<br /><br /><span style='font-size:10pt;'>");
reuse_question.append(tr("Bitcoin addresses are intended to only be used once, for a single payment. Sending to the same address again will harm the recipient's security, as well as the privacy of all Bitcoin users!"));
reuse_question.append("</span>");
SendConfirmationDialog confirmation_dialog(tr("Already paid"), reuse_question, "", reuse_details, ADDRESS_REUSE_OVERRIDE_DELAY, /*enable_send=*/true, /*always_show_unsigned=*/false, this);
confirmation_dialog.setIcon(QMessageBox::Warning);
confirmation_dialog.confirmButtonText = tr("Override");
confirmation_dialog.m_yes_button = QMessageBox::Ignore;
confirmation_dialog.m_cancel_button = QMessageBox::Ok;
if (static_cast<QMessageBox::StandardButton>(confirmation_dialog.exec()) == QMessageBox::Cancel) {
fNewRecipientAllowed = true;
return;
}
}
const QString confirmation = tr("Confirm send coins"); const QString confirmation = tr("Confirm send coins");
const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()}; const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()}; const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this); auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose); confirmationDialog->m_delete_on_close = true;
// TODO: Replace QDialog::exec() with safer QDialog::show(). // TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec()); const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
@ -1056,30 +1127,61 @@ void SendCoinsDialog::coinControlUpdateLabels()
} }
SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent) SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent)
: QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send) : QMessageBox(parent), secDelay(_secDelay), m_enable_save(always_show_unsigned || !enable_send), m_enable_send(enable_send)
{ {
setIcon(QMessageBox::Question); setIcon(QMessageBox::Question);
setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines). setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
setText(text); setText(text);
setInformativeText(informative_text); setInformativeText(informative_text);
setDetailedText(detailed_text); setDetailedText(detailed_text);
setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); }
if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
setDefaultButton(QMessageBox::Cancel); int SendConfirmationDialog::exec()
yesButton = button(QMessageBox::Yes); {
setStandardButtons(m_yes_button | m_cancel_button);
yesButton = button(m_yes_button);
QAbstractButton * const cancel_button_obj = button(m_cancel_button);
if (m_yes_button != QMessageBox::Yes || m_cancel_button != QMessageBox::Cancel) {
// We need to ensure the buttons have Yes/No roles, or they'll get ordered weird
// But only do it for customised yes/cancel buttons, so simple code can check results simply too
removeButton(cancel_button_obj);
addButton(cancel_button_obj, QMessageBox::NoRole);
setEscapeButton(cancel_button_obj);
removeButton(yesButton);
addButton(yesButton, QMessageBox::YesRole);
}
if (m_enable_save) addButton(QMessageBox::Save);
setDefaultButton(m_cancel_button);
if (confirmButtonText.isEmpty()) { if (confirmButtonText.isEmpty()) {
confirmButtonText = yesButton->text(); confirmButtonText = yesButton->text();
} }
m_psbt_button = button(QMessageBox::Save); m_psbt_button = button(QMessageBox::Save);
updateButtons(); updateButtons();
connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown); connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
countDownTimer.start(1s);
QMessageBox::exec();
int rv;
const auto clicked_button = clickedButton();
if (clicked_button == m_psbt_button) {
rv = QMessageBox::Save;
} else if (clicked_button == yesButton) {
rv = QMessageBox::Yes;
} else {
rv = QMessageBox::Cancel;
} }
int SendConfirmationDialog::exec() if (m_delete_on_close) delete this;
{
updateButtons(); return rv;
countDownTimer.start(1s);
return QMessageBox::exec();
} }
void SendConfirmationDialog::countDown() void SendConfirmationDialog::countDown()

View File

@ -123,12 +123,18 @@ Q_SIGNALS:
#define SEND_CONFIRM_DELAY 3 #define SEND_CONFIRM_DELAY 3
#define ADDRESS_REUSE_OVERRIDE_DELAY 10
class SendConfirmationDialog : public QMessageBox class SendConfirmationDialog : public QMessageBox
{ {
Q_OBJECT Q_OBJECT
public: public:
bool m_delete_on_close{false};
QString confirmButtonText{tr("Send")};
QMessageBox::StandardButton m_yes_button{QMessageBox::Yes};
QMessageBox::StandardButton m_cancel_button{QMessageBox::Cancel};
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr); SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr);
/* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is /* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is
clicked and QMessageBox::Save when "Create Unsigned" is clicked. */ clicked and QMessageBox::Save when "Create Unsigned" is clicked. */
@ -143,7 +149,7 @@ private:
QAbstractButton *m_psbt_button; QAbstractButton *m_psbt_button;
QTimer countDownTimer; QTimer countDownTimer;
int secDelay; int secDelay;
QString confirmButtonText{tr("Send")}; bool m_enable_save;
bool m_enable_send; bool m_enable_send;
QString m_psbt_button_text{tr("Create Unsigned")}; QString m_psbt_button_text{tr("Create Unsigned")};
}; };

View File

@ -71,6 +71,12 @@ void SendCoinsEntry::setModel(WalletModel *_model)
{ {
this->model = _model; this->model = _model;
if (_model) {
ui->payTo->setWarningValidator(new BitcoinAddressUnusedInWalletValidator(*_model));
} else {
ui->payTo->setWarningValidator(nullptr);
}
if (_model && _model->getOptionsModel()) if (_model && _model->getOptionsModel())
connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsEntry::updateDisplayUnit); connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsEntry::updateDisplayUnit);
@ -144,6 +150,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node)
return retval; return retval;
} }
bool SendCoinsEntry::hasPaytoWarning() const
{
return ui->payTo->hasWarning();
}
SendCoinsRecipient SendCoinsEntry::getValue() SendCoinsRecipient SendCoinsEntry::getValue()
{ {
recipient.address = ui->payTo->text(); recipient.address = ui->payTo->text();

View File

@ -33,6 +33,7 @@ public:
void setModel(WalletModel *model); void setModel(WalletModel *model);
bool validate(interfaces::Node& node); bool validate(interfaces::Node& node);
bool hasPaytoWarning() const;
SendCoinsRecipient getValue(); SendCoinsRecipient getValue();
/** Return whether the entry is still empty and unedited */ /** Return whether the entry is still empty and unedited */

View File

@ -58,19 +58,46 @@ using wallet::WalletRescanReserver;
namespace namespace
{ {
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSendAttempt(QString* text, QMessageBox::StandardButton confirm_type)
void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
{ {
QTimer::singleShot(0, [text, confirm_type]() {
for (QWidget* widget : QApplication::topLevelWidgets()) { for (QWidget* widget : QApplication::topLevelWidgets()) {
if (widget->inherits("SendConfirmationDialog")) { if (widget->inherits("SendConfirmationDialog")) {
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget); SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
if (text) *text = dialog->text(); if (text) *text = dialog->text();
QAbstractButton* button = dialog->button(confirm_type); QAbstractButton* button = dialog->button(confirm_type);
const QMessageBox::ButtonRole confirm_role = [confirm_type, button](){
if (button) return QMessageBox::InvalidRole;
switch (confirm_type) {
case QMessageBox::Yes: return QMessageBox::YesRole;
case QMessageBox::Cancel: return QMessageBox::NoRole;
default: return QMessageBox::InvalidRole;
}
}();
for (QAbstractButton* maybe_button : dialog->buttons()) {
if (dialog->buttonRole(maybe_button) == confirm_role) {
button = maybe_button;
} else if (maybe_button->text().startsWith("Override")) {
button = maybe_button;
break;
}
}
button->setEnabled(true); button->setEnabled(true);
button->click(); button->click();
if (!button->text().startsWith("Override")) return;
} }
} }
// Try again
QTimer::singleShot(0, [text, confirm_type]{
ConfirmSendAttempt(text, confirm_type);
});
}
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
{
QTimer::singleShot(0, [text, confirm_type]{
ConfirmSendAttempt(text, confirm_type);
}); });
} }

View File

@ -151,6 +151,22 @@ bool WalletModel::validateAddress(const QString& address) const
return IsValidDestinationString(address.toStdString()); return IsValidDestinationString(address.toStdString());
} }
bool WalletModel::checkAddressForUsage(const std::vector<std::string>& addresses) const
{
return m_wallet->checkAddressForUsage(addresses);
}
bool WalletModel::findAddressUsage(const QStringList& addresses, std::function<void(const QString&, const interfaces::WalletTx&, uint32_t)> callback) const
{
std::vector<std::string> std_addresses;
for (const auto& address : addresses) {
std_addresses.push_back(address.toStdString());
}
return m_wallet->findAddressUsage(std_addresses, [&callback](const std::string& address, const interfaces::WalletTx& wtx, uint32_t output_index){
callback(QString::fromStdString(address), wtx, output_index);
});
}
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl) WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
{ {
CAmount total = 0; CAmount total = 0;
@ -520,7 +536,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()}; const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()}; const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr); auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose); confirmationDialog->m_delete_on_close = true;
// TODO: Replace QDialog::exec() with safer QDialog::show(). // TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec()); const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
@ -627,3 +643,18 @@ CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
// Fetch balance from the wallet, taking into account the selected coins // Fetch balance from the wallet, taking into account the selected coins
return wallet().getAvailableBalance(*control); return wallet().getAvailableBalance(*control);
} }
BitcoinAddressUnusedInWalletValidator::BitcoinAddressUnusedInWalletValidator(const WalletModel& wallet_model, QObject *parent) :
QValidator(parent),
m_wallet_model(wallet_model)
{
}
QValidator::State BitcoinAddressUnusedInWalletValidator::validate(QString &input, int &pos) const
{
Q_UNUSED(pos);
if (m_wallet_model.checkAddressForUsage(std::vector<std::string>{input.toStdString()})) {
return QValidator::Invalid;
}
return QValidator::Acceptable;
}

View File

@ -6,15 +6,18 @@
#define BITCOIN_QT_WALLETMODEL_H #define BITCOIN_QT_WALLETMODEL_H
#include <key.h> #include <key.h>
#include <primitives/transaction.h>
#include <qt/walletmodeltransaction.h> #include <qt/walletmodeltransaction.h>
#include <interfaces/wallet.h> #include <interfaces/wallet.h>
#include <support/allocators/secure.h> #include <support/allocators/secure.h>
#include <string>
#include <vector> #include <vector>
#include <QObject> #include <QObject>
#include <QValidator>
enum class OutputType; enum class OutputType;
@ -81,6 +84,8 @@ public:
// Check address for validity // Check address for validity
bool validateAddress(const QString& address) const; bool validateAddress(const QString& address) const;
bool checkAddressForUsage(const std::vector<std::string>& addresses) const;
bool findAddressUsage(const QStringList& addresses, std::function<void(const QString&, const interfaces::WalletTx&, uint32_t)> callback) const;
// Return status record for SendCoins, contains error id + information // Return status record for SendCoins, contains error id + information
struct SendCoinsReturn struct SendCoinsReturn
@ -238,4 +243,16 @@ public Q_SLOTS:
void pollBalanceChanged(); void pollBalanceChanged();
}; };
class BitcoinAddressUnusedInWalletValidator : public QValidator
{
Q_OBJECT
const WalletModel& m_wallet_model;
public:
explicit BitcoinAddressUnusedInWalletValidator(const WalletModel&, QObject *parent=nullptr);
State validate(QString &input, int &pos) const override;
};
#endif // BITCOIN_QT_WALLETMODEL_H #endif // BITCOIN_QT_WALLETMODEL_H

View File

@ -8,6 +8,7 @@
#include <consensus/amount.h> #include <consensus/amount.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/handler.h> #include <interfaces/handler.h>
#include <key_io.h>
#include <policy/fees.h> #include <policy/fees.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <rpc/server.h> #include <rpc/server.h>
@ -30,6 +31,7 @@
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <memory> #include <memory>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -49,6 +51,16 @@ using interfaces::WalletTxOut;
using interfaces::WalletTxStatus; using interfaces::WalletTxStatus;
using interfaces::WalletValueMap; using interfaces::WalletValueMap;
std::set<CScript> AddressesToKeys(std::vector<std::string> addresses)
{
std::set<CScript> keys;
for (const auto& address : addresses) {
CScript scriptPubKey = GetScriptForDestination(DecodeDestination(address));
keys.insert(scriptPubKey);
}
return keys;
}
namespace wallet { namespace wallet {
// All members of the classes in this namespace are intentionally public, as the // All members of the classes in this namespace are intentionally public, as the
// classes themselves are private. // classes themselves are private.
@ -252,6 +264,23 @@ public:
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
return m_wallet->DisplayAddress(dest); return m_wallet->DisplayAddress(dest);
} }
bool checkAddressForUsage(const std::vector<std::string>& addresses) const override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->FindScriptPubKeyUsed(AddressesToKeys(addresses));
}
bool findAddressUsage(const std::vector<std::string>& addresses, std::function<void(const std::string&, const WalletTx&, uint32_t)> callback) const override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->FindScriptPubKeyUsed(AddressesToKeys(addresses), [&callback, this](const CWalletTx& wtx, uint32_t output_index){
CTxDestination dest;
bool success = ExtractDestination(wtx.tx->vout[output_index].scriptPubKey, dest);
assert(success); // It shouldn't be possible to end up here with anything unrecognised
std::string address = EncodeDestination(dest);
WalletTx interface_wtx = MakeWalletTx(*m_wallet, wtx);
callback(address, interface_wtx, output_index);
});
}
bool lockCoin(const COutPoint& output, const bool write_to_db) override bool lockCoin(const COutPoint& output, const bool write_to_db) override
{ {
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);