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.
This commit is contained in:
Alex Morcos
2016-01-07 16:31:27 -05:00
parent 9e69717254
commit 01e06d1fa3
5 changed files with 125 additions and 12 deletions

View File

@@ -48,6 +48,8 @@ bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
*/
CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
/** @defgroup mapWallet
*
* @{
@@ -455,8 +457,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;
}
@@ -610,7 +615,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());
}
}
@@ -631,7 +636,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))
{
@@ -681,7 +686,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;
@@ -768,6 +779,63 @@ 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);
// 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);
@@ -976,7 +1044,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())
@@ -992,7 +1060,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())
@@ -1166,7 +1234,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));
}
}
@@ -1186,7 +1254,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;
@@ -2927,8 +2995,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
@@ -2956,4 +3025,3 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee);
}