Skip to content

Commit 3375781

Browse files
committed
wallet: bdb batch 'ErasePrefix', do not create txn internally
Transactions are intended to be started on upper layers rather than internally by the bdb batch object. This enables us to consolidate different write operations within a procedure in the same db txn, improving consistency due to the atomic property of the transaction, as well as its performance due to the reduction of disk write operations. Important Note: This approach also ensures that the BerkeleyBatch::ErasePrefix function behaves exactly as the SQLiteBatch::ErasePrefix function, which does not create a db txn internally. Furthermore, since the `BerkeleyBatch::ErasePrefix' implementation erases records one by one (by traversing the db), this change ensures that the function is always called within an active txn context. Without this measure, there's a potential risk to consistency; certain records may be removed while others could persist due to an internal failure during the procedure.
1 parent cf4d72a commit 3375781

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

src/wallet/bdb.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,12 @@ bool BerkeleyBatch::HasKey(DataStream&& key)
887887

888888
bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
889889
{
890-
if (!TxnBegin()) return false;
890+
// Because this function erases records one by one, ensure that it is executed within a txn context.
891+
// Otherwise, consistency is at risk; it's possible that certain records are removed while others
892+
// remain due to an internal failure during the procedure.
893+
// Additionally, the Dbc::del() cursor delete call below would fail without an active transaction.
894+
if (!Assume(activeTxn)) return false;
895+
891896
auto cursor{std::make_unique<BerkeleyCursor>(m_database, *this)};
892897
// const_cast is safe below even though prefix_key is an in/out parameter,
893898
// because we are not using the DB_DBT_USERMEM flag, so BDB will allocate
@@ -901,7 +906,7 @@ bool BerkeleyBatch::ErasePrefix(Span<const std::byte> prefix)
901906
ret = cursor->dbc()->del(0);
902907
}
903908
cursor.reset();
904-
return TxnCommit() && (ret == 0 || ret == DB_NOTFOUND);
909+
return ret == 0 || ret == DB_NOTFOUND;
905910
}
906911

907912
void BerkeleyDatabase::AddRef()

src/wallet/test/db_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,39 @@ BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
228228
}
229229
}
230230

231+
// Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
232+
// Keys are in the form of std::pair<TYPE, ENTRY_ID>
233+
BOOST_AUTO_TEST_CASE(erase_prefix)
234+
{
235+
const std::string key = "key";
236+
const std::string key2 = "key2";
237+
const std::string value = "value";
238+
const std::string value2 = "value_2";
239+
auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
240+
241+
for (const auto& database : TestDatabases(m_path_root)) {
242+
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
243+
244+
// Write two entries with the same key type prefix, a third one with a different prefix
245+
// and a fourth one with the type-id values inverted
246+
BOOST_CHECK(batch->Write(make_key(key, value), value));
247+
BOOST_CHECK(batch->Write(make_key(key, value2), value2));
248+
BOOST_CHECK(batch->Write(make_key(key2, value), value));
249+
BOOST_CHECK(batch->Write(make_key(value, key), value));
250+
251+
// Erase the ones with the same prefix and verify result
252+
BOOST_CHECK(batch->TxnBegin());
253+
BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
254+
BOOST_CHECK(batch->TxnCommit());
255+
256+
BOOST_CHECK(!batch->Exists(make_key(key, value)));
257+
BOOST_CHECK(!batch->Exists(make_key(key, value2)));
258+
// Also verify that entries with a different prefix were not erased
259+
BOOST_CHECK(batch->Exists(make_key(key2, value)));
260+
BOOST_CHECK(batch->Exists(make_key(value, key)));
261+
}
262+
}
263+
231264
#ifdef USE_SQLITE
232265

233266
// Test-only statement execution error

src/wallet/test/wallet_tests.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,11 @@ BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)
445445
auto requests = wallet->GetAddressReceiveRequests();
446446
auto erequests = {"val_rr11", "val_rr20"};
447447
BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));
448-
WalletBatch batch{wallet->GetDatabase()};
449-
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
450-
BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
448+
RunWithinTxn(wallet->GetDatabase(), /*process_desc*/"test", [](WalletBatch& batch){
449+
BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));
450+
BOOST_CHECK(batch.EraseAddressData(ScriptHash()));
451+
return true;
452+
});
451453
});
452454
TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {
453455
BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));

0 commit comments

Comments
 (0)