diff options
Diffstat (limited to 'src/wallet.cpp')
| -rw-r--r-- | src/wallet.cpp | 914 |
1 files changed, 635 insertions, 279 deletions
diff --git a/src/wallet.cpp b/src/wallet.cpp index 2317ac31a..5e24738b5 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,16 +1,22 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2009-2014 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "wallet.h" -#include "walletdb.h" -#include "crypter.h" -#include "ui_interface.h" + #include "base58.h" +#include "coincontrol.h" +#include "net.h" + +#include <boost/algorithm/string/replace.hpp> +#include <openssl/rand.h> using namespace std; +// Settings +int64_t nTransactionFee = 0; +bool bSpendZeroConfChange = true; ////////////////////////////////////////////////////////////////////////////// // @@ -19,8 +25,8 @@ using namespace std; struct CompareValueOnly { - bool operator()(const pair<int64, pair<const CWalletTx*, unsigned int> >& t1, - const pair<int64, pair<const CWalletTx*, unsigned int> >& t2) const + bool operator()(const pair<int64_t, pair<const CWalletTx*, unsigned int> >& t1, + const pair<int64_t, pair<const CWalletTx*, unsigned int> >& t2) const { return t1.first < t2.first; } @@ -28,33 +34,47 @@ struct CompareValueOnly CPubKey CWallet::GenerateNewKey() { + AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets RandAddSeedPerfmon(); - CKey key; - key.MakeNewKey(fCompressed); + CKey secret; + secret.MakeNewKey(fCompressed); // Compressed public keys were introduced in version 0.6.0 if (fCompressed) SetMinVersion(FEATURE_COMPRPUBKEY); - if (!AddKey(key)) + CPubKey pubkey = secret.GetPubKey(); + + // Create new metadata + int64_t nCreationTime = GetTime(); + mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime); + if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) + nTimeFirstKey = nCreationTime; + + if (!AddKeyPubKey(secret, pubkey)) throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed"); - return key.GetPubKey(); + return pubkey; } -bool CWallet::AddKey(const CKey& key) +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) { - if (!CCryptoKeyStore::AddKey(key)) + AssertLockHeld(cs_wallet); // mapKeyMetadata + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) return false; if (!fFileBacked) return true; - if (!IsCrypted()) - return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } return true; } -bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector<unsigned char> &vchCryptedSecret) +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, + const vector<unsigned char> &vchCryptedSecret) { if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; @@ -63,13 +83,32 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector<unsigned char { LOCK(cs_wallet); if (pwalletdbEncryption) - return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret); + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); else - return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret); + return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, + vchCryptedSecret, + mapKeyMetadata[vchPubKey.GetID()]); } return false; } +bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) +{ + AssertLockHeld(cs_wallet); // mapKeyMetadata + if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey)) + nTimeFirstKey = meta.nCreateTime; + + mapKeyMetadata[pubkey.GetID()] = meta; + return true; +} + +bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); +} + bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) @@ -81,9 +120,6 @@ bool CWallet::AddCScript(const CScript& redeemScript) bool CWallet::Unlock(const SecureString& strWalletPassphrase) { - if (!IsLocked()) - return false; - CCrypter crypter; CKeyingMaterial vMasterKey; @@ -94,7 +130,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) - return false; + continue; // try another master key if (CCryptoKeyStore::Unlock(vMasterKey)) return true; } @@ -120,7 +156,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (CCryptoKeyStore::Unlock(vMasterKey)) { - int64 nStartTime = GetTimeMillis(); + int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); @@ -131,7 +167,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, if (pMasterKey.second.nDeriveIterations < 25000) pMasterKey.second.nDeriveIterations = 25000; - printf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); + LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; @@ -154,20 +190,9 @@ void CWallet::SetBestChain(const CBlockLocator& loc) walletdb.WriteBestBlock(loc); } -// This class implements an addrIncoming entry that causes pre-0.4 -// clients to crash on startup if reading a private-key-encrypted wallet. -class CCorruptAddress -{ -public: - IMPLEMENT_SERIALIZE - ( - if (nType & SER_DISK) - READWRITE(nVersion); - ) -}; - bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit) { + LOCK(cs_wallet); // nWalletVersion if (nWalletVersion >= nVersion) return true; @@ -183,13 +208,6 @@ bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, if (fFileBacked) { CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile); - if (nWalletVersion >= 40000) - { - // Versions prior to 0.4.0 did not support the "minversion" record. - // Use a CCorruptAddress to make them crash instead. - CCorruptAddress corruptAddress; - pwalletdb->WriteSetting("addrIncoming", corruptAddress); - } if (nWalletVersion > 40000) pwalletdb->WriteMinVersion(nWalletVersion); if (!pwalletdbIn) @@ -201,6 +219,7 @@ bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool CWallet::SetMaxVersion(int nVersion) { + LOCK(cs_wallet); // nWalletVersion, nWalletMaxVersion // cannot downgrade below current version if (nWalletVersion > nVersion) return false; @@ -210,6 +229,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()) @@ -228,7 +323,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); CCrypter crypter; - int64 nStartTime = GetTimeMillis(); + int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); @@ -239,7 +334,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) if (kMasterKey.nDeriveIterations < 25000) kMasterKey.nDeriveIterations = 25000; - printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) return false; @@ -291,9 +386,10 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) return true; } -int64 CWallet::IncOrderPosNext(CWalletDB *pwalletdb) +int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) { - int64 nRet = nOrderPosNext++; + AssertLockHeld(cs_wallet); // nOrderPosNext + int64_t nRet = nOrderPosNext++; if (pwalletdb) { pwalletdb->WriteOrderPosNext(nOrderPosNext); } else { @@ -304,6 +400,7 @@ int64 CWallet::IncOrderPosNext(CWalletDB *pwalletdb) CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount) { + AssertLockHeld(cs_wallet); // mapWallet CWalletDB walletdb(strWalletFile); // First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap. @@ -340,10 +437,10 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) { CWalletTx& wtx = (*mi).second; if (txin.prevout.n >= wtx.vout.size()) - printf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString().c_str()); + LogPrintf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString()); else if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) { - printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + LogPrintf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()), wtx.GetHash().ToString()); wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); @@ -362,9 +459,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 @@ -386,7 +490,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) unsigned int latestEntry = 0; { // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future - int64 latestTolerated = latestNow + 300; + int64_t latestTolerated = latestNow + 300; std::list<CAccountingEntry> acentries; TxItems txOrdered = OrderedTxItems(acentries); for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) @@ -395,7 +499,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) if (pwtx == &wtx) continue; CAccountingEntry *const pacentry = (*it).second.second; - int64 nSmartTime; + int64_t nSmartTime; if (pwtx) { nSmartTime = pwtx->nTimeSmart; @@ -418,10 +522,11 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else - printf("AddToWallet() : found %s in block %s not in index\n", - wtxIn.GetHash().ToString().substr(0,10).c_str(), - wtxIn.hashBlock.ToString().c_str()); + LogPrintf("AddToWallet() : found %s in block %s not in index\n", + wtxIn.GetHash().ToString(), + wtxIn.hashBlock.ToString()); } + AddToConflicts(hash); } bool fUpdated = false; @@ -448,34 +553,28 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) } //// debug print - printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); + LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if (fInsertedNew || fUpdated) if (!wtx.WriteToDisk()) return false; -#ifndef QT_GUI - // If default receiving address gets used, replace it with a new one - CScript scriptDefaultKey; - scriptDefaultKey.SetDestination(vchDefaultKey.GetID()); - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - { - if (txout.scriptPubKey == scriptDefaultKey) - { - CPubKey newDefaultKey; - if (GetKeyFromPool(newDefaultKey, false)) - { - SetDefaultKey(newDefaultKey); - SetAddressBookName(vchDefaultKey.GetID(), ""); - } - } - } -#endif + // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins WalletUpdateSpent(wtx); // Notify UI of new or updated transaction NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + + // notify an external script when a wallet transaction comes in or is updated + std::string strCmd = GetArg("-walletnotify", ""); + + if ( !strCmd.empty()) + { + boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex()); + boost::thread t(runCommand, strCmd); // thread runs free + } + } return true; } @@ -483,7 +582,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) // Add a transaction to the wallet, or update it. // pblock is optional, but should be provided if the transaction is known to be in a block. // If fUpdate is true, existing transactions will be updated. -bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock) +bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate) { { LOCK(cs_wallet); @@ -503,16 +602,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& return false; } -bool CWallet::EraseFromWallet(uint256 hash) +void CWallet::SyncTransaction(const uint256 &hash, const CTransaction& tx, const CBlock* pblock) { + AddToWalletIfInvolvingMe(hash, tx, pblock, true); +} + +void CWallet::EraseFromWallet(const uint256 &hash) { if (!fFileBacked) - return false; + return; { LOCK(cs_wallet); if (mapWallet.erase(hash)) CWalletDB(strWalletFile).EraseTx(hash); } - return true; + return; } @@ -532,7 +635,7 @@ bool CWallet::IsMine(const CTxIn &txin) const return false; } -int64 CWallet::GetDebit(const CTxIn &txin) const +int64_t CWallet::GetDebit(const CTxIn &txin) const { { LOCK(cs_wallet); @@ -568,9 +671,9 @@ bool CWallet::IsChange(const CTxOut& txout) const return false; } -int64 CWalletTx::GetTxTime() const +int64_t CWalletTx::GetTxTime() const { - int64 n = nTimeSmart; + int64_t n = nTimeSmart; return n ? n : nTimeReceived; } @@ -613,8 +716,8 @@ int CWalletTx::GetRequestCount() const return nRequests; } -void CWalletTx::GetAmounts(list<pair<CTxDestination, int64> >& listReceived, - list<pair<CTxDestination, int64> >& listSent, int64& nFee, string& strSentAccount) const +void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived, + list<pair<CTxDestination, int64_t> >& listSent, int64_t& nFee, string& strSentAccount) const { nFee = 0; listReceived.clear(); @@ -622,62 +725,75 @@ void CWalletTx::GetAmounts(list<pair<CTxDestination, int64> >& listReceived, strSentAccount = strFromAccount; // Compute fee: - int64 nDebit = GetDebit(); + int64_t nDebit = GetDebit(); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - int64 nValueOut = GetValueOut(); + int64_t nValueOut = GetValueOut(); nFee = nDebit - nValueOut; } // Sent/received. BOOST_FOREACH(const CTxOut& txout, vout) { + bool fIsMine; + // Only need to handle txouts if AT LEAST one of these is true: + // 1) they debit from us (sent) + // 2) the output is to us (received) + if (nDebit > 0) + { + // Don't report 'change' txouts + if (pwallet->IsChange(txout)) + continue; + fIsMine = pwallet->IsMine(txout); + } + else if (!(fIsMine = pwallet->IsMine(txout))) + continue; + + // In either case, we need to get the destination address CTxDestination address; - vector<unsigned char> vchPubKey; if (!ExtractDestination(txout.scriptPubKey, address)) { - printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->GetHash().ToString().c_str()); + LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + this->GetHash().ToString()); + address = CNoDestination(); } - // Don't report 'change' txouts - if (nDebit > 0 && pwallet->IsChange(txout)) - continue; - + // If we are debited by the transaction, add the output as a "sent" entry if (nDebit > 0) listSent.push_back(make_pair(address, txout.nValue)); - if (pwallet->IsMine(txout)) + // If we are receiving the output, add it as a "received" entry + if (fIsMine) listReceived.push_back(make_pair(address, txout.nValue)); } } -void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nReceived, - int64& nSent, int64& nFee) const +void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived, + int64_t& nSent, int64_t& nFee) const { nReceived = nSent = nFee = 0; - int64 allFee; + int64_t allFee; string strSentAccount; - list<pair<CTxDestination, int64> > listReceived; - list<pair<CTxDestination, int64> > listSent; + list<pair<CTxDestination, int64_t> > listReceived; + list<pair<CTxDestination, int64_t> > listSent; GetAmounts(listReceived, listSent, allFee, strSentAccount); if (strAccount == strSentAccount) { - BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& s, listSent) + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& s, listSent) nSent += s.second; nFee = allFee; } { LOCK(pwallet->cs_wallet); - BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived) + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listReceived) { if (pwallet->mapAddressBook.count(r.first)) { - map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first); - if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) + map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.first); + if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount) nReceived += r.second; } else if (strAccount.empty()) @@ -722,6 +838,10 @@ void CWalletTx::AddSupportingTransactions() { tx = *mapWalletPrev[hash]; } + else + { + continue; + } int nDepth = tx.SetMerkleBranch(); vtxPrev.push_back(tx); @@ -738,6 +858,25 @@ void CWalletTx::AddSupportingTransactions() reverse(vtxPrev.begin(), vtxPrev.end()); } +bool CWalletTx::AcceptWalletTransaction() +{ + { + LOCK(mempool.cs); + // Add previous supporting transactions first + BOOST_FOREACH(CMerkleTx& tx, vtxPrev) + { + if (!tx.IsCoinBase()) + { + uint256 hash = tx.GetHash(); + if (!mempool.exists(hash) && pcoinsTip->HaveCoins(hash)) + tx.AcceptToMemoryPool(false); + } + } + return AcceptToMemoryPool(false); + } + return false; +} + bool CWalletTx::WriteToDisk() { return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this); @@ -755,14 +894,21 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) LOCK(cs_wallet); while (pindex) { + // no need to read and scan block, if block was created before + // our wallet birthday (as adjusted for block time variability) + if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) { + pindex = chainActive.Next(pindex); + continue; + } + CBlock block; - block.ReadFromDisk(pindex); + ReadBlockFromDisk(block, pindex); BOOST_FOREACH(CTransaction& tx, block.vtx) { if (AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate)) ret++; } - pindex = pindex->pnext; + pindex = chainActive.Next(pindex); } } return ret; @@ -801,7 +947,7 @@ void CWallet::ReacceptWalletTransactions() } if (fUpdated) { - printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + LogPrintf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()), wtx.GetHash().ToString()); wtx.MarkDirty(); wtx.WriteToDisk(); } @@ -810,13 +956,13 @@ void CWallet::ReacceptWalletTransactions() { // Re-accept any txes of ours that aren't already in a block if (!wtx.IsCoinBase()) - wtx.AcceptWalletTransaction(false); + wtx.AcceptWalletTransaction(); } } if (fMissing) { // TODO: optimize this to scan just part of the block chain? - if (ScanForWalletTransactions(pindexGenesisBlock)) + if (ScanForWalletTransactions(chainActive.Genesis())) fRepeat = true; // Found missing transactions: re-do re-accept. } } @@ -826,7 +972,10 @@ void CWalletTx::RelayWalletTransaction() { BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) { - if (!tx.IsCoinBase()) + // Important: versions of bitcoin before 0.8.6 had a bug that inserted + // empty transactions into the vtxPrev, which will cause the node to be + // banned when retransmitted, hence the check for !tx.vin.empty() + if (!tx.IsCoinBase() && !tx.vin.empty()) if (tx.GetDepthInMainChain() == 0) RelayTransaction((CTransaction)tx, tx.GetHash()); } @@ -834,32 +983,42 @@ void CWalletTx::RelayWalletTransaction() { if (GetDepthInMainChain() == 0) { uint256 hash = GetHash(); - printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str()); + LogPrintf("Relaying wtx %s\n", hash.ToString()); RelayTransaction((CTransaction)*this, hash); } } } +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 // that these are our transactions. - static int64 nNextTime; - if (GetTime() < nNextTime) + if (GetTime() < nNextResend) return; - bool fFirst = (nNextTime == 0); - nNextTime = GetTime() + GetRand(30 * 60); + bool fFirst = (nNextResend == 0); + nNextResend = GetTime() + GetRand(30 * 60); if (fFirst) return; // Only do it if there's been a new block since last time - static int64 nLastTime; - if (nTimeBestReceived < nLastTime) + if (nTimeBestReceived < nLastResend) return; - nLastTime = GetTime(); + nLastResend = GetTime(); // Rebroadcast any of our txes that aren't in a block yet - printf("ResendWalletTransactions()\n"); + LogPrintf("ResendWalletTransactions()\n"); { LOCK(cs_wallet); // Sort them in chronological order @@ -869,7 +1028,7 @@ void CWallet::ResendWalletTransactions() CWalletTx& wtx = item.second; // Don't rebroadcast until it's had plenty of time that // it should have gotten in already by now. - if (nTimeBestReceived - (int64)wtx.nTimeReceived > 5 * 60) + if (nTimeBestReceived - (int64_t)wtx.nTimeReceived > 5 * 60) mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx)); } BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted) @@ -891,15 +1050,15 @@ void CWallet::ResendWalletTransactions() // -int64 CWallet::GetBalance() const +int64_t CWallet::GetBalance() const { - int64 nTotal = 0; + int64_t nTotal = 0; { LOCK(cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (pcoin->IsFinal() && pcoin->IsConfirmed()) + if (pcoin->IsTrusted()) nTotal += pcoin->GetAvailableCredit(); } } @@ -907,24 +1066,24 @@ int64 CWallet::GetBalance() const return nTotal; } -int64 CWallet::GetUnconfirmedBalance() const +int64_t CWallet::GetUnconfirmedBalance() const { - int64 nTotal = 0; + int64_t nTotal = 0; { LOCK(cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0)) nTotal += pcoin->GetAvailableCredit(); } } return nTotal; } -int64 CWallet::GetImmatureBalance() const +int64_t CWallet::GetImmatureBalance() const { - int64 nTotal = 0; + int64_t nTotal = 0; { LOCK(cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) @@ -937,7 +1096,7 @@ int64 CWallet::GetImmatureBalance() const } // populate vCoins with vector of spendable COutputs -void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const +void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const { vCoins.clear(); @@ -947,42 +1106,55 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const { const CWalletTx* pcoin = &(*it).second; - if (!pcoin->IsFinal()) + if (!IsFinalTx(*pcoin)) continue; - if (fOnlyConfirmed && !pcoin->IsConfirmed()) + if (fOnlyConfirmed && !pcoin->IsTrusted()) continue; if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) continue; + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < 0) + continue; + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && - !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0) - vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); + !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 && + (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + vCoins.push_back(COutput(pcoin, i, nDepth)); } } } } -static void ApproximateBestSubset(vector<pair<int64, pair<const CWalletTx*,unsigned int> > >vValue, int64 nTotalLower, int64 nTargetValue, - vector<char>& vfBest, int64& nBest, int iterations = 1000) +static void ApproximateBestSubset(vector<pair<int64_t, pair<const CWalletTx*,unsigned int> > >vValue, int64_t nTotalLower, int64_t nTargetValue, + vector<char>& vfBest, int64_t& nBest, int iterations = 1000) { vector<char> vfIncluded; vfBest.assign(vValue.size(), true); nBest = nTotalLower; + seed_insecure_rand(); + for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) { vfIncluded.assign(vValue.size(), false); - int64 nTotal = 0; + int64_t nTotal = 0; bool fReachedTarget = false; for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) { for (unsigned int i = 0; i < vValue.size(); i++) { - if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) + //The solver here uses a randomized algorithm, + //the randomness serves no real security purpose but is just + //needed to prevent degenerate behavior and it is important + //that the rng fast. We do not use a constant random sequence, + //because there may be some privacy improvement by making + //the selection random. + if (nPass == 0 ? insecure_rand()&1 : !vfIncluded[i]) { nTotal += vValue[i].first; vfIncluded[i] = true; @@ -1003,18 +1175,18 @@ static void ApproximateBestSubset(vector<pair<int64, pair<const CWalletTx*,unsig } } -bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins, - set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoinsMinConf(int64_t nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins, + set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const { setCoinsRet.clear(); nValueRet = 0; // List of values less than target - pair<int64, pair<const CWalletTx*,unsigned int> > coinLowestLarger; - coinLowestLarger.first = std::numeric_limits<int64>::max(); + pair<int64_t, pair<const CWalletTx*,unsigned int> > coinLowestLarger; + coinLowestLarger.first = std::numeric_limits<int64_t>::max(); coinLowestLarger.second.first = NULL; - vector<pair<int64, pair<const CWalletTx*,unsigned int> > > vValue; - int64 nTotalLower = 0; + vector<pair<int64_t, pair<const CWalletTx*,unsigned int> > > vValue; + int64_t nTotalLower = 0; random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); @@ -1026,9 +1198,9 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe continue; int i = output.i; - int64 n = pcoin->vout[i].nValue; + int64_t n = pcoin->vout[i].nValue; - pair<int64,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i)); + pair<int64_t,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i)); if (n == nTargetValue) { @@ -1069,7 +1241,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe // Solve subset sum by stochastic approximation sort(vValue.rbegin(), vValue.rend(), CompareValueOnly()); vector<char> vfBest; - int64 nBest; + int64_t nBest; ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000); if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT) @@ -1091,41 +1263,58 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe nValueRet += vValue[i].first; } - //// debug print - printf("SelectCoins() best subset: "); + LogPrint("selectcoins", "SelectCoins() best subset: "); for (unsigned int i = 0; i < vValue.size(); i++) if (vfBest[i]) - printf("%s ", FormatMoney(vValue[i].first).c_str()); - printf("total %s\n", FormatMoney(nBest).c_str()); + LogPrint("selectcoins", "%s ", FormatMoney(vValue[i].first)); + LogPrint("selectcoins", "total %s\n", FormatMoney(nBest)); } return true; } -bool CWallet::SelectCoins(int64 nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoins(int64_t nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl* coinControl) const { vector<COutput> vCoins; - AvailableCoins(vCoins); + AvailableCoins(vCoins, true, coinControl); + + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coinControl && coinControl->HasSelected()) + { + BOOST_FOREACH(const COutput& out, vCoins) + { + nValueRet += out.tx->vout[out.i].nValue; + setCoinsRet.insert(make_pair(out.tx, out.i)); + } + return (nValueRet >= nTargetValue); + } return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet)); + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); } -bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { - int64 nValue = 0; - BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) + int64_t nValue = 0; + BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend) { if (nValue < 0) + { + strFailReason = _("Transaction amounts must be positive"); return false; + } nValue += s.second; } if (vecSend.empty() || nValue < 0) + { + strFailReason = _("Transaction amounts must be positive"); return false; + } wtxNew.BindWallet(this); @@ -1133,65 +1322,100 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CW LOCK2(cs_main, cs_wallet); { nFeeRet = nTransactionFee; - loop + while (true) { wtxNew.vin.clear(); wtxNew.vout.clear(); wtxNew.fFromMe = true; - int64 nTotalValue = nValue + nFeeRet; + int64_t nTotalValue = nValue + nFeeRet; double dPriority = 0; // vouts to the payees - BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) - wtxNew.vout.push_back(CTxOut(s.second, s.first)); + BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend) + { + CTxOut txout(s.second, s.first); + if (txout.IsDust(CTransaction::nMinRelayTxFee)) + { + strFailReason = _("Transaction amount too small"); + return false; + } + wtxNew.vout.push_back(txout); + } // Choose coins to use set<pair<const CWalletTx*,unsigned int> > setCoins; - int64 nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn)) + int64_t nValueIn = 0; + if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) + { + strFailReason = _("Insufficient funds"); return false; + } BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { - int64 nCredit = pcoin.first->vout[pcoin.second].nValue; + int64_t nCredit = pcoin.first->vout[pcoin.second].nValue; //The priority after the next block (depth+1) is used instead of the current, //reflecting an assumption the user would accept a bit more delay for //a chance at a free transaction. dPriority += (double)nCredit * (pcoin.first->GetDepthInMainChain()+1); } - int64 nChange = nValueIn - nValue - nFeeRet; - // if sub-cent change is required, the fee must be raised to at least MIN_TX_FEE - // or until nChange becomes zero - // NOTE: this depends on the exact behaviour of GetMinFee - if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT) + int64_t nChange = nValueIn - nValue - nFeeRet; + // The following if statement should be removed once enough miners + // have upgraded to the 0.9 GetMinFee() rules. Until then, this avoids + // creating free transactions that have change outputs less than + // CENT bitcoins. + if (nFeeRet < CTransaction::nMinTxFee && nChange > 0 && nChange < CENT) { - int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); + int64_t nMoveToFee = min(nChange, CTransaction::nMinTxFee - nFeeRet); nChange -= nMoveToFee; nFeeRet += nMoveToFee; } if (nChange > 0) { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey = reservekey.GetReservedKey(); - // assert(mapKeys.count(vchPubKey)); - // Fill a vout to ourself // TODO: pass in scriptChange instead of reservekey so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - scriptChange.SetDestination(vchPubKey.GetID()); - // Insert change txn at random position: - vector<CTxOut>::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()+1); - wtxNew.vout.insert(position, CTxOut(nChange, scriptChange)); + // coin control: send change to custom address + if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) + scriptChange.SetDestination(coinControl->destChange); + + // no coin control: send change to newly generated address + else + { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey); + assert(ret); // should never fail, as we just unlocked + + scriptChange.SetDestination(vchPubKey.GetID()); + } + + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust(CTransaction::nMinRelayTxFee)) + { + nFeeRet += nChange; + reservekey.ReturnKey(); + } + else + { + // Insert change txn at random position: + vector<CTxOut>::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()+1); + wtxNew.vout.insert(position, newTxOut); + } } else reservekey.ReturnKey(); @@ -1204,18 +1428,24 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CW int nIn = 0; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) + { + strFailReason = _("Signing transaction failed"); return false; + } // Limit size unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= MAX_STANDARD_TX_SIZE) + { + strFailReason = _("Transaction too large"); return false; - dPriority /= nBytes; + } + dPriority = wtxNew.ComputePriority(dPriority, nBytes); // Check that enough fee is included - int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); - bool fAllowFree = CTransaction::AllowFree(dPriority); - int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND); + int64_t nPayFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); + bool fAllowFree = AllowFree(dPriority); + int64_t nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -1233,11 +1463,12 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CW return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { - vector< pair<CScript, int64> > vecSend; + vector< pair<CScript, int64_t> > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet); + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); } // Call after CreateTransaction unless you want to abort @@ -1245,7 +1476,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { { LOCK2(cs_main, cs_wallet); - printf("CommitTransaction:\n%s", wtxNew.ToString().c_str()); + LogPrintf("CommitTransaction:\n%s", wtxNew.ToString()); { // This is only to keep the database open to defeat the auto-flush for the // duration of this scope. This is the only place where this optimization @@ -1278,10 +1509,10 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) mapRequestCount[wtxNew.GetHash()] = 0; // Broadcast - if (!wtxNew.AcceptToMemoryPool(true, false)) + if (!wtxNew.AcceptToMemoryPool(false)) { // This must not fail. The transaction has already been signed and recorded. - printf("CommitTransaction() : Error: Transaction not valid"); + LogPrintf("CommitTransaction() : Error: Transaction not valid"); return false; } wtxNew.RelayWalletTransaction(); @@ -1292,31 +1523,26 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) -string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew) { CReserveKey reservekey(this); - int64 nFeeRequired; + int64_t nFeeRequired; if (IsLocked()) { string strError = _("Error: Wallet locked, unable to create transaction!"); - printf("SendMoney() : %s", strError.c_str()); + LogPrintf("SendMoney() : %s", strError); return strError; } - if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) + string strError; + if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError)) { - string strError; if (nValue + nFeeRequired > GetBalance()) - strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!"), FormatMoney(nFeeRequired).c_str()); - else - strError = _("Error: Transaction creation failed!"); - printf("SendMoney() : %s", strError.c_str()); + strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!"), FormatMoney(nFeeRequired)); + LogPrintf("SendMoney() : %s\n", strError); return strError; } - if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired)) - return "ABORTED"; - if (!CommitTransaction(wtxNew, reservekey)) return _("Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); @@ -1325,7 +1551,7 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, -string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoneyToDestination(const CTxDestination& address, int64_t nValue, CWalletTx& wtxNew) { // Check amount if (nValue <= 0) @@ -1337,7 +1563,7 @@ string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nVal CScript scriptPubKey; scriptPubKey.SetDestination(address); - return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee); + return SendMoney(scriptPubKey, nValue, wtxNew); } @@ -1353,6 +1579,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { if (CDB::Rewrite(strWalletFile, "\x04pool")) { + LOCK(cs_wallet); setKeyPool.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation @@ -1364,56 +1591,77 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) return nLoadWalletRet; fFirstRunRet = !vchDefaultKey.IsValid(); - NewThread(ThreadFlushWalletDB, &strWalletFile); return DB_LOAD_OK; } -bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName) +DBErrors CWallet::ZapWalletTx() { - std::map<CTxDestination, std::string>::iterator mi = mapAddressBook.find(address); - mapAddressBook[address] = strName; - NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) - return false; - return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); -} + return DB_LOAD_OK; + DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(this); + if (nZapWalletTxRet == DB_NEED_REWRITE) + { + if (CDB::Rewrite(strWalletFile, "\x04pool")) + { + LOCK(cs_wallet); + setKeyPool.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // the requires a new key. + } + } -bool CWallet::DelAddressBookName(const CTxDestination& address) -{ - mapAddressBook.erase(address); - NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), CT_DELETED); - if (!fFileBacked) - return false; - return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); + if (nZapWalletTxRet != DB_LOAD_OK) + return nZapWalletTxRet; + + return DB_LOAD_OK; } -void CWallet::PrintWallet(const CBlock& block) +bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose) { + bool fUpdated = false; { - LOCK(cs_wallet); - if (mapWallet.count(block.vtx[0].GetHash())) - { - CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; - printf(" mine: %d %d %"PRI64d"", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit()); - } + LOCK(cs_wallet); // mapAddressBook + std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address); + fUpdated = mi != mapAddressBook.end(); + mapAddressBook[address].name = strName; + if (!strPurpose.empty()) /* update purpose only if requested */ + mapAddressBook[address].purpose = strPurpose; } - printf("\n"); + NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address), + strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); + if (!fFileBacked) + return false; + if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose)) + return false; + return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); } -bool CWallet::GetTransaction(const uint256 &hashTx, CWalletTx& wtx) +bool CWallet::DelAddressBook(const CTxDestination& address) { { - LOCK(cs_wallet); - map<uint256, CWalletTx>::iterator mi = mapWallet.find(hashTx); - if (mi != mapWallet.end()) + LOCK(cs_wallet); // mapAddressBook + + if(fFileBacked) { - wtx = (*mi).second; - return true; + // Delete destdata tuples associated with address + std::string strAddress = CBitcoinAddress(address).ToString(); + BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata) + { + CWalletDB(strWalletFile).EraseDestData(strAddress, item.first); + } } + mapAddressBook.erase(address); } - return false; + + NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), "", CT_DELETED); + + if (!fFileBacked) + return false; + CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString()); + return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); } bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) @@ -1427,14 +1675,6 @@ bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) return true; } -bool GetWalletFile(CWallet* pwallet, string &strWalletFileOut) -{ - if (!pwallet->fFileBacked) - return false; - strWalletFileOut = pwallet->strWalletFile; - return true; -} - // // Mark old keypool keys as used, // and generate all new keys @@ -1444,26 +1684,26 @@ bool CWallet::NewKeyPool() { LOCK(cs_wallet); CWalletDB walletdb(strWalletFile); - BOOST_FOREACH(int64 nIndex, setKeyPool) + BOOST_FOREACH(int64_t nIndex, setKeyPool) walletdb.ErasePool(nIndex); setKeyPool.clear(); if (IsLocked()) return false; - int64 nKeys = max(GetArg("-keypool", 100), (int64)0); + int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0); for (int i = 0; i < nKeys; i++) { - int64 nIndex = i+1; + int64_t nIndex = i+1; walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); setKeyPool.insert(nIndex); } - printf("CWallet::NewKeyPool wrote %"PRI64d" new keys\n", nKeys); + LogPrintf("CWallet::NewKeyPool wrote %"PRId64" new keys\n", nKeys); } return true; } -bool CWallet::TopUpKeyPool() +bool CWallet::TopUpKeyPool(unsigned int kpSize) { { LOCK(cs_wallet); @@ -1474,22 +1714,27 @@ bool CWallet::TopUpKeyPool() CWalletDB walletdb(strWalletFile); // Top up key pool - unsigned int nTargetSize = max(GetArg("-keypool", 100), 0LL); + unsigned int nTargetSize; + if (kpSize > 0) + nTargetSize = kpSize; + else + nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0); + while (setKeyPool.size() < (nTargetSize + 1)) { - int64 nEnd = 1; + int64_t nEnd = 1; if (!setKeyPool.empty()) nEnd = *(--setKeyPool.end()) + 1; if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) throw runtime_error("TopUpKeyPool() : writing generated key failed"); setKeyPool.insert(nEnd); - printf("keypool added key %"PRI64d", size=%"PRIszu"\n", nEnd, setKeyPool.size()); + LogPrintf("keypool added key %"PRId64", size=%"PRIszu"\n", nEnd, setKeyPool.size()); } } return true; } -void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) { nIndex = -1; keypool.vchPubKey = CPubKey(); @@ -1512,17 +1757,17 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) if (!HaveKey(keypool.vchPubKey.GetID())) throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); assert(keypool.vchPubKey.IsValid()); - printf("keypool reserve %"PRI64d"\n", nIndex); + LogPrintf("keypool reserve %"PRId64"\n", nIndex); } } -int64 CWallet::AddReserveKey(const CKeyPool& keypool) +int64_t CWallet::AddReserveKey(const CKeyPool& keypool) { { LOCK2(cs_main, cs_wallet); CWalletDB walletdb(strWalletFile); - int64 nIndex = 1 + *(--setKeyPool.end()); + int64_t nIndex = 1 + *(--setKeyPool.end()); if (!walletdb.WritePool(nIndex, keypool)) throw runtime_error("AddReserveKey() : writing added key failed"); setKeyPool.insert(nIndex); @@ -1531,7 +1776,7 @@ int64 CWallet::AddReserveKey(const CKeyPool& keypool) return -1; } -void CWallet::KeepKey(int64 nIndex) +void CWallet::KeepKey(int64_t nIndex) { // Remove from key pool if (fFileBacked) @@ -1539,33 +1784,28 @@ void CWallet::KeepKey(int64 nIndex) CWalletDB walletdb(strWalletFile); walletdb.ErasePool(nIndex); } - printf("keypool keep %"PRI64d"\n", nIndex); + LogPrintf("keypool keep %"PRId64"\n", nIndex); } -void CWallet::ReturnKey(int64 nIndex) +void CWallet::ReturnKey(int64_t nIndex) { // Return to key pool { LOCK(cs_wallet); setKeyPool.insert(nIndex); } - printf("keypool return %"PRI64d"\n", nIndex); + LogPrintf("keypool return %"PRId64"\n", nIndex); } -bool CWallet::GetKeyFromPool(CPubKey& result, bool fAllowReuse) +bool CWallet::GetKeyFromPool(CPubKey& result) { - int64 nIndex = 0; + int64_t nIndex = 0; CKeyPool keypool; { LOCK(cs_wallet); ReserveKeyFromKeyPool(nIndex, keypool); if (nIndex == -1) { - if (fAllowReuse && vchDefaultKey.IsValid()) - { - result = vchDefaultKey; - return true; - } if (IsLocked()) return false; result = GenerateNewKey(); return true; @@ -1576,9 +1816,9 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool fAllowReuse) return true; } -int64 CWallet::GetOldestKeyPoolTime() +int64_t CWallet::GetOldestKeyPoolTime() { - int64 nIndex = 0; + int64_t nIndex = 0; CKeyPool keypool; ReserveKeyFromKeyPool(nIndex, keypool); if (nIndex == -1) @@ -1587,9 +1827,9 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } -std::map<CTxDestination, int64> CWallet::GetAddressBalances() +std::map<CTxDestination, int64_t> CWallet::GetAddressBalances() { - map<CTxDestination, int64> balances; + map<CTxDestination, int64_t> balances; { LOCK(cs_wallet); @@ -1597,7 +1837,7 @@ std::map<CTxDestination, int64> CWallet::GetAddressBalances() { CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted()) continue; if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) @@ -1615,7 +1855,7 @@ std::map<CTxDestination, int64> CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) continue; - int64 n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; + int64_t n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -1629,6 +1869,7 @@ std::map<CTxDestination, int64> CWallet::GetAddressBalances() set< set<CTxDestination> > CWallet::GetAddressGroupings() { + AssertLockHeld(cs_wallet); // mapWallet set< set<CTxDestination> > groupings; set<CTxDestination> grouping; @@ -1719,7 +1960,21 @@ set< set<CTxDestination> > CWallet::GetAddressGroupings() return ret; } -CPubKey CReserveKey::GetReservedKey() +set<CTxDestination> CWallet::GetAccountAddresses(string strAccount) const +{ + AssertLockHeld(cs_wallet); // mapWallet + set<CTxDestination> result; + BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook) + { + const CTxDestination& address = item.first; + const string& strName = item.second.name; + if (strName == strAccount) + result.insert(address); + } + return result; +} + +bool CReserveKey::GetReservedKey(CPubKey& pubkey) { if (nIndex == -1) { @@ -1727,14 +1982,17 @@ CPubKey CReserveKey::GetReservedKey() pwallet->ReserveKeyFromKeyPool(nIndex, keypool); if (nIndex != -1) vchPubKey = keypool.vchPubKey; - else - { - printf("CReserveKey::GetReservedKey(): Warning: Using default key instead of a new key, top up your keypool!"); - vchPubKey = pwallet->vchDefaultKey; + else { + if (pwallet->vchDefaultKey.IsValid()) { + LogPrintf("CReserveKey::GetReservedKey(): Warning: Using default key instead of a new key, top up your keypool!"); + vchPubKey = pwallet->vchDefaultKey; + } else + return false; } } assert(vchPubKey.IsValid()); - return vchPubKey; + pubkey = vchPubKey; + return true; } void CReserveKey::KeepKey() @@ -1753,14 +2011,14 @@ void CReserveKey::ReturnKey() vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) +void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const { setAddress.clear(); CWalletDB walletdb(strWalletFile); LOCK2(cs_main, cs_wallet); - BOOST_FOREACH(const int64& id, setKeyPool) + BOOST_FOREACH(const int64_t& id, setKeyPool) { CKeyPool keypool; if (!walletdb.ReadPool(id, keypool)) @@ -1786,21 +2044,25 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx) void CWallet::LockCoin(COutPoint& output) { + AssertLockHeld(cs_wallet); // setLockedCoins setLockedCoins.insert(output); } void CWallet::UnlockCoin(COutPoint& output) { + AssertLockHeld(cs_wallet); // setLockedCoins setLockedCoins.erase(output); } void CWallet::UnlockAllCoins() { + AssertLockHeld(cs_wallet); // setLockedCoins setLockedCoins.clear(); } bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const { + AssertLockHeld(cs_wallet); // setLockedCoins COutPoint outpt(hash, n); return (setLockedCoins.count(outpt) > 0); @@ -1808,6 +2070,7 @@ bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) { + AssertLockHeld(cs_wallet); // setLockedCoins for (std::set<COutPoint>::iterator it = setLockedCoins.begin(); it != setLockedCoins.end(); it++) { COutPoint outpt = (*it); @@ -1815,3 +2078,96 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) } } +void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const { + AssertLockHeld(cs_wallet); // mapKeyMetadata + mapKeyBirth.clear(); + + // get birth times for keys with metadata + for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) + if (it->second.nCreateTime) + mapKeyBirth[it->first] = it->second.nCreateTime; + + // map in which we'll infer heights of other keys + CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin + std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock; + std::set<CKeyID> setKeys; + GetKeys(setKeys); + BOOST_FOREACH(const CKeyID &keyid, setKeys) { + if (mapKeyBirth.count(keyid) == 0) + mapKeyFirstBlock[keyid] = pindexMax; + } + setKeys.clear(); + + // if there are no such keys, we're done + if (mapKeyFirstBlock.empty()) + return; + + // find first block that affects those keys, if there are any left + std::vector<CKeyID> vAffected; + for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { + // iterate over all wallet transactions... + const CWalletTx &wtx = (*it).second; + std::map<uint256, CBlockIndex*>::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); + if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { + // ... which are already in a block + int nHeight = blit->second->nHeight; + BOOST_FOREACH(const CTxOut &txout, wtx.vout) { + // iterate over all their outputs + ::ExtractAffectedKeys(*this, txout.scriptPubKey, vAffected); + BOOST_FOREACH(const CKeyID &keyid, vAffected) { + // ... and all their affected keys + std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) + rit->second = blit->second; + } + vAffected.clear(); + } + } + } + + // Extract block timestamps for those keys + for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) + mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off +} + +bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +{ + if (boost::get<CNoDestination>(&dest)) + return false; + + mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value); +} + +bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) +{ + if (!mapAddressBook[dest].destdata.erase(key)) + return false; + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).EraseDestData(CBitcoinAddress(dest).ToString(), key); +} + +bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +{ + mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); + return true; +} + +bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const +{ + std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest); + if(i != mapAddressBook.end()) + { + CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); + if(j != i->second.destdata.end()) + { + if(value) + *value = j->second; + return true; + } + } + return false; +} |