aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/bdb.cpp16
-rw-r--r--src/wallet/bdb.h3
-rw-r--r--src/wallet/coinselection.cpp59
-rw-r--r--src/wallet/coinselection.h31
-rw-r--r--src/wallet/db.cpp3
-rw-r--r--src/wallet/db.h8
-rw-r--r--src/wallet/dump.cpp10
-rw-r--r--src/wallet/init.cpp7
-rw-r--r--src/wallet/migrate.cpp784
-rw-r--r--src/wallet/migrate.h124
-rw-r--r--src/wallet/rpc/addresses.cpp18
-rw-r--r--src/wallet/rpc/backup.cpp27
-rw-r--r--src/wallet/rpc/coins.cpp8
-rw-r--r--src/wallet/rpc/spend.cpp28
-rw-r--r--src/wallet/rpc/transactions.cpp22
-rw-r--r--src/wallet/rpc/util.cpp4
-rw-r--r--src/wallet/rpc/wallet.cpp14
-rw-r--r--src/wallet/spend.cpp8
-rw-r--r--src/wallet/sqlite.cpp4
-rw-r--r--src/wallet/test/coinselector_tests.cpp79
-rw-r--r--src/wallet/test/db_tests.cpp44
-rw-r--r--src/wallet/test/fuzz/coinselection.cpp4
-rw-r--r--src/wallet/test/fuzz/scriptpubkeyman.cpp16
-rw-r--r--src/wallet/test/fuzz/wallet_bdb_parser.cpp135
-rw-r--r--src/wallet/test/util.cpp5
-rw-r--r--src/wallet/test/util.h4
-rw-r--r--src/wallet/test/wallet_tests.cpp10
-rw-r--r--src/wallet/wallet.cpp9
-rw-r--r--src/wallet/walletdb.cpp14
-rw-r--r--src/wallet/wallettool.cpp9
30 files changed, 1301 insertions, 206 deletions
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index 38cca32f80..d82d8d4513 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -65,6 +65,8 @@ RecursiveMutex cs_db;
std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment.
} // namespace
+static constexpr auto REVERSE_BYTE_ORDER{std::endian::native == std::endian::little ? 4321 : 1234};
+
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
{
return memcmp(value, &rhs.value, sizeof(value)) == 0;
@@ -300,7 +302,11 @@ static Span<const std::byte> SpanFromDbt(const SafeDbt& dbt)
}
BerkeleyDatabase::BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, fs::path filename, const DatabaseOptions& options) :
- WalletDatabase(), env(std::move(env)), m_filename(std::move(filename)), m_max_log_mb(options.max_log_mb)
+ WalletDatabase(),
+ env(std::move(env)),
+ m_byteswap(options.require_format == DatabaseFormat::BERKELEY_SWAP),
+ m_filename(std::move(filename)),
+ m_max_log_mb(options.max_log_mb)
{
auto inserted = this->env->m_databases.emplace(m_filename, std::ref(*this));
assert(inserted.second);
@@ -389,6 +395,10 @@ void BerkeleyDatabase::Open()
}
}
+ if (m_byteswap) {
+ pdb_temp->set_lorder(REVERSE_BYTE_ORDER);
+ }
+
ret = pdb_temp->open(nullptr, // Txn pointer
fMockDb ? nullptr : strFile.c_str(), // Filename
fMockDb ? strFile.c_str() : "main", // Logical db name
@@ -521,6 +531,10 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip)
BerkeleyBatch db(*this, true);
std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
+ if (m_byteswap) {
+ pdbCopy->set_lorder(REVERSE_BYTE_ORDER);
+ }
+
int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index 630630ebe0..af0c78f0d9 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -147,6 +147,9 @@ public:
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
std::unique_ptr<Db> m_db;
+ // Whether to byteswap
+ bool m_byteswap;
+
fs::path m_filename;
int64_t m_max_log_mb;
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 42615b5d42..f1706b6800 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -194,7 +194,7 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
for (const size_t& i : best_selection) {
result.AddInput(utxo_pool.at(i));
}
- result.ComputeAndSetWaste(cost_of_change, cost_of_change, CAmount{0});
+ result.RecalculateWaste(cost_of_change, cost_of_change, CAmount{0});
assert(best_waste == result.GetWaste());
return result;
@@ -792,35 +792,6 @@ void OutputGroupTypeMap::Push(const OutputGroup& group, OutputType type, bool in
}
}
-CAmount SelectionResult::GetSelectionWaste(CAmount change_cost, CAmount target, bool use_effective_value)
-{
- // This function should not be called with empty inputs as that would mean the selection failed
- assert(!m_selected_inputs.empty());
-
- // Always consider the cost of spending an input now vs in the future.
- CAmount waste = 0;
- for (const auto& coin_ptr : m_selected_inputs) {
- const COutput& coin = *coin_ptr;
- waste += coin.GetFee() - coin.long_term_fee;
- }
- // Bump fee of whole selection may diverge from sum of individual bump fees
- waste -= bump_fee_group_discount;
-
- if (change_cost) {
- // Consider the cost of making change and spending it in the future
- // If we aren't making change, the caller should've set change_cost to 0
- assert(change_cost > 0);
- waste += change_cost;
- } else {
- // When we are not making change (change_cost == 0), consider the excess we are throwing away to fees
- CAmount selected_effective_value = use_effective_value ? GetSelectedEffectiveValue() : GetSelectedValue();
- assert(selected_effective_value >= target);
- waste += selected_effective_value - target;
- }
-
- return waste;
-}
-
CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng)
{
if (payment_value <= CHANGE_LOWER / 2) {
@@ -839,16 +810,32 @@ void SelectionResult::SetBumpFeeDiscount(const CAmount discount)
bump_fee_group_discount = discount;
}
-
-void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
+void SelectionResult::RecalculateWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
{
- const CAmount change = GetChange(min_viable_change, change_fee);
+ // This function should not be called with empty inputs as that would mean the selection failed
+ assert(!m_selected_inputs.empty());
+
+ // Always consider the cost of spending an input now vs in the future.
+ CAmount waste = 0;
+ for (const auto& coin_ptr : m_selected_inputs) {
+ const COutput& coin = *coin_ptr;
+ waste += coin.GetFee() - coin.long_term_fee;
+ }
+ // Bump fee of whole selection may diverge from sum of individual bump fees
+ waste -= bump_fee_group_discount;
- if (change > 0) {
- m_waste = GetSelectionWaste(change_cost, m_target, m_use_effective);
+ if (GetChange(min_viable_change, change_fee)) {
+ // if we have a minimum viable amount after deducting fees, account for
+ // cost of creating and spending change
+ waste += change_cost;
} else {
- m_waste = GetSelectionWaste(0, m_target, m_use_effective);
+ // When we are not making change (GetChange(…) == 0), consider the excess we are throwing away to fees
+ CAmount selected_effective_value = m_use_effective ? GetSelectedEffectiveValue() : GetSelectedValue();
+ assert(selected_effective_value >= m_target);
+ waste += selected_effective_value - m_target;
}
+
+ m_waste = waste;
}
void SelectionResult::SetAlgoCompleted(bool algo_completed)
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 80c92e1b0e..9fb000422c 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -350,22 +350,6 @@ private:
}
}
- /** Compute the waste for this result given the cost of change
- * and the opportunity cost of spending these inputs now vs in the future.
- * If change exists, waste = change_cost + inputs * (effective_feerate - long_term_feerate)
- * If no change, waste = excess + inputs * (effective_feerate - long_term_feerate)
- * where excess = selected_effective_value - target
- * change_cost = effective_feerate * change_output_size + long_term_feerate * change_spend_size
- *
- * @param[in] change_cost The cost of creating change and spending it in the future.
- * Only used if there is change, in which case it must be positive.
- * Must be 0 if there is no change.
- * @param[in] target The amount targeted by the coin selection algorithm.
- * @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
- * @return The waste
- */
- [[nodiscard]] CAmount GetSelectionWaste(CAmount change_cost, CAmount target, bool use_effective_value = true);
-
public:
explicit SelectionResult(const CAmount target, SelectionAlgorithm algo)
: m_target(target), m_algo(algo) {}
@@ -387,8 +371,19 @@ public:
/** How much individual inputs overestimated the bump fees for shared ancestries */
void SetBumpFeeDiscount(const CAmount discount);
- /** Calculates and stores the waste for this selection via GetSelectionWaste */
- void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
+ /** Calculates and stores the waste for this result given the cost of change
+ * and the opportunity cost of spending these inputs now vs in the future.
+ * If change exists, waste = change_cost + inputs * (effective_feerate - long_term_feerate) - bump_fee_group_discount
+ * If no change, waste = excess + inputs * (effective_feerate - long_term_feerate) - bump_fee_group_discount
+ * where excess = selected_effective_value - target
+ * change_cost = effective_feerate * change_output_size + long_term_feerate * change_spend_size
+ *
+ * @param[in] min_viable_change The minimum amount necessary to make a change output economic
+ * @param[in] change_cost The cost of creating a change output and spending it in the future. Only
+ * used if there is change, in which case it must be non-negative.
+ * @param[in] change_fee The fee for creating a change output
+ */
+ void RecalculateWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
[[nodiscard]] CAmount GetWaste() const;
/** Tracks that algorithm was able to exhaustively search the entire combination space before hitting limit of tries */
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index ea06767e9b..a5a5f8ec6f 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -16,6 +16,9 @@
#include <vector>
namespace wallet {
+bool operator<(BytePrefix a, Span<const std::byte> b) { return a.prefix < b.subspan(0, std::min(a.prefix.size(), b.size())); }
+bool operator<(Span<const std::byte> a, BytePrefix b) { return a.subspan(0, std::min(a.size(), b.prefix.size())) < b.prefix; }
+
std::vector<fs::path> ListDatabases(const fs::path& wallet_dir)
{
std::vector<fs::path> paths;
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 084fcadc24..b45076d10c 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -20,6 +20,12 @@ class ArgsManager;
struct bilingual_str;
namespace wallet {
+// BytePrefix compares equality with other byte spans that begin with the same prefix.
+struct BytePrefix {
+ Span<const std::byte> prefix;
+};
+bool operator<(BytePrefix a, Span<const std::byte> b);
+bool operator<(Span<const std::byte> a, BytePrefix b);
class DatabaseCursor
{
@@ -177,6 +183,8 @@ public:
enum class DatabaseFormat {
BERKELEY,
SQLITE,
+ BERKELEY_RO,
+ BERKELEY_SWAP,
};
struct DatabaseOptions {
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
index 7a36910dc1..db2756e0ca 100644
--- a/src/wallet/dump.cpp
+++ b/src/wallet/dump.cpp
@@ -60,7 +60,13 @@ bool DumpWallet(const ArgsManager& args, WalletDatabase& db, bilingual_str& erro
hasher << Span{line};
// Write out the file format
- line = strprintf("%s,%s\n", "format", db.Format());
+ std::string format = db.Format();
+ // BDB files that are opened using BerkeleyRODatabase have it's format as "bdb_ro"
+ // We want to override that format back to "bdb"
+ if (format == "bdb_ro") {
+ format = "bdb";
+ }
+ line = strprintf("%s,%s\n", "format", format);
dump_file.write(line.data(), line.size());
hasher << Span{line};
@@ -180,6 +186,8 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::
data_format = DatabaseFormat::BERKELEY;
} else if (file_format == "sqlite") {
data_format = DatabaseFormat::SQLITE;
+ } else if (file_format == "bdb_swap") {
+ data_format = DatabaseFormat::BERKELEY_SWAP;
} else {
error = strprintf(_("Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or \"sqlite\"."), file_format);
return false;
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index f151fad740..14d22bb54e 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -3,9 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <common/args.h>
#include <init.h>
@@ -87,8 +85,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DatabaseOptions().max_log_mb), 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)", !DatabaseOptions().use_shared_memory), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
+ argsman.AddArg("-swapbdbendian", "Swaps the internal endianness of BDB wallet databases (default: false)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST);
#else
- argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb"});
+ argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb", "-swapbdbendian"});
#endif
#ifdef USE_SQLITE
diff --git a/src/wallet/migrate.cpp b/src/wallet/migrate.cpp
new file mode 100644
index 0000000000..09254a76ad
--- /dev/null
+++ b/src/wallet/migrate.cpp
@@ -0,0 +1,784 @@
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <compat/byteswap.h>
+#include <crypto/common.h> // For ReadBE32
+#include <logging.h>
+#include <streams.h>
+#include <util/translation.h>
+#include <wallet/migrate.h>
+
+#include <optional>
+#include <variant>
+
+namespace wallet {
+// Magic bytes in both endianness's
+constexpr uint32_t BTREE_MAGIC = 0x00053162; // If the file endianness matches our system, we see this magic
+constexpr uint32_t BTREE_MAGIC_OE = 0x62310500; // If the file endianness is the other one, we will see this magic
+
+// Subdatabase name
+static const std::vector<std::byte> SUBDATABASE_NAME = {std::byte{'m'}, std::byte{'a'}, std::byte{'i'}, std::byte{'n'}};
+
+enum class PageType : uint8_t {
+ /*
+ * BDB has several page types, most of which we do not use
+ * They are listed here for completeness, but commented out
+ * to avoid opening something unintended.
+ INVALID = 0, // Invalid page type
+ DUPLICATE = 1, // Duplicate. Deprecated and no longer used
+ HASH_UNSORTED = 2, // Hash pages. Deprecated.
+ RECNO_INTERNAL = 4, // Recno internal
+ RECNO_LEAF = 6, // Recno leaf
+ HASH_META = 8, // Hash metadata
+ QUEUE_META = 10, // Queue Metadata
+ QUEUE_DATA = 11, // Queue Data
+ DUPLICATE_LEAF = 12, // Off-page duplicate leaf
+ HASH_SORTED = 13, // Sorted hash page
+ */
+ BTREE_INTERNAL = 3, // BTree internal
+ BTREE_LEAF = 5, // BTree leaf
+ OVERFLOW_DATA = 7, // Overflow
+ BTREE_META = 9, // BTree metadata
+};
+
+enum class RecordType : uint8_t {
+ KEYDATA = 1,
+ // DUPLICATE = 2, Unused as our databases do not support duplicate records
+ OVERFLOW_DATA = 3,
+ DELETE = 0x80, // Indicate this record is deleted. This is OR'd with the real type.
+};
+
+enum class BTreeFlags : uint32_t {
+ /*
+ * BTree databases have feature flags, but we do not use them except for
+ * subdatabases. The unused flags are included for completeness, but commented out
+ * to avoid accidental use.
+ DUP = 1, // Duplicates
+ RECNO = 2, // Recno tree
+ RECNUM = 4, // BTree: Maintain record counts
+ FIXEDLEN = 8, // Recno: fixed length records
+ RENUMBER = 0x10, // Recno: renumber on insert/delete
+ DUPSORT = 0x40, // Duplicates are sorted
+ COMPRESS = 0x80, // Compressed
+ */
+ SUBDB = 0x20, // Subdatabases
+};
+
+/** Berkeley DB BTree metadata page layout */
+class MetaPage
+{
+public:
+ uint32_t lsn_file; // Log Sequence Number file
+ uint32_t lsn_offset; // Log Sequence Number offset
+ uint32_t page_num; // Current page number
+ uint32_t magic; // Magic number
+ uint32_t version; // Version
+ uint32_t pagesize; // Page size
+ uint8_t encrypt_algo; // Encryption algorithm
+ PageType type; // Page type
+ uint8_t metaflags; // Meta-only flags
+ uint8_t unused1; // Unused
+ uint32_t free_list; // Free list page number
+ uint32_t last_page; // Page number of last page in db
+ uint32_t partitions; // Number of partitions
+ uint32_t key_count; // Cached key count
+ uint32_t record_count; // Cached record count
+ BTreeFlags flags; // Flags
+ std::array<std::byte, 20> uid; // 20 byte unique file ID
+ uint32_t unused2; // Unused
+ uint32_t minkey; // Minimum key
+ uint32_t re_len; // Recno: fixed length record length
+ uint32_t re_pad; // Recno: fixed length record pad
+ uint32_t root; // Root page number
+ char unused3[368]; // 92 * 4 bytes of unused space
+ uint32_t crypto_magic; // Crypto magic number
+ char trash[12]; // 3 * 4 bytes of trash space
+ unsigned char iv[20]; // Crypto IV
+ unsigned char chksum[16]; // Checksum
+
+ bool other_endian;
+ uint32_t expected_page_num;
+
+ MetaPage(uint32_t expected_page_num) : expected_page_num(expected_page_num) {}
+ MetaPage() = delete;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ s >> lsn_file;
+ s >> lsn_offset;
+ s >> page_num;
+ s >> magic;
+ s >> version;
+ s >> pagesize;
+ s >> encrypt_algo;
+
+ other_endian = magic == BTREE_MAGIC_OE;
+
+ uint8_t uint8_type;
+ s >> uint8_type;
+ type = static_cast<PageType>(uint8_type);
+
+ s >> metaflags;
+ s >> unused1;
+ s >> free_list;
+ s >> last_page;
+ s >> partitions;
+ s >> key_count;
+ s >> record_count;
+
+ uint32_t uint32_flags;
+ s >> uint32_flags;
+ if (other_endian) {
+ uint32_flags = internal_bswap_32(uint32_flags);
+ }
+ flags = static_cast<BTreeFlags>(uint32_flags);
+
+ s >> uid;
+ s >> unused2;
+ s >> minkey;
+ s >> re_len;
+ s >> re_pad;
+ s >> root;
+ s >> unused3;
+ s >> crypto_magic;
+ s >> trash;
+ s >> iv;
+ s >> chksum;
+
+ if (other_endian) {
+ lsn_file = internal_bswap_32(lsn_file);
+ lsn_offset = internal_bswap_32(lsn_offset);
+ page_num = internal_bswap_32(page_num);
+ magic = internal_bswap_32(magic);
+ version = internal_bswap_32(version);
+ pagesize = internal_bswap_32(pagesize);
+ free_list = internal_bswap_32(free_list);
+ last_page = internal_bswap_32(last_page);
+ partitions = internal_bswap_32(partitions);
+ key_count = internal_bswap_32(key_count);
+ record_count = internal_bswap_32(record_count);
+ unused2 = internal_bswap_32(unused2);
+ minkey = internal_bswap_32(minkey);
+ re_len = internal_bswap_32(re_len);
+ re_pad = internal_bswap_32(re_pad);
+ root = internal_bswap_32(root);
+ crypto_magic = internal_bswap_32(crypto_magic);
+ }
+
+ // Page number must match
+ if (page_num != expected_page_num) {
+ throw std::runtime_error("Meta page number mismatch");
+ }
+
+ // Check magic
+ if (magic != BTREE_MAGIC) {
+ throw std::runtime_error("Not a BDB file");
+ }
+
+ // Only version 9 is supported
+ if (version != 9) {
+ throw std::runtime_error("Unsupported BDB data file version number");
+ }
+
+ // Page size must be 512 <= pagesize <= 64k, and be a power of 2
+ if (pagesize < 512 || pagesize > 65536 || (pagesize & (pagesize - 1)) != 0) {
+ throw std::runtime_error("Bad page size");
+ }
+
+ // Page type must be the btree type
+ if (type != PageType::BTREE_META) {
+ throw std::runtime_error("Unexpected page type, should be 9 (BTree Metadata)");
+ }
+
+ // Only supported meta-flag is subdatabase
+ if (flags != BTreeFlags::SUBDB) {
+ throw std::runtime_error("Unexpected database flags, should only be 0x20 (subdatabases)");
+ }
+ }
+};
+
+/** General class for records in a BDB BTree database. Contains common fields. */
+class RecordHeader
+{
+public:
+ uint16_t len; // Key/data item length
+ RecordType type; // Page type (BDB has this include a DELETE FLAG that we track separately)
+ bool deleted; // Whether the DELETE flag was set on type
+
+ static constexpr size_t SIZE = 3; // The record header is 3 bytes
+
+ bool other_endian;
+
+ RecordHeader(bool other_endian) : other_endian(other_endian) {}
+ RecordHeader() = delete;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ s >> len;
+
+ uint8_t uint8_type;
+ s >> uint8_type;
+ type = static_cast<RecordType>(uint8_type & ~static_cast<uint8_t>(RecordType::DELETE));
+ deleted = uint8_type & static_cast<uint8_t>(RecordType::DELETE);
+
+ if (other_endian) {
+ len = internal_bswap_16(len);
+ }
+ }
+};
+
+/** Class for data in the record directly */
+class DataRecord
+{
+public:
+ DataRecord(const RecordHeader& header) : m_header(header) {}
+ DataRecord() = delete;
+
+ RecordHeader m_header;
+
+ std::vector<std::byte> data; // Variable length key/data item
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ data.resize(m_header.len);
+ s.read(AsWritableBytes(Span(data.data(), data.size())));
+ }
+};
+
+/** Class for records representing internal nodes of the BTree. */
+class InternalRecord
+{
+public:
+ InternalRecord(const RecordHeader& header) : m_header(header) {}
+ InternalRecord() = delete;
+
+ RecordHeader m_header;
+
+ uint8_t unused; // Padding, unused
+ uint32_t page_num; // Page number of referenced page
+ uint32_t records; // Subtree record count
+ std::vector<std::byte> data; // Variable length key item
+
+ static constexpr size_t FIXED_SIZE = 9; // Size of fixed data is 9 bytes
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ s >> unused;
+ s >> page_num;
+ s >> records;
+
+ data.resize(m_header.len);
+ s.read(AsWritableBytes(Span(data.data(), data.size())));
+
+ if (m_header.other_endian) {
+ page_num = internal_bswap_32(page_num);
+ records = internal_bswap_32(records);
+ }
+ }
+};
+
+/** Class for records representing overflow records of the BTree.
+ * Overflow records point to a page which contains the data in the record.
+ * Those pages may point to further pages with the rest of the data if it does not fit
+ * in one page */
+class OverflowRecord
+{
+public:
+ OverflowRecord(const RecordHeader& header) : m_header(header) {}
+ OverflowRecord() = delete;
+
+ RecordHeader m_header;
+
+ uint8_t unused2; // Padding, unused
+ uint32_t page_number; // Page number where data begins
+ uint32_t item_len; // Total length of item
+
+ static constexpr size_t SIZE = 9; // Overflow record is always 9 bytes
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ s >> unused2;
+ s >> page_number;
+ s >> item_len;
+
+ if (m_header.other_endian) {
+ page_number = internal_bswap_32(page_number);
+ item_len = internal_bswap_32(item_len);
+ }
+ }
+};
+
+/** A generic data page in the database. Contains fields common to all data pages. */
+class PageHeader
+{
+public:
+ uint32_t lsn_file; // Log Sequence Number file
+ uint32_t lsn_offset; // Log Sequence Number offset
+ uint32_t page_num; // Current page number
+ uint32_t prev_page; // Previous page number
+ uint32_t next_page; // Next page number
+ uint16_t entries; // Number of items on the page
+ uint16_t hf_offset; // High free byte page offset
+ uint8_t level; // Btree page level
+ PageType type; // Page type
+
+ static constexpr int64_t SIZE = 26; // The header is 26 bytes
+
+ uint32_t expected_page_num;
+ bool other_endian;
+
+ PageHeader(uint32_t page_num, bool other_endian) : expected_page_num(page_num), other_endian(other_endian) {}
+ PageHeader() = delete;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ s >> lsn_file;
+ s >> lsn_offset;
+ s >> page_num;
+ s >> prev_page;
+ s >> next_page;
+ s >> entries;
+ s >> hf_offset;
+ s >> level;
+
+ uint8_t uint8_type;
+ s >> uint8_type;
+ type = static_cast<PageType>(uint8_type);
+
+ if (other_endian) {
+ lsn_file = internal_bswap_32(lsn_file);
+ lsn_offset = internal_bswap_32(lsn_offset);
+ page_num = internal_bswap_32(page_num);
+ prev_page = internal_bswap_32(prev_page);
+ next_page = internal_bswap_32(next_page);
+ entries = internal_bswap_16(entries);
+ hf_offset = internal_bswap_16(hf_offset);
+ }
+
+ if (expected_page_num != page_num) {
+ throw std::runtime_error("Page number mismatch");
+ }
+ if ((type != PageType::OVERFLOW_DATA && level < 1) || (type == PageType::OVERFLOW_DATA && level != 0)) {
+ throw std::runtime_error("Bad btree level");
+ }
+ }
+};
+
+/** A page of records in the database */
+class RecordsPage
+{
+public:
+ RecordsPage(const PageHeader& header) : m_header(header) {}
+ RecordsPage() = delete;
+
+ PageHeader m_header;
+
+ std::vector<uint16_t> indexes;
+ std::vector<std::variant<DataRecord, OverflowRecord>> records;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ // Current position within the page
+ int64_t pos = PageHeader::SIZE;
+
+ // Get the items
+ for (uint32_t i = 0; i < m_header.entries; ++i) {
+ // Get the index
+ uint16_t index;
+ s >> index;
+ if (m_header.other_endian) {
+ index = internal_bswap_16(index);
+ }
+ indexes.push_back(index);
+ pos += sizeof(uint16_t);
+
+ // Go to the offset from the index
+ int64_t to_jump = index - pos;
+ if (to_jump < 0) {
+ throw std::runtime_error("Data record position not in page");
+ }
+ s.ignore(to_jump);
+
+ // Read the record
+ RecordHeader rec_hdr(m_header.other_endian);
+ s >> rec_hdr;
+ to_jump += RecordHeader::SIZE;
+
+ switch (rec_hdr.type) {
+ case RecordType::KEYDATA: {
+ DataRecord record(rec_hdr);
+ s >> record;
+ records.emplace_back(record);
+ to_jump += rec_hdr.len;
+ break;
+ }
+ case RecordType::OVERFLOW_DATA: {
+ OverflowRecord record(rec_hdr);
+ s >> record;
+ records.emplace_back(record);
+ to_jump += OverflowRecord::SIZE;
+ break;
+ }
+ default:
+ throw std::runtime_error("Unknown record type in records page");
+ }
+
+ // Go back to the indexes
+ s.seek(-to_jump, SEEK_CUR);
+ }
+ }
+};
+
+/** A page containing overflow data */
+class OverflowPage
+{
+public:
+ OverflowPage(const PageHeader& header) : m_header(header) {}
+ OverflowPage() = delete;
+
+ PageHeader m_header;
+
+ // BDB overloads some page fields to store overflow page data
+ // hf_offset contains the length of the overflow data stored on this page
+ // entries contains a reference count for references to this item
+
+ // The overflow data itself. Begins immediately following header
+ std::vector<std::byte> data;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ data.resize(m_header.hf_offset);
+ s.read(AsWritableBytes(Span(data.data(), data.size())));
+ }
+};
+
+/** A page of records in the database */
+class InternalPage
+{
+public:
+ InternalPage(const PageHeader& header) : m_header(header) {}
+ InternalPage() = delete;
+
+ PageHeader m_header;
+
+ std::vector<uint16_t> indexes;
+ std::vector<InternalRecord> records;
+
+ template <typename Stream>
+ void Unserialize(Stream& s)
+ {
+ // Current position within the page
+ int64_t pos = PageHeader::SIZE;
+
+ // Get the items
+ for (uint32_t i = 0; i < m_header.entries; ++i) {
+ // Get the index
+ uint16_t index;
+ s >> index;
+ if (m_header.other_endian) {
+ index = internal_bswap_16(index);
+ }
+ indexes.push_back(index);
+ pos += sizeof(uint16_t);
+
+ // Go to the offset from the index
+ int64_t to_jump = index - pos;
+ if (to_jump < 0) {
+ throw std::runtime_error("Internal record position not in page");
+ }
+ s.ignore(to_jump);
+
+ // Read the record
+ RecordHeader rec_hdr(m_header.other_endian);
+ s >> rec_hdr;
+ to_jump += RecordHeader::SIZE;
+
+ if (rec_hdr.type != RecordType::KEYDATA) {
+ throw std::runtime_error("Unknown record type in internal page");
+ }
+ InternalRecord record(rec_hdr);
+ s >> record;
+ records.emplace_back(record);
+ to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;
+
+ // Go back to the indexes
+ s.seek(-to_jump, SEEK_CUR);
+ }
+ }
+};
+
+static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)
+{
+ int64_t pos = int64_t{page_num} * page_size;
+ s.seek(pos, SEEK_SET);
+}
+
+void BerkeleyRODatabase::Open()
+{
+ // Open the file
+ FILE* file = fsbridge::fopen(m_filepath, "rb");
+ AutoFile db_file(file);
+ if (db_file.IsNull()) {
+ throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");
+ }
+
+ uint32_t page_size = 4096; // Default page size
+
+ // Read the outer metapage
+ // Expected page number is 0
+ MetaPage outer_meta(0);
+ db_file >> outer_meta;
+ page_size = outer_meta.pagesize;
+
+ // Verify the size of the file is a multiple of the page size
+ db_file.seek(0, SEEK_END);
+ int64_t size = db_file.tell();
+
+ // Since BDB stores everything in a page, the file size should be a multiple of the page size;
+ // However, BDB doesn't actually check that this is the case, and enforcing this check results
+ // in us rejecting a database that BDB would not, so this check needs to be excluded.
+ // This is left commented out as a reminder to not accidentally implement this in the future.
+ // if (size % page_size != 0) {
+ // throw std::runtime_error("File size is not a multiple of page size");
+ // }
+
+ // Check the last page number
+ uint32_t expected_last_page = (size / page_size) - 1;
+ if (outer_meta.last_page != expected_last_page) {
+ throw std::runtime_error("Last page number could not fit in file");
+ }
+
+ // Make sure encryption is disabled
+ if (outer_meta.encrypt_algo != 0) {
+ throw std::runtime_error("BDB builtin encryption is not supported");
+ }
+
+ // Check all Log Sequence Numbers (LSN) point to file 0 and offset 1 which indicates that the LSNs were
+ // reset and that the log files are not necessary to get all of the data in the database.
+ for (uint32_t i = 0; i < outer_meta.last_page; ++i) {
+ // The LSN is composed of 2 32-bit ints, the first is a file id, the second an offset
+ // It will always be the first 8 bytes of a page, so we deserialize it directly for every page
+ uint32_t file;
+ uint32_t offset;
+ SeekToPage(db_file, i, page_size);
+ db_file >> file >> offset;
+ if (outer_meta.other_endian) {
+ file = internal_bswap_32(file);
+ offset = internal_bswap_32(offset);
+ }
+ if (file != 0 || offset != 1) {
+ throw std::runtime_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support");
+ }
+ }
+
+ // Read the root page
+ SeekToPage(db_file, outer_meta.root, page_size);
+ PageHeader header(outer_meta.root, outer_meta.other_endian);
+ db_file >> header;
+ if (header.type != PageType::BTREE_LEAF) {
+ throw std::runtime_error("Unexpected outer database root page type");
+ }
+ if (header.entries != 2) {
+ throw std::runtime_error("Unexpected number of entries in outer database root page");
+ }
+ RecordsPage page(header);
+ db_file >> page;
+
+ // First record should be the string "main"
+ if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {
+ throw std::runtime_error("Subdatabase has an unexpected name");
+ }
+ // Check length of page number for subdatabase location
+ if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {
+ throw std::runtime_error("Subdatabase page number has unexpected length");
+ }
+
+ // Read subdatabase page number
+ // It is written as a big endian 32 bit number
+ uint32_t main_db_page = ReadBE32(UCharCast(std::get<DataRecord>(page.records.at(1)).data.data()));
+
+ // The main database is in a page that doesn't exist
+ if (main_db_page > outer_meta.last_page) {
+ throw std::runtime_error("Page number is greater than database last page");
+ }
+
+ // Read the inner metapage
+ SeekToPage(db_file, main_db_page, page_size);
+ MetaPage inner_meta(main_db_page);
+ db_file >> inner_meta;
+
+ if (inner_meta.pagesize != page_size) {
+ throw std::runtime_error("Unexpected page size");
+ }
+
+ if (inner_meta.last_page > outer_meta.last_page) {
+ throw std::runtime_error("Subdatabase last page is greater than database last page");
+ }
+
+ // Make sure encryption is disabled
+ if (inner_meta.encrypt_algo != 0) {
+ throw std::runtime_error("BDB builtin encryption is not supported");
+ }
+
+ // Do a DFS through the BTree, starting at root
+ std::vector<uint32_t> pages{inner_meta.root};
+ while (pages.size() > 0) {
+ uint32_t curr_page = pages.back();
+ // It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
+ // last page. While we should be checking this, we can't.
+ // This is left commented out as a reminder to not accidentally implement this in the future.
+ // if (curr_page > inner_meta.last_page) {
+ // throw std::runtime_error("Page number is greater than subdatabase last page");
+ // }
+ pages.pop_back();
+ SeekToPage(db_file, curr_page, page_size);
+ PageHeader header(curr_page, inner_meta.other_endian);
+ db_file >> header;
+ switch (header.type) {
+ case PageType::BTREE_INTERNAL: {
+ InternalPage int_page(header);
+ db_file >> int_page;
+ for (const InternalRecord& rec : int_page.records) {
+ if (rec.m_header.deleted) continue;
+ pages.push_back(rec.page_num);
+ }
+ break;
+ }
+ case PageType::BTREE_LEAF: {
+ RecordsPage rec_page(header);
+ db_file >> rec_page;
+ if (rec_page.records.size() % 2 != 0) {
+ // BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected
+ throw std::runtime_error("Records page has odd number of records");
+ }
+ bool is_key = true;
+ std::vector<std::byte> key;
+ for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
+ std::vector<std::byte> data;
+ if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
+ if (drec->m_header.deleted) continue;
+ data = drec->data;
+ } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
+ if (orec->m_header.deleted) continue;
+ uint32_t next_page = orec->page_number;
+ while (next_page != 0) {
+ SeekToPage(db_file, next_page, page_size);
+ PageHeader opage_header(next_page, inner_meta.other_endian);
+ db_file >> opage_header;
+ if (opage_header.type != PageType::OVERFLOW_DATA) {
+ throw std::runtime_error("Bad overflow record page type");
+ }
+ OverflowPage opage(opage_header);
+ db_file >> opage;
+ data.insert(data.end(), opage.data.begin(), opage.data.end());
+ next_page = opage_header.next_page;
+ }
+ }
+
+ if (is_key) {
+ key = data;
+ } else {
+ m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
+ key.clear();
+ }
+ is_key = !is_key;
+ }
+ break;
+ }
+ default:
+ throw std::runtime_error("Unexpected page type");
+ }
+ }
+}
+
+std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch(bool flush_on_close)
+{
+ return std::make_unique<BerkeleyROBatch>(*this);
+}
+
+bool BerkeleyRODatabase::Backup(const std::string& dest) const
+{
+ fs::path src(m_filepath);
+ fs::path dst(fs::PathFromString(dest));
+
+ if (fs::is_directory(dst)) {
+ dst = BDBDataFile(dst);
+ }
+ try {
+ if (fs::exists(dst) && fs::equivalent(src, dst)) {
+ LogPrintf("cannot backup to wallet source file %s\n", fs::PathToString(dst));
+ return false;
+ }
+
+ fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
+ LogPrintf("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
+ return true;
+ } catch (const fs::filesystem_error& e) {
+ LogPrintf("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), fsbridge::get_filesystem_error_message(e));
+ return false;
+ }
+}
+
+bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
+{
+ SerializeData key_data{key.begin(), key.end()};
+ const auto it{m_database.m_records.find(key_data)};
+ if (it == m_database.m_records.end()) {
+ return false;
+ }
+ auto val = it->second;
+ value.clear();
+ value.write(Span(val));
+ return true;
+}
+
+bool BerkeleyROBatch::HasKey(DataStream&& key)
+{
+ SerializeData key_data{key.begin(), key.end()};
+ return m_database.m_records.count(key_data) > 0;
+}
+
+BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, Span<const std::byte> prefix)
+ : m_database(database)
+{
+ std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
+}
+
+DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
+{
+ if (m_cursor == m_cursor_end) {
+ return DatabaseCursor::Status::DONE;
+ }
+ ssKey.write(Span(m_cursor->first));
+ ssValue.write(Span(m_cursor->second));
+ m_cursor++;
+ return DatabaseCursor::Status::MORE;
+}
+
+std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(Span<const std::byte> prefix)
+{
+ return std::make_unique<BerkeleyROCursor>(m_database, prefix);
+}
+
+std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
+{
+ fs::path data_file = BDBDataFile(path);
+ try {
+ std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
+ status = DatabaseStatus::SUCCESS;
+ return db;
+ } catch (const std::runtime_error& e) {
+ error.original = e.what();
+ status = DatabaseStatus::FAILED_LOAD;
+ return nullptr;
+ }
+}
+} // namespace wallet
diff --git a/src/wallet/migrate.h b/src/wallet/migrate.h
new file mode 100644
index 0000000000..e4826450af
--- /dev/null
+++ b/src/wallet/migrate.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2021 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_MIGRATE_H
+#define BITCOIN_WALLET_MIGRATE_H
+
+#include <wallet/db.h>
+
+#include <optional>
+
+namespace wallet {
+
+using BerkeleyROData = std::map<SerializeData, SerializeData, std::less<>>;
+
+/**
+ * A class representing a BerkeleyDB file from which we can only read records.
+ * This is used only for migration of legacy to descriptor wallets
+ */
+class BerkeleyRODatabase : public WalletDatabase
+{
+private:
+ const fs::path m_filepath;
+
+public:
+ /** Create DB handle */
+ BerkeleyRODatabase(const fs::path& filepath, bool open = true) : WalletDatabase(), m_filepath(filepath)
+ {
+ if (open) Open();
+ }
+ ~BerkeleyRODatabase(){};
+
+ BerkeleyROData m_records;
+
+ /** Open the database if it is not already opened. */
+ void Open() override;
+
+ /** Indicate the a new database user has began using the database. Increments m_refcount */
+ void AddRef() override {}
+ /** Indicate that database user has stopped using the database and that it could be flushed or closed. Decrement m_refcount */
+ void RemoveRef() override {}
+
+ /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
+ */
+ bool Rewrite(const char* pszSkip = nullptr) override { return false; }
+
+ /** Back up the entire database to a file.
+ */
+ bool Backup(const std::string& strDest) const override;
+
+ /** Make sure all changes are flushed to database file.
+ */
+ void Flush() override {}
+ /** Flush to the database file and close the database.
+ * Also close the environment if no other databases are open in it.
+ */
+ void Close() override {}
+ /* flush the wallet passively (TRY_LOCK)
+ ideal to be called periodically */
+ bool PeriodicFlush() override { return false; }
+
+ void IncrementUpdateCounter() override {}
+
+ void ReloadDbEnv() override {}
+
+ /** Return path to main database file for logs and error messages. */
+ std::string Filename() override { return fs::PathToString(m_filepath); }
+
+ std::string Format() override { return "bdb_ro"; }
+
+ /** Make a DatabaseBatch connected to this database */
+ std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override;
+};
+
+class BerkeleyROCursor : public DatabaseCursor
+{
+private:
+ const BerkeleyRODatabase& m_database;
+ BerkeleyROData::const_iterator m_cursor;
+ BerkeleyROData::const_iterator m_cursor_end;
+
+public:
+ explicit BerkeleyROCursor(const BerkeleyRODatabase& database, Span<const std::byte> prefix = {});
+ ~BerkeleyROCursor() {}
+
+ Status Next(DataStream& key, DataStream& value) override;
+};
+
+/** RAII class that provides access to a BerkeleyRODatabase */
+class BerkeleyROBatch : public DatabaseBatch
+{
+private:
+ const BerkeleyRODatabase& m_database;
+
+ bool ReadKey(DataStream&& key, DataStream& value) override;
+ // WriteKey returns true since various automatic upgrades for older wallets will expect writing to not fail.
+ // It is okay for this batch type to not actually write anything as those automatic upgrades will occur again after migration.
+ bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override { return true; }
+ bool EraseKey(DataStream&& key) override { return false; }
+ bool HasKey(DataStream&& key) override;
+ bool ErasePrefix(Span<const std::byte> prefix) override { return false; }
+
+public:
+ explicit BerkeleyROBatch(const BerkeleyRODatabase& database) : m_database(database) {}
+ ~BerkeleyROBatch() {}
+
+ BerkeleyROBatch(const BerkeleyROBatch&) = delete;
+ BerkeleyROBatch& operator=(const BerkeleyROBatch&) = delete;
+
+ void Flush() override {}
+ void Close() override {}
+
+ std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<BerkeleyROCursor>(m_database); }
+ std::unique_ptr<DatabaseCursor> GetNewPrefixCursor(Span<const std::byte> prefix) override;
+ bool TxnBegin() override { return false; }
+ bool TxnCommit() override { return false; }
+ bool TxnAbort() override { return false; }
+};
+
+//! Return object giving access to Berkeley Read Only database at specified path.
+std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error);
+} // namespace wallet
+
+#endif // BITCOIN_WALLET_MIGRATE_H
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index bed9ec029a..17bb6320a1 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -2,9 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <core_io.h>
#include <key_io.h>
@@ -208,9 +206,9 @@ RPCHelpMan listaddressgroupings()
addressInfo.push_back(address_book_entry->GetLabel());
}
}
- jsonGrouping.push_back(addressInfo);
+ jsonGrouping.push_back(std::move(addressInfo));
}
- jsonGroupings.push_back(jsonGrouping);
+ jsonGroupings.push_back(std::move(jsonGrouping));
}
return jsonGroupings;
},
@@ -409,9 +407,9 @@ public:
// Only when the script corresponds to an address.
UniValue subobj(UniValue::VOBJ);
UniValue detail = DescribeAddress(embedded);
- subobj.pushKVs(detail);
+ subobj.pushKVs(std::move(detail));
UniValue wallet_detail = std::visit(*this, embedded);
- subobj.pushKVs(wallet_detail);
+ subobj.pushKVs(std::move(wallet_detail));
subobj.pushKV("address", EncodeDestination(embedded));
subobj.pushKV("scriptPubKey", HexStr(subscript));
// Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
@@ -492,7 +490,7 @@ static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestinatio
CScript script = GetScriptForDestination(dest);
std::unique_ptr<SigningProvider> provider = nullptr;
provider = wallet.GetSolvingProvider(script);
- ret.pushKVs(detail);
+ ret.pushKVs(std::move(detail));
ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
return ret;
}
@@ -609,7 +607,7 @@ RPCHelpMan getaddressinfo()
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
UniValue detail = DescribeWalletAddress(*pwallet, dest);
- ret.pushKVs(detail);
+ ret.pushKVs(std::move(detail));
ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
@@ -690,7 +688,7 @@ RPCHelpMan getaddressesbylabel()
// which currently is O(1).
UniValue value(UniValue::VOBJ);
value.pushKV("purpose", _purpose ? PurposeToString(*_purpose) : "unknown");
- ret.pushKVEnd(address, value);
+ ret.pushKVEnd(address, std::move(value));
}
});
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index ae2dfe5795..a76ae7196c 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -2,9 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <chain.h>
#include <clientversion.h>
@@ -458,12 +456,7 @@ RPCHelpMan importpubkey()
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
- if (!IsHex(request.params[0].get_str()))
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
- std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
- CPubKey pubKey(data);
- if (!pubKey.IsFullyValid())
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
+ CPubKey pubKey = HexToPubKey(request.params[0].get_str());
{
LOCK(pwallet->cs_wallet);
@@ -985,15 +978,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
}
for (size_t i = 0; i < pubKeys.size(); ++i) {
- const auto& str = pubKeys[i].get_str();
- if (!IsHex(str)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string");
- }
- auto parsed_pubkey = ParseHex(str);
- CPubKey pubkey(parsed_pubkey);
- if (!pubkey.IsFullyValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
- }
+ CPubKey pubkey = HexToPubKey(pubKeys[i].get_str());
pubkey_map.emplace(pubkey.GetID(), pubkey);
ordered_pubkeys.push_back(pubkey.GetID());
}
@@ -1842,16 +1827,16 @@ RPCHelpMan listdescriptors()
UniValue range(UniValue::VARR);
range.push_back(info.range->first);
range.push_back(info.range->second - 1);
- spk.pushKV("range", range);
+ spk.pushKV("range", std::move(range));
spk.pushKV("next", info.next_index);
spk.pushKV("next_index", info.next_index);
}
- descriptors.push_back(spk);
+ descriptors.push_back(std::move(spk));
}
UniValue response(UniValue::VOBJ);
response.pushKV("wallet_name", wallet->GetName());
- response.pushKV("descriptors", descriptors);
+ response.pushKV("descriptors", std::move(descriptors));
return response;
},
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index b6c7396f4b..2cf94a5722 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -416,7 +416,7 @@ RPCHelpMan listlockunspent()
o.pushKV("txid", outpt.hash.GetHex());
o.pushKV("vout", (int)outpt.n);
- ret.push_back(o);
+ ret.push_back(std::move(o));
}
return ret;
@@ -477,7 +477,7 @@ RPCHelpMan getbalances()
const auto full_bal = GetBalance(wallet, 0, false);
balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending));
}
- balances.pushKV("mine", balances_mine);
+ balances.pushKV("mine", std::move(balances_mine));
}
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
if (spk_man && spk_man->HaveWatchOnly()) {
@@ -485,7 +485,7 @@ RPCHelpMan getbalances()
balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted));
balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending));
balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
- balances.pushKV("watchonly", balances_watchonly);
+ balances.pushKV("watchonly", std::move(balances_watchonly));
}
AppendLastProcessedBlock(balances, wallet);
@@ -724,7 +724,7 @@ RPCHelpMan listunspent()
PushParentDescriptors(*pwallet, scriptPubKey, entry);
if (avoid_reuse) entry.pushKV("reused", reused);
entry.pushKV("safe", out.safe);
- results.push_back(entry);
+ results.push_back(std::move(entry));
}
return results;
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 6060f017ce..169f72c406 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -627,15 +627,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
const UniValue solving_data = options["solving_data"].get_obj();
if (solving_data.exists("pubkeys")) {
for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) {
- const std::string& pk_str = pk_univ.get_str();
- if (!IsHex(pk_str)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str));
- }
- const std::vector<unsigned char> data(ParseHex(pk_str));
- const CPubKey pubkey(data.begin(), data.end());
- if (!pubkey.IsFullyValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str));
- }
+ const CPubKey pubkey = HexToPubKey(pk_univ.get_str());
coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
// Add witness script for pubkeys
const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
@@ -726,7 +718,7 @@ static void SetOptionsInputWeights(const UniValue& inputs, UniValue& options)
weights.push_back(input);
}
}
- options.pushKV("input_weights", weights);
+ options.pushKV("input_weights", std::move(weights));
}
RPCHelpMan fundrawtransaction()
@@ -1175,7 +1167,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
for (const bilingual_str& error : errors) {
result_errors.push_back(error.original);
}
- result.pushKV("errors", result_errors);
+ result.pushKV("errors", std::move(result_errors));
return result;
},
@@ -1303,7 +1295,7 @@ RPCHelpMan sendall()
{
return RPCHelpMan{"sendall",
"EXPERIMENTAL warning: this call may be changed in future releases.\n"
- "\nSpend the value of all (or specific) confirmed UTXOs in the wallet to one or more recipients.\n"
+ "\nSpend the value of all (or specific) confirmed UTXOs and unconfirmed change in the wallet to one or more recipients.\n"
"Unconfirmed inbound UTXOs and locked UTXOs will not be spent. Sendall will respect the avoid_reuse wallet flag.\n"
"If your wallet contains many small inputs, either because it received tiny payments or as a result of accumulating change, consider using `send_max` to exclude inputs that are worth less than the fees needed to spend them.\n",
{
@@ -1396,7 +1388,7 @@ RPCHelpMan sendall()
if (recipient.isStr()) {
UniValue rkvp(UniValue::VOBJ);
rkvp.pushKV(recipient.get_str(), 0);
- recipient_key_value_pairs.push_back(rkvp);
+ recipient_key_value_pairs.push_back(std::move(rkvp));
addresses_without_amount.insert(recipient.get_str());
} else {
recipient_key_value_pairs.push_back(recipient);
@@ -1478,10 +1470,18 @@ RPCHelpMan sendall()
}
}
+ std::vector<COutPoint> outpoints_spent;
+ outpoints_spent.reserve(rawTx.vin.size());
+
+ for (const CTxIn& tx_in : rawTx.vin) {
+ outpoints_spent.push_back(tx_in.prevout);
+ }
+
// estimate final size of tx
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
- const CAmount effective_value{total_input_value - fee_from_size};
+ const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
+ CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);
if (fee_from_size > pwallet->m_default_max_tx_fee) {
throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original);
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 05b340995d..5abc983701 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -39,11 +39,11 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue
UniValue conflicts(UniValue::VARR);
for (const uint256& conflict : wallet.GetTxConflicts(wtx))
conflicts.push_back(conflict.GetHex());
- entry.pushKV("walletconflicts", conflicts);
+ entry.pushKV("walletconflicts", std::move(conflicts));
UniValue mempool_conflicts(UniValue::VARR);
for (const Txid& mempool_conflict : wtx.mempool_conflicts)
mempool_conflicts.push_back(mempool_conflict.GetHex());
- entry.pushKV("mempoolconflicts", mempool_conflicts);
+ entry.pushKV("mempoolconflicts", std::move(mempool_conflicts));
entry.pushKV("time", wtx.GetTxTime());
entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
@@ -172,8 +172,8 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
transactions.push_back(_item.GetHex());
}
}
- obj.pushKV("txids", transactions);
- ret.push_back(obj);
+ obj.pushKV("txids", std::move(transactions));
+ ret.push_back(std::move(obj));
}
};
@@ -195,7 +195,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
obj.pushKV("label", entry.first);
- ret.push_back(obj);
+ ret.push_back(std::move(obj));
}
}
@@ -353,7 +353,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
if (fLong)
WalletTxToJSON(wallet, wtx, entry);
entry.pushKV("abandoned", wtx.isAbandoned());
- ret.push_back(entry);
+ ret.push_back(std::move(entry));
}
}
@@ -396,7 +396,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
entry.pushKV("abandoned", wtx.isAbandoned());
if (fLong)
WalletTxToJSON(wallet, wtx, entry);
- ret.push_back(entry);
+ ret.push_back(std::move(entry));
}
}
}
@@ -682,8 +682,8 @@ RPCHelpMan listsinceblock()
CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
UniValue ret(UniValue::VOBJ);
- ret.pushKV("transactions", transactions);
- if (include_removed) ret.pushKV("removed", removed);
+ ret.pushKV("transactions", std::move(transactions));
+ if (include_removed) ret.pushKV("removed", std::move(removed));
ret.pushKV("lastblock", lastblock.GetHex());
return ret;
@@ -789,14 +789,14 @@ RPCHelpMan gettransaction()
UniValue details(UniValue::VARR);
ListTransactions(*pwallet, wtx, 0, false, details, filter, /*filter_label=*/std::nullopt);
- entry.pushKV("details", details);
+ entry.pushKV("details", std::move(details));
entry.pushKV("hex", EncodeHexTx(*wtx.tx));
if (verbose) {
UniValue decoded(UniValue::VOBJ);
TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false);
- entry.pushKV("decoded", decoded);
+ entry.pushKV("decoded", std::move(decoded));
}
AppendLastProcessedBlock(entry, *pwallet);
diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
index 1252843e9d..eb23c4555b 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -149,7 +149,7 @@ void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey,
for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
parent_descs.push_back(desc.descriptor->ToString());
}
- entry.pushKV("parent_descs", parent_descs);
+ entry.pushKV("parent_descs", std::move(parent_descs));
}
void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error)
@@ -185,7 +185,7 @@ void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) EXCLUSIVE_
UniValue lastprocessedblock{UniValue::VOBJ};
lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
- entry.pushKV("lastprocessedblock", lastprocessedblock);
+ entry.pushKV("lastprocessedblock", std::move(lastprocessedblock));
}
} // namespace wallet
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index a684d4e191..8c218ad766 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -3,9 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <core_io.h>
#include <key_io.h>
@@ -130,7 +128,7 @@ static RPCHelpMan getwalletinfo()
UniValue scanning(UniValue::VOBJ);
scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
scanning.pushKV("progress", pwallet->ScanningProgress());
- obj.pushKV("scanning", scanning);
+ obj.pushKV("scanning", std::move(scanning));
} else {
obj.pushKV("scanning", false);
}
@@ -174,11 +172,11 @@ static RPCHelpMan listwalletdir()
for (const auto& path : ListDatabases(GetWalletDir())) {
UniValue wallet(UniValue::VOBJ);
wallet.pushKV("name", path.utf8string());
- wallets.push_back(wallet);
+ wallets.push_back(std::move(wallet));
}
UniValue result(UniValue::VOBJ);
- result.pushKV("wallets", wallets);
+ result.pushKV("wallets", std::move(wallets));
return result;
},
};
@@ -397,7 +395,7 @@ static RPCHelpMan createwallet()
if (!request.params[4].isNull() && request.params[4].get_bool()) {
flags |= WALLET_FLAG_AVOID_REUSE;
}
- if (self.Arg<bool>(5)) {
+ if (self.Arg<bool>("descriptors")) {
#ifndef USE_SQLITE
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
#endif
@@ -491,7 +489,7 @@ static RPCHelpMan unloadwallet()
// Release the "main" shared pointer and prevent further notifications.
// Note that any attempt to load the same wallet would fail until the wallet
// is destroyed (see CheckUniqueFileid).
- std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
+ std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")};
if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 9a7e166e68..2322471402 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -711,7 +711,7 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) {
- cg_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
+ cg_result->RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*cg_result);
} else {
append_error(std::move(cg_result));
@@ -746,7 +746,7 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
if (bump_fee_overestimate) {
result.SetBumpFeeDiscount(bump_fee_overestimate);
}
- result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
+ result.RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
}
// Choose the result with the least waste
@@ -771,7 +771,7 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av
if (selection_target <= 0) {
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
result.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
- result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
+ result.RecalculateWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
return result;
}
@@ -792,7 +792,7 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av
SelectionResult preselected(pre_set_inputs.total_amount, SelectionAlgorithm::MANUAL);
preselected.AddInputs(pre_set_inputs.coins, coin_selection_params.m_subtract_fee_outputs);
op_selection_result->Merge(preselected);
- op_selection_result->ComputeAndSetWaste(coin_selection_params.min_viable_change,
+ op_selection_result->RecalculateWaste(coin_selection_params.min_viable_change,
coin_selection_params.m_cost_of_change,
coin_selection_params.m_change_fee);
}
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 34f18bf0b1..5e3a8179a2 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -2,9 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <wallet/sqlite.h>
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 9a349f0992..7bd92b471c 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -874,29 +874,32 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
BOOST_AUTO_TEST_CASE(waste_test)
{
const CAmount fee{100};
+ const CAmount min_viable_change{300};
const CAmount change_cost{125};
+ const CAmount change_fee{30};
const CAmount fee_diff{40};
const CAmount in_amt{3 * COIN};
const CAmount target{2 * COIN};
- const CAmount excess{in_amt - fee * 2 - target};
+ const CAmount excess{80};
+ const CAmount exact_target{in_amt - fee * 2}; // Maximum spendable amount after fees: no change, no excess
- // The following tests that the waste is calculated correctly in various scenarios.
- // ComputeAndSetWaste will first determine the size of the change output. We don't really
- // care about the change and just want to use the variant that always includes the change_cost,
- // so min_viable_change and change_fee are set to 0 to ensure that.
+ // In the following, we test that the waste is calculated correctly in various scenarios.
+ // Usually, RecalculateWaste would compute change_fee and change_cost on basis of the
+ // change output type, current feerate, and discard_feerate, but we use fixed values
+ // across this test to make the test easier to understand.
{
// Waste with change is the change cost and difference between fee and long term fee
SelectionResult selection1{target, SelectionAlgorithm::MANUAL};
- add_coin(1 * COIN, 1, selection1, fee, fee - fee_diff);
+ add_coin(1 * COIN, 1, selection1, /*fee=*/fee, /*long_term_fee=*/fee - fee_diff);
add_coin(2 * COIN, 2, selection1, fee, fee - fee_diff);
- selection1.ComputeAndSetWaste(/*min_viable_change=*/0, change_cost, /*change_fee=*/0);
+ selection1.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(fee_diff * 2 + change_cost, selection1.GetWaste());
// Waste will be greater when fee is greater, but long term fee is the same
SelectionResult selection2{target, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection2, fee * 2, fee - fee_diff);
add_coin(2 * COIN, 2, selection2, fee * 2, fee - fee_diff);
- selection2.ComputeAndSetWaste(/*min_viable_change=*/0, change_cost, /*change_fee=*/0);
+ selection2.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_GT(selection2.GetWaste(), selection1.GetWaste());
// Waste with change is the change cost and difference between fee and long term fee
@@ -904,25 +907,25 @@ BOOST_AUTO_TEST_CASE(waste_test)
SelectionResult selection3{target, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection3, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection3, fee, fee + fee_diff);
- selection3.ComputeAndSetWaste(/*min_viable_change=*/0, change_cost, /*change_fee=*/0);
+ selection3.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(fee_diff * -2 + change_cost, selection3.GetWaste());
BOOST_CHECK_LT(selection3.GetWaste(), selection1.GetWaste());
}
{
// Waste without change is the excess and difference between fee and long term fee
- SelectionResult selection_nochange1{target, SelectionAlgorithm::MANUAL};
+ SelectionResult selection_nochange1{exact_target - excess, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection_nochange1, fee, fee - fee_diff);
add_coin(2 * COIN, 2, selection_nochange1, fee, fee - fee_diff);
- selection_nochange1.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection_nochange1.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(fee_diff * 2 + excess, selection_nochange1.GetWaste());
// Waste without change is the excess and difference between fee and long term fee
// With long term fee greater than fee, waste should be less than when long term fee is less than fee
- SelectionResult selection_nochange2{target, SelectionAlgorithm::MANUAL};
+ SelectionResult selection_nochange2{exact_target - excess, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection_nochange2, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection_nochange2, fee, fee + fee_diff);
- selection_nochange2.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection_nochange2.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(fee_diff * -2 + excess, selection_nochange2.GetWaste());
BOOST_CHECK_LT(selection_nochange2.GetWaste(), selection_nochange1.GetWaste());
}
@@ -932,57 +935,54 @@ BOOST_AUTO_TEST_CASE(waste_test)
SelectionResult selection{target, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection, fee, fee);
add_coin(2 * COIN, 2, selection, fee, fee);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, change_cost, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(change_cost, selection.GetWaste());
}
{
// Waste without change and fee == long term fee is just the excess
- SelectionResult selection{target, SelectionAlgorithm::MANUAL};
+ SelectionResult selection{exact_target - excess, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection, fee, fee);
add_coin(2 * COIN, 2, selection, fee, fee);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(excess, selection.GetWaste());
}
{
- // No Waste when fee == long_term_fee, no change, and no excess
- const CAmount exact_target{in_amt - fee * 2};
+ // Waste is 0 when fee == long_term_fee, no change, and no excess
SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection, fee, fee);
add_coin(2 * COIN, 2, selection, fee, fee);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost , change_fee);
BOOST_CHECK_EQUAL(0, selection.GetWaste());
}
{
- // No Waste when (fee - long_term_fee) == (-cost_of_change), and no excess
+ // Waste is 0 when (fee - long_term_fee) == (-cost_of_change), and no excess
SelectionResult selection{target, SelectionAlgorithm::MANUAL};
- const CAmount new_change_cost{fee_diff * 2};
add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, new_change_cost, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, /*change_cost=*/fee_diff * 2, change_fee);
BOOST_CHECK_EQUAL(0, selection.GetWaste());
}
{
- // No Waste when (fee - long_term_fee) == (-excess), no change cost
- const CAmount new_target{in_amt - fee * 2 - fee_diff * 2};
+ // Waste is 0 when (fee - long_term_fee) == (-excess), no change cost
+ const CAmount new_target{exact_target - /*excess=*/fee_diff * 2};
SelectionResult selection{new_target, SelectionAlgorithm::MANUAL};
add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(0, selection.GetWaste());
}
{
// Negative waste when the long term fee is greater than the current fee and the selected value == target
- const CAmount exact_target{3 * COIN - 2 * fee};
SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, /*change_cost=*/0, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(target_waste1, selection.GetWaste());
}
@@ -990,10 +990,14 @@ BOOST_AUTO_TEST_CASE(waste_test)
// Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
SelectionResult selection{target, SelectionAlgorithm::MANUAL};
const CAmount large_fee_diff{90};
- const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
+ const CAmount target_waste2{-2 * large_fee_diff + change_cost};
+ // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
+ // = (2 * 100) - (2 * (100 + 90)) + 125
+ // = 200 - 380 + 125 = -55
+ assert(target_waste2 == -55);
add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
- selection.ComputeAndSetWaste(/*min_viable_change=*/0, change_cost, /*change_fee=*/0);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
BOOST_CHECK_EQUAL(target_waste2, selection.GetWaste());
}
}
@@ -1018,12 +1022,12 @@ BOOST_AUTO_TEST_CASE(bump_fee_test)
inputs[i]->ApplyBumpFee(20*(i+1));
}
- selection.ComputeAndSetWaste(min_viable_change, change_cost, change_fee);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
CAmount expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60;
BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
selection.SetBumpFeeDiscount(30);
- selection.ComputeAndSetWaste(min_viable_change, change_cost, change_fee);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
expected_waste = fee_diff * -2 + change_cost + /*bump_fees=*/60 - /*group_discount=*/30;
BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
}
@@ -1044,12 +1048,12 @@ BOOST_AUTO_TEST_CASE(bump_fee_test)
inputs[i]->ApplyBumpFee(20*(i+1));
}
- selection.ComputeAndSetWaste(min_viable_change, change_cost, change_fee);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
CAmount expected_waste = fee_diff * -2 + /*bump_fees=*/60 + /*excess = 100 - bump_fees*/40;
BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
selection.SetBumpFeeDiscount(30);
- selection.ComputeAndSetWaste(min_viable_change, change_cost, change_fee);
+ selection.RecalculateWaste(min_viable_change, change_cost, change_fee);
expected_waste = fee_diff * -2 + /*bump_fees=*/60 - /*group_discount=*/30 + /*excess = 100 - bump_fees + group_discount*/70;
BOOST_CHECK_EQUAL(expected_waste, selection.GetWaste());
}
@@ -1429,6 +1433,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
/*avoid_partial=*/false,
};
+ int max_weight = MAX_STANDARD_TX_WEIGHT - WITNESS_SCALE_FACTOR * (cs_params.tx_noinputs_size + cs_params.change_output_size);
{
// Scenario 1:
// The actor starts with 1x 50.0 BTC and 1515x 0.033 BTC (~100.0 BTC total) unspent outputs
@@ -1450,10 +1455,9 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
m_node);
BOOST_CHECK(result);
- // Verify that only the 50 BTC UTXO was selected
- const auto& selection_res = result->GetInputSet();
- BOOST_CHECK(selection_res.size() == 1);
- BOOST_CHECK((*selection_res.begin())->GetEffectiveValue() == 50 * COIN);
+ // Verify that the 50 BTC UTXO was selected, and result is below max_weight
+ BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN)));
+ BOOST_CHECK_LE(result->GetWeight(), max_weight);
}
{
@@ -1479,6 +1483,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN)));
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN)));
+ BOOST_CHECK_LE(result->GetWeight(), max_weight);
}
{
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index f783424df8..2fac356263 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -2,9 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <boost/test/unit_test.hpp>
@@ -18,6 +16,7 @@
#ifdef USE_SQLITE
#include <wallet/sqlite.h>
#endif
+#include <wallet/migrate.h>
#include <wallet/test/util.h>
#include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
@@ -134,6 +133,8 @@ static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path
bilingual_str error;
#ifdef USE_BDB
dbs.emplace_back(MakeBerkeleyDatabase(path_root / "bdb", options, status, error));
+ // Needs BDB to make the DB to read
+ dbs.emplace_back(std::make_unique<BerkeleyRODatabase>(BDBDataFile(path_root / "bdb"), /*open=*/false));
#endif
#ifdef USE_SQLITE
dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
@@ -148,11 +149,16 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
for (const auto& database : TestDatabases(m_path_root)) {
std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
- // Write elements to it
std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
- for (unsigned int i = 0; i < 10; i++) {
- for (const auto& prefix : prefixes) {
- BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
+ database->Open();
+ } else {
+ // Write elements to it if not berkeleyro
+ for (unsigned int i = 0; i < 10; i++) {
+ for (const auto& prefix : prefixes) {
+ BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
+ }
}
}
@@ -180,6 +186,8 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
// Let's now read it once more, it should return DONE
BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
}
+ handler.reset();
+ database->Close();
}
}
@@ -199,13 +207,23 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
for (const auto& database : TestDatabases(m_path_root)) {
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
- for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
- batch->Write(Span{k}, Span{v});
+
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
+ database->Open();
+ } else {
+ // Write elements to it if not berkeleyro
+ for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
+ batch->Write(Span{k}, Span{v});
+ }
}
+
CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
+ batch.reset();
+ database->Close();
}
}
@@ -215,6 +233,10 @@ BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
// To simulate the behavior, record overwrites are disallowed, and the test verifies
// that the database remains active after failing to store an existing record.
for (const auto& database : TestDatabases(m_path_root)) {
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // Skip this test if BerkeleyRO
+ continue;
+ }
// Write original record
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
std::string key = "key";
@@ -243,6 +265,10 @@ BOOST_AUTO_TEST_CASE(erase_prefix)
auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
for (const auto& database : TestDatabases(m_path_root)) {
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // Skip this test if BerkeleyRO
+ continue;
+ }
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
// Write two entries with the same key type prefix, a third one with a different prefix
diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp
index 331590df7f..644a8dd7ad 100644
--- a/src/wallet/test/fuzz/coinselection.cpp
+++ b/src/wallet/test/fuzz/coinselection.cpp
@@ -270,7 +270,7 @@ FUZZ_TARGET(coinselection)
if (result_srd) {
assert(result_srd->GetSelectedValue() >= target);
assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0); // Demonstrate that SRD creates change of at least CHANGE_LOWER
- result_srd->ComputeAndSetWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
+ result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
(void)result_srd->GetShuffledInputVector();
(void)result_srd->GetInputSet();
}
@@ -279,7 +279,7 @@ FUZZ_TARGET(coinselection)
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
if (result_knapsack) {
assert(result_knapsack->GetSelectedValue() >= target);
- result_knapsack->ComputeAndSetWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
+ result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
(void)result_knapsack->GetShuffledInputVector();
(void)result_knapsack->GetInputSet();
}
diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp
index 228e9629ed..835470aeae 100644
--- a/src/wallet/test/fuzz/scriptpubkeyman.cpp
+++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp
@@ -137,6 +137,15 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
PKHash{ConsumeUInt160(fuzzed_data_provider)}};
std::string str_sig;
(void)spk_manager->SignMessage(msg, pk_hash, str_sig);
+ (void)spk_manager->GetMetadata(dest);
+ }
+ }
+ },
+ [&] {
+ auto spks{spk_manager->GetScriptPubKeys()};
+ for (const CScript& spk : spks) {
+ if (fuzzed_data_provider.ConsumeBool()) {
+ spk_manager->MarkUnusedAddresses(spk);
}
}
},
@@ -148,6 +157,10 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
}
spk_manager->AddDescriptorKey(key, key.GetPubKey());
spk_manager->TopUp();
+ LOCK(spk_manager->cs_desc_man);
+ auto particular_key{spk_manager->GetKey(key.GetPubKey().GetID())};
+ assert(*particular_key == key);
+ assert(spk_manager->HasPrivKey(key.GetPubKey().GetID()));
},
[&] {
std::string descriptor;
@@ -194,6 +207,9 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
}
);
}
+
+ (void)spk_manager->GetEndRange();
+ (void)spk_manager->GetKeyPoolSize();
}
} // namespace
diff --git a/src/wallet/test/fuzz/wallet_bdb_parser.cpp b/src/wallet/test/fuzz/wallet_bdb_parser.cpp
new file mode 100644
index 0000000000..5216e09769
--- /dev/null
+++ b/src/wallet/test/fuzz/wallet_bdb_parser.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2023 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 <config/bitcoin-config.h> // IWYU pragma: keep
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <test/util/setup_common.h>
+#include <util/fs.h>
+#include <util/time.h>
+#include <util/translation.h>
+#include <wallet/bdb.h>
+#include <wallet/db.h>
+#include <wallet/dump.h>
+#include <wallet/migrate.h>
+
+#include <fstream>
+#include <iostream>
+
+using wallet::DatabaseOptions;
+using wallet::DatabaseStatus;
+
+namespace {
+TestingSetup* g_setup;
+} // namespace
+
+void initialize_wallet_bdb_parser()
+{
+ static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
+ g_setup = testing_setup.get();
+}
+
+FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
+{
+ const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";
+
+ {
+ AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
+ outfile << Span{buffer};
+ }
+
+ const DatabaseOptions options{};
+ DatabaseStatus status;
+ bilingual_str error;
+
+ fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
+ if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
+ remove(bdb_ro_dumpfile);
+ }
+ g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_ro_dumpfile));
+
+#ifdef USE_BDB
+ bool bdb_ro_err = false;
+ bool bdb_ro_strict_err = false;
+#endif
+ auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
+ if (db) {
+ assert(DumpWallet(g_setup->m_args, *db, error));
+ } else {
+#ifdef USE_BDB
+ bdb_ro_err = true;
+#endif
+ if (error.original.starts_with("AutoFile::ignore: end of file") ||
+ error.original.starts_with("AutoFile::read: end of file") ||
+ error.original == "Not a BDB file" ||
+ error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
+ error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
+ error.original == "Unexpected outer database root page type" ||
+ error.original == "Unexpected number of entries in outer database root page" ||
+ error.original == "Subdatabase page number has unexpected length" ||
+ error.original == "Unknown record type in records page" ||
+ error.original == "Unknown record type in internal page" ||
+ error.original == "Unexpected page size" ||
+ error.original == "Unexpected page type" ||
+ error.original == "Page number mismatch" ||
+ error.original == "Bad btree level" ||
+ error.original == "Bad page size" ||
+ error.original == "Meta page number mismatch" ||
+ error.original == "Data record position not in page" ||
+ error.original == "Internal record position not in page" ||
+ error.original == "LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support" ||
+ error.original == "Records page has odd number of records" ||
+ error.original == "Bad overflow record page type") {
+ // Do nothing
+ } else if (error.original == "Subdatabase last page is greater than database last page" ||
+ error.original == "Page number is greater than database last page" ||
+ error.original == "Last page number could not fit in file" ||
+ error.original == "Subdatabase has an unexpected name" ||
+ error.original == "Unsupported BDB data file version number" ||
+ error.original == "BDB builtin encryption is not supported") {
+#ifdef USE_BDB
+ bdb_ro_strict_err = true;
+#endif
+ } else {
+ throw std::runtime_error(error.original);
+ }
+ }
+
+#ifdef USE_BDB
+ // Try opening with BDB
+ fs::path bdb_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb.dump"};
+ if (fs::exists(bdb_dumpfile)) { // Writing into an existing dump file will throw an exception
+ remove(bdb_dumpfile);
+ }
+ g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_dumpfile));
+
+ try {
+ auto db{MakeBerkeleyDatabase(wallet_path, options, status, error)};
+ if (bdb_ro_err && !db) {
+ return;
+ }
+ assert(db);
+ if (bdb_ro_strict_err) {
+ // BerkeleyRO will be stricter than BDB. Ignore when those specific errors are hit.
+ return;
+ }
+ assert(!bdb_ro_err);
+ assert(DumpWallet(g_setup->m_args, *db, error));
+ } catch (const std::runtime_error& e) {
+ if (bdb_ro_err) return;
+ throw e;
+ }
+
+ // Make sure the dumpfiles match
+ if (fs::exists(bdb_ro_dumpfile) && fs::exists(bdb_dumpfile)) {
+ std::ifstream bdb_ro_dump(bdb_ro_dumpfile, std::ios_base::binary | std::ios_base::in);
+ std::ifstream bdb_dump(bdb_dumpfile, std::ios_base::binary | std::ios_base::in);
+ assert(std::equal(
+ std::istreambuf_iterator<char>(bdb_ro_dump.rdbuf()),
+ std::istreambuf_iterator<char>(),
+ std::istreambuf_iterator<char>(bdb_dump.rdbuf())));
+ }
+#endif
+}
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index 49d206f409..b21a9a601d 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -93,11 +93,6 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type)
return *Assert(w.GetNewDestination(output_type, ""));
}
-// BytePrefix compares equality with other byte spans that begin with the same prefix.
-struct BytePrefix { Span<const std::byte> prefix; };
-bool operator<(BytePrefix a, Span<const std::byte> b) { return a.prefix < b.subspan(0, std::min(a.prefix.size(), b.size())); }
-bool operator<(Span<const std::byte> a, BytePrefix b) { return a.subspan(0, std::min(a.size(), b.prefix.size())) < b.prefix; }
-
MockableCursor::MockableCursor(const MockableData& records, bool pass, Span<const std::byte> prefix)
{
m_pass = pass;
diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h
index 9f2974ece6..a3e6ede81e 100644
--- a/src/wallet/test/util.h
+++ b/src/wallet/test/util.h
@@ -5,9 +5,7 @@
#ifndef BITCOIN_WALLET_TEST_UTIL_H
#define BITCOIN_WALLET_TEST_UTIL_H
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <addresstype.h>
#include <wallet/db.h>
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 3a67b9a433..53f3bcc421 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -235,11 +235,11 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
key.pushKV("internal", UniValue(true));
- keys.push_back(key);
+ keys.push_back(std::move(key));
JSONRPCRequest request;
request.context = &context;
request.params.setArray();
- request.params.push_back(keys);
+ request.params.push_back(std::move(keys));
UniValue response = importmulti().HandleRequest(request);
BOOST_CHECK_EQUAL(response.write(),
@@ -499,8 +499,10 @@ static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan* spk_man, const CPubKey& a
// Cryptographically invalidate a PubKey whilst keeping length and first byte
static void PollutePubKey(CPubKey& pubkey)
{
- std::vector<unsigned char> pubkey_raw(pubkey.begin(), pubkey.end());
- std::fill(pubkey_raw.begin()+1, pubkey_raw.end(), 0);
+ assert(pubkey.size() >= 1);
+ std::vector<unsigned char> pubkey_raw;
+ pubkey_raw.push_back(pubkey[0]);
+ pubkey_raw.insert(pubkey_raw.end(), pubkey.size() - 1, 0);
pubkey = CPubKey(pubkey_raw);
assert(!pubkey.IsFullyValid());
assert(pubkey.IsValid());
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 8f4171eb15..8a79cf730b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -5,9 +5,7 @@
#include <wallet/wallet.h>
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <addresstype.h>
#include <blockfilter.h>
#include <chain.h>
@@ -375,7 +373,12 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
+ ArgsManager& args = *Assert(context.args);
+
if (wallet_creation_flags & WALLET_FLAG_DESCRIPTORS) options.require_format = DatabaseFormat::SQLITE;
+ else if (args.GetBoolArg("-swapbdbendian", false)) {
+ options.require_format = DatabaseFormat::BERKELEY_SWAP;
+ }
// Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index b1ce7ee4e7..f34fcfc3fd 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -3,9 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <wallet/walletdb.h>
@@ -23,6 +21,7 @@
#ifdef USE_BDB
#include <wallet/bdb.h>
#endif
+#include <wallet/migrate.h>
#ifdef USE_SQLITE
#include <wallet/sqlite.h>
#endif
@@ -1389,6 +1388,11 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
return nullptr;
}
+ // If BERKELEY was the format, then change the format from BERKELEY to BERKELEY_RO
+ if (format && options.require_format && format == DatabaseFormat::BERKELEY && options.require_format == DatabaseFormat::BERKELEY_RO) {
+ format = DatabaseFormat::BERKELEY_RO;
+ }
+
// A db already exists so format is set, but options also specifies the format, so make sure they agree
if (format && options.require_format && format != options.require_format) {
error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in required format.", fs::PathToString(path)));
@@ -1422,6 +1426,10 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas
}
}
+ if (format == DatabaseFormat::BERKELEY_RO) {
+ return MakeBerkeleyRODatabase(path, options, status, error);
+ }
+
#ifdef USE_BDB
if constexpr (true) {
return MakeBerkeleyDatabase(path, options, status, error);
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index cda344ab19..10785ad354 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -2,9 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#if defined(HAVE_CONFIG_H)
-#include <config/bitcoin-config.h>
-#endif
+#include <config/bitcoin-config.h> // IWYU pragma: keep
#include <wallet/wallettool.h>
@@ -194,6 +192,11 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
ReadDatabaseArgs(args, options);
options.require_existing = true;
DatabaseStatus status;
+
+ if (args.GetBoolArg("-withinternalbdb", false) && IsBDBFile(BDBDataFile(path))) {
+ options.require_format = DatabaseFormat::BERKELEY_RO;
+ }
+
bilingual_str error;
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
if (!database) {