X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fwallet.cpp;h=d7a70fe5636dc465b536a42a6b4158072063e455;hb=a49927a46d578d142411c52b17c6f11bb19da03d;hp=5f7cddf57e1f5c52602258c088acc32d56f4db84;hpb=0d7b28e52ea5302ce6aa17399a0027e33168c372;p=novacoin.git diff --git a/src/wallet.cpp b/src/wallet.cpp index 5f7cddf..d7a70fe 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,11 +1,12 @@ -// Copyright (c) 2009-2011 Satoshi Nakamoto & Bitcoin developers +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying -// file license.txt or http://www.opensource.org/licenses/mit-license.php. +// file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "headers.h" -#include "db.h" -#include "cryptopp/sha.h" +#include "wallet.h" +#include "walletdb.h" #include "crypter.h" +#include "ui_interface.h" using namespace std; @@ -15,6 +16,23 @@ using namespace std; // mapWallet // +std::vector CWallet::GenerateNewKey() +{ + bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets + + RandAddSeedPerfmon(); + CKey key; + key.MakeNewKey(fCompressed); + + // Compressed public keys were introduced in version 0.6.0 + if (fCompressed) + SetMinVersion(FEATURE_COMPRPUBKEY); + + if (!AddKey(key)) + throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed"); + return key.GetPubKey(); +} + bool CWallet::AddKey(const CKey& key) { if (!CCryptoKeyStore::AddKey(key)) @@ -32,25 +50,35 @@ bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector return false; if (!fFileBacked) return true; - CRITICAL_BLOCK(cs_pwalletdbEncryption) { + LOCK(cs_wallet); if (pwalletdbEncryption) return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret); else return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret); } + return false; } -bool CWallet::Unlock(const string& strWalletPassphrase) +bool CWallet::AddCScript(const CScript& redeemScript) { - CRITICAL_BLOCK(cs_vMasterKey) - { - if (!IsLocked()) - return false; + if (!CCryptoKeyStore::AddCScript(redeemScript)) + return false; + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); +} - CCrypter crypter; - CKeyingMaterial vMasterKey; +bool CWallet::Unlock(const SecureString& strWalletPassphrase) +{ + if (!IsLocked()) + return false; + CCrypter crypter; + CKeyingMaterial vMasterKey; + + { + LOCK(cs_wallet); BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) { if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) @@ -64,12 +92,12 @@ bool CWallet::Unlock(const string& strWalletPassphrase) return false; } -bool CWallet::ChangeWalletPassphrase(const string& strOldWalletPassphrase, const string& strNewWalletPassphrase) +bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) { - CRITICAL_BLOCK(cs_vMasterKey) - { - bool fWasLocked = IsLocked(); + bool fWasLocked = IsLocked(); + { + LOCK(cs_wallet); Lock(); CCrypter crypter; @@ -78,7 +106,7 @@ bool CWallet::ChangeWalletPassphrase(const string& strOldWalletPassphrase, const { if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) return false; - if(!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) return false; if (CCryptoKeyStore::Unlock(vMasterKey)) { @@ -106,9 +134,15 @@ bool CWallet::ChangeWalletPassphrase(const string& strOldWalletPassphrase, const } } } + return false; } +void CWallet::SetBestChain(const CBlockLocator& loc) +{ + CWalletDB walletdb(strWalletFile); + 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. @@ -122,52 +156,94 @@ public: ) }; -bool CWallet::EncryptWallet(const string& strWalletPassphrase) +bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit) { - CRITICAL_BLOCK(cs_mapPubKeys) - CRITICAL_BLOCK(cs_KeyStore) - CRITICAL_BLOCK(cs_vMasterKey) - CRITICAL_BLOCK(cs_pwalletdbEncryption) + if (nWalletVersion >= nVersion) + return true; + + // when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way + if (fExplicit && nVersion > nWalletMaxVersion) + nVersion = FEATURE_LATEST; + + nWalletVersion = nVersion; + + if (nVersion > nWalletMaxVersion) + nWalletMaxVersion = nVersion; + + if (fFileBacked) { - if (IsCrypted()) - return false; + 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) + delete pwalletdb; + } - CKeyingMaterial vMasterKey; - RandAddSeedPerfmon(); + return true; +} - vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); - RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); +bool CWallet::SetMaxVersion(int nVersion) +{ + // cannot downgrade below current version + if (nWalletVersion > nVersion) + return false; - CMasterKey kMasterKey; + nWalletMaxVersion = nVersion; - RandAddSeedPerfmon(); - kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); - RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + return true; +} - CCrypter crypter; - int64 nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); - kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); +bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) +{ + if (IsCrypted()) + return false; - nStartTime = GetTimeMillis(); - crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); - kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + CKeyingMaterial vMasterKey; + RandAddSeedPerfmon(); - if (kMasterKey.nDeriveIterations < 25000) - kMasterKey.nDeriveIterations = 25000; + vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); + RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); - printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + CMasterKey kMasterKey; - if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) - return false; - if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) - return false; + RandAddSeedPerfmon(); + kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); + RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); + + CCrypter crypter; + int64 nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); + kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (kMasterKey.nDeriveIterations < 25000) + kMasterKey.nDeriveIterations = 25000; + + printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); + + if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) + return false; + if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) + return false; + { + LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; if (fFileBacked) { pwalletdbEncryption = new CWalletDB(strWalletFile); - pwalletdbEncryption->TxnBegin(); + if (!pwalletdbEncryption->TxnBegin()) + return false; pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); } @@ -178,19 +254,28 @@ bool CWallet::EncryptWallet(const string& strWalletPassphrase) 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. } + // Encryption was introduced in version 0.4.0 + SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); + if (fFileBacked) { - CCorruptAddress corruptAddress; - pwalletdbEncryption->WriteSetting("addrIncoming", corruptAddress); 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. - pwalletdbEncryption->Close(); + delete pwalletdbEncryption; pwalletdbEncryption = NULL; } Lock(); + Unlock(strWalletPassphrase); + NewKeyPool(); + Lock(); + + // Need to completely rewrite the wallet file; if we don't, bdb might keep + // bits of the unencrypted private key in slack space in the database file. + CDB::Rewrite(strWalletFile); } + return true; } @@ -199,8 +284,8 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) // 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. - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); BOOST_FOREACH(const CTxIn& txin, tx.vin) { map::iterator mi = mapWallet.find(txin.prevout.hash); @@ -219,15 +304,24 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) } } +void CWallet::MarkDirty() +{ + { + LOCK(cs_wallet); + BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) + item.second.MarkDirty(); + } +} + bool CWallet::AddToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); // Inserts only if not already there, returns tx inserted or tx found pair::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn)); CWalletTx& wtx = (*ret.first).second; - wtx.pwallet = this; + wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) wtx.nTimeReceived = GetAdjustedTime(); @@ -262,7 +356,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) 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.SetBitcoinAddress(vchDefaultKey); @@ -270,11 +364,15 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) { if (txout.scriptPubKey == scriptDefaultKey) { - SetDefaultKey(GetOrReuseKeyFromPool()); - SetAddressBookName(PubKeyToAddress(vchDefaultKey), ""); + std::vector newDefaultKey; + if (GetKeyFromPool(newDefaultKey, false)) + { + SetDefaultKey(newDefaultKey); + SetAddressBookName(CBitcoinAddress(vchDefaultKey), ""); + } } } - +#endif // Notify UI vWalletUpdated.push_back(hash); @@ -287,21 +385,27 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) return true; } -bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate) +// 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) { uint256 hash = tx.GetHash(); - bool fExisted = mapWallet.count(hash); - 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); - return AddToWallet(wtx); + LOCK(cs_wallet); + bool fExisted = mapWallet.count(hash); + 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); + return AddToWallet(wtx); + } + else + WalletUpdateSpent(tx); } - else - WalletUpdateSpent(tx); return false; } @@ -309,8 +413,8 @@ bool CWallet::EraseFromWallet(uint256 hash) { if (!fFileBacked) return false; - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); if (mapWallet.erase(hash)) CWalletDB(strWalletFile).EraseTx(hash); } @@ -320,8 +424,8 @@ bool CWallet::EraseFromWallet(uint256 hash) bool CWallet::IsMine(const CTxIn &txin) const { - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); map::const_iterator mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) { @@ -336,8 +440,8 @@ bool CWallet::IsMine(const CTxIn &txin) const int64 CWallet::GetDebit(const CTxIn &txin) const { - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); map::const_iterator mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) { @@ -350,21 +454,28 @@ int64 CWallet::GetDebit(const CTxIn &txin) const return 0; } -int64 CWalletTx::GetTxTime() const +bool CWallet::IsChange(const CTxOut& txout) const { - if (!fTimeReceivedIsTxTime && hashBlock != 0) + CBitcoinAddress 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 + // 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 (ExtractAddress(txout.scriptPubKey, address) && HaveKey(address)) { - // If we did not receive the transaction directly, we rely on the block's - // time to figure out when it happened. We use the median over a range - // of blocks to try to filter out inaccurate block times. - map::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end()) - { - CBlockIndex* pindex = (*mi).second; - if (pindex) - return pindex->GetMedianTime(); - } + LOCK(cs_wallet); + if (!mapAddressBook.count(address)) + return true; } + return false; +} + +int64 CWalletTx::GetTxTime() const +{ return nTimeReceived; } @@ -372,8 +483,8 @@ int CWalletTx::GetRequestCount() const { // Returns -1 if it wasn't being tracked int nRequests = -1; - CRITICAL_BLOCK(pwallet->cs_mapRequestCount) { + LOCK(pwallet->cs_wallet); if (IsCoinBase()) { // Generated block @@ -407,8 +518,8 @@ int CWalletTx::GetRequestCount() const return nRequests; } -void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list >& listReceived, - list >& listSent, int64& nFee, string& strSentAccount) const +void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list >& listReceived, + list >& listSent, int64& nFee, string& strSentAccount) const { nGeneratedImmature = nGeneratedMature = nFee = 0; listReceived.clear(); @@ -432,18 +543,12 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l nFee = nDebit - nValueOut; } - // Sent/received. Standard client will never generate a send-to-multiple-recipients, - // but non-standard clients might (so return a list of address/amount pairs) + // Sent/received. BOOST_FOREACH(const CTxOut& txout, vout) { - string address; - uint160 hash160; + CBitcoinAddress address; vector vchPubKey; - if (ExtractHash160(txout.scriptPubKey, hash160)) - address = Hash160ToAddress(hash160); - else if (ExtractPubKey(txout.scriptPubKey, NULL, vchPubKey)) - address = PubKeyToAddress(vchPubKey); - else + if (!ExtractAddress(txout.scriptPubKey, address)) { printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString().c_str()); @@ -471,25 +576,25 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, i int64 allGeneratedImmature, allGeneratedMature, allFee; allGeneratedImmature = allGeneratedMature = allFee = 0; string strSentAccount; - list > listReceived; - list > listSent; + list > listReceived; + list > listSent; GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); if (strAccount == "") nGenerated = allGeneratedMature; if (strAccount == strSentAccount) { - BOOST_FOREACH(const PAIRTYPE(string,int64)& s, listSent) + BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& s, listSent) nSent += s.second; nFee = allFee; } - CRITICAL_BLOCK(pwallet->cs_mapAddressBook) { - BOOST_FOREACH(const PAIRTYPE(string,int64)& r, listReceived) + LOCK(pwallet->cs_wallet); + BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived) { if (pwallet->mapAddressBook.count(r.first)) { - map::const_iterator mi = pwallet->mapAddressBook.find(r.first); + map::const_iterator mi = pwallet->mapAddressBook.find(r.first); if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) nReceived += r.second; } @@ -513,11 +618,11 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) vWorkQueue.push_back(txin.prevout.hash); // This critsect is OK because txdb is already open - CRITICAL_BLOCK(pwallet->cs_mapWallet) { + LOCK(pwallet->cs_wallet); map mapWalletPrev; set setAlreadyDone; - for (int i = 0; i < vWorkQueue.size(); i++) + for (unsigned int i = 0; i < vWorkQueue.size(); i++) { uint256 hash = vWorkQueue[i]; if (setAlreadyDone.count(hash)) @@ -550,8 +655,10 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) vtxPrev.push_back(tx); if (nDepth < COPY_DEPTH) + { BOOST_FOREACH(const CTxIn& txin, tx.vin) vWorkQueue.push_back(txin.prevout.hash); + } } } } @@ -564,13 +671,16 @@ 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. int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) { int ret = 0; CBlockIndex* pindex = pindexStart; - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); while (pindex) { CBlock block; @@ -586,12 +696,22 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) 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) CRITICAL_BLOCK(cs_mapWallet) + while (fRepeat) { + LOCK(cs_wallet); fRepeat = false; vector vMissingTx; BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) @@ -610,7 +730,7 @@ void CWallet::ReacceptWalletTransactions() printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %d != wtx.vout.size() %d\n", txindex.vSpent.size(), wtx.vout.size()); continue; } - for (int i = 0; i < txindex.vSpent.size(); i++) + for (unsigned int i = 0; i < txindex.vSpent.size(); i++) { if (wtx.IsSpent(i)) continue; @@ -693,8 +813,8 @@ void CWallet::ResendWalletTransactions() // Rebroadcast any of our txes that aren't in a block yet printf("ResendWalletTransactions()\n"); CTxDB txdb("r"); - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); // Sort them in chronological order multimap mapSorted; BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) @@ -727,8 +847,8 @@ void CWallet::ResendWalletTransactions() int64 CWallet::GetBalance() const { int64 nTotal = 0; - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; @@ -741,6 +861,21 @@ int64 CWallet::GetBalance() const return nTotal; } +int64 CWallet::GetUnconfirmedBalance() const +{ + int64 nTotal = 0; + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsFinal() && pcoin->IsConfirmed()) + continue; + nTotal += pcoin->GetAvailableCredit(); + } + } + return nTotal; +} bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) const { @@ -749,13 +884,13 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe // List of values less than target pair > coinLowestLarger; - coinLowestLarger.first = INT64_MAX; + coinLowestLarger.first = std::numeric_limits::max(); coinLowestLarger.second.first = NULL; vector > > vValue; int64 nTotalLower = 0; - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); vector vCoins; vCoins.reserve(mapWallet.size()); for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) @@ -774,7 +909,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe if (nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs)) continue; - for (int i = 0; i < pcoin->vout.size(); i++) + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { if (pcoin->IsSpent(i) || !IsMine(pcoin->vout[i])) continue; @@ -807,7 +942,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe if (nTotalLower == nTargetValue || nTotalLower == nTargetValue + CENT) { - for (int i = 0; i < vValue.size(); ++i) + for (unsigned int i = 0; i < vValue.size(); ++i) { setCoinsRet.insert(vValue[i].second); nValueRet += vValue[i].first; @@ -840,7 +975,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe bool fReachedTarget = false; for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) { - for (int i = 0; i < vValue.size(); i++) + for (unsigned int i = 0; i < vValue.size(); i++) { if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) { @@ -869,7 +1004,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe nValueRet += coinLowestLarger.first; } else { - for (int i = 0; i < vValue.size(); i++) + for (unsigned int i = 0; i < vValue.size(); i++) if (vfBest[i]) { setCoinsRet.insert(vValue[i].second); @@ -878,7 +1013,7 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfThe //// debug print printf("SelectCoins() best subset: "); - for (int i = 0; i < vValue.size(); i++) + 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()); @@ -909,13 +1044,12 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW if (vecSend.empty() || nValue < 0) return false; - wtxNew.pwallet = this; + wtxNew.BindWallet(this); - CRITICAL_BLOCK(cs_main) { + LOCK2(cs_main, cs_wallet); // txdb must be opened before the mapWallet lock CTxDB txdb("r"); - CRITICAL_BLOCK(cs_mapWallet) { nFeeRet = nTransactionFee; loop @@ -941,9 +1075,18 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain(); } - // Fill a vout back to self with any change - int64 nChange = nValueIn - nTotalValue; - if (nChange >= CENT) + 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; + } + + 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 @@ -956,12 +1099,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW vector vchPubKey = reservekey.GetReservedKey(); // assert(mapKeys.count(vchPubKey)); - // Fill a vout to ourself, using same address type as the payment + // Fill a vout to ourself + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - if (vecSend[0].first.GetBitcoinAddressHash160() != 0) - scriptChange.SetBitcoinAddress(vchPubKey); - else - scriptChange << vchPubKey << OP_CHECKSIG; + scriptChange.SetBitcoinAddress(vchPubKey); // Insert change txn at random position: vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()); @@ -981,7 +1123,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW return false; // Limit size - unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK); + unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= MAX_BLOCK_SIZE_GEN/5) return false; dPriority /= nBytes; @@ -989,7 +1131,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); bool fAllowFree = CTransaction::AllowFree(dPriority); - int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree); + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -1017,10 +1159,9 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& w // Call after CreateTransaction unless you want to abort bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { - CRITICAL_BLOCK(cs_main) { + LOCK2(cs_main, cs_wallet); printf("CommitTransaction:\n%s", wtxNew.ToString().c_str()); - CRITICAL_BLOCK(cs_mapWallet) { // 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 @@ -1039,7 +1180,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) BOOST_FOREACH(const CTxIn& txin, wtxNew.vin) { CWalletTx &coin = mapWallet[txin.prevout.hash]; - coin.pwallet = this; + coin.BindWallet(this); coin.MarkSpent(txin.prevout.n); coin.WriteToDisk(); vWalletUpdated.push_back(coin.GetHash()); @@ -1050,8 +1191,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) } // Track how many getdata requests our transaction gets - CRITICAL_BLOCK(cs_mapRequestCount) - mapRequestCount[wtxNew.GetHash()] = 0; + mapRequestCount[wtxNew.GetHash()] = 0; // Broadcast if (!wtxNew.AcceptToMemoryPool()) @@ -1069,32 +1209,29 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) -// requires cs_main lock string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) { CReserveKey reservekey(this); int64 nFeeRequired; - CRITICAL_BLOCK(cs_vMasterKey) + + if (IsLocked()) { - 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; - } + 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 && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL)) + if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."))) return "ABORTED"; if (!CommitTransaction(wtxNew, reservekey)) @@ -1106,8 +1243,7 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, -// requires cs_main lock -string CWallet::SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoneyToBitcoinAddress(const CBitcoinAddress& address, int64 nValue, CWalletTx& wtxNew, bool fAskFee) { // Check amount if (nValue <= 0) @@ -1117,8 +1253,7 @@ string CWallet::SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWall // Parse bitcoin address CScript scriptPubKey; - if (!scriptPubKey.SetBitcoinAddress(strAddress)) - return _("Invalid bitcoin address"); + scriptPubKey.SetBitcoinAddress(address); return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee); } @@ -1132,46 +1267,50 @@ int CWallet::LoadWallet(bool& fFirstRunRet) return false; fFirstRunRet = false; int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); + if (nLoadWalletRet == DB_NEED_REWRITE) + { + if (CDB::Rewrite(strWalletFile, "\x04pool")) + { + 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.empty(); - if (!HaveKey(vchDefaultKey)) - { - // Create new keyUser and set as default key - RandAddSeedPerfmon(); - - SetDefaultKey(GetOrReuseKeyFromPool()); - if (!SetAddressBookName(PubKeyToAddress(vchDefaultKey), "")) - return DB_LOAD_FAIL; - } - CreateThread(ThreadFlushWalletDB, &strWalletFile); return DB_LOAD_OK; } -bool CWallet::SetAddressBookName(const string& strAddress, const string& strName) +bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& strName) { - mapAddressBook[strAddress] = strName; + mapAddressBook[address] = strName; + AddressBookRepaint(); if (!fFileBacked) return false; - return CWalletDB(strWalletFile).WriteName(strAddress, strName); + return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); } -bool CWallet::DelAddressBookName(const string& strAddress) +bool CWallet::DelAddressBookName(const CBitcoinAddress& address) { - mapAddressBook.erase(strAddress); + mapAddressBook.erase(address); + AddressBookRepaint(); if (!fFileBacked) return false; - return CWalletDB(strWalletFile).EraseName(strAddress); + return CWalletDB(strWalletFile).EraseName(address.ToString()); } void CWallet::PrintWallet(const CBlock& block) { - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); if (mapWallet.count(block.vtx[0].GetHash())) { CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; @@ -1183,8 +1322,8 @@ void CWallet::PrintWallet(const CBlock& block) bool CWallet::GetTransaction(const uint256 &hashTx, CWalletTx& wtx) { - CRITICAL_BLOCK(cs_mapWallet) { + LOCK(cs_wallet); map::iterator mi = mapWallet.find(hashTx); if (mi != mapWallet.end()) { @@ -1214,21 +1353,47 @@ bool GetWalletFile(CWallet* pwallet, string &strWalletFileOut) return true; } +// +// 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) + walletdb.ErasePool(nIndex); + setKeyPool.clear(); + + if (IsLocked()) + return false; + + int64 nKeys = max(GetArg("-keypool", 100), (int64)0); + for (int i = 0; i < nKeys; i++) + { + int64 nIndex = i+1; + walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey())); + setKeyPool.insert(nIndex); + } + printf("CWallet::NewKeyPool wrote %"PRI64d" new keys\n", nKeys); + } + return true; +} + bool CWallet::TopUpKeyPool() { - CRITICAL_BLOCK(cs_main) - CRITICAL_BLOCK(cs_mapWallet) - CRITICAL_BLOCK(cs_setKeyPool) - CRITICAL_BLOCK(cs_vMasterKey) { + LOCK(cs_wallet); + if (IsLocked()) return false; CWalletDB walletdb(strWalletFile); // Top up key pool - int64 nTargetSize = max(GetArg("-keypool", 100), (int64)0); - while (setKeyPool.size() < nTargetSize+1) + unsigned int nTargetSize = max(GetArg("-keypool", 100), 0LL); + while (setKeyPool.size() < (nTargetSize + 1)) { int64 nEnd = 1; if (!setKeyPool.empty()) @@ -1246,10 +1411,9 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) { nIndex = -1; keypool.vchPubKey.clear(); - CRITICAL_BLOCK(cs_main) - CRITICAL_BLOCK(cs_mapWallet) - CRITICAL_BLOCK(cs_setKeyPool) { + LOCK(cs_wallet); + if (!IsLocked()) TopUpKeyPool(); @@ -1263,23 +1427,35 @@ void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) setKeyPool.erase(setKeyPool.begin()); if (!walletdb.ReadPool(nIndex, keypool)) throw runtime_error("ReserveKeyFromKeyPool() : read failed"); - if (!HaveKey(keypool.vchPubKey)) + if (!HaveKey(Hash160(keypool.vchPubKey))) throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); assert(!keypool.vchPubKey.empty()); printf("keypool reserve %"PRI64d"\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) { // Remove from key pool if (fFileBacked) { CWalletDB walletdb(strWalletFile); - CRITICAL_BLOCK(cs_main) - { - walletdb.ErasePool(nIndex); - } + walletdb.ErasePool(nIndex); } printf("keypool keep %"PRI64d"\n", nIndex); } @@ -1287,20 +1463,35 @@ void CWallet::KeepKey(int64 nIndex) void CWallet::ReturnKey(int64 nIndex) { // Return to key pool - CRITICAL_BLOCK(cs_setKeyPool) + { + LOCK(cs_wallet); setKeyPool.insert(nIndex); + } printf("keypool return %"PRI64d"\n", nIndex); } -vector CWallet::GetOrReuseKeyFromPool() +bool CWallet::GetKeyFromPool(vector& result, bool fAllowReuse) { int64 nIndex = 0; CKeyPool keypool; - ReserveKeyFromKeyPool(nIndex, keypool); - if(nIndex == -1) - return vchDefaultKey; - KeepKey(nIndex); - return keypool.vchPubKey; + { + LOCK(cs_wallet); + ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex == -1) + { + if (fAllowReuse && !vchDefaultKey.empty()) + { + result = vchDefaultKey; + return true; + } + if (IsLocked()) return false; + result = GenerateNewKey(); + return true; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; } int64 CWallet::GetOldestKeyPoolTime() @@ -1323,7 +1514,10 @@ vector CReserveKey::GetReservedKey() if (nIndex != -1) vchPubKey = keypool.vchPubKey; else - vchPubKey = vchDefaultKey; + { + printf("CReserveKey::GetReservedKey(): Warning: using default key instead of a new key, top up your keypool."); + vchPubKey = pwallet->vchDefaultKey; + } } assert(!vchPubKey.empty()); return vchPubKey; @@ -1345,3 +1539,22 @@ void CReserveKey::ReturnKey() vchPubKey.clear(); } +void CWallet::GetAllReserveAddresses(set& setAddress) +{ + setAddress.clear(); + + CWalletDB walletdb(strWalletFile); + + LOCK2(cs_main, cs_wallet); + BOOST_FOREACH(const int64& id, setKeyPool) + { + CKeyPool keypool; + if (!walletdb.ReadPool(id, keypool)) + throw runtime_error("GetAllReserveKeyHashes() : read failed"); + CBitcoinAddress address(keypool.vchPubKey); + assert(!keypool.vchPubKey.empty()); + if (!HaveKey(address)) + throw runtime_error("GetAllReserveKeyHashes() : unknown key in key pool"); + setAddress.insert(address); + } +}