mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-05-31 16:24:48 +02:00
wallet: bugfix: ensure atomicity in settings updates
- Settings updates were not thread-safe, as they were executed in three separate steps: 1) Obtain settings value while acquiring the settings lock. 2) Modify settings value. 3) Overwrite settings value while acquiring the settings lock. This approach allowed concurrent threads to modify the same base value simultaneously, leading to data loss. When this occurred, the final settings state would only reflect the changes from the last thread that completed the operation, overwriting updates from other threads. Fix this by making the settings update operation atomic. - Add test coverage for this behavior. Co-authored-by: furszy <matiasfurszyfer@protonmail.com>
This commit is contained in:
@@ -329,6 +329,40 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
||||
}
|
||||
}
|
||||
|
||||
// This test verifies that wallet settings can be added and removed
|
||||
// concurrently, ensuring no race conditions occur during either process.
|
||||
BOOST_FIXTURE_TEST_CASE(write_wallet_settings_concurrently, TestingSetup)
|
||||
{
|
||||
WalletContext context;
|
||||
context.chain = m_node.chain.get();
|
||||
const auto NUM_WALLETS{5};
|
||||
|
||||
// Since we're counting the number of wallets, ensure we start without any.
|
||||
BOOST_REQUIRE(context.chain->getRwSetting("wallet").isNull());
|
||||
|
||||
const auto& check_concurrent_wallet = [&](const auto& settings_function, int num_expected_wallets) {
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(NUM_WALLETS);
|
||||
for (auto i{0}; i < NUM_WALLETS; ++i) threads.emplace_back(settings_function, i);
|
||||
for (auto& t : threads) t.join();
|
||||
|
||||
auto wallets = context.chain->getRwSetting("wallet");
|
||||
BOOST_CHECK_EQUAL(wallets.getValues().size(), num_expected_wallets);
|
||||
};
|
||||
|
||||
// Add NUM_WALLETS wallets concurrently, ensure we end up with NUM_WALLETS stored.
|
||||
check_concurrent_wallet([&context](int i) {
|
||||
Assert(AddWalletSetting(*context.chain, strprintf("wallet_%d", i)));
|
||||
},
|
||||
/*num_expected_wallets=*/NUM_WALLETS);
|
||||
|
||||
// Remove NUM_WALLETS wallets concurrently, ensure we end up with 0 wallets.
|
||||
check_concurrent_wallet([&context](int i) {
|
||||
Assert(RemoveWalletSetting(*context.chain, strprintf("wallet_%d", i)));
|
||||
},
|
||||
/*num_expected_wallets=*/0);
|
||||
}
|
||||
|
||||
// Check that GetImmatureCredit() returns a newly calculated value instead of
|
||||
// the cached value after a MarkDirty() call.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user