From 731b89b8b53cb2ea4d2d5c8f2875def515766ea1 Mon Sep 17 00:00:00 2001 From: Gavin Andresen Date: Thu, 13 Feb 2014 20:12:51 -0500 Subject: Track and report wallet transaction clones Adds a "walletconflicts" array to transaction info; if a wallet transaction is mutated, the alternate transaction id or ids are reported there (usually the array will be empty). Metadata from the original transaction is copied to the mutant, so the transaction time and "from" account of the mutant are reported correctly. --- src/rpcwallet.cpp | 11 ++++-- src/wallet.cpp | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/wallet.h | 14 +++++++- src/walletdb.cpp | 2 +- 4 files changed, 121 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 2b49762d4a..97c4008dac 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -51,7 +51,12 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry) entry.push_back(Pair("blockindex", wtx.nIndex)); entry.push_back(Pair("blocktime", (boost::int64_t)(mapBlockIndex[wtx.hashBlock]->nTime))); } - entry.push_back(Pair("txid", wtx.GetHash().GetHex())); + uint256 hash = wtx.GetHash(); + entry.push_back(Pair("txid", hash.GetHex())); + Array conflicts; + BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts()) + conflicts.push_back(conflict.GetHex()); + entry.push_back(Pair("walletconflicts", conflicts)); entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime())); entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived)); BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) @@ -621,7 +626,7 @@ Value getbalance(const Array& params, bool fHelp) for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; - if (!wtx.IsTrusted()) + if (!wtx.IsTrusted() || wtx.GetBlocksToMaturity() > 0) continue; int64_t allFee; @@ -1325,6 +1330,8 @@ Value listaccounts(const Array& params, bool fHelp) string strSentAccount; list > listReceived; list > listSent; + if (wtx.GetBlocksToMaturity() > 0) + continue; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount); mapAccountBalances[strSentAccount] -= nFee; BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent) diff --git a/src/wallet.cpp b/src/wallet.cpp index 2853f375a5..c5010f15db 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -231,6 +231,82 @@ bool CWallet::SetMaxVersion(int nVersion) return true; } +set CWallet::GetConflicts(const uint256& txid) const +{ + set result; + AssertLockHeld(cs_wallet); + + std::map::const_iterator it = mapWallet.find(txid); + if (it == mapWallet.end()) + return result; + const CWalletTx& wtx = it->second; + + std::pair range; + + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + range = mapTxConflicts.equal_range(txin.prevout); + for (TxConflicts::const_iterator it = range.first; it != range.second; ++it) + result.insert(it->second); + } + return result; +} + +void CWallet::SyncMetaData(pair range) +{ + // We want all the wallet transactions in range to have the same metadata as + // the oldest (smallest nOrderPos). + // So: find smallest nOrderPos: + + int nMinOrderPos = std::numeric_limits::max(); + const CWalletTx* copyFrom = NULL; + for (TxConflicts::iterator it = range.first; it != range.second; ++it) + { + const uint256& hash = it->second; + int n = mapWallet[hash].nOrderPos; + if (n < nMinOrderPos) + { + nMinOrderPos = n; + copyFrom = &mapWallet[hash]; + } + } + // Now copy data from copyFrom to rest: + for (TxConflicts::iterator it = range.first; it != range.second; ++it) + { + const uint256& hash = it->second; + CWalletTx* copyTo = &mapWallet[hash]; + if (copyFrom == copyTo) continue; + copyTo->mapValue = copyFrom->mapValue; + copyTo->vOrderForm = copyFrom->vOrderForm; + // fTimeReceivedIsTxTime not copied on purpose + // nTimeReceived not copied on purpose + copyTo->nTimeSmart = copyFrom->nTimeSmart; + copyTo->fFromMe = copyFrom->fFromMe; + copyTo->strFromAccount = copyFrom->strFromAccount; + // vfSpent not copied on purpose + // nOrderPos not copied on purpose + // cached members not copied on purpose + } +} + +void CWallet::AddToConflicts(const uint256& wtxhash) +{ + assert(mapWallet.count(wtxhash)); + CWalletTx& thisTx = mapWallet[wtxhash]; + if (thisTx.IsCoinBase()) + return; + + BOOST_FOREACH(const CTxIn& txin, thisTx.vin) + { + mapTxConflicts.insert(make_pair(txin.prevout, wtxhash)); + + pair range; + range = mapTxConflicts.equal_range(txin.prevout); + if (range.first != range.second) + SyncMetaData(range); + } +} + bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) { if (IsCrypted()) @@ -385,9 +461,16 @@ void CWallet::MarkDirty() } } -bool CWallet::AddToWallet(const CWalletTx& wtxIn) +bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet) { uint256 hash = wtxIn.GetHash(); + + if (fFromLoadWallet) + { + mapWallet[hash] = wtxIn; + AddToConflicts(hash); + } + else { LOCK(cs_wallet); // Inserts only if not already there, returns tx inserted or tx found @@ -445,6 +528,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) wtxIn.GetHash().ToString(), wtxIn.hashBlock.ToString()); } + AddToConflicts(hash); } bool fUpdated = false; @@ -907,6 +991,18 @@ void CWalletTx::RelayWalletTransaction() } } +set CWalletTx::GetConflicts() const +{ + set result; + if (pwallet != NULL) + { + uint256 myHash = GetHash(); + result = pwallet->GetConflicts(myHash); + result.erase(myHash); + } + return result; +} + void CWallet::ResendWalletTransactions() { // Do this infrequently and randomly to avoid giving away @@ -980,7 +1076,7 @@ int64_t CWallet::GetUnconfirmedBalance() const for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted()) + if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0)) nTotal += pcoin->GetAvailableCredit(); } } diff --git a/src/wallet.h b/src/wallet.h index ed63e2a714..d36bab53bc 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -108,6 +108,12 @@ private: int64_t nNextResend; int64_t nLastResend; + // Used to detect and report conflicted transactions: + typedef std::multimap TxConflicts; + TxConflicts mapTxConflicts; + void AddToConflicts(const uint256& wtxhash); + void SyncMetaData(std::pair); + public: /// Main wallet lock. /// This lock protects all the fields added by CWallet @@ -151,6 +157,7 @@ public: } std::map mapWallet; + int64_t nOrderPosNext; std::map mapRequestCount; @@ -223,7 +230,7 @@ public: TxItems OrderedTxItems(std::list& acentries, std::string strAccount = ""); void MarkDirty(); - bool AddToWallet(const CWalletTx& wtxIn); + bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet=false); void SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock); bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate); void EraseFromWallet(const uint256 &hash); @@ -357,6 +364,9 @@ public: // get the current wallet format (the oldest client version guaranteed to understand this wallet) int GetVersion() { AssertLockHeld(cs_wallet); return nWalletVersion; } + // Get wallet transactions that conflict with given transaction (spend same outputs) + std::set GetConflicts(const uint256& txid) const; + /** Address book entry changed. * @note called with lock cs_wallet held. */ @@ -752,6 +762,8 @@ public: void AddSupportingTransactions(); bool AcceptWalletTransaction(); void RelayWalletTransaction(); + + std::set GetConflicts() const; }; diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 56349fcfbd..0b21ce7a66 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -382,7 +382,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, if (wtx.nOrderPos == -1) wss.fAnyUnordered = true; - pwallet->mapWallet[hash] = wtx; + pwallet->AddToWallet(wtx, true); //// debug print //LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString()); //LogPrintf(" %12"PRId64" %s %s %s\n", -- cgit v1.2.3