diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/coincontrol.h | 20 | ||||
-rw-r--r-- | src/wallet/db.h | 4 | ||||
-rw-r--r-- | src/wallet/feebumper.cpp | 7 | ||||
-rw-r--r-- | src/wallet/feebumper.h | 4 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 18 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 349 | ||||
-rw-r--r-- | src/wallet/rpcwallet.h | 2 | ||||
-rw-r--r-- | src/wallet/test/crypto_tests.cpp | 4 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 8 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 526 | ||||
-rw-r--r-- | src/wallet/wallet.h | 46 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 18 |
12 files changed, 655 insertions, 351 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index cb4719ae90..fc0e7c519e 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,9 +6,12 @@ #define BITCOIN_WALLET_COINCONTROL_H #include "policy/feerate.h" +#include "policy/fees.h" #include "primitives/transaction.h" #include "wallet/wallet.h" +#include <boost/optional.hpp> + /** Coin Control Features. */ class CCoinControl { @@ -18,14 +21,16 @@ public: bool fAllowOtherInputs; //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria bool fAllowWatchOnly; - //! Override estimated feerate + //! Override automatic min/max checks on fee, m_feerate must be set if true bool fOverrideFeeRate; - //! Feerate to use if overrideFeeRate is true - CFeeRate nFeeRate; - //! Override the default confirmation target, 0 = use default - int nConfirmTarget; + //! Override the default payTxFee if set + boost::optional<CFeeRate> m_feerate; + //! Override the default confirmation target if set + boost::optional<unsigned int> m_confirm_target; //! Signal BIP-125 replace by fee. bool signalRbf; + //! Fee estimation mode to control arguments to estimateSmartFee + FeeEstimateMode m_fee_mode; CCoinControl() { @@ -38,10 +43,11 @@ public: fAllowOtherInputs = false; fAllowWatchOnly = false; setSelected.clear(); - nFeeRate = CFeeRate(0); + m_feerate.reset(); fOverrideFeeRate = false; - nConfirmTarget = 0; + m_confirm_target.reset(); signalRbf = fWalletRbf; + m_fee_mode = FeeEstimateMode::UNSET; } bool HasSelected() const diff --git a/src/wallet/db.h b/src/wallet/db.h index 7cccc65660..4f3ad0c42d 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -95,13 +95,13 @@ class CWalletDBWrapper friend class CDB; public: /** Create dummy DB handle */ - CWalletDBWrapper() : nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr) + CWalletDBWrapper() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr) { } /** Create DB handle to real database */ CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in) : - nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in) + nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in) { } diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 6a9e6cf9ff..4bfd8726a5 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "consensus/validation.h" +#include "wallet/coincontrol.h" #include "wallet/feebumper.h" #include "wallet/wallet.h" #include "policy/fees.h" @@ -66,7 +67,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx return true; } -CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable) +CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee) : txid(std::move(txidIn)), nOldFee(0), @@ -165,7 +166,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf nNewFee = totalFee; nNewFeeRate = CFeeRate(totalFee, maxNewTxSize); } else { - nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr, ignoreGlobalPayTxFee); + nNewFee = CWallet::GetMinimumFee(maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */); nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); // New fee rate must be at least old rate + minimum incremental relay rate @@ -220,7 +221,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf } // Mark new tx not replaceable, if requested. - if (!newTxReplaceable) { + if (!coin_control.signalRbf) { for (auto& input : mtx.vin) { if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; } diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index fc32316704..3d64e53c15 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -10,6 +10,8 @@ class CWallet; class CWalletTx; class uint256; +class CCoinControl; +enum class FeeEstimateMode; enum class BumpFeeResult { @@ -24,7 +26,7 @@ enum class BumpFeeResult class CFeeBumper { public: - CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable); + CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee); BumpFeeResult getResult() const { return currentResult; } const std::vector<std::string>& getErrors() const { return vErrors; } CAmount getOldFee() const { return nOldFee; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9f42b1f266..5abf32480a 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -106,12 +106,12 @@ UniValue importprivkey(const JSONRPCRequest& request) std::string strSecret = request.params[0].get_str(); std::string strLabel = ""; - if (request.params.size() > 1) + if (!request.params[1].isNull()) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; - if (request.params.size() > 2) + if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) @@ -245,12 +245,12 @@ UniValue importaddress(const JSONRPCRequest& request) std::string strLabel = ""; - if (request.params.size() > 1) + if (!request.params[1].isNull()) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; - if (request.params.size() > 2) + if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) @@ -258,7 +258,7 @@ UniValue importaddress(const JSONRPCRequest& request) // Whether to import a p2sh version, too bool fP2SH = false; - if (request.params.size() > 3) + if (!request.params[3].isNull()) fP2SH = request.params[3].get_bool(); LOCK2(cs_main, pwallet->cs_wallet); @@ -410,12 +410,12 @@ UniValue importpubkey(const JSONRPCRequest& request) std::string strLabel = ""; - if (request.params.size() > 1) + if (!request.params[1].isNull()) strLabel = request.params[1].get_str(); // Whether to perform rescan after import bool fRescan = true; - if (request.params.size() > 2) + if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); if (fRescan && fPruneMode) @@ -1028,7 +1028,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( - "importmulti \"requests\" \"options\"\n\n" + "importmulti \"requests\" ( \"options\" )\n\n" "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n" "Arguments:\n" "1. requests (array, required) Data to be imported\n" @@ -1071,7 +1071,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) //Default options bool fRescan = true; - if (mainRequest.params.size() > 1) { + if (!mainRequest.params[1].isNull()) { const UniValue& options = mainRequest.params[1]; if (options.exists("rescan")) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 246d81fd5b..a7c229fa74 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,6 +9,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "validation.h" #include "net.h" #include "policy/feerate.h" @@ -30,10 +31,21 @@ #include <univalue.h> +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; + CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) { - // TODO: Some way to access secondary wallets - return vpwallets.empty() ? nullptr : vpwallets[0]; + if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { + // wallet endpoint was used + std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + for (CWalletRef pwallet : ::vpwallets) { + if (pwallet->GetName() == requestedWallet) { + return pwallet; + } + } + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr; } std::string HelpRequiringPassphrase(CWallet * const pwallet) @@ -45,13 +57,19 @@ std::string HelpRequiringPassphrase(CWallet * const pwallet) bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) { - if (!pwallet) { - if (!avoidException) - throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); - else - return false; - } - return true; + if (pwallet) return true; + if (avoidException) return false; + if (::vpwallets.empty()) { + // Note: It isn't currently possible to trigger this error because + // wallet RPC methods aren't registered unless a wallet is loaded. But + // this error is being kept as a precaution, because it's possible in + // the future that wallet RPC methods might get or remain registered + // when no wallets are loaded. + throw JSONRPCError( + RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)"); + } + throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, + "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } void EnsureWalletIsUnlocked(CWallet * const pwallet) @@ -134,7 +152,7 @@ UniValue getnewaddress(const JSONRPCRequest& request) // Parse the account first so we don't generate a key if there's an error std::string strAccount; - if (request.params.size() > 0) + if (!request.params[0].isNull()) strAccount = AccountFromValue(request.params[0]); if (!pwallet->IsLocked()) { @@ -205,7 +223,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) + if (request.fHelp || request.params.size() > 0) throw std::runtime_error( "getrawchangeaddress\n" "\nReturns a new Bitcoin address, for receiving change.\n" @@ -356,7 +374,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request) return ret; } -static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew) +static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, const CCoinControl& coin_control) { CAmount curBalance = pwallet->GetBalance(); @@ -382,7 +400,7 @@ static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CA int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) { + if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); throw JSONRPCError(RPC_WALLET_ERROR, strError); @@ -401,9 +419,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error( - "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount )\n" + "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n" "\nSend an amount to a given address.\n" + HelpRequiringPassphrase(pwallet) + "\nArguments:\n" @@ -416,6 +434,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request) " transaction, just kept in your wallet.\n" "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n" " The recipient will receive less bitcoins than you enter in the amount field.\n" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" "\nResult:\n" "\"txid\" (string) The transaction id.\n" "\nExamples:\n" @@ -444,12 +468,29 @@ UniValue sendtoaddress(const JSONRPCRequest& request) wtx.mapValue["to"] = request.params[3].get_str(); bool fSubtractFeeFromAmount = false; - if (request.params.size() > 4) + if (request.params.size() > 4 && !request.params[4].isNull()) { fSubtractFeeFromAmount = request.params[4].get_bool(); + } + + CCoinControl coin_control; + if (request.params.size() > 5 && !request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (request.params.size() > 6 && !request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (request.params.size() > 7 && !request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + EnsureWalletIsUnlocked(pwallet); - SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx); + SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, coin_control); return wtx.GetHash().GetHex(); } @@ -461,7 +502,7 @@ UniValue listaddressgroupings(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp) + if (request.fHelp || request.params.size() != 0) throw std::runtime_error( "listaddressgroupings\n" "\nLists groups of addresses which have had their common ownership\n" @@ -587,7 +628,7 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + "\nThe amount including unconfirmed transactions, zero confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") + - "\nThe amount with at least 6 confirmation, very safe\n" + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") @@ -606,7 +647,7 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) // Minimum confirmations int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); // Tally @@ -647,7 +688,7 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbyaccount", "\"\"") + "\nAmount received at the tabby account including unconfirmed amounts with zero confirmations\n" + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") + - "\nThe amount with at least 6 confirmation, very safe\n" + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6") @@ -657,7 +698,7 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request) // Minimum confirmations int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); // Get the set of pub keys assigned to account @@ -717,9 +758,9 @@ UniValue getbalance(const JSONRPCRequest& request) "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "\nExamples:\n" - "\nThe total amount in the wallet\n" + "\nThe total amount in the wallet with 1 or more confirmations\n" + HelpExampleCli("getbalance", "") + - "\nThe total amount in the wallet at least 5 blocks confirmed\n" + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getbalance", "\"*\", 6") @@ -734,10 +775,10 @@ UniValue getbalance(const JSONRPCRequest& request) const std::string* account = account_param != "*" ? &account_param : nullptr; int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 2) + if(!request.params[2].isNull()) if(request.params[2].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -875,7 +916,8 @@ UniValue sendfrom(const JSONRPCRequest& request) if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); - SendMoney(pwallet, address.Get(), nAmount, false, wtx); + CCoinControl no_coin_control; // This is a deprecated API + SendMoney(pwallet, address.Get(), nAmount, false, wtx, no_coin_control); return wtx.GetHash().GetHex(); } @@ -888,9 +930,9 @@ UniValue sendmany(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error( - "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n" + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n" "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" @@ -910,7 +952,13 @@ UniValue sendmany(const JSONRPCRequest& request) " \"address\" (string) Subtract fee from this address\n" " ,...\n" " ]\n" - "\nResult:\n" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + "\nResult:\n" "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" "\nExamples:\n" @@ -933,7 +981,7 @@ UniValue sendmany(const JSONRPCRequest& request) std::string strAccount = AccountFromValue(request.params[0]); UniValue sendTo = request.params[1].get_obj(); int nMinDepth = 1; - if (request.params.size() > 2) + if (!request.params[2].isNull()) nMinDepth = request.params[2].get_int(); CWalletTx wtx; @@ -942,9 +990,24 @@ UniValue sendmany(const JSONRPCRequest& request) wtx.mapValue["comment"] = request.params[3].get_str(); UniValue subtractFeeFromAmount(UniValue::VARR); - if (request.params.size() > 4) + if (request.params.size() > 4 && !request.params[4].isNull()) subtractFeeFromAmount = request.params[4].get_array(); + CCoinControl coin_control; + if (request.params.size() > 5 && !request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (request.params.size() > 6 && !request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (request.params.size() > 7 && !request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + std::set<CBitcoinAddress> setAddress; std::vector<CRecipient> vecSend; @@ -989,7 +1052,7 @@ UniValue sendmany(const JSONRPCRequest& request) CAmount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; - bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason); + bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; @@ -1173,16 +1236,16 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA { // Minimum confirmations int nMinDepth = 1; - if (params.size() > 0) + if (!params[0].isNull()) nMinDepth = params[0].get_int(); // Whether to include empty accounts bool fIncludeEmpty = false; - if (params.size() > 1) + if (!params[1].isNull()) fIncludeEmpty = params[1].get_bool(); isminefilter filter = ISMINE_SPENDABLE; - if(params.size() > 2) + if(!params[2].isNull()) if(params[2].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -1377,6 +1440,17 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) entry.push_back(Pair("address", addr.ToString())); } +/** + * List transactions based on the given criteria. + * + * @param pwallet The wallet. + * @param wtx The wallet transaction. + * @param strAccount The account, if any, or "*" for all. + * @param nMinDepth The minimum confirmation depth. + * @param fLong Whether to include the JSON version of the transaction. + * @param ret The UniValue into which the result is stored. + * @param filter The "is mine" filter bool. + */ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) { CAmount nFee; @@ -1544,16 +1618,16 @@ UniValue listtransactions(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); std::string strAccount = "*"; - if (request.params.size() > 0) + if (!request.params[0].isNull()) strAccount = request.params[0].get_str(); int nCount = 10; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nCount = request.params[1].get_int(); int nFrom = 0; - if (request.params.size() > 2) + if (!request.params[2].isNull()) nFrom = request.params[2].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 3) + if(!request.params[3].isNull()) if(request.params[3].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -1693,14 +1767,18 @@ UniValue listsinceblock(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp) + if (request.fHelp || request.params.size() > 4) throw std::runtime_error( - "listsinceblock ( \"blockhash\" target_confirmations include_watchonly)\n" - "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n" + "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n" + "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n" + "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" + "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n" "\nArguments:\n" "1. \"blockhash\" (string, optional) The block hash to list transactions since\n" - "2. target_confirmations: (numeric, optional) The confirmations required, must be 1 or more\n" - "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')" + "2. target_confirmations: (numeric, optional, default=1) Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value\n" + "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" + "4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n" + " (not guaranteed to work on pruned nodes)\n" "\nResult:\n" "{\n" " \"transactions\": [\n" @@ -1725,8 +1803,12 @@ UniValue listsinceblock(const JSONRPCRequest& request) " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n" " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n" - " ],\n" - " \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n" + " ],\n" + " \"removed\": [\n" + " <structure is the same as \"transactions\" above, only present if include_removed=true>\n" + " Note: transactions that were readded in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n" + " ],\n" + " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n" "}\n" "\nExamples:\n" + HelpExampleCli("listsinceblock", "") @@ -1736,21 +1818,19 @@ UniValue listsinceblock(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - const CBlockIndex *pindex = NULL; + const CBlockIndex* pindex = NULL; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. + const CBlockIndex* paltindex = NULL; // Block index of the specified block, even if it's in a deactivated chain. int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; - if (request.params.size() > 0) - { + if (!request.params[0].isNull()) { uint256 blockId; blockId.SetHex(request.params[0].get_str()); BlockMap::iterator it = mapBlockIndex.find(blockId); - if (it != mapBlockIndex.end()) - { - pindex = it->second; - if (chainActive[pindex->nHeight] != pindex) - { + if (it != mapBlockIndex.end()) { + paltindex = pindex = it->second; + if (chainActive[pindex->nHeight] != pindex) { // the block being asked for is a part of a deactivated chain; // we don't want to depend on its perceived height in the block // chain, we want to instead use the last common ancestor @@ -1759,19 +1839,20 @@ UniValue listsinceblock(const JSONRPCRequest& request) } } - if (request.params.size() > 1) - { + if (!request.params[1].isNull()) { target_confirms = request.params[1].get_int(); - if (target_confirms < 1) + if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); + } } - if (request.params.size() > 2 && request.params[2].get_bool()) - { + if (!request.params[2].isNull() && request.params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } + bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); + int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; UniValue transactions(UniValue::VARR); @@ -1779,8 +1860,27 @@ UniValue listsinceblock(const JSONRPCRequest& request) for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) + if (depth == -1 || tx.GetDepthInMainChain() < depth) { ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); + } + } + + // when a reorg'd block is requested, we also list any relevant transactions + // in the blocks of the chain that was detached + UniValue removed(UniValue::VARR); + while (include_removed && paltindex && paltindex != pindex) { + CBlock block; + if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + } + for (const CTransactionRef& tx : block.vtx) { + if (pwallet->mapWallet.count(tx->GetHash()) > 0) { + // We want all transactions regardless of confirmation count to appear here, + // even negative confirmation ones, hence the big negative. + ListTransactions(pwallet, pwallet->mapWallet[tx->GetHash()], "*", -100000000, true, removed, filter); + } + } + paltindex = paltindex->pprev; } CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; @@ -1788,6 +1888,7 @@ UniValue listsinceblock(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); ret.push_back(Pair("transactions", transactions)); + if (include_removed) ret.push_back(Pair("removed", removed)); ret.push_back(Pair("lastblock", lastblock.GetHex())); return ret; @@ -1851,7 +1952,7 @@ UniValue gettransaction(const JSONRPCRequest& request) hash.SetHex(request.params[0].get_str()); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 1) + if(!request.params[1].isNull()) if(request.params[1].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -1973,7 +2074,7 @@ UniValue keypoolrefill(const JSONRPCRequest& request) // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; - if (request.params.size() > 0) { + if (!request.params[0].isNull()) { if (request.params[0].get_int() < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); kpSize = (unsigned int)request.params[0].get_int(); @@ -2016,7 +2117,7 @@ UniValue walletpassphrase(const JSONRPCRequest& request) "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" "time that overrides the old one.\n" "\nExamples:\n" - "\nunlock the wallet for 60 seconds\n" + "\nUnlock the wallet for 60 seconds\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + @@ -2171,11 +2272,11 @@ UniValue encryptwallet(const JSONRPCRequest& request) "\nArguments:\n" "1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n" "\nExamples:\n" - "\nEncrypt you wallet\n" + "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + - "\nNow we can so something like sign\n" + "\nNow we can do something like sign\n" + HelpExampleCli("signmessage", "\"address\" \"test message\"") + "\nNow lock the wallet again by removing the passphrase\n" + HelpExampleCli("walletlock", "") + @@ -2398,6 +2499,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) "Returns an object containing various wallet state info.\n" "\nResult:\n" "{\n" + " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" @@ -2420,6 +2522,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + obj.push_back(Pair("walletname", pwallet->GetName())); obj.push_back(Pair("walletversion", pwallet->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance()))); obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()))); @@ -2440,6 +2543,39 @@ UniValue getwalletinfo(const JSONRPCRequest& request) return obj; } +UniValue listwallets(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "listwallets\n" + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n" + "\nResult:\n" + "[ (json array of strings)\n" + " \"walletname\" (string) the wallet name\n" + " ...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("listwallets", "") + + HelpExampleRpc("listwallets", "") + ); + + UniValue obj(UniValue::VARR); + + for (CWalletRef pwallet : vpwallets) { + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + LOCK(pwallet->cs_wallet); + + obj.push_back(pwallet->GetName()); + } + + return obj; +} + UniValue resendwallettransactions(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -2566,7 +2702,7 @@ UniValue listunspent(const JSONRPCRequest& request) CAmount nMinimumSumAmount = MAX_MONEY; uint64_t nMaximumCount = 0; - if (request.params.size() > 4) { + if (!request.params[4].isNull()) { const UniValue& options = request.params[4].get_obj(); if (options.exists("minimumAmount")) @@ -2635,7 +2771,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( "fundrawtransaction \"hexstring\" ( options )\n" "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" @@ -2656,7 +2792,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " \"changePosition\" (numeric, optional, default random) The index of the change output\n" " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n" " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n" " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" @@ -2666,6 +2801,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " [vout_index,...]\n" " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" " }\n" " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" "\nResult:\n" @@ -2688,17 +2828,12 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) RPCTypeCheck(request.params, {UniValue::VSTR}); CCoinControl coinControl; - coinControl.destChange = CNoDestination(); int changePosition = -1; - coinControl.fAllowWatchOnly = false; // include watching bool lockUnspents = false; - bool reserveChangeKey = true; - coinControl.nFeeRate = CFeeRate(0); - coinControl.fOverrideFeeRate = false; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; - if (request.params.size() > 1) { + if (!request.params[1].isNull()) { if (request.params[1].type() == UniValue::VBOOL) { // backward compatibility bool only fallback coinControl.fAllowWatchOnly = request.params[1].get_bool(); @@ -2714,10 +2849,12 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, - {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, + {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. {"feeRate", UniValueType()}, // will be checked below {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"replaceable", UniValueType(UniValue::VBOOL)}, + {"conf_target", UniValueType(UniValue::VNUM)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, }, true, true); @@ -2739,12 +2876,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) if (options.exists("lockUnspents")) lockUnspents = options["lockUnspents"].get_bool(); - if (options.exists("reserveChangeKey")) - reserveChangeKey = options["reserveChangeKey"].get_bool(); - if (options.exists("feeRate")) { - coinControl.nFeeRate = CFeeRate(AmountFromValue(options["feeRate"])); + coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); coinControl.fOverrideFeeRate = true; } @@ -2754,6 +2888,20 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) if (options.exists("replaceable")) { coinControl.signalRbf = options["replaceable"].get_bool(); } + if (options.exists("conf_target")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate"); + } + coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]); + } + if (options.exists("estimate_mode")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate"); + } + if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } } } @@ -2782,7 +2930,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) { + if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } @@ -2831,6 +2979,10 @@ UniValue bumpfee(const JSONRPCRequest& request) " so the new transaction will not be explicitly bip-125 replaceable (though it may\n" " still be replaceable in practice, for example if it has unconfirmed ancestors which\n" " are replaceable).\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" " }\n" "\nResult:\n" "{\n" @@ -2849,31 +3001,24 @@ UniValue bumpfee(const JSONRPCRequest& request) hash.SetHex(request.params[0].get_str()); // optional parameters - bool ignoreGlobalPayTxFee = false; - int newConfirmTarget = nTxConfirmTarget; CAmount totalFee = 0; - bool replaceable = true; - if (request.params.size() > 1) { + CCoinControl coin_control; + coin_control.signalRbf = true; + if (!request.params[1].isNull()) { UniValue options = request.params[1]; RPCTypeCheckObj(options, { {"confTarget", UniValueType(UniValue::VNUM)}, {"totalFee", UniValueType(UniValue::VNUM)}, {"replaceable", UniValueType(UniValue::VBOOL)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, }, true, true); if (options.exists("confTarget") && options.exists("totalFee")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction."); - } else if (options.exists("confTarget")) { - // If the user has explicitly set a confTarget in this rpc call, - // then override the default logic that uses the global payTxFee - // instead of the confirmation target. - ignoreGlobalPayTxFee = true; - newConfirmTarget = options["confTarget"].get_int(); - if (newConfirmTarget <= 0) { // upper-bound will be checked by estimatefee/smartfee - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid confTarget (cannot be <= 0)"); - } + } else if (options.exists("confTarget")) { // TODO: alias this to conf_target + coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]); } else if (options.exists("totalFee")) { totalFee = options["totalFee"].get_int64(); if (totalFee <= 0) { @@ -2882,14 +3027,19 @@ UniValue bumpfee(const JSONRPCRequest& request) } if (options.exists("replaceable")) { - replaceable = options["replaceable"].get_bool(); + coin_control.signalRbf = options["replaceable"].get_bool(); + } + if (options.exists("estimate_mode")) { + if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } } } LOCK2(cs_main, pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable); + CFeeBumper feeBump(pwallet, hash, coin_control, totalFee); BumpFeeResult res = feeBump.getResult(); if (res != BumpFeeResult::OK) { @@ -3025,14 +3175,15 @@ static const CRPCCommand commands[] = { "wallet", "listlockunspent", &listlockunspent, false, {} }, { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, + { "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, { "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwallets", &listwallets, true, {} }, { "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} }, { "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} }, { "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, - { "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} }, - { "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} }, + { "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, + { "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, { "wallet", "setaccount", &setaccount, true, {"address","account"} }, { "wallet", "settxfee", &settxfee, true, {"amount"} }, { "wallet", "signmessage", &signmessage, true, {"address","message"} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index bd5dad18ca..31e2f6a699 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -16,7 +16,7 @@ void RegisterWalletRPCCommands(CRPCTable &t); * @param[in] request JSONRPCRequest that wishes to access a wallet * @return NULL if no wallet should be used, or a pointer to the CWallet */ -CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest&); +CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request); std::string HelpRequiringPassphrase(CWallet *); void EnsureWalletIsUnlocked(CWallet *); diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp index 524a72c303..744063624c 100644 --- a/src/wallet/test/crypto_tests.cpp +++ b/src/wallet/test/crypto_tests.cpp @@ -26,8 +26,8 @@ bool OldSetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<u if (i != (int)WALLET_CRYPTO_KEY_SIZE) { - memory_cleanse(chKey, sizeof(chKey)); - memory_cleanse(chIV, sizeof(chIV)); + memory_cleanse(chKey, WALLET_CRYPTO_KEY_SIZE); + memory_cleanse(chIV, WALLET_CRYPTO_IV_SIZE); return false; } return true; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 96a1b14b60..4a2cc9a139 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -13,6 +13,7 @@ #include "rpc/server.h" #include "test/test_bitcoin.h" #include "validation.h" +#include "wallet/coincontrol.h" #include "wallet/test/wallet_test_fixture.h" #include <boost/test/unit_test.hpp> @@ -468,7 +469,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) JSONRPCRequest request; request.params.setArray(); - request.params.push_back("wallet.backup"); + request.params.push_back((pathTemp / "wallet.backup").string()); vpwallets.insert(vpwallets.begin(), &wallet); ::dumpwallet(request); } @@ -480,7 +481,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) JSONRPCRequest request; request.params.setArray(); - request.params.push_back("wallet.backup"); + request.params.push_back((pathTemp / "wallet.backup").string()); vpwallets[0] = &wallet; ::importwallet(request); @@ -617,7 +618,8 @@ public: CAmount fee; int changePos = -1; std::string error; - BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error)); + CCoinControl dummy; + BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state)); auto it = wallet->mapWallet.find(wtx.GetHash()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f5d9b33acc..cf75cdd744 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -57,6 +57,8 @@ CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE); */ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); +CFeeRate CWallet::m_discard_rate = CFeeRate(DEFAULT_DISCARD_FEE); + const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet @@ -87,7 +89,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey(bool internal) +CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) { AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -100,14 +102,15 @@ CPubKey CWallet::GenerateNewKey(bool internal) // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { - DeriveNewChildKey(metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); + DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); } else { secret.MakeNewKey(fCompressed); } // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) + if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY); + } CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); @@ -115,12 +118,13 @@ CPubKey CWallet::GenerateNewKey(bool internal) mapKeyMetadata[pubkey.GetID()] = metadata; UpdateTimeFirstKey(nCreationTime); - if (!AddKeyPubKey(secret, pubkey)) + if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } return pubkey; } -void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal) +void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k CKey key; //master key seed (256bit) @@ -162,33 +166,52 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool inter secret = childKey.key; metadata.hdMasterKeyID = hdChain.masterKeyID; // update the chain model in the database - if (!CWalletDB(*dbw).WriteHDChain(hdChain)) + if (!walletdb.WriteHDChain(hdChain)) throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); } -bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey& secret, const CPubKey &pubkey) { AssertLockHeld(cs_wallet); // mapKeyMetadata - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) + + // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !pwalletdbEncryption; + if (needsDB) { + pwalletdbEncryption = &walletdb; + } + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (needsDB) pwalletdbEncryption = NULL; return false; + } + if (needsDB) pwalletdbEncryption = NULL; // check if we need to remove from watch-only CScript script; script = GetScriptForDestination(pubkey.GetID()); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } script = GetScriptForRawPubKey(pubkey); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } if (!IsCrypted()) { - return CWalletDB(*dbw).WriteKey(pubkey, + return walletdb.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } return true; } +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + CWalletDB walletdb(*dbw); + return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey); +} + bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { @@ -445,11 +468,26 @@ bool CWallet::Verify() uiInterface.InitMessage(_("Verifying wallet(s)...")); + // Keep track of each wallet absolute path to detect duplicates. + std::set<fs::path> wallet_paths; + for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { if (boost::filesystem::path(walletFile).filename() != walletFile) { - return InitError(_("-wallet parameter must only specify a filename (not a path)")); - } else if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { - return InitError(_("Invalid characters in -wallet filename")); + return InitError(strprintf(_("Error loading wallet %s. -wallet parameter must only specify a filename (not a path)."), walletFile)); + } + + if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { + return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile)); + } + + fs::path wallet_path = fs::absolute(walletFile, GetDataDir()); + + if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { + return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile)); + } + + if (!wallet_paths.insert(wallet_path).second) { + return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile)); } std::string strError; @@ -2450,7 +2488,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx) return true; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; @@ -2469,11 +2507,16 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC CReserveKey reservekey(this); CWalletTx wtx; - if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false)) + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; + } - if (nChangePosInOut != -1) + if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + // we dont have the normal Create/Commit cycle, and dont want to risk reusing change, + // so just remove the key from the keypool here. + reservekey.KeepKey(); + } // Copy output sizes from new transaction; they may have had the fee subtracted from them for (unsigned int idx = 0; idx < tx.vout.size(); idx++) @@ -2494,15 +2537,23 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } } - // optionally keep the change output key - if (keepReserveKey) - reservekey.KeepKey(); return true; } +static CFeeRate GetDiscardRate(const CBlockPolicyEstimator& estimator) +{ + unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */); + // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate + discard_rate = (discard_rate == CFeeRate(0)) ? CWallet::m_discard_rate : std::min(discard_rate, CWallet::m_discard_rate); + // Discard rate must be at least dustRelayFee + discard_rate = std::max(discard_rate, ::dustRelayFee); + return discard_rate; +} + bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign) + int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; int nChangePosRequest = nChangePosInOut; @@ -2567,9 +2618,43 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT LOCK2(cs_main, cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, true, coinControl); + AvailableCoins(vAvailableCoins, true, &coin_control); + + // Create change script that will be used if we need change + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (!boost::get<CNoDestination>(&coin_control.destChange)) { + scriptChange = GetScriptForDestination(coin_control.destChange); + } else { // no coin control: send change to newly generated address + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey, true); + if (!ret) + { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + CTxOut change_prototype_txout(0, scriptChange); + size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); + + CFeeRate discard_rate = GetDiscardRate(::feeEstimator); nFeeRet = 0; + bool pick_new_inputs = true; + CAmount nValueIn = 0; // Start with no fee and loop until there is enough fee while (true) { @@ -2615,58 +2700,29 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } // Choose coins to use - CAmount nValueIn = 0; - setCoins.clear(); - if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) - { - strFailReason = _("Insufficient funds"); - return false; + if (pick_new_inputs) { + nValueIn = 0; + setCoins.clear(); + if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control)) + { + strFailReason = _("Insufficient funds"); + return false; + } } const CAmount nChange = nValueIn - nValueToSelect; + if (nChange > 0) { // Fill a vout to ourself - // TODO: pass in scriptChange instead of reservekey so - // change transaction isn't always pay-to-bitcoin-address - CScript scriptChange; - - // coin control: send change to custom address - if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) - scriptChange = GetScriptForDestination(coinControl->destChange); - - // no coin control: send change to newly generated address - else - { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey, true); - if (!ret) - { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); - return false; - } - - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - } - CTxOut newTxOut(nChange, scriptChange); // Never create dust outputs; if we would, just // add the dust to the fee. - if (IsDust(newTxOut, ::dustRelayFee)) + if (IsDust(newTxOut, discard_rate)) { nChangePosInOut = -1; nFeeRet += nChange; - reservekey.ReturnKey(); } else { @@ -2685,7 +2741,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT txNew.vout.insert(position, newTxOut); } } else { - reservekey.ReturnKey(); nChangePosInOut = -1; } @@ -2699,8 +2754,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT // to avoid conflicting with other possible uses of nSequence, // and in the spirit of "smallest possible change from prior // behavior." - bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf; - const uint32_t nSequence = rbf ? MAX_BIP125_RBF_SEQUENCE : (std::numeric_limits<unsigned int>::max() - 1); + const uint32_t nSequence = coin_control.signalRbf ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); for (const auto& coin : setCoins) txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), nSequence)); @@ -2719,14 +2773,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT vin.scriptWitness.SetNull(); } - // Allow to override the default confirmation target over the CoinControl instance - int currentConfirmationTarget = nTxConfirmTarget; - if (coinControl && coinControl->nConfirmTarget > 0) - currentConfirmationTarget = coinControl->nConfirmTarget; - - CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc); - if (coinControl && coinControl->fOverrideFeeRate) - nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); + CAmount nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc); // If we made it here and we aren't even able to meet the relay fee on the next pass, give up // because we must be at the maximum allowed fee. @@ -2737,16 +2784,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } if (nFeeRet >= nFeeNeeded) { - // Reduce fee to only the needed amount if we have change - // output to increase. This prevents potential overpayment - // in fees if the coins selected to meet nFeeNeeded result - // in a transaction that requires less fee than the prior - // iteration. + // Reduce fee to only the needed amount if possible. This + // prevents potential overpayment in fees if the coins + // selected to meet nFeeNeeded result in a transaction that + // requires less fee than the prior iteration. + // TODO: The case where nSubtractFeeFromAmount > 0 remains // to be addressed because it requires returning the fee to // the payees and not the change output. - // TODO: The case where there is no change output remains - // to be addressed so we avoid creating too small an output. + + // If we have no change and a big enough excess fee, then + // try to construct transaction again only without picking + // new inputs. We now know we only need the smaller fee + // (because of reduced tx size) and so we should add a + // change output. Only try this once. + CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, coin_control, ::mempool, ::feeEstimator, nullptr); + CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate); + CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change; + if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { + pick_new_inputs = false; + nFeeRet = nFeeNeeded + fee_needed_for_change; + continue; + } + + // If we have change output already, just increase it if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { CAmount extraFeePaid = nFeeRet - nFeeNeeded; std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut; @@ -2755,6 +2816,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } break; // Done, enough fee included. } + else if (!pick_new_inputs) { + // This shouldn't happen, we should have had enough excess + // fee to pay for the new output and still meet nFeeNeeded + strFailReason = _("Transaction fee and change calculation failed"); + return false; + } // Try to reduce change to include necessary fee if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { @@ -2774,6 +2841,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } } + if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change + if (sign) { CTransaction txNewConst(txNew); @@ -2905,33 +2974,61 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); } -CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee) +CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc) { - // payTxFee is the user-set global for desired feerate - CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); - // User didn't set: use -txconfirmtarget to estimate... - if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) { - nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, true).GetFee(nTxBytes); - // ... unless we don't have enough mempool data for estimatefee, then use fallbackFee - if (nFeeNeeded == 0) { - nFeeNeeded = fallbackFee.GetFee(nTxBytes); + /* User control of how to calculate fee uses the following parameter precedence: + 1. coin_control.m_feerate + 2. coin_control.m_confirm_target + 3. payTxFee (user-set global variable) + 4. nTxConfirmTarget (user-set global variable) + The first parameter that is set is used. + */ + CAmount fee_needed; + if (coin_control.m_feerate) { // 1. + fee_needed = coin_control.m_feerate->GetFee(nTxBytes); + if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; + // Allow to override automatic min/max check over coin control instance + if (coin_control.fOverrideFeeRate) return fee_needed; + } + else if (!coin_control.m_confirm_target && ::payTxFee != CFeeRate(0)) { // 3. TODO: remove magic value of 0 for global payTxFee + fee_needed = ::payTxFee.GetFee(nTxBytes); + if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; + } + else { // 2. or 4. + // We will use smart fee estimation + unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : ::nTxConfirmTarget; + // By default estimates are economical iff we are signaling opt-in-RBF + bool conservative_estimate = !coin_control.signalRbf; + // Allow to override the default fee estimate mode over the CoinControl instance + if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true; + else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false; + + fee_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate).GetFee(nTxBytes); + if (fee_needed == 0) { + // if we don't have enough data for estimateSmartFee, then use fallbackFee + fee_needed = fallbackFee.GetFee(nTxBytes); if (feeCalc) feeCalc->reason = FeeReason::FALLBACK; } - } else { - if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; + // Obey mempool min fee when using smart fee estimation + CAmount min_mempool_fee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nTxBytes); + if (fee_needed < min_mempool_fee) { + fee_needed = min_mempool_fee; + if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN; + } } + // prevent user from paying a fee below minRelayTxFee or minTxFee - CAmount requiredFee = GetRequiredFee(nTxBytes); - if (requiredFee > nFeeNeeded) { - nFeeNeeded = requiredFee; + CAmount required_fee = GetRequiredFee(nTxBytes); + if (required_fee > fee_needed) { + fee_needed = required_fee; if (feeCalc) feeCalc->reason = FeeReason::REQUIRED; } // But always obey the maximum - if (nFeeNeeded > maxTxFee) { - nFeeNeeded = maxTxFee; + if (fee_needed > maxTxFee) { + fee_needed = maxTxFee; if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; } - return nFeeNeeded; + return fee_needed; } @@ -2946,7 +3043,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) if (dbw->Rewrite("\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -2974,7 +3072,8 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { if (dbw->Rewrite("\x04pool")) { - setKeyPool.clear(); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -2999,7 +3098,8 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) if (dbw->Rewrite("\x04pool")) { LOCK(cs_wallet); - setKeyPool.clear(); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. @@ -3083,9 +3183,16 @@ bool CWallet::NewKeyPool() { LOCK(cs_wallet); CWalletDB walletdb(*dbw); - for (int64_t nIndex : setKeyPool) + + for (int64_t nIndex : setInternalKeyPool) { + walletdb.ErasePool(nIndex); + } + setInternalKeyPool.clear(); + + for (int64_t nIndex : setExternalKeyPool) { walletdb.ErasePool(nIndex); - setKeyPool.clear(); + } + setExternalKeyPool.clear(); if (!TopUpKeyPool()) { return false; @@ -3097,25 +3204,8 @@ bool CWallet::NewKeyPool() size_t CWallet::KeypoolCountExternalKeys() { - AssertLockHeld(cs_wallet); // setKeyPool - - // immediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported - if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) - return setKeyPool.size(); - - CWalletDB walletdb(*dbw); - - // count amount of external keys - size_t amountE = 0; - for(const int64_t& id : setKeyPool) - { - CKeyPool tmpKeypool; - if (!walletdb.ReadPool(id, tmpKeypool)) - throw std::runtime_error(std::string(__func__) + ": read failed"); - amountE += !tmpKeypool.fInternal; - } - - return amountE; + AssertLockHeld(cs_wallet); // setExternalKeyPool + return setExternalKeyPool.size(); } bool CWallet::TopUpKeyPool(unsigned int kpSize) @@ -3135,10 +3225,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) // count amount of available keys (internal, external) // make sure the keypool of external and internal keys fits the user selected target (-keypool) - int64_t amountExternal = KeypoolCountExternalKeys(); - int64_t amountInternal = setKeyPool.size() - amountExternal; - int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0); - int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountInternal, (int64_t) 0); + int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0); + int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0); if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) { @@ -3149,21 +3237,31 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) CWalletDB walletdb(*dbw); for (int64_t i = missingInternal + missingExternal; i--;) { - int64_t nEnd = 1; - if (i < missingInternal) + if (i < missingInternal) { internal = true; - if (!setKeyPool.empty()) - nEnd = *(--setKeyPool.end()) + 1; - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal))) + } + + assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys? + int64_t index = ++m_max_keypool_index; + + if (!walletdb.WritePool(index, CKeyPool(GenerateNewKey(walletdb, internal), internal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); - setKeyPool.insert(nEnd); - LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setKeyPool.size(), internal); + } + + if (internal) { + setInternalKeyPool.insert(index); + } else { + setExternalKeyPool.insert(index); + } + } + if (missingInternal + missingExternal > 0) { + LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); } } return true; } -void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal) +void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); @@ -3173,30 +3271,30 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool int if (!IsLocked()) TopUpKeyPool(); + bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; + std::set<int64_t>& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool; + // Get the oldest key if(setKeyPool.empty()) return; CWalletDB walletdb(*dbw); - // try to find a key that matches the internal/external filter - for(const int64_t& id : setKeyPool) - { - CKeyPool tmpKeypool; - if (!walletdb.ReadPool(id, tmpKeypool)) - throw std::runtime_error(std::string(__func__) + ": read failed"); - if (!HaveKey(tmpKeypool.vchPubKey.GetID())) - throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); - if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT) || tmpKeypool.fInternal == internal) - { - nIndex = id; - keypool = tmpKeypool; - setKeyPool.erase(id); - assert(keypool.vchPubKey.IsValid()); - LogPrintf("keypool reserve %d\n", nIndex); - return; - } + auto it = setKeyPool.begin(); + nIndex = *it; + setKeyPool.erase(it); + if (!walletdb.ReadPool(nIndex, keypool)) { + throw std::runtime_error(std::string(__func__) + ": read failed"); + } + if (!HaveKey(keypool.vchPubKey.GetID())) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + if (keypool.fInternal != fReturningInternal) { + throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); } + + assert(keypool.vchPubKey.IsValid()); + LogPrintf("keypool reserve %d\n", nIndex); } } @@ -3208,12 +3306,16 @@ void CWallet::KeepKey(int64_t nIndex) LogPrintf("keypool keep %d\n", nIndex); } -void CWallet::ReturnKey(int64_t nIndex) +void CWallet::ReturnKey(int64_t nIndex, bool fInternal) { // Return to key pool { LOCK(cs_wallet); - setKeyPool.insert(nIndex); + if (fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } } LogPrintf("keypool return %d\n", nIndex); } @@ -3228,7 +3330,8 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) if (nIndex == -1) { if (IsLocked()) return false; - result = GenerateNewKey(internal); + CWalletDB walletdb(*dbw); + result = GenerateNewKey(walletdb, internal); return true; } KeepKey(nIndex); @@ -3237,48 +3340,35 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) return true; } -int64_t CWallet::GetOldestKeyPoolTime() -{ - LOCK(cs_wallet); - - // if the keypool is empty, return <NOW> - if (setKeyPool.empty()) +static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) { + if (setKeyPool.empty()) { return GetTime(); + } CKeyPool keypool; - CWalletDB walletdb(*dbw); - - if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) - { - // if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key) - int64_t now = GetTime(); - int64_t oldest_external = now, oldest_internal = now; - - for(const int64_t& id : setKeyPool) - { - if (!walletdb.ReadPool(id, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read failed"); - } - if (keypool.fInternal && keypool.nTime < oldest_internal) { - oldest_internal = keypool.nTime; - } - else if (!keypool.fInternal && keypool.nTime < oldest_external) { - oldest_external = keypool.nTime; - } - if (oldest_internal != now && oldest_external != now) { - break; - } - } - return std::max(oldest_internal, oldest_external); - } - // load oldest key from keypool, get time and return int64_t nIndex = *(setKeyPool.begin()); - if (!walletdb.ReadPool(nIndex, keypool)) + if (!walletdb.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); + } assert(keypool.vchPubKey.IsValid()); return keypool.nTime; } +int64_t CWallet::GetOldestKeyPoolTime() +{ + LOCK(cs_wallet); + + CWalletDB walletdb(*dbw); + + // load oldest key from keypool, get time and return + int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb); + if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { + oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey); + } + + return oldestKey; +} + std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() { std::map<CTxDestination, CAmount> balances; @@ -3437,6 +3527,7 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) else { return false; } + fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); pubkey = vchPubKey; @@ -3453,19 +3544,14 @@ void CReserveKey::KeepKey() void CReserveKey::ReturnKey() { - if (nIndex != -1) - pwallet->ReturnKey(nIndex); + if (nIndex != -1) { + pwallet->ReturnKey(nIndex, fInternal); + } nIndex = -1; vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const -{ - setAddress.clear(); - - CWalletDB walletdb(*dbw); - - LOCK2(cs_main, cs_wallet); +static void LoadReserveKeysToSet(std::set<CKeyID>& setAddress, const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) { for (const int64_t& id : setKeyPool) { CKeyPool keypool; @@ -3473,12 +3559,27 @@ void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const throw std::runtime_error(std::string(__func__) + ": read failed"); assert(keypool.vchPubKey.IsValid()); CKeyID keyID = keypool.vchPubKey.GetID(); - if (!HaveKey(keyID)) - throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); setAddress.insert(keyID); } } +void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const +{ + setAddress.clear(); + + CWalletDB walletdb(*dbw); + + LOCK2(cs_main, cs_wallet); + LoadReserveKeysToSet(setAddress, setInternalKeyPool, walletdb); + LoadReserveKeysToSet(setAddress, setExternalKeyPool, walletdb); + + for (const CKeyID& keyID : setAddress) { + if (!HaveKey(keyID)) { + throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); + } + } +} + void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script) { std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this); @@ -3738,6 +3839,9 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); + strUsage += HelpMessageOpt("-discardfee=<amt>", strprintf(_("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " + "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target"), + CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE))); strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE))); strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"), @@ -4002,20 +4106,30 @@ bool CWallet::ParameterInteraction() LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__); } - if (GetBoolArg("-salvagewallet", false) && SoftSetBoolArg("-rescan", true)) { + if (GetBoolArg("-salvagewallet", false)) { if (is_multiwallet) { return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet")); } // Rewrite just private keys: rescan to find transactions - LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + if (SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + } } - // -zapwallettx implies a rescan - if (GetBoolArg("-zapwallettxes", false) && SoftSetBoolArg("-rescan", true)) { + int zapwallettxes = GetArg("-zapwallettxes", 0); + // -zapwallettxes implies dropping the mempool on startup + if (zapwallettxes != 0 && SoftSetBoolArg("-persistmempool", false)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -persistmempool=0\n", __func__, zapwallettxes); + } + + // -zapwallettxes implies a rescan + if (zapwallettxes != 0) { if (is_multiwallet) { return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes")); } - LogPrintf("%s: parameter interaction: -zapwallettxes=<mode> -> setting -rescan=1\n", __func__); + if (SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -rescan=1\n", __func__, zapwallettxes); + } } if (is_multiwallet) { @@ -4053,6 +4167,16 @@ bool CWallet::ParameterInteraction() _("This is the transaction fee you may pay when fee estimates are not available.")); CWallet::fallbackFee = CFeeRate(nFeePerK); } + if (IsArgSet("-discardfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(GetArg("-discardfee", ""), nFeePerK)) + return InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), GetArg("-discardfee", ""))); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(AmountHighWarn("-discardfee") + " " + + _("This is the transaction fee you may discard if change is smaller than dust at this level")); + CWallet::m_discard_rate = CFeeRate(nFeePerK); + } if (IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 4f558adc77..7ef2e6f1d8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -40,11 +40,13 @@ extern unsigned int nTxConfirmTarget; extern bool bSpendZeroConfChange; extern bool fWalletRbf; -static const unsigned int DEFAULT_KEYPOOL_SIZE = 100; +static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default static const CAmount DEFAULT_TRANSACTION_FEE = 0; //! -fallbackfee default static const CAmount DEFAULT_FALLBACK_FEE = 20000; +//! -m_discard_rate default +static const CAmount DEFAULT_DISCARD_FEE = 10000; //! -mintxfee default static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000; //! minimum recommended increment for BIP 125 replacement txs @@ -80,6 +82,7 @@ class CTxMemPool; class CBlockPolicyEstimator; class CWalletTx; struct FeeCalculation; +enum class FeeEstimateMode; /** (client) version numbers for particular wallet features */ enum WalletFeature @@ -696,9 +699,11 @@ private: CHDChain hdChain; /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false); + void DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal = false); - std::set<int64_t> setKeyPool; + std::set<int64_t> setInternalKeyPool; + std::set<int64_t> setExternalKeyPool; + int64_t m_max_keypool_index; int64_t nTimeFirstKey; @@ -741,9 +746,14 @@ public: } } - void LoadKeyPool(int nIndex, const CKeyPool &keypool) + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { - setKeyPool.insert(nIndex); + if (keypool.fInternal) { + setInternalKeyPool.insert(nIndex); + } else { + setExternalKeyPool.insert(nIndex); + } + m_max_keypool_index = std::max(m_max_keypool_index, nIndex); // If no metadata exists yet, create a default with the pool key's // creation time. Note that this may be overwritten by actually @@ -789,6 +799,7 @@ public: nAccountingEntryNumber = 0; nNextResend = 0; nLastResend = 0; + m_max_keypool_index = 0; nTimeFirstKey = 0; fBroadcastTransactions = false; nRelockTime = 0; @@ -860,9 +871,10 @@ public: * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(bool internal = false); + CPubKey GenerateNewKey(CWalletDB& walletdb, bool internal = false); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; + bool AddKeyPubKeyWithDB(CWalletDB &walletdb,const CKey& key, const CPubKey &pubkey); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) @@ -939,7 +951,7 @@ public: * Insert additional inputs into the transaction by * calling CreateTransaction(); */ - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); bool SignTransaction(CMutableTransaction& tx); /** @@ -948,7 +960,7 @@ public: * @note passing nChangePosInOut as -1 will result in setting a random position */ bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); + std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries); @@ -959,11 +971,12 @@ public: static CFeeRate minTxFee; static CFeeRate fallbackFee; + static CFeeRate m_discard_rate; /** * Estimate the minimum fee considering user set parameters * and the required fee */ - static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc = nullptr, bool ignoreGlobalPayTxFee = false); + static CAmount GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc); /** * Return the minimum required fee taking into account the * floating relay fee and user set minimum transaction fee @@ -973,9 +986,9 @@ public: bool NewKeyPool(); size_t KeypoolCountExternalKeys(); bool TopUpKeyPool(unsigned int kpSize = 0); - void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal); + void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex); + void ReturnKey(int64_t nIndex, bool fInternal); bool GetKeyFromPool(CPubKey &key, bool internal = false); int64_t GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set<CKeyID>& setAddress) const; @@ -1029,8 +1042,8 @@ public: unsigned int GetKeyPoolSize() { - AssertLockHeld(cs_wallet); // setKeyPool - return setKeyPool.size(); + AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool + return setInternalKeyPool.size() + setExternalKeyPool.size(); } bool SetDefaultKey(const CPubKey &vchPubKey); @@ -1053,7 +1066,9 @@ public: //! Flush wallet (bitdb flush) void Flush(bool shutdown=false); - //! Verify the wallet database and perform salvage if required + //! 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 (CWallet::ParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). static bool Verify(); /** @@ -1134,11 +1149,13 @@ protected: CWallet* pwallet; int64_t nIndex; CPubKey vchPubKey; + bool fInternal; public: CReserveKey(CWallet* pwalletIn) { nIndex = -1; pwallet = pwalletIn; + fInternal = false; } CReserveKey() = default; @@ -1211,4 +1228,5 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins } return true; } + #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index deb09a4771..65a28af46d 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -94,23 +94,23 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) { - return WriteIC(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false); + return WriteIC(std::make_pair(std::string("cscript"), hash), redeemScript, false); } bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta) { - if (!WriteIC(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta)) { + if (!WriteIC(std::make_pair(std::string("watchmeta"), dest), keyMeta)) { return false; } - return WriteIC(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1'); + return WriteIC(std::make_pair(std::string("watchs"), dest), '1'); } bool CWalletDB::EraseWatchOnly(const CScript &dest) { - if (!EraseIC(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)))) { + if (!EraseIC(std::make_pair(std::string("watchmeta"), dest))) { return false; } - return EraseIC(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest))); + return EraseIC(std::make_pair(std::string("watchs"), dest)); } bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) @@ -323,7 +323,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { wss.nWatchKeys++; CScript script; - ssKey >> *(CScriptBase*)(&script); + ssKey >> script; char fYes; ssValue >> fYes; if (fYes == '1') @@ -440,7 +440,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, else if (strType == "watchmeta") { CScript script; - ssKey >> *(CScriptBase*)(&script); + ssKey >> script; keyID = CScriptID(script); } @@ -474,7 +474,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, uint160 hash; ssKey >> hash; CScript script; - ssValue >> *(CScriptBase*)(&script); + ssValue >> script; if (!pwallet->LoadCScript(script)) { strErr = "Error reading wallet database: LoadCScript failed"; @@ -747,7 +747,7 @@ DBErrors CWalletDB::ZapWalletTx(std::vector<CWalletTx>& vWtx) void MaybeCompactWalletDB() { - static std::atomic<bool> fOneThread; + static std::atomic<bool> fOneThread(false); if (fOneThread.exchange(true)) { return; } |