diff --git a/qa/rpc-tests/txnmall.sh b/qa/rpc-tests/txnmall.sh index 7aca5f36d..6bf92fce4 100755 --- a/qa/rpc-tests/txnmall.sh +++ b/qa/rpc-tests/txnmall.sh @@ -88,8 +88,10 @@ B2ADDRESS=$( $CLI $B2ARGS getnewaddress ) # Have B1 create two transactions; second will # spend change from first, since B1 starts with only a single # 50 bitcoin output: -TXID1=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 1.0 ) -TXID2=$( $CLI $B1ARGS sendtoaddress $B2ADDRESS 2.0 ) +$CLI $B1ARGS move "" "foo" 10.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: RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 ) @@ -122,7 +124,9 @@ echo "Mutated: " $MUTATEDTXID $CLI $B2ARGS addnode 127.0.0.1:11000 onetry WaitPeers "$B1ARGS" 1 -$CLI $B2ARGS setgenerate true 1 +$CLI $B2ARGS setgenerate true 3 +WaitBlocks +$CLI $B1ARGS setgenerate true 3 WaitBlocks $CLI $B2ARGS stop > /dev/null 2>&1 diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 2b49762d4..97c4008da 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 b579480f7..b4d6fc319 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 dc709632b..95fb0ee9d 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); @@ -358,6 +365,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. */ @@ -753,6 +763,8 @@ public: void AddSupportingTransactions(); bool AcceptWalletTransaction(); void RelayWalletTransaction(); + + std::set GetConflicts() const; }; diff --git a/src/walletdb.cpp b/src/walletdb.cpp index b3cc9a235..2f8c827bc 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",