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.
This commit is contained in:
Gavin Andresen
2014-02-13 20:12:51 -05:00
parent 9a3d936fc2
commit 731b89b8b5
5 changed files with 128 additions and 9 deletions

View File

@@ -231,6 +231,82 @@ bool CWallet::SetMaxVersion(int nVersion)
return true;
}
set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
set<uint256> result;
AssertLockHeld(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end())
return result;
const CWalletTx& wtx = it->second;
std::pair<TxConflicts::const_iterator, TxConflicts::const_iterator> 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<TxConflicts::iterator, TxConflicts::iterator> 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<int>::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<TxConflicts::iterator, TxConflicts::iterator> 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<uint256> CWalletTx::GetConflicts() const
{
set<uint256> 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<uint256, CWalletTx>::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();
}
}