db_tests.cpp
1 // Copyright (c) 2018-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <boost/test/unit_test.hpp> 6 7 #include <test/util/common.h> 8 #include <test/util/setup_common.h> 9 #include <util/check.h> 10 #include <util/fs.h> 11 #include <util/translation.h> 12 #include <wallet/sqlite.h> 13 #include <wallet/migrate.h> 14 #include <wallet/test/util.h> 15 #include <wallet/walletutil.h> 16 17 #include <cstddef> 18 #include <memory> 19 #include <span> 20 #include <string> 21 #include <string_view> 22 #include <utility> 23 #include <vector> 24 25 inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv) 26 { 27 std::span key{kv.first}, value{kv.second}; 28 os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \"" 29 << std::string_view{reinterpret_cast<const char*>(value.data()), value.size()} << "\")"; 30 return os; 31 } 32 33 namespace wallet { 34 35 inline std::span<const std::byte> StringBytes(std::string_view str) 36 { 37 return std::as_bytes(std::span{str}); 38 } 39 40 static SerializeData StringData(std::string_view str) 41 { 42 auto bytes = StringBytes(str); 43 return SerializeData{bytes.begin(), bytes.end()}; 44 } 45 46 static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected) 47 { 48 std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix); 49 MockableData actual; 50 while (true) { 51 DataStream key, value; 52 DatabaseCursor::Status status = cursor->Next(key, value); 53 if (status == DatabaseCursor::Status::DONE) break; 54 BOOST_CHECK(status == DatabaseCursor::Status::MORE); 55 BOOST_CHECK( 56 actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second); 57 } 58 BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end()); 59 } 60 61 BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup) 62 63 static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root) 64 { 65 std::vector<std::unique_ptr<WalletDatabase>> dbs; 66 DatabaseOptions options; 67 DatabaseStatus status; 68 bilingual_str error; 69 // Unable to test BerkeleyRO since we cannot create a new BDB database to open 70 dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error)); 71 dbs.emplace_back(CreateMockableWalletDatabase()); 72 return dbs; 73 } 74 75 BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test) 76 { 77 // Test each supported db 78 for (const auto& database : TestDatabases(m_path_root)) { 79 std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"}; 80 81 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch(); 82 // Write elements to it 83 for (unsigned int i = 0; i < 10; i++) { 84 for (const auto& prefix : prefixes) { 85 BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i)); 86 } 87 } 88 89 // Now read all the items by prefix and verify that each element gets parsed correctly 90 for (const auto& prefix : prefixes) { 91 DataStream s_prefix; 92 s_prefix << prefix; 93 std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix); 94 DataStream key; 95 DataStream value; 96 for (int i = 0; i < 10; i++) { 97 DatabaseCursor::Status status = cursor->Next(key, value); 98 BOOST_CHECK_EQUAL(status, DatabaseCursor::Status::MORE); 99 100 std::string key_back; 101 unsigned int i_back; 102 key >> key_back >> i_back; 103 BOOST_CHECK_EQUAL(key_back, prefix); 104 105 unsigned int value_back; 106 value >> value_back; 107 BOOST_CHECK_EQUAL(value_back, i_back); 108 } 109 110 // Let's now read it once more, it should return DONE 111 BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE); 112 } 113 handler.reset(); 114 database->Close(); 115 } 116 } 117 118 // Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't 119 // covered in the higher level test above. The higher level test uses 120 // serialized strings which are prefixed with string length, so it doesn't test 121 // truly empty prefixes or prefixes that begin with \xff 122 BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test) 123 { 124 const MockableData::value_type 125 e{StringData(""), StringData("e")}, 126 p{StringData("prefix"), StringData("p")}, 127 ps{StringData("prefixsuffix"), StringData("ps")}, 128 f{StringData("\xff"), StringData("f")}, 129 fs{StringData("\xffsuffix"), StringData("fs")}, 130 ff{StringData("\xff\xff"), StringData("ff")}, 131 ffs{StringData("\xff\xffsuffix"), StringData("ffs")}; 132 for (const auto& database : TestDatabases(m_path_root)) { 133 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch(); 134 135 // Write elements to it if not berkeleyro 136 for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) { 137 batch->Write(std::span{k}, std::span{v}); 138 } 139 140 CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs}); 141 CheckPrefix(*batch, StringBytes("prefix"), {p, ps}); 142 CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs}); 143 CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs}); 144 batch.reset(); 145 database->Close(); 146 } 147 } 148 149 BOOST_AUTO_TEST_CASE(db_availability_after_write_error) 150 { 151 // Ensures the database remains accessible without deadlocking after a write error. 152 // To simulate the behavior, record overwrites are disallowed, and the test verifies 153 // that the database remains active after failing to store an existing record. 154 for (const auto& database : TestDatabases(m_path_root)) { 155 // Write original record 156 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch(); 157 std::string key = "key"; 158 std::string value = "value"; 159 std::string value2 = "value_2"; 160 BOOST_CHECK(batch->Write(key, value)); 161 // Attempt to overwrite the record (expect failure) 162 BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false)); 163 // Successfully overwrite the record 164 BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true)); 165 // Sanity-check; read and verify the overwritten value 166 std::string read_value; 167 BOOST_CHECK(batch->Read(key, read_value)); 168 BOOST_CHECK_EQUAL(read_value, value2); 169 } 170 } 171 172 // Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet. 173 // Keys are in the form of std::pair<TYPE, ENTRY_ID> 174 BOOST_AUTO_TEST_CASE(erase_prefix) 175 { 176 const std::string key = "key"; 177 const std::string key2 = "key2"; 178 const std::string value = "value"; 179 const std::string value2 = "value_2"; 180 auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); }; 181 182 for (const auto& database : TestDatabases(m_path_root)) { 183 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) { 184 // Skip this test if BerkeleyRO 185 continue; 186 } 187 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch(); 188 189 // Write two entries with the same key type prefix, a third one with a different prefix 190 // and a fourth one with the type-id values inverted 191 BOOST_CHECK(batch->Write(make_key(key, value), value)); 192 BOOST_CHECK(batch->Write(make_key(key, value2), value2)); 193 BOOST_CHECK(batch->Write(make_key(key2, value), value)); 194 BOOST_CHECK(batch->Write(make_key(value, key), value)); 195 196 // Erase the ones with the same prefix and verify result 197 BOOST_CHECK(batch->TxnBegin()); 198 BOOST_CHECK(batch->ErasePrefix(DataStream() << key)); 199 BOOST_CHECK(batch->TxnCommit()); 200 201 BOOST_CHECK(!batch->Exists(make_key(key, value))); 202 BOOST_CHECK(!batch->Exists(make_key(key, value2))); 203 // Also verify that entries with a different prefix were not erased 204 BOOST_CHECK(batch->Exists(make_key(key2, value))); 205 BOOST_CHECK(batch->Exists(make_key(value, key))); 206 } 207 } 208 209 // Test-only statement execution error 210 constexpr int TEST_SQLITE_ERROR = -999; 211 212 class DbExecBlocker : public SQliteExecHandler 213 { 214 private: 215 SQliteExecHandler m_base_exec; 216 std::set<std::string> m_blocked_statements; 217 public: 218 DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {} 219 int Exec(SQLiteDatabase& database, const std::string& statement) override { 220 if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR; 221 return m_base_exec.Exec(database, statement); 222 } 223 }; 224 225 BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn) 226 { 227 // Verifies that there is no active dangling, to-be-reversed db txn 228 // after the batch object that initiated it is destroyed. 229 DatabaseOptions options; 230 DatabaseStatus status; 231 bilingual_str error; 232 std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error); 233 234 std::string key = "key"; 235 std::string value = "value"; 236 237 std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database); 238 BOOST_CHECK(batch->TxnBegin()); 239 BOOST_CHECK(batch->Write(key, value)); 240 // Set a handler to prevent txn abortion during destruction. 241 // Mimicking a db statement execution failure. 242 batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"})); 243 // Destroy batch 244 batch.reset(); 245 246 // Ensure there is no dangling, to-be-reversed db txn 247 BOOST_CHECK(!database->HasActiveTxn()); 248 249 // And, just as a sanity check; verify that new batchs only write what they suppose to write 250 // and nothing else. 251 std::string key2 = "key2"; 252 std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database); 253 BOOST_CHECK(batch2->Write(key2, value)); 254 // The first key must not exist 255 BOOST_CHECK(!batch2->Exists(key)); 256 } 257 258 BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere) 259 { 260 std::string key = "key"; 261 std::string value = "value"; 262 std::string value2 = "value_2"; 263 264 DatabaseOptions options; 265 DatabaseStatus status; 266 bilingual_str error; 267 const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error); 268 269 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch(); 270 271 // Verify concurrent db transactions does not interfere between each other. 272 // Start db txn, write key and check the key does exist within the db txn. 273 BOOST_CHECK(handler->TxnBegin()); 274 BOOST_CHECK(handler->Write(key, value)); 275 BOOST_CHECK(handler->Exists(key)); 276 277 // But, the same key, does not exist in another handler 278 std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch(); 279 BOOST_CHECK(handler2->Exists(key)); 280 281 // Attempt to commit the handler txn calling the handler2 methods. 282 // Which, must not be possible. 283 BOOST_CHECK(!handler2->TxnCommit()); 284 BOOST_CHECK(!handler2->TxnAbort()); 285 286 // Only the first handler can commit the changes. 287 BOOST_CHECK(handler->TxnCommit()); 288 // And, once commit is completed, handler2 can read the record 289 std::string read_value; 290 BOOST_CHECK(handler2->Read(key, read_value)); 291 BOOST_CHECK_EQUAL(read_value, value); 292 293 // Also, once txn is committed, single write statements are re-enabled. 294 // Which means that handler2 can read the record changes directly. 295 BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true)); 296 BOOST_CHECK(handler2->Read(key, read_value)); 297 BOOST_CHECK_EQUAL(read_value, value2); 298 } 299 300 BOOST_AUTO_TEST_SUITE_END() 301 } // namespace wallet