Skip to content

Commit 778ebf3

Browse files
morcosWarrows
authored andcommitted
Add new rpc call: abandontransaction
Unconfirmed transactions that are not in your mempool either due to eviction or other means may be unlikely to be mined. abandontransaction gives the wallet a way to no longer consider as spent the coins that are inputs to such a transaction. All dependent transactions in the wallet will also be marked as abandoned.
1 parent 0e86c3e commit 778ebf3

File tree

5 files changed

+123
-13
lines changed

5 files changed

+123
-13
lines changed

src/rpc/server.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ static const CRPCCommand vRPCCommands[] =
415415
{"wallet", "getstakingstatus", &getstakingstatus, false, false, true},
416416
{"wallet", "getstakesplitthreshold", &getstakesplitthreshold, false, false, true},
417417
{"wallet", "gettransaction", &gettransaction, false, false, true},
418+
{"wallet", "abandontransaction", &abandontransaction, false, false, true},
418419
{"wallet", "getunconfirmedbalance", &getunconfirmedbalance, false, false, true},
419420
{"wallet", "getwalletinfo", &getwalletinfo, false, false, true},
420421
{"wallet", "importprivkey", &importprivkey, true, false, true},

src/rpc/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ extern UniValue listaddressgroupings(const UniValue& params, bool fHelp);
236236
extern UniValue listaccounts(const UniValue& params, bool fHelp);
237237
extern UniValue listsinceblock(const UniValue& params, bool fHelp);
238238
extern UniValue gettransaction(const UniValue& params, bool fHelp);
239+
extern UniValue abandontransaction(const UniValue& params, bool fHelp);
239240
extern UniValue backupwallet(const UniValue& params, bool fHelp);
240241
extern UniValue keypoolrefill(const UniValue& params, bool fHelp);
241242
extern UniValue walletpassphrase(const UniValue& params, bool fHelp);

src/wallet/rpcwallet.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,39 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
16881688
return entry;
16891689
}
16901690

1691+
UniValue abandontransaction(const UniValue& params, bool fHelp)
1692+
{
1693+
if (fHelp || params.size() != 1)
1694+
throw std::runtime_error(
1695+
"abandontransaction \"txid\"\n"
1696+
"\nMark in-wallet transaction <txid> as abandoned\n"
1697+
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
1698+
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
1699+
"It only works on transactions which are not included in a block and are not currently in the mempool.\n"
1700+
"It has no effect on transactions which are already conflicted or abandoned.\n"
1701+
"\nArguments:\n"
1702+
"1. \"txid\" (string, required) The transaction id\n"
1703+
"\nResult:\n"
1704+
"\nExamples:\n"
1705+
+ HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
1706+
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
1707+
);
1708+
1709+
EnsureWalletIsUnlocked();
1710+
1711+
LOCK2(cs_main, pwalletMain->cs_wallet);
1712+
1713+
uint256 hash;
1714+
hash.SetHex(params[0].get_str());
1715+
1716+
if (!pwalletMain->mapWallet.count(hash))
1717+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
1718+
if (!pwalletMain->AbandonTransaction(hash))
1719+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
1720+
1721+
return NullUniValue;
1722+
}
1723+
16911724

16921725
UniValue backupwallet(const UniValue& params, bool fHelp)
16931726
{

src/wallet/wallet.cpp

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ int64_t nStartupTime = GetTime(); //!< Client startup time for use with automint
6161
*/
6262
CFeeRate CWallet::minTxFee = CFeeRate(10000);
6363

64+
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
65+
6466
/** @defgroup mapWallet
6567
*
6668
* @{
@@ -469,8 +471,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
469471
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
470472
const uint256& wtxid = it->second;
471473
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
472-
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0)
473-
return true; // Spent
474+
if (mit != mapWallet.end()) {
475+
int depth = mit->second.GetDepthInMainChain();
476+
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
477+
return true; // Spent
478+
}
474479
}
475480
return false;
476481
}
@@ -694,7 +699,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
694699
for (const CTxIn& txin : wtx.vin) {
695700
if (mapWallet.count(txin.prevout.hash)) {
696701
CWalletTx& prevtx = mapWallet[txin.prevout.hash];
697-
if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) {
702+
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
698703
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
699704
}
700705
}
@@ -704,7 +709,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
704709
bool fUpdated = false;
705710
if (!fInsertedNew) {
706711
// Merge
707-
if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock) {
712+
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) {
713+
wtx.hashBlock = wtxIn.hashBlock;
714+
fUpdated = true;
715+
}
716+
// If no longer abandoned, update
717+
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) {
708718
wtx.hashBlock = wtxIn.hashBlock;
709719
fUpdated = true;
710720
}
@@ -752,7 +762,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
752762
{
753763
{
754764
AssertLockHeld(cs_wallet);
755-
765+
756766
if (pblock) {
757767
for (const CTxIn& txin : tx.vin) {
758768
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
@@ -765,7 +775,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
765775
}
766776
}
767777
}
768-
778+
769779
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
770780
if (fExisted && !fUpdate) return false;
771781
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
@@ -783,6 +793,63 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
783793
return false;
784794
}
785795

796+
bool CWallet::AbandonTransaction(const uint256& hashTx)
797+
{
798+
LOCK2(cs_main, cs_wallet);
799+
800+
// Do not flush the wallet here for performance reasons
801+
CWalletDB walletdb(strWalletFile, "r+", false);
802+
803+
std::set<uint256> todo;
804+
std::set<uint256> done;
805+
806+
// Can't mark abandoned if confirmed or in mempool
807+
assert(mapWallet.count(hashTx));
808+
CWalletTx& origtx = mapWallet[hashTx];
809+
if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
810+
return false;
811+
}
812+
813+
todo.insert(hashTx);
814+
815+
while (!todo.empty()) {
816+
uint256 now = *todo.begin();
817+
todo.erase(now);
818+
done.insert(now);
819+
assert(mapWallet.count(now));
820+
CWalletTx& wtx = mapWallet[now];
821+
int currentconfirm = wtx.GetDepthInMainChain();
822+
// If the orig tx was not in block, none of its spends can be
823+
assert(currentconfirm <= 0);
824+
// if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
825+
if (currentconfirm == 0 && !wtx.isAbandoned()) {
826+
// If the orig tx was not in block/mempool, none of its spends can be in mempool
827+
assert(!wtx.InMempool());
828+
wtx.nIndex = -1;
829+
wtx.setAbandoned();
830+
wtx.MarkDirty();
831+
wtx.WriteToDisk(&walletdb);
832+
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
833+
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0));
834+
while (iter != mapTxSpends.end() && iter->first.hash == now) {
835+
if (!done.count(iter->second)) {
836+
todo.insert(iter->second);
837+
}
838+
iter++;
839+
}
840+
// If a transaction changes 'conflicted' state, that changes the balance
841+
// available of the outputs it spends. So force those to be recomputed
842+
for (const CTxIn& txin: wtx.vin)
843+
{
844+
if (mapWallet.count(txin.prevout.hash))
845+
mapWallet[txin.prevout.hash].MarkDirty();
846+
}
847+
}
848+
}
849+
850+
return true;
851+
}
852+
786853
void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
787854
{
788855
LOCK2(cs_main, cs_wallet);
@@ -828,7 +895,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
828895
}
829896
// If a transaction changes 'conflicted' state, that changes the balance
830897
// available of the outputs it spends. So force those to be recomputed
831-
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
898+
for (const CTxIn& txin: wtx.vin)
832899
{
833900
if (mapWallet.count(txin.prevout.hash))
834901
mapWallet[txin.prevout.hash].MarkDirty();
@@ -987,7 +1054,7 @@ int CWalletTx::GetRequestCount() const
9871054
LOCK(pwallet->cs_wallet);
9881055
if (IsCoinBase()) {
9891056
// Generated block
990-
if (hashBlock != 0) {
1057+
if (!hashUnset()) {
9911058
std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
9921059
if (mi != pwallet->mapRequestCount.end())
9931060
nRequests = (*mi).second;
@@ -999,7 +1066,7 @@ int CWalletTx::GetRequestCount() const
9991066
nRequests = (*mi).second;
10001067

10011068
// How about the block it's in?
1002-
if (nRequests == 0 && hashBlock != 0) {
1069+
if (nRequests == 0 && !hashUnset()) {
10031070
std::map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
10041071
if (mi != pwallet->mapRequestCount.end())
10051072
nRequests = (*mi).second;
@@ -1451,7 +1518,7 @@ void CWallet::ReacceptWalletTransactions()
14511518

14521519
int nDepth = wtx.GetDepthInMainChain();
14531520

1454-
if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth == 0) {
1521+
if (!wtx.IsCoinBase() && !wtx.IsCoinStake() && nDepth == 0 && !wtx.isAbandoned()) {
14551522
// Try to add to memory pool
14561523
LOCK(mempool.cs);
14571524
wtx.AcceptToMemoryPool(false);
@@ -1472,7 +1539,7 @@ void CWalletTx::RelayWalletTransaction(std::string strCommand)
14721539
{
14731540
LOCK(cs_main);
14741541
if (!IsCoinBase()) {
1475-
if (GetDepthInMainChain() == 0) {
1542+
if (GetDepthInMainChain() == 0 && !isAbandoned()) {
14761543
uint256 hash = GetHash();
14771544
LogPrintf("Relaying wtx %s\n", hash.ToString());
14781545

@@ -3748,7 +3815,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
37483815

37493816
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
37503817
{
3751-
if (hashBlock == 0)
3818+
if (hashUnset())
37523819
return 0;
37533820
AssertLockHeld(cs_main);
37543821

@@ -5463,7 +5530,7 @@ bool CWalletTx::IsTrusted() const
54635530
return false;
54645531
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
54655532
return false;
5466-
5533+
54675534
// Don't trust unconfirmed transactions from us unless they are in the mempool.
54685535
{
54695536
LOCK(mempool.cs);

src/wallet/wallet.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
531531
//! Get wallet transactions that conflict with given transaction (spend same outputs)
532532
std::set<uint256> GetConflicts(const uint256& txid) const;
533533

534+
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
535+
bool AbandonTransaction(const uint256& hashTx);
536+
534537
/**
535538
* Address book entry changed.
536539
* @note called with lock cs_wallet held.
@@ -617,6 +620,8 @@ class CMerkleTx : public CTransaction
617620
{
618621
private:
619622
int GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const;
623+
/** Constant used in hashBlock to indicate tx has been abandoned */
624+
static const uint256 ABANDON_HASH;
620625

621626
public:
622627
uint256 hashBlock;
@@ -680,6 +685,9 @@ class CMerkleTx : public CTransaction
680685
bool AcceptToMemoryPool(bool fLimitFree = true, bool fRejectInsaneFee = true, bool ignoreFees = false);
681686
int GetTransactionLockSignatures() const;
682687
bool IsTransactionLockTimedOut() const;
688+
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
689+
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
690+
void setAbandoned() { hashBlock = ABANDON_HASH; }
683691
};
684692

685693
/**

0 commit comments

Comments
 (0)