mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 18:09:47 +01:00
Merge bitcoin/bitcoin#28987: wallet: simplify and batch zap wallet txes process
9a3c5c8697
scripted-diff: rename ZapSelectTx to RemoveTxs (furszy)83b762845f
wallet: batch and simplify ZapSelectTx process (furszy)595d50a103
wallet: migration, remove extra NotifyTransactionChanged call (furszy)a2b071f992
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: ACK9a3c5c8697
josibake: ACK9a3c5c8697
Tree-SHA512: fb2ecc48224c400ab3b1fbb32e174b5b13bf03794717727f80f01f55fb183883b067a68c0a127b2de8885564da15425d021a96541953bf38a72becc2e9929ccf
This commit is contained in:
commit
6ff0aa089c
@ -394,14 +394,8 @@ RPCHelpMan removeprunedfunds()
|
||||
uint256 hash(ParseHashV(request.params[0], "txid"));
|
||||
std::vector<uint256> vHash;
|
||||
vHash.push_back(hash);
|
||||
std::vector<uint256> vHashOut;
|
||||
|
||||
if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
|
||||
}
|
||||
|
||||
if(vHashOut.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
|
||||
if (auto res = pwallet->RemoveTxs(vHash); !res) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
|
||||
}
|
||||
|
||||
return UniValue::VNULL;
|
||||
|
@ -888,7 +888,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
|
||||
UnloadWallet(std::move(wallet));
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
|
||||
BOOST_FIXTURE_TEST_CASE(RemoveTxs, TestChain100Setup)
|
||||
{
|
||||
m_args.ForceSetArg("-unsafesqlitesync", "1");
|
||||
WalletContext context;
|
||||
@ -913,8 +913,8 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
|
||||
BOOST_CHECK(wallet->HasWalletSpend(prev_tx));
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
|
||||
|
||||
std::vector<uint256> vHashIn{ block_hash }, vHashOut;
|
||||
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
|
||||
std::vector<uint256> vHashIn{ block_hash };
|
||||
BOOST_CHECK(wallet->RemoveTxs(vHashIn));
|
||||
|
||||
BOOST_CHECK(!wallet->HasWalletSpend(prev_tx));
|
||||
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
|
||||
|
@ -2320,12 +2320,41 @@ DBErrors CWallet::LoadWallet()
|
||||
return nLoadWalletRet;
|
||||
}
|
||||
|
||||
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
|
||||
util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
DBErrors nZapSelectTxRet = WalletBatch(GetDatabase()).ZapSelectTx(vHashIn, vHashOut);
|
||||
for (const uint256& hash : vHashOut) {
|
||||
const auto& it = mapWallet.find(hash);
|
||||
WalletBatch batch(GetDatabase());
|
||||
if (!batch.TxnBegin()) return util::Error{_("Error starting db txn for wallet transactions removal")};
|
||||
|
||||
// Check for transaction existence and remove entries from disk
|
||||
using TxIterator = std::unordered_map<uint256, CWalletTx, SaltedTxidHasher>::const_iterator;
|
||||
std::vector<TxIterator> erased_txs;
|
||||
bilingual_str str_err;
|
||||
for (const uint256& hash : txs_to_remove) {
|
||||
auto it_wtx = mapWallet.find(hash);
|
||||
if (it_wtx == mapWallet.end()) {
|
||||
str_err = strprintf(_("Transaction %s does not belong to this wallet"), hash.GetHex());
|
||||
break;
|
||||
}
|
||||
if (!batch.EraseTx(hash)) {
|
||||
str_err = strprintf(_("Failure removing transaction: %s"), hash.GetHex());
|
||||
break;
|
||||
}
|
||||
erased_txs.emplace_back(it_wtx);
|
||||
}
|
||||
|
||||
// Roll back removals in case of an error
|
||||
if (!str_err.empty()) {
|
||||
batch.TxnAbort();
|
||||
return util::Error{str_err};
|
||||
}
|
||||
|
||||
// Dump changes to disk
|
||||
if (!batch.TxnCommit()) return util::Error{_("Error committing db txn for wallet transactions removal")};
|
||||
|
||||
// Update the in-memory state and notify upper layers about the removals
|
||||
for (const auto& it : erased_txs) {
|
||||
const uint256 hash{it->first};
|
||||
wtxOrdered.erase(it->second.m_it_wtxOrdered);
|
||||
for (const auto& txin : it->second.tx->vin)
|
||||
mapTxSpends.erase(txin.prevout);
|
||||
@ -2333,22 +2362,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
|
||||
NotifyTransactionChanged(hash, CT_DELETED);
|
||||
}
|
||||
|
||||
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
|
||||
{
|
||||
if (GetDatabase().Rewrite("\x04pool"))
|
||||
{
|
||||
for (const auto& spk_man_pair : m_spk_managers) {
|
||||
spk_man_pair.second->RewriteDB();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nZapSelectTxRet != DBErrors::LOAD_OK)
|
||||
return nZapSelectTxRet;
|
||||
|
||||
MarkDirty();
|
||||
|
||||
return DBErrors::LOAD_OK;
|
||||
return {}; // all good
|
||||
}
|
||||
|
||||
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)
|
||||
watchonly_batch.reset(); // Flush
|
||||
// Do the removes
|
||||
if (txids_to_delete.size() > 0) {
|
||||
std::vector<uint256> deleted_txids;
|
||||
if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) {
|
||||
error = _("Error: Could not delete watchonly transactions");
|
||||
if (auto res = RemoveTxs(txids_to_delete); !res) {
|
||||
error = _("Error: Could not delete watchonly transactions. ") + util::ErrorString(res);
|
||||
return false;
|
||||
}
|
||||
if (deleted_txids != txids_to_delete) {
|
||||
error = _("Error: Not all watchonly txs could be deleted");
|
||||
return false;
|
||||
}
|
||||
// Tell the GUI of each tx
|
||||
for (const uint256& txid : deleted_txids) {
|
||||
NotifyTransactionChanged(txid, CT_UPDATED);
|
||||
}
|
||||
}
|
||||
|
||||
// Pair external wallets with their corresponding db handler
|
||||
|
@ -790,7 +790,9 @@ public:
|
||||
void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override;
|
||||
|
||||
DBErrors LoadWallet();
|
||||
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/** Erases the provided transactions from the wallet. */
|
||||
util::Result<void> RemoveTxs(std::vector<uint256>& txs_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::optional<AddressPurpose>& purpose);
|
||||
|
||||
|
@ -1230,90 +1230,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||
return result;
|
||||
}
|
||||
|
||||
DBErrors WalletBatch::FindWalletTxHashes(std::vector<uint256>& tx_hashes)
|
||||
{
|
||||
DBErrors result = DBErrors::LOAD_OK;
|
||||
|
||||
try {
|
||||
int nMinVersion = 0;
|
||||
if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
|
||||
if (nMinVersion > FEATURE_LATEST)
|
||||
return DBErrors::TOO_NEW;
|
||||
}
|
||||
|
||||
// Get cursor
|
||||
std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
|
||||
if (!cursor)
|
||||
{
|
||||
LogPrintf("Error getting wallet database cursor\n");
|
||||
return DBErrors::CORRUPT;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Read next record
|
||||
DataStream ssKey{};
|
||||
DataStream ssValue{};
|
||||
DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
|
||||
if (status == DatabaseCursor::Status::DONE) {
|
||||
break;
|
||||
} else if (status == DatabaseCursor::Status::FAIL) {
|
||||
LogPrintf("Error reading next record from wallet database\n");
|
||||
return DBErrors::CORRUPT;
|
||||
}
|
||||
|
||||
std::string strType;
|
||||
ssKey >> strType;
|
||||
if (strType == DBKeys::TX) {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
tx_hashes.push_back(hash);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
result = DBErrors::CORRUPT;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut)
|
||||
{
|
||||
// build list of wallet TX hashes
|
||||
std::vector<uint256> vTxHash;
|
||||
DBErrors err = FindWalletTxHashes(vTxHash);
|
||||
if (err != DBErrors::LOAD_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
std::sort(vTxHash.begin(), vTxHash.end());
|
||||
std::sort(vTxHashIn.begin(), vTxHashIn.end());
|
||||
|
||||
// erase each matching wallet TX
|
||||
bool delerror = false;
|
||||
std::vector<uint256>::iterator it = vTxHashIn.begin();
|
||||
for (const uint256& hash : vTxHash) {
|
||||
while (it < vTxHashIn.end() && (*it) < hash) {
|
||||
it++;
|
||||
}
|
||||
if (it == vTxHashIn.end()) {
|
||||
break;
|
||||
}
|
||||
else if ((*it) == hash) {
|
||||
if(!EraseTx(hash)) {
|
||||
LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
|
||||
delerror = true;
|
||||
}
|
||||
vTxHashOut.push_back(hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (delerror) {
|
||||
return DBErrors::CORRUPT;
|
||||
}
|
||||
return DBErrors::LOAD_OK;
|
||||
}
|
||||
|
||||
void MaybeCompactWalletDB(WalletContext& context)
|
||||
{
|
||||
static std::atomic<bool> fOneThread(false);
|
||||
|
@ -275,8 +275,6 @@ public:
|
||||
bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
|
||||
|
||||
DBErrors LoadWallet(CWallet* pwallet);
|
||||
DBErrors FindWalletTxHashes(std::vector<uint256>& tx_hashes);
|
||||
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
|
||||
|
||||
//! write the hdchain model (external chain child index counter)
|
||||
bool WriteHDChain(const CHDChain& chain);
|
||||
|
@ -120,7 +120,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework):
|
||||
assert_equal(address_info['ismine'], True)
|
||||
|
||||
# Remove transactions
|
||||
assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1)
|
||||
assert_raises_rpc_error(-4, f'Transaction {txnid1} does not belong to this wallet', w1.removeprunedfunds, txnid1)
|
||||
assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid1]
|
||||
|
||||
wwatch.removeprunedfunds(txnid2)
|
||||
|
Loading…
Reference in New Issue
Block a user