Skip to content

Commit 459baa1

Browse files
committed
Merge #16208: wallet: Consume ReserveDestination on successful CreateTransaction
e10e1e8 Restrict lifetime of ReserveDestination to CWallet::CreateTransaction (Gregory Sanders) d9ff862 CreateTransaction calls KeepDestination on ReserveDestination before success (Gregory Sanders) Pull request description: The typical usage pattern of `ReserveDestination` is to explicitly `KeepDestination`, or `ReturnDestination` when it's detected it will not be used. Implementers such as myself may fail to complete this pattern, and could result in key re-use: bitcoin/bitcoin#15557 (comment) Since ReserveDestination is currently only used directly in the `CreateTransaction`/`CommitTransaction` flow(or fee bumping where it's just used in `CreateTransaction`), I instead make the assumption that if a transaction is returned by `CreateTransaction` it's highly likely that it will be accepted by the caller, and the `ReserveDestination` kept. This simplifies the API as well. There are very few cases where this would not be the case which may result in keys being burned. Those failure cases appear to be: `CommitTransaction` failing to get the transaction into the mempool Belt and suspenders check in `WalletModel::prepareTransaction` Alternative to bitcoin/bitcoin#15796 ACKs for top commit: achow101: ACK e10e1e8 Reviewed the diff stevenroose: utACK e10e1e8 meshcollider: utACK e10e1e8 Tree-SHA512: 78d047a00f39ab41cfa297052cc1e9c224d5f47d3d2299face650d71827635de077ac33fb4ab9f7dc6fc5a27f4a68415a1bc9ca33a3cb09a78f4f15b2a48411b
2 parents 24dbcf3 + e10e1e8 commit 459baa1

File tree

6 files changed

+23
-35
lines changed

6 files changed

+23
-35
lines changed

src/interfaces/wallet.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace {
3636
class PendingWalletTxImpl : public PendingWalletTx
3737
{
3838
public:
39-
explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_dest(&wallet) {}
39+
explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet) {}
4040

4141
const CTransaction& get() override { return *m_tx; }
4242

@@ -47,7 +47,7 @@ class PendingWalletTxImpl : public PendingWalletTx
4747
auto locked_chain = m_wallet.chain().lock();
4848
LOCK(m_wallet.cs_wallet);
4949
CValidationState state;
50-
if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_dest, state)) {
50+
if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), state)) {
5151
reject_reason = state.GetRejectReason();
5252
return false;
5353
}
@@ -56,7 +56,6 @@ class PendingWalletTxImpl : public PendingWalletTx
5656

5757
CTransactionRef m_tx;
5858
CWallet& m_wallet;
59-
ReserveDestination m_dest;
6059
};
6160

6261
//! Construct wallet tx struct.
@@ -238,7 +237,7 @@ class WalletImpl : public Wallet
238237
auto locked_chain = m_wallet->chain().lock();
239238
LOCK(m_wallet->cs_wallet);
240239
auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet);
241-
if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_dest, fee, change_pos,
240+
if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, fee, change_pos,
242241
fail_reason, coin_control, sign)) {
243242
return {};
244243
}

src/wallet/feebumper.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,14 @@ Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCo
272272
new_coin_control.m_min_depth = 1;
273273

274274
CTransactionRef tx_new = MakeTransactionRef();
275-
ReserveDestination reservedest(wallet);
276275
CAmount fee_ret;
277276
int change_pos_in_out = -1; // No requested location for change
278277
std::string fail_reason;
279-
if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservedest, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) {
278+
if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) {
280279
errors.push_back("Unable to create transaction: " + fail_reason);
281280
return Result::WALLET_ERROR;
282281
}
283282

284-
// If change key hasn't been ReturnKey'ed by this point, we take it out of keypool
285-
reservedest.KeepDestination();
286-
287283
// Write back new fee if successful
288284
new_fee = fee_ret;
289285

@@ -330,9 +326,8 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti
330326
mapValue_t mapValue = oldWtx.mapValue;
331327
mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
332328

333-
ReserveDestination reservedest(wallet);
334329
CValidationState state;
335-
if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservedest, state)) {
330+
if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, state)) {
336331
// NOTE: CommitTransaction never returns false, so this should never happen.
337332
errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state)));
338333
return Result::WALLET_ERROR;

src/wallet/rpcwallet.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,21 +317,20 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet
317317
CScript scriptPubKey = GetScriptForDestination(address);
318318

319319
// Create and send the transaction
320-
ReserveDestination reservedest(pwallet);
321320
CAmount nFeeRequired;
322321
std::string strError;
323322
std::vector<CRecipient> vecSend;
324323
int nChangePosRet = -1;
325324
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
326325
vecSend.push_back(recipient);
327326
CTransactionRef tx;
328-
if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservedest, nFeeRequired, nChangePosRet, strError, coin_control)) {
327+
if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strError, coin_control)) {
329328
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
330329
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
331330
throw JSONRPCError(RPC_WALLET_ERROR, strError);
332331
}
333332
CValidationState state;
334-
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservedest, state)) {
333+
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) {
335334
strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
336335
throw JSONRPCError(RPC_WALLET_ERROR, strError);
337336
}
@@ -915,16 +914,15 @@ static UniValue sendmany(const JSONRPCRequest& request)
915914
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
916915

917916
// Send
918-
ReserveDestination changedest(pwallet);
919917
CAmount nFeeRequired = 0;
920918
int nChangePosRet = -1;
921919
std::string strFailReason;
922920
CTransactionRef tx;
923-
bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, changedest, nFeeRequired, nChangePosRet, strFailReason, coin_control);
921+
bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strFailReason, coin_control);
924922
if (!fCreated)
925923
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
926924
CValidationState state;
927-
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, changedest, state)) {
925+
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) {
928926
strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state));
929927
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
930928
}

src/wallet/test/wallet_tests.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,17 +361,16 @@ class ListCoinsTestingSetup : public TestChain100Setup
361361
CWalletTx& AddTx(CRecipient recipient)
362362
{
363363
CTransactionRef tx;
364-
ReserveDestination reservedest(wallet.get());
365364
CAmount fee;
366365
int changePos = -1;
367366
std::string error;
368367
CCoinControl dummy;
369368
{
370369
auto locked_chain = m_chain->lock();
371-
BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, reservedest, fee, changePos, error, dummy));
370+
BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, fee, changePos, error, dummy));
372371
}
373372
CValidationState state;
374-
BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservedest, state));
373+
BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, state));
375374
CMutableTransaction blocktx;
376375
{
377376
LOCK(wallet->cs_wallet);

src/wallet/wallet.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,17 +2779,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
27792779
auto locked_chain = chain().lock();
27802780
LOCK(cs_wallet);
27812781

2782-
ReserveDestination reservedest(this);
27832782
CTransactionRef tx_new;
2784-
if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservedest, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
2783+
if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
27852784
return false;
27862785
}
27872786

27882787
if (nChangePosInOut != -1) {
27892788
tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
2790-
// We don't have the normal Create/Commit cycle, and don't want to risk
2791-
// reusing change, so just remove the key from the keypool here.
2792-
reservedest.KeepDestination();
27932789
}
27942790

27952791
// Copy output sizes from new transaction; they may have had the fee
@@ -2900,10 +2896,11 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec
29002896
return m_default_address_type;
29012897
}
29022898

2903-
bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, ReserveDestination& reservedest, CAmount& nFeeRet,
2899+
bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet,
29042900
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
29052901
{
29062902
CAmount nValue = 0;
2903+
ReserveDestination reservedest(this);
29072904
int nChangePosRequest = nChangePosInOut;
29082905
unsigned int nSubtractFeeFromAmount = 0;
29092906
for (const auto& recipient : vecSend)
@@ -3183,8 +3180,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
31833180
}
31843181
}
31853182

3186-
if (nChangePosInOut == -1) reservedest.ReturnDestination(); // Return any reserved address if we don't have change
3187-
31883183
// Shuffle selected coins and fill in final vin
31893184
txNew.vin.clear();
31903185
std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
@@ -3247,6 +3242,10 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
32473242
}
32483243
}
32493244

3245+
// Before we return success, we assume any change key will be used to prevent
3246+
// accidental re-use.
3247+
reservedest.KeepDestination();
3248+
32503249
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",
32513250
nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
32523251
feeCalc.est.pass.start, feeCalc.est.pass.end,
@@ -3261,7 +3260,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
32613260
/**
32623261
* Call after CreateTransaction unless you want to abort
32633262
*/
3264-
bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, ReserveDestination& reservedest, CValidationState& state)
3263+
bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state)
32653264
{
32663265
{
32673266
auto locked_chain = chain().lock();
@@ -3275,8 +3274,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
32753274

32763275
WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */
32773276
{
3278-
// Take key pair from key pool so it won't be used again
3279-
reservedest.KeepDestination();
32803277

32813278
// Add tx to wallet, because if it has change it's also ours,
32823279
// otherwise just for transaction history.

src/wallet/wallet.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ class CKeyPool
266266

267267
/** A wrapper to reserve an address from a wallet
268268
*
269-
* ReserveDestination is used to reserve an address. It is passed around
270-
* during the CreateTransaction/CommitTransaction procedure.
269+
* ReserveDestination is used to reserve an address.
270+
* It is currently only used inside of CreateTransaction.
271271
*
272272
* Instantiating a ReserveDestination does not reserve an address. To do so,
273273
* GetReservedDestination() needs to be called on the object. Once an address has been
@@ -1137,9 +1137,9 @@ class CWallet final : public FillableSigningProvider, private interfaces::Chain:
11371137
* selected by SelectCoins(); Also create the change output, when needed
11381138
* @note passing nChangePosInOut as -1 will result in setting a random position
11391139
*/
1140-
bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, ReserveDestination& reservedest, CAmount& nFeeRet, int& nChangePosInOut,
1140+
bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut,
11411141
std::string& strFailReason, const CCoinControl& coin_control, bool sign = true);
1142-
bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, ReserveDestination& reservedest, CValidationState& state);
1142+
bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state);
11431143

11441144
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
11451145
{

0 commit comments

Comments
 (0)