Skip to content

Commit 1769828

Browse files
author
MarcoFalke
committed
Merge bitcoin#19501: send* RPCs in the wallet returns the "fee reason"
69cf5d4 [test] Make sure send rpc returns fee reason (Sishir Giri) d5863c0 [send] Make send RPCs return fee reason (Sishir Giri) Pull request description: Whenever a wallet funds a transaction, the fee reason is reported to the user only if the verbose is set to true. I added an extra parameter to `CreateTransaction` function in wallet.cpp. Then I implemented the fee reason return logic in `SendMoney` in rpcwallet.cpp, followed by verbose parameter in `sendtoaddress` and `sendmany` functions. I also added a fee reason test case in walletbasic.py. link to the issue: maflcko#22 (comment) ACKs for top commit: instagibbs: ACK bitcoin@69cf5d4 meshcollider: utACK 69cf5d4 Tree-SHA512: 2e3af32dcfbd5511ba95f8bc8edca7acfe709a8430ff03e43172e5d0af3dfa4b2f57906978e7f272d878043b9ed8c6004674cf47d7496b005d5f612e9a58aa0e
2 parents 36f5a58 + 69cf5d4 commit 1769828

File tree

8 files changed

+71
-20
lines changed

8 files changed

+71
-20
lines changed

src/interfaces/wallet.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,9 @@ class WalletImpl : public Wallet
231231
{
232232
LOCK(m_wallet->cs_wallet);
233233
CTransactionRef tx;
234+
FeeCalculation fee_calc_out;
234235
if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos,
235-
fail_reason, coin_control, sign)) {
236+
fail_reason, coin_control, fee_calc_out, sign)) {
236237
return {};
237238
}
238239
return tx;

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
4141
{ "sendtoaddress", 5 , "replaceable" },
4242
{ "sendtoaddress", 6 , "conf_target" },
4343
{ "sendtoaddress", 8, "avoid_reuse" },
44+
{ "sendtoaddress", 9, "verbose"},
4445
{ "settxfee", 0, "amount" },
4546
{ "sethdseed", 0, "newkeypool" },
4647
{ "getreceivedbyaddress", 1, "minconf" },
@@ -72,6 +73,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
7273
{ "sendmany", 4, "subtractfeefrom" },
7374
{ "sendmany", 5 , "replaceable" },
7475
{ "sendmany", 6 , "conf_target" },
76+
{ "sendmany", 8, "verbose" },
7577
{ "deriveaddresses", 1, "range" },
7678
{ "scantxoutset", 1, "scanobjects" },
7779
{ "addmultisigaddress", 0, "nrequired" },

src/wallet/feebumper.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
219219
CAmount fee_ret;
220220
int change_pos_in_out = -1; // No requested location for change
221221
bilingual_str fail_reason;
222-
if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) {
222+
FeeCalculation fee_calc_out;
223+
if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) {
223224
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
224225
return Result::WALLET_ERROR;
225226
}

src/wallet/rpcwallet.cpp

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_f
397397
}
398398
}
399399

400-
UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value)
400+
UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose)
401401
{
402402
EnsureWalletIsUnlocked(pwallet);
403403

@@ -409,11 +409,18 @@ UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std
409409
int nChangePosRet = -1;
410410
bilingual_str error;
411411
CTransactionRef tx;
412-
bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
412+
FeeCalculation fee_calc_out;
413+
bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
413414
if (!fCreated) {
414415
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
415416
}
416417
pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
418+
if (verbose) {
419+
UniValue entry(UniValue::VOBJ);
420+
entry.pushKV("txid", tx->GetHash().GetHex());
421+
entry.pushKV("fee_reason", StringForFeeReason(fee_calc_out.reason));
422+
return entry;
423+
}
417424
return tx->GetHash().GetHex();
418425
}
419426

@@ -438,9 +445,19 @@ static RPCHelpMan sendtoaddress()
438445
" \"" + FeeModes("\"\n\"") + "\""},
439446
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
440447
" dirty if they have previously been used in a transaction."},
448+
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra information about the transaction."},
441449
},
442-
RPCResult{
443-
RPCResult::Type::STR_HEX, "txid", "The transaction id."
450+
{
451+
RPCResult{"if verbose is not set or set to false",
452+
RPCResult::Type::STR_HEX, "txid", "The transaction id."
453+
},
454+
RPCResult{"if verbose is set to true",
455+
RPCResult::Type::OBJ, "", "",
456+
{
457+
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
458+
{RPCResult::Type::STR, "fee reason", "The transaction fee reason."}
459+
},
460+
},
444461
},
445462
RPCExamples{
446463
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
@@ -497,8 +514,9 @@ static RPCHelpMan sendtoaddress()
497514

498515
std::vector<CRecipient> recipients;
499516
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
517+
bool verbose = request.params[9].isNull() ? false: request.params[9].get_bool();
500518

501-
return SendMoney(pwallet, coin_control, recipients, mapValue);
519+
return SendMoney(pwallet, coin_control, recipients, mapValue, verbose);
502520
},
503521
};
504522
}
@@ -853,11 +871,22 @@ static RPCHelpMan sendmany()
853871
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
854872
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
855873
" \"" + FeeModes("\"\n\"") + "\""},
874+
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
875+
},
876+
{
877+
RPCResult{"if verbose is not set or set to false",
878+
RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
879+
"the number of addresses."
880+
},
881+
RPCResult{"if verbose is set to true",
882+
RPCResult::Type::OBJ, "", "",
883+
{
884+
{RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
885+
"the number of addresses."},
886+
{RPCResult::Type::STR, "fee reason", "The transaction fee reason."}
887+
},
888+
},
856889
},
857-
RPCResult{
858-
RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
859-
"the number of addresses."
860-
},
861890
RPCExamples{
862891
"\nSend two amounts to two different addresses:\n"
863892
+ HelpExampleCli("sendmany", "\"\" \"{\\\"" + EXAMPLE_ADDRESS[0] + "\\\":0.01,\\\"" + EXAMPLE_ADDRESS[1] + "\\\":0.02}\"") +
@@ -902,12 +931,14 @@ static RPCHelpMan sendmany()
902931

903932
std::vector<CRecipient> recipients;
904933
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
934+
bool verbose = request.params[8].isNull() ? false : request.params[8].get_bool();
905935

906-
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue));
936+
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue), verbose);
907937
},
908938
};
909939
}
910940

941+
911942
static RPCHelpMan addmultisigaddress()
912943
{
913944
return RPCHelpMan{"addmultisigaddress",
@@ -4500,8 +4531,8 @@ static const CRPCCommand commands[] =
45004531
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
45014532
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
45024533
{ "wallet", "send", &send, {"outputs","conf_target","estimate_mode","options"} },
4503-
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
4504-
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse"} },
4534+
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","verbose"} },
4535+
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","verbose"} },
45054536
{ "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
45064537
{ "wallet", "setlabel", &setlabel, {"address","label"} },
45074538
{ "wallet", "settxfee", &settxfee, {"amount"} },

src/wallet/test/wallet_tests.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,9 @@ class ListCoinsTestingSetup : public TestChain100Setup
524524
int changePos = -1;
525525
bilingual_str error;
526526
CCoinControl dummy;
527+
FeeCalculation fee_calc_out;
527528
{
528-
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy));
529+
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy, fee_calc_out));
529530
}
530531
wallet->CommitTransaction(tx, {}, {});
531532
CMutableTransaction blocktx;

src/wallet/wallet.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2629,7 +2629,8 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
26292629
LOCK(cs_wallet);
26302630

26312631
CTransactionRef tx_new;
2632-
if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, false)) {
2632+
FeeCalculation fee_calc_out;
2633+
if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) {
26332634
return false;
26342635
}
26352636

@@ -2753,6 +2754,7 @@ bool CWallet::CreateTransactionInternal(
27532754
int& nChangePosInOut,
27542755
bilingual_str& error,
27552756
const CCoinControl& coin_control,
2757+
FeeCalculation& fee_calc_out,
27562758
bool sign)
27572759
{
27582760
CAmount nValue = 0;
@@ -3096,6 +3098,7 @@ bool CWallet::CreateTransactionInternal(
30963098
// Before we return success, we assume any change key will be used to prevent
30973099
// accidental re-use.
30983100
reservedest.KeepDestination();
3101+
fee_calc_out = feeCalc;
30993102

31003103
WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
31013104
nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
@@ -3115,19 +3118,20 @@ bool CWallet::CreateTransaction(
31153118
int& nChangePosInOut,
31163119
bilingual_str& error,
31173120
const CCoinControl& coin_control,
3121+
FeeCalculation& fee_calc_out,
31183122
bool sign)
31193123
{
31203124
int nChangePosIn = nChangePosInOut;
31213125
CTransactionRef tx2 = tx;
3122-
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, sign);
3126+
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
31233127
// try with avoidpartialspends unless it's enabled already
31243128
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
31253129
CCoinControl tmp_cc = coin_control;
31263130
tmp_cc.m_avoid_partial_spends = true;
31273131
CAmount nFeeRet2;
31283132
int nChangePosInOut2 = nChangePosIn;
31293133
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
3130-
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, sign)) {
3134+
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) {
31313135
// if fee of this alternative one is within the range of the max fee, we use this one
31323136
const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
31333137
WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");

src/wallet/wallet.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
723723
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
724724
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
725725

726-
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign);
726+
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign);
727727

728728
public:
729729
/*
@@ -974,7 +974,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
974974
* selected by SelectCoins(); Also create the change output, when needed
975975
* @note passing nChangePosInOut as -1 will result in setting a random position
976976
*/
977-
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign = true);
977+
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
978978
/**
979979
* Submit the transaction to the node's mempool and then relay to peers.
980980
* Should be called after CreateTransaction unless you want to abort

test/functional/wallet_basic.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,17 @@ def run_test(self):
661661
assert_array_result(tx["details"], {"category": "receive"}, expected_receive_vout)
662662
assert_equal(tx[verbose_field], self.nodes[0].decoderawtransaction(tx["hex"]))
663663

664+
self.log.info("Test send* RPCs with verbose=True")
665+
address = self.nodes[0].getnewaddress("test")
666+
txid_feeReason_one = self.nodes[2].sendtoaddress(address = address, amount = 5, verbose = True)
667+
assert_equal(txid_feeReason_one["fee_reason"], "Fallback fee")
668+
txid_feeReason_two = self.nodes[2].sendmany(dummy = '', amounts = {address: 5}, verbose = True)
669+
assert_equal(txid_feeReason_two["fee_reason"], "Fallback fee")
670+
self.log.info("Test send* RPCs with verbose=False")
671+
txid_feeReason_three = self.nodes[2].sendtoaddress(address = address, amount = 5, verbose = False)
672+
assert_equal(self.nodes[2].gettransaction(txid_feeReason_three)['txid'], txid_feeReason_three)
673+
txid_feeReason_four = self.nodes[2].sendmany(dummy = '', amounts = {address: 5}, verbose = False)
674+
assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four)
664675

665676
if __name__ == '__main__':
666677
WalletTest().main()

0 commit comments

Comments
 (0)