Skip to content

Commit 731b89b

Browse files
committed
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.
1 parent 9a3d936 commit 731b89b

File tree

5 files changed

+128
-9
lines changed

5 files changed

+128
-9
lines changed

qa/rpc-tests/txnmall.sh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ B2ADDRESS=$( $CLI $B2ARGS getnewaddress )
8888
# Have B1 create two transactions; second will
8989
# spend change from first, since B1 starts with only a single
9090
# 50 bitcoin output:
91-
TXID1=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 1.0 )
92-
TXID2=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 2.0 )
91+
$CLI $B1ARGS move "" "foo" 10.0
92+
$CLI $B1ARGS move "" "bar" 10.0
93+
TXID1=$( $CLI $B1ARGS sendfrom foo $B2ADDRESS 1.0 0)
94+
TXID2=$( $CLI $B1ARGS sendfrom bar $B2ADDRESS 2.0 0)
9395

9496
# Mutate TXID1 and add it to B2's memory pool:
9597
RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 )
@@ -122,7 +124,9 @@ echo "Mutated: " $MUTATEDTXID
122124
$CLI $B2ARGS addnode 127.0.0.1:11000 onetry
123125
WaitPeers "$B1ARGS" 1
124126

125-
$CLI $B2ARGS setgenerate true 1
127+
$CLI $B2ARGS setgenerate true 3
128+
WaitBlocks
129+
$CLI $B1ARGS setgenerate true 3
126130
WaitBlocks
127131

128132
$CLI $B2ARGS stop > /dev/null 2>&1

src/rpcwallet.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
5151
entry.push_back(Pair("blockindex", wtx.nIndex));
5252
entry.push_back(Pair("blocktime", (boost::int64_t)(mapBlockIndex[wtx.hashBlock]->nTime)));
5353
}
54-
entry.push_back(Pair("txid", wtx.GetHash().GetHex()));
54+
uint256 hash = wtx.GetHash();
55+
entry.push_back(Pair("txid", hash.GetHex()));
56+
Array conflicts;
57+
BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
58+
conflicts.push_back(conflict.GetHex());
59+
entry.push_back(Pair("walletconflicts", conflicts));
5560
entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime()));
5661
entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived));
5762
BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue)
@@ -621,7 +626,7 @@ Value getbalance(const Array& params, bool fHelp)
621626
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
622627
{
623628
const CWalletTx& wtx = (*it).second;
624-
if (!wtx.IsTrusted())
629+
if (!wtx.IsTrusted() || wtx.GetBlocksToMaturity() > 0)
625630
continue;
626631

627632
int64_t allFee;
@@ -1325,6 +1330,8 @@ Value listaccounts(const Array& params, bool fHelp)
13251330
string strSentAccount;
13261331
list<pair<CTxDestination, int64_t> > listReceived;
13271332
list<pair<CTxDestination, int64_t> > listSent;
1333+
if (wtx.GetBlocksToMaturity() > 0)
1334+
continue;
13281335
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount);
13291336
mapAccountBalances[strSentAccount] -= nFee;
13301337
BOOST_FOREACH(const PAIRTYPE(CTxDestination, int64_t)& s, listSent)

src/wallet.cpp

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,82 @@ bool CWallet::SetMaxVersion(int nVersion)
231231
return true;
232232
}
233233

234+
set<uint256> CWallet::GetConflicts(const uint256& txid) const
235+
{
236+
set<uint256> result;
237+
AssertLockHeld(cs_wallet);
238+
239+
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
240+
if (it == mapWallet.end())
241+
return result;
242+
const CWalletTx& wtx = it->second;
243+
244+
std::pair<TxConflicts::const_iterator, TxConflicts::const_iterator> range;
245+
246+
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
247+
{
248+
range = mapTxConflicts.equal_range(txin.prevout);
249+
for (TxConflicts::const_iterator it = range.first; it != range.second; ++it)
250+
result.insert(it->second);
251+
}
252+
return result;
253+
}
254+
255+
void CWallet::SyncMetaData(pair<TxConflicts::iterator, TxConflicts::iterator> range)
256+
{
257+
// We want all the wallet transactions in range to have the same metadata as
258+
// the oldest (smallest nOrderPos).
259+
// So: find smallest nOrderPos:
260+
261+
int nMinOrderPos = std::numeric_limits<int>::max();
262+
const CWalletTx* copyFrom = NULL;
263+
for (TxConflicts::iterator it = range.first; it != range.second; ++it)
264+
{
265+
const uint256& hash = it->second;
266+
int n = mapWallet[hash].nOrderPos;
267+
if (n < nMinOrderPos)
268+
{
269+
nMinOrderPos = n;
270+
copyFrom = &mapWallet[hash];
271+
}
272+
}
273+
// Now copy data from copyFrom to rest:
274+
for (TxConflicts::iterator it = range.first; it != range.second; ++it)
275+
{
276+
const uint256& hash = it->second;
277+
CWalletTx* copyTo = &mapWallet[hash];
278+
if (copyFrom == copyTo) continue;
279+
copyTo->mapValue = copyFrom->mapValue;
280+
copyTo->vOrderForm = copyFrom->vOrderForm;
281+
// fTimeReceivedIsTxTime not copied on purpose
282+
// nTimeReceived not copied on purpose
283+
copyTo->nTimeSmart = copyFrom->nTimeSmart;
284+
copyTo->fFromMe = copyFrom->fFromMe;
285+
copyTo->strFromAccount = copyFrom->strFromAccount;
286+
// vfSpent not copied on purpose
287+
// nOrderPos not copied on purpose
288+
// cached members not copied on purpose
289+
}
290+
}
291+
292+
void CWallet::AddToConflicts(const uint256& wtxhash)
293+
{
294+
assert(mapWallet.count(wtxhash));
295+
CWalletTx& thisTx = mapWallet[wtxhash];
296+
if (thisTx.IsCoinBase())
297+
return;
298+
299+
BOOST_FOREACH(const CTxIn& txin, thisTx.vin)
300+
{
301+
mapTxConflicts.insert(make_pair(txin.prevout, wtxhash));
302+
303+
pair<TxConflicts::iterator, TxConflicts::iterator> range;
304+
range = mapTxConflicts.equal_range(txin.prevout);
305+
if (range.first != range.second)
306+
SyncMetaData(range);
307+
}
308+
}
309+
234310
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
235311
{
236312
if (IsCrypted())
@@ -385,9 +461,16 @@ void CWallet::MarkDirty()
385461
}
386462
}
387463

388-
bool CWallet::AddToWallet(const CWalletTx& wtxIn)
464+
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet)
389465
{
390466
uint256 hash = wtxIn.GetHash();
467+
468+
if (fFromLoadWallet)
469+
{
470+
mapWallet[hash] = wtxIn;
471+
AddToConflicts(hash);
472+
}
473+
else
391474
{
392475
LOCK(cs_wallet);
393476
// Inserts only if not already there, returns tx inserted or tx found
@@ -445,6 +528,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
445528
wtxIn.GetHash().ToString(),
446529
wtxIn.hashBlock.ToString());
447530
}
531+
AddToConflicts(hash);
448532
}
449533

450534
bool fUpdated = false;
@@ -907,6 +991,18 @@ void CWalletTx::RelayWalletTransaction()
907991
}
908992
}
909993

994+
set<uint256> CWalletTx::GetConflicts() const
995+
{
996+
set<uint256> result;
997+
if (pwallet != NULL)
998+
{
999+
uint256 myHash = GetHash();
1000+
result = pwallet->GetConflicts(myHash);
1001+
result.erase(myHash);
1002+
}
1003+
return result;
1004+
}
1005+
9101006
void CWallet::ResendWalletTransactions()
9111007
{
9121008
// Do this infrequently and randomly to avoid giving away
@@ -980,7 +1076,7 @@ int64_t CWallet::GetUnconfirmedBalance() const
9801076
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
9811077
{
9821078
const CWalletTx* pcoin = &(*it).second;
983-
if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted())
1079+
if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
9841080
nTotal += pcoin->GetAvailableCredit();
9851081
}
9861082
}

src/wallet.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface
108108
int64_t nNextResend;
109109
int64_t nLastResend;
110110

111+
// Used to detect and report conflicted transactions:
112+
typedef std::multimap<COutPoint, uint256> TxConflicts;
113+
TxConflicts mapTxConflicts;
114+
void AddToConflicts(const uint256& wtxhash);
115+
void SyncMetaData(std::pair<TxConflicts::iterator, TxConflicts::iterator>);
116+
111117
public:
112118
/// Main wallet lock.
113119
/// This lock protects all the fields added by CWallet
@@ -151,6 +157,7 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface
151157
}
152158

153159
std::map<uint256, CWalletTx> mapWallet;
160+
154161
int64_t nOrderPosNext;
155162
std::map<uint256, int> mapRequestCount;
156163

@@ -223,7 +230,7 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface
223230
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
224231

225232
void MarkDirty();
226-
bool AddToWallet(const CWalletTx& wtxIn);
233+
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet=false);
227234
void SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock);
228235
bool AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate);
229236
void EraseFromWallet(const uint256 &hash);
@@ -357,6 +364,9 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface
357364
// get the current wallet format (the oldest client version guaranteed to understand this wallet)
358365
int GetVersion() { AssertLockHeld(cs_wallet); return nWalletVersion; }
359366

367+
// Get wallet transactions that conflict with given transaction (spend same outputs)
368+
std::set<uint256> GetConflicts(const uint256& txid) const;
369+
360370
/** Address book entry changed.
361371
* @note called with lock cs_wallet held.
362372
*/
@@ -752,6 +762,8 @@ class CWalletTx : public CMerkleTx
752762
void AddSupportingTransactions();
753763
bool AcceptWalletTransaction();
754764
void RelayWalletTransaction();
765+
766+
std::set<uint256> GetConflicts() const;
755767
};
756768

757769

src/walletdb.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
382382
if (wtx.nOrderPos == -1)
383383
wss.fAnyUnordered = true;
384384

385-
pwallet->mapWallet[hash] = wtx;
385+
pwallet->AddToWallet(wtx, true);
386386
//// debug print
387387
//LogPrintf("LoadWallet %s\n", wtx.GetHash().ToString());
388388
//LogPrintf(" %12"PRId64" %s %s %s\n",

0 commit comments

Comments
 (0)