diff options
Diffstat (limited to 'src/wallet.cpp')
| -rw-r--r-- | src/wallet.cpp | 1912 |
1 files changed, 1333 insertions, 579 deletions
diff --git a/src/wallet.cpp b/src/wallet.cpp index 4d99ce656..6c5af3bdc 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,60 +1,121 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2012 The Bitcoin developers -// Distributed under the MIT/X11 software license, see the accompanying +// Copyright (c) 2009-2014 The Bitcoin Core developers +// Distributed under the MIT 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 "checkpoints.h" +#include "coincontrol.h" +#include "net.h" +#include "script/script.h" +#include "script/sign.h" +#include "timedata.h" +#include "util.h" +#include "utilmoneystr.h" -using namespace std; +#include <assert.h> +#include <boost/algorithm/string/replace.hpp> +#include <boost/thread.hpp> -////////////////////////////////////////////////////////////////////////////// -// -// mapWallet -// +using namespace std; + +/** + * Settings + */ +CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); +CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; +unsigned int nTxConfirmTarget = 1; +bool bSpendZeroConfChange = true; +bool fSendFreeTransactions = false; +bool fPayAtLeastCustomFee = true; + +/** + * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) + * Override with -mintxfee + */ +CFeeRate CWallet::minTxFee = CFeeRate(1000); + +/** @defgroup mapWallet + * + * @{ + */ 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<CAmount, pair<const CWalletTx*, unsigned int> >& t1, + const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t2) const { return t1.first < t2.first; } }; +std::string COutput::ToString() const +{ + return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue)); +} + +const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const +{ + LOCK(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash); + if (it == mapWallet.end()) + return NULL; + return &(it->second); +} + 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(); + assert(secret.VerifyPubKey(pubkey)); + + // 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; + + // check if we need to remove from watch-only + CScript script; + script = GetScriptForDestination(pubkey.GetID()); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); + 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 +124,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)) @@ -79,11 +159,54 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } -bool CWallet::Unlock(const SecureString& strWalletPassphrase) +bool CWallet::LoadCScript(const CScript& redeemScript) +{ + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = CBitcoinAddress(CScriptID(redeemScript)).ToString(); + LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", + __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; + } + + return CCryptoKeyStore::AddCScript(redeemScript); +} + +bool CWallet::AddWatchOnly(const CScript &dest) { - if (!IsLocked()) + if (!CCryptoKeyStore::AddWatchOnly(dest)) return false; + nTimeFirstKey = 1; // No birthday information for watch-only keys. + NotifyWatchonlyChanged(true); + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteWatchOnly(dest); +} +bool CWallet::RemoveWatchOnly(const CScript &dest) +{ + AssertLockHeld(cs_wallet); + if (!CCryptoKeyStore::RemoveWatchOnly(dest)) + return false; + if (!HaveWatchOnly()) + NotifyWatchonlyChanged(false); + if (fFileBacked) + if (!CWalletDB(strWalletFile).EraseWatchOnly(dest)) + return false; + + return true; +} + +bool CWallet::LoadWatchOnly(const CScript &dest) +{ + return CCryptoKeyStore::AddWatchOnly(dest); +} + +bool CWallet::Unlock(const SecureString& strWalletPassphrase) +{ CCrypter crypter; CKeyingMaterial vMasterKey; @@ -94,7 +217,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 +243,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 +254,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 +277,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 +295,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 +306,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 +316,106 @@ 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<TxSpends::const_iterator, TxSpends::const_iterator> range; + + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + if (mapTxSpends.count(txin.prevout) <= 1) + continue; // No conflict if zero or one spends + range = mapTxSpends.equal_range(txin.prevout); + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) + result.insert(it->second); + } + return result; +} + +void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::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 (TxSpends::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 (TxSpends::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; + // nOrderPos not copied on purpose + // cached members not copied on purpose + } +} + +/** + * Outpoint is spent if any non-conflicted transaction + * spends it: + */ +bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +{ + const COutPoint outpoint(hash, n); + pair<TxSpends::const_iterator, TxSpends::const_iterator> range; + range = mapTxSpends.equal_range(outpoint); + + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) + { + const uint256& wtxid = it->second; + std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); + if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) + return true; // Spent + } + return false; +} + +void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid) +{ + mapTxSpends.insert(make_pair(outpoint, wtxid)); + + pair<TxSpends::iterator, TxSpends::iterator> range; + range = mapTxSpends.equal_range(outpoint); + SyncMetaData(range); +} + + +void CWallet::AddToSpends(const uint256& wtxid) +{ + assert(mapWallet.count(wtxid)); + CWalletTx& thisTx = mapWallet[wtxid]; + if (thisTx.IsCoinBase()) // Coinbases don't spend anything! + return; + + BOOST_FOREACH(const CTxIn& txin, thisTx.vin) + AddToSpends(txin.prevout, wtxid); +} + bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) { if (IsCrypted()) @@ -219,16 +425,16 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) RandAddSeedPerfmon(); vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); - RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); + GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); CMasterKey kMasterKey; - RandAddSeedPerfmon(); + kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); - RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + GetRandBytes(&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 +445,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; @@ -251,17 +457,25 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; if (fFileBacked) { + assert(!pwalletdbEncryption); pwalletdbEncryption = new CWalletDB(strWalletFile); - if (!pwalletdbEncryption->TxnBegin()) + if (!pwalletdbEncryption->TxnBegin()) { + delete pwalletdbEncryption; + pwalletdbEncryption = NULL; return false; + } pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); } if (!EncryptKeys(vMasterKey)) { - if (fFileBacked) + if (fFileBacked) { pwalletdbEncryption->TxnAbort(); - exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet. + delete pwalletdbEncryption; + } + // We now probably have half of our keys encrypted in memory, and half not... + // die and let the user reload their unencrypted wallet. + assert(false); } // Encryption was introduced in version 0.4.0 @@ -269,8 +483,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) if (fFileBacked) { - if (!pwalletdbEncryption->TxnCommit()) - exit(1); //We now have keys encrypted in memory, but no on disk...die to avoid confusion and let the user reload their unencrypted wallet. + if (!pwalletdbEncryption->TxnCommit()) { + delete pwalletdbEncryption; + // We now have keys encrypted in memory, but not on disk... + // die to avoid confusion and let the user reload their unencrypted wallet. + assert(false); + } delete pwalletdbEncryption; pwalletdbEncryption = NULL; @@ -291,29 +509,41 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) return true; } -void CWallet::WalletUpdateSpent(const CTransaction &tx) +int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) { - // Anytime a signature is successfully verified, it's proof the outpoint is spent. - // Update the wallet spent flag if it doesn't know due to wallet.dat being - // restored from backup or the user making copies of wallet.dat. + AssertLockHeld(cs_wallet); // nOrderPosNext + int64_t nRet = nOrderPosNext++; + if (pwalletdb) { + pwalletdb->WriteOrderPosNext(nOrderPosNext); + } else { + CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext); + } + return nRet; +} + +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. + TxItems txOrdered; + + // Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry + // would make this much faster for applications that do this a lot. + for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { - LOCK(cs_wallet); - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) - { - CWalletTx& wtx = (*mi).second; - 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()); - wtx.MarkSpent(txin.prevout.n); - wtx.WriteToDisk(); - NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); - } - } - } + CWalletTx* wtx = &((*it).second); + txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0))); } + acentries.clear(); + walletdb.ListAccountCreditDebit(strAccount, acentries); + BOOST_FOREACH(CAccountingEntry& entry, acentries) + { + txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry))); + } + + return txOrdered; } void CWallet::MarkDirty() @@ -325,9 +555,17 @@ 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; + mapWallet[hash].BindWallet(this); + AddToSpends(hash); + } + else { LOCK(cs_wallet); // Inserts only if not already there, returns tx inserted or tx found @@ -336,13 +574,63 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) + { wtx.nTimeReceived = GetAdjustedTime(); + wtx.nOrderPos = IncOrderPosNext(); + + wtx.nTimeSmart = wtx.nTimeReceived; + if (!wtxIn.hashBlock.IsNull()) + { + if (mapBlockIndex.count(wtxIn.hashBlock)) + { + int64_t latestNow = wtx.nTimeReceived; + int64_t latestEntry = 0; + { + // Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future + int64_t latestTolerated = latestNow + 300; + std::list<CAccountingEntry> acentries; + TxItems txOrdered = OrderedTxItems(acentries); + for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx == &wtx) + continue; + CAccountingEntry *const pacentry = (*it).second.second; + int64_t nSmartTime; + if (pwtx) + { + nSmartTime = pwtx->nTimeSmart; + if (!nSmartTime) + nSmartTime = pwtx->nTimeReceived; + } + else + nSmartTime = pacentry->nTime; + if (nSmartTime <= latestTolerated) + { + latestEntry = nSmartTime; + if (nSmartTime > latestNow) + latestNow = nSmartTime; + break; + } + } + } + + int64_t blocktime = mapBlockIndex[wtxIn.hashBlock]->GetBlockTime(); + wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); + } + else + LogPrintf("AddToWallet() : found %s in block %s not in index\n", + wtxIn.GetHash().ToString(), + wtxIn.hashBlock.ToString()); + } + AddToSpends(hash); + } bool fUpdated = false; if (!fInsertedNew) { // Merge - if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock) + if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; @@ -358,80 +646,88 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) wtx.fFromMe = wtxIn.fFromMe; fUpdated = true; } - fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent); } //// 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); + + // Break debit/credit balance caches: + wtx.MarkDirty(); // 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; } -// 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 CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock) +/** + * 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 CTransaction& tx, const CBlock* pblock, bool fUpdate) { - uint256 hash = tx.GetHash(); { - LOCK(cs_wallet); - bool fExisted = mapWallet.count(hash); + AssertLockHeld(cs_wallet); + bool fExisted = mapWallet.count(tx.GetHash()) != 0; if (fExisted && !fUpdate) return false; if (fExisted || IsMine(tx) || IsFromMe(tx)) { CWalletTx wtx(this,tx); // Get merkle branch if transaction was found in a block if (pblock) - wtx.SetMerkleBranch(pblock); + wtx.SetMerkleBranch(*pblock); return AddToWallet(wtx); } - else - WalletUpdateSpent(tx); } return false; } -bool CWallet::EraseFromWallet(uint256 hash) +void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock) +{ + LOCK2(cs_main, cs_wallet); + if (!AddToWalletIfInvolvingMe(tx, pblock, true)) + return; // Not one of ours + + // If a transaction changes 'conflicted' state, that changes the balance + // available of the outputs it spends. So force those to be + // recomputed, also: + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + if (mapWallet.count(txin.prevout.hash)) + mapWallet[txin.prevout.hash].MarkDirty(); + } +} + +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; } -bool CWallet::IsMine(const CTxIn &txin) const +isminetype CWallet::IsMine(const CTxIn &txin) const { { LOCK(cs_wallet); @@ -440,14 +736,13 @@ bool CWallet::IsMine(const CTxIn &txin) const { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.vout.size()) - if (IsMine(prev.vout[txin.prevout.n])) - return true; + return IsMine(prev.vout[txin.prevout.n]); } } - return false; + return ISMINE_NO; } -int64 CWallet::GetDebit(const CTxIn &txin) const +CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const { { LOCK(cs_wallet); @@ -456,7 +751,7 @@ int64 CWallet::GetDebit(const CTxIn &txin) const { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.vout.size()) - if (IsMine(prev.vout[txin.prevout.n])) + if (IsMine(prev.vout[txin.prevout.n]) & filter) return prev.vout[txin.prevout.n].nValue; } } @@ -465,17 +760,19 @@ int64 CWallet::GetDebit(const CTxIn &txin) const bool CWallet::IsChange(const CTxOut& txout) const { - CTxDestination address; - // TODO: fix handling of 'change' outputs. The assumption is that any - // payment to a TX_PUBKEYHASH that is mine but isn't in the address book + // payment to a script that is ours, but is not in the address book // is change. That assumption is likely to break when we implement multisignature // wallets that return change back into a multi-signature-protected address; // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (ExtractDestination(txout.scriptPubKey, address) && ::IsMine(*this, address)) + if (::IsMine(*this, txout.scriptPubKey)) { + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address)) + return true; + LOCK(cs_wallet); if (!mapAddressBook.count(address)) return true; @@ -483,9 +780,10 @@ bool CWallet::IsChange(const CTxOut& txout) const return false; } -int64 CWalletTx::GetTxTime() const +int64_t CWalletTx::GetTxTime() const { - return nTimeReceived; + int64_t n = nTimeSmart; + return n ? n : nTimeReceived; } int CWalletTx::GetRequestCount() const @@ -497,7 +795,7 @@ int CWalletTx::GetRequestCount() const if (IsCoinBase()) { // Generated block - if (hashBlock != 0) + if (!hashBlock.IsNull()) { map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); if (mi != pwallet->mapRequestCount.end()) @@ -513,7 +811,7 @@ int CWalletTx::GetRequestCount() const nRequests = (*mi).second; // How about the block it's in? - if (nRequests == 0 && hashBlock != 0) + if (nRequests == 0 && !hashBlock.IsNull()) { map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); if (mi != pwallet->mapRequestCount.end()) @@ -527,300 +825,208 @@ int CWalletTx::GetRequestCount() const return nRequests; } -void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list<pair<CTxDestination, int64> >& listReceived, - list<pair<CTxDestination, int64> >& listSent, int64& nFee, string& strSentAccount) const +void CWalletTx::GetAmounts(list<COutputEntry>& listReceived, + list<COutputEntry>& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const { - nGeneratedImmature = nGeneratedMature = nFee = 0; + nFee = 0; listReceived.clear(); listSent.clear(); strSentAccount = strFromAccount; - if (IsCoinBase()) - { - if (GetBlocksToMaturity() > 0) - nGeneratedImmature = pwallet->GetCredit(*this); - else - nGeneratedMature = GetCredit(); - return; - } - // Compute fee: - int64 nDebit = GetDebit(); + CAmount nDebit = GetDebit(filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - int64 nValueOut = GetValueOut(); + CAmount nValueOut = GetValueOut(); nFee = nDebit - nValueOut; } // Sent/received. - BOOST_FOREACH(const CTxOut& txout, vout) + for (unsigned int i = 0; i < vout.size(); ++i) { + const CTxOut& txout = vout[i]; + isminetype fIsMine = pwallet->IsMine(txout); + // 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; + } + else if (!(fIsMine & filter)) + 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; + COutputEntry output = {address, txout.nValue, (int)i}; + // 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)); + listSent.push_back(output); - if (pwallet->IsMine(txout)) - listReceived.push_back(make_pair(address, txout.nValue)); + // If we are receiving the output, add it as a "received" entry + if (fIsMine & filter) + listReceived.push_back(output); } } -void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, - int64& nSent, int64& nFee) const +void CWalletTx::GetAccountAmounts(const string& strAccount, CAmount& nReceived, + CAmount& nSent, CAmount& nFee, const isminefilter& filter) const { - nGenerated = nReceived = nSent = nFee = 0; + nReceived = nSent = nFee = 0; - int64 allGeneratedImmature, allGeneratedMature, allFee; - allGeneratedImmature = allGeneratedMature = allFee = 0; + CAmount allFee; string strSentAccount; - list<pair<CTxDestination, int64> > listReceived; - list<pair<CTxDestination, int64> > listSent; - GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); + list<COutputEntry> listReceived; + list<COutputEntry> listSent; + GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); - if (strAccount == "") - nGenerated = allGeneratedMature; if (strAccount == strSentAccount) { - BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& s, listSent) - nSent += s.second; + BOOST_FOREACH(const COutputEntry& s, listSent) + nSent += s.amount; nFee = allFee; } { LOCK(pwallet->cs_wallet); - BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64)& r, listReceived) + BOOST_FOREACH(const COutputEntry& r, listReceived) { - if (pwallet->mapAddressBook.count(r.first)) + if (pwallet->mapAddressBook.count(r.destination)) { - map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first); - if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) - nReceived += r.second; + map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.destination); + if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount) + nReceived += r.amount; } else if (strAccount.empty()) { - nReceived += r.second; + nReceived += r.amount; } } } } -void CWalletTx::AddSupportingTransactions(CTxDB& txdb) -{ - vtxPrev.clear(); - - const int COPY_DEPTH = 3; - if (SetMerkleBranch() < COPY_DEPTH) - { - vector<uint256> vWorkQueue; - BOOST_FOREACH(const CTxIn& txin, vin) - vWorkQueue.push_back(txin.prevout.hash); - - // This critsect is OK because txdb is already open - { - LOCK(pwallet->cs_wallet); - map<uint256, const CMerkleTx*> mapWalletPrev; - set<uint256> setAlreadyDone; - for (unsigned int i = 0; i < vWorkQueue.size(); i++) - { - uint256 hash = vWorkQueue[i]; - if (setAlreadyDone.count(hash)) - continue; - setAlreadyDone.insert(hash); - - CMerkleTx tx; - map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(hash); - if (mi != pwallet->mapWallet.end()) - { - tx = (*mi).second; - BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev) - mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev; - } - else if (mapWalletPrev.count(hash)) - { - tx = *mapWalletPrev[hash]; - } - else if (!fClient && txdb.ReadDiskTx(hash, tx)) - { - ; - } - else - { - printf("ERROR: AddSupportingTransactions() : unsupported transaction\n"); - continue; - } - - int nDepth = tx.SetMerkleBranch(); - vtxPrev.push_back(tx); - - if (nDepth < COPY_DEPTH) - { - BOOST_FOREACH(const CTxIn& txin, tx.vin) - vWorkQueue.push_back(txin.prevout.hash); - } - } - } - } - - reverse(vtxPrev.begin(), vtxPrev.end()); -} bool CWalletTx::WriteToDisk() { return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this); } -// Scan the block chain (starting in pindexStart) for transactions -// from or to us. If fUpdate is true, found transactions that already -// exist in the wallet will be updated. +/** + * Scan the block chain (starting in pindexStart) for transactions + * from or to us. If fUpdate is true, found transactions that already + * exist in the wallet will be updated. + */ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) { int ret = 0; + int64_t nNow = GetTime(); CBlockIndex* pindex = pindexStart; { - LOCK(cs_wallet); + LOCK2(cs_main, cs_wallet); + + // no need to read and scan block, if block was created before + // our wallet birthday (as adjusted for block time variability) + while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - 7200))) + pindex = chainActive.Next(pindex); + + ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup + double dProgressStart = Checkpoints::GuessVerificationProgress(pindex, false); + double dProgressTip = Checkpoints::GuessVerificationProgress(chainActive.Tip(), false); while (pindex) { + if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); + CBlock block; - block.ReadFromDisk(pindex, true); + ReadBlockFromDisk(block, pindex); BOOST_FOREACH(CTransaction& tx, block.vtx) { if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) ret++; } - pindex = pindex->pnext; + pindex = chainActive.Next(pindex); + if (GetTime() >= nNow + 60) { + nNow = GetTime(); + LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(pindex)); + } } + ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI } return ret; } -int CWallet::ScanForWalletTransaction(const uint256& hashTx) -{ - CTransaction tx; - tx.ReadFromDisk(COutPoint(hashTx, 0)); - if (AddToWalletIfInvolvingMe(tx, NULL, true, true)) - return 1; - return 0; -} - void CWallet::ReacceptWalletTransactions() { - CTxDB txdb("r"); - bool fRepeat = true; - while (fRepeat) + LOCK2(cs_main, cs_wallet); + BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { - LOCK(cs_wallet); - fRepeat = false; - vector<CDiskTxPos> vMissingTx; - BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) - { - CWalletTx& wtx = item.second; - if (wtx.IsCoinBase() && wtx.IsSpent(0)) - continue; + const uint256& wtxid = item.first; + CWalletTx& wtx = item.second; + assert(wtx.GetHash() == wtxid); - CTxIndex txindex; - bool fUpdated = false; - if (txdb.ReadTxIndex(wtx.GetHash(), txindex)) - { - // Update fSpent if a tx got spent somewhere else by a copy of wallet.dat - if (txindex.vSpent.size() != wtx.vout.size()) - { - printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size()); - continue; - } - for (unsigned int i = 0; i < txindex.vSpent.size(); i++) - { - if (wtx.IsSpent(i)) - continue; - if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i])) - { - wtx.MarkSpent(i); - fUpdated = true; - vMissingTx.push_back(txindex.vSpent[i]); - } - } - if (fUpdated) - { - printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); - wtx.MarkDirty(); - wtx.WriteToDisk(); - } - } - else - { - // Reaccept any txes of ours that aren't already in a block - if (!wtx.IsCoinBase()) - wtx.AcceptWalletTransaction(txdb, false); - } - } - if (!vMissingTx.empty()) + int nDepth = wtx.GetDepthInMainChain(); + + if (!wtx.IsCoinBase() && nDepth < 0) { - // TODO: optimize this to scan just part of the block chain? - if (ScanForWalletTransactions(pindexGenesisBlock)) - fRepeat = true; // Found missing transactions: re-do Reaccept. + // Try to add to memory pool + LOCK(mempool.cs); + wtx.AcceptToMemoryPool(false); } } } -void CWalletTx::RelayWalletTransaction(CTxDB& txdb) +void CWalletTx::RelayWalletTransaction() { - BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) - { - if (!tx.IsCoinBase()) - { - uint256 hash = tx.GetHash(); - if (!txdb.ContainsTx(hash)) - RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); - } - } if (!IsCoinBase()) { - uint256 hash = GetHash(); - if (!txdb.ContainsTx(hash)) - { - printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str()); - RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); + if (GetDepthInMainChain() == 0) { + LogPrintf("Relaying wtx %s\n", GetHash().ToString()); + RelayTransaction((CTransaction)*this); } } } -void CWalletTx::RelayWalletTransaction() +set<uint256> CWalletTx::GetConflicts() const { - CTxDB txdb("r"); - RelayWalletTransaction(txdb); + 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"); - CTxDB txdb("r"); + LogPrintf("ResendWalletTransactions()\n"); { LOCK(cs_wallet); // Sort them in chronological order @@ -830,37 +1036,37 @@ 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) { CWalletTx& wtx = *item.second; - wtx.RelayWalletTransaction(txdb); + wtx.RelayWalletTransaction(); } } } +/** @} */ // end of mapWallet +/** @defgroup Actions + * + * @{ + */ -////////////////////////////////////////////////////////////////////////////// -// -// Actions -// - -int64 CWallet::GetBalance() const +CAmount CWallet::GetBalance() const { - int64 nTotal = 0; + CAmount nTotal = 0; { - LOCK(cs_wallet); + LOCK2(cs_main, 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(); } } @@ -868,81 +1074,144 @@ int64 CWallet::GetBalance() const return nTotal; } -int64 CWallet::GetUnconfirmedBalance() const +CAmount CWallet::GetUnconfirmedBalance() const { - int64 nTotal = 0; + CAmount nTotal = 0; { - LOCK(cs_wallet); + LOCK2(cs_main, 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 +CAmount CWallet::GetImmatureBalance() const { - int64 nTotal = 0; + CAmount nTotal = 0; { - LOCK(cs_wallet); + LOCK2(cs_main, cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { - const CWalletTx& pcoin = (*it).second; - if (pcoin.IsCoinBase() && pcoin.GetBlocksToMaturity() > 0 && pcoin.GetDepthInMainChain() >= 2) - nTotal += GetCredit(pcoin); + const CWalletTx* pcoin = &(*it).second; + nTotal += pcoin->GetImmatureCredit(); } } return nTotal; } -// populate vCoins with vector of spendable COutputs -void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const +CAmount CWallet::GetWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsTrusted()) + nTotal += pcoin->GetAvailableWatchOnlyCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0)) + nTotal += pcoin->GetAvailableWatchOnlyCredit(); + } + } + return nTotal; +} + +CAmount CWallet::GetImmatureWatchOnlyBalance() const +{ + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + nTotal += pcoin->GetImmatureWatchOnlyCredit(); + } + } + return nTotal; +} + +/** + * populate vCoins with vector of available COutputs. + */ +void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const { vCoins.clear(); { - LOCK(cs_wallet); + LOCK2(cs_main, cs_wallet); for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const uint256& wtxid = it->first; 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; - for (unsigned int i = 0; i < pcoin->vout.size(); i++) - if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue > 0) - vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < 0) + continue; + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + isminetype mine = IsMine(pcoin->vout[i]); + if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && + !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 && + (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); + } } } } -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<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, + vector<char>& vfBest, CAmount& 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; + CAmount 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 is 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; @@ -963,32 +1232,35 @@ 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(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins, + set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& 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<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger; + coinLowestLarger.first = std::numeric_limits<CAmount>::max(); coinLowestLarger.second.first = NULL; - vector<pair<int64, pair<const CWalletTx*,unsigned int> > > vValue; - int64 nTotalLower = 0; + vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue; + CAmount nTotalLower = 0; random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); - BOOST_FOREACH(COutput output, vCoins) + BOOST_FOREACH(const COutput &output, vCoins) { + if (!output.fSpendable) + continue; + const CWalletTx *pcoin = output.tx; - if (output.nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs)) + if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) continue; int i = output.i; - int64 n = pcoin->vout[i].nValue; + CAmount n = pcoin->vout[i].nValue; - pair<int64,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i)); + pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i)); if (n == nTargetValue) { @@ -1029,7 +1301,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; + CAmount nBest; ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000); if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT) @@ -1051,160 +1323,260 @@ 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(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& 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) + { + if(!out.fSpendable) + continue; + 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, CAmount> >& vecSend, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { - int64 nValue = 0; - BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) + CAmount nValue = 0; + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& 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.fTimeReceivedIsTxTime = true; wtxNew.BindWallet(this); + CMutableTransaction txNew; + + // Discourage fee sniping. + // + // However because of a off-by-one-error in previous versions we need to + // neuter it by setting nLockTime to at least one less than nBestHeight. + // Secondly currently propagation of transactions created for block heights + // corresponding to blocks that were just mined may be iffy - transactions + // aren't re-accepted into the mempool - we additionally neuter the code by + // going ten blocks back. Doesn't yet do anything for sniping, but does act + // to shake out wallet bugs like not showing nLockTime'd transactions at + // all. + txNew.nLockTime = std::max(0, chainActive.Height() - 10); + + // Secondly occasionally randomly pick a nLockTime even further back, so + // that transactions that are delayed after signing for whatever reason, + // e.g. high-latency mix networks and some CoinJoin implementations, have + // better privacy. + if (GetRandInt(10) == 0) + txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); + + assert(txNew.nLockTime <= (unsigned int)chainActive.Height()); + assert(txNew.nLockTime < LOCKTIME_THRESHOLD); { LOCK2(cs_main, cs_wallet); - // txdb must be opened before the mapWallet lock - CTxDB txdb("r"); { - nFeeRet = nTransactionFee; - loop + nFeeRet = 0; + while (true) { - wtxNew.vin.clear(); - wtxNew.vout.clear(); + txNew.vin.clear(); + txNew.vout.clear(); wtxNew.fFromMe = true; - int64 nTotalValue = nValue + nFeeRet; + CAmount 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, CAmount)& s, vecSend) + { + CTxOut txout(s.second, s.first); + if (txout.IsDust(::minRelayTxFee)) + { + strFailReason = _("Transaction amount too small"); + return false; + } + txNew.vout.push_back(txout); + } // Choose coins to use set<pair<const CWalletTx*,unsigned int> > setCoins; - int64 nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn)) + CAmount 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; - dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain(); + CAmount 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 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); - nChange -= nMoveToFee; - nFeeRet += nMoveToFee; - } + CAmount nChange = nValueIn - nValue - nFeeRet; 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()); - wtxNew.vout.insert(position, CTxOut(nChange, scriptChange)); + // coin control: send change to custom address + if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) + scriptChange = GetScriptForDestination(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 = GetScriptForDestination(vchPubKey.GetID()); + } + + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust(::minRelayTxFee)) + { + nFeeRet += nChange; + reservekey.ReturnKey(); + } + else + { + // Insert change txn at random position: + vector<CTxOut>::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1); + txNew.vout.insert(position, newTxOut); + } } else reservekey.ReturnKey(); // Fill vin + // + // Note how the sequence number is set to max()-1 so that the + // nLockTime set above actually works. BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) - wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second)); + txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(), + std::numeric_limits<unsigned int>::max()-1)); // Sign int nIn = 0; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) - if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) + if (!SignSignature(*this, *coin.first, txNew, nIn++)) + { + strFailReason = _("Signing transaction failed"); return false; + } + + // Embed the constructed transaction data in wtxNew. + *static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew); // Limit size unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); - if (nBytes >= MAX_BLOCK_SIZE_GEN/5) + 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); - if (nFeeRet < max(nPayFee, nMinFee)) + // Can we complete this as a free transaction? + if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) { - nFeeRet = max(nPayFee, nMinFee); - continue; + // Not enough fee: enough priority? + double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget); + // Not enough mempool history to estimate: use hard-coded AllowFree. + if (dPriorityNeeded <= 0 && AllowFree(dPriority)) + break; + + // Small enough, and priority high enough, to send for free + if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded) + break; } - // Fill vtxPrev by copying from previous transactions vtxPrev - wtxNew.AddSupportingTransactions(txdb); - wtxNew.fTimeReceivedIsTxTime = true; + CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - break; + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) + { + strFailReason = _("Transaction too large for fee policy"); + return false; + } + + if (nFeeRet >= nFeeNeeded) + break; // Done, enough fee included. + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + continue; } } } return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { - vector< pair<CScript, int64> > vecSend; + vector< pair<CScript, CAmount> > 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 +/** + * Call after CreateTransaction unless you want to abort + */ 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 @@ -1218,14 +1590,12 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) // otherwise just for transaction history. AddToWallet(wtxNew); - // Mark old coins as spent + // Notify that old coins are spent set<CWalletTx*> setCoins; BOOST_FOREACH(const CTxIn& txin, wtxNew.vin) { CWalletTx &coin = mapWallet[txin.prevout.hash]; coin.BindWallet(this); - coin.MarkSpent(txin.prevout.n); - coin.WriteToDisk(); NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } @@ -1237,10 +1607,10 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) mapRequestCount[wtxNew.GetHash()] = 0; // Broadcast - if (!wtxNew.AcceptToMemoryPool()) + 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(); @@ -1248,132 +1618,127 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) return true; } - - - -string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) -{ - CReserveKey reservekey(this); - int64 nFeeRequired; - - if (IsLocked()) - { - string strError = _("Error: Wallet locked, unable to create transaction "); - printf("SendMoney() : %s", strError.c_str()); - return strError; - } - if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) - { - 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()); - return strError; - } - - if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) - 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."); - - return ""; -} - - - -string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee) -{ - // Check amount - if (nValue <= 0) - return _("Invalid amount"); - if (nValue + nTransactionFee > GetBalance()) - return _("Insufficient funds"); - - // Parse Bitcoin address - CScript scriptPubKey; - scriptPubKey.SetDestination(address); - - return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee); +CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool) +{ + // payTxFee is user-set "I want to pay this much" + CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); + // user selected total at least (default=true) + if (fPayAtLeastCustomFee && nFeeNeeded > 0 && nFeeNeeded < payTxFee.GetFeePerK()) + nFeeNeeded = payTxFee.GetFeePerK(); + // User didn't set: use -txconfirmtarget to estimate... + if (nFeeNeeded == 0) + nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes); + // ... unless we don't have enough mempool data, in which case fall + // back to a hard-coded fee + if (nFeeNeeded == 0) + nFeeNeeded = minTxFee.GetFee(nTxBytes); + // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee + if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes)) + nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes); + // But always obey the maximum + if (nFeeNeeded > maxTxFee) + nFeeNeeded = maxTxFee; + return nFeeNeeded; } -int CWallet::LoadWallet(bool& fFirstRunRet) +DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { if (!fFileBacked) - return false; + return DB_LOAD_OK; fFirstRunRet = false; - int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); + DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); if (nLoadWalletRet == 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. } - nLoadWalletRet = DB_NEED_REWRITE; } if (nLoadWalletRet != DB_LOAD_OK) return nLoadWalletRet; fFirstRunRet = !vchDefaultKey.IsValid(); - CreateThread(ThreadFlushWalletDB, &strWalletFile); + uiInterface.LoadWallet(this); + return DB_LOAD_OK; } -bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName) +DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) { - 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, vWtx); + 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 + // that 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 %d", 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) != ISMINE_NO, + 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) != ISMINE_NO, "", 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) @@ -1387,43 +1752,35 @@ 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 -// +/** + * Mark old keypool keys as used, + * and generate all new keys + */ 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 %d new keys\n", nKeys); } return true; } -bool CWallet::TopUpKeyPool() +bool CWallet::TopUpKeyPool(unsigned int kpSize) { { LOCK(cs_wallet); @@ -1434,22 +1791,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=%d\n", nEnd, setKeyPool.size()); + LogPrintf("keypool added key %d, size=%u\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(); @@ -1472,26 +1834,11 @@ 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 %d\n", nIndex); } } -int64 CWallet::AddReserveKey(const CKeyPool& keypool) -{ - { - LOCK2(cs_main, cs_wallet); - CWalletDB walletdb(strWalletFile); - - int64 nIndex = 1 + *(--setKeyPool.end()); - if (!walletdb.WritePool(nIndex, keypool)) - throw runtime_error("AddReserveKey() : writing added key failed"); - setKeyPool.insert(nIndex); - return nIndex; - } - return -1; -} - -void CWallet::KeepKey(int64 nIndex) +void CWallet::KeepKey(int64_t nIndex) { // Remove from key pool if (fFileBacked) @@ -1499,33 +1846,28 @@ void CWallet::KeepKey(int64 nIndex) CWalletDB walletdb(strWalletFile); walletdb.ErasePool(nIndex); } - printf("keypool keep %"PRI64d"\n", nIndex); + LogPrintf("keypool keep %d\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 %d\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; @@ -1536,9 +1878,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) @@ -1547,7 +1889,154 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } -CPubKey CReserveKey::GetReservedKey() +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() +{ + map<CTxDestination, CAmount> balances; + + { + LOCK(cs_wallet); + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted()) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) + continue; + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) + { + CTxDestination addr; + if (!IsMine(pcoin->vout[i])) + continue; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) + continue; + + CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue; + + if (!balances.count(addr)) + balances[addr] = 0; + balances[addr] += n; + } + } + } + + return balances; +} + +set< set<CTxDestination> > CWallet::GetAddressGroupings() +{ + AssertLockHeld(cs_wallet); // mapWallet + set< set<CTxDestination> > groupings; + set<CTxDestination> grouping; + + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (pcoin->vin.size() > 0) + { + bool any_mine = false; + // group all input addresses with each other + BOOST_FOREACH(CTxIn txin, pcoin->vin) + { + CTxDestination address; + if(!IsMine(txin)) /* If this input isn't mine, ignore it */ + continue; + if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address)) + continue; + grouping.insert(address); + any_mine = true; + } + + // group change with input addresses + if (any_mine) + { + BOOST_FOREACH(CTxOut txout, pcoin->vout) + if (IsChange(txout)) + { + CTxDestination txoutAddr; + if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) + continue; + grouping.insert(txoutAddr); + } + } + if (grouping.size() > 0) + { + groupings.insert(grouping); + grouping.clear(); + } + } + + // group lone addrs by themselves + for (unsigned int i = 0; i < pcoin->vout.size(); i++) + if (IsMine(pcoin->vout[i])) + { + CTxDestination address; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address)) + continue; + grouping.insert(address); + groupings.insert(grouping); + grouping.clear(); + } + } + + set< set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses + map< CTxDestination, set<CTxDestination>* > setmap; // map addresses to the unique group containing it + BOOST_FOREACH(set<CTxDestination> grouping, groupings) + { + // make a set of all the groups hit by this new group + set< set<CTxDestination>* > hits; + map< CTxDestination, set<CTxDestination>* >::iterator it; + BOOST_FOREACH(CTxDestination address, grouping) + if ((it = setmap.find(address)) != setmap.end()) + hits.insert((*it).second); + + // merge all hit groups into a new single group and delete old groups + set<CTxDestination>* merged = new set<CTxDestination>(grouping); + BOOST_FOREACH(set<CTxDestination>* hit, hits) + { + merged->insert(hit->begin(), hit->end()); + uniqueGroupings.erase(hit); + delete hit; + } + uniqueGroupings.insert(merged); + + // update setmap + BOOST_FOREACH(CTxDestination element, *merged) + setmap[element] = merged; + } + + set< set<CTxDestination> > ret; + BOOST_FOREACH(set<CTxDestination>* uniqueGrouping, uniqueGroupings) + { + ret.insert(*uniqueGrouping); + delete uniqueGrouping; + } + + return ret; +} + +set<CTxDestination> CWallet::GetAccountAddresses(string strAccount) const +{ + LOCK(cs_wallet); + 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) { @@ -1555,14 +2044,13 @@ 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 { + return false; } } assert(vchPubKey.IsValid()); - return vchPubKey; + pubkey = vchPubKey; + return true; } void CReserveKey::KeepKey() @@ -1581,14 +2069,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)) @@ -1611,3 +2099,269 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx) NotifyTransactionChanged(this, hashTx, CT_UPDATED); } } + +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); +} + +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); + vOutpts.push_back(outpt); + } +} + +/** @} */ // end of Actions + +class CAffectedKeysVisitor : public boost::static_visitor<void> { +private: + const CKeyStore &keystore; + std::vector<CKeyID> &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector<CTxDestination> vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + BOOST_FOREACH(const CTxDestination &dest, vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination &none) {} +}; + +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; + BlockMap::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 + CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); + 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->GetBlockTime() - 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; +} + +CKeyPool::CKeyPool() +{ + nTime = GetTime(); +} + +CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn) +{ + nTime = GetTime(); + vchPubKey = vchPubKeyIn; +} + +CWalletKey::CWalletKey(int64_t nExpires) +{ + nTimeCreated = (nExpires ? GetTime() : 0); + nTimeExpires = nExpires; +} + +int CMerkleTx::SetMerkleBranch(const CBlock& block) +{ + AssertLockHeld(cs_main); + CBlock blockTmp; + + // Update the tx's hashBlock + hashBlock = block.GetHash(); + + // Locate the transaction + for (nIndex = 0; nIndex < (int)block.vtx.size(); nIndex++) + if (block.vtx[nIndex] == *(CTransaction*)this) + break; + if (nIndex == (int)block.vtx.size()) + { + vMerkleBranch.clear(); + nIndex = -1; + LogPrintf("ERROR: SetMerkleBranch() : couldn't find tx in block\n"); + return 0; + } + + // Fill in merkle branch + vMerkleBranch = block.GetMerkleBranch(nIndex); + + // Is the tx in a block that's in the main chain + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi == mapBlockIndex.end()) + return 0; + const CBlockIndex* pindex = (*mi).second; + if (!pindex || !chainActive.Contains(pindex)) + return 0; + + return chainActive.Height() - pindex->nHeight + 1; +} + +int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const +{ + if (hashBlock.IsNull() || nIndex == -1) + return 0; + AssertLockHeld(cs_main); + + // Find the block it claims to be in + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi == mapBlockIndex.end()) + return 0; + CBlockIndex* pindex = (*mi).second; + if (!pindex || !chainActive.Contains(pindex)) + return 0; + + // Make sure the merkle branch connects to this block + if (!fMerkleVerified) + { + if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot) + return 0; + fMerkleVerified = true; + } + + pindexRet = pindex; + return chainActive.Height() - pindex->nHeight + 1; +} + +int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +{ + AssertLockHeld(cs_main); + int nResult = GetDepthInMainChainINTERNAL(pindexRet); + if (nResult == 0 && !mempool.exists(GetHash())) + return -1; // Not in chain, not in mempool + + return nResult; +} + +int CMerkleTx::GetBlocksToMaturity() const +{ + if (!IsCoinBase()) + return 0; + return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain()); +} + + +bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectInsaneFee) +{ + CValidationState state; + return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectInsaneFee); +} + |