diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/coincontrol.h | 2 | ||||
-rw-r--r-- | src/wallet/feebumper.cpp | 159 | ||||
-rw-r--r-- | src/wallet/feebumper.h | 13 | ||||
-rw-r--r-- | src/wallet/init.cpp | 101 | ||||
-rw-r--r-- | src/wallet/load.cpp | 112 | ||||
-rw-r--r-- | src/wallet/load.h | 38 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 32 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 126 | ||||
-rw-r--r-- | src/wallet/test/coinselector_tests.cpp | 5 | ||||
-rw-r--r-- | src/wallet/test/db_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/test/init_test_fixture.h | 2 | ||||
-rw-r--r-- | src/wallet/test/init_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/test/psbt_wallet_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/test/wallet_crypto_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/test/wallet_test_fixture.cpp | 7 | ||||
-rw-r--r-- | src/wallet/test/wallet_test_fixture.h | 2 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 30 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 348 | ||||
-rw-r--r-- | src/wallet/wallet.h | 77 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallettool.cpp | 7 |
21 files changed, 553 insertions, 518 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 48a924abfb..9257b272bc 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -36,6 +36,8 @@ public: bool m_avoid_partial_spends; //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; + //! Minimum chain depth value for coin availability + int m_min_depth{0}; CCoinControl() { diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index ef7f3be728..4ec9dca420 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -14,7 +14,9 @@ #include <validation.h> //for mempool access #include <txmempool.h> #include <util/moneystr.h> +#include <util/rbf.h> #include <util/system.h> +#include <util/validation.h> #include <net.h> //! Check whether transaction has descendant in wallet or mempool, or has been @@ -73,9 +75,11 @@ bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) return res == feebumper::Result::OK; } -Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) +Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { + new_fee = total_fee; + auto locked_chain = wallet->chain().lock(); LOCK(wallet->cs_wallet); errors.clear(); @@ -119,7 +123,6 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // calculate the old fee and fee-rate old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); CFeeRate nOldFeeRate(old_fee, txSize); - CFeeRate nNewFeeRate; // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to // future proof against changes to network wide policy for incremental relay // fee that our node may not be aware of. @@ -129,34 +132,17 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin walletIncrementalRelayFee = nodeIncrementalRelayFee; } - if (total_fee > 0) { - CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + nodeIncrementalRelayFee.GetFee(maxNewTxSize); - if (total_fee < minTotalFee) { - errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", - FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(nodeIncrementalRelayFee.GetFee(maxNewTxSize)))); - return Result::INVALID_PARAMETER; - } - CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize); - if (total_fee < requiredFee) { - errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", - FormatMoney(requiredFee))); - return Result::INVALID_PARAMETER; - } - new_fee = total_fee; - nNewFeeRate = CFeeRate(total_fee, maxNewTxSize); - } else { - new_fee = GetMinimumFee(*wallet, maxNewTxSize, coin_control, nullptr /* FeeCalculation */); - nNewFeeRate = CFeeRate(new_fee, maxNewTxSize); - - // New fee rate must be at least old rate + minimum incremental relay rate - // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized - // in that unit (fee per kb). - // However, nOldFeeRate is a calculated value from the tx fee/size, so - // add 1 satoshi to the result, because it may have been rounded down. - if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) { - nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()); - new_fee = nNewFeeRate.GetFee(maxNewTxSize); - } + CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + nodeIncrementalRelayFee.GetFee(maxNewTxSize); + if (total_fee < minTotalFee) { + errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", + FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(nodeIncrementalRelayFee.GetFee(maxNewTxSize)))); + return Result::INVALID_PARAMETER; + } + CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize); + if (total_fee < requiredFee) { + errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", + FormatMoney(requiredFee))); + return Result::INVALID_PARAMETER; } // Check that in all cases the new fee doesn't violate maxTxFee @@ -172,15 +158,15 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps, // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a // moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment. - CFeeRate minMempoolFeeRate = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + CFeeRate minMempoolFeeRate = wallet->chain().mempoolMinFee(); + CFeeRate nNewFeeRate = CFeeRate(total_fee, maxNewTxSize); if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { errors.push_back(strprintf( "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- " - "the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction", + "the totalFee value should be at least %s to add transaction", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), - FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), - FormatMoney(minMempoolFeeRate.GetFeePerK()))); + FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)))); return Result::WALLET_ERROR; } @@ -210,6 +196,109 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } } + return Result::OK; +} + + +Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<std::string>& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) +{ + // We are going to modify coin control later, copy to re-use + CCoinControl new_coin_control(coin_control); + + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); + errors.clear(); + auto it = wallet->mapWallet.find(txid); + if (it == wallet->mapWallet.end()) { + errors.push_back("Invalid or non-wallet transaction id"); + return Result::INVALID_ADDRESS_OR_KEY; + } + const CWalletTx& wtx = it->second; + + Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); + if (result != Result::OK) { + return result; + } + + // Fill in recipients(and preserve a single change key if there is one) + std::vector<CRecipient> recipients; + for (const auto& output : wtx.tx->vout) { + if (!wallet->IsChange(output)) { + CRecipient recipient = {output.scriptPubKey, output.nValue, false}; + recipients.push_back(recipient); + } else { + CTxDestination change_dest; + ExtractDestination(output.scriptPubKey, change_dest); + new_coin_control.destChange = change_dest; + } + } + + // Get the fee rate of the original transaction. This is calculated from + // the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the + // result. + old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); + // Feerate of thing we are bumping + CFeeRate feerate(old_fee, txSize); + feerate += CFeeRate(1); + + // The node has a configurable incremental relay fee. Increment the fee by + // the minimum of that and the wallet's conservative + // WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to + // network wide policy for incremental relay fee that our node may not be + // aware of. This ensures we're over the over the required relay fee rate + // (BIP 125 rule 4). The replacement tx will be at least as large as the + // original tx, so the total fee will be greater (BIP 125 rule 3) + CFeeRate node_incremental_relay_fee = wallet->chain().relayIncrementalFee(); + CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); + feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee); + + // Fee rate must also be at least the wallet's GetMinimumFeeRate + CFeeRate min_feerate(GetMinimumFeeRate(*wallet, new_coin_control, /* feeCalc */ nullptr)); + + // Set the required fee rate for the replacement transaction in coin control. + new_coin_control.m_feerate = std::max(feerate, min_feerate); + + // Fill in required inputs we are double-spending(all of them) + // N.B.: bip125 doesn't require all the inputs in the replaced transaction to be + // used in the replacement transaction, but it's very important for wallets to make + // sure that happens. If not, it would be possible to bump a transaction A twice to + // A2 and A3 where A2 and A3 don't conflict (or alternatively bump A to A2 and A2 + // to A3 where A and A3 don't conflict). If both later get confirmed then the sender + // has accidentally double paid. + for (const auto& inputs : wtx.tx->vin) { + new_coin_control.Select(COutPoint(inputs.prevout)); + } + new_coin_control.fAllowOtherInputs = true; + + // We cannot source new unconfirmed inputs(bip125 rule 2) + new_coin_control.m_min_depth = 1; + + CTransactionRef tx_new = MakeTransactionRef(); + CReserveKey reservekey(wallet); + CAmount fee_ret; + int change_pos_in_out = -1; // No requested location for change + std::string fail_reason; + if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservekey, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { + errors.push_back("Unable to create transaction: " + fail_reason); + return Result::WALLET_ERROR; + } + + // If change key hasn't been ReturnKey'ed by this point, we take it out of keypool + reservekey.KeepKey(); + + // Write back new fee if successful + new_fee = fee_ret; + + // Write back transaction + mtx = CMutableTransaction(*tx_new); + // Mark new tx not replaceable, if requested. + if (!coin_control.m_signal_bip125_rbf.get_value_or(wallet->m_signal_rbf)) { + for (auto& input : mtx.vin) { + if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; + } + } return Result::OK; } diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index a1eb5d0e0d..f9cbfc5f68 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -28,8 +28,8 @@ enum class Result //! Return whether transaction can be bumped. bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid); -//! Create bumpfee transaction. -Result CreateTransaction(const CWallet* wallet, +//! Create bumpfee transaction based on total amount. +Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, @@ -38,6 +38,15 @@ Result CreateTransaction(const CWallet* wallet, CAmount& new_fee, CMutableTransaction& mtx); +//! Create bumpfee transaction based on feerate estimates. +Result CreateRateBumpTransaction(CWallet* wallet, + const uint256& txid, + const CCoinControl& coin_control, + std::vector<std::string>& errors, + CAmount& old_fee, + CAmount& new_fee, + CMutableTransaction& mtx); + //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was //! impossible to create the signature(s) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 76a7eaa681..e7eea94e06 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -9,6 +9,7 @@ #include <net.h> #include <scheduler.h> #include <outputtype.h> +#include <util/error.h> #include <util/system.h> #include <util/moneystr.h> #include <validation.h> @@ -130,58 +131,6 @@ bool WalletInit::ParameterInteraction() const return true; } -bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) -{ - if (gArgs.IsArgSet("-walletdir")) { - fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); - boost::system::error_code error; - // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory - fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); - if (error || !fs::exists(wallet_dir)) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); - return false; - } else if (!fs::is_directory(wallet_dir)) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); - return false; - // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version - } else if (!wallet_dir.is_absolute()) { - chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); - return false; - } - gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); - } - - LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); - - chain.initMessage(_("Verifying wallet(s)...")); - - // Parameter interaction code should have thrown an error if -salvagewallet - // was enabled with more than wallet file, so the wallet_files size check - // here should have no effect. - bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; - - // Keep track of each wallet absolute path to detect duplicates. - std::set<fs::path> wallet_paths; - - for (const auto& wallet_file : wallet_files) { - WalletLocation location(wallet_file); - - if (!wallet_paths.insert(location.GetPath()).second) { - chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); - return false; - } - - std::string error_string; - std::string warning_string; - bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); - if (!error_string.empty()) chain.initError(error_string); - if (!warning_string.empty()) chain.initWarning(warning_string); - if (!verify_success) return false; - } - - return true; -} - void WalletInit::Construct(InitInterfaces& interfaces) const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { @@ -191,51 +140,3 @@ void WalletInit::Construct(InitInterfaces& interfaces) const gArgs.SoftSetArg("-wallet", ""); interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); } - -bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) -{ - for (const std::string& walletFile : wallet_files) { - std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); - if (!pwallet) { - return false; - } - AddWallet(pwallet); - } - - return true; -} - -void StartWallets(CScheduler& scheduler) -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->postInitProcess(); - } - - // Run a thread to flush wallet periodically - scheduler.scheduleEvery(MaybeCompactWalletDB, 500); -} - -void FlushWallets() -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->Flush(false); - } -} - -void StopWallets() -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->Flush(true); - } -} - -void UnloadWallets() -{ - auto wallets = GetWallets(); - while (!wallets.empty()) { - auto wallet = wallets.back(); - wallets.pop_back(); - RemoveWallet(wallet); - UnloadWallet(std::move(wallet)); - } -} diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp new file mode 100644 index 0000000000..79c5f439df --- /dev/null +++ b/src/wallet/load.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <wallet/load.h> + +#include <interfaces/chain.h> +#include <scheduler.h> +#include <util/system.h> +#include <wallet/wallet.h> + +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + if (gArgs.IsArgSet("-walletdir")) { + fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); + boost::system::error_code error; + // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory + fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); + if (error || !fs::exists(wallet_dir)) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); + return false; + } else if (!fs::is_directory(wallet_dir)) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + return false; + // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version + } else if (!wallet_dir.is_absolute()) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); + return false; + } + gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); + } + + LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); + + chain.initMessage(_("Verifying wallet(s)...")); + + // Parameter interaction code should have thrown an error if -salvagewallet + // was enabled with more than wallet file, so the wallet_files size check + // here should have no effect. + bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; + + // Keep track of each wallet absolute path to detect duplicates. + std::set<fs::path> wallet_paths; + + for (const auto& wallet_file : wallet_files) { + WalletLocation location(wallet_file); + + if (!wallet_paths.insert(location.GetPath()).second) { + chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); + return false; + } + + std::string error_string; + std::string warning_string; + bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); + if (!error_string.empty()) chain.initError(error_string); + if (!warning_string.empty()) chain.initWarning(warning_string); + if (!verify_success) return false; + } + + return true; +} + +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + for (const std::string& walletFile : wallet_files) { + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); + if (!pwallet) { + return false; + } + AddWallet(pwallet); + } + + return true; +} + +void StartWallets(CScheduler& scheduler) +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->postInitProcess(); + } + + // Schedule periodic wallet flushes and tx rebroadcasts + scheduler.scheduleEvery(MaybeCompactWalletDB, 500); + scheduler.scheduleEvery(MaybeResendWalletTxs, 1000); +} + +void FlushWallets() +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->Flush(false); + } +} + +void StopWallets() +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->Flush(true); + } +} + +void UnloadWallets() +{ + auto wallets = GetWallets(); + while (!wallets.empty()) { + auto wallet = wallets.back(); + wallets.pop_back(); + RemoveWallet(wallet); + UnloadWallet(std::move(wallet)); + } +} diff --git a/src/wallet/load.h b/src/wallet/load.h new file mode 100644 index 0000000000..9bb6f2166e --- /dev/null +++ b/src/wallet/load.h @@ -0,0 +1,38 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-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_WALLET_LOAD_H +#define BITCOIN_WALLET_LOAD_H + +#include <string> +#include <vector> + +class CScheduler; + +namespace interfaces { +class Chain; +} // namespace interfaces + +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +//! This function will perform salvage on the wallet if requested, as long as only one wallet is +//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Load wallet databases. +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void UnloadWallets(); + +#endif // BITCOIN_WALLET_LOAD_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 6c3b5a49dc..c339e111ba 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -348,7 +348,11 @@ UniValue importaddress(const JSONRPCRequest& request) if (fRescan) { RescanWallet(*pwallet, reserver); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } } return NullUniValue; @@ -532,7 +536,11 @@ UniValue importpubkey(const JSONRPCRequest& request) if (fRescan) { RescanWallet(*pwallet, reserver); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } } return NullUniValue; @@ -892,7 +900,7 @@ struct ImportData // Output data std::set<CScript> import_scripts; std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) - std::map<CKeyID, KeyOriginInfo> key_origins; + std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins; }; enum class ScriptContext @@ -1189,6 +1197,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(), [&](const std::pair<CKeyID, CPubKey>& used_key) { return privkey_map.count(used_key.first) > 0; + }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(), + [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) { + return privkey_map.count(entry.first) > 0; }); if (!watch_only && !spendable) { warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); @@ -1266,7 +1277,10 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } pwallet->UpdateTimeFirstKey(timestamp); - } + } + for (const auto& entry : import_data.key_origins) { + pwallet->AddKeyOrigin(entry.second.first, entry.second.second); + } for (const CKeyID& id : ordered_pubkeys) { auto entry = pubkey_map.find(id); if (entry == pubkey_map.end()) { @@ -1277,10 +1291,6 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - const auto& key_orig_it = import_data.key_origins.find(id); - if (key_orig_it != import_data.key_origins.end()) { - pwallet->AddKeyOrigin(pubkey, key_orig_it->second); - } pwallet->mapKeyMetadata[id].nCreateTime = timestamp; // Add to keypool only works with pubkeys @@ -1468,7 +1478,11 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) } if (fRescan && fRunScan && requests.size()) { int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d32613fc77..0d804dcc10 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7,7 +7,6 @@ #include <chain.h> #include <consensus/validation.h> #include <core_io.h> -#include <httpserver.h> #include <init.h> #include <interfaces/chain.h> #include <validation.h> @@ -19,7 +18,7 @@ #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> -#include <rpc/rawtransaction.h> +#include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> @@ -27,8 +26,11 @@ #include <shutdown.h> #include <timedata.h> #include <util/bip32.h> +#include <util/fees.h> #include <util/system.h> #include <util/moneystr.h> +#include <util/url.h> +#include <util/validation.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/psbtwallet.h> @@ -308,7 +310,7 @@ static UniValue setlabel(const JSONRPCRequest& request) static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { - CAmount curBalance = pwallet->GetBalance(); + CAmount curBalance = pwallet->GetBalance().m_mine_trusted; // Check amount if (nValue <= 0) @@ -761,12 +763,14 @@ static UniValue getbalance(const JSONRPCRequest& request) min_depth = request.params[1].get_int(); } - isminefilter filter = ISMINE_SPENDABLE; + bool include_watchonly = false; if (!request.params[2].isNull() && request.params[2].get_bool()) { - filter = filter | ISMINE_WATCH_ONLY; + include_watchonly = true; } - return ValueFromAmount(pwallet->GetBalance(filter, min_depth)); + const auto bal = pwallet->GetBalance(min_depth); + + return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); } static UniValue getunconfirmedbalance(const JSONRPCRequest &request) @@ -794,7 +798,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetUnconfirmedBalance()); + return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); } @@ -807,9 +811,7 @@ static UniValue sendmany(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) - throw std::runtime_error( - RPCHelpMan{"sendmany", + const RPCHelpMan help{"sendmany", "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n", { @@ -819,7 +821,7 @@ static UniValue sendmany(const JSONRPCRequest& request) {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, }, }, - {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only use the balance confirmed at least this many times."}, + {"minconf", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "Ignored dummy value"}, {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment"}, {"subtractfeefrom", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array with addresses.\n" " The fee will be equally deducted from the amount of each selected address.\n" @@ -850,7 +852,11 @@ static UniValue sendmany(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -867,9 +873,6 @@ static UniValue sendmany(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } UniValue sendTo = request.params[1].get_obj(); - int nMinDepth = 1; - if (!request.params[2].isNull()) - nMinDepth = request.params[2].get_int(); mapValue_t mapValue; if (!request.params[3].isNull() && !request.params[3].get_str().empty()) @@ -897,7 +900,6 @@ static UniValue sendmany(const JSONRPCRequest& request) std::set<CTxDestination> destinations; std::vector<CRecipient> vecSend; - CAmount totalAmount = 0; std::vector<std::string> keys = sendTo.getKeys(); for (const std::string& name_ : keys) { CTxDestination dest = DecodeDestination(name_); @@ -914,7 +916,6 @@ static UniValue sendmany(const JSONRPCRequest& request) CAmount nAmount = AmountFromValue(sendTo[name_]); if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - totalAmount += nAmount; bool fSubtractFeeFromAmount = false; for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) { @@ -929,11 +930,6 @@ static UniValue sendmany(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - // Check funds - if (totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth)) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds"); - } - // Shuffle recipient list std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); @@ -1760,7 +1756,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); - std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); + std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); return entry; @@ -1978,7 +1974,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) // wallet before the following callback is called. If a valid shared pointer // is acquired in the callback then the wallet is still loaded. std::weak_ptr<CWallet> weak_wallet = wallet; - RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { + pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { if (auto shared_wallet = weak_wallet.lock()) { LOCK(shared_wallet->cs_wallet); shared_wallet->Lock(); @@ -2424,11 +2420,12 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + const auto bal = pwallet->GetBalance(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); - obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance())); - obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())); - obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())); + obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); + obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); + obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); obj.pushKV("txcount", (int)pwallet->mapWallet.size()); obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); @@ -2668,50 +2665,6 @@ static UniValue unloadwallet(const JSONRPCRequest& request) return NullUniValue; } -static UniValue resendwallettransactions(const JSONRPCRequest& request) -{ - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - RPCHelpMan{"resendwallettransactions", - "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" - "Intended only for testing; the wallet code periodically re-broadcasts\n" - "automatically.\n", - {}, - RPCResult{ - "Returns an RPC error if -walletbroadcast is set to false.\n" - "Returns array of transaction ids that were re-broadcast.\n" - }, - RPCExamples{""}, - }.ToString() - ); - - if (!pwallet->chain().p2pEnabled()) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - - auto locked_chain = pwallet->chain().lock(); - LOCK(pwallet->cs_wallet); - - if (!pwallet->GetBroadcastTransactions()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); - } - - std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime()); - UniValue result(UniValue::VARR); - for (const uint256& txid : txids) - { - result.push_back(txid.ToString()); - } - return result; -} - static UniValue listunspent(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -3150,7 +3103,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"}, {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"}, - {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"}, }, }, }, @@ -3214,9 +3167,9 @@ static UniValue bumpfee(const JSONRPCRequest& request) RPCHelpMan{"bumpfee", "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" - "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n" - "If the change output is not big enough to cover the increased fee, the command will currently fail\n" - "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" + "The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n" + "If `totalFee` is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" + "All inputs in the original transaction will be included in the replacement transaction.\n" "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" "By default, the new fee will be calculated automatically using estimatesmartfee.\n" "The user can specify a confirmation target for estimatesmartfee.\n" @@ -3313,7 +3266,14 @@ static UniValue bumpfee(const JSONRPCRequest& request) CAmount old_fee; CAmount new_fee; CMutableTransaction mtx; - feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx); + feebumper::Result res; + if (totalFee > 0) { + // Targeting total fee bump. Requires a change output of sufficient size. + res = feebumper::CreateTotalBumpTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx); + } else { + // Targeting feerate bump. + res = feebumper::CreateRateBumpTransaction(pwallet, hash, coin_control, errors, old_fee, new_fee, mtx); + } if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: @@ -3455,7 +3415,7 @@ class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> public: CWallet * const pwallet; - void ProcessSubScript(const CScript& subscript, UniValue& obj, bool include_addresses = false) const + void ProcessSubScript(const CScript& subscript, UniValue& obj) const { // Always present: script type and redeemscript std::vector<std::vector<unsigned char>> solutions_data; @@ -3464,7 +3424,6 @@ public: obj.pushKV("hex", HexStr(subscript.begin(), subscript.end())); CTxDestination embedded; - UniValue a(UniValue::VARR); if (ExtractDestination(subscript, embedded)) { // Only when the script corresponds to an address. UniValue subobj(UniValue::VOBJ); @@ -3477,7 +3436,6 @@ public: // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works. if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]); obj.pushKV("embedded", std::move(subobj)); - if (include_addresses) a.push_back(EncodeDestination(embedded)); } else if (which_type == TX_MULTISIG) { // Also report some information on multisig scripts (which do not have a corresponding address). // TODO: abstract out the common functionality between this logic and ExtractDestinations. @@ -3485,17 +3443,10 @@ public: UniValue pubkeys(UniValue::VARR); for (size_t i = 1; i < solutions_data.size() - 1; ++i) { CPubKey key(solutions_data[i].begin(), solutions_data[i].end()); - if (include_addresses) a.push_back(EncodeDestination(key.GetID())); pubkeys.push_back(HexStr(key.begin(), key.end())); } obj.pushKV("pubkeys", std::move(pubkeys)); } - - // The "addresses" field is confusing because it refers to public keys using their P2PKH address. - // For that reason, only add the 'addresses' field when needed for backward compatibility. New applications - // can use the 'embedded'->'address' field for P2SH or P2WSH wrapped addresses, and 'pubkeys' for - // inspecting multisig participants. - if (include_addresses) obj.pushKV("addresses", std::move(a)); } explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {} @@ -3518,7 +3469,7 @@ public: UniValue obj(UniValue::VOBJ); CScript subscript; if (pwallet && pwallet->GetCScript(scriptID, subscript)) { - ProcessSubScript(subscript, obj, IsDeprecatedRPCEnabled("validateaddress")); + ProcessSubScript(subscript, obj); } return obj; } @@ -4085,7 +4036,6 @@ UniValue importmulti(const JSONRPCRequest& request); static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- - { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 5c65acf601..b7ca746f8b 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -8,7 +8,7 @@ #include <amount.h> #include <primitives/transaction.h> #include <random.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> @@ -29,7 +29,7 @@ typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; static auto testChain = interfaces::MakeChain(); -static CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); +static CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); @@ -135,7 +135,6 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) ///////////////////////// // Known Outcome tests // ///////////////////////// - BOOST_TEST_MESSAGE("Testing known outcomes"); // Empty utxo pool BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index 2a64749379..d9b07af329 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -7,7 +7,7 @@ #include <boost/test/unit_test.hpp> #include <fs.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/db.h> diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index cd47b31da1..0f2d9fbd3d 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -6,7 +6,7 @@ #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #include <interfaces/chain.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> struct InitWalletDirTestingSetup: public BasicTestingSetup { diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 5852d3ef84..e1c53c83e2 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -4,7 +4,7 @@ #include <boost/test/unit_test.hpp> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/init_test_fixture.h> #include <init.h> diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 789e86e21b..f774cb4ad1 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -12,7 +12,7 @@ #include <univalue.h> #include <boost/test/unit_test.hpp> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/wallet_test_fixture.h> BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index ae7092fa89..acc61c984f 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <util/strencodings.h> #include <wallet/crypter.h> diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 6a051214a9..6526e69eea 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -8,12 +8,13 @@ #include <wallet/db.h> #include <wallet/rpcwallet.h> -WalletTestingSetup::WalletTestingSetup(const std::string& chainName): - TestingSetup(chainName), m_wallet(*m_chain, WalletLocation(), WalletDatabase::CreateMock()) +WalletTestingSetup::WalletTestingSetup(const std::string& chainName) + : TestingSetup(chainName), + m_wallet(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); - m_wallet.m_chain_notifications_handler = m_chain->handleNotifications(m_wallet); + m_wallet.handleNotifications(); m_chain_client->registerRpcs(); } diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index dcfbc55f94..1017e61700 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <interfaces/chain.h> #include <interfaces/wallet.h> diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index af57dbf5f6..3cdbde33c3 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -13,7 +13,7 @@ #include <consensus/validation.h> #include <interfaces/chain.h> #include <rpc/server.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/test/wallet_test_fixture.h> @@ -49,7 +49,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions accommodates a null start block. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -58,13 +58,13 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -73,7 +73,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); } // Prune the older block file. @@ -83,7 +83,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -92,7 +92,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); } // Prune the remaining block file. @@ -101,7 +101,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions scans no blocks. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -110,7 +110,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } } @@ -135,7 +135,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); @@ -198,7 +198,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Import key into wallet and call dumpwallet to create backup file. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); @@ -214,7 +214,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); @@ -245,7 +245,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { auto chain = interfaces::MakeChain(); - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LOCK(wallet.cs_wallet); @@ -340,7 +340,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); + wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); @@ -451,7 +451,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fd0297bae3..d076aa5e6f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,13 +1,11 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-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. #include <wallet/wallet.h> -#include <checkpoints.h> #include <chain.h> -#include <wallet/coincontrol.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> @@ -16,7 +14,6 @@ #include <key.h> #include <key_io.h> #include <keystore.h> -#include <validation.h> #include <net.h> #include <policy/fees.h> #include <policy/policy.h> @@ -29,7 +26,13 @@ #include <timedata.h> #include <txmempool.h> #include <util/bip32.h> +#include <util/error.h> +#include <util/fees.h> #include <util/moneystr.h> +#include <util/rbf.h> +#include <util/validation.h> +#include <validation.h> +#include <wallet/coincontrol.h> #include <wallet/fees.h> #include <algorithm> @@ -1276,10 +1279,13 @@ void CWallet::BlockDisconnected(const CBlock& block) { } } +void CWallet::UpdatedBlockTip() +{ + m_best_block_time = GetTime(); +} void CWallet::BlockUntilSyncedToCurrentChain() { - AssertLockNotHeld(cs_main); AssertLockNotHeld(cs_wallet); { @@ -1861,13 +1867,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc return result; } -void CWallet::ReacceptWalletTransactions() +void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) { // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) return; - auto locked_chain = chain().lock(); - LOCK(cs_wallet); std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order @@ -1877,7 +1881,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(*locked_chain); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1888,26 +1892,31 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); CValidationState state; - wtx.AcceptToMemoryPool(*locked_chain, state); + wtx.AcceptToMemoryPool(locked_chain, state); } } bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain) { - assert(pwallet->GetBroadcastTransactions()); - if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) - { - CValidationState state; - /* GetDepthInMainChain already catches known conflicts. */ - if (InMempool() || AcceptToMemoryPool(locked_chain, state)) { - pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); - if (pwallet->chain().p2pEnabled()) { - pwallet->chain().relayTransaction(GetHash()); - return true; - } - } - } - return false; + // Can't relay if wallet is not broadcasting + if (!pwallet->GetBroadcastTransactions()) return false; + // Don't relay coinbase transactions outside blocks + if (IsCoinBase()) return false; + // Don't relay abandoned transactions + if (isAbandoned()) return false; + // Don't relay conflicted or already confirmed transactions + if (GetDepthInMainChain(locked_chain) != 0) return false; + // Don't relay transactions that aren't accepted to the mempool + CValidationState unused_state; + if (!InMempool() && !AcceptToMemoryPool(locked_chain, unused_state)) return false; + // Don't try to relay if the node is not connected to the p2p network + if (!pwallet->chain().p2pEnabled()) return false; + + // Try to relay the transaction + pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); + pwallet->chain().relayTransaction(GetHash()); + + return true; } std::set<uint256> CWalletTx::GetConflicts() const @@ -2112,184 +2121,95 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const return CTransaction(tx1) == CTransaction(tx2); } -std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime) +// Rebroadcast transactions from the wallet. We do this on a random timer +// to slightly obfuscate which transactions come from our wallet. +// +// Ideally, we'd only resend transactions that we think should have been +// mined in the most recent block. Any transaction that wasn't in the top +// blockweight of transactions in the mempool shouldn't have been mined, +// and so is probably just sitting in the mempool waiting to be confirmed. +// Rebroadcasting does nothing to speed up confirmation and only damages +// privacy. +void CWallet::ResendWalletTransactions() { - std::vector<uint256> result; - - LOCK(cs_wallet); - - // Sort them in chronological order - std::multimap<unsigned int, CWalletTx*> mapSorted; - for (std::pair<const uint256, CWalletTx>& item : mapWallet) - { - CWalletTx& wtx = item.second; - // Don't rebroadcast if newer than nTime: - if (wtx.nTimeReceived > nTime) - continue; - mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx)); - } - for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted) - { - CWalletTx& wtx = *item.second; - if (wtx.RelayWalletTransaction(locked_chain)) { - result.push_back(wtx.GetHash()); - } - } - return result; -} + // During reindex, importing and IBD, old wallet transactions become + // unconfirmed. Don't resend them as that would spam other nodes. + if (!chain().isReadyToBroadcast()) return; -void CWallet::ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) -{ // Do this infrequently and randomly to avoid giving away // that these are our transactions. - if (GetTime() < nNextResend || !fBroadcastTransactions) - return; + if (GetTime() < nNextResend || !fBroadcastTransactions) return; bool fFirst = (nNextResend == 0); nNextResend = GetTime() + GetRand(30 * 60); - if (fFirst) - return; + if (fFirst) return; // Only do it if there's been a new block since last time - if (nBestBlockTime < nLastResend) - return; + if (m_best_block_time < nLastResend) return; nLastResend = GetTime(); - // Rebroadcast unconfirmed txes older than 5 minutes before the last - // block was found: - std::vector<uint256> relayed = ResendWalletTransactionsBefore(locked_chain, nBestBlockTime-5*60); - if (!relayed.empty()) - WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); -} - -/** @} */ // end of mapWallet - - - - -/** @defgroup Actions - * - * @{ - */ - + int relayed_tx_count = 0; -CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const -{ - CAmount nTotal = 0; - { + { // locked_chain and cs_wallet scope auto locked_chain = chain().lock(); LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) { - nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter); - } - } - } - - return nTotal; -} -CAmount CWallet::GetUnconfirmedBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(*locked_chain); + // Relay transactions + for (std::pair<const uint256, CWalletTx>& item : mapWallet) { + CWalletTx& wtx = item.second; + // only rebroadcast unconfirmed txes older than 5 minutes before the + // last block was found + if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count; } - } - return nTotal; -} + } // locked_chain and cs_wallet -CAmount CWallet::GetImmatureBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureCredit(*locked_chain); - } + if (relayed_tx_count > 0) { + WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count); } - return nTotal; } -CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const +/** @} */ // end of mapWallet + +void MaybeResendWalletTxs() { - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); - } + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->ResendWalletTransactions(); } - return nTotal; } -CAmount CWallet::GetImmatureWatchOnlyBalance() const + +/** @defgroup Actions + * + * @{ + */ + + +CWallet::Balance CWallet::GetBalance(const int min_depth) const { - CAmount nTotal = 0; + Balance ret; { auto locked_chain = chain().lock(); LOCK(cs_wallet); for (const auto& entry : mapWallet) { - const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain); - } - } - return nTotal; -} - -// Calculate total balance in a different way from GetBalance. The biggest -// difference is that GetBalance sums up all unspent TxOuts paying to the -// wallet, while this sums up both spent and unspent TxOuts paying to the -// wallet, and then subtracts the values of TxIns spending from the wallet. This -// also has fewer restrictions on which unconfirmed transactions are considered -// trusted. -CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const -{ - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - - CAmount balance = 0; - for (const auto& entry : mapWallet) { - const CWalletTx& wtx = entry.second; - const int depth = wtx.GetDepthInMainChain(*locked_chain); - if (depth < 0 || !locked_chain->checkFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) { - continue; - } - - // Loop through tx outputs and add incoming payments. For outgoing txs, - // treat change outputs specially, as part of the amount debited. - CAmount debit = wtx.GetDebit(filter); - const bool outgoing = debit > 0; - for (const CTxOut& out : wtx.tx->vout) { - if (outgoing && IsChange(out)) { - debit -= out.nValue; - } else if (IsMine(out) & filter && depth >= minDepth) { - balance += out.nValue; + const CWalletTx& wtx = entry.second; + const bool is_trusted{wtx.IsTrusted(*locked_chain)}; + const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; + const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE)}; + const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY)}; + if (is_trusted && tx_depth >= min_depth) { + ret.m_mine_trusted += tx_credit_mine; + ret.m_watchonly_trusted += tx_credit_watchonly; } - } - - // For outgoing txs, subtract amount debited. - if (outgoing) { - balance -= debit; + if (!is_trusted && tx_depth == 0 && wtx.InMempool()) { + ret.m_mine_untrusted_pending += tx_credit_mine; + ret.m_watchonly_untrusted_pending += tx_credit_watchonly; + } + ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); + ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); } } - - return balance; + return ret; } CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const @@ -2318,25 +2238,25 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< for (const auto& entry : mapWallet) { const uint256& wtxid = entry.first; - const CWalletTx* pcoin = &entry.second; + const CWalletTx& wtx = entry.second; - if (!locked_chain.checkFinalTx(*pcoin->tx)) { + if (!locked_chain.checkFinalTx(*wtx.tx)) { continue; } - if (pcoin->IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < 0) continue; // We should not consider coins which aren't at least in our mempool // It's possible for these to be conflicted via ancestors which we may never be able to detect - if (nDepth == 0 && !pcoin->InMempool()) + if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = pcoin->IsTrusted(locked_chain); + bool safeTx = wtx.IsTrusted(locked_chain); // We should not consider coins from transactions that are replacing // other transactions. @@ -2353,7 +2273,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // be a 1-block reorg away from the chain where transactions A and C // were accepted to another chain where B, B', and C were all // accepted. - if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) { + if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) { safeTx = false; } @@ -2365,7 +2285,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // intending to replace A', but potentially resulting in a scenario // where A, A', and D could all be accepted (instead of just B and // D, or just A and A' like the user would want). - if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { + if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { safeTx = false; } @@ -2376,8 +2296,8 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (nDepth < nMinDepth || nDepth > nMaxDepth) continue; - for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { - if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount) + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) continue; if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) @@ -2389,20 +2309,20 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (IsSpent(locked_chain, wtxid, i)) continue; - isminetype mine = IsMine(pcoin->tx->vout[i]); + isminetype mine = IsMine(wtx.tx->vout[i]); if (mine == ISMINE_NO) { continue; } - bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey); + bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { - nTotal += pcoin->tx->vout[i].nValue; + nTotal += wtx.tx->vout[i].nValue; if (nTotal >= nMinimumSumAmount) { return; @@ -2560,13 +2480,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { - const CWalletTx* pcoin = &it->second; + const CWalletTx& wtx = it->second; // Clearly invalid input, fail - if (pcoin->tx->vout.size() <= outpoint.n) + if (wtx.tx->vout.size() <= outpoint.n) return false; // Just to calculate the marginal byte size - nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n)); + nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue; + setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n)); } else return false; // TODO: Allow non-wallet inputs } @@ -2815,7 +2735,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0, coin_control.m_min_depth); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -3195,7 +3115,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); fFirstRunRet = false; @@ -3604,27 +3523,27 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: LOCK(cs_wallet); for (const auto& walletEntry : mapWallet) { - const CWalletTx *pcoin = &walletEntry.second; + const CWalletTx& wtx = walletEntry.second; - if (!pcoin->IsTrusted(locked_chain)) + if (!wtx.IsTrusted(locked_chain)) continue; - if (pcoin->IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(locked_chain); - if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) + int nDepth = wtx.GetDepthInMainChain(locked_chain); + if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; - for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; - if (!IsMine(pcoin->tx->vout[i])) + if (!IsMine(wtx.tx->vout[i])) continue; - if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) + if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3644,13 +3563,13 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() for (const auto& walletEntry : mapWallet) { - const CWalletTx *pcoin = &walletEntry.second; + const CWalletTx& wtx = walletEntry.second; - if (pcoin->tx->vin.size() > 0) + if (wtx.tx->vin.size() > 0) { bool any_mine = false; // group all input addresses with each other - for (const CTxIn& txin : pcoin->tx->vin) + for (const CTxIn& txin : wtx.tx->vin) { CTxDestination address; if(!IsMine(txin)) /* If this input isn't mine, ignore it */ @@ -3664,7 +3583,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() // group change with input addresses if (any_mine) { - for (const CTxOut& txout : pcoin->tx->vout) + for (const CTxOut& txout : wtx.tx->vout) if (IsChange(txout)) { CTxDestination txoutAddr; @@ -3681,7 +3600,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() } // group lone addrs by themselves - for (const auto& txout : pcoin->tx->vout) + for (const auto& txout : wtx.tx->vout) if (IsMine(txout)) { CTxDestination address; @@ -4059,7 +3978,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b if (salvage_wallet) { // Recover readable keypairs: - CWallet dummyWallet(chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; @@ -4079,7 +3998,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.GetBoolArg("-zapwallettxes", false)) { chain.initMessage(_("Zapping all transactions from wallet...")); - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(chain, location, WalletDatabase::Create(location.GetPath())); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { chain.initError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); @@ -4093,7 +4012,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -4383,7 +4302,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, chain.loadWallet(interfaces::MakeWallet(walletInstance)); // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. - walletInstance->m_chain_notifications_handler = chain.handleNotifications(*walletInstance); + walletInstance->handleNotifications(); walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); @@ -4396,11 +4315,22 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return walletInstance; } +void CWallet::handleNotifications() +{ + m_chain_notifications_handler = m_chain->handleNotifications(*this); +} + void CWallet::postInitProcess() { + auto locked_chain = chain().lock(); + LOCK(cs_wallet); + // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded - ReacceptWalletTransactions(); + ReacceptWalletTransactions(*locked_chain); + + // Update wallet transactions with current mempool transactions. + chain().requestMempoolTransactions(*this); } bool CWallet::BackupWallet(const std::string& strDest) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 86448efcaf..62ba0aa962 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -11,16 +11,16 @@ #include <interfaces/handler.h> #include <outputtype.h> #include <policy/feerate.h> +#include <script/ismine.h> +#include <script/sign.h> #include <streams.h> #include <tinyformat.h> #include <ui_interface.h> #include <util/strencodings.h> -#include <validationinterface.h> -#include <script/ismine.h> -#include <script/sign.h> #include <util/system.h> -#include <wallet/crypter.h> +#include <validationinterface.h> #include <wallet/coinselection.h> +#include <wallet/crypter.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -35,26 +35,6 @@ #include <utility> #include <vector> -//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. -//! This function will perform salvage on the wallet if requested, as long as only one wallet is -//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). -bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); - -//! Load wallet databases. -bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); - -//! Complete startup of wallets. -void StartWallets(CScheduler& scheduler); - -//! Flush all wallets in preparation for shutdown. -void FlushWallets(); - -//! Stop all wallets. Wallets will be flushed first. -void StopWallets(); - -//! Close all wallets. -void UnloadWallets(); - //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. @@ -535,7 +515,7 @@ public: int64_t GetTxTime() const; - // RelayWalletTransaction may only be called if fBroadcastTransactions! + // Pass this transaction to the node to relay to its peers bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain); /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ @@ -657,6 +637,8 @@ private: int64_t nNextResend = 0; int64_t nLastResend = 0; bool fBroadcastTransactions = false; + // Local time that the tip block was received. Used to schedule wallet rebroadcasts. + std::atomic<int64_t> m_best_block_time {0}; /** * Used to keep track of spent outpoints, and @@ -722,7 +704,7 @@ private: bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Interface for accessing chain state. */ - interfaces::Chain& m_chain; + interfaces::Chain* m_chain; /** Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; @@ -785,7 +767,10 @@ public: unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(interfaces::Chain& chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_chain(chain), m_location(location), database(std::move(database)) + CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) + : m_chain(chain), + m_location(location), + database(std::move(database)) { } @@ -812,8 +797,11 @@ public: /** Registered interfaces::Chain::Notifications handler. */ std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; + /** Register the wallet for chain notifications */ + void handleNotifications(); + /** Interface for accessing chain state. */ - interfaces::Chain& chain() const { return m_chain; } + interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -926,6 +914,7 @@ public: void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const CBlock& block) override; + void UpdatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); struct ScanResult { @@ -945,16 +934,17 @@ public: }; ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; - void ReacceptWalletTransactions(); - void ResendWalletTransactions(interfaces::Chain::Lock& locked_chain, int64_t nBestBlockTime) override; - // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! - std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime); - CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; - CAmount GetUnconfirmedBalance() const; - CAmount GetImmatureBalance() const; - CAmount GetUnconfirmedWatchOnlyBalance() const; - CAmount GetImmatureWatchOnlyBalance() const; - CAmount GetLegacyBalance(const isminefilter& filter, int minDepth) const; + void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void ResendWalletTransactions(); + struct Balance { + CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more + CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) + CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_watchonly_trusted{0}; + CAmount m_watchonly_untrusted_pending{0}; + CAmount m_watchonly_immature{0}; + }; + Balance GetBalance(int min_depth = 0) const; CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend); @@ -1224,12 +1214,16 @@ public: /** Add a KeyOriginInfo to the wallet */ bool AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info); - - friend struct WalletTestingSetup; }; +/** + * Called periodically by the schedule thread. Prompts individual wallets to resend + * their transactions. Actual rebroadcast schedule is managed by the wallets themselves. + */ +void MaybeResendWalletTxs(); + /** A key allocated from the key pool. */ -class CReserveKey final : public CReserveScript +class CReserveKey { protected: CWallet* pwallet; @@ -1254,7 +1248,6 @@ public: void ReturnKey(); bool GetReservedKey(CPubKey &pubkey, bool internal = false); void KeepKey(); - void KeepScript() override { KeepKey(); } }; /** RAII object to check and reserve a wallet rescan */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5149bb0263..3122cd6fa4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -5,7 +5,7 @@ #include <wallet/walletdb.h> -#include <consensus/tx_verify.h> +#include <consensus/tx_check.h> #include <consensus/validation.h> #include <fs.h> #include <key_io.h> diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 797f051189..1ff1e8b840 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -4,7 +4,6 @@ #include <base58.h> #include <fs.h> -#include <interfaces/chain.h> #include <util/system.h> #include <wallet/wallet.h> #include <wallet/walletutil.h> @@ -28,8 +27,7 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs:: return nullptr; } // dummy chain interface - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet_instance(new CWallet(*chain, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); + std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); bool first_run = true; DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run); if (load_wallet_ret != DBErrors::LOAD_OK) { @@ -56,8 +54,7 @@ static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::pa } // dummy chain interface - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet_instance(new CWallet(*chain, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); + std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); DBErrors load_wallet_ret; try { bool first_run; |