diff --git a/qa/rpc-tests/importprunedfunds.py b/qa/rpc-tests/importprunedfunds.py index bac144cd7..5cbdcde9a 100755 --- a/qa/rpc-tests/importprunedfunds.py +++ b/qa/rpc-tests/importprunedfunds.py @@ -115,5 +115,26 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], True) + #Remove transactions + + try: + self.nodes[1].removeprunedfunds(txnid1) + except JSONRPCException,e: + errorString = e.error['message'] + + assert('does not exist' in errorString) + + balance1 = Decimal(self.nodes[1].getbalance("", 0, True)) + assert_equal(balance1, Decimal('0.075')) + + + self.nodes[1].removeprunedfunds(txnid2) + balance2 = Decimal(self.nodes[1].getbalance("", 0, True)) + assert_equal(balance2, Decimal('0.025')) + + self.nodes[1].removeprunedfunds(txnid3) + balance3 = Decimal(self.nodes[1].getbalance("", 0, True)) + assert_equal(balance3, Decimal('0.0')) + if __name__ == '__main__': ImportPrunedFundsTest ().main () diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 899ed1b3d..bb40cf724 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -310,6 +310,44 @@ UniValue importprunedfunds(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); } +UniValue removeprunedfunds(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "removeprunedfunds \"txid\"\n" + "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will effect wallet balances.\n" + "\nArguments:\n" + "1. \"txid\" (string, required) The hex-encoded id of the transaction you are deleting\n" + "\nExamples:\n" + + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + uint256 hash; + hash.SetHex(params[0].get_str()); + vector vHash; + vHash.push_back(hash); + vector vHashOut; + + if(pwalletMain->ZapSelectTx(vHash, vHashOut) != DB_LOAD_OK) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Could not properly delete the transaction."); + } + + if(vHashOut.empty()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction does not exist in wallet."); + } + + ThreadFlushWalletDB(pwalletMain->strWalletFile); + + return NullUniValue; +} + UniValue importpubkey(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index fbe95a14c..29f7802c5 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2504,6 +2504,7 @@ extern UniValue importpubkey(const UniValue& params, bool fHelp); extern UniValue dumpwallet(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp); extern UniValue importprunedfunds(const UniValue& params, bool fHelp); +extern UniValue removeprunedfunds(const UniValue& params, bool fHelp); const CRPCCommand vWalletRPCCommands[] = { // category name actor (function) okSafeMode @@ -2552,6 +2553,7 @@ const CRPCCommand vWalletRPCCommands[] = { "wallet", "walletlock", &walletlock, true }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, { "wallet", "walletpassphrase", &walletpassphrase, true }, + { "wallet", "removeprunedfunds", &removeprunedfunds, true }, }; void walletRegisterRPCCommands() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7d1928dd6..801ef9868 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2362,6 +2362,31 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) return DB_LOAD_OK; } +DBErrors CWallet::ZapSelectTx(vector& vHashIn, vector& vHashOut) +{ + if (!fFileBacked) + return DB_LOAD_OK; + DBErrors nZapSelectTxRet = CWalletDB(strWalletFile,"cr+").ZapSelectTx(this, vHashIn, vHashOut); + if (nZapSelectTxRet == DB_NEED_REWRITE) + { + if (CDB::Rewrite(strWalletFile, "\x04pool")) + { + LOCK(cs_wallet); + setKeyPool.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // that requires a new key. + } + } + + if (nZapSelectTxRet != DB_LOAD_OK) + return nZapSelectTxRet; + + MarkDirty(); + + return DB_LOAD_OK; + +} DBErrors CWallet::ZapWalletTx(std::vector& vWtx) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e37d972a1..5db36f52d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -792,6 +792,7 @@ public: DBErrors LoadWallet(bool& fFirstRunRet); DBErrors ZapWalletTx(std::vector& vWtx); + DBErrors ZapSelectTx(std::vector& vHashIn, std::vector& vHashOut); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 0a4a1dae2..f2b5408e9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -785,6 +785,45 @@ DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector& vTxHash, vec return result; } +DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, vector& vTxHashIn, vector& vTxHashOut) +{ + // build list of wallet TXs and hashes + vector vTxHash; + vector vWtx; + DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx); + if (err != DB_LOAD_OK) { + return err; + } + + std::sort(vTxHash.begin(), vTxHash.end()); + std::sort(vTxHashIn.begin(), vTxHashIn.end()); + + // erase each matching wallet TX + bool delerror = false; + vector::iterator it = vTxHashIn.begin(); + BOOST_FOREACH (uint256 hash, vTxHash) { + while (it < vTxHashIn.end() && (*it) < hash) { + it++; + } + if (it == vTxHashIn.end()) { + break; + } + else if ((*it) == hash) { + pwallet->mapWallet.erase(hash); + if(!EraseTx(hash)) { + LogPrint("db", "Transaction was found for deletion but returned database error: %s\n", hash.GetHex()); + delerror = true; + } + vTxHashOut.push_back(hash); + } + } + + if (delerror) { + return DB_CORRUPT; + } + return DB_LOAD_OK; +} + DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector& vWtx) { // build list of wallet TXs diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 7e8cc4084..fe6c36634 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -130,6 +130,7 @@ public: DBErrors LoadWallet(CWallet* pwallet); DBErrors FindWalletTx(CWallet* pwallet, std::vector& vTxHash, std::vector& vWtx); DBErrors ZapWalletTx(CWallet* pwallet, std::vector& vWtx); + DBErrors ZapSelectTx(CWallet* pwallet, std::vector& vHashIn, std::vector& vHashOut); static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename);