aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/bdb.cpp66
-rw-r--r--src/wallet/bdb.h15
-rw-r--r--src/wallet/coinselection.cpp68
-rw-r--r--src/wallet/coinselection.h30
-rw-r--r--src/wallet/crypter.h2
-rw-r--r--src/wallet/db.cpp117
-rw-r--r--src/wallet/db.h11
-rw-r--r--src/wallet/dump.cpp282
-rw-r--r--src/wallet/dump.h17
-rw-r--r--src/wallet/feebumper.cpp8
-rw-r--r--src/wallet/fees.cpp4
-rw-r--r--src/wallet/init.cpp15
-rw-r--r--src/wallet/interfaces.cpp587
-rw-r--r--src/wallet/ismine.h22
-rw-r--r--src/wallet/load.cpp23
-rw-r--r--src/wallet/load.h2
-rw-r--r--src/wallet/rpcdump.cpp75
-rw-r--r--src/wallet/rpcwallet.cpp444
-rw-r--r--src/wallet/salvage.cpp7
-rw-r--r--src/wallet/scriptpubkeyman.cpp16
-rw-r--r--src/wallet/scriptpubkeyman.h15
-rw-r--r--src/wallet/sqlite.cpp52
-rw-r--r--src/wallet/sqlite.h5
-rw-r--r--src/wallet/test/coinselector_tests.cpp109
-rw-r--r--src/wallet/test/db_tests.cpp17
-rw-r--r--src/wallet/test/init_test_fixture.cpp8
-rw-r--r--src/wallet/test/init_test_fixture.h3
-rw-r--r--src/wallet/test/init_tests.cpp10
-rw-r--r--src/wallet/test/ismine_tests.cpp5
-rw-r--r--src/wallet/test/scriptpubkeyman_tests.cpp4
-rw-r--r--src/wallet/test/wallet_crypto_tests.cpp2
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp4
-rw-r--r--src/wallet/test/wallet_test_fixture.h3
-rw-r--r--src/wallet/test/wallet_tests.cpp65
-rw-r--r--src/wallet/test/walletdb_tests.cpp29
-rw-r--r--src/wallet/wallet.cpp357
-rw-r--r--src/wallet/wallet.h51
-rw-r--r--src/wallet/walletdb.cpp40
-rw-r--r--src/wallet/walletdb.h1
-rw-r--r--src/wallet/wallettool.cpp120
-rw-r--r--src/wallet/wallettool.h4
-rw-r--r--src/wallet/walletutil.cpp48
-rw-r--r--src/wallet/walletutil.h8
43 files changed, 1990 insertions, 781 deletions
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 85aae0170d..ad40e6da9a 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -53,16 +53,13 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
}
/**
- * @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
- * @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
+ * @param[in] env_directory Path to environment directory
* @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment
* erases the weak pointer from the g_dbenvs map.
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
*/
-std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory)
{
- fs::path env_directory;
- SplitWalletPath(wallet_path, env_directory, database_filename);
LOCK(cs_db);
auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>());
if (inserted.second) {
@@ -491,9 +488,9 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip)
break;
}
if (pszSkip &&
- strncmp(ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
+ strncmp((const char*)ssKey.data(), pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
continue;
- if (strncmp(ssKey.data(), "\x07version", 8) == 0) {
+ if (strncmp((const char*)ssKey.data(), "\x07version", 8) == 0) {
// Update version:
ssValue.clear();
ssValue << CLIENT_VERSION;
@@ -726,6 +723,23 @@ bool BerkeleyBatch::TxnAbort()
return (ret == 0);
}
+bool BerkeleyDatabaseSanityCheck()
+{
+ int major, minor;
+ DbEnv::version(&major, &minor, nullptr);
+
+ /* If the major version differs, or the minor version of library is *older*
+ * than the header that was compiled against, flag an error.
+ */
+ if (major != DB_VERSION_MAJOR || minor < DB_VERSION_MINOR) {
+ LogPrintf("BerkeleyDB database version conflict: header version is %d.%d, library version is %d.%d\n",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, major, minor);
+ return false;
+ }
+
+ return true;
+}
+
std::string BerkeleyDatabaseVersion()
{
return DbEnv::version(nullptr, nullptr, nullptr);
@@ -808,21 +822,14 @@ std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(bool flush_on_close)
return MakeUnique<BerkeleyBatch>(*this, false, flush_on_close);
}
-bool ExistsBerkeleyDatabase(const fs::path& path)
-{
- fs::path env_directory;
- std::string data_filename;
- SplitWalletPath(path, env_directory, data_filename);
- return IsBDBFile(env_directory / data_filename);
-}
-
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
+ fs::path data_file = BDBDataFile(path);
std::unique_ptr<BerkeleyDatabase> db;
{
LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor
- std::string data_filename;
- std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(path, data_filename);
+ std::string data_filename = data_file.filename().string();
+ std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path());
if (env->m_databases.count(data_filename)) {
error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", (env->Directory() / data_filename).string()));
status = DatabaseStatus::FAILED_ALREADY_LOADED;
@@ -839,28 +846,3 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con
status = DatabaseStatus::SUCCESS;
return db;
}
-
-bool IsBDBFile(const fs::path& path)
-{
- if (!fs::exists(path)) return false;
-
- // A Berkeley DB Btree file has at least 4K.
- // This check also prevents opening lock files.
- boost::system::error_code ec;
- auto size = fs::file_size(path, ec);
- if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
- if (size < 4096) return false;
-
- fsbridge::ifstream file(path, std::ios::binary);
- if (!file.is_open()) return false;
-
- file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
- uint32_t data = 0;
- file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
-
- // Berkeley DB Btree magic bytes, from:
- // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
- // - big endian systems - 00 05 31 62
- // - little endian systems - 62 31 05 00
- return data == 0x00053162 || data == 0x62310500;
-}
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index 5403e95ee4..a8209587d7 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -56,7 +56,7 @@ public:
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
std::condition_variable_any m_db_in_use;
- BerkeleyEnvironment(const fs::path& env_directory);
+ explicit BerkeleyEnvironment(const fs::path& env_directory);
BerkeleyEnvironment();
~BerkeleyEnvironment();
void Reset();
@@ -83,11 +83,8 @@ public:
}
};
-/** Get BerkeleyEnvironment and database filename given a wallet path. */
-std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
-
-/** Check format of database file */
-bool IsBDBFile(const fs::path& path);
+/** Get BerkeleyEnvironment given a directory path. */
+std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory);
class BerkeleyBatch;
@@ -146,6 +143,7 @@ public:
/** Return path to main database filename */
std::string Filename() override { return (env->Directory() / strFile).string(); }
+ std::string Format() override { return "bdb"; }
/**
* Pointer to shared database environment.
*
@@ -225,8 +223,9 @@ public:
std::string BerkeleyDatabaseVersion();
-//! Check if Berkeley database exists at specified path.
-bool ExistsBerkeleyDatabase(const fs::path& path);
+/** Perform sanity check of runtime BDB version versus linked BDB version.
+ */
+bool BerkeleyDatabaseSanityCheck();
//! Return object giving access to Berkeley database at specified path.
std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 1a45a2b313..10f89e3a6f 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -300,8 +300,26 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
******************************************************************************/
-void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
+void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
+ // Compute the effective value first
+ const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
+ const CAmount ev = output.txout.nValue - coin_fee;
+
+ // Filter for positive only here before adding the coin
+ if (positive_only && ev <= 0) return;
+
m_outputs.push_back(output);
+ CInputCoin& coin = m_outputs.back();
+
+ coin.m_fee = coin_fee;
+ fee += coin.m_fee;
+
+ coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
+ long_term_fee += coin.m_long_term_fee;
+
+ coin.effective_value = ev;
+ effective_value += coin.effective_value;
+
m_from_me &= from_me;
m_value += output.txout.nValue;
m_depth = std::min(m_depth, depth);
@@ -312,20 +330,6 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
// coin itself; thus, this value is counted as the max, not the sum
m_descendants = std::max(m_descendants, descendants);
- effective_value += output.effective_value;
- fee += output.m_fee;
- long_term_fee += output.m_long_term_fee;
-}
-
-std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
- auto it = m_outputs.begin();
- while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
- if (it == m_outputs.end()) return it;
- m_value -= output.txout.nValue;
- effective_value -= output.effective_value;
- fee -= output.m_fee;
- long_term_fee -= output.m_long_term_fee;
- return m_outputs.erase(it);
}
bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const
@@ -334,35 +338,3 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
&& m_ancestors <= eligibility_filter.max_ancestors
&& m_descendants <= eligibility_filter.max_descendants;
}
-
-void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
-{
- fee = 0;
- long_term_fee = 0;
- effective_value = 0;
- for (CInputCoin& coin : m_outputs) {
- coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
- fee += coin.m_fee;
-
- coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
- long_term_fee += coin.m_long_term_fee;
-
- coin.effective_value = coin.txout.nValue - coin.m_fee;
- effective_value += coin.effective_value;
- }
-}
-
-OutputGroup OutputGroup::GetPositiveOnlyGroup()
-{
- OutputGroup group(*this);
- for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
- const CInputCoin& coin = *it;
- // Only include outputs that are positive effective value (i.e. not dust)
- if (coin.effective_value <= 0) {
- it = group.Discard(coin);
- } else {
- ++it;
- }
- }
- return group;
-}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 49c1134ec6..f0e1addaf1 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -6,11 +6,10 @@
#define BITCOIN_WALLET_COINSELECTION_H
#include <amount.h>
+#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <random.h>
-class CFeeRate;
-
//! target minimum change amount
static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees
@@ -63,9 +62,11 @@ struct CoinEligibilityFilter
const int conf_theirs;
const uint64_t max_ancestors;
const uint64_t max_descendants;
+ const bool m_include_partial_groups{false}; //! Include partial destination groups when avoid_reuse and there are full groups
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
+ CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {}
};
struct OutputGroup
@@ -78,27 +79,18 @@ struct OutputGroup
size_t m_descendants{0};
CAmount effective_value{0};
CAmount fee{0};
+ CFeeRate m_effective_feerate{0};
CAmount long_term_fee{0};
+ CFeeRate m_long_term_feerate{0};
OutputGroup() {}
- OutputGroup(std::vector<CInputCoin>&& outputs, bool from_me, CAmount value, int depth, size_t ancestors, size_t descendants)
- : m_outputs(std::move(outputs))
- , m_from_me(from_me)
- , m_value(value)
- , m_depth(depth)
- , m_ancestors(ancestors)
- , m_descendants(descendants)
+ OutputGroup(const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate) :
+ m_effective_feerate(effective_feerate),
+ m_long_term_feerate(long_term_feerate)
{}
- OutputGroup(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) : OutputGroup() {
- Insert(output, depth, from_me, ancestors, descendants);
- }
- void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
- std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
- bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
- //! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
- void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
- OutputGroup GetPositiveOnlyGroup();
+ void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
+ bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
};
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index f2df786e2e..f7325541a9 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-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.
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index bd1d114730..cd49baeb78 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -3,23 +3,130 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <chainparams.h>
#include <fs.h>
+#include <logging.h>
#include <wallet/db.h>
#include <string>
-void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename)
+std::vector<fs::path> ListDatabases(const fs::path& wallet_dir)
+{
+ const size_t offset = wallet_dir.string().size() + 1;
+ std::vector<fs::path> paths;
+ boost::system::error_code ec;
+
+ for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
+ if (ec) {
+ LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string());
+ continue;
+ }
+
+ 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 &&
+ (IsBDBFile(BDBDataFile(it->path())) || IsSQLiteFile(SQLiteDataFile(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 && IsBDBFile(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;
+}
+
+fs::path BDBDataFile(const fs::path& wallet_path)
{
if (fs::is_regular_file(wallet_path)) {
// Special case for backwards compatibility: if wallet path points to an
// existing file, treat it as the path to a BDB data file in a parent
// directory that also contains BDB log files.
- env_directory = wallet_path.parent_path();
- database_filename = wallet_path.filename().string();
+ return wallet_path;
} else {
// Normal case: Interpret wallet path as a directory path containing
// data and log files.
- env_directory = wallet_path;
- database_filename = "wallet.dat";
+ return wallet_path / "wallet.dat";
}
}
+
+fs::path SQLiteDataFile(const fs::path& path)
+{
+ return path / "wallet.dat";
+}
+
+bool IsBDBFile(const fs::path& path)
+{
+ if (!fs::exists(path)) return false;
+
+ // A Berkeley DB Btree file has at least 4K.
+ // This check also prevents opening lock files.
+ boost::system::error_code ec;
+ auto size = fs::file_size(path, ec);
+ if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
+ if (size < 4096) return false;
+
+ fsbridge::ifstream file(path, std::ios::binary);
+ if (!file.is_open()) return false;
+
+ file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
+ uint32_t data = 0;
+ file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
+
+ // Berkeley DB Btree magic bytes, from:
+ // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
+ // - big endian systems - 00 05 31 62
+ // - little endian systems - 62 31 05 00
+ return data == 0x00053162 || data == 0x62310500;
+}
+
+bool IsSQLiteFile(const fs::path& path)
+{
+ if (!fs::exists(path)) return false;
+
+ // A SQLite Database file is at least 512 bytes.
+ boost::system::error_code ec;
+ auto size = fs::file_size(path, ec);
+ if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
+ if (size < 512) return false;
+
+ fsbridge::ifstream file(path, std::ios::binary);
+ if (!file.is_open()) return false;
+
+ // Magic is at beginning and is 16 bytes long
+ char magic[16];
+ file.read(magic, 16);
+
+ // Application id is at offset 68 and 4 bytes long
+ file.seekg(68, std::ios::beg);
+ char app_id[4];
+ file.read(app_id, 4);
+
+ file.close();
+
+ // Check the magic, see https://sqlite.org/fileformat2.html
+ std::string magic_str(magic, 16);
+ if (magic_str != std::string("SQLite format 3", 16)) {
+ return false;
+ }
+
+ // Check the application id matches our network magic
+ return memcmp(Params().MessageStart(), app_id, 4) == 0;
+}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 3ecccd4e00..2c75486a44 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -144,6 +144,8 @@ public:
/** Return path to main database file for logs and error messages. */
virtual std::string Filename() = 0;
+ virtual std::string Format() = 0;
+
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
@@ -190,6 +192,7 @@ public:
void IncrementUpdateCounter() override { ++nUpdateCounter; }
void ReloadDbEnv() override {}
std::string Filename() override { return "dummy"; }
+ std::string Format() override { return "dummy"; }
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return MakeUnique<DummyBatch>(); }
};
@@ -220,6 +223,14 @@ enum class DatabaseStatus {
FAILED_ENCRYPT,
};
+/** Recursively list database paths in directory. */
+std::vector<fs::path> ListDatabases(const fs::path& path);
+
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
+fs::path BDBDataFile(const fs::path& path);
+fs::path SQLiteDataFile(const fs::path& path);
+bool IsBDBFile(const fs::path& path);
+bool IsSQLiteFile(const fs::path& path);
+
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
new file mode 100644
index 0000000000..e314107988
--- /dev/null
+++ b/src/wallet/dump.cpp
@@ -0,0 +1,282 @@
+// Copyright (c) 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 <wallet/dump.h>
+
+#include <util/translation.h>
+#include <wallet/wallet.h>
+
+static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
+uint32_t DUMP_VERSION = 1;
+
+bool DumpWallet(CWallet& wallet, bilingual_str& error)
+{
+ // Get the dumpfile
+ std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ if (dump_filename.empty()) {
+ error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
+ return false;
+ }
+
+ fs::path path = dump_filename;
+ path = fs::absolute(path);
+ if (fs::exists(path)) {
+ error = strprintf(_("File %s already exists. If you are sure this is what you want, move it out of the way first."), path.string());
+ return false;
+ }
+ fsbridge::ofstream dump_file;
+ dump_file.open(path);
+ if (dump_file.fail()) {
+ error = strprintf(_("Unable to open %s for writing"), path.string());
+ return false;
+ }
+
+ CHashWriter hasher(0, 0);
+
+ WalletDatabase& db = wallet.GetDatabase();
+ std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
+
+ bool ret = true;
+ if (!batch->StartCursor()) {
+ error = _("Error: Couldn't create cursor into database");
+ ret = false;
+ }
+
+ // Write out a magic string with version
+ std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
+ dump_file.write(line.data(), line.size());
+ hasher.write(line.data(), line.size());
+
+ // Write out the file format
+ line = strprintf("%s,%s\n", "format", db.Format());
+ dump_file.write(line.data(), line.size());
+ hasher.write(line.data(), line.size());
+
+ if (ret) {
+
+ // Read the records
+ while (true) {
+ CDataStream ss_key(SER_DISK, CLIENT_VERSION);
+ CDataStream ss_value(SER_DISK, CLIENT_VERSION);
+ bool complete;
+ ret = batch->ReadAtCursor(ss_key, ss_value, complete);
+ if (complete) {
+ ret = true;
+ break;
+ } else if (!ret) {
+ error = _("Error reading next record from wallet database");
+ break;
+ }
+ std::string key_str = HexStr(ss_key);
+ std::string value_str = HexStr(ss_value);
+ line = strprintf("%s,%s\n", key_str, value_str);
+ dump_file.write(line.data(), line.size());
+ hasher.write(line.data(), line.size());
+ }
+ }
+
+ batch->CloseCursor();
+ batch.reset();
+
+ // Close the wallet after we're done with it. The caller won't be doing this
+ wallet.Close();
+
+ if (ret) {
+ // Write the hash
+ tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
+ dump_file.close();
+ } else {
+ // Remove the dumpfile on failure
+ dump_file.close();
+ fs::remove(path);
+ }
+
+ return ret;
+}
+
+// The standard wallet deleter function blocks on the validation interface
+// queue, which doesn't exist for the bitcoin-wallet. Define our own
+// deleter here.
+static void WalletToolReleaseWallet(CWallet* wallet)
+{
+ wallet->WalletLogPrintf("Releasing wallet\n");
+ wallet->Close();
+ delete wallet;
+}
+
+bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
+{
+ // Get the dumpfile
+ std::string dump_filename = gArgs.GetArg("-dumpfile", "");
+ if (dump_filename.empty()) {
+ error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
+ return false;
+ }
+
+ fs::path dump_path = dump_filename;
+ dump_path = fs::absolute(dump_path);
+ if (!fs::exists(dump_path)) {
+ error = strprintf(_("Dump file %s does not exist."), dump_path.string());
+ return false;
+ }
+ fsbridge::ifstream dump_file(dump_path);
+
+ // Compute the checksum
+ CHashWriter hasher(0, 0);
+ uint256 checksum;
+
+ // Check the magic and version
+ std::string magic_key;
+ std::getline(dump_file, magic_key, ',');
+ std::string version_value;
+ std::getline(dump_file, version_value, '\n');
+ if (magic_key != DUMP_MAGIC) {
+ error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
+ dump_file.close();
+ return false;
+ }
+ // Check the version number (value of first record)
+ uint32_t ver;
+ if (!ParseUInt32(version_value, &ver)) {
+ error =strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
+ dump_file.close();
+ return false;
+ }
+ if (ver != DUMP_VERSION) {
+ error = strprintf(_("Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s"), version_value);
+ dump_file.close();
+ return false;
+ }
+ std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
+ hasher.write(magic_hasher_line.data(), magic_hasher_line.size());
+
+ // Get the stored file format
+ std::string format_key;
+ std::getline(dump_file, format_key, ',');
+ std::string format_value;
+ std::getline(dump_file, format_value, '\n');
+ if (format_key != "format") {
+ error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
+ dump_file.close();
+ return false;
+ }
+ // Get the data file format with format_value as the default
+ std::string file_format = gArgs.GetArg("-format", format_value);
+ if (file_format.empty()) {
+ error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided.");
+ return false;
+ }
+ DatabaseFormat data_format;
+ if (file_format == "bdb") {
+ data_format = DatabaseFormat::BERKELEY;
+ } else if (file_format == "sqlite") {
+ data_format = DatabaseFormat::SQLITE;
+ } else {
+ error = strprintf(_("Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or \"sqlite\"."), file_format);
+ return false;
+ }
+ if (file_format != format_value) {
+ warnings.push_back(strprintf(_("Warning: Dumpfile wallet format \"%s\" does not match command line specified format \"%s\"."), format_value, file_format));
+ }
+ std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
+ hasher.write(format_hasher_line.data(), format_hasher_line.size());
+
+ DatabaseOptions options;
+ DatabaseStatus status;
+ options.require_create = true;
+ options.require_format = data_format;
+ std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
+ if (!database) return false;
+
+ // dummy chain interface
+ bool ret = true;
+ std::shared_ptr<CWallet> wallet(new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet);
+ {
+ LOCK(wallet->cs_wallet);
+ bool first_run = true;
+ DBErrors load_wallet_ret = wallet->LoadWallet(first_run);
+ if (load_wallet_ret != DBErrors::LOAD_OK) {
+ error = strprintf(_("Error creating %s"), name);
+ return false;
+ }
+
+ // Get the database handle
+ WalletDatabase& db = wallet->GetDatabase();
+ std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
+ batch->TxnBegin();
+
+ // Read the records from the dump file and write them to the database
+ while (dump_file.good()) {
+ std::string key;
+ std::getline(dump_file, key, ',');
+ std::string value;
+ std::getline(dump_file, value, '\n');
+
+ if (key == "checksum") {
+ std::vector<unsigned char> parsed_checksum = ParseHex(value);
+ std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
+ break;
+ }
+
+ std::string line = strprintf("%s,%s\n", key, value);
+ hasher.write(line.data(), line.size());
+
+ if (key.empty() || value.empty()) {
+ continue;
+ }
+
+ if (!IsHex(key)) {
+ error = strprintf(_("Error: Got key that was not hex: %s"), key);
+ ret = false;
+ break;
+ }
+ if (!IsHex(value)) {
+ error = strprintf(_("Error: Got value that was not hex: %s"), value);
+ ret = false;
+ break;
+ }
+
+ std::vector<unsigned char> k = ParseHex(key);
+ std::vector<unsigned char> v = ParseHex(value);
+
+ CDataStream ss_key(k, SER_DISK, CLIENT_VERSION);
+ CDataStream ss_value(v, SER_DISK, CLIENT_VERSION);
+
+ if (!batch->Write(ss_key, ss_value)) {
+ error = strprintf(_("Error: Unable to write record to new wallet"));
+ ret = false;
+ break;
+ }
+ }
+
+ if (ret) {
+ uint256 comp_checksum = hasher.GetHash();
+ if (checksum.IsNull()) {
+ error = _("Error: Missing checksum");
+ ret = false;
+ } else if (checksum != comp_checksum) {
+ error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
+ ret = false;
+ }
+ }
+
+ if (ret) {
+ batch->TxnCommit();
+ } else {
+ batch->TxnAbort();
+ }
+
+ batch.reset();
+
+ dump_file.close();
+ }
+ wallet.reset(); // The pointer deleter will close the wallet for us.
+
+ // Remove the wallet dir if we have a failure
+ if (!ret) {
+ fs::remove_all(wallet_path);
+ }
+
+ return ret;
+}
diff --git a/src/wallet/dump.h b/src/wallet/dump.h
new file mode 100644
index 0000000000..d0a4f5ef1d
--- /dev/null
+++ b/src/wallet/dump.h
@@ -0,0 +1,17 @@
+// Copyright (c) 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.
+
+#ifndef BITCOIN_WALLET_DUMP_H
+#define BITCOIN_WALLET_DUMP_H
+
+#include <fs.h>
+
+class CWallet;
+
+struct bilingual_str;
+
+bool DumpWallet(CWallet& wallet, bilingual_str& error);
+bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
+
+#endif // BITCOIN_WALLET_DUMP_H
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 6cbad14de8..5e319d4f95 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -111,7 +111,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
return feebumper::Result::OK;
}
-static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, CCoinControl& coin_control)
+static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, const CCoinControl& coin_control)
{
// Get the fee rate of the original transaction. This is calculated from
// the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the
@@ -215,7 +215,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// We cannot source new unconfirmed inputs(bip125 rule 2)
new_coin_control.m_min_depth = 1;
- CTransactionRef tx_new = MakeTransactionRef();
+ CTransactionRef tx_new;
CAmount fee_ret;
int change_pos_in_out = -1; // No requested location for change
bilingual_str fail_reason;
@@ -231,7 +231,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// Write back transaction
mtx = CMutableTransaction(*tx_new);
// Mark new tx not replaceable, if requested.
- if (!coin_control.m_signal_bip125_rbf.get_value_or(wallet.m_signal_rbf)) {
+ if (!coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf)) {
for (auto& input : mtx.vin) {
if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
}
@@ -256,7 +256,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
errors.push_back(Untranslated("Invalid or non-wallet transaction id"));
return Result::MISC_ERROR;
}
- CWalletTx& oldWtx = it->second;
+ const CWalletTx& oldWtx = it->second;
// make sure the transaction still has no descendants and hasn't been mined in the meantime
Result result = PreconditionChecks(wallet, oldWtx, errors);
diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp
index 249bc833c6..429101e774 100644
--- a/src/wallet/fees.cpp
+++ b/src/wallet/fees.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-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.
@@ -49,7 +49,7 @@ CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_contr
// We will use smart fee estimation
unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : wallet.m_confirm_target;
// By default estimates are economical iff we are signaling opt-in-RBF
- bool conservative_estimate = !coin_control.m_signal_bip125_rbf.get_value_or(wallet.m_signal_rbf);
+ bool conservative_estimate = !coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf);
// 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;
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 5d8c4fba29..0d2be64dfb 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>
@@ -60,7 +63,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
+ argsman.AddArg("-wallet=<path>", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to <walletdir>. This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in <walletdir>.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
#if HAVE_SYSTEM
@@ -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"});
@@ -78,6 +86,11 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
bool WalletInit::ParameterInteraction() const
{
+#ifdef USE_BDB
+ if (!BerkeleyDatabaseSanityCheck()) {
+ return InitError(Untranslated("A version conflict was detected between the run-time BerkeleyDB library and the one used during compilation."));
+ }
+#endif
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
for (const std::string& wallet : gArgs.GetArgs("-wallet")) {
LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet);
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
new file mode 100644
index 0000000000..e4e8c50f4f
--- /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(const 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(const 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 : ListDatabases(GetWalletDir())) {
+ 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/ismine.h b/src/wallet/ismine.h
index 5cdd7dff80..38ed7e7770 100644
--- a/src/wallet/ismine.h
+++ b/src/wallet/ismine.h
@@ -14,7 +14,27 @@
class CWallet;
class CScript;
-/** IsMine() return codes */
+/**
+ * IsMine() return codes, which depend on ScriptPubKeyMan implementation.
+ * Not every ScriptPubKeyMan covers all types, please refer to
+ * https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.21.0.md#ismine-semantics
+ * for better understanding.
+ *
+ * For LegacyScriptPubKeyMan,
+ * ISMINE_NO: the scriptPubKey is not in the wallet;
+ * ISMINE_WATCH_ONLY: the scriptPubKey has been imported into the wallet;
+ * ISMINE_SPENDABLE: the scriptPubKey corresponds to an address owned by the wallet user (can spend with the private key);
+ * ISMINE_USED: the scriptPubKey corresponds to a used address owned by the wallet user;
+ * ISMINE_ALL: all ISMINE flags except for USED;
+ * ISMINE_ALL_USED: all ISMINE flags including USED;
+ * ISMINE_ENUM_ELEMENTS: the number of isminetype enum elements.
+ *
+ * For DescriptorScriptPubKeyMan and future ScriptPubKeyMan,
+ * ISMINE_NO: the scriptPubKey is not in the wallet;
+ * ISMINE_SPENDABLE: the scriptPubKey matches a scriptPubKey in the wallet.
+ * ISMINE_USED: the scriptPubKey corresponds to a used address owned by the wallet user.
+ *
+ */
enum isminetype : unsigned int
{
ISMINE_NO = 0,
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 1b057000d2..30832f983b 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -62,20 +62,25 @@ bool VerifyWallets(interfaces::Chain& chain)
std::set<fs::path> wallet_paths;
for (const auto& wallet_file : gArgs.GetArgs("-wallet")) {
- const fs::path path = fs::absolute(wallet_file, GetWalletDir());
+ const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), wallet_file);
if (!wallet_paths.insert(path).second) {
- chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
- return false;
+ chain.initWarning(strprintf(_("Ignoring duplicate -wallet %s."), wallet_file));
+ continue;
}
DatabaseOptions options;
DatabaseStatus status;
+ options.require_existing = true;
options.verify = true;
bilingual_str error_string;
if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
- chain.initError(error_string);
- return false;
+ if (status == DatabaseStatus::FAILED_NOT_FOUND) {
+ chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s\n", error_string.original)));
+ } else {
+ chain.initError(error_string);
+ return false;
+ }
}
}
@@ -85,13 +90,21 @@ bool VerifyWallets(interfaces::Chain& chain)
bool LoadWallets(interfaces::Chain& chain)
{
try {
+ std::set<fs::path> wallet_paths;
for (const std::string& name : gArgs.GetArgs("-wallet")) {
+ if (!wallet_paths.insert(name).second) {
+ continue;
+ }
DatabaseOptions options;
DatabaseStatus status;
+ options.require_existing = true;
options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
bilingual_str error;
std::vector<bilingual_str> warnings;
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
+ if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) {
+ continue;
+ }
std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings) : nullptr;
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
if (!pwallet) {
diff --git a/src/wallet/load.h b/src/wallet/load.h
index e12343de27..7910f0d6e1 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-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.
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 064084a413..99803a91d2 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));
}
@@ -1738,3 +1740,72 @@ RPCHelpMan importdescriptors()
},
};
}
+
+RPCHelpMan listdescriptors()
+{
+ return RPCHelpMan{
+ "listdescriptors",
+ "\nList descriptors imported into a descriptor-enabled wallet.",
+ {},
+ RPCResult{
+ RPCResult::Type::ARR, "", "Response is an array of descriptor objects",
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "desc", "Descriptor string representation"},
+ {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
+ {RPCResult::Type::BOOL, "active", "Activeness flag"},
+ {RPCResult::Type::BOOL, "internal", true, "Whether this is internal or external descriptor; defined only for active descriptors"},
+ {RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", {
+ {RPCResult::Type::NUM, "", "Range start inclusive"},
+ {RPCResult::Type::NUM, "", "Range end inclusive"},
+ }},
+ {RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+
+ if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
+ }
+
+ LOCK(wallet->cs_wallet);
+
+ UniValue response(UniValue::VARR);
+ const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
+ for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
+ const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
+ if (!desc_spk_man) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
+ }
+ UniValue spk(UniValue::VOBJ);
+ LOCK(desc_spk_man->cs_desc_man);
+ const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
+ spk.pushKV("desc", wallet_descriptor.descriptor->ToString());
+ spk.pushKV("timestamp", wallet_descriptor.creation_time);
+ const bool active = active_spk_mans.count(desc_spk_man) != 0;
+ spk.pushKV("active", active);
+ const auto& type = wallet_descriptor.descriptor->GetOutputType();
+ if (active && type != nullopt) {
+ spk.pushKV("internal", wallet->GetScriptPubKeyMan(*type, true) == desc_spk_man);
+ }
+ if (wallet_descriptor.descriptor->IsRange()) {
+ UniValue range(UniValue::VARR);
+ range.push_back(wallet_descriptor.range_start);
+ range.push_back(wallet_descriptor.range_end - 1);
+ spk.pushKV("range", range);
+ spk.pushKV("next", wallet_descriptor.next_index);
+ }
+ response.push_back(spk);
+ }
+
+ return response;
+},
+ };
+}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 2295fb0ef1..46de273d63 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 (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
- if (estimate_param.isNull()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
+ 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.");
}
-
- CAmount fee_rate = AmountFromValue(estimate_param);
- if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
- fee_rate /= WALLET_BTC_KB_TO_SAT_B;
+ if (!estimate_mode.isNull() && estimate_mode.get_str() != "unset") {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
}
-
- 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());
}
}
@@ -310,7 +309,7 @@ static RPCHelpMan getrawchangeaddress()
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
}
- OutputType output_type = pwallet->m_default_change_type.get_value_or(pwallet->m_default_address_type);
+ OutputType output_type = pwallet->m_default_change_type.value_or(pwallet->m_default_address_type);
if (!request.params[0].isNull()) {
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
@@ -440,11 +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 default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + 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."},
},
{
@@ -460,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
{
@@ -500,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);
@@ -514,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);
},
@@ -622,7 +627,7 @@ static RPCHelpMan signmessage()
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
- const PKHash *pkhash = boost::get<PKHash>(&dest);
+ const PKHash* pkhash = std::get_if<PKHash>(&dest);
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
@@ -868,9 +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 default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + 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."},
},
{
@@ -927,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);
},
@@ -1567,8 +1573,7 @@ static RPCHelpMan listsinceblock()
LOCK(wallet.cs_wallet);
- // The way the 'height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0.
- Optional<int> height = MakeOptional(false, int()); // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
+ Optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
Optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain.
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
@@ -2308,7 +2313,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"
@@ -2422,6 +2427,7 @@ static RPCHelpMan getwalletinfo()
{
{RPCResult::Type::STR, "walletname", "the wallet name"},
{RPCResult::Type::NUM, "walletversion", "the wallet version"},
+ {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
{RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
{RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
{RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
@@ -2430,7 +2436,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"},
@@ -2465,6 +2471,7 @@ static RPCHelpMan getwalletinfo()
int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
+ obj.pushKV("format", pwallet->GetDatabase().Format());
obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
@@ -2529,7 +2536,7 @@ static RPCHelpMan listwalletdir()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
UniValue wallets(UniValue::VARR);
- for (const auto& path : ListWalletDir()) {
+ for (const auto& path : ListDatabases(GetWalletDir())) {
UniValue wallet(UniValue::VOBJ);
wallet.pushKV("name", path.string());
wallets.push_back(wallet);
@@ -2608,7 +2615,18 @@ static RPCHelpMan loadwallet()
if (!wallet) {
// Map bad format to not found, since bad format is returned when the
// wallet directory exists, but doesn't contain a data file.
- RPCErrorCode code = status == DatabaseStatus::FAILED_NOT_FOUND || status == DatabaseStatus::FAILED_BAD_FORMAT ? RPC_WALLET_NOT_FOUND : RPC_WALLET_ERROR;
+ RPCErrorCode code = RPC_WALLET_ERROR;
+ switch (status) {
+ case DatabaseStatus::FAILED_NOT_FOUND:
+ case DatabaseStatus::FAILED_BAD_FORMAT:
+ code = RPC_WALLET_NOT_FOUND;
+ break;
+ case DatabaseStatus::FAILED_ALREADY_LOADED:
+ code = RPC_WALLET_ALREADY_LOADED;
+ break;
+ default: // RPC_WALLET_ERROR is returned for all other cases.
+ break;
+ }
throw JSONRPCError(code, error.original);
}
@@ -2740,10 +2758,19 @@ static RPCHelpMan createwallet()
flags |= WALLET_FLAG_AVOID_REUSE;
}
if (!request.params[5].isNull() && request.params[5].get_bool()) {
+#ifndef USE_SQLITE
+ throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
+#endif
flags |= WALLET_FLAG_DESCRIPTORS;
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;
@@ -2772,7 +2799,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, "", "", {
@@ -2786,8 +2813,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();
@@ -2986,7 +3013,7 @@ static RPCHelpMan listunspent()
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
if (scriptPubKey.IsPayToScriptHash()) {
- const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
+ const CScriptID& hash = CScriptID(std::get<ScriptHash>(address));
CScript redeemScript;
if (provider->GetCScript(hash, redeemScript)) {
entry.pushKV("redeemScript", HexStr(redeemScript));
@@ -2996,7 +3023,7 @@ static RPCHelpMan listunspent()
bool extracted = ExtractDestination(redeemScript, witness_destination);
CHECK_NONFATAL(extracted);
// Also return the witness script
- const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination);
+ const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(witness_destination);
CScriptID id;
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
@@ -3006,7 +3033,7 @@ static RPCHelpMan listunspent()
}
}
} else if (scriptPubKey.IsPayToWitnessScriptHash()) {
- const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address);
+ const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(address);
CScriptID id;
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
CScript witnessScript;
@@ -3039,7 +3066,7 @@ static RPCHelpMan listunspent()
};
}
-void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, 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
@@ -3072,7 +3099,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)},
@@ -3119,10 +3147,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");
@@ -3137,7 +3167,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
@@ -3194,7 +3224,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"
@@ -3205,7 +3236,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 default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + 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\"") + "\""},
},
@@ -3257,7 +3288,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)));
@@ -3365,34 +3396,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 estimatesmartfee.\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 default", "Confirmation target (in blocks)"},
- {"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
- "Specify a fee rate instead of relying on the built-in fee estimator.\n"
- "Must be at least 0.0001 " + CURRENCY_UNIT + " per 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\"") + "\""},
},
@@ -3425,10 +3460,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
CWallet* const pwallet = wallet.get();
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) {
- if (!pwallet->chain().rpcEnableDeprecated("bumpfee")) {
- throw JSONRPCError(RPC_METHOD_DEPRECATED, "Using bumpfee with wallets that have private keys disabled is deprecated. Use psbtbumpfee instead or restart bitcoind with -deprecatedrpc=bumpfee. This functionality will be removed in 0.22");
- }
- want_psbt = true;
+ throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.");
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
@@ -3445,7 +3477,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)},
},
@@ -3457,23 +3489,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, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
- }
- coin_control.m_confirm_target = ParseConfirmTarget(conf_target, pwallet->chain().estimateMaxBlocks());
- } 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
@@ -3634,7 +3653,7 @@ static RPCHelpMan rescanblockchain()
};
}
-class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue>
+class DescribeWalletAddressVisitor
{
public:
const SigningProvider * const provider;
@@ -3653,7 +3672,7 @@ public:
UniValue subobj(UniValue::VOBJ);
UniValue detail = DescribeAddress(embedded);
subobj.pushKVs(detail);
- UniValue wallet_detail = boost::apply_visitor(*this, embedded);
+ UniValue wallet_detail = std::visit(*this, embedded);
subobj.pushKVs(wallet_detail);
subobj.pushKV("address", EncodeDestination(embedded));
subobj.pushKV("scriptPubKey", HexStr(subscript));
@@ -3736,7 +3755,7 @@ static UniValue DescribeWalletAddress(const CWallet* const pwallet, const CTxDes
provider = pwallet->GetSolvingProvider(script);
}
ret.pushKVs(detail);
- ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider.get()), dest));
+ ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
return ret;
}
@@ -3812,13 +3831,19 @@ RPCHelpMan getaddressinfo()
LOCK(pwallet->cs_wallet);
- UniValue ret(UniValue::VOBJ);
- CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ std::string error_msg;
+ CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
+
// Make sure the destination is valid
if (!IsValidDestination(dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ // Set generic error message in case 'DecodeDestination' didn't set it
+ if (error_msg.empty()) error_msg = "Invalid address";
+
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error_msg);
}
+ UniValue ret(UniValue::VOBJ);
+
std::string currentAddress = EncodeDestination(dest);
ret.pushKV("address", currentAddress);
@@ -4009,9 +4034,10 @@ static RPCHelpMan send()
},
},
},
- {"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + 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."},
@@ -4019,9 +4045,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 default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + 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."},
@@ -4035,7 +4062,7 @@ static RPCHelpMan send()
{"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
{"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
{"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."},
- {"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A JSON array of integers.\n"
+ {"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "Outputs to subtract the fee from, specified as integer indices.\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"
"If no outputs are specified here, the sender pays the fee.",
@@ -4058,18 +4085,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
+ UniValueType(), // fee_rate, will be checked by AmountFromValue() in SetFeeEstimateMode()
+ UniValue::VOBJ, // options
}, true
);
@@ -4077,18 +4111,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");
}
@@ -4116,9 +4160,9 @@ static RPCHelpMan send()
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
CCoinControl coin_control;
// Automatically select coins, unless at least one is manually selected. Can
- // be overriden by options.add_inputs.
+ // 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")) {
@@ -4203,7 +4247,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);
@@ -4346,7 +4390,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"
@@ -4357,7 +4402,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 */ "fall back to wallet's confirmation target (txconfirmtarget)", "Confirmation target (in blocks)"},
+ {"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\"") + "\""},
},
@@ -4404,7 +4449,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);
@@ -4433,12 +4478,21 @@ 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)"}
+ },
},
- RPCResults{},
RPCExamples{
HelpExampleCli("upgradewallet", "169900")
+ HelpExampleRpc("upgradewallet", "169900")
@@ -4457,13 +4511,31 @@ 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);
+ 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 error.original;
+ return obj;
},
};
}
@@ -4479,73 +4551,75 @@ RPCHelpMan importprunedfunds();
RPCHelpMan removeprunedfunds();
RPCHelpMan importmulti();
RPCHelpMan importdescriptors();
+RPCHelpMan listdescriptors();
Span<const CRPCCommand> GetWalletRPCCommands()
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
- { "wallet", "abandontransaction", &abandontransaction, {"txid"} },
- { "wallet", "abortrescan", &abortrescan, {} },
- { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
- { "wallet", "backupwallet", &backupwallet, {"destination"} },
- { "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
- { "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
- { "wallet", "dumpwallet", &dumpwallet, {"filename"} },
- { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
- { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
- { "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
- { "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} },
- { "wallet", "getnewaddress", &getnewaddress, {"label","address_type"} },
- { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
- { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
- { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
- { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","verbose"} },
- { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
- { "wallet", "getbalances", &getbalances, {} },
- { "wallet", "getwalletinfo", &getwalletinfo, {} },
- { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
- { "wallet", "importdescriptors", &importdescriptors, {"requests"} },
- { "wallet", "importmulti", &importmulti, {"requests","options"} },
- { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} },
- { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
- { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} },
- { "wallet", "importwallet", &importwallet, {"filename"} },
- { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
- { "wallet", "listaddressgroupings", &listaddressgroupings, {} },
- { "wallet", "listlabels", &listlabels, {"purpose"} },
- { "wallet", "listlockunspent", &listlockunspent, {} },
- { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
- { "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
- { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
- { "wallet", "listtransactions", &listtransactions, {"label|dummy","count","skip","include_watchonly"} },
- { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
- { "wallet", "listwalletdir", &listwalletdir, {} },
- { "wallet", "listwallets", &listwallets, {} },
- { "wallet", "loadwallet", &loadwallet, {"filename", "load_on_startup"} },
- { "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", "sethdseed", &sethdseed, {"newkeypool","seed"} },
- { "wallet", "setlabel", &setlabel, {"address","label"} },
- { "wallet", "settxfee", &settxfee, {"amount"} },
- { "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
- { "wallet", "signmessage", &signmessage, {"address","message"} },
- { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
- { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} },
- { "wallet", "upgradewallet", &upgradewallet, {"version"} },
- { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
- { "wallet", "walletlock", &walletlock, {} },
- { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
- { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
- { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
+{ // category actor (function)
+ // ------------------ ------------------------
+ { "rawtransactions", &fundrawtransaction, },
+ { "wallet", &abandontransaction, },
+ { "wallet", &abortrescan, },
+ { "wallet", &addmultisigaddress, },
+ { "wallet", &backupwallet, },
+ { "wallet", &bumpfee, },
+ { "wallet", &psbtbumpfee, },
+ { "wallet", &createwallet, },
+ { "wallet", &dumpprivkey, },
+ { "wallet", &dumpwallet, },
+ { "wallet", &encryptwallet, },
+ { "wallet", &getaddressesbylabel, },
+ { "wallet", &getaddressinfo, },
+ { "wallet", &getbalance, },
+ { "wallet", &getnewaddress, },
+ { "wallet", &getrawchangeaddress, },
+ { "wallet", &getreceivedbyaddress, },
+ { "wallet", &getreceivedbylabel, },
+ { "wallet", &gettransaction, },
+ { "wallet", &getunconfirmedbalance, },
+ { "wallet", &getbalances, },
+ { "wallet", &getwalletinfo, },
+ { "wallet", &importaddress, },
+ { "wallet", &importdescriptors, },
+ { "wallet", &importmulti, },
+ { "wallet", &importprivkey, },
+ { "wallet", &importprunedfunds, },
+ { "wallet", &importpubkey, },
+ { "wallet", &importwallet, },
+ { "wallet", &keypoolrefill, },
+ { "wallet", &listaddressgroupings, },
+ { "wallet", &listdescriptors, },
+ { "wallet", &listlabels, },
+ { "wallet", &listlockunspent, },
+ { "wallet", &listreceivedbyaddress, },
+ { "wallet", &listreceivedbylabel, },
+ { "wallet", &listsinceblock, },
+ { "wallet", &listtransactions, },
+ { "wallet", &listunspent, },
+ { "wallet", &listwalletdir, },
+ { "wallet", &listwallets, },
+ { "wallet", &loadwallet, },
+ { "wallet", &lockunspent, },
+ { "wallet", &removeprunedfunds, },
+ { "wallet", &rescanblockchain, },
+ { "wallet", &send, },
+ { "wallet", &sendmany, },
+ { "wallet", &sendtoaddress, },
+ { "wallet", &sethdseed, },
+ { "wallet", &setlabel, },
+ { "wallet", &settxfee, },
+ { "wallet", &setwalletflag, },
+ { "wallet", &signmessage, },
+ { "wallet", &signrawtransactionwithwallet, },
+ { "wallet", &unloadwallet, },
+ { "wallet", &upgradewallet, },
+ { "wallet", &walletcreatefundedpsbt, },
+ { "wallet", &walletlock, },
+ { "wallet", &walletpassphrase, },
+ { "wallet", &walletpassphrasechange, },
+ { "wallet", &walletprocesspsbt, },
};
// clang-format on
return MakeSpan(commands);
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index 225b975067..09a9ec68cd 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,11 +28,13 @@ 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;
- std::string filename;
- std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
+ BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
+ std::string filename = berkeley_database.Filename();
+ std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
if (!env->Open(error)) {
return false;
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index ceda99c64a..55110f30d9 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>
@@ -437,12 +440,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);
@@ -452,10 +455,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 14fb1fa89f..8f6b69bc78 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>
@@ -33,11 +34,11 @@ class WalletStorage
public:
virtual ~WalletStorage() = default;
virtual const std::string GetDisplayName() const = 0;
- virtual WalletDatabase& GetDatabase() = 0;
+ virtual WalletDatabase& GetDatabase() const = 0;
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;
@@ -171,7 +172,7 @@ protected:
WalletStorage& m_storage;
public:
- ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
+ explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
virtual ~ScriptPubKeyMan() {};
virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; }
virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
@@ -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; }
@@ -303,7 +304,7 @@ private:
/* the HD chain data model (external chain counters) */
CHDChain m_hd_chain;
- std::unordered_map<CKeyID, CHDChain, KeyIDHasher> m_inactive_hd_chains;
+ std::unordered_map<CKeyID, CHDChain, SaltedSipHasher> m_inactive_hd_chains;
/* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
@@ -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;
@@ -503,7 +504,7 @@ class LegacySigningProvider : public SigningProvider
private:
const LegacyScriptPubKeyMan& m_spk_man;
public:
- LegacySigningProvider(const LegacyScriptPubKeyMan& spk_man) : m_spk_man(spk_man) {}
+ explicit LegacySigningProvider(const LegacyScriptPubKeyMan& spk_man) : m_spk_man(spk_man) {}
bool GetCScript(const CScriptID &scriptid, CScript& script) const override { return m_spk_man.GetCScript(scriptid, script); }
bool HaveCScript(const CScriptID &scriptid) const override { return m_spk_man.HaveCScript(scriptid); }
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 02a161ecbd..0fb3b1d3c4 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -17,7 +17,6 @@
#include <sqlite3.h>
#include <stdint.h>
-static const char* const DATABASE_FILENAME = "wallet.dat";
static constexpr int32_t WALLET_SCHEMA_VERSION = 0;
static Mutex g_sqlite_mutex;
@@ -206,7 +205,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)));
@@ -566,25 +567,20 @@ bool SQLiteBatch::TxnAbort()
return res == SQLITE_OK;
}
-bool ExistsSQLiteDatabase(const fs::path& path)
-{
- const fs::path file = path / DATABASE_FILENAME;
- return fs::symlink_status(file).type() == fs::regular_file && IsSQLiteFile(file);
-}
-
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
{
- const fs::path file = path / DATABASE_FILENAME;
try {
- auto db = MakeUnique<SQLiteDatabase>(path, file);
+ fs::path data_file = SQLiteDataFile(path);
+ auto db = MakeUnique<SQLiteDatabase>(data_file.parent_path(), data_file);
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
+ status = DatabaseStatus::SUCCESS;
return db;
} catch (const std::runtime_error& e) {
status = DatabaseStatus::FAILED_LOAD;
- error.original = e.what();
+ error = Untranslated(e.what());
return nullptr;
}
}
@@ -593,37 +589,3 @@ std::string SQLiteDatabaseVersion()
{
return std::string(sqlite3_libversion());
}
-
-bool IsSQLiteFile(const fs::path& path)
-{
- if (!fs::exists(path)) return false;
-
- // A SQLite Database file is at least 512 bytes.
- boost::system::error_code ec;
- auto size = fs::file_size(path, ec);
- if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
- if (size < 512) return false;
-
- fsbridge::ifstream file(path, std::ios::binary);
- if (!file.is_open()) return false;
-
- // Magic is at beginning and is 16 bytes long
- char magic[16];
- file.read(magic, 16);
-
- // Application id is at offset 68 and 4 bytes long
- file.seekg(68, std::ios::beg);
- char app_id[4];
- file.read(app_id, 4);
-
- file.close();
-
- // Check the magic, see https://sqlite.org/fileformat2.html
- std::string magic_str(magic);
- if (magic_str != std::string("SQLite format 3")) {
- return false;
- }
-
- // Check the application id matches our network magic
- return memcmp(Params().MessageStart(), app_id, 4) == 0;
-}
diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h
index 5e5e93903b..70ab4f797a 100644
--- a/src/wallet/sqlite.h
+++ b/src/wallet/sqlite.h
@@ -37,7 +37,7 @@ public:
explicit SQLiteBatch(SQLiteDatabase& database);
~SQLiteBatch() override { Close(); }
- /* No-op. See commeng on SQLiteDatabase::Flush */
+ /* No-op. See comment on SQLiteDatabase::Flush */
void Flush() override {}
void Close() override;
@@ -105,6 +105,7 @@ public:
void IncrementUpdateCounter() override { ++nUpdateCounter; }
std::string Filename() override { return m_file_path; }
+ std::string Format() override { return "sqlite"; }
/** Make a SQLiteBatch connected to this database */
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
@@ -112,10 +113,8 @@ public:
sqlite3* m_db{nullptr};
};
-bool ExistsSQLiteDatabase(const fs::path& path);
std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
std::string SQLiteDatabaseVersion();
-bool IsSQLiteFile(const fs::path& path);
#endif // BITCOIN_WALLET_SQLITE_H
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index f38ccba384..ffac78d752 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -35,7 +35,7 @@ static CAmount balance = 0;
CoinEligibilityFilter filter_standard(1, 6, 0);
CoinEligibilityFilter filter_confirmed(1, 1, 0);
CoinEligibilityFilter filter_standard_extra(6, 6, 0);
-CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0);
+CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0, false);
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
{
@@ -64,7 +64,8 @@ static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bo
if (spendable) {
CTxDestination dest;
std::string error;
- assert(wallet.GetNewDestination(OutputType::BECH32, "", dest, error));
+ const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
+ assert(destination_ok);
tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
}
if (fIsFromMe) {
@@ -114,7 +115,10 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
- for (auto& coin : coins) static_groups.emplace_back(coin, 0, true, 0, 0);
+ for (auto& coin : coins) {
+ static_groups.emplace_back();
+ static_groups.back().Insert(coin, 0, true, 0, 0, false);
+ }
return static_groups;
}
@@ -122,7 +126,10 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
- for (auto& coin : coins) static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0);
+ for (auto& coin : coins) {
+ static_groups.emplace_back();
+ static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
+ }
return static_groups;
}
@@ -262,28 +269,28 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
}
// Make sure that effective value is working in SelectCoinsMinConf when BnB is used
- CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0);
+ CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0, false);
CoinSet setCoinsRet;
CAmount nValueRet;
bool bnb_used;
empty_wallet();
add_coin(1);
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
// Test fees subtracted from output:
empty_wallet();
add_coin(1 * CENT);
vCoins.at(0).nInputBytes = 40;
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
coin_selection_params_bnb.m_subtract_fee_outputs = true;
- BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// Make sure that can use BnB when there are preset inputs
empty_wallet();
{
- std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
+ std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
bool firstRun;
wallet->LoadWallet(firstRun);
wallet->SetupLegacyScriptPubKeyMan();
@@ -316,24 +323,24 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
empty_wallet();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
add_coin(1*CENT, 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// but we can find a new 1 cent
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
add_coin(2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// we can make 3 cents of new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
add_coin(5*CENT); // add a mature 5 cent coin,
@@ -343,33 +350,33 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// but we can make 37 cents if we accept new coins from ourself
- BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
// and we can make 38 cents if we accept all new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(nValueRet == 8 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -383,30 +390,30 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -415,11 +422,11 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin( 2*COIN);
add_coin( 3*COIN);
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -434,14 +441,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
- BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
// but if we add a bigger coin, small change is avoided
add_coin(1111*MIN_CHANGE);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// if we add more small coins:
@@ -449,16 +456,16 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 7 / 10);
// and try again to make 1.0 * MIN_CHANGE
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
- // run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
+ // run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
empty_wallet();
for (int j = 0; j < 20; j++)
add_coin(50000 * COIN);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
@@ -471,7 +478,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 7 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -481,7 +488,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 8 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
@@ -492,12 +499,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 100);
// trying to make 100.01 from these three coins
- BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
}
@@ -511,7 +518,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
if (amt - 2000 < MIN_CHANGE) {
// needs more than one input:
@@ -537,8 +544,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
- BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
int fails = 0;
@@ -546,8 +553,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
@@ -569,8 +576,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
@@ -597,7 +604,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(1000 * COIN);
add_coin(3 * COIN);
- BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -632,13 +639,13 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CAmount target = rand.randrange(balance - 1000) + 1000;
// Perform selection
- CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0);
- CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0);
+ CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0, false);
+ CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0, false);
CoinSet out_set;
CAmount out_value = 0;
bool bnb_used = false;
- BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_bnb, bnb_used) ||
- testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_knapsack, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_bnb, bnb_used) ||
+ testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_knapsack, bnb_used));
BOOST_CHECK_GE(out_value, target);
}
}
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 8f0083cd2e..b2eb8e4bca 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2019 The Bitcoin Core developers
+// 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.
@@ -13,6 +13,13 @@
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
+static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, std::string& database_filename)
+{
+ fs::path data_file = BDBDataFile(path);
+ database_filename = data_file.filename().string();
+ return GetBerkeleyEnv(data_file.parent_path());
+}
+
BOOST_AUTO_TEST_CASE(getwalletenv_file)
{
std::string test_name = "test_name.dat";
@@ -23,8 +30,8 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file)
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
- BOOST_CHECK(filename == test_name);
- BOOST_CHECK(env->Directory() == datadir);
+ BOOST_CHECK_EQUAL(filename, test_name);
+ BOOST_CHECK_EQUAL(env->Directory(), datadir);
}
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
@@ -34,8 +41,8 @@ BOOST_AUTO_TEST_CASE(getwalletenv_directory)
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
- BOOST_CHECK(filename == expected_name);
- BOOST_CHECK(env->Directory() == datadir);
+ BOOST_CHECK_EQUAL(filename, expected_name);
+ BOOST_CHECK_EQUAL(env->Directory(), datadir);
}
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index c80310045a..f035a70a20 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -1,8 +1,9 @@
-// Copyright (c) 2018-2019 The Bitcoin Core developers
+// 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 <fs.h>
+#include <univalue.h>
#include <util/check.h>
#include <util/system.h>
@@ -10,7 +11,7 @@
InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
{
- m_wallet_client = MakeWalletClient(*m_chain, *Assert(m_node.args));
+ m_wallet_client = MakeWalletClient(*m_node.chain, *Assert(m_node.args));
std::string sep;
sep += fs::path::preferred_separator;
@@ -37,6 +38,9 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam
InitWalletDirTestingSetup::~InitWalletDirTestingSetup()
{
+ gArgs.LockSettings([&](util::Settings& settings) {
+ settings.forced_settings.erase("walletdir");
+ });
fs::current_path(m_cwd);
}
diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h
index f5bade77df..37ae907de5 100644
--- a/src/wallet/test/init_test_fixture.h
+++ b/src/wallet/test/init_test_fixture.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2019 The Bitcoin Core developers
+// 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.
@@ -19,7 +19,6 @@ struct InitWalletDirTestingSetup: public BasicTestingSetup {
fs::path m_datadir;
fs::path m_cwd;
std::map<std::string, fs::path> m_walletdir_path_cases;
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<interfaces::WalletClient> m_wallet_client;
};
diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp
index 9b905569fc..45e1b8c4b8 100644
--- a/src/wallet/test/init_tests.cpp
+++ b/src/wallet/test/init_tests.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2019 The Bitcoin Core developers
+// 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.
@@ -19,7 +19,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default)
BOOST_CHECK(result == true);
fs::path walletdir = gArgs.GetArg("-walletdir", "");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
- BOOST_CHECK(walletdir == expected_path);
+ BOOST_CHECK_EQUAL(walletdir, expected_path);
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
@@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
BOOST_CHECK(result == true);
fs::path walletdir = gArgs.GetArg("-walletdir", "");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]);
- BOOST_CHECK(walletdir == expected_path);
+ BOOST_CHECK_EQUAL(walletdir, expected_path);
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist)
@@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing)
BOOST_CHECK(result == true);
fs::path walletdir = gArgs.GetArg("-walletdir", "");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
- BOOST_CHECK(walletdir == expected_path);
+ BOOST_CHECK_EQUAL(walletdir, expected_path);
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2)
@@ -79,7 +79,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2)
BOOST_CHECK(result == true);
fs::path walletdir = gArgs.GetArg("-walletdir", "");
fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]);
- BOOST_CHECK(walletdir == expected_path);
+ BOOST_CHECK_EQUAL(walletdir, expected_path);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp
index d5aed99d99..5d25885bd4 100644
--- a/src/wallet/test/ismine_tests.cpp
+++ b/src/wallet/test/ismine_tests.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -27,8 +27,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
CKey uncompressedKey;
uncompressedKey.MakeNewKey(false);
CPubKey uncompressedPubkey = uncompressedKey.GetPubKey();
- NodeContext node;
- std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
+ std::unique_ptr<interfaces::Chain>& chain = m_node.chain;
CScript scriptPubKey;
isminetype result;
diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp
index f7c1337b0d..347a436429 100644
--- a/src/wallet/test/scriptpubkeyman_tests.cpp
+++ b/src/wallet/test/scriptpubkeyman_tests.cpp
@@ -17,9 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(CanProvide)
{
// Set up wallet and keyman variables.
- NodeContext node;
- std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
// Make a 1 of 2 multisig script
diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp
index 10ddfa22ef..5b421840e0 100644
--- a/src/wallet/test/wallet_crypto_tests.cpp
+++ b/src/wallet/test/wallet_crypto_tests.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2019 The Bitcoin Core developers
+// Copyright (c) 2014-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.
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index 4d6f427618..badf2eb459 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -6,10 +6,10 @@
WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
: TestingSetup(chainName),
- m_wallet(m_chain.get(), "", CreateMockWalletDatabase())
+ m_wallet(m_node.chain.get(), "", CreateMockWalletDatabase())
{
bool fFirstRun;
m_wallet.LoadWallet(fFirstRun);
- m_chain_notifications_handler = m_chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
+ m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
m_wallet_client->registerRpcs();
}
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index ba8a5ff1f3..ab7fb8c42b 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -20,8 +20,7 @@
struct WalletTestingSetup : public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
- std::unique_ptr<interfaces::WalletClient> m_wallet_client = interfaces::MakeWalletClient(*m_chain, *Assert(m_node.args));
+ std::unique_ptr<interfaces::WalletClient> m_wallet_client = interfaces::MakeWalletClient(*m_node.chain, *Assert(m_node.args));
CWallet m_wallet;
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
};
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c42114c394..5480f3ab22 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -28,6 +28,8 @@ RPCHelpMan importmulti();
RPCHelpMan dumpwallet();
RPCHelpMan importwallet();
+extern RecursiveMutex cs_wallets;
+
// Ensure that fee levels defined in the wallet are at least as high
// as the default levels for node policy.
static_assert(DEFAULT_TRANSACTION_MINFEE >= DEFAULT_MIN_RELAY_TX_FEE, "wallet minimum fee is smaller than default relay fee");
@@ -83,12 +85,9 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = ::ChainActive().Tip();
- NodeContext node;
- auto chain = interfaces::MakeChain(node);
-
// Verify ScanForWalletTransactions fails to read an unknown start block.
{
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@@ -107,7 +106,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@@ -133,7 +132,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@@ -158,7 +157,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions scans no blocks.
{
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@@ -183,9 +182,6 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = ::ChainActive().Tip();
- NodeContext node;
- auto chain = interfaces::MakeChain(node);
-
// Prune the older block file.
{
LOCK(cs_main);
@@ -197,7 +193,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
// before the missing block, and success for a key whose creation time is
// after.
{
- std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
AddWallet(wallet);
@@ -255,14 +251,11 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
- NodeContext node;
- auto chain = interfaces::MakeChain(node);
-
std::string backup_file = (GetDataDir() / "wallet.backup").string();
// Import key into wallet and call dumpwallet to create backup file.
{
- std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
@@ -284,7 +277,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
- std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
@@ -317,10 +310,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
- NodeContext node;
- auto chain = interfaces::MakeChain(node);
-
- CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
CWalletTx wtx(&wallet, m_coinbase_txns.back());
@@ -495,7 +485,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- wallet = MakeUnique<CWallet>(m_chain.get(), "", CreateMockWalletDatabase());
+ wallet = MakeUnique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
{
LOCK2(wallet->cs_wallet, ::cs_main);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
@@ -545,7 +535,6 @@ public:
return it->second;
}
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<CWallet> wallet;
};
@@ -561,7 +550,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
- BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
// Check initial balance from one mature coinbase transaction.
@@ -577,7 +566,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
- BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
// Lock both coins. Confirm number of available coins drops to 0.
@@ -606,15 +595,13 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
- BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
+ BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
}
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
- NodeContext node;
- auto chain = interfaces::MakeChain(node);
- std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
@@ -688,7 +675,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,11 +693,10 @@ 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);
- auto wallet = TestLoadWallet(*chain);
+ auto wallet = TestLoadWallet(*m_node.chain);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
@@ -745,12 +731,12 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
- BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
+ BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
// Reload wallet and make sure new transactions are detected despite events
// being blocked
- wallet = TestLoadWallet(*chain);
+ wallet = TestLoadWallet(*m_node.chain);
BOOST_CHECK(rescan_completed);
BOOST_CHECK_EQUAL(addtx_count, 2);
{
@@ -777,18 +763,20 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
// deadlock during the sync and simulates a new block notification happening
// as soon as possible.
addtx_count = 0;
- auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet) {
+ auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet, cs_wallets) {
BOOST_CHECK(rescan_completed);
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
- BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
+ BOOST_CHECK(m_node.chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error));
+ LEAVE_CRITICAL_SECTION(cs_wallets);
LEAVE_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
SyncWithValidationInterfaceQueue();
ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
+ ENTER_CRITICAL_SECTION(cs_wallets);
});
- wallet = TestLoadWallet(*chain);
+ wallet = TestLoadWallet(*m_node.chain);
BOOST_CHECK_EQUAL(addtx_count, 4);
{
LOCK(wallet->cs_wallet);
@@ -802,8 +790,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
- auto chain = interfaces::MakeChain(m_node);
- auto wallet = TestLoadWallet(*chain);
+ auto wallet = TestLoadWallet(*m_node.chain);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
diff --git a/src/wallet/test/walletdb_tests.cpp b/src/wallet/test/walletdb_tests.cpp
new file mode 100644
index 0000000000..558121ae42
--- /dev/null
+++ b/src/wallet/test/walletdb_tests.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2012-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 <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 6b7d05fdf3..db80745db0 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -52,7 +52,7 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
-static RecursiveMutex cs_wallets;
+RecursiveMutex cs_wallets;
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
@@ -85,9 +85,9 @@ static void UpdateWalletSetting(interfaces::Chain& chain,
std::vector<bilingual_str>& warnings)
{
if (load_on_startup == nullopt) return;
- if (load_on_startup.get() && !AddWalletSetting(chain, wallet_name)) {
+ if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
- } else if (!load_on_startup.get() && !RemoveWalletSetting(chain, wallet_name)) {
+ } else if (!load_on_startup.value() && !RemoveWalletSetting(chain, wallet_name)) {
warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."));
}
}
@@ -100,6 +100,7 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet)
if (i != vpwallets.end()) return false;
vpwallets.push_back(wallet);
wallet->ConnectScriptPubKeyManNotifiers();
+ wallet->NotifyCanGetAddressesChanged();
return true;
}
@@ -418,7 +419,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
return false;
if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
- WalletBatch(*database).WriteMasterKey(pMasterKey.first, pMasterKey.second);
+ WalletBatch(GetDatabase()).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
@@ -431,27 +432,19 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
void CWallet::chainStateFlushed(const CBlockLocator& loc)
{
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
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(*database);
+ WalletBatch* batch = batch_in ? batch_in : new WalletBatch(GetDatabase());
if (nWalletVersion > 40000)
batch->WriteMinVersion(nWalletVersion);
if (!batch_in)
@@ -459,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;
@@ -503,12 +484,12 @@ bool CWallet::HasWalletSpend(const uint256& txid) const
void CWallet::Flush()
{
- database->Flush();
+ GetDatabase().Flush();
}
void CWallet::Close()
{
- database->Close();
+ GetDatabase().Close();
}
void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> range)
@@ -589,7 +570,7 @@ void CWallet::AddToSpends(const uint256& wtxid)
{
auto it = mapWallet.find(wtxid);
assert(it != mapWallet.end());
- CWalletTx& thisTx = it->second;
+ const CWalletTx& thisTx = it->second;
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
@@ -634,7 +615,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
- WalletBatch* encrypted_batch = new WalletBatch(*database);
+ WalletBatch* encrypted_batch = new WalletBatch(GetDatabase());
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
@@ -655,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;
@@ -686,12 +667,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
- database->Rewrite();
+ GetDatabase().Rewrite();
// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
- database->ReloadDbEnv();
+ GetDatabase().ReloadDbEnv();
}
NotifyStatusChanged(this);
@@ -702,7 +683,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
DBErrors CWallet::ReorderTransactions()
{
LOCK(cs_wallet);
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
// Old wallets didn't have any defined order for transactions
// Probably a bad idea to change the output of this
@@ -763,7 +744,7 @@ int64_t CWallet::IncOrderPosNext(WalletBatch* batch)
if (batch) {
batch->WriteOrderPosNext(nOrderPosNext);
} else {
- WalletBatch(*database).WriteOrderPosNext(nOrderPosNext);
+ WalletBatch(GetDatabase()).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
@@ -793,7 +774,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
wtx.mapValue["replaced_by_txid"] = newHash.ToString();
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
bool success = true;
if (!batch.WriteTx(wtx)) {
@@ -865,7 +846,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
{
LOCK(cs_wallet);
- WalletBatch batch(*database, fFlushOnClose);
+ WalletBatch batch(GetDatabase(), fFlushOnClose);
uint256 hash = tx->GetHash();
@@ -965,11 +946,12 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
}
// If wallet doesn't have a chain (e.g wallet-tool), don't bother to update txn.
if (HaveChain()) {
- Optional<int> block_height = chain().getBlockHeight(wtx.m_confirm.hashBlock);
- if (block_height) {
+ bool active;
+ int height;
+ if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) {
// Update cached block height variable since it not stored in the
// serialized transaction.
- wtx.m_confirm.block_height = *block_height;
+ wtx.m_confirm.block_height = height;
} else if (wtx.isConflicted() || wtx.isConfirmed()) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
@@ -1064,7 +1046,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
{
LOCK(cs_wallet);
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
std::set<uint256> todo;
std::set<uint256> done;
@@ -1072,7 +1054,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
// Can't mark abandoned if confirmed or in mempool
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
- CWalletTx& origtx = it->second;
+ const CWalletTx& origtx = it->second;
if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -1127,7 +1109,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
return;
// Do not flush the wallet here for performance reasons
- WalletBatch batch(*database, false);
+ WalletBatch batch(GetDatabase(), false);
std::set<uint256> todo;
std::set<uint256> done;
@@ -1196,9 +1178,8 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
// Handle transactions that were removed from the mempool because they
// conflict with transactions in a newly connected block.
if (reason == MemPoolRemovalReason::CONFLICT) {
- // Call SyncNotifications, so external -walletnotify notifications will
- // be triggered for these transactions. Set Status::UNCONFIRMED instead
- // of Status::CONFLICTED for a few reasons:
+ // Trigger external -walletnotify notifications for these transactions.
+ // Set Status::UNCONFIRMED instead of Status::CONFLICTED for a few reasons:
//
// 1. The transactionRemovedFromMempool callback does not currently
// provide the conflicting block's hash and height, and for backwards
@@ -1465,13 +1446,13 @@ void CWallet::SetWalletFlag(uint64_t flags)
{
LOCK(cs_wallet);
m_wallet_flags |= flags;
- if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags))
+ if (!WalletBatch(GetDatabase()).WriteWalletFlags(m_wallet_flags))
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
void CWallet::UnsetWalletFlag(uint64_t flag)
{
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
UnsetWalletFlagWithDB(batch, flag);
}
@@ -1510,7 +1491,7 @@ bool CWallet::AddWalletFlags(uint64_t flags)
LOCK(cs_wallet);
// We should never be writing unknown non-tolerable wallet flags
assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
- if (!WalletBatch(*database).WriteWalletFlags(flags)) {
+ if (!WalletBatch(GetDatabase()).WriteWalletFlags(flags)) {
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
@@ -1601,7 +1582,7 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
return false;
}
if (apply_label) {
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
for (const CScript& script : script_pub_keys) {
CTxDestination dest;
ExtractDestination(script, dest);
@@ -1777,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))));
}
@@ -1786,18 +1771,22 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
+ // Read block data
CBlock block;
- bool next_block;
+ chain().findBlock(block_hash, FoundBlock().data(block));
+
+ // Find next block separately from reading data above, because reading
+ // is slow and there might be a reorg while it is read.
+ bool block_still_active = false;
+ bool next_block = false;
uint256 next_block_hash;
- bool reorg = false;
- if (chain().findBlock(block_hash, FoundBlock().data(block)) && !block.IsNull()) {
+ chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash)));
+
+ if (!block.IsNull()) {
LOCK(cs_wallet);
- next_block = chain().findNextBlock(block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg);
- if (reorg) {
+ if (!block_still_active) {
// Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block.
- // TODO: This should return success instead of failure, see
- // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
break;
@@ -1812,13 +1801,12 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
- next_block = chain().findNextBlock(block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg);
}
if (max_height && block_height >= *max_height) {
break;
}
{
- if (!next_block || reorg) {
+ if (!next_block) {
// break successfully when rescan has reached the tip, or
// previous block is no longer on the chain due to a reorg
break;
@@ -2369,13 +2357,12 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
return ptx->vout[n];
}
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
setCoinsRet.clear();
nValueRet = 0;
- std::vector<OutputGroup> utxo_pool;
if (coin_selection_params.use_bnb) {
// Get long term estimate
FeeCalculation feeCalc;
@@ -2383,35 +2370,27 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
temp.m_confirm_target = 1008;
CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc);
- // Calculate cost of change
- CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
+ // Get the feerate for effective value.
+ // When subtracting the fee from the outputs, we want the effective feerate to be 0
+ CFeeRate effective_feerate{0};
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ effective_feerate = coin_selection_params.effective_fee;
+ }
- // Filter by the min conf specs and add to utxo_pool and calculate effective value
- for (OutputGroup& group : groups) {
- if (!group.EligibleForSpending(eligibility_filter)) continue;
+ std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, long_term_feerate, eligibility_filter, true /* positive_only */);
- if (coin_selection_params.m_subtract_fee_outputs) {
- // Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
- group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
- } else {
- group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
- }
+ // Calculate cost of change
+ CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
- OutputGroup pos_group = group.GetPositiveOnlyGroup();
- if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
- }
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
bnb_used = true;
- return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
+ return SelectCoinsBnB(groups, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
} else {
- // Filter by the min conf specs and add to utxo_pool
- for (const OutputGroup& group : groups) {
- if (!group.EligibleForSpending(eligibility_filter)) continue;
- utxo_pool.push_back(group);
- }
+ std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, CFeeRate(0), CFeeRate(0), eligibility_filter, false /* positive_only */);
+
bnb_used = false;
- return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
+ return KnapsackSolver(nTargetValue, groups, setCoinsRet, nValueRet);
}
}
@@ -2494,16 +2473,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// explicitly shuffling the outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
- std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
-
bool res = value_to_select <= 0 ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
util::insert(setCoinsRet, setPresetCoins);
@@ -2721,7 +2698,7 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin
return locktime;
}
-OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend)
+OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const
{
// If -changetype is specified, always use that change type.
if (change_type) {
@@ -2794,6 +2771,7 @@ bool CWallet::CreateTransactionInternal(
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
+ coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
// Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservedest so
@@ -2801,7 +2779,7 @@ bool CWallet::CreateTransactionInternal(
CScript scriptChange;
// coin control: send change to custom address
- if (!boost::get<CNoDestination>(&coin_control.destChange)) {
+ if (!std::get_if<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.
@@ -2833,7 +2811,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;
}
@@ -3063,7 +3041,7 @@ bool CWallet::CreateTransactionInternal(
// to avoid conflicting with other possible uses of nSequence,
// and in the spirit of "smallest possible change from prior
// behavior."
- const uint32_t nSequence = coin_control.m_signal_bip125_rbf.get_value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
+ const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
for (const auto& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
}
@@ -3105,10 +3083,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;
}
@@ -3124,13 +3102,14 @@ bool CWallet::CreateTransaction(
bool sign)
{
int nChangePosIn = nChangePosInOut;
- CTransactionRef tx2 = tx;
+ Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
// try with avoidpartialspends unless it's enabled already
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
CAmount nFeeRet2;
+ CTransactionRef tx2;
int nChangePosInOut2 = nChangePosIn;
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) {
@@ -3192,10 +3171,10 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
LOCK(cs_wallet);
fFirstRunRet = false;
- DBErrors nLoadWalletRet = WalletBatch(*database).LoadWallet(this);
+ DBErrors nLoadWalletRet = WalletBatch(GetDatabase()).LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
- if (database->Rewrite("\x04pool"))
+ if (GetDatabase().Rewrite("\x04pool"))
{
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
@@ -3219,7 +3198,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
AssertLockHeld(cs_wallet);
- DBErrors nZapSelectTxRet = WalletBatch(*database).ZapSelectTx(vHashIn, vHashOut);
+ DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
for (const uint256& hash : vHashOut) {
const auto& it = mapWallet.find(hash);
wtxOrdered.erase(it->second.m_it_wtxOrdered);
@@ -3231,7 +3210,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
{
- if (database->Rewrite("\x04pool"))
+ if (GetDatabase().Rewrite("\x04pool"))
{
for (const auto& spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
@@ -3269,14 +3248,14 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose)
{
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
return SetAddressBookWithDB(batch, address, strName, strPurpose);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
{
bool is_mine;
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
{
LOCK(cs_wallet);
// If we want to delete receiving addresses, we need to take care that DestData "used" (and possibly newer DestData) gets preserved (and the "deleted" address transformed into a change entry instead of actually being deleted)
@@ -3735,7 +3714,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value)
{
- if (boost::get<CNoDestination>(&dest))
+ if (std::get_if<CNoDestination>(&dest))
return false;
m_address_book[dest].destdata.insert(std::make_pair(key, value));
@@ -3791,7 +3770,7 @@ std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, cons
// 2. Path to an existing directory.
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
- const fs::path& wallet_path = fs::absolute(name, GetWalletDir());
+ const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), name);
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
@@ -4023,7 +4002,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
int rescan_height = 0;
if (!gArgs.GetBoolArg("-rescan", false))
{
- WalletBatch batch(*walletInstance->database);
+ WalletBatch batch(walletInstance->GetDatabase());
CBlockLocator locator;
if (batch.ReadBestBlock(locator)) {
if (const Optional<int> fork_height = chain.findLocatorFork(locator)) {
@@ -4066,16 +4045,13 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
- // The way the 'time_first_key' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0.
- Optional<int64_t> time_first_key = MakeOptional(false, int64_t());;
+ Optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
if (time_first_key) {
- if (Optional<int> first_block = chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) {
- rescan_height = *first_block;
- }
+ chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, FoundBlock().height(rescan_height));
}
{
@@ -4086,7 +4062,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
- walletInstance->database->IncrementUpdateCounter();
+ walletInstance->GetDatabase().IncrementUpdateCounter();
}
{
@@ -4117,36 +4093,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;
}
}
@@ -4167,7 +4140,7 @@ void CWallet::postInitProcess()
bool CWallet::BackupWallet(const std::string& strDest) const
{
- return database->Backup(strDest);
+ return GetDatabase().Backup(strDest);
}
CKeyPool::CKeyPool()
@@ -4209,51 +4182,90 @@ bool CWalletTx::IsImmatureCoinBase() const
return GetBlocksToMaturity() > 0;
}
-std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const {
- std::vector<OutputGroup> groups;
- std::map<CTxDestination, OutputGroup> gmap;
- std::set<CTxDestination> full_groups;
+std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const
+{
+ std::vector<OutputGroup> groups_out;
- for (const auto& output : outputs) {
- if (output.fSpendable) {
- CTxDestination dst;
- CInputCoin input_coin = output.GetInputCoin();
+ if (separate_coins) {
+ // Single coin means no grouping. Each COutput gets its own OutputGroup.
+ for (const COutput& output : outputs) {
+ // Skip outputs we cannot spend
+ if (!output.fSpendable) continue;
size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
- auto it = gmap.find(dst);
- if (it != gmap.end()) {
- // Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
- // number of entries, to protect against inadvertently creating
- // a too-large transaction when using -avoidpartialspends to
- // prevent breaking consensus or surprising users with a very
- // high amount of fees.
- if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
- groups.push_back(it->second);
- it->second = OutputGroup{};
- full_groups.insert(dst);
- }
- it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- } else {
- gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- }
- } else {
- groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- }
+ CInputCoin input_coin = output.GetInputCoin();
+
+ // Make an OutputGroup containing just this output
+ OutputGroup group{effective_feerate, long_term_feerate};
+ group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
+
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
+ if (positive_only && group.effective_value <= 0) continue;
+ if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
+ }
+ return groups_out;
+ }
+
+ // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
+ // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
+ // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
+ // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
+ // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
+ // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
+ std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
+ for (const auto& output : outputs) {
+ // Skip outputs we cannot spend
+ if (!output.fSpendable) continue;
+
+ size_t ancestors, descendants;
+ chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
+ CInputCoin input_coin = output.GetInputCoin();
+ CScript spk = input_coin.txout.scriptPubKey;
+
+ std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
+
+ if (groups.size() == 0) {
+ // No OutputGroups for this scriptPubKey yet, add one
+ groups.emplace_back(effective_feerate, long_term_feerate);
+ }
+
+ // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
+ // A pointer is used here so that group can be reassigned later if it is full.
+ OutputGroup* group = &groups.back();
+
+ // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends
+ // to avoid surprising users with very high fees.
+ if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
+ // The last output group is full, add a new group to the vector and use that group for the insertion
+ groups.emplace_back(effective_feerate, long_term_feerate);
+ group = &groups.back();
}
+
+ // Add the input_coin to group
+ group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
}
- if (!single_coin) {
- for (auto& it : gmap) {
- auto& group = it.second;
- if (full_groups.count(it.first) > 0) {
- // Make this unattractive as we want coin selection to avoid it if possible
- group.m_ancestors = max_ancestors - 1;
+
+ // Now we go through the entire map and pull out the OutputGroups
+ for (const auto& spk_and_groups_pair: spk_to_groups_map) {
+ const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second;
+
+ // Go through the vector backwards. This allows for the first item we deal with being the partial group.
+ for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) {
+ const OutputGroup& group = *group_it;
+
+ // Don't include partial groups if there are full groups too and we don't want partial groups
+ if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) {
+ continue;
}
- groups.push_back(group);
+
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
+ if (positive_only && group.effective_value <= 0) continue;
+ if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
}
}
- return groups;
+
+ return groups_out;
}
bool CWallet::IsCrypted() const
@@ -4470,7 +4482,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
- WalletBatch batch(*database);
+ WalletBatch batch(GetDatabase());
if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
}
@@ -4510,7 +4522,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");
@@ -4555,7 +4567,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
@@ -4567,7 +4582,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 245144a1c9..4fc4466604 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -610,8 +610,16 @@ struct CoinSelectionParams
size_t tx_noinputs_size = 0;
//! Indicate that we are subtracting the fee from outputs
bool m_subtract_fee_outputs = false;
-
- CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {}
+ bool m_avoid_partial_spends = false;
+
+ CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) :
+ use_bnb(use_bnb),
+ change_output_size(change_output_size),
+ change_spend_size(change_spend_size),
+ effective_fee(effective_fee),
+ tx_noinputs_size(tx_noinputs_size),
+ m_avoid_partial_spends(avoid_partial)
+ {}
CoinSelectionParams() {}
};
@@ -636,9 +644,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.
@@ -698,7 +703,7 @@ private:
std::string m_name;
/** Internal database handle. */
- std::unique_ptr<WalletDatabase> database;
+ std::unique_ptr<WalletDatabase> const m_database;
/**
* The following is used to keep track of how far behind the wallet is
@@ -732,14 +737,11 @@ public:
*/
mutable RecursiveMutex cs_wallet;
- /** Get database handle used by this wallet. Ideally this function would
- * not be necessary.
- */
- WalletDatabase& GetDBHandle()
+ WalletDatabase& GetDatabase() const override
{
- return *database;
+ assert(static_cast<bool>(m_database));
+ return *m_database;
}
- WalletDatabase& GetDatabase() override { return *database; }
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
@@ -761,7 +763,7 @@ public:
CWallet(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database)
: m_chain(chain),
m_name(name),
- database(std::move(database))
+ m_database(std::move(database))
{
}
@@ -800,8 +802,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.
@@ -824,7 +826,7 @@ public:
* completion the coin set and corresponding actual target value is
* assembled
*/
- bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+ bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -833,7 +835,7 @@ public:
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const;
+ std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const;
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -853,7 +855,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
@@ -936,7 +938,7 @@ public:
Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
- OutputType TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend);
+ OutputType TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
/**
* Insert additional inputs into the transaction by
@@ -1076,11 +1078,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; }
@@ -1201,7 +1200,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;
@@ -1280,7 +1279,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 0092a29cb4..4e6270220e 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -14,8 +14,12 @@
#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
#include <wallet/wallet.h>
#include <atomic>
@@ -360,7 +364,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{
ssValue >> hash;
}
- catch (...) {}
+ catch (const std::ios_base::failure&) {}
bool fSkipCheck = false;
@@ -422,7 +426,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
uint256 checksum;
ssValue >> checksum;
if ((checksum_valid = Hash(vchPrivKey) != checksum)) {
- strErr = "Error reading wallet database: Crypted key corrupt";
+ strErr = "Error reading wallet database: Encrypted key corrupt";
return false;
}
}
@@ -941,7 +945,7 @@ void MaybeCompactWalletDB()
}
for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
- WalletDatabase& dbh = pwallet->GetDBHandle();
+ WalletDatabase& dbh = pwallet->GetDatabase();
unsigned int nUpdateCounter = dbh.nUpdateCounter;
@@ -1009,10 +1013,10 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
Optional<DatabaseFormat> format;
if (exists) {
- if (ExistsBerkeleyDatabase(path)) {
+ if (IsBDBFile(BDBDataFile(path))) {
format = DatabaseFormat::BERKELEY;
}
- if (ExistsSQLiteDatabase(path)) {
+ if (IsSQLiteFile(SQLiteDataFile(path))) {
if (format) {
error = Untranslated(strprintf("Failed to load database path '%s'. Data is in ambiguous format.", path.string()));
status = DatabaseStatus::FAILED_BAD_FORMAT;
@@ -1048,11 +1052,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 (format && format == DatabaseFormat::SQLITE) {
+ // 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
+ format = DatabaseFormat::SQLITE;
+#endif
+#ifdef USE_BDB
+ format = DatabaseFormat::BERKELEY;
+#endif
+ }
+
+ 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. */
@@ -1064,5 +1088,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 4452840eb1..b2cb0bf479 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -5,6 +5,7 @@
#include <fs.h>
#include <util/system.h>
#include <util/translation.h>
+#include <wallet/dump.h>
#include <wallet/salvage.h>
#include <wallet/wallet.h>
#include <wallet/walletutil.h>
@@ -21,30 +22,27 @@ static void WalletToolReleaseWallet(CWallet* wallet)
delete wallet;
}
-static void WalletCreate(CWallet* wallet_instance)
+static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flags)
{
LOCK(wallet_instance->cs_wallet);
wallet_instance->SetMinVersion(FEATURE_HD_SPLIT);
+ wallet_instance->AddWalletFlags(wallet_creation_flags);
- // generate a new HD seed
- auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
- CPubKey seed = spk_man->GenerateNewSeed();
- spk_man->SetHDSeed(seed);
+ if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
+ spk_man->SetupGeneration(false);
+ } else {
+ wallet_instance->SetupDescriptorScriptPubKeyMans();
+ }
tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
}
-static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, bool create)
+static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options)
{
- DatabaseOptions options;
DatabaseStatus status;
- if (create) {
- options.require_create = true;
- } else {
- options.require_existing = true;
- }
bilingual_str error;
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
if (!database) {
@@ -85,7 +83,7 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa
}
}
- if (create) WalletCreate(wallet_instance.get());
+ if (options.require_create) WalletCreate(wallet_instance.get(), options.create_flags);
return wallet_instance;
}
@@ -95,6 +93,9 @@ static void WalletShowInfo(CWallet* wallet_instance)
LOCK(wallet_instance->cs_wallet);
tfm::format(std::cout, "Wallet info\n===========\n");
+ tfm::format(std::cout, "Name: %s\n", wallet_instance->GetName());
+ tfm::format(std::cout, "Format: %s\n", wallet_instance->GetDatabase().Format());
+ tfm::format(std::cout, "Descriptors: %s\n", wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? "yes" : "no");
tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no");
tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no");
tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
@@ -102,36 +103,89 @@ static void WalletShowInfo(CWallet* wallet_instance)
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
}
-bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
+bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
{
- fs::path path = fs::absolute(name, GetWalletDir());
+ if (args.IsArgSet("-format") && command != "createfromdump") {
+ tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n");
+ return false;
+ }
+ if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump") {
+ tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\" and \"createfromdump\" commands.\n");
+ return false;
+ }
+ if (args.IsArgSet("-descriptors") && command != "create") {
+ tfm::format(std::cerr, "The -descriptors option can only be used with the 'create' command.\n");
+ return false;
+ }
+ if (command == "create" && !args.IsArgSet("-wallet")) {
+ tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
+ return false;
+ }
+ const std::string name = args.GetArg("-wallet", "");
+ const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), name);
if (command == "create") {
- std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ true);
+ DatabaseOptions options;
+ options.require_create = true;
+ if (args.GetBoolArg("-descriptors", false)) {
+ options.create_flags |= WALLET_FLAG_DESCRIPTORS;
+ options.require_format = DatabaseFormat::SQLITE;
+ }
+
+ std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
}
- } else if (command == "info" || command == "salvage") {
- if (command == "info") {
- std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
- if (!wallet_instance) return false;
- WalletShowInfo(wallet_instance.get());
- wallet_instance->Close();
- } else if (command == "salvage") {
- bilingual_str error;
- std::vector<bilingual_str> warnings;
- bool ret = RecoverDatabaseFile(path, error, warnings);
- if (!ret) {
- for (const auto& warning : warnings) {
- tfm::format(std::cerr, "%s\n", warning.original);
- }
- if (!error.empty()) {
- tfm::format(std::cerr, "%s\n", error.original);
- }
+ } else if (command == "info") {
+ DatabaseOptions options;
+ options.require_existing = true;
+ std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
+ if (!wallet_instance) return false;
+ 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);
+ if (!ret) {
+ for (const auto& warning : warnings) {
+ tfm::format(std::cerr, "%s\n", warning.original);
}
+ if (!error.empty()) {
+ tfm::format(std::cerr, "%s\n", error.original);
+ }
+ }
+ return ret;
+#else
+ tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
+ return false;
+#endif
+ } else if (command == "dump") {
+ DatabaseOptions options;
+ options.require_existing = true;
+ std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
+ if (!wallet_instance) return false;
+ bilingual_str error;
+ bool ret = DumpWallet(*wallet_instance, error);
+ if (!ret && !error.empty()) {
+ tfm::format(std::cerr, "%s\n", error.original);
return ret;
}
+ tfm::format(std::cout, "The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n");
+ return ret;
+ } else if (command == "createfromdump") {
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+ bool ret = CreateFromDump(name, path, error, warnings);
+ for (const auto& warning : warnings) {
+ tfm::format(std::cout, "%s\n", warning.original);
+ }
+ if (!ret && !error.empty()) {
+ tfm::format(std::cerr, "%s\n", error.original);
+ }
+ return ret;
} else {
tfm::format(std::cerr, "Invalid command: %s\n", command);
return false;
diff --git a/src/wallet/wallettool.h b/src/wallet/wallettool.h
index d0b8d6812a..f4516bb5bc 100644
--- a/src/wallet/wallettool.h
+++ b/src/wallet/wallettool.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2016-2019 The Bitcoin Core developers
+// Copyright (c) 2016-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.
@@ -10,7 +10,7 @@
namespace WalletTool {
void WalletShowInfo(CWallet* wallet_instance);
-bool ExecuteWalletToolFunc(const std::string& command, const std::string& file);
+bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command);
} // namespace WalletTool
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index a2a55f9751..dd2f071b6c 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -7,9 +7,6 @@
#include <logging.h>
#include <util/system.h>
-bool ExistsBerkeleyDatabase(const fs::path& path);
-bool ExistsSQLiteDatabase(const fs::path& path);
-
fs::path GetWalletDir()
{
fs::path path;
@@ -32,41 +29,16 @@ fs::path GetWalletDir()
return path;
}
-std::vector<fs::path> ListWalletDir()
+bool IsFeatureSupported(int wallet_version, int feature_version)
{
- const fs::path wallet_dir = GetWalletDir();
- const size_t offset = wallet_dir.string().size() + 1;
- std::vector<fs::path> paths;
- boost::system::error_code ec;
-
- for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
- if (ec) {
- LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string());
- 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);
+ return wallet_version >= feature_version;
+}
- 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);
- }
- }
+WalletFeature GetClosestWalletFeature(int version)
+{
+ static constexpr std::array 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 paths;
+ return static_cast<WalletFeature>(0);
}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index afdcb2e18a..67b2ee2b98 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2019 The Bitcoin Core developers
+// Copyright (c) 2017-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.
@@ -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
@@ -64,9 +65,6 @@ enum WalletFlags : uint64_t {
//! Get the path of the wallet directory.
fs::path GetWalletDir();
-//! Get wallets in wallet directory.
-std::vector<fs::path> ListWalletDir();
-
/** Descriptor with some wallet metadata */
class WalletDescriptor
{