re: #24914 (review)
I don't think upper_bound or equal_range will work. They will find the upper bound to be the first item that is greater than our prefix, but any record with the right prefix and is longer than the prefix will be greater than it, so we end up actually only getting the record(s) that exactly match the prefix.
Oops, you're right. But I think the following is an elegant way to make equal_range work (implementation based on https://stackoverflow.com/questions/44717939/elegant-way-to-find-keys-with-given-prefix-in-stdmap-or-elements-in-stdset):
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index f14f451b52b8..0256d3fafbf6 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -61,25 +61,15 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type)
return *Assert(w.GetNewDestination(output_type, ""));
}
-MockableCursor::MockableCursor(const std::map<SerializeData, SerializeData>& records, bool pass, Span<std::byte> prefix)
+// BytePrefix object compares equal with other byte spans beginning 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<std::byte> prefix)
{
m_pass = pass;
-
- // Start the cursor at the first value that is greater than or equal to the prefix
- m_cursor = records.lower_bound(SerializeData(prefix.begin(), prefix.end()));
-
- // The end cursor is the first item that is greater than or equal to the prefix + 1 (when interpreted as an integer)
- SerializeData end_range(prefix.begin(), prefix.end());
- auto it = end_range.rbegin();
- for (; it != end_range.rend(); ++it) {
- if (*it == std::byte(std::numeric_limits<unsigned char>::max())) {
- *it = std::byte(0);
- continue;
- }
- *it = std::byte(std::to_integer<unsigned char>(*it) + 1);
- break;
- }
- m_cursor_end = records.lower_bound(end_range);
+ std::tie(m_cursor, m_cursor_end) = records.equal_range(BytePrefix{prefix});
}
DatabaseCursor::Status MockableCursor::Next(DataStream& key, DataStream& value)
@@ -165,7 +155,7 @@ bool MockableBatch::ErasePrefix(Span<const std::byte> prefix)
return true;
}
-std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(std::map<SerializeData, SerializeData> records)
+std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(MockableData records)
{
return std::make_unique<MockableDatabase>(records);
}
diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h
index 75f9b46c2a1d..3fdf40d13ccb 100644
--- a/src/wallet/test/util.h
+++ b/src/wallet/test/util.h
@@ -32,15 +32,17 @@ std::string getnewaddress(CWallet& w);
/** Returns a new destination, of an specific type, from the wallet */
CTxDestination getNewDestination(CWallet& w, OutputType output_type);
+using MockableData = std::map<SerializeData, SerializeData, std::less<>>;
+
class MockableCursor: public DatabaseCursor
{
public:
- std::map<SerializeData, SerializeData>::const_iterator m_cursor;
- std::map<SerializeData, SerializeData>::const_iterator m_cursor_end;
+ MockableData::const_iterator m_cursor;
+ MockableData::const_iterator m_cursor_end;
bool m_pass;
- explicit MockableCursor(const std::map<SerializeData, SerializeData>& records, bool pass) : m_cursor(records.begin()), m_cursor_end(records.end()), m_pass(pass) {}
- MockableCursor(const std::map<SerializeData, SerializeData>& records, bool pass, Span<std::byte> prefix);
+ explicit MockableCursor(const MockableData& records, bool pass) : m_cursor(records.begin()), m_cursor_end(records.end()), m_pass(pass) {}
+ MockableCursor(const MockableData& records, bool pass, Span<std::byte> prefix);
~MockableCursor() {}
Status Next(DataStream& key, DataStream& value) override;
@@ -49,7 +51,7 @@ public:
class MockableBatch : public DatabaseBatch
{
private:
- std::map<SerializeData, SerializeData>& m_records;
+ MockableData& m_records;
bool m_pass;
bool ReadKey(DataStream&& key, DataStream& value) override;
@@ -59,7 +61,7 @@ private:
bool ErasePrefix(Span<const std::byte> prefix) override;
public:
- explicit MockableBatch(std::map<SerializeData, SerializeData>& records, bool pass) : m_records(records), m_pass(pass) {}
+ explicit MockableBatch(MockableData& records, bool pass) : m_records(records), m_pass(pass) {}
~MockableBatch() {}
void Flush() override {}
@@ -82,10 +84,10 @@ public:
class MockableDatabase : public WalletDatabase
{
public:
- std::map<SerializeData, SerializeData> m_records;
+ MockableData m_records;
bool m_pass{true};
- MockableDatabase(std::map<SerializeData, SerializeData> records = {}) : WalletDatabase(), m_records(records) {}
+ MockableDatabase(MockableData records = {}) : WalletDatabase(), m_records(records) {}
~MockableDatabase() {};
void Open() override {}
@@ -105,7 +107,7 @@ public:
std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<MockableBatch>(m_records, m_pass); }
};
-std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(std::map<SerializeData, SerializeData> records = {});
+std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(MockableData records = {});
MockableDatabase& GetMockableDatabase(CWallet& wallet);
} // namespace wallet
diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp
index 6823eafdfa7f..c1ff7baae117 100644
--- a/src/wallet/test/walletload_tests.cpp
+++ b/src/wallet/test/walletload_tests.cpp
@@ -83,7 +83,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_ckey, TestingSetup)
{
SerializeData ckey_record_key;
SerializeData ckey_record_value;
- std::map<SerializeData, SerializeData> records;
+ MockableData records;
{
// Context setup.