Skip to content

Commit b82067b

Browse files
committed
wallet: try -avoidpartialspends mode and use its result if fees are below threshold
The threshold is defined by a new max avoid partial spends fee flag, which defaults to 0 (i.e. if fees are unchanged, use the grouped option).
1 parent e3272ff commit b82067b

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

src/dummywallet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
3535
"-discardfee=<amt>",
3636
"-fallbackfee=<amt>",
3737
"-keypool=<n>",
38+
"-maxapsfee=<n>",
3839
"-maxtxfee=<amt>",
3940
"-mintxfee=<amt>",
4041
"-paytxfee=<amt>",

src/wallet/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
4848
argsman.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
4949
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
5050
argsman.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
51+
argsman.AddArg("-maxapsfee=<n>", strprintf("Spend up to this amount in additional (absolute) fees (in %s) if it allows the use of partial spend avoidance (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
5152
argsman.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)",
5253
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
5354
argsman.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",

src/wallet/wallet.cpp

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2706,7 +2706,14 @@ OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_typ
27062706
return m_default_address_type;
27072707
}
27082708

2709-
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign)
2709+
bool CWallet::CreateTransactionInternal(
2710+
const std::vector<CRecipient>& vecSend,
2711+
CTransactionRef& tx,
2712+
CAmount& nFeeRet,
2713+
int& nChangePosInOut,
2714+
bilingual_str& error,
2715+
const CCoinControl& coin_control,
2716+
bool sign)
27102717
{
27112718
CAmount nValue = 0;
27122719
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
@@ -3061,6 +3068,39 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac
30613068
return true;
30623069
}
30633070

3071+
bool CWallet::CreateTransaction(
3072+
const std::vector<CRecipient>& vecSend,
3073+
CTransactionRef& tx,
3074+
CAmount& nFeeRet,
3075+
int& nChangePosInOut,
3076+
bilingual_str& error,
3077+
const CCoinControl& coin_control,
3078+
bool sign)
3079+
{
3080+
int nChangePosIn = nChangePosInOut;
3081+
CTransactionRef tx2 = tx;
3082+
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, sign);
3083+
// try with avoidpartialspends unless it's enabled already
3084+
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
3085+
CCoinControl tmp_cc = coin_control;
3086+
tmp_cc.m_avoid_partial_spends = true;
3087+
CAmount nFeeRet2;
3088+
int nChangePosInOut2 = nChangePosIn;
3089+
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
3090+
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, sign)) {
3091+
// if fee of this alternative one is within the range of the max fee, we use this one
3092+
const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
3093+
WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
3094+
if (use_aps) {
3095+
tx = tx2;
3096+
nFeeRet = nFeeRet2;
3097+
nChangePosInOut = nChangePosInOut2;
3098+
}
3099+
}
3100+
}
3101+
return res;
3102+
}
3103+
30643104
void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm)
30653105
{
30663106
LOCK(cs_wallet);
@@ -3878,6 +3918,21 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
38783918
walletInstance->m_min_fee = CFeeRate(n);
38793919
}
38803920

3921+
if (gArgs.IsArgSet("-maxapsfee")) {
3922+
CAmount n = 0;
3923+
if (gArgs.GetArg("-maxapsfee", "") == "-1") {
3924+
n = -1;
3925+
} else if (!ParseMoney(gArgs.GetArg("-maxapsfee", ""), n)) {
3926+
error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", ""));
3927+
return nullptr;
3928+
}
3929+
if (n > HIGH_APS_FEE) {
3930+
warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
3931+
_("This is the maximum transaction fee you pay to prioritize partial spend avoidance over regular coin selection."));
3932+
}
3933+
walletInstance->m_max_aps_fee = n;
3934+
}
3935+
38813936
if (gArgs.IsArgSet("-fallbackfee")) {
38823937
CAmount nFeePerK = 0;
38833938
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {

src/wallet/wallet.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ static const CAmount DEFAULT_FALLBACK_FEE = 0;
7272
static const CAmount DEFAULT_DISCARD_FEE = 10000;
7373
//! -mintxfee default
7474
static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
75+
/**
76+
* maximum fee increase allowed to do partial spend avoidance, even for nodes with this feature disabled by default
77+
*
78+
* A value of -1 disables this feature completely.
79+
* A value of 0 (current default) means to attempt to do partial spend avoidance, and use its results if the fees remain *unchanged*
80+
* A value > 0 means to do partial spend avoidance if the fee difference against a regular coin selection instance is in the range [0..value].
81+
*/
82+
static const CAmount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = 0;
83+
//! discourage APS fee higher than this amount
84+
constexpr CAmount HIGH_APS_FEE{COIN / 10000};
7585
//! minimum recommended increment for BIP 125 replacement txs
7686
static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
7787
//! Default for -spendzeroconfchange
@@ -719,6 +729,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
719729
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
720730
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
721731

732+
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign);
733+
722734
public:
723735
/*
724736
* Main wallet lock.
@@ -1008,6 +1020,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
10081020
*/
10091021
CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
10101022
CFeeRate m_discard_rate{DEFAULT_DISCARD_FEE};
1023+
CAmount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; //!< note: this is absolute fee, not fee rate
10111024
OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
10121025
/**
10131026
* Default output type for change outputs. When unset, automatically choose type

0 commit comments

Comments
 (0)