mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-08-04 05:54:48 +02:00
Merge g562 via wallet_warn_reuse_gui
This commit is contained in:
commit
00cd83d47c
@ -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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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")};
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
|
@ -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 */
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user