Browse Source

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.
0.10
Gavin Andresen 11 years ago
parent
commit
731b89b8b5
  1. 10
      qa/rpc-tests/txnmall.sh
  2. 11
      src/rpcwallet.cpp
  3. 100
      src/wallet.cpp
  4. 14
      src/wallet.h
  5. 2
      src/walletdb.cpp

10
qa/rpc-tests/txnmall.sh

@ -88,8 +88,10 @@ B2ADDRESS=$( $CLI $B2ARGS getnewaddress )
# Have B1 create two transactions; second will # Have B1 create two transactions; second will
# spend change from first, since B1 starts with only a single # spend change from first, since B1 starts with only a single
# 50 bitcoin output: # 50 bitcoin output:
TXID1=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 1.0 ) $CLI $B1ARGS move "" "foo" 10.0
TXID2=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 2.0 ) $CLI $B1ARGS move "" "bar" 10.0
TXID1=$( $CLI $B1ARGS sendfrom foo $B2ADDRESS 1.0 0)
TXID2=$( $CLI $B1ARGS sendfrom bar $B2ADDRESS 2.0 0)
# Mutate TXID1 and add it to B2's memory pool: # Mutate TXID1 and add it to B2's memory pool:
RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 ) RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 )
@ -122,7 +124,9 @@ echo "Mutated: " $MUTATEDTXID
$CLI $B2ARGS addnode 127.0.0.1:11000 onetry $CLI $B2ARGS addnode 127.0.0.1:11000 onetry
WaitPeers "$B1ARGS" 1 WaitPeers "$B1ARGS" 1
$CLI $B2ARGS setgenerate true 1 $CLI $B2ARGS setgenerate true 3
WaitBlocks
$CLI $B1ARGS setgenerate true 3
WaitBlocks WaitBlocks
$CLI $B2ARGS stop > /dev/null 2>&1 $CLI $B2ARGS stop > /dev/null 2>&1

11
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("blockindex", wtx.nIndex));
entry.push_back(Pair("blocktime", (boost::int64_t)(mapBlockIndex[wtx.hashBlock]->nTime))); 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("time", (boost::int64_t)wtx.GetTxTime()));
entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived)); entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived));
BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue)
@ -621,7 +626,7 @@ Value getbalance(const Array& params, bool fHelp)
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
{ {
const CWalletTx& wtx = (*it).second; const CWalletTx& wtx = (*it).second;
if (!wtx.IsTrusted()) if (!wtx.IsTrusted() || wtx.GetBlocksToMaturity() > 0)
continue; continue;
int64_t allFee; int64_t allFee;
@ -1325,6 +1330,8 @@ Value listaccounts(const Array& params, bool fHelp)
string strSentAccount; string strSentAccount;
list<pair<CTxDestination, int64_t> > listReceived; list<pair<CTxDestination, int64_t> > listReceived;
list<pair<CTxDestination, int64_t> > listSent; list<pair<CTxDestination, int64_t> > listSent;
if (wtx.GetBlocksToMaturity() > 0)
continue;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount); wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount);
mapAccountBalances[strSentAccount] -= nFee; mapAccountBalances[strSentAccount] -= nFee;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent) BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent)

100
src/wallet.cpp

@ -231,6 +231,82 @@ bool CWallet::SetMaxVersion(int nVersion)
return true; 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) bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{ {
if (IsCrypted()) 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(); uint256 hash = wtxIn.GetHash();
if (fFromLoadWallet)
{
mapWallet[hash] = wtxIn;
AddToConflicts(hash);
}
else
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
// Inserts only if not already there, returns tx inserted or tx found // 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.GetHash().ToString(),
wtxIn.hashBlock.ToString()); wtxIn.hashBlock.ToString());
} }
AddToConflicts(hash);
} }
bool fUpdated = false; 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() void CWallet::ResendWalletTransactions()
{ {
// Do this infrequently and randomly to avoid giving away // 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) for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{ {
const CWalletTx* pcoin = &(*it).second; const CWalletTx* pcoin = &(*it).second;
if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted()) if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
nTotal += pcoin->GetAvailableCredit(); nTotal += pcoin->GetAvailableCredit();
} }
} }

14
src/wallet.h

@ -108,6 +108,12 @@ private:
int64_t nNextResend; int64_t nNextResend;
int64_t nLastResend; int64_t nLastResend;
// Used to detect and report conflicted transactions:
typedef std::multimap<COutPoint, uint256> TxConflicts;
TxConflicts mapTxConflicts;
void AddToConflicts(const uint256& wtxhash);
void SyncMetaData(std::pair<TxConflicts::iterator, TxConflicts::iterator>);
public: public:
/// Main wallet lock. /// Main wallet lock.
/// This lock protects all the fields added by CWallet /// This lock protects all the fields added by CWallet
@ -151,6 +157,7 @@ public:
} }
std::map<uint256, CWalletTx> mapWallet; std::map<uint256, CWalletTx> mapWallet;
int64_t nOrderPosNext; int64_t nOrderPosNext;
std::map<uint256, int> mapRequestCount; std::map<uint256, int> mapRequestCount;
@ -223,7 +230,7 @@ public:
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = ""); TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
void MarkDirty(); 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); void SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock);
bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate); bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate);
void EraseFromWallet(const uint256 &hash); void EraseFromWallet(const uint256 &hash);
@ -357,6 +364,9 @@ public:
// get the current wallet format (the oldest client version guaranteed to understand this wallet) // get the current wallet format (the oldest client version guaranteed to understand this wallet)
int GetVersion() { AssertLockHeld(cs_wallet); return nWalletVersion; } int GetVersion() { AssertLockHeld(cs_wallet); return nWalletVersion; }
// Get wallet transactions that conflict with given transaction (spend same outputs)
std::set<uint256> GetConflicts(const uint256& txid) const;
/** Address book entry changed. /** Address book entry changed.
* @note called with lock cs_wallet held. * @note called with lock cs_wallet held.
*/ */
@ -752,6 +762,8 @@ public:
void AddSupportingTransactions(); void AddSupportingTransactions();
bool AcceptWalletTransaction(); bool AcceptWalletTransaction();
void RelayWalletTransaction(); void RelayWalletTransaction();
std::set<uint256> GetConflicts() const;
}; };

2
src/walletdb.cpp

@ -382,7 +382,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
if (wtx.nOrderPos == -1) if (wtx.nOrderPos == -1)
wss.fAnyUnordered = true; wss.fAnyUnordered = true;
pwallet->mapWallet[hash] = wtx; pwallet->AddToWallet(wtx, true);
//// debug print //// debug print
//LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString()); //LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString());
//LogPrintf(" %12"PRId64" %s %s %s\n", //LogPrintf(" %12"PRId64" %s %s %s\n",

Loading…
Cancel
Save