Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bitcoin-wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)

// Hidden
argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
argsman.AddArg("wipetxes", "Wipe all transactions from a wallet", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be hidden? (is it actually?)

Copy link
Copy Markdown
Author

@UdjinM6 UdjinM6 Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not and shouldn't be. It looks like we added "salvage" option in the wrong place 07a4d48#diff-0d04b20c6ef91f3f8e0ab7cf0e17dde5665b82e9c838f8ad94fc5bd488757d76R34 and then we failed to remove // Hidden 1253e6d#diff-0d04b20c6ef91f3f8e0ab7cf0e17dde5665b82e9c838f8ad94fc5bd488757d76R32.

}

static bool WalletAppInit(int argc, char* argv[])
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "echojson", 9, "arg9" },
{ "rescanblockchain", 0, "start_height"},
{ "rescanblockchain", 1, "stop_height"},
{ "wipewallettxes", 0, "keep_confirmed"},
{ "createwallet", 1, "disable_private_keys"},
{ "createwallet", 2, "blank"},
{ "createwallet", 4, "avoid_reuse"},
Expand Down
69 changes: 69 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3550,6 +3550,74 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
return response;
}

static UniValue wipewallettxes(const JSONRPCRequest& request)
{
RPCHelpMan{"wipewallettxes",
"\nWipe wallet transactions.\n"
"Note: Use \"rescanblockchain\" to initiate the scanning progress and recover wallet transactions.\n",
{
{"keep_confirmed", RPCArg::Type::BOOL, /* default */ "false", "Do not wipe confirmed transactions"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("wipewallettxes", "")
+ HelpExampleRpc("wipewallettxes", "")
},
}.Check(request);

std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();

WalletRescanReserver reserver(pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort rescan or wait.");
}

LOCK(pwallet->cs_wallet);

bool keep_confirmed{false};
if (!request.params[0].isNull()) {
keep_confirmed = request.params[0].get_bool();
}

const size_t WALLET_SIZE{pwallet->mapWallet.size()};
const size_t STEPS{20};
const size_t BATCH_SIZE = std::max(WALLET_SIZE / STEPS, size_t(1000));

pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions...").translated, pwallet->GetDisplayName()), 0);

for (size_t progress = 0; progress < STEPS; ++progress) {
std::vector<uint256> vHashIn;
std::vector<uint256> vHashOut;
size_t count{0};

for (auto& [txid, wtx] : pwallet->mapWallet) {
if (progress < STEPS - 1 && ++count > BATCH_SIZE) break;
if (keep_confirmed && wtx.m_confirm.status == CWalletTx::CONFIRMED) continue;
vHashIn.push_back(txid);
}

if (vHashIn.size() > 0 && pwallet->ZapSelectTx(vHashIn, vHashOut) != DBErrors::LOAD_OK) {
pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions...").translated, pwallet->GetDisplayName()), 100);
throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete transactions.");
}

CHECK_NONFATAL(vHashOut.size() == vHashIn.size());

if (pwallet->IsAbortingRescan() || pwallet->chain().shutdownRequested()) {
pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions...").translated, pwallet->GetDisplayName()), 100);
throw JSONRPCError(RPC_MISC_ERROR, "Wiping was aborted by user.");
}

pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions...").translated, pwallet->GetDisplayName()), std::max(1, std::min(99, int(progress * 100 / STEPS))));
}

pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions...").translated, pwallet->GetDisplayName()), 100);

return NullUniValue;
}

class DescribeWalletAddressVisitor
{
public:
Expand Down Expand Up @@ -4169,6 +4237,7 @@ static const CRPCCommand commands[] =
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout","mixingonly"} },
{ "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
{ "wallet", "wipewallettxes", &wipewallettxes, {"keep_confirmed"} },
};
// clang-format on

Expand Down
6 changes: 5 additions & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1918,7 +1918,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc

WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());

fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
uint256 end_hash = tip_hash;
Expand Down Expand Up @@ -3876,6 +3875,9 @@ void CWallet::AutoLockMasternodeCollaterals()
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
AssertLockHeld(cs_wallet);

WalletLogPrintf("ZapSelectTx started for %d transactions...\n", vHashIn.size());

DBErrors nZapSelectTxRet = WalletBatch(*database).ZapSelectTx(vHashIn, vHashOut);
for (uint256 hash : vHashOut) {
const auto& it = mapWallet.find(hash);
Expand All @@ -3899,6 +3901,8 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256

MarkDirty();

WalletLogPrintf("ZapSelectTx completed for %d transactions.\n", vHashOut.size());

return DBErrors::LOAD_OK;
}

Expand Down
3 changes: 2 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati

bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool fForMixingOnly = false, bool accept_no_keys = false);

std::atomic<bool> fAbortRescan{false};
std::atomic<bool> fAbortRescan{false}; // reset by WalletRescanReserver::reserve()
std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
std::atomic<int64_t> m_scanning_start{0};
std::atomic<double> m_scanning_progress{0};
Expand Down Expand Up @@ -1384,6 +1384,7 @@ class WalletRescanReserver
}
m_wallet->m_scanning_start = GetTimeMillis();
m_wallet->m_scanning_progress = 0;
m_wallet->fAbortRescan = false;
m_could_reserve = true;
return true;
}
Expand Down
28 changes: 27 additions & 1 deletion src/wallet/wallettool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
}
} else if (command == "info" || command == "salvage") {
} else if (command == "info" || command == "salvage" || command == "wipetxes") {
if (command == "info") {
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
if (!wallet_instance) return false;
Expand All @@ -137,6 +137,32 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
#else
tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
return false;
#endif
} else if (command == "wipetxes") {
#ifdef USE_BDB
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, /* create= */ false);
if (wallet_instance == nullptr) return false;

std::vector<uint256> vHash;
std::vector<uint256> vHashOut;

LOCK(wallet_instance->cs_wallet);

for (auto& [txid, _] : wallet_instance->mapWallet) {
vHash.push_back(txid);
}

if (wallet_instance->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
tfm::format(std::cerr, "Could not properly delete transactions");
wallet_instance->Close();
return false;
}

wallet_instance->Close();
return vHashOut.size() == vHash.size();
#else
tfm::format(std::cerr, "Wipetxes command is not available as BDB support is not compiled");
return false;
#endif
}
} else {
Expand Down
41 changes: 41 additions & 0 deletions test/functional/rpc_wipewallettxes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
# Copyright (c) 2023 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test transaction wiping using the wipewallettxes RPC."""

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error


class WipeWalletTxesTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def run_test(self):
self.log.info("Test that wipewallettxes removes txes and rescanblockchain is able to recover them")
self.nodes[0].generate(101)
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 103)
self.nodes[0].wipewallettxes()
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 0)
self.nodes[0].rescanblockchain()
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 103)

self.log.info("Test that wipewallettxes removes txes but keeps confirmed ones when asked to")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 104)
self.nodes[0].wipewallettxes(True)
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 103)
self.nodes[0].rescanblockchain()
assert_equal(self.nodes[0].getwalletinfo()["txcount"], 103)
assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", self.nodes[0].gettransaction, txid)


if __name__ == '__main__':
WipeWalletTxesTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
'wallet_create_tx.py',
'p2p_fingerprint.py',
'rpc_platform_filter.py',
'rpc_wipewallettxes.py',
'feature_dip0020_activation.py',
'feature_uacomment.py',
'wallet_coinbase_category.py',
Expand Down
26 changes: 26 additions & 0 deletions test/functional/tool_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,31 @@ def test_salvage(self):

self.assert_tool_output('', '-wallet=salvage', 'salvage')

def test_wipe(self):
out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): yes
Keypool Size: 2
Transactions: 1
Address Book: 1
''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')

self.assert_tool_output('', '-wallet=' + self.default_wallet_name, 'wipetxes')

out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): yes
Keypool Size: 2
Transactions: 0
Address Book: 1
''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')

def run_test(self):
self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)
self.test_invalid_tool_commands_and_args()
Expand All @@ -238,6 +263,7 @@ def run_test(self):
self.test_getwalletinfo_on_different_wallet()
if self.is_bdb_compiled():
self.test_salvage()
self.test_wipe()

if __name__ == '__main__':
ToolWalletTest().main()