mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-06-03 16:02:34 +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
|
||||
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.
|
||||
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 */
|
||||
#define STYLE_INVALID "border: 3px solid #FF8080"
|
||||
/* "Warning" field background style */
|
||||
#define STYLE_INCORRECT "border: 3px solid #FFFF80"
|
||||
|
||||
/* Transaction list -- unconfirmed transaction */
|
||||
#define COLOR_UNCONFIRMED QColor(128, 128, 128)
|
||||
|
@ -91,9 +91,19 @@ using namespace std::chrono_literals;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -60,6 +60,8 @@ namespace GUIUtil
|
||||
constexpr auto dialog_flags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
|
||||
|
||||
// Create human-readable string from date
|
||||
QString dateStr(const QDate &datetime);
|
||||
QString dateStr(qint64 nTime);
|
||||
QString dateTimeStr(const QDateTime &datetime);
|
||||
QString dateTimeStr(qint64 nTime);
|
||||
|
||||
|
@ -13,22 +13,34 @@ QValidatedLineEdit::QValidatedLineEdit(QWidget* parent)
|
||||
connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid);
|
||||
}
|
||||
|
||||
QValidatedLineEdit::~QValidatedLineEdit()
|
||||
{
|
||||
delete m_warning_validator;
|
||||
}
|
||||
|
||||
void QValidatedLineEdit::setText(const QString& text)
|
||||
{
|
||||
QLineEdit::setText(text);
|
||||
checkValidity();
|
||||
}
|
||||
|
||||
void QValidatedLineEdit::setValid(bool _valid)
|
||||
void QValidatedLineEdit::setValid(bool _valid, bool with_warning)
|
||||
{
|
||||
if(_valid == this->valid)
|
||||
{
|
||||
return;
|
||||
if (with_warning == m_has_warning || !valid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(_valid)
|
||||
{
|
||||
setStyleSheet("");
|
||||
m_has_warning = with_warning;
|
||||
if (with_warning) {
|
||||
setStyleSheet("QValidatedLineEdit { " STYLE_INCORRECT "}");
|
||||
} else {
|
||||
setStyleSheet("");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -82,13 +94,14 @@ void QValidatedLineEdit::setEnabled(bool enabled)
|
||||
|
||||
void QValidatedLineEdit::checkValidity()
|
||||
{
|
||||
const bool has_warning = checkWarning();
|
||||
if (text().isEmpty())
|
||||
{
|
||||
setValid(true);
|
||||
}
|
||||
else if (hasAcceptableInput())
|
||||
{
|
||||
setValid(true);
|
||||
setValid(true, has_warning);
|
||||
|
||||
// Check contents on focus out
|
||||
if (checkValidator)
|
||||
@ -96,7 +109,7 @@ void QValidatedLineEdit::checkValidity()
|
||||
QString address = text();
|
||||
int pos = 0;
|
||||
if (checkValidator->validate(address, pos) == QValidator::Acceptable)
|
||||
setValid(true);
|
||||
setValid(true, has_warning);
|
||||
else
|
||||
setValid(false);
|
||||
}
|
||||
@ -126,3 +139,28 @@ bool QValidatedLineEdit::isValid()
|
||||
|
||||
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:
|
||||
explicit QValidatedLineEdit(QWidget *parent);
|
||||
~QValidatedLineEdit();
|
||||
void clear();
|
||||
void setCheckValidator(const QValidator *v);
|
||||
bool isValid();
|
||||
void setWarningValidator(const QValidator *);
|
||||
bool hasWarning() const;
|
||||
|
||||
protected:
|
||||
void focusInEvent(QFocusEvent *evt) override;
|
||||
@ -27,10 +30,12 @@ protected:
|
||||
private:
|
||||
bool valid{true};
|
||||
const QValidator* checkValidator{nullptr};
|
||||
bool m_has_warning{false};
|
||||
const QValidator *m_warning_validator{nullptr};
|
||||
|
||||
public Q_SLOTS:
|
||||
void setText(const QString&);
|
||||
void setValid(bool valid);
|
||||
void setValid(bool valid, bool with_warning=false);
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -39,6 +44,7 @@ Q_SIGNALS:
|
||||
private Q_SLOTS:
|
||||
void markValid();
|
||||
void checkValidity();
|
||||
bool checkWarning() const;
|
||||
};
|
||||
|
||||
#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;
|
||||
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 bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
|
||||
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);
|
||||
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
confirmationDialog->m_delete_on_close = true;
|
||||
// TODO: Replace QDialog::exec() with safer QDialog::show().
|
||||
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)
|
||||
: 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);
|
||||
setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
|
||||
setText(text);
|
||||
setInformativeText(informative_text);
|
||||
setDetailedText(detailed_text);
|
||||
setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
|
||||
if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
|
||||
setDefaultButton(QMessageBox::Cancel);
|
||||
yesButton = button(QMessageBox::Yes);
|
||||
}
|
||||
|
||||
int SendConfirmationDialog::exec()
|
||||
{
|
||||
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()) {
|
||||
confirmButtonText = yesButton->text();
|
||||
}
|
||||
m_psbt_button = button(QMessageBox::Save);
|
||||
updateButtons();
|
||||
connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
|
||||
}
|
||||
|
||||
int SendConfirmationDialog::exec()
|
||||
{
|
||||
updateButtons();
|
||||
connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
|
||||
countDownTimer.start(1s);
|
||||
return QMessageBox::exec();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (m_delete_on_close) delete this;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void SendConfirmationDialog::countDown()
|
||||
|
@ -123,12 +123,18 @@ Q_SIGNALS:
|
||||
|
||||
|
||||
#define SEND_CONFIRM_DELAY 3
|
||||
#define ADDRESS_REUSE_OVERRIDE_DELAY 10
|
||||
|
||||
class SendConfirmationDialog : public QMessageBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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);
|
||||
/* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is
|
||||
clicked and QMessageBox::Save when "Create Unsigned" is clicked. */
|
||||
@ -143,7 +149,7 @@ private:
|
||||
QAbstractButton *m_psbt_button;
|
||||
QTimer countDownTimer;
|
||||
int secDelay;
|
||||
QString confirmButtonText{tr("Send")};
|
||||
bool m_enable_save;
|
||||
bool m_enable_send;
|
||||
QString m_psbt_button_text{tr("Create Unsigned")};
|
||||
};
|
||||
|
@ -71,6 +71,12 @@ void SendCoinsEntry::setModel(WalletModel *_model)
|
||||
{
|
||||
this->model = _model;
|
||||
|
||||
if (_model) {
|
||||
ui->payTo->setWarningValidator(new BitcoinAddressUnusedInWalletValidator(*_model));
|
||||
} else {
|
||||
ui->payTo->setWarningValidator(nullptr);
|
||||
}
|
||||
|
||||
if (_model && _model->getOptionsModel())
|
||||
connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsEntry::updateDisplayUnit);
|
||||
|
||||
@ -144,6 +150,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node)
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool SendCoinsEntry::hasPaytoWarning() const
|
||||
{
|
||||
return ui->payTo->hasWarning();
|
||||
}
|
||||
|
||||
SendCoinsRecipient SendCoinsEntry::getValue()
|
||||
{
|
||||
recipient.address = ui->payTo->text();
|
||||
|
@ -33,6 +33,7 @@ public:
|
||||
|
||||
void setModel(WalletModel *model);
|
||||
bool validate(interfaces::Node& node);
|
||||
bool hasPaytoWarning() const;
|
||||
SendCoinsRecipient getValue();
|
||||
|
||||
/** Return whether the entry is still empty and unedited */
|
||||
|
@ -58,19 +58,46 @@ using wallet::WalletRescanReserver;
|
||||
|
||||
namespace
|
||||
{
|
||||
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
|
||||
void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
|
||||
void ConfirmSendAttempt(QString* text, QMessageBox::StandardButton confirm_type)
|
||||
{
|
||||
QTimer::singleShot(0, [text, confirm_type]() {
|
||||
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
||||
if (widget->inherits("SendConfirmationDialog")) {
|
||||
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
||||
if (text) *text = dialog->text();
|
||||
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->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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
|
||||
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().
|
||||
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
|
||||
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
|
||||
|
||||
#include <key.h>
|
||||
#include <primitives/transaction.h>
|
||||
|
||||
#include <qt/walletmodeltransaction.h>
|
||||
|
||||
#include <interfaces/wallet.h>
|
||||
#include <support/allocators/secure.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
#include <QValidator>
|
||||
|
||||
enum class OutputType;
|
||||
|
||||
@ -81,6 +84,8 @@ public:
|
||||
|
||||
// Check address for validity
|
||||
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
|
||||
struct SendCoinsReturn
|
||||
@ -238,4 +243,16 @@ public Q_SLOTS:
|
||||
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
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <consensus/amount.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/fees.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <rpc/server.h>
|
||||
@ -30,6 +31,7 @@
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -49,6 +51,16 @@ using interfaces::WalletTxOut;
|
||||
using interfaces::WalletTxStatus;
|
||||
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 {
|
||||
// All members of the classes in this namespace are intentionally public, as the
|
||||
// classes themselves are private.
|
||||
@ -252,6 +264,23 @@ public:
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
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
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
|
Loading…
Reference in New Issue
Block a user