I added a new rpc call that allows you to remove a watch only address
Format:
removeaddress address/script (p2sh) (purgetransactions)
I added a new rpc call that allows you to remove a watch only address
Format:
removeaddress address/script (p2sh) (purgetransactions)
886+
887+ // Notify UI of new or updated transaction
888+ NotifyTransactionChanged(this, tx.GetHash(), CT_DELETED);
889+
890+ // Notify an external script when a wallet transaction comes in or is updated
891+ std::string strCmd = gArgs.GetArg("-walletnotify", "");
342@@ -343,6 +343,89 @@ UniValue importaddress(const JSONRPCRequest& request)
343 return NullUniValue;
344 }
345
346+static void RemoveAddress(CWallet*, const CTxDestination& dest);
347+static void RemoveScript(CWallet* const pwallet, const CScript& script) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
348+{
349+ if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE)
970@@ -947,7 +971,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
971 if (pIndex != nullptr)
972 wtx.SetMerkleBranch(pIndex, posInBlock);
973
974- return AddToWallet(wtx, false);
975+ if (IsMine(tx) || IsFromMe(tx))
976+ return AddToWallet(wtx, false);
977+
978+ return RemoveFromWallet(tx);
962@@ -947,7 +963,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
963 if (pIndex != nullptr)
964 wtx.SetMerkleBranch(pIndex, posInBlock);
965
966- return AddToWallet(wtx, false);
967+ if (IsMine(tx) || IsFromMe(tx))
968+ return AddToWallet(wtx, false);
969+
970+ return RemoveFromWallet(tx);
LoadToWallet()
rather then an expansive rescan?
I don’t see the reason why a rescan would be required. IMO it’s only required after adding a script where its creation-timestamp lies in the past (expected historical transactions). You don’t need to go through the whole (or part of) the blockchain history just to know that a transaction in your wallet doesn’t belong to you anymore. Also, don’t forget that rescans are incompatible for pruned peers.
It should be sufficient to loop though mapWallet
and remove transactions that are no longer IsMine(tx) || IsFromMe(tx)
(after the deletion of one or many scripts/addresses).
importmulti
for more advanced script removing and batches would eventually make sense.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
401+ throw std::runtime_error(
402+ RPCHelpMan{"removeaddress",
403+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
404+ {
405+ {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The Bitcoin address (or hex-encoded script)"},
406+ {"p2sh", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Add the P2SH version of the script as well"},
purge_transactions
(default true) about removing existing transactions from the wallet.
943+ AssertLockHeld(cs_wallet);
944+
945+ std::vector<CWalletTx> vWtx;
946+ for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) {
947+ CWalletTx tx = it->second;
948+ if (!IsFromMe(*tx.tx) && !IsMine(*tx.tx)) {
lint-assertions.sh
and lint-python.sh
linters do not pass. Please fix.
923@@ -924,6 +924,42 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn)
924 }
925 }
926
927+void CWallet::RemoveOldTransactions(const CScript& script)
928+{
929+ // AssertLockHeld(cs_wallet);
127@@ -128,6 +128,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
128 { "importprivkey", 2, "rescan" },
129 { "importaddress", 2, "rescan" },
130 { "importaddress", 3, "p2sh" },
131+ { "removeaddress", 2, "p2sh" },
330+ if (!isRedeemScript && ismine == ISMINE_SPENDABLE) {
331+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet contains the private key for this address");
332+ } else if (!isRedeemScript && ismine == ISMINE_NO) {
333+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet does not contain this address or script");
334+ }
335+ assert(ismine == ISMINE_WATCH_ONLY || isRedeemScript);
CHECK_NONFATAL
is recommended instead of assert
for rpc code.
923@@ -924,6 +924,45 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn)
924 }
925 }
926
927+void CWallet::RemoveOldTransactions(const CScript& script)
RemoveTransactions()
would be a better name, as this can remove any transactions, being “old” is not a requirement.
RemoveOldTransactions()
got renamed to RemoveTransactions()
but calling plance didn’t get changed, so it doesn’t build now.
0wallet/rpcdump.cpp: In function ‘void RemoveScript(CWallet*, const CScript&, bool, bool)’:
1wallet/rpcdump.cpp:356:18: error: ‘class CWallet’ has no member named ‘RemoveOldTransactions’; did you mean ‘RemoveTransactions’?
2 356 | pwallet->RemoveOldTransactions(script);
3 | ^~~~~~~~~~~~~~~~~~~~~
4 | RemoveTransactions
RemoveOldTransactions()
got renamed toRemoveTransactions()
but calling plance didn’t get changed, so it doesn’t build now.0wallet/rpcdump.cpp: In function ‘void RemoveScript(CWallet*, const CScript&, bool, bool)’: 1wallet/rpcdump.cpp:356:18: error: ‘class CWallet’ has no member named ‘RemoveOldTransactions’; did you mean ‘RemoveTransactions’? 2 356 | pwallet->RemoveOldTransactions(script); 3 | ^~~~~~~~~~~~~~~~~~~~~ 4 | RemoveTransactions
woops, fixed now :)
382+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Remove the P2SH version of the script as well"},
383+ {"purgetransactions", RPCArg::Type::BOOL, /* default_val */ "false", "Remove existing transactions from the wallet"},
384+ },
385+ RPCResult{RPCResult::Type::NONE, "", ""},
386+ RPCExamples{
387+ "\nExamples:\n"
@benthecarman CIs are still red since ad0de4c.
Sorry, everything was passing locally, looks like it had silent merge conflicts with master
again
332+ } else if (!isRedeemScript && ismine == ISMINE_NO) {
333+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet does not contain this address or script");
334+ }
335+ CHECK_NONFATAL(ismine == ISMINE_WATCH_ONLY || isRedeemScript);
336+
337+ auto spk_man = pwallet->GetLegacyScriptPubKeyMan();
Try this instead, to guarantee it only works for legacy wallets:
0 LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
There are a few import scenarios where some scripts and pubkeys get left behind and that could cause IsMine
issues. There will also be leftover CKeyMetadata
entries. I think these will all need to be cleaned up.
Importing any P2SH or P2WSH address with a redeemScript will add that redeemScript to mapScripts
. If pubkeys were imported to the keypool, then those pubkeys are still in the keypool after removeaddress
. Those need to be removed otherwise users could be sending money to an address that the wallet is no longer watching.
There are a few import scenarios where some scripts and pubkeys get left behind and that could cause
IsMine
issues. There will also be leftoverCKeyMetadata
entries. I think these will all need to be cleaned up.Importing any P2SH or P2WSH address with a redeemScript will add that redeemScript to
mapScripts
. If pubkeys were imported to the keypool, then those pubkeys are still in the keypool afterremoveaddress
. Those need to be removed otherwise users could be sending money to an address that the wallet is no longer watching.
Sounds like most of this needs to be fixed, however, do note this is only for watch only addresses. So, I don’t believe we need to worry about the key pool.
however, do note this is only for watch only addresses. So, I don’t believe we need to worry about the key pool.
importmulti
allows people to import address with their pubkeys and add the pubkeys into the keypool. The scriptPubKeys still end up in setWatchOnly
and the pubkeys in mapWatchKeys
, but also those pubkeys go in the keypool so that getnewaddress
can work. Anything imported with those and then used with removeaddress
would still have the pubkey in the keypool.
There are a few import scenarios where some scripts and pubkeys get left behind and that could cause
IsMine
issues. There will also be leftoverCKeyMetadata
entries. I think these will all need to be cleaned up.Importing any P2SH or P2WSH address with a redeemScript will add that redeemScript to
mapScripts
. If pubkeys were imported to the keypool, then those pubkeys are still in the keypool afterremoveaddress
. Those need to be removed otherwise users could be sending money to an address that the wallet is no longer watching.
I believe this is addressed now
359+ if (IsValidDestination(dest)) {
360+ pwallet->DelAddressBook(dest);
361+ }
362+}
363+
364+UniValue removeaddress(const JSONRPCRequest& request)
379+ },
380+ }.Check(request);
381+
382+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
383+ if (!wallet) return NullUniValue;
384+ CWallet* const pwallet = wallet.get();
0 const CWallet& wallet = *pwallet.get();
As the previous line checks for nullptr, might be good to just take a reference instead of keeping to treat it as pointer that can be nullptr.
980@@ -981,6 +981,45 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
981 return true;
982 }
983
984+void CWallet::RemoveTransactions(const CScript& script)
985+{
986+ AssertLockHeld(cs_wallet);
After the rebase, I’m getting compile warnings for this line consistent with the CI:
0wallet/wallet.cpp:986:5: warning: calling function 'AssertLockHeldInternal<AnnotatedMixin<std::__1::recursive_mutex> >' requires holding mutex 'cs_wallet' exclusively [-Wthread-safety-analysis]
1 AssertLockHeld(cs_wallet);
2 ^
3./sync.h:81:28: note: expanded from macro 'AssertLockHeld'
4#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
5 ^
6wallet/wallet.cpp:989:54: warning: reading variable 'mapWallet' requires holding mutex 'cs_wallet' [-Wthread-safety-analysis]
7 for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) {
8 ^
9wallet/wallet.cpp:989:79: warning: reading variable 'mapWallet' requires holding mutex 'cs_wallet' [-Wthread-safety-analysis]
10 for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) {
11 ^
12wallet/wallet.cpp:1001:67: warning: reading variable 'mapWallet' requires holding mutex 'cs_wallet' [-Wthread-safety-analysis]
13 std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
14 ^
15wallet/wallet.cpp:1002:27: warning: reading variable 'mapWallet' requires holding mutex 'cs_wallet' [-Wthread-safety-analysis]
16 if (mi != mapWallet.end()) {
17 ^
18wallet/wallet.cpp:1011:56: warning: calling function 'IsMine' requires holding mutex 'cs_wallet' exclusively [-Wthread-safety-analysis]
19 if (txContainsScript && !IsFromMe(*wTx.tx) && !IsMine(*wTx.tx)) {
20 ^
21wallet/wallet.cpp:1017:9: warning: reading variable 'mapWallet' requires holding mutex 'cs_wallet' [-Wthread-safety-analysis]
22 mapWallet.erase(wtx->GetHash());
23 ^
247 warnings generated.
0@@ -0,0 +1,4 @@
1+New RPCs
2+--------
3+- A new `removeaddress` RPC will remove a watch only address or script
4+ from the current wallet.
0 from the current wallet. (#15129)
373+ "removeaddress",
374+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
375+ {
376+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
377+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Remove the P2SH version of the script as well"},
378+ {"purgetransactions", RPCArg::Type::BOOL, /* default_val */ "false", "Remove existing transactions from the wallet"},
ISTM current practice is to use snake case for the field name
0 {"purge_transactions", RPCArg::Type::BOOL, /* default_val */ "false", "Whether to remove existing transactions from the wallet"},
372+ return RPCHelpMan{
373+ "removeaddress",
374+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
375+ {
376+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
377+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Remove the P2SH version of the script as well"},
0 {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Whether to remove the P2SH version of the script as well"},
375+ {
376+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
377+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Remove the P2SH version of the script as well"},
378+ {"purgetransactions", RPCArg::Type::BOOL, /* default_val */ "false", "Remove existing transactions from the wallet"},
379+ },
380+ RPCResult{RPCResult::Type::NONE, "", ""},
What should be returned?
It does seem ok to me to return null like this PR is currently doing. But if you wanted to extend it you could return lists of scripts and addresses removed by the recursive removescript/removeaddress implementation (maybe in a similar format similar to the importmulti requests parameter). There could also be a “warnings” field for example if you remove a known P2SH redeem script without removing the associated address.
390+
391+ // Whether to remove the p2sh version too
392+ bool fP2SH = false;
393+ if (!request.params[1].isNull()) {
394+ fP2SH = request.params[1].get_bool();
395+ }
can replace these lines (and have constness and follow variable naming style per doc/developer-notes.md) with
0const bool is_p2sh{request.params[1].isNull() ? false : request.params[1].get_bool()};
396+
397+ // Whether to remove the wallet transactions too
398+ bool fPurgeTxs = false;
399+ if (!request.params[2].isNull()) {
400+ fPurgeTxs = request.params[2].get_bool();
401+ }
idem
0const bool purge_txns{request.params[2].isNull() ? false : request.params[2].get_bool()};
322@@ -323,6 +323,103 @@ RPCHelpMan importaddress()
323 };
324 }
325
326+static void RemoveAddress(CWallet*, const CTxDestination&, bool);
327+static void RemoveScript(CWallet* const pwallet, const CScript& script, bool isRedeemScript, bool purgeTxs) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
consider using the current naming conventions in doc/developer-notes.md
0static void RemoveScript(CWallet* const pwallet, const CScript& script, bool is_redeem_script, bool purge_txns) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
887+ }
888+
889+ if (!HaveWatchOnly())
890+ NotifyWatchonlyChanged(false);
891+ if (!WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest))
892+ return false;
Please see doc/developer-notes.md about formatting conditionals
0 - If an `if` only has a single-statement `then`-clause, it can appear
1 on the same line as the `if`, without braces. In every other case,
2 braces are required, and the `then` and `else` clauses must appear
3 correctly indented on a new line.
511+ # Remove the address from node1
512+ self.nodes[1].removeaddress(address_to_import2)
513+
514+ # Check the address was removed and other address is unaffected
515+ assert(not self.nodes[1].getaddressinfo(address_to_import2)["iswatchonly"])
516+ assert(self.nodes[1].getaddressinfo(address_to_import)["ismine"])
assert
and assert not
without parens seems to be the most-used style in the functional tests
377+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Remove the P2SH version of the script as well"},
378+ {"purgetransactions", RPCArg::Type::BOOL, /* default_val */ "false", "Remove existing transactions from the wallet"},
379+ },
380+ RPCResult{RPCResult::Type::NONE, "", ""},
381+ RPCExamples{
382+ "\nRemove an address\n" + HelpExampleCli("removeaddress", "\"myaddress\"") +
369+
370+RPCHelpMan removeaddress()
371+{
372+ return RPCHelpMan{
373+ "removeaddress",
374+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
removeaddress
? (I don’t have an immediate suggestion.)
importaddress
can import a script as well, I think this name works.
1007+ }
1008+ }
1009+ }
1010+
1011+ if (txContainsScript && !IsFromMe(*wTx.tx) && !IsMine(*wTx.tx)) {
1012+ vWtx.push_back(MakeUnique<CWalletTx>(this, MakeTransactionRef(std::move(*wTx.tx))));
std::make_unique
in new code.
Concept ACK. Compiled successfully on Ubuntu. Tests passed.
Tried experimenting:
Adding address with importaddress
and removing with removeaddress
worked fine
Adding address with importmulti
gave checksum error maybe because of new checksum used for bech32 addresses but I am not sure.
importmulti
works fine I was using different public keys in both commands earlier.
Will try experimenting with addmultisigaddress
as well. It gave error (The wallet contains the private key for this address) if the multisig was created using addresses from wallet. Maybe addresses/keys that do not belong to wallet will work fine and will be removed with removeaddress
.
This functionality is only intended for use with non-watchonly addresses. https://bitcoincore.org/en/doc/0.21.0/rpc/wallet/addmultisigaddress/
So maybe removeaddress
will only work for addresses added by importaddress
or importmulti
. I was assuming addmultisigaddress
is a combination of createmultisig
and importmulti
.
373+ "removeaddress",
374+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
375+ {
376+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
377+ {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Whether to remove the P2SH version of the script as well"},
378+ {"purge_transactions", RPCArg::Type::BOOL, /* default */ "false", "Whether to remove existing transactions from the wallet"},
Rebased and fixed silent merge conflicts
Addressed @fanquake’s comment
ACK https://github.com/bitcoin/bitcoin/pull/15129/commits/723aed1c0c36ea32ac831ce51b923ec43edc764b
Major changes since last review: #15129 (review)
purge_transactions
argument of removeaddress
, that should be added.
It looks to me there is no test for
purge_transactions
argument ofremoveaddress
, that should be added.
Added
382+ RPCExamples{
383+ "\nRemove an address\n" + HelpExampleCli("removeaddress", "\"myaddress\"") +
384+ "\nRemove a script\n" + HelpExampleCli("removeaddress", "\"myscript\"") +
385+ "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeaddress", "\"myaddress\"")},
386+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
387+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
384+ "\nRemove a script\n" + HelpExampleCli("removeaddress", "\"myscript\"") +
385+ "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeaddress", "\"myaddress\"")},
386+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
387+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
388+ if (!wallet) return NullUniValue;
389+ CWallet& pwallet = *wallet.get();
503@@ -504,15 +504,58 @@ def run_test(self):
504 {"address": address_to_import},
505 {"spendable": False})
506
507- # 5. Import private key of the previously imported address on node1
508+ # 5. Remove the address from node1
503@@ -504,15 +504,58 @@ def run_test(self):
504 {"address": address_to_import},
505 {"spendable": False})
506
507- # 5. Import private key of the previously imported address on node1
508+ # 5. Remove the address from node1
509+ self.nodes[1].removeaddress(address_to_import, False, False)
510+
555+ self.nodes[1].removeaddress(address_to_import2)
556+
557+ # Check the address was removed and other address is unaffected
558+ assert not self.nodes[1].getaddressinfo(address_to_import2)["iswatchonly"]
559+ assert self.nodes[1].getaddressinfo(address_to_import)["ismine"]
560+
p2sh
flag as well, all these examples only touch addresses not scripts
1051+ }
1052+ }
1053+ }
1054+ }
1055+
1056+ if (txContainsScript && !IsFromMe(*wTx.tx) && !IsMine(*wTx.tx)) {
In commit “rpc: Added ability to remove watch only addresses” (b8eb5880693358951229512987beffcff51e0ecf)
Note (no changes requested): Check for isfromme / ismine mirrors existing logic in AddToWalletIfInvolvingMe and seems sufficient. I was initially thinking the IsFromMe check might fail to remove all relevant transactions because the for loop here ignores dependencies between transactions, so if IsFromMe was called on a child transaction and the parent was only removed later, the child might fail to be removed. But I think this not a problem because the PR only supports moving watch-only addresses, not spendable addresses, so no IsFromMe result on any transaction will be changed by watch only address removal.
373+ return RPCHelpMan{
374+ "removeaddress",
375+ "\nRemoves an address or script (in hex) that was being watched as if it were in your wallet but was not being used to spend. Requires a new wallet backup.\n",
376+ {
377+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
378+ {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to remove the P2SH version of the script as well"},
In commit “rpc: Added ability to remove watch only addresses” (b8eb5880693358951229512987beffcff51e0ecf)
This documentation is mirrored from the importaddress
documentation and it is good that you are keeping it consistent, but I think it is actually a little confusing and would be nice to clarify both places. I would maybe change the help string here to something like "If this is true and a script is being removed (not an address, if an address is being removed this is required to be false), then treat the script as a P2SH redeem script and remove the associated P2SH address from the wallet as well."
For importaddress
you could just use the same help text just replacing “remove[d]” with “add[ed]”.
It might be also be helpful to add a general note about the limitations of this RPC like “This RPC method does not currently support removing P2WSH or P2SH-P2WSH witness scripts at the same time as their associated addresses, the same way it does support this P2SH redeem scripts, so separate calls must be made to do this, first removing the addresses, and then removing the witness scripts.”
341+ throw JSONRPCError(RPC_WALLET_ERROR, "Error removing address/script from wallet");
342+ }
343+ if (is_redeem_script) {
344+ CScriptID scriptID = CScriptID(script);
345+ if (!spk_man.HaveCScript(scriptID)) {
346+ throw JSONRPCError(RPC_WALLET_ERROR, "Error removing p2sh redeemScript from wallet");
In commit “rpc: Added ability to remove watch only addresses” (b8eb5880693358951229512987beffcff51e0ecf)
Important: It seems dangerous to do this error check and raise an error after calling PurgeWatchOnly above. It would be better to check for this error condition early so the remove either fully fails or fully succeeds instead of half removing the redeem script.
340+ if (spk_man.HaveWatchOnly(script) && !spk_man.PurgeWatchOnly(script)) {
341+ throw JSONRPCError(RPC_WALLET_ERROR, "Error removing address/script from wallet");
342+ }
343+ if (is_redeem_script) {
344+ CScriptID scriptID = CScriptID(script);
345+ if (!spk_man.HaveCScript(scriptID)) {
In commit “rpc: Added ability to remove watch only addresses” (b8eb5880693358951229512987beffcff51e0ecf)
Important: Because there is no RemoveCScript function being called here to undo the effect of AddCScript/AddCScriptWithDB, the removeaddress
call does not fully undo the effects of the equivalent importaddress
call when the p2sh
argument is true, and a copy of the script is still left behind in mapScripts
and in the database file. It would be good to either note this as a limitation of the RPC in the documentation, or to fix it by actually removing the script and scriptid from the database.
re: #15129 (review)
a copy of the script is still left behind in
mapScripts
and in the database file
Might be good follow up with @achow101 about his earlier comment here #15129 (comment) since I’m assuming leaving data behind in mapscripts is wasteful but doesn’t cause real problems, but his comment seems to imply otherwise.
Also on Andrew’s other comment about the keypool, #15129 (comment), I think you could address it again with documentation just saying removeaddress
only undoes the effects of importaddress
, it doesn’t fully undo the effects of importmulti
if the address was imported with other data like public keys.
343+ if (is_redeem_script) {
344+ CScriptID scriptID = CScriptID(script);
345+ if (!spk_man.HaveCScript(scriptID)) {
346+ throw JSONRPCError(RPC_WALLET_ERROR, "Error removing p2sh redeemScript from wallet");
347+ }
348+ RemoveAddress(pwallet, ScriptHash(scriptID), purge_txns);
In commit “rpc: Added ability to remove watch only addresses” (b8eb5880693358951229512987beffcff51e0ecf)
Important: Again because this can throw it seems dangerous to call this here after PurgeWatchOnly has run and succeeded. Would be better to call PurgeWatchOnly after this so the RPC either completely fails or completely succeeds instead of partially succeeding.
🐙 This pull request conflicts with the target branch and needs rebase.
Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a “draft”.
1030+{
1031+ LOCK(cs_wallet);
1032+
1033+ std::vector<std::unique_ptr<CWalletTx>> vWtx;
1034+ for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) {
1035+ const CWalletTx& wTx = it->second;
0 for (auto& [txid, wTx] : mapWallet) {
1057+ vWtx.push_back(std::make_unique<CWalletTx>(this, MakeTransactionRef(std::move(*wTx.tx))));
1058+ }
1059+ }
1060+
1061+ for (const auto& wtx : vWtx) {
1062+ mapWallet.erase(wtx->GetHash());
At the very least, we need to delete it from wtxOrdered
too - and probably mapTxSpends
(see CWallet::ZapSelectTx
- maybe we should abstract this to a new method). Ubuntu hirsute (newer libc?) crashes in the tests due to a use-after-free trying to iterate wtxOrdered after removeaddress; I’m not sure if there’s a good way to generalise this test to detect the problem on older systems.
Not sure why, but simply doing wtxOrdered.erase(wtx->m_it_wtxOrdered);
doesn’t appear to fix it :/
492@@ -493,26 +493,90 @@ def run_test(self):
493 assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
494 self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
495
496- # 2. Import address from node2 to node1
497+ # Import address from node2 to node1
self.log.info
calls - is it still really about “sendtoaddress raises if an invalid fee_rate is passed” here?
@benthecarman You don’t plan to continue work on this? If not, I could take over.
Sorry I don’t have time to work on this at the moment. Feel free to take it over