mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Merge pull request #4887 from knst/bc-bp-4
Bitcoin backports #13756 #15770 #17730 #17750 #17752 #17826 #17857 #15886
This commit is contained in:
commit
aa97e52f26
@ -47,11 +47,15 @@ $(package)_config_opts += -no-ico
|
||||
$(package)_config_opts += -no-iconv
|
||||
$(package)_config_opts += -no-kms
|
||||
$(package)_config_opts += -no-linuxfb
|
||||
$(package)_config_opts += -no-libjpeg
|
||||
$(package)_config_opts += -no-libproxy
|
||||
$(package)_config_opts += -no-libudev
|
||||
$(package)_config_opts += -no-mtdev
|
||||
$(package)_config_opts += -no-openssl
|
||||
$(package)_config_opts += -no-openvg
|
||||
$(package)_config_opts += -no-reduce-relocations
|
||||
$(package)_config_opts += -no-sctp
|
||||
$(package)_config_opts += -no-securetransport
|
||||
$(package)_config_opts += -no-sql-db2
|
||||
$(package)_config_opts += -no-sql-ibase
|
||||
$(package)_config_opts += -no-sql-oci
|
||||
@ -61,6 +65,7 @@ $(package)_config_opts += -no-sql-odbc
|
||||
$(package)_config_opts += -no-sql-psql
|
||||
$(package)_config_opts += -no-sql-sqlite
|
||||
$(package)_config_opts += -no-sql-sqlite2
|
||||
$(package)_config_opts += -no-system-proxies
|
||||
$(package)_config_opts += -no-use-gold-linker
|
||||
$(package)_config_opts += -nomake examples
|
||||
$(package)_config_opts += -nomake tests
|
||||
@ -69,7 +74,6 @@ $(package)_config_opts += -opensource
|
||||
$(package)_config_opts += -pkg-config
|
||||
$(package)_config_opts += -prefix $(host_prefix)
|
||||
$(package)_config_opts += -qt-libpng
|
||||
$(package)_config_opts += -qt-libjpeg
|
||||
$(package)_config_opts += -qt-pcre
|
||||
$(package)_config_opts += -qt-harfbuzz
|
||||
$(package)_config_opts += -qt-zlib
|
||||
@ -82,15 +86,19 @@ $(package)_config_opts += -no-feature-concurrent
|
||||
$(package)_config_opts += -no-feature-dial
|
||||
$(package)_config_opts += -no-feature-fontcombobox
|
||||
$(package)_config_opts += -no-feature-ftp
|
||||
$(package)_config_opts += -no-feature-http
|
||||
$(package)_config_opts += -no-feature-image_heuristic_mask
|
||||
$(package)_config_opts += -no-feature-keysequenceedit
|
||||
$(package)_config_opts += -no-feature-lcdnumber
|
||||
$(package)_config_opts += -no-feature-networkdiskcache
|
||||
$(package)_config_opts += -no-feature-networkproxy
|
||||
$(package)_config_opts += -no-feature-pdf
|
||||
$(package)_config_opts += -no-feature-printdialog
|
||||
$(package)_config_opts += -no-feature-printer
|
||||
$(package)_config_opts += -no-feature-printpreviewdialog
|
||||
$(package)_config_opts += -no-feature-printpreviewwidget
|
||||
$(package)_config_opts += -no-feature-sessionmanager
|
||||
$(package)_config_opts += -no-feature-socks5
|
||||
$(package)_config_opts += -no-feature-sql
|
||||
$(package)_config_opts += -no-feature-sqlmodel
|
||||
$(package)_config_opts += -no-feature-statemachine
|
||||
|
@ -924,7 +924,7 @@ Current subtrees include:
|
||||
- Used by leveldb for hardware acceleration of CRC32C checksums for data integrity.
|
||||
- Upstream at https://github.com/google/crc32c ; Maintained by Google.
|
||||
|
||||
- src/libsecp256k1
|
||||
- src/secp256k1
|
||||
- Upstream at https://github.com/bitcoin-core/secp256k1/ ; actively maintained by Core contributors.
|
||||
|
||||
- src/crypto/ctaes
|
||||
|
39
doc/release-notes-13756.md
Normal file
39
doc/release-notes-13756.md
Normal file
@ -0,0 +1,39 @@
|
||||
Coin selection
|
||||
--------------
|
||||
|
||||
### Reuse Avoidance
|
||||
|
||||
A new wallet flag `avoid_reuse` has been added (default off). When enabled,
|
||||
a wallet will distinguish between used and unused addresses, and default to not
|
||||
use the former in coin selection.
|
||||
|
||||
(Note: rescanning the blockchain is required, to correctly mark previously
|
||||
used destinations.)
|
||||
|
||||
Together with "avoid partial spends" (present as of Bitcoin v0.17), this
|
||||
addresses a serious privacy issue where a malicious user can track spends by
|
||||
peppering a previously paid to address with near-dust outputs, which would then
|
||||
be inadvertently included in future payments.
|
||||
|
||||
New RPCs
|
||||
--------
|
||||
|
||||
- A new `setwalletflag` RPC sets/unsets flags for an existing wallet.
|
||||
|
||||
|
||||
Updated RPCs
|
||||
------------
|
||||
|
||||
Several RPCs have been updated to include an "avoid_reuse" flag, used to control
|
||||
whether already used addresses should be left out or included in the operation.
|
||||
These include:
|
||||
|
||||
- createwallet
|
||||
- getbalance
|
||||
- sendtoaddress
|
||||
|
||||
In addition, `sendtoaddress` has been changed to enable `-avoidpartialspends` when
|
||||
`avoid_reuse` is enabled.
|
||||
|
||||
The listunspent RPC has also been updated to now include a "reused" bool, for nodes
|
||||
with "avoid_reuse" enabled.
|
@ -905,13 +905,13 @@ clean-local:
|
||||
check-symbols: $(bin_PROGRAMS)
|
||||
if GLIBC_BACK_COMPAT
|
||||
@echo "Checking glibc back compat..."
|
||||
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py < $(bin_PROGRAMS)
|
||||
$(AM_V_at) READELF=$(READELF) CPPFILT=$(CPPFILT) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
||||
endif
|
||||
|
||||
check-security: $(bin_PROGRAMS)
|
||||
if HARDEN
|
||||
@echo "Checking binary security..."
|
||||
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py < $(bin_PROGRAMS)
|
||||
$(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) OTOOL=$(OTOOL) $(PYTHON) $(top_srcdir)/contrib/devtools/security-check.py $(bin_PROGRAMS)
|
||||
endif
|
||||
|
||||
|
||||
|
@ -192,7 +192,7 @@ public:
|
||||
std::string getNetwork() override { return Params().NetworkIDString(); }
|
||||
void initLogging() override { InitLogging(gArgs); }
|
||||
void initParameterInteraction() override { InitParameterInteraction(gArgs); }
|
||||
std::string getWarnings(const std::string& type) override { return GetWarnings(type); }
|
||||
std::string getWarnings() override { return GetWarnings(true); }
|
||||
uint64_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
|
||||
bool baseInitialize() override
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ public:
|
||||
virtual void initParameterInteraction() = 0;
|
||||
|
||||
//! Get warnings.
|
||||
virtual std::string getWarnings(const std::string& type) = 0;
|
||||
virtual std::string getWarnings() = 0;
|
||||
|
||||
// Get log flags.
|
||||
virtual uint64_t getLogCategories() = 0;
|
||||
|
@ -430,7 +430,7 @@ public:
|
||||
CAmount getAvailableBalance(const CCoinControl& coin_control) override
|
||||
{
|
||||
if (coin_control.IsUsingCoinJoin()) {
|
||||
return m_wallet->GetBalance(0, false, &coin_control).m_anonymized;
|
||||
return m_wallet->GetBalance(0, coin_control.m_avoid_address_reuse, false, &coin_control).m_anonymized;
|
||||
} else {
|
||||
return m_wallet->GetAvailableBalance(&coin_control);
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ enum BlockSource ClientModel::getBlockSource() const
|
||||
|
||||
QString ClientModel::getStatusBarWarnings() const
|
||||
{
|
||||
return QString::fromStdString(m_node.getWarnings("gui"));
|
||||
return QString::fromStdString(m_node.getWarnings());
|
||||
}
|
||||
|
||||
OptionsModel *ClientModel::getOptionsModel()
|
||||
|
@ -141,7 +141,7 @@ BitcoinCore::BitcoinCore(interfaces::Node& node) :
|
||||
void BitcoinCore::handleRunawayException(const std::exception_ptr e)
|
||||
{
|
||||
PrintExceptionContinue(e, "Runaway exception");
|
||||
Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings("gui")));
|
||||
Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings()));
|
||||
}
|
||||
|
||||
void BitcoinCore::initialize()
|
||||
@ -587,6 +587,7 @@ int GuiMain(int argc, char* argv[])
|
||||
qInstallMessageHandler(DebugMessageHandler);
|
||||
// Allow parameter interaction before we create the options model
|
||||
app.parameterSetup();
|
||||
GUIUtil::LogQtInfo();
|
||||
// Load custom application fonts and setup font management
|
||||
if (!GUIUtil::loadFonts()) {
|
||||
QMessageBox::critical(0, PACKAGE_NAME,
|
||||
@ -705,7 +706,7 @@ int GuiMain(int argc, char* argv[])
|
||||
}
|
||||
} catch (...) {
|
||||
PrintExceptionContinue(std::current_exception(), "Runaway exception");
|
||||
app.handleRunawayException(QString::fromStdString(node->getWarnings("gui")));
|
||||
app.handleRunawayException(QString::fromStdString(node->getWarnings()));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
@ -53,17 +53,23 @@
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QFontMetrics>
|
||||
#include <QGuiApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QPointer>
|
||||
#include <QProgressDialog>
|
||||
#include <QScreen>
|
||||
#include <QSettings>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QTextDocument> // for Qt::mightBeRichText
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
|
||||
@ -1876,4 +1882,23 @@ int TextWidth(const QFontMetrics& fm, const QString& text)
|
||||
#endif
|
||||
}
|
||||
|
||||
void LogQtInfo()
|
||||
{
|
||||
#ifdef QT_STATIC
|
||||
const std::string qt_link{"static"};
|
||||
#else
|
||||
const std::string qt_link{"dynamic"};
|
||||
#endif
|
||||
#ifdef QT_STATICPLUGIN
|
||||
const std::string plugin_link{"static"};
|
||||
#else
|
||||
const std::string plugin_link{"dynamic"};
|
||||
#endif
|
||||
LogPrintf("Qt %s (%s), plugin=%s (%s)\n", qVersion(), qt_link, QGuiApplication::platformName().toStdString(), plugin_link);
|
||||
LogPrintf("System: %s, %s\n", QSysInfo::prettyProductName().toStdString(), QSysInfo::buildAbi().toStdString());
|
||||
for (const QScreen* s : QGuiApplication::screens()) {
|
||||
LogPrintf("Screen: %s %dx%d, pixel ratio=%.1f\n", s->name().toStdString(), s->size().width(), s->size().height(), s->devicePixelRatio());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GUIUtil
|
||||
|
@ -475,6 +475,11 @@ namespace GUIUtil
|
||||
* In Qt 5.11 the QFontMetrics::horizontalAdvance() was introduced.
|
||||
*/
|
||||
int TextWidth(const QFontMetrics& fm, const QString& text);
|
||||
|
||||
/**
|
||||
* Writes to debug.log short info about the used Qt and the host system.
|
||||
*/
|
||||
void LogQtInfo();
|
||||
} // namespace GUIUtil
|
||||
|
||||
#endif // BITCOIN_QT_GUIUTIL_H
|
||||
|
@ -319,23 +319,21 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
QStringList formatted;
|
||||
for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
|
||||
{
|
||||
// generate bold amount string with wallet name in case of multiwallet
|
||||
QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
||||
// generate amount string with wallet name in case of multiwallet
|
||||
QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
|
||||
if (model->isMultiwallet()) {
|
||||
amount.append(" <u>"+tr("from wallet %1").arg(GUIUtil::HtmlEscape(model->getWalletName()))+"</u> ");
|
||||
amount.append(tr(" from wallet '%1'").arg(model->getWalletName()));
|
||||
}
|
||||
amount.append("</b> ");
|
||||
// generate monospace address string
|
||||
QString address = "<span style='font-family: monospace;'>" + rcp.address;
|
||||
address.append("</span>");
|
||||
|
||||
// generate address string
|
||||
QString address = rcp.address;
|
||||
|
||||
QString recipientElement;
|
||||
recipientElement = "<br />";
|
||||
|
||||
{
|
||||
if(rcp.label.length() > 0) // label with address
|
||||
{
|
||||
recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
|
||||
recipientElement.append(tr("%1 to '%2'").arg(amount, rcp.label));
|
||||
recipientElement.append(QString(" (%1)").arg(address));
|
||||
}
|
||||
else // just address
|
||||
@ -347,21 +345,14 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
}
|
||||
|
||||
// Limit number of displayed entries
|
||||
int messageEntries = formatted.size();
|
||||
int displayedEntries = 0;
|
||||
for(int i = 0; i < formatted.size(); i++){
|
||||
if(i >= MAX_SEND_POPUP_ENTRIES){
|
||||
formatted.removeLast();
|
||||
i--;
|
||||
}
|
||||
else{
|
||||
displayedEntries = i+1;
|
||||
}
|
||||
QStringList formatted_short(formatted);
|
||||
if (formatted_short.size() > MAX_SEND_POPUP_ENTRIES) {
|
||||
formatted_short.erase(formatted_short.begin() + MAX_SEND_POPUP_ENTRIES, formatted_short.end());
|
||||
}
|
||||
|
||||
QString questionString = tr("Are you sure you want to send?");
|
||||
questionString.append("<br /><br />");
|
||||
questionString.append(formatted.join("<br />"));
|
||||
questionString.append(formatted_short.join("<br />"));
|
||||
questionString.append("<br />");
|
||||
|
||||
QString strCoinJoinName = QString::fromStdString(gCoinJoinName);
|
||||
@ -372,6 +363,9 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
questionString.append(tr("using") + " <b>" + tr("any available funds") + "</b>");
|
||||
}
|
||||
|
||||
int messageEntries = formatted.size();
|
||||
int displayedEntries = formatted_short.size();
|
||||
|
||||
if (displayedEntries < messageEntries) {
|
||||
questionString.append("<br />");
|
||||
questionString.append("<span style='" + GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR) + "'>");
|
||||
@ -437,9 +431,14 @@ void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
|
||||
questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
|
||||
.arg(alternativeUnits.join(" " + tr("or") + " ")));
|
||||
|
||||
// Display message box
|
||||
SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
|
||||
questionString, SEND_CONFIRM_DELAY, this);
|
||||
QString informative_text;
|
||||
QString detailed_text;
|
||||
if (formatted.size() > 1) {
|
||||
informative_text = tr("To review recipient list click \"Show Details...\"");
|
||||
detailed_text = formatted.join("\n\n");
|
||||
}
|
||||
|
||||
SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, this);
|
||||
confirmationDialog.exec();
|
||||
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
|
||||
|
||||
@ -954,11 +953,16 @@ void SendCoinsDialog::keepChangeAddressChanged(bool checked)
|
||||
fKeepChangeAddress = checked;
|
||||
}
|
||||
|
||||
SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
|
||||
QWidget *parent) :
|
||||
QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
|
||||
SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, QWidget* parent)
|
||||
: QMessageBox(parent), secDelay(_secDelay)
|
||||
{
|
||||
GUIUtil::updateFonts();
|
||||
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);
|
||||
setDefaultButton(QMessageBox::Cancel);
|
||||
yesButton = button(QMessageBox::Yes);
|
||||
updateYesButton();
|
||||
|
@ -114,6 +114,7 @@ class SendConfirmationDialog : public QMessageBox
|
||||
|
||||
public:
|
||||
SendConfirmationDialog(const QString &title, const QString &text, int secDelay = 0, QWidget *parent = nullptr);
|
||||
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = 0, QWidget* parent = nullptr);
|
||||
int exec() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -1564,7 +1564,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
|
||||
obj.pushKV("softforks", softforks);
|
||||
obj.pushKV("bip9_softforks", bip9_softforks);
|
||||
|
||||
obj.pushKV("warnings", GetWarnings("statusbar"));
|
||||
obj.pushKV("warnings", GetWarnings(false));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "sendtoaddress", 5, "use_is" },
|
||||
{ "sendtoaddress", 6, "use_cj" },
|
||||
{ "sendtoaddress", 7, "conf_target" },
|
||||
{ "sendtoaddress", 9, "avoid_reuse" },
|
||||
{ "settxfee", 0, "amount" },
|
||||
{ "getreceivedbyaddress", 1, "minconf" },
|
||||
{ "getreceivedbyaddress", 2, "addlocked" },
|
||||
@ -59,6 +60,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "getbalance", 1, "minconf" },
|
||||
{ "getbalance", 2, "addlocked" },
|
||||
{ "getbalance", 3, "include_watchonly" },
|
||||
{ "getbalance", 4, "avoid_reuse" },
|
||||
{ "getchaintips", 0, "count" },
|
||||
{ "getchaintips", 1, "branchlen" },
|
||||
{ "getblockhash", 0, "height" },
|
||||
@ -160,6 +162,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "setnetworkactive", 0, "state" },
|
||||
{ "setcoinjoinrounds", 0, "rounds" },
|
||||
{ "setcoinjoinamount", 0, "amount" },
|
||||
{ "setwalletflag", 1, "value" },
|
||||
{ "getmempoolancestors", 1, "verbose" },
|
||||
{ "getmempooldescendants", 1, "verbose" },
|
||||
{ "logging", 0, "include" },
|
||||
@ -195,6 +198,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "rescanblockchain", 1, "stop_height"},
|
||||
{ "createwallet", 1, "disable_private_keys"},
|
||||
{ "createwallet", 2, "blank"},
|
||||
{ "createwallet", 4, "avoid_reuse"},
|
||||
{ "upgradetohd", 3, "rescan"},
|
||||
{ "getnodeaddresses", 0, "count"},
|
||||
{ "stop", 0, "wait" },
|
||||
|
@ -443,7 +443,7 @@ static UniValue getmininginfo(const JSONRPCRequest& request)
|
||||
obj.pushKV("networkhashps", getnetworkhashps(request));
|
||||
obj.pushKV("pooledtx", (uint64_t)mempool.size());
|
||||
obj.pushKV("chain", Params().NetworkIDString());
|
||||
obj.pushKV("warnings", GetWarnings("statusbar"));
|
||||
obj.pushKV("warnings", GetWarnings(false));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -576,7 +576,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
|
||||
}
|
||||
}
|
||||
obj.pushKV("localaddresses", localAddresses);
|
||||
obj.pushKV("warnings", GetWarnings("statusbar"));
|
||||
obj.pushKV("warnings", GetWarnings(false));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -826,11 +826,9 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
|
||||
// TODO: temporary migration code for old clients. Remove in v0.20
|
||||
if (request.params[1].isBool()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
|
||||
} else if (request.params[1].isNum()) {
|
||||
} else if (!request.params[1].isNull()) {
|
||||
CFeeRate fr(AmountFromValue(request.params[1]));
|
||||
max_raw_tx_fee = fr.GetFee(GetVirtualTransactionSize(*tx));
|
||||
} else if (!request.params[1].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
|
||||
}
|
||||
|
||||
bool bypass_limits = false;
|
||||
@ -904,11 +902,9 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
||||
// TODO: temporary migration code for old clients. Remove in v0.20
|
||||
if (request.params[1].isBool()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
|
||||
} else if (request.params[1].isNum()) {
|
||||
} else if (!request.params[1].isNull()) {
|
||||
CFeeRate fr(AmountFromValue(request.params[1]));
|
||||
max_raw_tx_fee = fr.GetFee(GetVirtualTransactionSize(*tx));
|
||||
} else if (!request.params[1].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
|
||||
}
|
||||
|
||||
CTxMemPool& mempool = EnsureMemPool(request.context);
|
||||
|
@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(addtimedata)
|
||||
MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5
|
||||
}
|
||||
|
||||
BOOST_CHECK(GetWarnings("gui").find("clock is wrong") != std::string::npos);
|
||||
BOOST_CHECK(GetWarnings(true).find("clock is wrong") != std::string::npos);
|
||||
|
||||
// nTimeOffset is not changed if the median of offsets exceeds DEFAULT_MAX_TIME_ADJUSTMENT
|
||||
BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
|
||||
|
@ -13,6 +13,7 @@ void CCoinControl::SetNull(bool fResetCoinType)
|
||||
fAllowOtherInputs = false;
|
||||
fAllowWatchOnly = false;
|
||||
m_avoid_partial_spends = gArgs.GetBoolArg("-avoidpartialspends", DEFAULT_AVOIDPARTIALSPENDS);
|
||||
m_avoid_address_reuse = false;
|
||||
setSelected.clear();
|
||||
m_feerate.reset();
|
||||
fOverrideFeeRate = false;
|
||||
|
@ -46,6 +46,8 @@ public:
|
||||
Optional<unsigned int> m_confirm_target;
|
||||
//! Avoid partial use of funds sent to a given address
|
||||
bool m_avoid_partial_spends;
|
||||
//! Forbids inclusion of dirty (previously used) addresses
|
||||
bool m_avoid_address_reuse;
|
||||
//! Fee estimation mode to control arguments to estimateSmartFee
|
||||
FeeEstimateMode m_fee_mode;
|
||||
//! Minimum chain depth value for coin availability
|
||||
|
@ -44,7 +44,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit();
|
||||
|
||||
void WalletInit::AddWalletOptions(ArgsManager& argsman) const
|
||||
{
|
||||
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-createwalletbackups=<n>", strprintf("Number of automatic wallet backups (default: %u)", nWalletBackups), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
|
||||
#if HAVE_SYSTEM
|
||||
|
@ -20,7 +20,9 @@ enum isminetype : unsigned int
|
||||
ISMINE_NO = 0,
|
||||
ISMINE_WATCH_ONLY = 1 << 0,
|
||||
ISMINE_SPENDABLE = 1 << 1,
|
||||
ISMINE_USED = 1 << 2,
|
||||
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE,
|
||||
ISMINE_ALL_USED = ISMINE_ALL | ISMINE_USED,
|
||||
ISMINE_ENUM_ELEMENTS,
|
||||
};
|
||||
/** used for bitflags of isminetype */
|
||||
|
@ -49,6 +49,17 @@
|
||||
|
||||
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
||||
|
||||
static inline bool GetAvoidReuseFlag(CWallet * const pwallet, const UniValue& param) {
|
||||
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
|
||||
|
||||
if (avoid_reuse && !can_avoid_reuse) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "wallet does not have the \"avoid reuse\" feature enabled");
|
||||
}
|
||||
|
||||
return avoid_reuse;
|
||||
}
|
||||
|
||||
bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
|
||||
{
|
||||
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
|
||||
@ -280,7 +291,7 @@ static UniValue setlabel(const JSONRPCRequest& request)
|
||||
|
||||
static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
|
||||
{
|
||||
CAmount curBalance = pwallet->GetBalance().m_mine_trusted;
|
||||
CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
|
||||
|
||||
// Check amount
|
||||
if (nValue <= 0)
|
||||
@ -319,6 +330,10 @@ static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& a
|
||||
|
||||
static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
RPCHelpMan{"sendtoaddress",
|
||||
"\nSend an amount to a given address." +
|
||||
HelpRequiringPassphrase() + "\n",
|
||||
@ -339,6 +354,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||
" \"UNSET\"\n"
|
||||
" \"ECONOMICAL\"\n"
|
||||
" \"CONSERVATIVE\""},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ? "true" : "unavailable", "Avoid spending from dirty addresses; addresses are considered\n"
|
||||
" dirty if they have previously been used in a transaction."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR_HEX, "txid", "The transaction id."
|
||||
@ -351,10 +368,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
pwallet->BlockUntilSyncedToCurrentChain();
|
||||
@ -399,6 +412,10 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||
}
|
||||
}
|
||||
|
||||
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[9]);
|
||||
// We also enable partial spend avoidance if reuse avoidance is set.
|
||||
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
|
||||
@ -711,6 +728,10 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request)
|
||||
|
||||
static UniValue getbalance(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
RPCHelpMan{"getbalance",
|
||||
"\nReturns the total available balance.\n"
|
||||
"The available balance is what the wallet considers currently spendable, and is\n"
|
||||
@ -720,6 +741,7 @@ static UniValue getbalance(const JSONRPCRequest& request)
|
||||
{"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."},
|
||||
{"addlocked", RPCArg::Type::BOOL, /* default */ "false", "Whether to include transactions locked via InstantSend in the wallet's balance."},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ? "true" : "unavailable", "Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this wallet."
|
||||
@ -734,10 +756,6 @@ static UniValue getbalance(const JSONRPCRequest& request)
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
pwallet->BlockUntilSyncedToCurrentChain();
|
||||
@ -765,7 +783,8 @@ static UniValue getbalance(const JSONRPCRequest& request)
|
||||
include_watchonly = true;
|
||||
}
|
||||
|
||||
const auto bal = pwallet->GetBalance(min_depth, fAddLocked);
|
||||
bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[4]);
|
||||
const auto bal = pwallet->GetBalance(min_depth, avoid_reuse, fAddLocked);
|
||||
|
||||
return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
|
||||
}
|
||||
@ -2414,6 +2433,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
|
||||
{RPCResult::Type::NUM, "hdinternalkeyindex", "current internal childkey index"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
|
||||
{RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
|
||||
{
|
||||
{RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
|
||||
@ -2477,6 +2497,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
|
||||
}
|
||||
obj.pushKV("hdaccounts", accounts);
|
||||
}
|
||||
obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
|
||||
if (pwallet->IsScanning()) {
|
||||
UniValue scanning(UniValue::VOBJ);
|
||||
scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
|
||||
@ -2703,6 +2724,72 @@ static UniValue loadwallet(const JSONRPCRequest& request)
|
||||
return obj;
|
||||
}
|
||||
|
||||
static UniValue setwalletflag(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
std::string flags = "";
|
||||
for (auto& it : WALLET_FLAG_MAP)
|
||||
if (it.second & MUTABLE_WALLET_FLAGS)
|
||||
flags += (flags == "" ? "" : ", ") + it.first;
|
||||
|
||||
RPCHelpMan{"setwalletflag",
|
||||
"\nChange the state of the given wallet flag for a wallet.\n",
|
||||
{
|
||||
{"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
|
||||
{"value", RPCArg::Type::BOOL, /* default */ "true", "The new state."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
|
||||
{RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
|
||||
{RPCResult::Type::STR, "warnings", "Any warnings associated with the change"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("setwalletflag", "avoid_reuse")
|
||||
+ HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
std::string flag_str = request.params[0].get_str();
|
||||
bool value = request.params[1].isNull() || request.params[1].get_bool();
|
||||
|
||||
if (!WALLET_FLAG_MAP.count(flag_str)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
|
||||
}
|
||||
|
||||
auto flag = WALLET_FLAG_MAP.at(flag_str);
|
||||
|
||||
if (!(flag & MUTABLE_WALLET_FLAGS)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
|
||||
}
|
||||
|
||||
UniValue res(UniValue::VOBJ);
|
||||
|
||||
if (pwallet->IsWalletFlagSet(flag) == value) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
|
||||
}
|
||||
|
||||
res.pushKV("flag_name", flag_str);
|
||||
res.pushKV("flag_state", value);
|
||||
|
||||
if (value) {
|
||||
pwallet->SetWalletFlag(flag);
|
||||
} else {
|
||||
pwallet->UnsetWalletFlag(flag);
|
||||
}
|
||||
|
||||
if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
|
||||
res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static UniValue createwallet(const JSONRPCRequest& request)
|
||||
{
|
||||
RPCHelpMan{
|
||||
@ -2713,6 +2800,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
|
||||
{"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
|
||||
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
@ -2747,6 +2835,10 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.params[4].isNull() && request.params[4].get_bool()) {
|
||||
flags |= WALLET_FLAG_AVOID_REUSE;
|
||||
}
|
||||
|
||||
bilingual_str error;
|
||||
std::shared_ptr<CWallet> wallet;
|
||||
WalletCreationStatus status = CreateWallet(*context.chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet);
|
||||
@ -2810,6 +2902,11 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
|
||||
|
||||
static UniValue listunspent(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
RPCHelpMan{"listunspent",
|
||||
"\nReturns array of unspent transaction outputs\n"
|
||||
"with between minconf and maxconf (inclusive) confirmations.\n"
|
||||
@ -2852,6 +2949,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
{RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"},
|
||||
{RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"},
|
||||
{RPCResult::Type::STR, "desc", "(only when solvable) A descriptor for spending this output"},
|
||||
{RPCResult::Type::BOOL, "reused", /* optional*/ true, "Whether this output is reused/dirty (sent to an address that was previously spent from)"},
|
||||
{RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions"
|
||||
" from outside keys and unconfirmed replacement transactions are considered unsafe\n"
|
||||
"and are not eligible for spending by fundrawtransaction and sendtoaddress."},
|
||||
@ -2867,10 +2965,6 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (!request.params[0].isNull()) {
|
||||
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
|
||||
@ -2960,6 +3054,8 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
UniValue results(UniValue::VARR);
|
||||
std::vector<COutput> vecOutputs;
|
||||
{
|
||||
coinControl.m_avoid_address_reuse = false;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
pwallet->AvailableCoins(vecOutputs, !include_unsafe, &coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
|
||||
}
|
||||
@ -2970,6 +3066,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
CTxDestination address;
|
||||
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
|
||||
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
||||
bool reused = avoid_reuse && pwallet->IsUsedDestination(address);
|
||||
|
||||
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
||||
continue;
|
||||
@ -3004,6 +3101,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
||||
auto descriptor = InferDescriptor(scriptPubKey, *pwallet);
|
||||
entry.pushKV("desc", descriptor->ToString());
|
||||
}
|
||||
if (avoid_reuse) entry.pushKV("reused", reused);
|
||||
entry.pushKV("safe", out.fSafe);
|
||||
entry.pushKV("coinjoin_rounds", pwallet->GetRealOutpointCoinJoinRounds(COutPoint(out.tx->GetHash(), out.i)));
|
||||
results.push_back(entry);
|
||||
@ -3872,14 +3970,14 @@ static const CRPCCommand commands[] =
|
||||
{ "wallet", "abortrescan", &abortrescan, {} },
|
||||
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label"} },
|
||||
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
|
||||
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} },
|
||||
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse"} },
|
||||
{ "wallet", "dumphdinfo", &dumphdinfo, {} },
|
||||
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
|
||||
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
|
||||
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
|
||||
{ "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
|
||||
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
|
||||
{ "wallet", "getbalance", &getbalance, {"dummy","minconf","addlocked","include_watchonly"} },
|
||||
{ "wallet", "getbalance", &getbalance, {"dummy","minconf","addlocked","include_watchonly", "avoid_reuse"} },
|
||||
{ "wallet", "getnewaddress", &getnewaddress, {"label"} },
|
||||
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {} },
|
||||
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf","addlocked"} },
|
||||
@ -3911,11 +4009,12 @@ static const CRPCCommand commands[] =
|
||||
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
||||
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","addlocked","comment","subtractfeefrom","use_is","use_cj","conf_target","estimate_mode"} },
|
||||
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","use_is","use_cj","conf_target","estimate_mode"} },
|
||||
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","use_is","use_cj","conf_target","estimate_mode", "avoid_reuse"} },
|
||||
{ "wallet", "setcoinjoinrounds", &setcoinjoinrounds, {"rounds"} },
|
||||
{ "wallet", "setcoinjoinamount", &setcoinjoinamount, {"amount"} },
|
||||
{ "wallet", "setlabel", &setlabel, {"address","label"} },
|
||||
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
||||
{ "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
|
||||
{ "wallet", "signmessage", &signmessage, {"address","message"} },
|
||||
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
|
||||
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
|
||||
|
@ -53,6 +53,14 @@
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
|
||||
{WALLET_FLAG_AVOID_REUSE,
|
||||
"You need to rescan the blockchain in order to correctly mark used "
|
||||
"destinations in the past. Until this is done, some destinations may "
|
||||
"be considered unused, even if the opposite is the case."
|
||||
},
|
||||
};
|
||||
|
||||
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
|
||||
|
||||
static CCriticalSection cs_wallets;
|
||||
@ -1178,6 +1186,37 @@ void CWallet::MarkDirty()
|
||||
fAnonymizableTallyCachedNonDenom = false;
|
||||
}
|
||||
|
||||
void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool used)
|
||||
{
|
||||
const CWalletTx* srctx = GetWalletTx(hash);
|
||||
if (!srctx) return;
|
||||
|
||||
CTxDestination dst;
|
||||
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
|
||||
if (::IsMine(*this, dst)) {
|
||||
LOCK(cs_wallet);
|
||||
if (used && !GetDestData(dst, "used", nullptr)) {
|
||||
AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null)
|
||||
} else if (!used && GetDestData(dst, "used", nullptr)) {
|
||||
EraseDestData(dst, "used");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CWallet::IsUsedDestination(const CTxDestination& dst) const
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr);
|
||||
}
|
||||
|
||||
bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const
|
||||
{
|
||||
CTxDestination dst;
|
||||
const CWalletTx* srctx = GetWalletTx(hash);
|
||||
return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst);
|
||||
}
|
||||
|
||||
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
@ -1186,6 +1225,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||
|
||||
uint256 hash = wtxIn.GetHash();
|
||||
|
||||
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
|
||||
// Mark used destinations
|
||||
for (const CTxIn& txin : wtxIn.tx->vin) {
|
||||
const COutPoint& op = txin.prevout;
|
||||
SetUsedDestinationState(op.hash, op.n, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts only if not already there, returns tx inserted or tx found
|
||||
std::pair<std::map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(std::make_pair(hash, wtxIn));
|
||||
CWalletTx& wtx = (*ret.first).second;
|
||||
@ -2122,7 +2169,7 @@ void CWallet::UnsetWalletFlag(WalletBatch& batch, uint64_t flag)
|
||||
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
|
||||
}
|
||||
|
||||
bool CWallet::IsWalletFlagSet(uint64_t flag)
|
||||
bool CWallet::IsWalletFlagSet(uint64_t flag) const
|
||||
{
|
||||
return (m_wallet_flags & flag);
|
||||
}
|
||||
@ -2131,7 +2178,7 @@ bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
m_wallet_flags = overwriteFlags;
|
||||
if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) {
|
||||
if (((overwriteFlags & KNOWN_WALLET_FLAGS) >> 32) ^ (overwriteFlags >> 32)) {
|
||||
// contains unknown non-tolerable wallet flags
|
||||
return false;
|
||||
}
|
||||
@ -2631,7 +2678,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter
|
||||
return 0;
|
||||
|
||||
// Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
|
||||
bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY;
|
||||
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
|
||||
|
||||
// Must wait until coinbase is safely deep enough in the chain before valuing it
|
||||
if (IsImmatureCoinBase())
|
||||
@ -2641,11 +2688,12 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter
|
||||
return m_amounts[AVAILABLE_CREDIT].m_value[filter];
|
||||
}
|
||||
|
||||
bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
CAmount nCredit = 0;
|
||||
uint256 hashTx = GetHash();
|
||||
for (unsigned int i = 0; i < tx->vout.size(); i++)
|
||||
{
|
||||
if (!pwallet->IsSpent(hashTx, i))
|
||||
if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i)))
|
||||
{
|
||||
const CTxOut &txout = tx->vout[i];
|
||||
nCredit += pwallet->GetCredit(txout, filter);
|
||||
@ -2899,16 +2947,17 @@ std::unordered_set<const CWalletTx*, WalletTxHasher> CWallet::GetSpendableTXs()
|
||||
return ret;
|
||||
}
|
||||
|
||||
CWallet::Balance CWallet::GetBalance(const int min_depth, const bool fAddLocked, const CCoinControl* coinControl) const
|
||||
CWallet::Balance CWallet::GetBalance(const int min_depth, const bool avoid_reuse, const bool fAddLocked, const CCoinControl* coinControl) const
|
||||
{
|
||||
Balance ret;
|
||||
isminefilter reuse_filter = avoid_reuse ? 0 : ISMINE_USED;
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
for (auto pcoin : GetSpendableTXs()) {
|
||||
const bool is_trusted{pcoin->IsTrusted()};
|
||||
const int tx_depth{pcoin->GetDepthInMainChain()};
|
||||
const CAmount tx_credit_mine{pcoin->GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE)};
|
||||
const CAmount tx_credit_watchonly{pcoin->GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY)};
|
||||
const CAmount tx_credit_mine{pcoin->GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
|
||||
const CAmount tx_credit_watchonly{pcoin->GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
|
||||
if (is_trusted && ((tx_depth >= min_depth) || (fAddLocked && pcoin->IsLockedByInstantSend()))) {
|
||||
ret.m_mine_trusted += tx_credit_mine;
|
||||
ret.m_watchonly_trusted += tx_credit_watchonly;
|
||||
@ -3020,6 +3069,9 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
|
||||
CoinType nCoinType = coinControl ? coinControl->nCoinType : CoinType::ALL_COINS;
|
||||
|
||||
CAmount nTotal = 0;
|
||||
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
|
||||
// a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
|
||||
bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
|
||||
|
||||
for (auto pcoin : GetSpendableTXs()) {
|
||||
const uint256& wtxid = pcoin->GetHash();
|
||||
@ -3084,6 +3136,9 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allow_used_addresses && IsUsedDestination(wtxid, i)) {
|
||||
continue;
|
||||
}
|
||||
bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey);
|
||||
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
|
||||
|
||||
@ -5071,12 +5126,8 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
|
||||
if (fFirstRun)
|
||||
{
|
||||
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
//selective allow to set flags
|
||||
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
} else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) {
|
||||
walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET);
|
||||
} else {
|
||||
walletInstance->SetWalletFlags(wallet_creation_flags, false);
|
||||
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
|
||||
// Create new HD chain
|
||||
if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
|
||||
std::string strSeed = gArgs.GetArg("-hdseed", "not hex");
|
||||
|
@ -132,6 +132,10 @@ enum WalletFlags : uint64_t {
|
||||
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
|
||||
// unknown wallet flags in the lower section <= (1 << 31) will be tolerated
|
||||
|
||||
// will categorize coins as clean (not reused) and dirty (reused), and handle
|
||||
// them with privacy considerations in mind
|
||||
WALLET_FLAG_AVOID_REUSE = (1ULL << 0),
|
||||
|
||||
// Indicates that the metadata has already been upgraded to contain key origins
|
||||
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
|
||||
|
||||
@ -151,7 +155,23 @@ enum WalletFlags : uint64_t {
|
||||
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
|
||||
};
|
||||
|
||||
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA;
|
||||
static constexpr uint64_t KNOWN_WALLET_FLAGS =
|
||||
WALLET_FLAG_AVOID_REUSE
|
||||
| WALLET_FLAG_BLANK_WALLET
|
||||
| WALLET_FLAG_KEY_ORIGIN_METADATA
|
||||
| WALLET_FLAG_DISABLE_PRIVATE_KEYS;
|
||||
|
||||
static constexpr uint64_t MUTABLE_WALLET_FLAGS =
|
||||
WALLET_FLAG_AVOID_REUSE;
|
||||
|
||||
static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
|
||||
{"avoid_reuse", WALLET_FLAG_AVOID_REUSE},
|
||||
{"blank", WALLET_FLAG_BLANK_WALLET},
|
||||
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
|
||||
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
|
||||
};
|
||||
|
||||
extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS;
|
||||
|
||||
/** A key from a CWallet's keypool
|
||||
*
|
||||
@ -1005,6 +1025,12 @@ public:
|
||||
bool IsFullyMixed(const COutPoint& outpoint) const;
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
// Whether this or any UTXO with the same CTxDestination has been spent.
|
||||
bool IsUsedDestination(const CTxDestination& dst) const;
|
||||
bool IsUsedDestination(const uint256& hash, unsigned int n) const;
|
||||
void SetUsedDestinationState(const uint256& hash, unsigned int n, bool used);
|
||||
|
||||
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
@ -1144,7 +1170,7 @@ public:
|
||||
CAmount m_denominated_trusted{0};
|
||||
CAmount m_denominated_untrusted_pending{0};
|
||||
};
|
||||
Balance GetBalance(int min_depth = 0, const bool fAddLocked = false, const CCoinControl* coinControl = nullptr) const;
|
||||
Balance GetBalance(const int min_depth = 0, const bool avoid_reuse = true, const bool fAddLocked = false, const CCoinControl* coinControl = nullptr) const;
|
||||
|
||||
CAmount GetAnonymizableBalance(bool fSkipDenominated = false, bool fSkipUnconfirmed = true) const;
|
||||
float GetAverageAnonymizedRounds() const;
|
||||
@ -1420,7 +1446,7 @@ public:
|
||||
void UnsetWalletFlag(WalletBatch& batch, uint64_t flag);
|
||||
|
||||
/** check if a certain wallet flag is set */
|
||||
bool IsWalletFlagSet(uint64_t flag);
|
||||
bool IsWalletFlagSet(uint64_t flag) const;
|
||||
|
||||
/** overwrite all flags by the given uint64_t
|
||||
returns false if unknown, non-tolerable flags are present */
|
||||
|
@ -39,41 +39,34 @@ void SetfLargeWorkInvalidChainFound(bool flag)
|
||||
fLargeWorkInvalidChainFound = flag;
|
||||
}
|
||||
|
||||
std::string GetWarnings(const std::string& strFor)
|
||||
std::string GetWarnings(bool verbose)
|
||||
{
|
||||
std::string strStatusBar;
|
||||
std::string strGUI;
|
||||
const std::string uiAlertSeparator = "<hr />";
|
||||
std::string warnings_concise;
|
||||
std::string warnings_verbose;
|
||||
const std::string warning_separator = "<hr />";
|
||||
|
||||
LOCK(g_warnings_mutex);
|
||||
|
||||
// Pre-release build warning
|
||||
if (!CLIENT_VERSION_IS_RELEASE) {
|
||||
strStatusBar = "This is a pre-release test build - use at your own risk - do not use for mining or merchant applications";
|
||||
strGUI = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications").translated;
|
||||
warnings_concise = "This is a pre-release test build - use at your own risk - do not use for mining or merchant applications";
|
||||
warnings_verbose = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications").translated;
|
||||
}
|
||||
|
||||
// Misc warnings like out of disk space and clock is wrong
|
||||
if (strMiscWarning != "")
|
||||
{
|
||||
strStatusBar = strMiscWarning;
|
||||
strGUI += (strGUI.empty() ? "" : uiAlertSeparator) + strMiscWarning;
|
||||
if (strMiscWarning != "") {
|
||||
warnings_concise = strMiscWarning;
|
||||
warnings_verbose += (warnings_verbose.empty() ? "" : warning_separator) + strMiscWarning;
|
||||
}
|
||||
|
||||
if (fLargeWorkForkFound)
|
||||
{
|
||||
strStatusBar = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.";
|
||||
strGUI += (strGUI.empty() ? "" : uiAlertSeparator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.").translated;
|
||||
}
|
||||
else if (fLargeWorkInvalidChainFound)
|
||||
{
|
||||
strStatusBar = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.";
|
||||
strGUI += (strGUI.empty() ? "" : uiAlertSeparator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.").translated;
|
||||
if (fLargeWorkForkFound) {
|
||||
warnings_concise = "Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.";
|
||||
warnings_verbose += (warnings_verbose.empty() ? "" : warning_separator) + _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.").translated;
|
||||
} else if (fLargeWorkInvalidChainFound) {
|
||||
warnings_concise = "Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.";
|
||||
warnings_verbose += (warnings_verbose.empty() ? "" : warning_separator) + _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.").translated;
|
||||
}
|
||||
|
||||
if (strFor == "gui")
|
||||
return strGUI;
|
||||
else if (strFor == "statusbar")
|
||||
return strStatusBar;
|
||||
assert(!"GetWarnings(): invalid parameter");
|
||||
return "error";
|
||||
if (verbose) return warnings_verbose;
|
||||
else return warnings_concise;
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ void SetfLargeWorkForkFound(bool flag);
|
||||
bool GetfLargeWorkForkFound();
|
||||
void SetfLargeWorkInvalidChainFound(bool flag);
|
||||
/** Format a string that describes several potential problems detected by the core.
|
||||
* @param[in] strFor can have the following values:
|
||||
* - "statusbar": get the most important warning
|
||||
* - "gui": get all warnings, translated (where possible) for GUI, separated by <hr />
|
||||
* @returns the warning string selected by strFor
|
||||
* @param[in] verbose bool
|
||||
* - if true, get all warnings, translated (where possible), separated by <hr />
|
||||
* - if false, get the most important warning
|
||||
* @returns the warning string
|
||||
*/
|
||||
std::string GetWarnings(const std::string& strFor);
|
||||
std::string GetWarnings(bool verbose);
|
||||
|
||||
#endif // BITCOIN_WARNINGS_H
|
||||
|
@ -389,9 +389,9 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||
# and sendrawtransaction should throw
|
||||
assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000)
|
||||
# And below calls should both succeed
|
||||
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate=0.00007000)[0]
|
||||
testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.00007000')[0]
|
||||
assert_equal(testres['allowed'], True)
|
||||
self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate=0.00007000)
|
||||
self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.00007000')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -141,6 +141,7 @@ BASE_SCRIPTS = [
|
||||
'rpc_misc.py',
|
||||
'interface_rest.py',
|
||||
'mempool_spend_coinbase.py',
|
||||
'wallet_avoidreuse.py',
|
||||
'mempool_reorg.py',
|
||||
'mempool_persist.py',
|
||||
'wallet_multiwallet.py',
|
||||
|
217
test/functional/wallet_avoidreuse.py
Executable file
217
test/functional/wallet_avoidreuse.py
Executable file
@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 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.
|
||||
"""Test the avoid_reuse and setwalletflag features."""
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
connect_nodes,
|
||||
)
|
||||
|
||||
# TODO: Copied from wallet_groups.py -- should perhaps move into util.py
|
||||
def assert_approx(v, vexp, vspan=0.00001):
|
||||
if v < vexp - vspan:
|
||||
raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
|
||||
if v > vexp + vspan:
|
||||
raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
|
||||
|
||||
def reset_balance(node, discardaddr):
|
||||
'''Throw away all owned coins by the node so it gets a balance of 0.'''
|
||||
balance = node.getbalance(avoid_reuse=False)
|
||||
if balance > 0.5:
|
||||
node.sendtoaddress(address=discardaddr, amount=balance, subtractfeefromamount=True, avoid_reuse=False)
|
||||
|
||||
def count_unspent(node):
|
||||
'''Count the unspent outputs for the given node and return various statistics'''
|
||||
r = {
|
||||
"total": {
|
||||
"count": 0,
|
||||
"sum": 0,
|
||||
},
|
||||
"reused": {
|
||||
"count": 0,
|
||||
"sum": 0,
|
||||
},
|
||||
}
|
||||
supports_reused = True
|
||||
for utxo in node.listunspent(minconf=0):
|
||||
r["total"]["count"] += 1
|
||||
r["total"]["sum"] += utxo["amount"]
|
||||
if supports_reused and "reused" in utxo:
|
||||
if utxo["reused"]:
|
||||
r["reused"]["count"] += 1
|
||||
r["reused"]["sum"] += utxo["amount"]
|
||||
else:
|
||||
supports_reused = False
|
||||
r["reused"]["supported"] = supports_reused
|
||||
return r
|
||||
|
||||
def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None):
|
||||
'''Make assertions about a node's unspent output statistics'''
|
||||
stats = count_unspent(node)
|
||||
if total_count is not None:
|
||||
assert_equal(stats["total"]["count"], total_count)
|
||||
if total_sum is not None:
|
||||
assert_approx(stats["total"]["sum"], total_sum, 0.001)
|
||||
if reused_supported is not None:
|
||||
assert_equal(stats["reused"]["supported"], reused_supported)
|
||||
if reused_count is not None:
|
||||
assert_equal(stats["reused"]["count"], reused_count)
|
||||
if reused_sum is not None:
|
||||
assert_approx(stats["reused"]["sum"], reused_sum, 0.001)
|
||||
|
||||
class AvoidReuseTest(BitcoinTestFramework):
|
||||
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = False
|
||||
self.num_nodes = 2
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
'''Set up initial chain and run tests defined below'''
|
||||
|
||||
self.test_persistence()
|
||||
self.test_immutable()
|
||||
|
||||
self.nodes[0].generate(110)
|
||||
self.sync_all()
|
||||
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
|
||||
self.test_fund_send_fund_senddirty()
|
||||
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
|
||||
self.test_fund_send_fund_send()
|
||||
|
||||
def test_persistence(self):
|
||||
'''Test that wallet files persist the avoid_reuse flag.'''
|
||||
# Configure node 1 to use avoid_reuse
|
||||
self.nodes[1].setwalletflag('avoid_reuse')
|
||||
|
||||
# Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true
|
||||
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
|
||||
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
|
||||
|
||||
# Stop and restart node 1
|
||||
self.stop_node(1)
|
||||
self.start_node(1)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
|
||||
# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
|
||||
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
|
||||
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
|
||||
|
||||
# Attempting to set flag to its current state should throw
|
||||
assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False)
|
||||
assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True)
|
||||
|
||||
def test_immutable(self):
|
||||
'''Test immutable wallet flags'''
|
||||
# Attempt to set the disable_private_keys flag; this should not work
|
||||
assert_raises_rpc_error(-8, "Wallet flag is immutable", self.nodes[1].setwalletflag, 'disable_private_keys')
|
||||
|
||||
tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"
|
||||
|
||||
# Create a wallet with disable_private_keys set; this should work
|
||||
self.nodes[1].createwallet(tempwallet, True)
|
||||
w = self.nodes[1].get_wallet_rpc(tempwallet)
|
||||
|
||||
# Attempt to unset the disable_private_keys flag; this should not work
|
||||
assert_raises_rpc_error(-8, "Wallet flag is immutable", w.setwalletflag, 'disable_private_keys', False)
|
||||
|
||||
# Unload temp wallet
|
||||
self.nodes[1].unloadwallet(tempwallet)
|
||||
|
||||
def test_fund_send_fund_senddirty(self):
|
||||
'''
|
||||
Test the same as test_fund_send_fund_send, except send the 10 BTC with
|
||||
the avoid_reuse flag set to false. This means the 10 BTC send should succeed,
|
||||
where it fails in test_fund_send_fund_send.
|
||||
'''
|
||||
|
||||
fundaddr = self.nodes[1].getnewaddress()
|
||||
retaddr = self.nodes[0].getnewaddress()
|
||||
|
||||
self.nodes[0].sendtoaddress(fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 1 single, unused 10 btc output
|
||||
assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
|
||||
|
||||
self.nodes[1].sendtoaddress(retaddr, 5)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 1 single, unused 5 btc output
|
||||
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
|
||||
|
||||
self.nodes[0].sendtoaddress(fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
|
||||
|
||||
self.nodes[1].sendtoaddress(address=retaddr, amount=10, avoid_reuse=False)
|
||||
|
||||
# listunspent should show 1 total outputs (5 btc), unused
|
||||
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_count=0)
|
||||
|
||||
# node 1 should now have about 5 btc left (for both cases)
|
||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)
|
||||
|
||||
def test_fund_send_fund_send(self):
|
||||
'''
|
||||
Test the simple case where [1] generates a new address A, then
|
||||
[0] sends 10 BTC to A.
|
||||
[1] spends 5 BTC from A. (leaving roughly 5 BTC useable)
|
||||
[0] sends 10 BTC to A again.
|
||||
[1] tries to spend 10 BTC (fails; dirty).
|
||||
[1] tries to spend 4 BTC (succeeds; change address sufficient)
|
||||
'''
|
||||
|
||||
fundaddr = self.nodes[1].getnewaddress()
|
||||
retaddr = self.nodes[0].getnewaddress()
|
||||
|
||||
self.nodes[0].sendtoaddress(fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 1 single, unused 10 btc output
|
||||
assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
|
||||
|
||||
self.nodes[1].sendtoaddress(retaddr, 5)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 1 single, unused 5 btc output
|
||||
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
|
||||
|
||||
self.nodes[0].sendtoaddress(fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
|
||||
|
||||
# node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
|
||||
|
||||
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
|
||||
|
||||
self.nodes[1].sendtoaddress(retaddr, 4)
|
||||
|
||||
# listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
|
||||
|
||||
# node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 1, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
|
||||
|
||||
if __name__ == '__main__':
|
||||
AvoidReuseTest().main()
|
@ -79,6 +79,9 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].getbalance("*"), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1, True), 500)
|
||||
assert_equal(self.nodes[0].getbalance("*", 1, True, False), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1, addlocked=True), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1, avoid_reuse=False), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=1), 500)
|
||||
assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 1000)
|
||||
assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 500)
|
||||
|
Loading…
Reference in New Issue
Block a user