diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/rpcserver.cpp | 1 | ||||
-rw-r--r-- | src/rpcserver.h | 1 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 34 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 101 | ||||
-rw-r--r-- | src/wallet/wallet.h | 11 |
5 files changed, 131 insertions, 17 deletions
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 78d6898bc3..53c368b271 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -346,6 +346,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false }, { "wallet", "gettransaction", &gettransaction, false }, + { "wallet", "abandontransaction", &abandontransaction, false }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false }, { "wallet", "getwalletinfo", &getwalletinfo, false }, { "wallet", "importprivkey", &importprivkey, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 9dce318872..29f5036580 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -223,6 +223,7 @@ extern UniValue listaddressgroupings(const UniValue& params, bool fHelp); extern UniValue listaccounts(const UniValue& params, bool fHelp); extern UniValue listsinceblock(const UniValue& params, bool fHelp); extern UniValue gettransaction(const UniValue& params, bool fHelp); +extern UniValue abandontransaction(const UniValue& params, bool fHelp); extern UniValue backupwallet(const UniValue& params, bool fHelp); extern UniValue keypoolrefill(const UniValue& params, bool fHelp); extern UniValue walletpassphrase(const UniValue& params, bool fHelp); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 374f2fd401..9e7d9cc98a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1764,6 +1764,40 @@ UniValue gettransaction(const UniValue& params, bool fHelp) return entry; } +UniValue abandontransaction(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "abandontransaction \"txid\"\n" + "\nMark in-wallet transaction <txid> as abandoned\n" + "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" + "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" + "It only works on transactions which are not included in a block and are not currently in the mempool.\n" + "It has no effect on transactions which are already conflicted or abandoned.\n" + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id\n" + "\nResult:\n" + "\nExamples:\n" + + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + uint256 hash; + hash.SetHex(params[0].get_str()); + + if (!pwalletMain->mapWallet.count(hash)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); + if (!pwalletMain->AbandonTransaction(hash)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); + + return NullUniValue; +} + UniValue backupwallet(const UniValue& params, bool fHelp) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6b2ebebba7..cbc71aa16b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -54,6 +54,8 @@ CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE); */ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); +const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + /** @defgroup mapWallet * * @{ @@ -461,8 +463,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const { const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); - if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) - return true; // Spent + if (mit != mapWallet.end()) { + int depth = mit->second.GetDepthInMainChain(); + if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) + return true; // Spent + } } return false; } @@ -616,7 +621,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD BOOST_FOREACH(const CTxIn& txin, wtx.vin) { if (mapWallet.count(txin.prevout.hash)) { CWalletTx& prevtx = mapWallet[txin.prevout.hash]; - if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) { + if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { MarkConflicted(prevtx.hashBlock, wtx.GetHash()); } } @@ -637,7 +642,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0))); wtx.nTimeSmart = wtx.nTimeReceived; - if (!wtxIn.hashBlock.IsNull()) + if (!wtxIn.hashUnset()) { if (mapBlockIndex.count(wtxIn.hashBlock)) { @@ -687,7 +692,13 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD if (!fInsertedNew) { // Merge - if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock) + if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) + { + wtx.hashBlock = wtxIn.hashBlock; + fUpdated = true; + } + // If no longer abandoned, update + if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; @@ -774,6 +785,64 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl return false; } +bool CWallet::AbandonTransaction(const uint256& hashTx) +{ + LOCK2(cs_main, cs_wallet); + + // Do not flush the wallet here for performance reasons + CWalletDB walletdb(strWalletFile, "r+", false); + + std::set<uint256> todo; + std::set<uint256> done; + + // Can't mark abandoned if confirmed or in mempool + assert(mapWallet.count(hashTx)); + CWalletTx& origtx = mapWallet[hashTx]; + if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) { + return false; + } + + todo.insert(hashTx); + + while (!todo.empty()) { + uint256 now = *todo.begin(); + todo.erase(now); + done.insert(now); + assert(mapWallet.count(now)); + CWalletTx& wtx = mapWallet[now]; + int currentconfirm = wtx.GetDepthInMainChain(); + // If the orig tx was not in block, none of its spends can be + assert(currentconfirm <= 0); + // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} + if (currentconfirm == 0 && !wtx.isAbandoned()) { + // If the orig tx was not in block/mempool, none of its spends can be in mempool + assert(!wtx.InMempool()); + wtx.nIndex = -1; + wtx.setAbandoned(); + wtx.MarkDirty(); + wtx.WriteToDisk(&walletdb); + NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED); + // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too + TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0)); + while (iter != mapTxSpends.end() && iter->first.hash == now) { + if (!done.count(iter->second)) { + todo.insert(iter->second); + } + iter++; + } + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be recomputed + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + if (mapWallet.count(txin.prevout.hash)) + mapWallet[txin.prevout.hash].MarkDirty(); + } + } + } + + return true; +} + void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) { LOCK2(cs_main, cs_wallet); @@ -790,14 +859,14 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) // Do not flush the wallet here for performance reasons CWalletDB walletdb(strWalletFile, "r+", false); - std::deque<uint256> todo; + std::set<uint256> todo; std::set<uint256> done; - todo.push_back(hashTx); + todo.insert(hashTx); while (!todo.empty()) { - uint256 now = todo.front(); - todo.pop_front(); + uint256 now = *todo.begin(); + todo.erase(now); done.insert(now); assert(mapWallet.count(now)); CWalletTx& wtx = mapWallet[now]; @@ -813,7 +882,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.hash == now) { if (!done.count(iter->second)) { - todo.push_back(iter->second); + todo.insert(iter->second); } iter++; } @@ -982,7 +1051,7 @@ int CWalletTx::GetRequestCount() const if (IsCoinBase()) { // Generated block - if (!hashBlock.IsNull()) + if (!hashUnset()) { map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); if (mi != pwallet->mapRequestCount.end()) @@ -998,7 +1067,7 @@ int CWalletTx::GetRequestCount() const nRequests = (*mi).second; // How about the block it's in? - if (nRequests == 0 && !hashBlock.IsNull()) + if (nRequests == 0 && !hashUnset()) { map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); if (mi != pwallet->mapRequestCount.end()) @@ -1172,7 +1241,7 @@ void CWallet::ReacceptWalletTransactions() int nDepth = wtx.GetDepthInMainChain(); - if (!wtx.IsCoinBase() && nDepth == 0) { + if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); } } @@ -1192,7 +1261,7 @@ bool CWalletTx::RelayWalletTransaction() assert(pwallet->GetBroadcastTransactions()); if (!IsCoinBase()) { - if (GetDepthInMainChain() == 0) { + if (GetDepthInMainChain() == 0 && !isAbandoned()) { LogPrintf("Relaying wtx %s\n", GetHash().ToString()); RelayTransaction((CTransaction)*this); return true; @@ -2931,8 +3000,9 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block) int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const { - if (hashBlock.IsNull()) + if (hashUnset()) return 0; + AssertLockHeld(cs_main); // Find the block it claims to be in @@ -2960,4 +3030,3 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) CValidationState state; return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee); } - diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9bbd52f14c..2176a5ff66 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -156,6 +156,10 @@ struct COutputEntry /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx : public CTransaction { +private: + /** Constant used in hashBlock to indicate tx has been abandoned */ + static const uint256 ABANDON_HASH; + public: uint256 hashBlock; @@ -207,6 +211,9 @@ public: bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } int GetBlocksToMaturity() const; bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true); + bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } + bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } + void setAbandoned() { hashBlock = ABANDON_HASH; } }; /** @@ -567,7 +574,6 @@ private: /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); - void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); public: @@ -865,6 +871,9 @@ public: bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } + + /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ + bool AbandonTransaction(const uint256& hashTx); }; /** A key allocated from the key pool. */ |