Skip to content

Commit 01e06d1

Browse files
committed
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 9e69717 commit 01e06d1

File tree

5 files changed

+125
-12
lines changed

5 files changed

+125
-12
lines changed

src/rpcserver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ static const CRPCCommand vRPCCommands[] =
346346
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false },
347347
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false },
348348
{ "wallet", "gettransaction", &gettransaction, false },
349+
{ "wallet", "abandontransaction", &abandontransaction, false },
349350
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false },
350351
{ "wallet", "getwalletinfo", &getwalletinfo, false },
351352
{ "wallet", "importprivkey", &importprivkey, true },

src/rpcserver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ extern UniValue listaddressgroupings(const UniValue& params, bool fHelp);
221221
extern UniValue listaccounts(const UniValue& params, bool fHelp);
222222
extern UniValue listsinceblock(const UniValue& params, bool fHelp);
223223
extern UniValue gettransaction(const UniValue& params, bool fHelp);
224+
extern UniValue abandontransaction(const UniValue& params, bool fHelp);
224225
extern UniValue backupwallet(const UniValue& params, bool fHelp);
225226
extern UniValue keypoolrefill(const UniValue& params, bool fHelp);
226227
extern UniValue walletpassphrase(const UniValue& params, bool fHelp);

src/wallet/rpcwallet.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,40 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
17641764
return entry;
17651765
}
17661766

1767+
UniValue abandontransaction(const UniValue& params, bool fHelp)
1768+
{
1769+
if (!EnsureWalletIsAvailable(fHelp))
1770+
return NullUniValue;
1771+
1772+
if (fHelp || params.size() != 1)
1773+
throw runtime_error(
1774+
"abandontransaction \"txid\"\n"
1775+
"\nMark in-wallet transaction <txid> as abandoned\n"
1776+
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
1777+
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
1778+
"It only works on transactions which are not included in a block and are not currently in the mempool.\n"
1779+
"It has no effect on transactions which are already conflicted or abandoned.\n"
1780+
"\nArguments:\n"
1781+
"1. \"txid\" (string, required) The transaction id\n"
1782+
"\nResult:\n"
1783+
"\nExamples:\n"
1784+
+ HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
1785+
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
1786+
);
1787+
1788+
LOCK2(cs_main, pwalletMain->cs_wallet);
1789+
1790+
uint256 hash;
1791+
hash.SetHex(params[0].get_str());
1792+
1793+
if (!pwalletMain->mapWallet.count(hash))
1794+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
1795+
if (!pwalletMain->AbandonTransaction(hash))
1796+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
1797+
1798+
return NullUniValue;
1799+
}
1800+
17671801

17681802
UniValue backupwallet(const UniValue& params, bool fHelp)
17691803
{

src/wallet/wallet.cpp

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
4848
*/
4949
CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
5050

51+
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
52+
5153
/** @defgroup mapWallet
5254
*
5355
* @{
@@ -455,8 +457,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
455457
{
456458
const uint256& wtxid = it->second;
457459
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
458-
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0)
459-
return true; // Spent
460+
if (mit != mapWallet.end()) {
461+
int depth = mit->second.GetDepthInMainChain();
462+
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
463+
return true; // Spent
464+
}
460465
}
461466
return false;
462467
}
@@ -610,7 +615,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
610615
BOOST_FOREACH(const CTxIn& txin, wtx.vin) {
611616
if (mapWallet.count(txin.prevout.hash)) {
612617
CWalletTx& prevtx = mapWallet[txin.prevout.hash];
613-
if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) {
618+
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
614619
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
615620
}
616621
}
@@ -631,7 +636,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
631636
wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
632637

633638
wtx.nTimeSmart = wtx.nTimeReceived;
634-
if (!wtxIn.hashBlock.IsNull())
639+
if (!wtxIn.hashUnset())
635640
{
636641
if (mapBlockIndex.count(wtxIn.hashBlock))
637642
{
@@ -681,7 +686,13 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
681686
if (!fInsertedNew)
682687
{
683688
// Merge
684-
if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock)
689+
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock)
690+
{
691+
wtx.hashBlock = wtxIn.hashBlock;
692+
fUpdated = true;
693+
}
694+
// If no longer abandoned, update
695+
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
685696
{
686697
wtx.hashBlock = wtxIn.hashBlock;
687698
fUpdated = true;
@@ -768,6 +779,63 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
768779
return false;
769780
}
770781

782+
bool CWallet::AbandonTransaction(const uint256& hashTx)
783+
{
784+
LOCK2(cs_main, cs_wallet);
785+
786+
// Do not flush the wallet here for performance reasons
787+
CWalletDB walletdb(strWalletFile, "r+", false);
788+
789+
std::set<uint256> todo;
790+
std::set<uint256> done;
791+
792+
// Can't mark abandoned if confirmed or in mempool
793+
assert(mapWallet.count(hashTx));
794+
CWalletTx& origtx = mapWallet[hashTx];
795+
if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
796+
return false;
797+
}
798+
799+
todo.insert(hashTx);
800+
801+
while (!todo.empty()) {
802+
uint256 now = *todo.begin();
803+
todo.erase(now);
804+
done.insert(now);
805+
assert(mapWallet.count(now));
806+
CWalletTx& wtx = mapWallet[now];
807+
int currentconfirm = wtx.GetDepthInMainChain();
808+
// If the orig tx was not in block, none of its spends can be
809+
assert(currentconfirm <= 0);
810+
// if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
811+
if (currentconfirm == 0 && !wtx.isAbandoned()) {
812+
// If the orig tx was not in block/mempool, none of its spends can be in mempool
813+
assert(!wtx.InMempool());
814+
wtx.nIndex = -1;
815+
wtx.setAbandoned();
816+
wtx.MarkDirty();
817+
wtx.WriteToDisk(&walletdb);
818+
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
819+
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0));
820+
while (iter != mapTxSpends.end() && iter->first.hash == now) {
821+
if (!done.count(iter->second)) {
822+
todo.insert(iter->second);
823+
}
824+
iter++;
825+
}
826+
// If a transaction changes 'conflicted' state, that changes the balance
827+
// available of the outputs it spends. So force those to be recomputed
828+
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
829+
{
830+
if (mapWallet.count(txin.prevout.hash))
831+
mapWallet[txin.prevout.hash].MarkDirty();
832+
}
833+
}
834+
}
835+
836+
return true;
837+
}
838+
771839
void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
772840
{
773841
LOCK2(cs_main, cs_wallet);
@@ -976,7 +1044,7 @@ int CWalletTx::GetRequestCount() const
9761044
if (IsCoinBase())
9771045
{
9781046
// Generated block
979-
if (!hashBlock.IsNull())
1047+
if (!hashUnset())
9801048
{
9811049
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
9821050
if (mi != pwallet->mapRequestCount.end())
@@ -992,7 +1060,7 @@ int CWalletTx::GetRequestCount() const
9921060
nRequests = (*mi).second;
9931061

9941062
// How about the block it's in?
995-
if (nRequests == 0 && !hashBlock.IsNull())
1063+
if (nRequests == 0 && !hashUnset())
9961064
{
9971065
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
9981066
if (mi != pwallet->mapRequestCount.end())
@@ -1166,7 +1234,7 @@ void CWallet::ReacceptWalletTransactions()
11661234

11671235
int nDepth = wtx.GetDepthInMainChain();
11681236

1169-
if (!wtx.IsCoinBase() && nDepth == 0) {
1237+
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
11701238
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
11711239
}
11721240
}
@@ -1186,7 +1254,7 @@ bool CWalletTx::RelayWalletTransaction()
11861254
assert(pwallet->GetBroadcastTransactions());
11871255
if (!IsCoinBase())
11881256
{
1189-
if (GetDepthInMainChain() == 0) {
1257+
if (GetDepthInMainChain() == 0 && !isAbandoned()) {
11901258
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
11911259
RelayTransaction((CTransaction)*this);
11921260
return true;
@@ -2927,8 +2995,9 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
29272995

29282996
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
29292997
{
2930-
if (hashBlock.IsNull())
2998+
if (hashUnset())
29312999
return 0;
3000+
29323001
AssertLockHeld(cs_main);
29333002

29343003
// Find the block it claims to be in
@@ -2956,4 +3025,3 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
29563025
CValidationState state;
29573026
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee);
29583027
}
2959-

src/wallet/wallet.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ struct COutputEntry
155155
/** A transaction with a merkle branch linking it to the block chain. */
156156
class CMerkleTx : public CTransaction
157157
{
158+
private:
159+
/** Constant used in hashBlock to indicate tx has been abandoned */
160+
static const uint256 ABANDON_HASH;
161+
158162
public:
159163
uint256 hashBlock;
160164

@@ -206,6 +210,9 @@ class CMerkleTx : public CTransaction
206210
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
207211
int GetBlocksToMaturity() const;
208212
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
213+
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
214+
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
215+
void setAbandoned() { hashBlock = ABANDON_HASH; }
209216
};
210217

211218
/**
@@ -486,7 +493,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
486493
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
487494
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
488495

489-
490496
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
491497

492498
public:
@@ -783,6 +789,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
783789
bool GetBroadcastTransactions() const { return fBroadcastTransactions; }
784790
/** Set whether this wallet broadcasts transactions. */
785791
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
792+
793+
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
794+
bool AbandonTransaction(const uint256& hashTx);
786795
};
787796

788797
/** A key allocated from the key pool. */

0 commit comments

Comments
 (0)