diff --git a/doc/release-notes.md b/doc/release-notes.md
index c2f5e5d21..145a08e85 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,5 +1,13 @@
Bitcoin ABC version 0.21.9 is now available from:
This release includes the following features and fixes:
+- Improve management of maxfee by the wallet.
+
+Wallet changes
+--------------
+When creating a transaction with a fee above `-maxtxfee` (default 0.1 BCH),
+the RPC commands `walletcreatefundedpsbt` and `fundrawtransaction` will now fail
+instead of rounding down the fee. Beware that the `feeRate` argument is specified
+in BCH per kilobyte, not satoshi per byte.
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index c5b1fc98f..aa75d74e7 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -1,535 +1,533 @@
// Copyright (c) 2011-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for GetBoolArg
#include
#include
#include
#include
#include
#include
WalletModel::WalletModel(std::unique_ptr wallet,
interfaces::Node &node,
const PlatformStyle *platformStyle,
OptionsModel *_optionsModel, QObject *parent)
: QObject(parent), m_wallet(std::move(wallet)), m_node(node),
optionsModel(_optionsModel), addressTableModel(nullptr),
transactionTableModel(nullptr), recentRequestsTableModel(nullptr),
cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) {
fHaveWatchOnly = m_wallet->haveWatchOnly();
addressTableModel = new AddressTableModel(this);
transactionTableModel = new TransactionTableModel(platformStyle, this);
recentRequestsTableModel = new RecentRequestsTableModel(this);
// This timer will be fired repeatedly to update the balance
pollTimer = new QTimer(this);
connect(pollTimer, &QTimer::timeout, this,
&WalletModel::pollBalanceChanged);
pollTimer->start(MODEL_UPDATE_DELAY);
subscribeToCoreSignals();
}
WalletModel::~WalletModel() {
unsubscribeFromCoreSignals();
}
void WalletModel::updateStatus() {
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
if (cachedEncryptionStatus != newEncryptionStatus) {
Q_EMIT encryptionStatusChanged();
}
}
void WalletModel::pollBalanceChanged() {
// Try to get balances and return early if locks can't be acquired. This
// avoids the GUI from getting stuck on periodical polls if the core is
// holding the locks for a longer time - for example, during a wallet
// rescan.
interfaces::WalletBalances new_balances;
int numBlocks = -1;
if (!m_wallet->tryGetBalances(new_balances, numBlocks)) {
return;
}
if (fForceCheckBalanceChanged || m_node.getNumBlocks() != cachedNumBlocks) {
fForceCheckBalanceChanged = false;
// Balance and number of transactions might have changed
cachedNumBlocks = m_node.getNumBlocks();
checkBalanceChanged(new_balances);
if (transactionTableModel) {
transactionTableModel->updateConfirmations();
}
}
}
void WalletModel::checkBalanceChanged(
const interfaces::WalletBalances &new_balances) {
if (new_balances.balanceChanged(m_cached_balances)) {
m_cached_balances = new_balances;
Q_EMIT balanceChanged(new_balances);
}
}
void WalletModel::updateTransaction() {
// Balance and number of transactions might have changed
fForceCheckBalanceChanged = true;
}
void WalletModel::updateAddressBook(const QString &address,
const QString &label, bool isMine,
const QString &purpose, int status) {
if (addressTableModel) {
addressTableModel->updateEntry(address, label, isMine, purpose, status);
}
}
void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) {
fHaveWatchOnly = fHaveWatchonly;
Q_EMIT notifyWatchonlyChanged(fHaveWatchonly);
}
bool WalletModel::validateAddress(const QString &address) {
return IsValidDestinationString(address.toStdString(), getChainParams());
}
WalletModel::SendCoinsReturn
WalletModel::prepareTransaction(WalletModelTransaction &transaction,
const CCoinControl &coinControl) {
Amount total = Amount::zero();
bool fSubtractFeeFromAmount = false;
QList recipients = transaction.getRecipients();
std::vector vecSend;
if (recipients.empty()) {
return OK;
}
// Used to detect duplicates
QSet setAddress;
int nAddresses = 0;
// Pre-check input data for validity
for (const SendCoinsRecipient &rcp : recipients) {
if (rcp.fSubtractFeeFromAmount) {
fSubtractFeeFromAmount = true;
}
#ifdef ENABLE_BIP70
// PaymentRequest...
if (rcp.paymentRequest.IsInitialized()) {
Amount subtotal = Amount::zero();
const payments::PaymentDetails &details =
rcp.paymentRequest.getDetails();
for (int i = 0; i < details.outputs_size(); i++) {
const payments::Output &out = details.outputs(i);
if (out.amount() <= 0) {
continue;
}
subtotal += int64_t(out.amount()) * SATOSHI;
const uint8_t *scriptStr = (const uint8_t *)out.script().data();
CScript scriptPubKey(scriptStr,
scriptStr + out.script().size());
Amount nAmount = int64_t(out.amount()) * SATOSHI;
CRecipient recipient = {scriptPubKey, nAmount,
rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
if (subtotal <= Amount::zero()) {
return InvalidAmount;
}
total += subtotal;
}
// User-entered bitcoin address / amount:
else
#endif
{
if (!validateAddress(rcp.address)) {
return InvalidAddress;
}
if (rcp.amount <= Amount::zero()) {
return InvalidAmount;
}
setAddress.insert(rcp.address);
++nAddresses;
CScript scriptPubKey = GetScriptForDestination(
DecodeDestination(rcp.address.toStdString(), getChainParams()));
CRecipient recipient = {scriptPubKey, Amount(rcp.amount),
rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
total += rcp.amount;
}
}
if (setAddress.size() != nAddresses) {
return DuplicateAddress;
}
Amount nBalance = m_wallet->getAvailableBalance(coinControl);
if (total > nBalance) {
return AmountExceedsBalance;
}
Amount nFeeRequired = Amount::zero();
int nChangePosRet = -1;
std::string strFailReason;
auto &newTx = transaction.getWtx();
newTx =
m_wallet->createTransaction(vecSend, coinControl, true /* sign */,
nChangePosRet, nFeeRequired, strFailReason);
transaction.setTransactionFee(nFeeRequired);
if (fSubtractFeeFromAmount && newTx) {
transaction.reassignAmounts(nChangePosRet);
}
if (!newTx) {
if (!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) {
return SendCoinsReturn(AmountWithFeeExceedsBalance);
}
Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
- // reject absurdly high fee. (This can never happen because the wallet caps
- // the fee at m_default_max_tx_fee. This merely serves as a
- // belt-and-suspenders check)
+ // Reject absurdly high fee
if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
return AbsurdFee;
}
return SendCoinsReturn(OK);
}
WalletModel::SendCoinsReturn
WalletModel::sendCoins(WalletModelTransaction &transaction) {
/* store serialized transaction */
QByteArray transaction_array;
std::vector> vOrderForm;
for (const SendCoinsRecipient &rcp : transaction.getRecipients()) {
#ifdef ENABLE_BIP70
if (rcp.paymentRequest.IsInitialized()) {
// Make sure any payment requests involved are still valid.
if (PaymentServer::verifyExpired(rcp.paymentRequest.getDetails())) {
return PaymentRequestExpired;
}
// Store PaymentRequests in wtx.vOrderForm in wallet.
std::string value;
rcp.paymentRequest.SerializeToString(&value);
vOrderForm.emplace_back("PaymentRequest", std::move(value));
} else
#endif
{
if (!rcp.message.isEmpty()) {
// Message from normal bitcoincash:URI
// (bitcoincash:123...?message=example)
vOrderForm.emplace_back("Message", rcp.message.toStdString());
}
}
}
auto &newTx = transaction.getWtx();
std::string rejectReason;
if (!newTx->commit({} /* mapValue */, std::move(vOrderForm),
rejectReason)) {
return SendCoinsReturn(TransactionCommitFailed,
QString::fromStdString(rejectReason));
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << newTx->get();
transaction_array.append(&(ssTx[0]), ssTx.size());
// Add addresses / update labels that we've sent to the address book, and
// emit coinsSent signal for each recipient
for (const SendCoinsRecipient &rcp : transaction.getRecipients()) {
// Don't touch the address book when we have a payment request
#ifdef ENABLE_BIP70
if (!rcp.paymentRequest.IsInitialized())
#endif
{
std::string strAddress = rcp.address.toStdString();
CTxDestination dest =
DecodeDestination(strAddress, getChainParams());
std::string strLabel = rcp.label.toStdString();
// Check if we have a new address or an updated label
std::string name;
if (!m_wallet->getAddress(dest, &name, /* is_mine= */ nullptr,
/* purpose= */ nullptr)) {
m_wallet->setAddressBook(dest, strLabel, "send");
} else if (name != strLabel) {
// "" means don't change purpose
m_wallet->setAddressBook(dest, strLabel, "");
}
}
Q_EMIT coinsSent(this, rcp, transaction_array);
}
// update balance immediately, otherwise there could be a short noticeable
// delay until pollBalanceChanged hits
checkBalanceChanged(m_wallet->getBalances());
return SendCoinsReturn(OK);
}
OptionsModel *WalletModel::getOptionsModel() {
return optionsModel;
}
AddressTableModel *WalletModel::getAddressTableModel() {
return addressTableModel;
}
TransactionTableModel *WalletModel::getTransactionTableModel() {
return transactionTableModel;
}
RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() {
return recentRequestsTableModel;
}
WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const {
if (!m_wallet->isCrypted()) {
return Unencrypted;
} else if (m_wallet->isLocked()) {
return Locked;
} else {
return Unlocked;
}
}
bool WalletModel::setWalletEncrypted(bool encrypted,
const SecureString &passphrase) {
if (encrypted) {
// Encrypt
return m_wallet->encryptWallet(passphrase);
} else {
// Decrypt -- TODO; not supported yet
return false;
}
}
bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) {
if (locked) {
// Lock
return m_wallet->lock();
} else {
// Unlock
return m_wallet->unlock(passPhrase);
}
}
bool WalletModel::changePassphrase(const SecureString &oldPass,
const SecureString &newPass) {
// Make sure wallet is locked before attempting pass change
m_wallet->lock();
return m_wallet->changeWalletPassphrase(oldPass, newPass);
}
// Handlers for core signals
static void NotifyUnload(WalletModel *walletModel) {
qDebug() << "NotifyUnload";
bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
assert(invoked);
}
static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) {
qDebug() << "NotifyKeyStoreStatusChanged";
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus",
Qt::QueuedConnection);
assert(invoked);
}
static void NotifyAddressBookChanged(WalletModel *walletmodel,
const CTxDestination &address,
const std::string &label, bool isMine,
const std::string &purpose,
ChangeType status) {
QString strAddress = QString::fromStdString(
EncodeCashAddr(address, walletmodel->getChainParams()));
QString strLabel = QString::fromStdString(label);
QString strPurpose = QString::fromStdString(purpose);
qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel +
" isMine=" + QString::number(isMine) +
" purpose=" + strPurpose +
" status=" + QString::number(status);
bool invoked = QMetaObject::invokeMethod(
walletmodel, "updateAddressBook", Qt::QueuedConnection,
Q_ARG(QString, strAddress), Q_ARG(QString, strLabel),
Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status));
assert(invoked);
}
static void NotifyTransactionChanged(WalletModel *walletmodel, const TxId &hash,
ChangeType status) {
Q_UNUSED(hash);
Q_UNUSED(status);
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction",
Qt::QueuedConnection);
assert(invoked);
}
static void ShowProgress(WalletModel *walletmodel, const std::string &title,
int nProgress) {
// emits signal "showProgress"
bool invoked = QMetaObject::invokeMethod(
walletmodel, "showProgress", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress));
assert(invoked);
}
static void NotifyWatchonlyChanged(WalletModel *walletmodel,
bool fHaveWatchonly) {
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag",
Qt::QueuedConnection,
Q_ARG(bool, fHaveWatchonly));
assert(invoked);
}
static void NotifyCanGetAddressesChanged(WalletModel *walletmodel) {
bool invoked =
QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged");
assert(invoked);
}
void WalletModel::subscribeToCoreSignals() {
// Connect signals to wallet
m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this));
m_handler_status_changed = m_wallet->handleStatusChanged(
std::bind(&NotifyKeyStoreStatusChanged, this));
m_handler_address_book_changed = m_wallet->handleAddressBookChanged(
std::bind(NotifyAddressBookChanged, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5));
m_handler_transaction_changed = m_wallet->handleTransactionChanged(
std::bind(NotifyTransactionChanged, this, std::placeholders::_1,
std::placeholders::_2));
m_handler_show_progress = m_wallet->handleShowProgress(std::bind(
ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(
std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1));
m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(
std::bind(NotifyCanGetAddressesChanged, this));
}
void WalletModel::unsubscribeFromCoreSignals() {
// Disconnect signals from wallet
m_handler_unload->disconnect();
m_handler_status_changed->disconnect();
m_handler_address_book_changed->disconnect();
m_handler_transaction_changed->disconnect();
m_handler_show_progress->disconnect();
m_handler_watch_only_changed->disconnect();
m_handler_can_get_addrs_changed->disconnect();
}
// WalletModel::UnlockContext implementation
WalletModel::UnlockContext WalletModel::requestUnlock() {
bool was_locked = getEncryptionStatus() == Locked;
if (was_locked) {
// Request UI to unlock wallet
Q_EMIT requireUnlock();
}
// If wallet is still locked, unlock was failed or cancelled, mark context
// as invalid
bool valid = getEncryptionStatus() != Locked;
return UnlockContext(this, valid, was_locked);
}
WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid,
bool _relock)
: wallet(_wallet), valid(_valid), relock(_relock) {}
WalletModel::UnlockContext::~UnlockContext() {
if (valid && relock) {
wallet->setWalletLocked(true);
}
}
void WalletModel::UnlockContext::CopyFrom(const UnlockContext &rhs) {
// Transfer context; old object no longer relocks wallet
*this = rhs;
rhs.relock = false;
}
void WalletModel::loadReceiveRequests(
std::vector &vReceiveRequests) {
// receive request
vReceiveRequests = m_wallet->getDestValues("rr");
}
bool WalletModel::saveReceiveRequest(const std::string &sAddress,
const int64_t nId,
const std::string &sRequest) {
CTxDestination dest = DecodeDestination(sAddress, getChainParams());
std::stringstream ss;
ss << nId;
// "rr" prefix = "receive request" in destdata
std::string key = "rr" + ss.str();
return sRequest.empty() ? m_wallet->eraseDestData(dest, key)
: m_wallet->addDestData(dest, key, sRequest);
}
bool WalletModel::isWalletEnabled() {
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
}
bool WalletModel::privateKeysDisabled() const {
return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
}
bool WalletModel::canGetAddresses() const {
return m_wallet->canGetAddresses();
}
QString WalletModel::getWalletName() const {
return QString::fromStdString(m_wallet->getWalletName());
}
QString WalletModel::getDisplayName() const {
const QString name = getWalletName();
return name.isEmpty() ? "[" + tr("default wallet") + "]" : name;
}
bool WalletModel::isMultiwallet() {
return m_node.getWallets().size() > 1;
}
const CChainParams &WalletModel::getChainParams() const {
return Params();
}
diff --git a/src/util/error.cpp b/src/util/error.cpp
index 01dc5f4ab..2537807a2 100644
--- a/src/util/error.cpp
+++ b/src/util/error.cpp
@@ -1,43 +1,46 @@
// Copyright (c) 2010-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
std::string TransactionErrorString(const TransactionError error) {
switch (error) {
case TransactionError::OK:
return "No error";
case TransactionError::MISSING_INPUTS:
return "Missing inputs";
case TransactionError::ALREADY_IN_CHAIN:
return "Transaction already in block chain";
case TransactionError::P2P_DISABLED:
return "Peer-to-peer functionality missing or disabled";
case TransactionError::MEMPOOL_REJECTED:
return "Transaction rejected by AcceptToMemoryPool";
case TransactionError::MEMPOOL_ERROR:
return "AcceptToMemoryPool failed";
case TransactionError::INVALID_PSBT:
return "PSBT is not sane";
case TransactionError::PSBT_MISMATCH:
return "PSBTs not compatible (different transactions)";
case TransactionError::SIGHASH_MISMATCH:
return "Specified sighash value does not match existing value";
- } // no default case, so the compiler can warn about missing cases
+ case TransactionError::MAX_FEE_EXCEEDED:
+ return "Fee exceeds maximum configured by -maxtxfee";
+ // no default case, so the compiler can warn about missing cases
+ }
assert(false);
}
std::string AmountHighWarn(const std::string &optname) {
return strprintf(_("%s is set very high!").translated, optname);
}
std::string AmountErrMsg(const std::string &optname,
const std::string &strValue) {
return strprintf(_("Invalid amount for -%s=: '%s'").translated,
optname, strValue);
}
diff --git a/src/util/error.h b/src/util/error.h
index 5c42c1e25..e8b1c9564 100644
--- a/src/util/error.h
+++ b/src/util/error.h
@@ -1,40 +1,41 @@
// Copyright (c) 2010-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_ERROR_H
#define BITCOIN_UTIL_ERROR_H
/**
* util/error.h is a common place for definitions of simple error types and
* string functions. Types and functions defined here should not require any
* outside dependencies.
*
* Error types defined here can be used in different parts of the bitcoin
* codebase, to avoid the need to write boilerplate code catching and
* translating errors passed across wallet/node/rpc/gui code boundaries.
*/
#include
enum class TransactionError {
OK, //!< No error
MISSING_INPUTS,
ALREADY_IN_CHAIN,
P2P_DISABLED,
MEMPOOL_REJECTED,
MEMPOOL_ERROR,
INVALID_PSBT,
PSBT_MISMATCH,
SIGHASH_MISMATCH,
+ MAX_FEE_EXCEEDED,
};
std::string TransactionErrorString(TransactionError error);
std::string AmountHighWarn(const std::string &optname);
std::string AmountErrMsg(const std::string &optname,
const std::string &strValue);
#endif // BITCOIN_UTIL_ERROR_H
diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp
index 997886579..8aeb91b39 100644
--- a/src/wallet/fees.cpp
+++ b/src/wallet/fees.cpp
@@ -1,56 +1,47 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
#include
#include
#include
#include
Amount GetRequiredFee(const CWallet &wallet, unsigned int nTxBytes) {
return GetRequiredFeeRate(wallet).GetFeeCeiling(nTxBytes);
}
Amount GetMinimumFee(const CWallet &wallet, unsigned int nTxBytes,
const CCoinControl &coin_control) {
- Amount nFeeNeeded =
- GetMinimumFeeRate(wallet, coin_control).GetFeeCeiling(nTxBytes);
-
- // But always obey the maximum.
- const Amount max_tx_fee = wallet.m_default_max_tx_fee;
- if (nFeeNeeded > max_tx_fee) {
- nFeeNeeded = max_tx_fee;
- }
-
- return nFeeNeeded;
+ return GetMinimumFeeRate(wallet, coin_control).GetFeeCeiling(nTxBytes);
}
CFeeRate GetRequiredFeeRate(const CWallet &wallet) {
return std::max(wallet.m_min_fee, wallet.chain().relayMinFee());
}
CFeeRate GetMinimumFeeRate(const CWallet &wallet,
const CCoinControl &coin_control) {
CFeeRate neededFeeRate =
(coin_control.fOverrideFeeRate && coin_control.m_feerate)
? *coin_control.m_feerate
: wallet.m_pay_tx_fee;
if (neededFeeRate == CFeeRate()) {
neededFeeRate = wallet.chain().estimateFee();
// ... unless we don't have enough mempool data for estimatefee, then
// use fallback fee.
if (neededFeeRate == CFeeRate()) {
neededFeeRate = wallet.m_fallback_fee;
}
}
// Prevent user from paying a fee below minRelayTxFee or minTxFee.
return std::max(neededFeeRate, GetRequiredFeeRate(wallet));
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 21034d7dd..098382b6c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5014 +1,5020 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include