aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/init.cpp8
-rw-r--r--src/wallet/interfaces.cpp587
-rw-r--r--src/wallet/rpcdump.cpp6
-rw-r--r--src/wallet/rpcwallet.cpp259
-rw-r--r--src/wallet/salvage.cpp2
-rw-r--r--src/wallet/scriptpubkeyman.cpp16
-rw-r--r--src/wallet/scriptpubkeyman.h7
-rw-r--r--src/wallet/sqlite.cpp4
-rw-r--r--src/wallet/test/wallet_tests.cpp4
-rw-r--r--src/wallet/test/walletdb_tests.cpp29
-rw-r--r--src/wallet/wallet.cpp72
-rw-r--r--src/wallet/wallet.h20
-rw-r--r--src/wallet/walletdb.cpp34
-rw-r--r--src/wallet/walletdb.h1
-rw-r--r--src/wallet/wallettool.cpp5
-rw-r--r--src/wallet/walletutil.cpp57
-rw-r--r--src/wallet/walletutil.h3
17 files changed, 912 insertions, 202 deletions
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 8b2ef191fb..085dde1026 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -15,6 +15,9 @@
#include <util/moneystr.h>
#include <util/system.h>
#include <util/translation.h>
+#ifdef USE_BDB
+#include <wallet/bdb.h>
+#endif
#include <wallet/coincontrol.h>
#include <wallet/wallet.h>
#include <walletinitinterface.h>
@@ -68,9 +71,14 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
#endif
argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+#ifdef USE_BDB
argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+#else
+ argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb"});
+#endif
+
argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
argsman.AddHiddenArgs({"-zapwallettxes"});
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
new file mode 100644
index 0000000000..3fbba9ab92
--- /dev/null
+++ b/src/wallet/interfaces.cpp
@@ -0,0 +1,587 @@
+// Copyright (c) 2018-2020 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 <interfaces/wallet.h>
+
+#include <amount.h>
+#include <interfaces/chain.h>
+#include <interfaces/handler.h>
+#include <policy/fees.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <script/standard.h>
+#include <support/allocators/secure.h>
+#include <sync.h>
+#include <uint256.h>
+#include <util/check.h>
+#include <util/ref.h>
+#include <util/system.h>
+#include <util/ui_change_type.h>
+#include <wallet/context.h>
+#include <wallet/feebumper.h>
+#include <wallet/fees.h>
+#include <wallet/ismine.h>
+#include <wallet/load.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+using interfaces::Chain;
+using interfaces::FoundBlock;
+using interfaces::Handler;
+using interfaces::MakeHandler;
+using interfaces::Wallet;
+using interfaces::WalletAddress;
+using interfaces::WalletBalances;
+using interfaces::WalletClient;
+using interfaces::WalletOrderForm;
+using interfaces::WalletTx;
+using interfaces::WalletTxOut;
+using interfaces::WalletTxStatus;
+using interfaces::WalletValueMap;
+
+namespace wallet {
+namespace {
+//! Construct wallet tx struct.
+WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
+{
+ LOCK(wallet.cs_wallet);
+ WalletTx result;
+ result.tx = wtx.tx;
+ result.txin_is_mine.reserve(wtx.tx->vin.size());
+ for (const auto& txin : wtx.tx->vin) {
+ result.txin_is_mine.emplace_back(wallet.IsMine(txin));
+ }
+ result.txout_is_mine.reserve(wtx.tx->vout.size());
+ result.txout_address.reserve(wtx.tx->vout.size());
+ result.txout_address_is_mine.reserve(wtx.tx->vout.size());
+ for (const auto& txout : wtx.tx->vout) {
+ result.txout_is_mine.emplace_back(wallet.IsMine(txout));
+ result.txout_address.emplace_back();
+ result.txout_address_is_mine.emplace_back(ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ?
+ wallet.IsMine(result.txout_address.back()) :
+ ISMINE_NO);
+ }
+ result.credit = wtx.GetCredit(ISMINE_ALL);
+ result.debit = wtx.GetDebit(ISMINE_ALL);
+ result.change = wtx.GetChange();
+ result.time = wtx.GetTxTime();
+ result.value_map = wtx.mapValue;
+ result.is_coinbase = wtx.IsCoinBase();
+ return result;
+}
+
+//! Construct wallet tx status struct.
+WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx)
+{
+ WalletTxStatus result;
+ result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max();
+ result.blocks_to_maturity = wtx.GetBlocksToMaturity();
+ result.depth_in_main_chain = wtx.GetDepthInMainChain();
+ result.time_received = wtx.nTimeReceived;
+ result.lock_time = wtx.tx->nLockTime;
+ result.is_final = wallet.chain().checkFinalTx(*wtx.tx);
+ result.is_trusted = wtx.IsTrusted();
+ result.is_abandoned = wtx.isAbandoned();
+ result.is_coinbase = wtx.IsCoinBase();
+ result.is_in_main_chain = wtx.IsInMainChain();
+ return result;
+}
+
+//! Construct wallet TxOut struct.
+WalletTxOut MakeWalletTxOut(CWallet& wallet,
+ const CWalletTx& wtx,
+ int n,
+ int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ WalletTxOut result;
+ result.txout = wtx.tx->vout[n];
+ result.time = wtx.GetTxTime();
+ result.depth_in_main_chain = depth;
+ result.is_spent = wallet.IsSpent(wtx.GetHash(), n);
+ return result;
+}
+
+class WalletImpl : public Wallet
+{
+public:
+ explicit WalletImpl(const std::shared_ptr<CWallet>& wallet) : m_wallet(wallet) {}
+
+ bool encryptWallet(const SecureString& wallet_passphrase) override
+ {
+ return m_wallet->EncryptWallet(wallet_passphrase);
+ }
+ bool isCrypted() override { return m_wallet->IsCrypted(); }
+ bool lock() override { return m_wallet->Lock(); }
+ bool unlock(const SecureString& wallet_passphrase) override { return m_wallet->Unlock(wallet_passphrase); }
+ bool isLocked() override { return m_wallet->IsLocked(); }
+ bool changeWalletPassphrase(const SecureString& old_wallet_passphrase,
+ const SecureString& new_wallet_passphrase) override
+ {
+ return m_wallet->ChangeWalletPassphrase(old_wallet_passphrase, new_wallet_passphrase);
+ }
+ void abortRescan() override { m_wallet->AbortRescan(); }
+ bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
+ std::string getWalletName() override { return m_wallet->GetName(); }
+ bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ std::string error;
+ return m_wallet->GetNewDestination(type, label, dest, error);
+ }
+ bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override
+ {
+ std::unique_ptr<SigningProvider> provider = m_wallet->GetSolvingProvider(script);
+ if (provider) {
+ return provider->GetPubKey(address, pub_key);
+ }
+ return false;
+ }
+ SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) override
+ {
+ return m_wallet->SignMessage(message, pkhash, str_sig);
+ }
+ bool isSpendable(const CTxDestination& dest) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->IsMine(dest) & ISMINE_SPENDABLE;
+ }
+ bool haveWatchOnly() override
+ {
+ auto spk_man = m_wallet->GetLegacyScriptPubKeyMan();
+ if (spk_man) {
+ return spk_man->HaveWatchOnly();
+ }
+ return false;
+ };
+ bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) override
+ {
+ return m_wallet->SetAddressBook(dest, name, purpose);
+ }
+ bool delAddressBook(const CTxDestination& dest) override
+ {
+ return m_wallet->DelAddressBook(dest);
+ }
+ bool getAddress(const CTxDestination& dest,
+ std::string* name,
+ isminetype* is_mine,
+ std::string* purpose) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ auto it = m_wallet->m_address_book.find(dest);
+ if (it == m_wallet->m_address_book.end() || it->second.IsChange()) {
+ return false;
+ }
+ if (name) {
+ *name = it->second.GetLabel();
+ }
+ if (is_mine) {
+ *is_mine = m_wallet->IsMine(dest);
+ }
+ if (purpose) {
+ *purpose = it->second.purpose;
+ }
+ return true;
+ }
+ std::vector<WalletAddress> getAddresses() override
+ {
+ LOCK(m_wallet->cs_wallet);
+ std::vector<WalletAddress> result;
+ for (const auto& item : m_wallet->m_address_book) {
+ if (item.second.IsChange()) continue;
+ result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.GetLabel(), item.second.purpose);
+ }
+ return result;
+ }
+ bool addDestData(const CTxDestination& dest, const std::string& key, const std::string& value) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ WalletBatch batch{m_wallet->GetDatabase()};
+ return m_wallet->AddDestData(batch, dest, key, value);
+ }
+ bool eraseDestData(const CTxDestination& dest, const std::string& key) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ WalletBatch batch{m_wallet->GetDatabase()};
+ return m_wallet->EraseDestData(batch, dest, key);
+ }
+ std::vector<std::string> getDestValues(const std::string& prefix) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->GetDestValues(prefix);
+ }
+ void lockCoin(const COutPoint& output) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->LockCoin(output);
+ }
+ void unlockCoin(const COutPoint& output) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->UnlockCoin(output);
+ }
+ bool isLockedCoin(const COutPoint& output) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->IsLockedCoin(output.hash, output.n);
+ }
+ void listLockedCoins(std::vector<COutPoint>& outputs) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->ListLockedCoins(outputs);
+ }
+ CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
+ const CCoinControl& coin_control,
+ bool sign,
+ int& change_pos,
+ CAmount& fee,
+ bilingual_str& fail_reason) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ CTransactionRef tx;
+ FeeCalculation fee_calc_out;
+ if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos,
+ fail_reason, coin_control, fee_calc_out, sign)) {
+ return {};
+ }
+ return tx;
+ }
+ void commitTransaction(CTransactionRef tx,
+ WalletValueMap value_map,
+ WalletOrderForm order_form) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form));
+ }
+ bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
+ bool abandonTransaction(const uint256& txid) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->AbandonTransaction(txid);
+ }
+ bool transactionCanBeBumped(const uint256& txid) override
+ {
+ return feebumper::TransactionCanBeBumped(*m_wallet.get(), txid);
+ }
+ bool createBumpTransaction(const uint256& txid,
+ const CCoinControl& coin_control,
+ std::vector<bilingual_str>& errors,
+ CAmount& old_fee,
+ CAmount& new_fee,
+ CMutableTransaction& mtx) override
+ {
+ return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx) == feebumper::Result::OK;
+ }
+ bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); }
+ bool commitBumpTransaction(const uint256& txid,
+ CMutableTransaction&& mtx,
+ std::vector<bilingual_str>& errors,
+ uint256& bumped_txid) override
+ {
+ return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) ==
+ feebumper::Result::OK;
+ }
+ CTransactionRef getTx(const uint256& txid) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ auto mi = m_wallet->mapWallet.find(txid);
+ if (mi != m_wallet->mapWallet.end()) {
+ return mi->second.tx;
+ }
+ return {};
+ }
+ WalletTx getWalletTx(const uint256& txid) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ auto mi = m_wallet->mapWallet.find(txid);
+ if (mi != m_wallet->mapWallet.end()) {
+ return MakeWalletTx(*m_wallet, mi->second);
+ }
+ return {};
+ }
+ std::vector<WalletTx> getWalletTxs() override
+ {
+ LOCK(m_wallet->cs_wallet);
+ std::vector<WalletTx> result;
+ result.reserve(m_wallet->mapWallet.size());
+ for (const auto& entry : m_wallet->mapWallet) {
+ result.emplace_back(MakeWalletTx(*m_wallet, entry.second));
+ }
+ return result;
+ }
+ bool tryGetTxStatus(const uint256& txid,
+ interfaces::WalletTxStatus& tx_status,
+ int& num_blocks,
+ int64_t& block_time) override
+ {
+ TRY_LOCK(m_wallet->cs_wallet, locked_wallet);
+ if (!locked_wallet) {
+ return false;
+ }
+ auto mi = m_wallet->mapWallet.find(txid);
+ if (mi == m_wallet->mapWallet.end()) {
+ return false;
+ }
+ num_blocks = m_wallet->GetLastBlockHeight();
+ block_time = -1;
+ CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(block_time)));
+ tx_status = MakeWalletTxStatus(*m_wallet, mi->second);
+ return true;
+ }
+ WalletTx getWalletTxDetails(const uint256& txid,
+ WalletTxStatus& tx_status,
+ WalletOrderForm& order_form,
+ bool& in_mempool,
+ int& num_blocks) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ auto mi = m_wallet->mapWallet.find(txid);
+ if (mi != m_wallet->mapWallet.end()) {
+ num_blocks = m_wallet->GetLastBlockHeight();
+ in_mempool = mi->second.InMempool();
+ order_form = mi->second.vOrderForm;
+ tx_status = MakeWalletTxStatus(*m_wallet, mi->second);
+ return MakeWalletTx(*m_wallet, mi->second);
+ }
+ return {};
+ }
+ TransactionError fillPSBT(int sighash_type,
+ bool sign,
+ bool bip32derivs,
+ PartiallySignedTransaction& psbtx,
+ bool& complete,
+ size_t* n_signed) override
+ {
+ return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed);
+ }
+ WalletBalances getBalances() override
+ {
+ const auto bal = m_wallet->GetBalance();
+ WalletBalances result;
+ result.balance = bal.m_mine_trusted;
+ result.unconfirmed_balance = bal.m_mine_untrusted_pending;
+ result.immature_balance = bal.m_mine_immature;
+ result.have_watch_only = haveWatchOnly();
+ if (result.have_watch_only) {
+ result.watch_only_balance = bal.m_watchonly_trusted;
+ result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending;
+ result.immature_watch_only_balance = bal.m_watchonly_immature;
+ }
+ return result;
+ }
+ bool tryGetBalances(WalletBalances& balances, uint256& block_hash) override
+ {
+ TRY_LOCK(m_wallet->cs_wallet, locked_wallet);
+ if (!locked_wallet) {
+ return false;
+ }
+ block_hash = m_wallet->GetLastBlockHash();
+ balances = getBalances();
+ return true;
+ }
+ CAmount getBalance() override { return m_wallet->GetBalance().m_mine_trusted; }
+ CAmount getAvailableBalance(const CCoinControl& coin_control) override
+ {
+ return m_wallet->GetAvailableBalance(&coin_control);
+ }
+ isminetype txinIsMine(const CTxIn& txin) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->IsMine(txin);
+ }
+ isminetype txoutIsMine(const CTxOut& txout) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->IsMine(txout);
+ }
+ CAmount getDebit(const CTxIn& txin, isminefilter filter) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->GetDebit(txin, filter);
+ }
+ CAmount getCredit(const CTxOut& txout, isminefilter filter) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ return m_wallet->GetCredit(txout, filter);
+ }
+ CoinsList listCoins() override
+ {
+ LOCK(m_wallet->cs_wallet);
+ CoinsList result;
+ for (const auto& entry : m_wallet->ListCoins()) {
+ auto& group = result[entry.first];
+ for (const auto& coin : entry.second) {
+ group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
+ MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth));
+ }
+ }
+ return result;
+ }
+ std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override
+ {
+ LOCK(m_wallet->cs_wallet);
+ std::vector<WalletTxOut> result;
+ result.reserve(outputs.size());
+ for (const auto& output : outputs) {
+ result.emplace_back();
+ auto it = m_wallet->mapWallet.find(output.hash);
+ if (it != m_wallet->mapWallet.end()) {
+ int depth = it->second.GetDepthInMainChain();
+ if (depth >= 0) {
+ result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth);
+ }
+ }
+ }
+ return result;
+ }
+ CAmount getRequiredFee(unsigned int tx_bytes) override { return GetRequiredFee(*m_wallet, tx_bytes); }
+ CAmount getMinimumFee(unsigned int tx_bytes,
+ const CCoinControl& coin_control,
+ int* returned_target,
+ FeeReason* reason) override
+ {
+ FeeCalculation fee_calc;
+ CAmount result;
+ result = GetMinimumFee(*m_wallet, tx_bytes, coin_control, &fee_calc);
+ if (returned_target) *returned_target = fee_calc.returnedTarget;
+ if (reason) *reason = fee_calc.reason;
+ return result;
+ }
+ unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
+ bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
+ bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
+ bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); }
+ OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; }
+ CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
+ void remove() override
+ {
+ RemoveWallet(m_wallet, false /* load_on_start */);
+ }
+ bool isLegacy() override { return m_wallet->IsLegacy(); }
+ std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyUnload.connect(fn));
+ }
+ std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override
+ {
+ return MakeHandler(m_wallet->ShowProgress.connect(fn));
+ }
+ std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyStatusChanged.connect([fn](CWallet*) { fn(); }));
+ }
+ std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyAddressBookChanged.connect(
+ [fn](CWallet*, const CTxDestination& address, const std::string& label, bool is_mine,
+ const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
+ }
+ std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyTransactionChanged.connect(
+ [fn](CWallet*, const uint256& txid, ChangeType status) { fn(txid, status); }));
+ }
+ std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyWatchonlyChanged.connect(fn));
+ }
+ std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) override
+ {
+ return MakeHandler(m_wallet->NotifyCanGetAddressesChanged.connect(fn));
+ }
+ CWallet* wallet() override { return m_wallet.get(); }
+
+ std::shared_ptr<CWallet> m_wallet;
+};
+
+class WalletClientImpl : public WalletClient
+{
+public:
+ WalletClientImpl(Chain& chain, ArgsManager& args)
+ {
+ m_context.chain = &chain;
+ m_context.args = &args;
+ }
+ ~WalletClientImpl() override { UnloadWallets(); }
+
+ //! ChainClient methods
+ void registerRpcs() override
+ {
+ for (const CRPCCommand& command : GetWalletRPCCommands()) {
+ m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
+ return command.actor({request, m_context}, result, last_handler);
+ }, command.argNames, command.unique_id);
+ m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
+ }
+ }
+ bool verify() override { return VerifyWallets(*m_context.chain); }
+ bool load() override { return LoadWallets(*m_context.chain); }
+ void start(CScheduler& scheduler) override { return StartWallets(scheduler, *Assert(m_context.args)); }
+ void flush() override { return FlushWallets(); }
+ void stop() override { return StopWallets(); }
+ void setMockTime(int64_t time) override { return SetMockTime(time); }
+
+ //! WalletClient methods
+ std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) override
+ {
+ std::shared_ptr<CWallet> wallet;
+ DatabaseOptions options;
+ DatabaseStatus status;
+ options.require_create = true;
+ options.create_flags = wallet_creation_flags;
+ options.create_passphrase = passphrase;
+ return MakeWallet(CreateWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
+ }
+ std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
+ {
+ DatabaseOptions options;
+ DatabaseStatus status;
+ options.require_existing = true;
+ return MakeWallet(LoadWallet(*m_context.chain, name, true /* load_on_start */, options, status, error, warnings));
+ }
+ std::string getWalletDir() override
+ {
+ return GetWalletDir().string();
+ }
+ std::vector<std::string> listWalletDir() override
+ {
+ std::vector<std::string> paths;
+ for (auto& path : ListWalletDir()) {
+ paths.push_back(path.string());
+ }
+ return paths;
+ }
+ std::vector<std::unique_ptr<Wallet>> getWallets() override
+ {
+ std::vector<std::unique_ptr<Wallet>> wallets;
+ for (const auto& wallet : GetWallets()) {
+ wallets.emplace_back(MakeWallet(wallet));
+ }
+ return wallets;
+ }
+ std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) override
+ {
+ return HandleLoadWallet(std::move(fn));
+ }
+
+ WalletContext m_context;
+ const std::vector<std::string> m_wallet_filenames;
+ std::vector<std::unique_ptr<Handler>> m_rpc_handlers;
+ std::list<CRPCCommand> m_rpc_commands;
+};
+} // namespace
+} // namespace wallet
+
+namespace interfaces {
+std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? MakeUnique<wallet::WalletImpl>(wallet) : nullptr; }
+
+std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args)
+{
+ return MakeUnique<wallet::WalletClientImpl>(chain, args);
+}
+} // namespace interfaces
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 884ab58497..6b46868d10 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1523,7 +1523,9 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue&
// Need to ExpandPrivate to check if private keys are available for all pubkeys
FlatSigningProvider expand_keys;
std::vector<CScript> scripts;
- parsed_desc->Expand(0, keys, scripts, expand_keys);
+ if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
+ }
parsed_desc->ExpandPrivate(0, keys, expand_keys);
// Check if all private keys are provided
@@ -1559,7 +1561,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue&
}
// Add descriptor to the wallet
- auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label);
+ auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label, internal);
if (spk_manager == nullptr) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index ebcab1227d..7ea6a214b2 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -8,6 +8,7 @@
#include <interfaces/chain.h>
#include <key_io.h>
#include <node/context.h>
+#include <optional.h>
#include <outputtype.h>
#include <policy/feerate.h>
#include <policy/fees.h>
@@ -47,8 +48,6 @@ using interfaces::FoundBlock;
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
-static const uint32_t WALLET_BTC_KB_TO_SAT_B = COIN / 1000; // 1 sat / B = 0.00001 BTC / kB
-
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
@@ -198,36 +197,36 @@ static std::string LabelFromValue(const UniValue& value)
/**
* Update coin control with fee estimation based on the given parameters
*
- * @param[in] pwallet Wallet pointer
- * @param[in,out] cc Coin control which is to be updated
- * @param[in] estimate_mode String value (e.g. "ECONOMICAL")
- * @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
- * @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
+ * @param[in] wallet Wallet reference
+ * @param[in,out] cc Coin control to be updated
+ * @param[in] conf_target UniValue integer; confirmation target in blocks, values between 1 and 1008 are valid per policy/fees.h;
+ * @param[in] estimate_mode UniValue string; fee estimation mode, valid values are "unset", "economical" or "conservative";
+ * @param[in] fee_rate UniValue real; fee rate in sat/vB;
+ * if present, both conf_target and estimate_mode must either be null, or "unset"
+ * @param[in] override_min_fee bool; whether to set fOverrideFeeRate to true to disable minimum fee rate checks and instead
+ * verify only that fee_rate is greater than 0
+ * @throws a JSONRPCError if conf_target, estimate_mode, or fee_rate contain invalid values or are in conflict
*/
-static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
+static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, bool override_min_fee)
{
- if (!estimate_mode.isNull()) {
- if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
+ if (!fee_rate.isNull()) {
+ if (!conf_target.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
}
- }
-
- if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
- if (estimate_param.isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Selected estimate_mode %s requires a fee rate to be specified in conf_target", estimate_mode.get_str()));
+ if (!estimate_mode.isNull() && estimate_mode.get_str() != "unset") {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
}
-
- CAmount fee_rate = AmountFromValue(estimate_param);
- if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
- fee_rate /= WALLET_BTC_KB_TO_SAT_B;
- }
-
- cc.m_feerate = CFeeRate(fee_rate);
-
- // default RBF to true for explicit fee rate modes
+ cc.m_feerate = CFeeRate(AmountFromValue(fee_rate), COIN);
+ if (override_min_fee) cc.fOverrideFeeRate = true;
+ // Default RBF to true for explicit fee_rate, if unset.
if (cc.m_signal_bip125_rbf == nullopt) cc.m_signal_bip125_rbf = true;
- } else if (!estimate_param.isNull()) {
- cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
+ return;
+ }
+ if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
+ }
+ if (!conf_target.isNull()) {
+ cc.m_confirm_target = ParseConfirmTarget(conf_target, wallet.chain().estimateMaxBlocks());
}
}
@@ -440,12 +439,12 @@ static RPCHelpMan sendtoaddress()
{"subtractfeefromamount", RPCArg::Type::BOOL, /* 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."},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
"dirty if they have previously been used in a transaction."},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra information about the transaction."},
},
{
@@ -461,12 +460,17 @@ static RPCHelpMan sendtoaddress()
},
},
RPCExamples{
- HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
- + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
- + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
- + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
- + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
- + HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
+ "\nSend 0.1 BTC\n"
+ + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1") +
+ "\nSend 0.1 BTC with a confirmation target of 6 blocks in economical fee estimate mode using positional arguments\n"
+ + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"sean's outpost\" false true 6 economical") +
+ "\nSend 0.1 BTC with a fee rate of 1.1 " + CURRENCY_ATOM + "/vB, subtract fee from amount, BIP125-replaceable, using positional arguments\n"
+ + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"drinks\" \"room77\" true true null \"unset\" null 1.1") +
+ "\nSend 0.2 BTC with a confirmation target of 6 blocks in economical fee estimate mode using named arguments\n"
+ + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.2 conf_target=6 estimate_mode=\"economical\"") +
+ "\nSend 0.5 BTC with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n"
+ + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25")
+ + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25 subtractfeefromamount=false replaceable=true avoid_reuse=true comment=\"2 pizzas\" comment_to=\"jeremy\" verbose=true")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
@@ -501,7 +505,7 @@ static RPCHelpMan sendtoaddress()
// We also enable partial spend avoidance if reuse avoidance is set.
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
- SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
+ SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false);
EnsureWalletIsUnlocked(pwallet);
@@ -515,7 +519,7 @@ static RPCHelpMan sendtoaddress()
std::vector<CRecipient> recipients;
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
- bool verbose = request.params[9].isNull() ? false: request.params[9].get_bool();
+ const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()};
return SendMoney(pwallet, coin_control, recipients, mapValue, verbose);
},
@@ -869,10 +873,10 @@ static RPCHelpMan sendmany()
},
},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
},
{
@@ -929,11 +933,11 @@ static RPCHelpMan sendmany()
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
}
- SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
+ SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[8], /* override_min_fee */ false);
std::vector<CRecipient> recipients;
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
- bool verbose = request.params[8].isNull() ? false : request.params[8].get_bool();
+ const bool verbose{request.params[9].isNull() ? false : request.params[9].get_bool()};
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue), verbose);
},
@@ -2310,7 +2314,7 @@ static RPCHelpMan settxfee()
"\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n"
"Can be deactivated by passing 0 as the fee. In that case automatic fee selection will be used by default.\n",
{
- {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kB"},
+ {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kvB"},
},
RPCResult{
RPCResult::Type::BOOL, "", "Returns true if successful"
@@ -2433,7 +2437,7 @@ static RPCHelpMan getwalletinfo()
{RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
{RPCResult::Type::NUM, "keypoolsize_hd_internal", "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
{RPCResult::Type::NUM_TIME, "unlocked_until", /* optional */ true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
- {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"},
+ {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true, "the Hash160 of the HD seed (only present when HD is enabled)"},
{RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
{RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
@@ -2751,6 +2755,12 @@ static RPCHelpMan createwallet()
warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
}
+#ifndef USE_BDB
+ if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
+ }
+#endif
+
DatabaseOptions options;
DatabaseStatus status;
options.require_create = true;
@@ -2779,7 +2789,7 @@ static RPCHelpMan unloadwallet()
"Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
"Specifying the wallet name on a wallet endpoint is invalid.",
{
- {"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."},
+ {"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC endpoint", "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
RPCResult{RPCResult::Type::OBJ, "", "", {
@@ -2793,8 +2803,8 @@ static RPCHelpMan unloadwallet()
{
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
- if (!request.params[0].isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet");
+ if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
}
} else {
wallet_name = request.params[0].get_str();
@@ -3046,7 +3056,7 @@ static RPCHelpMan listunspent()
};
}
-void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl)
+void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
{
// 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
@@ -3079,7 +3089,8 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
{"lockUnspents", UniValueType(UniValue::VBOOL)},
{"lock_unspents", UniValueType(UniValue::VBOOL)},
{"locktime", UniValueType(UniValue::VNUM)},
- {"feeRate", UniValueType()}, // will be checked below
+ {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
+ {"feeRate", UniValueType()}, // will be checked by AmountFromValue() below
{"psbt", UniValueType(UniValue::VBOOL)},
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
@@ -3126,10 +3137,12 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
lockUnspents = (options.exists("lock_unspents") ? options["lock_unspents"] : options["lockUnspents"]).get_bool();
}
- if (options.exists("feeRate"))
- {
+ if (options.exists("feeRate")) {
+ if (options.exists("fee_rate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both fee_rate (" + CURRENCY_ATOM + "/vB) and feeRate (" + CURRENCY_UNIT + "/kvB)");
+ }
if (options.exists("conf_target")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
}
if (options.exists("estimate_mode")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
@@ -3144,7 +3157,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
if (options.exists("replaceable")) {
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
}
- SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
+ SetFeeEstimateMode(*pwallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee);
}
} else {
// if options is null and not a bool
@@ -3201,7 +3214,8 @@ static RPCHelpMan fundrawtransaction()
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
- {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
+ {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_UNIT + "/kvB."},
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The integers.\n"
"The fee will be equally deducted from the amount of each specified output.\n"
"Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
@@ -3212,8 +3226,7 @@ static RPCHelpMan fundrawtransaction()
},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
@@ -3265,7 +3278,7 @@ static RPCHelpMan fundrawtransaction()
CCoinControl coin_control;
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
coin_control.m_add_inputs = true;
- FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control);
+ FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true);
UniValue result(UniValue::VOBJ);
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
@@ -3373,35 +3386,38 @@ RPCHelpMan signrawtransactionwithwallet()
static RPCHelpMan bumpfee_helper(std::string method_name)
{
bool want_psbt = method_name == "psbtbumpfee";
+ const std::string incremental_fee{CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE).ToString(FeeEstimateMode::SAT_VB)};
return RPCHelpMan{method_name,
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
+ std::string(want_psbt ? "Returns a PSBT instead of creating and signing a new transaction.\n" : "") +
"An opt-in RBF transaction with the given txid must be in the wallet.\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"
+ "The command will pay the additional fee by reducing change outputs or adding inputs when necessary.\n"
+ "It may add a new change output if one does not already exist.\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 the estimatesmartfee RPC.\n"
"The user can specify a confirmation target for estimatesmartfee.\n"
- "Alternatively, the user can specify a fee_rate (" + CURRENCY_UNIT + " per kB) for the new transaction.\n"
+ "Alternatively, the user can specify a fee rate in " + CURRENCY_ATOM + "/vB for the new transaction.\n"
"At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
- "returned by getnetworkinfo) to enter the node's mempool.\n",
+ "returned by getnetworkinfo) to enter the node's mempool.\n"
+ "* WARNING: before version 0.21, fee_rate was in " + CURRENCY_UNIT + "/kvB. As of 0.21, fee_rate is in " + CURRENCY_ATOM + "/vB. *\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
- {"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + "/kB.\n"
- "Specify a fee rate instead of relying on the built-in fee estimator.\n"
- "Must be at least 0.0001 " + CURRENCY_UNIT + "/kB higher than the current transaction fee rate.\n"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks\n"},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation",
+ "\nSpecify a fee rate in " + CURRENCY_ATOM + "/vB instead of relying on the built-in fee estimator.\n"
+ "Must be at least " + incremental_fee + " higher than the current transaction fee rate.\n"
+ "WARNING: before version 0.21, fee_rate was in " + CURRENCY_UNIT + "/kvB. As of 0.21, fee_rate is in " + CURRENCY_ATOM + "/vB.\n"},
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
"marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
"be left unchanged from the original. If false, any input sequence numbers in the\n"
"original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
"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)."},
+ "are replaceable).\n"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
@@ -3454,7 +3470,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
{
{"confTarget", UniValueType(UniValue::VNUM)},
{"conf_target", UniValueType(UniValue::VNUM)},
- {"fee_rate", UniValueType(UniValue::VNUM)},
+ {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
{"replaceable", UniValueType(UniValue::VBOOL)},
{"estimate_mode", UniValueType(UniValue::VSTR)},
},
@@ -3466,22 +3482,10 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
auto conf_target = options.exists("confTarget") ? options["confTarget"] : options["conf_target"];
- if (!conf_target.isNull()) {
- if (options.exists("fee_rate")) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
- }
- } else if (options.exists("fee_rate")) {
- CFeeRate fee_rate(AmountFromValue(options["fee_rate"]));
- if (fee_rate <= CFeeRate(0)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid fee_rate %s (must be greater than 0)", fee_rate.ToString()));
- }
- coin_control.m_feerate = fee_rate;
- }
-
if (options.exists("replaceable")) {
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
}
- SetFeeEstimateMode(pwallet, coin_control, options["estimate_mode"], conf_target);
+ SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /* override_min_fee */ false);
}
// Make sure the results are valid at least up to the most recent block
@@ -3593,7 +3597,7 @@ static RPCHelpMan rescanblockchain()
}
int start_height = 0;
- Optional<int> stop_height;
+ Optional<int> stop_height = MakeOptional(false, int());
uint256 start_block;
{
LOCK(pwallet->cs_wallet);
@@ -4017,10 +4021,10 @@ static RPCHelpMan send()
},
},
},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
{"add_inputs", RPCArg::Type::BOOL, /* default */ "false", "If inputs are specified, automatically include more if they are not enough."},
@@ -4028,10 +4032,10 @@ static RPCHelpMan send()
{"change_address", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"},
{"change_position", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
@@ -4068,18 +4072,25 @@ static RPCHelpMan send()
}
},
RPCExamples{""
- "\nSend with a fee rate of 1 satoshi per byte\n"
- + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 sat/b\n") +
- "\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n"
+ "\nSend 0.1 BTC with a confirmation target of 6 blocks in economical fee estimate mode\n"
+ + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 6 economical\n") +
+ "Send 0.2 BTC with a fee rate of 1.1 " + CURRENCY_ATOM + "/vB using positional arguments\n"
+ + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' null \"unset\" 1.1\n") +
+ "Send 0.2 BTC with a fee rate of 1 " + CURRENCY_ATOM + "/vB using the options argument\n"
+ + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' null \"unset\" null '{\"fee_rate\": 1}'\n") +
+ "Send 0.3 BTC with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n"
+ + HelpExampleCli("-named send", "outputs='{\"" + EXAMPLE_ADDRESS[0] + "\": 0.3}' fee_rate=25\n") +
+ "Create a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n"
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
RPCTypeCheck(request.params, {
- UniValueType(), // ARR or OBJ, checked later
- UniValue::VNUM,
- UniValue::VSTR,
- UniValue::VOBJ
+ UniValueType(), // outputs (ARR or OBJ, checked later)
+ UniValue::VNUM, // conf_target
+ UniValue::VSTR, // estimate_mode
+ UniValue::VNUM, // fee_rate
+ UniValue::VOBJ, // options
}, true
);
@@ -4087,18 +4098,28 @@ static RPCHelpMan send()
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
- UniValue options = request.params[3];
- if (options.exists("feeRate") || options.exists("fee_rate") || options.exists("estimate_mode") || options.exists("conf_target")) {
+ UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
+ if (options.exists("conf_target") || options.exists("estimate_mode")) {
if (!request.params[1].isNull() || !request.params[2].isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Use either conf_target and estimate_mode or the options dictionary to control fee rate");
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both");
}
} else {
options.pushKV("conf_target", request.params[1]);
options.pushKV("estimate_mode", request.params[2]);
}
+ if (options.exists("fee_rate")) {
+ if (!request.params[3].isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both");
+ }
+ } else {
+ options.pushKV("fee_rate", request.params[3]);
+ }
if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
}
+ if (options.exists("feeRate")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate");
+ }
if (options.exists("changeAddress")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
}
@@ -4128,7 +4149,7 @@ static RPCHelpMan send()
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
- FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control);
+ FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false);
bool add_to_wallet = true;
if (options.exists("add_to_wallet")) {
@@ -4213,7 +4234,7 @@ static RPCHelpMan sethdseed()
// Do not do anything to non-HD wallets
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
}
EnsureWalletIsUnlocked(pwallet);
@@ -4356,7 +4377,8 @@ static RPCHelpMan walletcreatefundedpsbt()
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
- {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
+ {"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
+ {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_UNIT + "/kvB."},
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The outputs to subtract the fee from.\n"
"The fee will be equally deducted from the amount of each specified output.\n"
"Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
@@ -4367,8 +4389,7 @@ static RPCHelpMan walletcreatefundedpsbt()
},
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees"},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
- "or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
+ {"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
},
@@ -4415,7 +4436,7 @@ static RPCHelpMan walletcreatefundedpsbt()
// Automatically select coins, unless at least one is manually selected. Can
// be overridden by options.add_inputs.
coin_control.m_add_inputs = rawTx.vin.size() == 0;
- FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control);
+ FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true);
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
@@ -4444,14 +4465,18 @@ static RPCHelpMan walletcreatefundedpsbt()
static RPCHelpMan upgradewallet()
{
return RPCHelpMan{"upgradewallet",
- "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified\n"
+ "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
"New keys may be generated and a new wallet backup will need to be made.",
{
- {"version", RPCArg::Type::NUM, /* default */ strprintf("%d", FEATURE_LATEST), "The version number to upgrade to. Default is the latest wallet version"}
+ {"version", RPCArg::Type::NUM, /* default */ strprintf("%d", FEATURE_LATEST), "The version number to upgrade to. Default is the latest wallet version."}
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
+ {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
+ {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
+ {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
+ {RPCResult::Type::STR, "result", /* optional */ true, "Description of result, if no error"},
{RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"}
},
},
@@ -4473,14 +4498,28 @@ static RPCHelpMan upgradewallet()
if (!request.params[0].isNull()) {
version = request.params[0].get_int();
}
-
bilingual_str error;
- std::vector<bilingual_str> warnings;
- if (!pwallet->UpgradeWallet(version, error, warnings)) {
- throw JSONRPCError(RPC_WALLET_ERROR, error.original);
+ const int previous_version{pwallet->GetVersion()};
+ const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
+ const int current_version{pwallet->GetVersion()};
+ std::string result;
+
+ if (wallet_upgraded) {
+ if (previous_version == current_version) {
+ result = "Already at latest version. Wallet version unchanged.";
+ } else {
+ result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
+ }
}
+
UniValue obj(UniValue::VOBJ);
- if (!error.empty()) {
+ obj.pushKV("wallet_name", pwallet->GetName());
+ obj.pushKV("previous_version", previous_version);
+ obj.pushKV("current_version", current_version);
+ if (!result.empty()) {
+ obj.pushKV("result", result);
+ } else {
+ CHECK_NONFATAL(!error.empty());
obj.pushKV("error", error.original);
}
return obj;
@@ -4550,9 +4589,9 @@ static const CRPCCommand commands[] =
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
- { "wallet", "send", &send, {"outputs","conf_target","estimate_mode","options"} },
- { "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","verbose"} },
- { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","verbose"} },
+ { "wallet", "send", &send, {"outputs","conf_target","estimate_mode","fee_rate","options"} },
+ { "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","fee_rate","verbose"} },
+ { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","fee_rate","verbose"} },
{ "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
{ "wallet", "setlabel", &setlabel, {"address","label"} },
{ "wallet", "settxfee", &settxfee, {"amount"} },
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index 225b975067..da5ca7858f 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -6,6 +6,7 @@
#include <fs.h>
#include <streams.h>
#include <util/translation.h>
+#include <wallet/bdb.h>
#include <wallet/salvage.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
@@ -27,6 +28,7 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
DatabaseStatus status;
options.require_existing = true;
options.verify = false;
+ options.require_format = DatabaseFormat::BERKELEY;
std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
if (!database) return false;
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 188289b010..15972fe7bb 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -3,12 +3,15 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <key_io.h>
+#include <logging.h>
#include <outputtype.h>
#include <script/descriptor.h>
#include <script/sign.h>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <util/string.h>
+#include <util/system.h>
+#include <util/time.h>
#include <util/translation.h>
#include <wallet/scriptpubkeyman.h>
@@ -438,12 +441,12 @@ bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const
return keypool_has_keys;
}
-bool LegacyScriptPubKeyMan::Upgrade(int prev_version, bilingual_str& error)
+bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual_str& error)
{
LOCK(cs_KeyStore);
bool hd_upgrade = false;
bool split_upgrade = false;
- if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) {
+ if (IsFeatureSupported(new_version, FEATURE_HD) && !IsHDEnabled()) {
WalletLogPrintf("Upgrading wallet to HD\n");
m_storage.SetMinVersion(FEATURE_HD);
@@ -453,10 +456,17 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, bilingual_str& error)
hd_upgrade = true;
}
// Upgrade to HD chain split if necessary
- if (m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
+ if (!IsFeatureSupported(prev_version, FEATURE_HD_SPLIT) && IsFeatureSupported(new_version, FEATURE_HD_SPLIT)) {
WalletLogPrintf("Upgrading wallet to use HD chain split\n");
m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
split_upgrade = FEATURE_HD_SPLIT > prev_version;
+ // Upgrade the HDChain
+ if (m_hd_chain.nVersion < CHDChain::VERSION_HD_CHAIN_SPLIT) {
+ m_hd_chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
+ if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(m_hd_chain)) {
+ throw std::runtime_error(std::string(__func__) + ": writing chain failed");
+ }
+ }
}
// Mark all keys currently in the keypool as pre-split
if (split_upgrade) {
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 63c10b7a0d..43791acfcf 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -11,6 +11,7 @@
#include <script/standard.h>
#include <util/error.h>
#include <util/message.h>
+#include <util/time.h>
#include <wallet/crypter.h>
#include <wallet/ismine.h>
#include <wallet/walletdb.h>
@@ -37,7 +38,7 @@ public:
virtual bool IsWalletFlagSet(uint64_t) const = 0;
virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
- virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0;
+ virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
virtual bool HasEncryptionKeys() const = 0;
virtual bool IsLocked() const = 0;
@@ -206,7 +207,7 @@ public:
virtual bool CanGetAddresses(bool internal = false) const { return false; }
/** Upgrades the wallet to the specified version */
- virtual bool Upgrade(int prev_version, bilingual_str& error) { return false; }
+ virtual bool Upgrade(int prev_version, int new_version, bilingual_str& error) { return false; }
virtual bool HavePrivateKeys() const { return false; }
@@ -371,7 +372,7 @@ public:
bool SetupGeneration(bool force = false) override;
- bool Upgrade(int prev_version, bilingual_str& error) override;
+ bool Upgrade(int prev_version, int new_version, bilingual_str& error) override;
bool HavePrivateKeys() const override;
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index d83332c194..d278d96476 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -206,7 +206,9 @@ void SQLiteDatabase::Open()
}
if (m_db == nullptr) {
- TryCreateDirectories(m_dir_path);
+ if (!m_mock) {
+ TryCreateDirectories(m_dir_path);
+ }
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c42114c394..4911af08c6 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -688,7 +688,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure, malformed_descriptor);
}
-//! Test CreateWalletFromFile function and its behavior handling potential race
+//! Test CWallet::Create() and its behavior handling potential race
//! conditions if it's called the same time an incoming transaction shows up in
//! the mempool or a new block.
//!
@@ -706,7 +706,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
//! wallet rescan and notifications are immediately synced, to verify the wallet
//! must already have a handler in place for them, and there's no gap after
//! rescanning where new transactions in new blocks could be lost.
-BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
+BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
{
// Create new wallet with known key and unload it.
auto chain = interfaces::MakeChain(m_node);
diff --git a/src/wallet/test/walletdb_tests.cpp b/src/wallet/test/walletdb_tests.cpp
new file mode 100644
index 0000000000..a3859e2e4b
--- /dev/null
+++ b/src/wallet/test/walletdb_tests.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2012-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 <test/util/setup_common.h>
+#include <clientversion.h>
+#include <streams.h>
+#include <uint256.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(walletdb_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(walletdb_readkeyvalue)
+{
+ /**
+ * When ReadKeyValue() reads from either a "key" or "wkey" it first reads the CDataStream steam into a
+ * CPrivKey or CWalletKey respectively and then reads a hash of the pubkey and privkey into a uint256.
+ * Wallets from 0.8 or before do not store the pubkey/privkey hash, trying to read the hash from old
+ * wallets throws an exception, for backwards compatibility this read is wrapped in a try block to
+ * silently fail. The test here makes sure the type of exception thrown from CDataStream::read()
+ * matches the type we expect, otherwise we need to update the "key"/"wkey" exception type caught.
+ */
+ CDataStream ssValue(SER_DISK, CLIENT_VERSION);
+ uint256 dummy;
+ BOOST_CHECK_THROW(ssValue >> dummy, std::ios_base::failure);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 8609520c1b..65b54f39b4 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -436,21 +436,13 @@ void CWallet::chainStateFlushed(const CBlockLocator& loc)
batch.WriteBestBlock(loc);
}
-void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit)
+void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in)
{
LOCK(cs_wallet);
if (nWalletVersion >= nVersion)
return;
-
- // when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
- if (fExplicit && nVersion > nWalletMaxVersion)
- nVersion = FEATURE_LATEST;
-
nWalletVersion = nVersion;
- if (nVersion > nWalletMaxVersion)
- nWalletMaxVersion = nVersion;
-
{
WalletBatch* batch = batch_in ? batch_in : new WalletBatch(GetDatabase());
if (nWalletVersion > 40000)
@@ -460,18 +452,6 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in,
}
}
-bool CWallet::SetMaxVersion(int nVersion)
-{
- LOCK(cs_wallet);
- // cannot downgrade below current version
- if (nWalletVersion > nVersion)
- return false;
-
- nWalletMaxVersion = nVersion;
-
- return true;
-}
-
std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
std::set<uint256> result;
@@ -656,7 +636,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
}
// Encryption was introduced in version 0.4.0
- SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
+ SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch);
if (!encrypted_batch->TxnCommit()) {
delete encrypted_batch;
@@ -1778,7 +1758,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
double progress_current = progress_begin;
int block_height = start_height;
while (!fAbortRescan && !chain().shutdownRequested()) {
- m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
+ if (progress_end - progress_begin > 0.0) {
+ m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
+ } else { // avoid divide-by-zero for single block scan range (i.e. start and stop hashes are equal)
+ m_scanning_progress = 0;
+ }
if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
@@ -2834,7 +2818,7 @@ bool CWallet::CreateTransactionInternal(
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
// provided one
if (coin_control.m_feerate && nFeeRateNeeded > *coin_control.m_feerate) {
- error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(), nFeeRateNeeded.ToString());
+ error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), nFeeRateNeeded.ToString(FeeEstimateMode::SAT_VB));
return false;
}
@@ -3106,10 +3090,10 @@ bool CWallet::CreateTransactionInternal(
WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
feeCalc.est.pass.start, feeCalc.est.pass.end,
- 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool),
+ (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
feeCalc.est.fail.start, feeCalc.est.fail.end,
- 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool),
+ (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
return true;
}
@@ -4118,36 +4102,33 @@ const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest
return &address_book_it->second;
}
-bool CWallet::UpgradeWallet(int version, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool CWallet::UpgradeWallet(int version, bilingual_str& error)
{
int prev_version = GetVersion();
- int nMaxVersion = version;
- if (nMaxVersion == 0) // the -upgradewallet without argument case
- {
+ if (version == 0) {
WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
- nMaxVersion = FEATURE_LATEST;
- SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
+ version = FEATURE_LATEST;
} else {
- WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
+ WalletLogPrintf("Allowing wallet upgrade up to %i\n", version);
}
- if (nMaxVersion < GetVersion())
- {
- error = _("Cannot downgrade wallet");
+ if (version < prev_version) {
+ error = strprintf(_("Cannot downgrade wallet from version %i to version %i. Wallet version unchanged."), prev_version, version);
return false;
}
- SetMaxVersion(nMaxVersion);
LOCK(cs_wallet);
// Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
- int max_version = GetVersion();
- if (!CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) {
- error = _("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use version 169900 or no version specified.");
+ if (!CanSupportFeature(FEATURE_HD_SPLIT) && version >= FEATURE_HD_SPLIT && version < FEATURE_PRE_SPLIT_KEYPOOL) {
+ error = strprintf(_("Cannot upgrade a non HD split wallet from version %i to version %i without upgrading to support pre-split keypool. Please use version %i or no version specified."), prev_version, version, FEATURE_PRE_SPLIT_KEYPOOL);
return false;
}
+ // Permanently upgrade to the version
+ SetMinVersion(GetClosestWalletFeature(version));
+
for (auto spk_man : GetActiveScriptPubKeyMans()) {
- if (!spk_man->Upgrade(prev_version, error)) {
+ if (!spk_man->Upgrade(prev_version, version, error)) {
return false;
}
}
@@ -4511,7 +4492,7 @@ DescriptorScriptPubKeyMan* CWallet::GetDescriptorScriptPubKeyMan(const WalletDes
return nullptr;
}
-ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label)
+ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal)
{
if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
WalletLogPrintf("Cannot add WalletDescriptor to a non-descriptor wallet\n");
@@ -4556,7 +4537,10 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
// Top up key pool, the manager will generate new scriptPubKeys internally
- new_spk_man->TopUp();
+ if (!new_spk_man->TopUp()) {
+ WalletLogPrintf("Could not top up scriptPubKeys\n");
+ return nullptr;
+ }
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
@@ -4568,7 +4552,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
CTxDestination dest;
- if (ExtractDestination(script_pub_keys.at(0), dest)) {
+ if (!internal && ExtractDestination(script_pub_keys.at(0), dest)) {
SetAddressBook(dest, label, "receive");
}
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 5e23622e23..e6beb111fb 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -636,9 +636,6 @@ private:
//! the current wallet version: clients below this version are not able to load the wallet
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
- //! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
- int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
-
int64_t nNextResend = 0;
bool fBroadcastTransactions = false;
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
@@ -797,8 +794,8 @@ public:
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! check whether we are allowed to upgrade (or already support) to the named feature
- bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
+ //! check whether we support the named feature
+ bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
/**
* populate vCoins with vector of available COutputs.
@@ -850,7 +847,7 @@ public:
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
+ bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
/**
* Adds a destination data tuple to the store, and saves it to disk
@@ -1073,11 +1070,8 @@ public:
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
- void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override;
-
- //! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
- bool SetMaxVersion(int nVersion);
+ //! signify that a particular wallet feature is now used.
+ void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr) override;
//! get the current wallet format (the oldest client version guaranteed to understand this wallet)
int GetVersion() const { LOCK(cs_wallet); return nWalletVersion; }
@@ -1198,7 +1192,7 @@ public:
};
/** Upgrade the wallet */
- bool UpgradeWallet(int version, bilingual_str& error, std::vector<bilingual_str>& warnings);
+ bool UpgradeWallet(int version, bilingual_str& error);
//! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers
std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const;
@@ -1277,7 +1271,7 @@ public:
DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const;
//! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated output type
- ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label);
+ ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal);
};
/**
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 9ed9818479..c0521d3386 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -14,7 +14,9 @@
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
+#ifdef USE_BDB
#include <wallet/bdb.h>
+#endif
#ifdef USE_SQLITE
#include <wallet/sqlite.h>
#endif
@@ -362,7 +364,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{
ssValue >> hash;
}
- catch (...) {}
+ catch (const std::ios_base::failure&) {}
bool fSkipCheck = false;
@@ -1011,9 +1013,11 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
Optional<DatabaseFormat> format;
if (exists) {
+#ifdef USE_BDB
if (ExistsBerkeleyDatabase(path)) {
format = DatabaseFormat::BERKELEY;
}
+#endif
#ifdef USE_SQLITE
if (ExistsSQLiteDatabase(path)) {
if (format) {
@@ -1052,15 +1056,31 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
// Format is not set when a db doesn't already exist, so use the format specified by the options if it is set.
if (!format && options.require_format) format = options.require_format;
+ // If the format is not specified or detected, choose the default format based on what is available. We prefer BDB over SQLite for now.
+ if (!format) {
#ifdef USE_SQLITE
- if (format && format == DatabaseFormat::SQLITE) {
- return MakeSQLiteDatabase(path, options, status, error);
+ format = DatabaseFormat::SQLITE;
+#endif
+#ifdef USE_BDB
+ format = DatabaseFormat::BERKELEY;
+#endif
}
-#else
- assert(format != DatabaseFormat::SQLITE);
+
+ if (format == DatabaseFormat::SQLITE) {
+#ifdef USE_SQLITE
+ return MakeSQLiteDatabase(path, options, status, error);
#endif
+ error = Untranslated(strprintf("Failed to open database path '%s'. Build does not support SQLite database format.", path.string()));
+ status = DatabaseStatus::FAILED_BAD_FORMAT;
+ return nullptr;
+ }
+#ifdef USE_BDB
return MakeBerkeleyDatabase(path, options, status, error);
+#endif
+ error = Untranslated(strprintf("Failed to open database path '%s'. Build does not support Berkeley DB database format.", path.string()));
+ status = DatabaseStatus::FAILED_BAD_FORMAT;
+ return nullptr;
}
/** Return object for accessing dummy database with no read/write capabilities. */
@@ -1072,5 +1092,9 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase()
/** Return object for accessing temporary in-memory database. */
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
{
+#ifdef USE_BDB
return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
+#elif USE_SQLITE
+ return MakeUnique<SQLiteDatabase>("", "", true);
+#endif
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 7f1b86e458..e7b2d7d780 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -8,7 +8,6 @@
#include <amount.h>
#include <script/sign.h>
-#include <wallet/bdb.h>
#include <wallet/db.h>
#include <wallet/walletutil.h>
#include <key.h>
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 0e18d6a740..dad1232e10 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -122,6 +122,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
} else if (command == "salvage") {
+#ifdef USE_BDB
bilingual_str error;
std::vector<bilingual_str> warnings;
bool ret = RecoverDatabaseFile(path, error, warnings);
@@ -134,6 +135,10 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
}
}
return ret;
+#else
+ tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
+ return false;
+#endif
}
} else {
tfm::format(std::cerr, "Invalid command: %s\n", command);
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index 2f3e597b90..9be610e8bd 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -7,7 +7,11 @@
#include <logging.h>
#include <util/system.h>
+#ifdef USE_BDB
bool ExistsBerkeleyDatabase(const fs::path& path);
+#else
+# define ExistsBerkeleyDatabase(path) (false)
+#endif
#ifdef USE_SQLITE
bool ExistsSQLiteDatabase(const fs::path& path);
#else
@@ -49,28 +53,47 @@ std::vector<fs::path> ListWalletDir()
continue;
}
- // Get wallet path relative to walletdir by removing walletdir from the wallet path.
- // This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60.
- const fs::path path = it->path().string().substr(offset);
+ try {
+ // Get wallet path relative to walletdir by removing walletdir from the wallet path.
+ // This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60.
+ const fs::path path = it->path().string().substr(offset);
- if (it->status().type() == fs::directory_file &&
- (ExistsBerkeleyDatabase(it->path()) || ExistsSQLiteDatabase(it->path()))) {
- // Found a directory which contains wallet.dat btree file, add it as a wallet.
- paths.emplace_back(path);
- } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && ExistsBerkeleyDatabase(it->path())) {
- if (it->path().filename() == "wallet.dat") {
- // Found top-level wallet.dat btree file, add top level directory ""
- // as a wallet.
- paths.emplace_back();
- } else {
- // Found top-level btree file not called wallet.dat. Current bitcoin
- // software will never create these files but will allow them to be
- // opened in a shared database environment for backwards compatibility.
- // Add it to the list of available wallets.
+ if (it->status().type() == fs::directory_file &&
+ (ExistsBerkeleyDatabase(it->path()) || ExistsSQLiteDatabase(it->path()))) {
+ // Found a directory which contains wallet.dat btree file, add it as a wallet.
paths.emplace_back(path);
+ } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && ExistsBerkeleyDatabase(it->path())) {
+ if (it->path().filename() == "wallet.dat") {
+ // Found top-level wallet.dat btree file, add top level directory ""
+ // as a wallet.
+ paths.emplace_back();
+ } else {
+ // Found top-level btree file not called wallet.dat. Current bitcoin
+ // software will never create these files but will allow them to be
+ // opened in a shared database environment for backwards compatibility.
+ // Add it to the list of available wallets.
+ paths.emplace_back(path);
+ }
}
+ } catch (const std::exception& e) {
+ LogPrintf("%s: Error scanning %s: %s\n", __func__, it->path().string(), e.what());
+ it.no_push();
}
}
return paths;
}
+
+bool IsFeatureSupported(int wallet_version, int feature_version)
+{
+ return wallet_version >= feature_version;
+}
+
+WalletFeature GetClosestWalletFeature(int version)
+{
+ const std::array<WalletFeature, 8> wallet_features{{FEATURE_LATEST, FEATURE_PRE_SPLIT_KEYPOOL, FEATURE_NO_DEFAULT_KEY, FEATURE_HD_SPLIT, FEATURE_HD, FEATURE_COMPRPUBKEY, FEATURE_WALLETCRYPT, FEATURE_BASE}};
+ for (const WalletFeature& wf : wallet_features) {
+ if (version >= wf) return wf;
+ }
+ return static_cast<WalletFeature>(0);
+}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index afdcb2e18a..27521abd81 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -29,7 +29,8 @@ enum WalletFeature
FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
};
-
+bool IsFeatureSupported(int wallet_version, int feature_version);
+WalletFeature GetClosestWalletFeature(int version);
enum WalletFlags : uint64_t {
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown