Skip to content

Commit fb75cd0

Browse files
committed
Merge #9377: fundrawtransaction: Keep change-output keys by default, make it optional
c9f3062 Add fundrawtransactions new reserveChangeKey option to the release notes (Jonas Schnelli) 9eb325d [QA] Add test for fundrawtransactions new reserveChangeKey option (Jonas Schnelli) 9aa4e6a [Wallet] Add an option to keep the change address key, true by default (Jonas Schnelli)
2 parents 82274c0 + c9f3062 commit fb75cd0

File tree

5 files changed

+50
-4
lines changed

5 files changed

+50
-4
lines changed

doc/release-notes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ Call "getmininginfo" loses the "testnet" field in favor of the more generic "cha
128128

129129
### Wallet
130130

131+
0.14.0 Fundrawtransaction change address reuse
132+
==============================================
133+
134+
Before 0.14, `fundrawtransaction` was by default wallet stateless. In almost all cases `fundrawtransaction` does add a change-output to the outputs of the funded transaction. Before 0.14, the used keypool key was never marked as change-address key and directly returned to the keypool (leading to address reuse).
135+
Before 0.14, calling `getnewaddress` directly after `fundrawtransaction` did generate the same address as the change-output address.
136+
137+
Since 0.14, fundrawtransaction does reserve the change-output-key from the keypool by default (optional by setting `reserveChangeKey`, default = `true`)
138+
139+
Users should also consider using `getrawchangeaddress()` in conjunction with `fundrawtransaction`'s `changeAddress` option.
140+
131141
### GUI
132142

133143
### Tests

qa/rpc-tests/fundrawtransaction.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ def run_test(self):
651651
assert_equal(len(self.nodes[3].listunspent(1)), 1)
652652

653653
inputs = []
654-
outputs = {self.nodes[2].getnewaddress() : 1}
654+
outputs = {self.nodes[3].getnewaddress() : 1}
655655
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
656656
result = self.nodes[3].fundrawtransaction(rawtx) # uses min_relay_tx_fee (set by settxfee)
657657
result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2*min_relay_tx_fee})
@@ -660,6 +660,32 @@ def run_test(self):
660660
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
661661
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
662662

663+
#############################
664+
# Test address reuse option #
665+
#############################
666+
667+
result3 = self.nodes[3].fundrawtransaction(rawtx, {"reserveChangeKey": False})
668+
res_dec = self.nodes[0].decoderawtransaction(result3["hex"])
669+
changeaddress = ""
670+
for out in res_dec['vout']:
671+
if out['value'] > 1.0:
672+
changeaddress += out['scriptPubKey']['addresses'][0]
673+
assert(changeaddress != "")
674+
nextaddr = self.nodes[3].getnewaddress()
675+
# frt should not have removed the key from the keypool
676+
assert(changeaddress == nextaddr)
677+
678+
result3 = self.nodes[3].fundrawtransaction(rawtx)
679+
res_dec = self.nodes[0].decoderawtransaction(result3["hex"])
680+
changeaddress = ""
681+
for out in res_dec['vout']:
682+
if out['value'] > 1.0:
683+
changeaddress += out['scriptPubKey']['addresses'][0]
684+
assert(changeaddress != "")
685+
nextaddr = self.nodes[3].getnewaddress()
686+
# Now the change address key should be removed from the keypool
687+
assert(changeaddress != nextaddr)
688+
663689
######################################
664690
# Test subtractFeeFromOutputs option #
665691
######################################

src/wallet/rpcwallet.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2511,6 +2511,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
25112511
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
25122512
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
25132513
" \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
2514+
" \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n"
25142515
" \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n"
25152516
" \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
25162517
" The fee will be equally deducted from the amount of each specified output.\n"
@@ -2543,6 +2544,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
25432544
int changePosition = -1;
25442545
bool includeWatching = false;
25452546
bool lockUnspents = false;
2547+
bool reserveChangeKey = true;
25462548
CFeeRate feeRate = CFeeRate(0);
25472549
bool overrideEstimatedFeerate = false;
25482550
UniValue subtractFeeFromOutputs;
@@ -2564,6 +2566,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
25642566
{"changePosition", UniValueType(UniValue::VNUM)},
25652567
{"includeWatching", UniValueType(UniValue::VBOOL)},
25662568
{"lockUnspents", UniValueType(UniValue::VBOOL)},
2569+
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
25672570
{"feeRate", UniValueType()}, // will be checked below
25682571
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
25692572
},
@@ -2587,6 +2590,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
25872590
if (options.exists("lockUnspents"))
25882591
lockUnspents = options["lockUnspents"].get_bool();
25892592

2593+
if (options.exists("reserveChangeKey"))
2594+
reserveChangeKey = options["reserveChangeKey"].get_bool();
2595+
25902596
if (options.exists("feeRate"))
25912597
{
25922598
feeRate = CFeeRate(AmountFromValue(options["feeRate"]));
@@ -2623,7 +2629,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26232629
CAmount nFeeOut;
26242630
string strFailReason;
26252631

2626-
if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, changeAddress))
2632+
if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress))
26272633
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
26282634

26292635
UniValue result(UniValue::VOBJ);

src/wallet/wallet.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2282,7 +2282,7 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount&
22822282
return res;
22832283
}
22842284

2285-
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, const CTxDestination& destChange)
2285+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination& destChange)
22862286
{
22872287
vector<CRecipient> vecSend;
22882288

@@ -2331,6 +2331,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
23312331
}
23322332
}
23332333

2334+
// optionally keep the change output key
2335+
if (keepReserveKey)
2336+
reservekey.KeepKey();
2337+
23342338
return true;
23352339
}
23362340

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
780780
* Insert additional inputs into the transaction by
781781
* calling CreateTransaction();
782782
*/
783-
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, const CTxDestination& destChange = CNoDestination());
783+
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey = true, const CTxDestination& destChange = CNoDestination());
784784

785785
/**
786786
* Create a new transaction paying the recipients with a set of coins

0 commit comments

Comments
 (0)