Skip to content

Commit 6ff0aa0

Browse files
committed
Merge bitcoin/bitcoin#28987: wallet: simplify and batch zap wallet txes process
9a3c5c8 scripted-diff: rename ZapSelectTx to RemoveTxs (furszy) 83b7628 wallet: batch and simplify ZapSelectTx process (furszy) 595d50a wallet: migration, remove extra NotifyTransactionChanged call (furszy) a2b071f wallet: ZapSelectTx, remove db rewrite code (furszy) Pull request description: Work decoupled from #28574. Brother of #28894. Includes two different, yet interconnected, performance and code improvements to the zap wallet transactions process. 1) As the goal of the `ZapSelectTx` function is to erase tx records that match any of the inputted hashes. There is no need to traverse the whole database record by record. We could just check if the tx exist, and remove it directly by calling `EraseTx()`. 2) Instead of performing single write operations per removed tx record, this PR batches them all within a single atomic db txn. Moreover, these changes will enable us to consolidate all individual write operations that take place during the wallet migration process into a single db txn in the future. ACKs for top commit: achow101: ACK 9a3c5c8 josibake: ACK bitcoin/bitcoin@9a3c5c8 Tree-SHA512: fb2ecc48224c400ab3b1fbb32e174b5b13bf03794717727f80f01f55fb183883b067a68c0a127b2de8885564da15425d021a96541953bf38a72becc2e9929ccf
2 parents c6398c6 + 9a3c5c8 commit 6ff0aa0

File tree

7 files changed

+45
-128
lines changed

7 files changed

+45
-128
lines changed

src/wallet/rpc/backup.cpp

+2-8
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,8 @@ RPCHelpMan removeprunedfunds()
394394
uint256 hash(ParseHashV(request.params[0], "txid"));
395395
std::vector<uint256> vHash;
396396
vHash.push_back(hash);
397-
std::vector<uint256> vHashOut;
398-
399-
if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
400-
throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
401-
}
402-
403-
if(vHashOut.empty()) {
404-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
397+
if (auto res = pwallet->RemoveTxs(vHash); !res) {
398+
throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
405399
}
406400

407401
return UniValue::VNULL;

src/wallet/test/wallet_tests.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
888888
UnloadWallet(std::move(wallet));
889889
}
890890

891-
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
891+
BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)
892892
{
893893
m_args.ForceSetArg("-unsafesqlitesync", "1");
894894
WalletContext context;
@@ -913,8 +913,8 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
913913
BOOST_CHECK(wallet->HasWalletSpend(prev_tx));
914914
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
915915

916-
std::vector<uint256> vHashIn{ block_hash }, vHashOut;
917-
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
916+
std::vector<uint256> vHashIn{ block_hash };
917+
BOOST_CHECK(wallet->RemoveTxs(vHashIn));
918918

919919
BOOST_CHECK(!wallet->HasWalletSpend(prev_tx));
920920
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);

src/wallet/wallet.cpp

+36-29
Original file line numberDiff line numberDiff line change
@@ -2320,35 +2320,51 @@ DBErrors CWallet::LoadWallet()
23202320
return nLoadWalletRet;
23212321
}
23222322

2323-
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
2323+
util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
23242324
{
23252325
AssertLockHeld(cs_wallet);
2326-
DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
2327-
for (const uint256& hash : vHashOut) {
2328-
const auto& it = mapWallet.find(hash);
2326+
WalletBatch batch(GetDatabase());
2327+
if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")};
2328+
2329+
// Check for transaction existence and remove entries from disk
2330+
using TxIterator = std::unordered_map<uint256, CWalletTx, SaltedTxidHasher>::const_iterator;
2331+
std::vector<TxIterator> erased_txs;
2332+
bilingual_str str_err;
2333+
for (const uint256& hash : txs_to_remove) {
2334+
auto it_wtx = mapWallet.find(hash);
2335+
if (it_wtx == mapWallet.end()) {
2336+
str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex());
2337+
break;
2338+
}
2339+
if (!batch.EraseTx(hash)) {
2340+
str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex());
2341+
break;
2342+
}
2343+
erased_txs.emplace_back(it_wtx);
2344+
}
2345+
2346+
// Roll back removals in case of an error
2347+
if (!str_err.empty()) {
2348+
batch.TxnAbort();
2349+
return util::Error{str_err};
2350+
}
2351+
2352+
// Dump changes to disk
2353+
if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")};
2354+
2355+
// Update the in-memory state and notify upper layers about the removals
2356+
for (const auto& it : erased_txs) {
2357+
const uint256 hash{it->first};
23292358
wtxOrdered.erase(it->second.m_it_wtxOrdered);
23302359
for (const auto& txin : it->second.tx->vin)
23312360
mapTxSpends.erase(txin.prevout);
23322361
mapWallet.erase(it);
23332362
NotifyTransactionChanged(hash, CT_DELETED);
23342363
}
23352364

2336-
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
2337-
{
2338-
if (GetDatabase().Rewrite("\x04pool"))
2339-
{
2340-
for (const auto& spk_man_pair : m_spk_managers) {
2341-
spk_man_pair.second->RewriteDB();
2342-
}
2343-
}
2344-
}
2345-
2346-
if (nZapSelectTxRet != DBErrors::LOAD_OK)
2347-
return nZapSelectTxRet;
2348-
23492365
MarkDirty();
23502366

2351-
return DBErrors::LOAD_OK;
2367+
return {}; // all good
23522368
}
23532369

23542370
bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& new_purpose)
@@ -4025,19 +4041,10 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
40254041
watchonly_batch.reset(); // Flush
40264042
// Do the removes
40274043
if (txids_to_delete.size() > 0) {
4028-
std::vector<uint256> deleted_txids;
4029-
if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) {
4030-
error = _("Error: Could not delete watchonly transactions");
4044+
if (auto res = RemoveTxs(txids_to_delete); !res) {
4045+
error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res);
40314046
return false;
40324047
}
4033-
if (deleted_txids != txids_to_delete) {
4034-
error = _("Error: Not all watchonly txs could be deleted");
4035-
return false;
4036-
}
4037-
// Tell the GUI of each tx
4038-
for (const uint256& txid : deleted_txids) {
4039-
NotifyTransactionChanged(txid, CT_UPDATED);
4040-
}
40414048
}
40424049

40434050
// Pair external wallets with their corresponding db handler

src/wallet/wallet.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
790790
void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override;
791791

792792
DBErrors LoadWallet();
793-
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
793+
794+
/** Erases the provided transactions from the wallet. */
795+
util::Result<void> RemoveTxs(std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
794796

795797
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& purpose);
796798

src/wallet/walletdb.cpp

-84
Original file line numberDiff line numberDiff line change
@@ -1230,90 +1230,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
12301230
return result;
12311231
}
12321232

1233-
DBErrors WalletBatch::FindWalletTxHashes(std::vector<uint256>& tx_hashes)
1234-
{
1235-
DBErrors result = DBErrors::LOAD_OK;
1236-
1237-
try {
1238-
int nMinVersion = 0;
1239-
if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
1240-
if (nMinVersion > FEATURE_LATEST)
1241-
return DBErrors::TOO_NEW;
1242-
}
1243-
1244-
// Get cursor
1245-
std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
1246-
if (!cursor)
1247-
{
1248-
LogPrintf("Error getting wallet database cursor\n");
1249-
return DBErrors::CORRUPT;
1250-
}
1251-
1252-
while (true)
1253-
{
1254-
// Read next record
1255-
DataStream ssKey{};
1256-
DataStream ssValue{};
1257-
DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
1258-
if (status == DatabaseCursor::Status::DONE) {
1259-
break;
1260-
} else if (status == DatabaseCursor::Status::FAIL) {
1261-
LogPrintf("Error reading next record from wallet database\n");
1262-
return DBErrors::CORRUPT;
1263-
}
1264-
1265-
std::string strType;
1266-
ssKey >> strType;
1267-
if (strType == DBKeys::TX) {
1268-
uint256 hash;
1269-
ssKey >> hash;
1270-
tx_hashes.push_back(hash);
1271-
}
1272-
}
1273-
} catch (...) {
1274-
result = DBErrors::CORRUPT;
1275-
}
1276-
1277-
return result;
1278-
}
1279-
1280-
DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut)
1281-
{
1282-
// build list of wallet TX hashes
1283-
std::vector<uint256> vTxHash;
1284-
DBErrors err = FindWalletTxHashes(vTxHash);
1285-
if (err != DBErrors::LOAD_OK) {
1286-
return err;
1287-
}
1288-
1289-
std::sort(vTxHash.begin(), vTxHash.end());
1290-
std::sort(vTxHashIn.begin(), vTxHashIn.end());
1291-
1292-
// erase each matching wallet TX
1293-
bool delerror = false;
1294-
std::vector<uint256>::iterator it = vTxHashIn.begin();
1295-
for (const uint256& hash : vTxHash) {
1296-
while (it < vTxHashIn.end() && (*it) < hash) {
1297-
it++;
1298-
}
1299-
if (it == vTxHashIn.end()) {
1300-
break;
1301-
}
1302-
else if ((*it) == hash) {
1303-
if(!EraseTx(hash)) {
1304-
LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
1305-
delerror = true;
1306-
}
1307-
vTxHashOut.push_back(hash);
1308-
}
1309-
}
1310-
1311-
if (delerror) {
1312-
return DBErrors::CORRUPT;
1313-
}
1314-
return DBErrors::LOAD_OK;
1315-
}
1316-
13171233
void MaybeCompactWalletDB(WalletContext& context)
13181234
{
13191235
static std::atomic<bool> fOneThread(false);

src/wallet/walletdb.h

-2
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ class WalletBatch
275275
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
276276

277277
DBErrors LoadWallet(CWallet* pwallet);
278-
DBErrors FindWalletTxHashes(std::vector<uint256>& tx_hashes);
279-
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
280278

281279
//! write the hdchain model (external chain child index counter)
282280
bool WriteHDChain(const CHDChain& chain);

test/functional/wallet_importprunedfunds.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def run_test(self):
120120
assert_equal(address_info['ismine'], True)
121121

122122
# Remove transactions
123-
assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1)
123+
assert_raises_rpc_error(-4, f'Transaction {txnid1} does not belong to this wallet', w1.removeprunedfunds, txnid1)
124124
assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid1]
125125

126126
wwatch.removeprunedfunds(txnid2)

0 commit comments

Comments
 (0)