X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fwallet.cpp;h=12fb9fdcc7754cc62a829dfdcbc7813ee0c7d052;hb=09f2a990950512cbe418526d91838887a0dded2a;hp=a9fc92fd78cef2aa1190866368d2b758d1cecf99;hpb=e89b9f6a2abaa120ff0fc3cea9ae364e8cbd25e4;p=novacoin.git diff --git a/src/wallet.cpp b/src/wallet.cpp index a9fc92f..12fb9fd 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,50 +1,244 @@ -// Copyright (c) 2009-2011 Satoshi Nakamoto & Bitcoin developers +// Copyright (c) 2009-2011 Satoshi Nakamoto +// Copyright (c) 2011 The Bitcoin developers +// Copyright (c) 2011-2012 The PPCoin developers // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. #include "headers.h" #include "db.h" -#include "cryptopp/sha.h" +#include "crypter.h" +#include "checkpoints.h" using namespace std; - ////////////////////////////////////////////////////////////////////////////// // // mapWallet // -void WalletUpdateSpent(const COutPoint& prevout) +bool CWallet::AddKey(const CKey& key) +{ + if (!CCryptoKeyStore::AddKey(key)) + return false; + if (!fFileBacked) + return true; + if (!IsCrypted()) + return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); + return true; +} + +bool CWallet::AddCryptedKey(const vector &vchPubKey, const vector &vchCryptedSecret) +{ + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + if (!fFileBacked) + return true; + CRITICAL_BLOCK(cs_wallet) + { + if (pwalletdbEncryption) + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret); + else + return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret); + } + return false; +} + +// ppcoin: optional setting to create coinstake only when unlocked; +// serves to disable the trivial sendmoney when OS account compromised +bool fWalletUnlockStakeOnly = false; + +bool CWallet::Unlock(const SecureString& strWalletPassphrase) +{ + if (!IsLocked()) + return false; + + CCrypter crypter; + CKeyingMaterial vMasterKey; + + CRITICAL_BLOCK(cs_wallet) + BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(vMasterKey)) + return true; + } + return false; +} + +bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) +{ + bool fWasLocked = IsLocked(); + + CRITICAL_BLOCK(cs_wallet) + { + Lock(); + + CCrypter crypter; + CKeyingMaterial vMasterKey; + BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys) + { + if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) + return false; + if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) + return false; + if (CCryptoKeyStore::Unlock(vMasterKey)) + { + int64 nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); + + nStartTime = GetTimeMillis(); + crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); + pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; + + if (pMasterKey.second.nDeriveIterations < 25000) + pMasterKey.second.nDeriveIterations = 25000; + + printf("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; + if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) + return false; + CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second); + if (fWasLocked) + Lock(); + return true; + } + } + } + + return false; +} + + +// 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::EncryptWallet(const SecureString& strWalletPassphrase) +{ + if (IsCrypted()) + return false; + + CKeyingMaterial vMasterKey; + RandAddSeedPerfmon(); + + vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); + RAND_bytes(&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); + + 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; + + CRITICAL_BLOCK(cs_wallet) + { + mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; + if (fFileBacked) + { + pwalletdbEncryption = new CWalletDB(strWalletFile); + pwalletdbEncryption->TxnBegin(); + pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); + } + + if (!EncryptKeys(vMasterKey)) + { + 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. + } + + 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(); + 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; +} + +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) + CRITICAL_BLOCK(cs_wallet) { - map::iterator mi = mapWallet.find(prevout.hash); - if (mi != mapWallet.end()) + BOOST_FOREACH(const CTxIn& txin, tx.vin) { - CWalletTx& wtx = (*mi).second; - if (!wtx.IsSpent(prevout.n) && wtx.vout[prevout.n].IsMine()) + map::iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) { - printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); - wtx.MarkSpent(prevout.n); - wtx.WriteToDisk(); - vWalletUpdated.push_back(prevout.hash); + CWalletTx& wtx = (*mi).second; + if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n])) + { + printf("WalletUpdateSpent found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + wtx.MarkSpent(txin.prevout.n); + wtx.WriteToDisk(); + vWalletUpdated.push_back(txin.prevout.hash); + } } } } } -bool AddToWallet(const CWalletTx& wtxIn) +bool CWallet::AddToWallet(const CWalletTx& wtxIn) { uint256 hash = wtxIn.GetHash(); - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(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; bool fInsertedNew = ret.second; if (fInsertedNew) wtx.nTimeReceived = GetAdjustedTime(); @@ -79,7 +273,7 @@ bool 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); @@ -87,15 +281,20 @@ bool AddToWallet(const CWalletTx& wtxIn) { if (txout.scriptPubKey == scriptDefaultKey) { - CWalletDB walletdb; - vchDefaultKey = GetKeyFromKeyPool(); - walletdb.WriteDefaultKey(vchDefaultKey); - walletdb.WriteName(PubKeyToAddress(vchDefaultKey), ""); + std::vector newDefaultKey; + if (GetKeyFromPool(newDefaultKey, false)) + { + SetDefaultKey(newDefaultKey); + SetAddressBookName(CBitcoinAddress(vchDefaultKey), ""); + } } } - +#endif // Notify UI vWalletUpdated.push_back(hash); + + // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins + WalletUpdateSpent(wtx); } // Refresh UI @@ -103,60 +302,70 @@ bool AddToWallet(const CWalletTx& wtxIn) return true; } -bool 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) { uint256 hash = tx.GetHash(); - bool fExisted = mapWallet.count(hash); - if (fExisted && !fUpdate) return false; - if (fExisted || tx.IsMine() || tx.IsFromMe()) + CRITICAL_BLOCK(cs_wallet) { - CWalletTx wtx(tx); - // Get merkle branch if transaction was found in a block - if (pblock) - wtx.SetMerkleBranch(pblock); - return AddToWallet(wtx); + 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); } return false; } -bool EraseFromWallet(uint256 hash) +bool CWallet::EraseFromWallet(uint256 hash) { - CRITICAL_BLOCK(cs_mapWallet) + if (!fFileBacked) + return false; + CRITICAL_BLOCK(cs_wallet) { if (mapWallet.erase(hash)) - CWalletDB().EraseTx(hash); + CWalletDB(strWalletFile).EraseTx(hash); } return true; } -bool CTxIn::IsMine() const +bool CWallet::IsMine(const CTxIn &txin) const { - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_wallet) { - map::iterator mi = mapWallet.find(prevout.hash); + map::const_iterator mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; - if (prevout.n < prev.vout.size()) - if (prev.vout[prevout.n].IsMine()) + if (txin.prevout.n < prev.vout.size()) + if (IsMine(prev.vout[txin.prevout.n])) return true; } } return false; } -int64 CTxIn::GetDebit() const +int64 CWallet::GetDebit(const CTxIn &txin) const { - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_wallet) { - map::iterator mi = mapWallet.find(prevout.hash); + map::const_iterator mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; - if (prevout.n < prev.vout.size()) - if (prev.vout[prevout.n].IsMine()) - return prev.vout[prevout.n].nValue; + if (txin.prevout.n < prev.vout.size()) + if (IsMine(prev.vout[txin.prevout.n])) + return prev.vout[txin.prevout.n].nValue; } } return 0; @@ -164,19 +373,6 @@ int64 CTxIn::GetDebit() const int64 CWalletTx::GetTxTime() const { - if (!fTimeReceivedIsTxTime && hashBlock != 0) - { - // 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(); - } - } return nTimeReceived; } @@ -184,31 +380,31 @@ int CWalletTx::GetRequestCount() const { // Returns -1 if it wasn't being tracked int nRequests = -1; - CRITICAL_BLOCK(cs_mapRequestCount) + CRITICAL_BLOCK(pwallet->cs_wallet) { - if (IsCoinBase()) + if (IsCoinBase() || IsCoinStake()) { // Generated block if (hashBlock != 0) { - map::iterator mi = mapRequestCount.find(hashBlock); - if (mi != mapRequestCount.end()) + map::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); + if (mi != pwallet->mapRequestCount.end()) nRequests = (*mi).second; } } else { // Did anyone request this transaction? - map::iterator mi = mapRequestCount.find(GetHash()); - if (mi != mapRequestCount.end()) + map::const_iterator mi = pwallet->mapRequestCount.find(GetHash()); + if (mi != pwallet->mapRequestCount.end()) { nRequests = (*mi).second; // How about the block it's in? if (nRequests == 0 && hashBlock != 0) { - map::iterator mi = mapRequestCount.find(hashBlock); - if (mi != mapRequestCount.end()) + map::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); + if (mi != pwallet->mapRequestCount.end()) nRequests = (*mi).second; else nRequests = 1; // If it's in someone else's block it must have got out @@ -219,18 +415,18 @@ 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(); listSent.clear(); strSentAccount = strFromAccount; - if (IsCoinBase()) + if (IsCoinBase() || IsCoinStake()) { if (GetBlocksToMaturity() > 0) - nGeneratedImmature = CTransaction::GetCredit(); + nGeneratedImmature = pwallet->GetCredit(*this); else nGeneratedMature = GetCredit(); return; @@ -248,14 +444,9 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l // but non-standard clients might (so return a list of address/amount pairs) 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, false, vchPubKey)) - address = PubKeyToAddress(vchPubKey); - else + if (!ExtractAddress(txout.scriptPubKey, NULL, address)) { printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString().c_str()); @@ -263,13 +454,13 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l } // Don't report 'change' txouts - if (nDebit > 0 && txout.IsChange()) + if (nDebit > 0 && pwallet->IsChange(txout)) continue; if (nDebit > 0) listSent.push_back(make_pair(address, txout.nValue)); - if (txout.IsMine()) + if (pwallet->IsMine(txout)) listReceived.push_back(make_pair(address, txout.nValue)); } @@ -283,28 +474,27 @@ 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(cs_mapAddressBook) + CRITICAL_BLOCK(pwallet->cs_wallet) { - BOOST_FOREACH(const PAIRTYPE(string,int64)& r, listReceived) + BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress,int64)& r, listReceived) { - if (mapAddressBook.count(r.first)) + if (pwallet->mapAddressBook.count(r.first)) { - if (mapAddressBook[r.first] == strAccount) - { + map::const_iterator mi = pwallet->mapAddressBook.find(r.first); + if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount) nReceived += r.second; - } } else if (strAccount.empty()) { @@ -326,7 +516,7 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) vWorkQueue.push_back(txin.prevout.hash); // This critsect is OK because txdb is already open - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(pwallet->cs_wallet) { map mapWalletPrev; set setAlreadyDone; @@ -338,10 +528,11 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) setAlreadyDone.insert(hash); CMerkleTx tx; - if (mapWallet.count(hash)) + map::const_iterator mi = pwallet->mapWallet.find(hash); + if (mi != pwallet->mapWallet.end()) { - tx = mapWallet[hash]; - BOOST_FOREACH(const CMerkleTx& txWalletPrev, mapWallet[hash].vtxPrev) + tx = (*mi).second; + BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev) mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev; } else if (mapWalletPrev.count(hash)) @@ -373,15 +564,18 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) bool CWalletTx::WriteToDisk() { - return CWalletDB().WriteTx(GetHash(), *this); + return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this); } -int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) +// 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) + CRITICAL_BLOCK(cs_wallet) { while (pindex) { @@ -398,18 +592,18 @@ int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) return ret; } -void ReacceptWalletTransactions() +void CWallet::ReacceptWalletTransactions() { CTxDB txdb("r"); bool fRepeat = true; - while (fRepeat) CRITICAL_BLOCK(cs_mapWallet) + while (fRepeat) CRITICAL_BLOCK(cs_wallet) { fRepeat = false; vector vMissingTx; BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { CWalletTx& wtx = item.second; - if (wtx.IsCoinBase() && wtx.IsSpent(0)) + if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1))) continue; CTxIndex txindex; @@ -426,7 +620,7 @@ void ReacceptWalletTransactions() { if (wtx.IsSpent(i)) continue; - if (!txindex.vSpent[i].IsNull() && wtx.vout[i].IsMine()) + if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i])) { wtx.MarkSpent(i); fUpdated = true; @@ -435,7 +629,7 @@ void ReacceptWalletTransactions() } if (fUpdated) { - printf("ReacceptWalletTransactions found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); + printf("ReacceptWalletTransactions found spent coin %sppc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkDirty(); wtx.WriteToDisk(); } @@ -443,7 +637,7 @@ void ReacceptWalletTransactions() else { // Reaccept any txes of ours that aren't already in a block - if (!wtx.IsCoinBase()) + if (!(wtx.IsCoinBase() || wtx.IsCoinStake())) wtx.AcceptWalletTransaction(txdb, false); } } @@ -460,14 +654,14 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb) { BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) { - if (!tx.IsCoinBase()) + if (!(tx.IsCoinBase() || tx.IsCoinStake())) { uint256 hash = tx.GetHash(); if (!txdb.ContainsTx(hash)) RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); } } - if (!IsCoinBase()) + if (!(IsCoinBase() || IsCoinStake())) { uint256 hash = GetHash(); if (!txdb.ContainsTx(hash)) @@ -484,7 +678,7 @@ void CWalletTx::RelayWalletTransaction() RelayWalletTransaction(txdb); } -void ResendWalletTransactions() +void CWallet::ResendWalletTransactions() { // Do this infrequently and randomly to avoid giving away // that these are our transactions. @@ -505,7 +699,7 @@ void ResendWalletTransactions() // Rebroadcast any of our txes that aren't in a block yet printf("ResendWalletTransactions()\n"); CTxDB txdb("r"); - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_wallet) { // Sort them in chronological order multimap mapSorted; @@ -536,53 +730,97 @@ void ResendWalletTransactions() // -int64 GetBalance() +int64 CWallet::GetBalance() const { - int64 nStart = GetTimeMillis(); - int64 nTotal = 0; - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_wallet) { - for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { - CWalletTx* pcoin = &(*it).second; + const CWalletTx* pcoin = &(*it).second; if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) continue; nTotal += pcoin->GetAvailableCredit(); } } - //printf("GetBalance() %"PRI64d"ms\n", GetTimeMillis() - nStart); return nTotal; } +int64 CWallet::GetUnconfirmedBalance() const +{ + int64 nTotal = 0; + CRITICAL_BLOCK(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; +} + +// ppcoin: total coins staked (non-spendable until maturity) +int64 CWallet::GetStake() const +{ + int64 nTotal = 0; + CRITICAL_BLOCK(cs_wallet) + { + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) + nTotal += CWallet::GetCredit(*pcoin); + } + } + return nTotal; +} + +int64 CWallet::GetNewMint() const +{ + int64 nTotal = 0; + CRITICAL_BLOCK(cs_wallet) + { + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) + nTotal += CWallet::GetCredit(*pcoin); + } + } + return nTotal; +} -bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) + +bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, set >& setCoinsRet, int64& nValueRet) const { setCoinsRet.clear(); nValueRet = 0; // List of values less than target - pair > coinLowestLarger; + pair > coinLowestLarger; coinLowestLarger.first = INT64_MAX; coinLowestLarger.second.first = NULL; - vector > > vValue; + vector > > vValue; int64 nTotalLower = 0; - CRITICAL_BLOCK(cs_mapWallet) + CRITICAL_BLOCK(cs_wallet) { - vector vCoins; + vector vCoins; vCoins.reserve(mapWallet.size()); - for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) vCoins.push_back(&(*it).second); random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); - BOOST_FOREACH(CWalletTx* pcoin, vCoins) + BOOST_FOREACH(const CWalletTx* pcoin, vCoins) { if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) continue; - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + if ((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetBlocksToMaturity() > 0) continue; int nDepth = pcoin->GetDepthInMainChain(); @@ -591,15 +829,18 @@ bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set< for (int i = 0; i < pcoin->vout.size(); i++) { - if (pcoin->IsSpent(i) || !pcoin->vout[i].IsMine()) + if (pcoin->IsSpent(i) || !IsMine(pcoin->vout[i])) continue; + if (pcoin->nTime > nSpendTime) + continue; // ppcoin: timestamp must not exceed spend time + int64 n = pcoin->vout[i].nValue; if (n <= 0) continue; - pair > coin = make_pair(n,make_pair(pcoin,i)); + pair > coin = make_pair(n,make_pair(pcoin,i)); if (n == nTargetValue) { @@ -702,17 +943,17 @@ bool SelectCoinsMinConf(int64 nTargetValue, int nConfMine, int nConfTheirs, set< return true; } -bool SelectCoins(int64 nTargetValue, set >& setCoinsRet, int64& nValueRet) +bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet) const { - return (SelectCoinsMinConf(nTargetValue, 1, 6, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 1, 1, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 0, 1, setCoinsRet, nValueRet)); + return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, setCoinsRet, nValueRet)); } -bool CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) { int64 nValue = 0; BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) @@ -724,11 +965,13 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& if (vecSend.empty() || nValue < 0) return false; + wtxNew.pwallet = this; + CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(cs_wallet) { // txdb must be opened before the mapWallet lock CTxDB txdb("r"); - CRITICAL_BLOCK(cs_mapWallet) { nFeeRet = nTransactionFee; loop @@ -744,19 +987,27 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew.vout.push_back(CTxOut(s.second, s.first)); // Choose coins to use - set > setCoins; + set > setCoins; int64 nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn)) + if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn)) return false; - BOOST_FOREACH(PAIRTYPE(CWalletTx*, unsigned int) pcoin, setCoins) + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { int64 nCredit = pcoin.first->vout[pcoin.second].nValue; 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 + 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 @@ -767,11 +1018,11 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& // Reserve a new key pair from key pool vector vchPubKey = reservekey.GetReservedKey(); - assert(mapKeys.count(vchPubKey)); + // assert(mapKeys.count(vchPubKey)); // Fill a vout to ourself, using same address type as the payment CScript scriptChange; - if (vecSend[0].first.GetBitcoinAddressHash160() != 0) + if (vecSend[0].first.GetBitcoinAddress().IsValid()) scriptChange.SetBitcoinAddress(vchPubKey); else scriptChange << vchPubKey << OP_CHECKSIG; @@ -784,13 +1035,13 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& reservekey.ReturnKey(); // Fill vin - BOOST_FOREACH(const PAIRTYPE(CWalletTx*,unsigned int)& coin, setCoins) + BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second)); // Sign int nIn = 0; - BOOST_FOREACH(const PAIRTYPE(CWalletTx*,unsigned int)& coin, setCoins) - if (!SignSignature(*coin.first, wtxNew, nIn++)) + BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) + if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) return false; // Limit size @@ -801,8 +1052,7 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& // 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, false); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -820,25 +1070,116 @@ bool CreateTransaction(const vector >& vecSend, CWalletTx& return true; } -bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) +bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet) { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet); } +// ppcoin: create coin stake transaction +bool CWallet::CreateCoinStake(unsigned int nBits, CTransaction& txNew) +{ + CBigNum bnTargetPerCoinDay; + bnTargetPerCoinDay.SetCompact(nBits); + + CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(cs_wallet) + { + txNew.vin.clear(); + txNew.vout.clear(); + // Mark coin stake transaction + CScript scriptEmpty; + scriptEmpty.clear(); + txNew.vout.push_back(CTxOut(0, scriptEmpty)); + // Choose coins to use + int64 nBalance = GetBalance(); + if (nBalance <= nBalanceReserve) + return false; + set > setCoins; + vector vwtxPrev; + int64 nValueIn = 0; + if (!SelectCoins(nBalance - nBalanceReserve, txNew.nTime, setCoins, nValueIn)) + return false; + if (setCoins.empty()) + return false; + int64 nCredit = 0; + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + CTxDB txdb("r"); + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex)) + continue; + + // Read block header + CBlock block; + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + continue; + if (block.GetBlockTime() + STAKE_MIN_AGE > txNew.nTime) + continue; // only count coins meeting min age requirement + + int64 nValueIn = pcoin.first->vout[pcoin.second].nValue; + CBigNum bnCoinDay = CBigNum(nValueIn) * (txNew.nTime-pcoin.first->nTime) / COIN / (24 * 60 * 60); + // Calculate hash + CDataStream ss(SER_GETHASH, VERSION); + ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << pcoin.first->nTime << pcoin.second << txNew.nTime; + if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay) + { + txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + nCredit += pcoin.first->vout[pcoin.second].nValue; + vwtxPrev.push_back(pcoin.first); + // Set output scriptPubKey + txNew.vout.push_back(CTxOut(0, pcoin.first->vout[pcoin.second].scriptPubKey)); + break; + } + } + if (nCredit == 0 || nCredit > nBalance - nBalanceReserve) + return false; + BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + { + if (pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey && pcoin.first->GetHash() != txNew.vin[0].prevout.hash) + { + if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nBalanceReserve) + break; + txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + nCredit += pcoin.first->vout[pcoin.second].nValue; + vwtxPrev.push_back(pcoin.first); + } + } + // Calculate coin age reward + { + uint64 nCoinAge; + CTxDB txdb("r"); + if (!txNew.GetCoinAge(txdb, nCoinAge)) + return error("CreateCoinStake : failed to calculate coin age"); + nCredit += GetProofOfStakeReward(nCoinAge); + } + // Set output amount + txNew.vout[1].nValue = nCredit; + + // Sign + int nIn = 0; + BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev) + { + if (!SignSignature(*this, *pcoin, txNew, nIn++)) + return error("CreateCoinStake : failed to sign coinstake"); + } + } + return true; +} + // Call after CreateTransaction unless you want to abort -bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) +bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { CRITICAL_BLOCK(cs_main) + CRITICAL_BLOCK(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 // maybe makes sense; please don't do it anywhere else. - CWalletDB walletdb("r"); + CWalletDB* pwalletdb = fFileBacked ? new CWalletDB(strWalletFile,"r") : NULL; // Take key pair from key pool so it won't be used again reservekey.KeepKey(); @@ -851,16 +1192,19 @@ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) set setCoins; BOOST_FOREACH(const CTxIn& txin, wtxNew.vin) { - CWalletTx &pcoin = mapWallet[txin.prevout.hash]; - pcoin.MarkSpent(txin.prevout.n); - pcoin.WriteToDisk(); - vWalletUpdated.push_back(pcoin.GetHash()); + CWalletTx &coin = mapWallet[txin.prevout.hash]; + coin.pwallet = this; + coin.MarkSpent(txin.prevout.n); + coin.WriteToDisk(); + vWalletUpdated.push_back(coin.GetHash()); } + + if (fFileBacked) + delete pwalletdb; } // Track how many getdata requests our transaction gets - CRITICAL_BLOCK(cs_mapRequestCount) - mapRequestCount[wtxNew.GetHash()] = 0; + mapRequestCount[wtxNew.GetHash()] = 0; // Broadcast if (!wtxNew.AcceptToMemoryPool()) @@ -878,11 +1222,23 @@ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) -// requires cs_main lock -string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) { - CReserveKey reservekey; + 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 (fWalletUnlockStakeOnly) + { + string strError = _("Error: Wallet unlocked for coinstake only, unable to create transaction."); + printf("SendMoney() : %s", strError.c_str()); + return strError; + } if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) { string strError; @@ -906,8 +1262,7 @@ string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAs -// requires cs_main lock -string 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) @@ -917,8 +1272,7 @@ string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtx // Parse bitcoin address CScript scriptPubKey; - if (!scriptPubKey.SetBitcoinAddress(strAddress)) - return _("Invalid bitcoin address"); + scriptPubKey.SetBitcoinAddress(address); return SendMoney(scriptPubKey, nValue, wtxNew, fAskFee); } @@ -926,42 +1280,151 @@ string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtx -bool LoadWallet(bool& fFirstRunRet) +int CWallet::LoadWallet(bool& fFirstRunRet) { - fFirstRunRet = false; - if (!CWalletDB("cr+").LoadWallet()) + if (!fFileBacked) return false; - fFirstRunRet = vchDefaultKey.empty(); - - if (mapKeys.count(vchDefaultKey)) + fFirstRunRet = false; + int nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this); + if (nLoadWalletRet == DB_NEED_REWRITE) { - // Set keyUser - keyUser.SetPubKey(vchDefaultKey); - keyUser.SetPrivKey(mapKeys[vchDefaultKey]); + 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; } - else + + if (nLoadWalletRet != DB_LOAD_OK) + return nLoadWalletRet; + fFirstRunRet = vchDefaultKey.empty(); + + if (!HaveKey(Hash160(vchDefaultKey))) { // Create new keyUser and set as default key RandAddSeedPerfmon(); - CWalletDB walletdb; - vchDefaultKey = GetKeyFromKeyPool(); - walletdb.WriteDefaultKey(vchDefaultKey); - walletdb.WriteName(PubKeyToAddress(vchDefaultKey), ""); + std::vector newDefaultKey; + if (!GetKeyFromPool(newDefaultKey, false)) + return DB_LOAD_FAIL; + SetDefaultKey(newDefaultKey); + if (!SetAddressBookName(CBitcoinAddress(vchDefaultKey), "")) + return DB_LOAD_FAIL; } - CreateThread(ThreadFlushWalletDB, NULL); + CreateThread(ThreadFlushWalletDB, &strWalletFile); + return DB_LOAD_OK; +} + + +bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& strName) +{ + mapAddressBook[address] = strName; + if (!fFileBacked) + return false; + return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); +} + +bool CWallet::DelAddressBookName(const CBitcoinAddress& address) +{ + mapAddressBook.erase(address); + if (!fFileBacked) + return false; + return CWalletDB(strWalletFile).EraseName(address.ToString()); +} + + +void CWallet::PrintWallet(const CBlock& block) +{ + CRITICAL_BLOCK(cs_wallet) + { + if (block.IsProofOfWork() && mapWallet.count(block.vtx[0].GetHash())) + { + CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()]; + printf(" mine: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); + } + if (block.IsProofOfStake() && mapWallet.count(block.vtx[1].GetHash())) + { + CWalletTx& wtx = mapWallet[block.vtx[1].GetHash()]; + printf(" stake: %d %d %s", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), FormatMoney(wtx.GetCredit()).c_str()); + } + } + printf("\n"); +} + +bool CWallet::GetTransaction(const uint256 &hashTx, CWalletTx& wtx) +{ + CRITICAL_BLOCK(cs_wallet) + { + map::iterator mi = mapWallet.find(hashTx); + if (mi != mapWallet.end()) + { + wtx = (*mi).second; + return true; + } + } + return false; +} + +bool CWallet::SetDefaultKey(const std::vector &vchPubKey) +{ + if (fFileBacked) + { + if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey)) + return false; + } + vchDefaultKey = vchPubKey; return true; } -void CWalletDB::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +bool GetWalletFile(CWallet* pwallet, string &strWalletFileOut) { - nIndex = -1; - keypool.vchPubKey.clear(); - CRITICAL_BLOCK(cs_main) - CRITICAL_BLOCK(cs_mapWallet) - CRITICAL_BLOCK(cs_setKeyPool) + if (!pwallet->fFileBacked) + return false; + strWalletFileOut = pwallet->strWalletFile; + return true; +} + +// +// Mark old keypool keys as used, +// and generate all new keys +// +bool CWallet::NewKeyPool() +{ + CRITICAL_BLOCK(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_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) @@ -969,71 +1432,212 @@ void CWalletDB::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) int64 nEnd = 1; if (!setKeyPool.empty()) nEnd = *(--setKeyPool.end()) + 1; - if (!Write(make_pair(string("pool"), nEnd), CKeyPool(GenerateNewKey()))) - throw runtime_error("ReserveKeyFromKeyPool() : writing generated key failed"); + 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()); } + } + return true; +} + +void CWallet::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +{ + nIndex = -1; + keypool.vchPubKey.clear(); + CRITICAL_BLOCK(cs_wallet) + { + if (!IsLocked()) + TopUpKeyPool(); // Get the oldest key - assert(!setKeyPool.empty()); + if(setKeyPool.empty()) + return; + + CWalletDB walletdb(strWalletFile); + nIndex = *(setKeyPool.begin()); setKeyPool.erase(setKeyPool.begin()); - if (!Read(make_pair(string("pool"), nIndex), keypool)) + if (!walletdb.ReadPool(nIndex, keypool)) throw runtime_error("ReserveKeyFromKeyPool() : read failed"); - if (!mapKeys.count(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); } } -void CWalletDB::KeepKey(int64 nIndex) +void CWallet::KeepKey(int64 nIndex) { // Remove from key pool - CRITICAL_BLOCK(cs_main) - CRITICAL_BLOCK(cs_mapWallet) + if (fFileBacked) { - Erase(make_pair(string("pool"), nIndex)); + CWalletDB walletdb(strWalletFile); + walletdb.ErasePool(nIndex); } printf("keypool keep %"PRI64d"\n", nIndex); } -void CWalletDB::ReturnKey(int64 nIndex) +void CWallet::ReturnKey(int64 nIndex) { // Return to key pool - CRITICAL_BLOCK(cs_setKeyPool) + CRITICAL_BLOCK(cs_wallet) setKeyPool.insert(nIndex); printf("keypool return %"PRI64d"\n", nIndex); } -vector GetKeyFromKeyPool() +bool CWallet::GetKeyFromPool(vector& result, bool fAllowReuse) { - CWalletDB walletdb; int64 nIndex = 0; CKeyPool keypool; - walletdb.ReserveKeyFromKeyPool(nIndex, keypool); - walletdb.KeepKey(nIndex); - return keypool.vchPubKey; + CRITICAL_BLOCK(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 GetOldestKeyPoolTime() +int64 CWallet::GetOldestKeyPoolTime() { - CWalletDB walletdb; int64 nIndex = 0; CKeyPool keypool; - walletdb.ReserveKeyFromKeyPool(nIndex, keypool); - walletdb.ReturnKey(nIndex); + ReserveKeyFromKeyPool(nIndex, keypool); + if (nIndex == -1) + return GetTime(); + ReturnKey(nIndex); return keypool.nTime; } -std::vector CReserveKey::GetReservedKey() +// ppcoin: check 'spent' consistency between wallet and txindex +bool CWallet::CheckSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion) +{ + nMismatchFound = 0; + nBalanceInQuestion = 0; + CRITICAL_BLOCK(cs_wallet) + { + vector vCoins; + vCoins.reserve(mapWallet.size()); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + vCoins.push_back(&(*it).second); + + CTxDB txdb("r"); + BOOST_FOREACH(const CWalletTx* pcoin, vCoins) + { + // Find the corresponding transaction index + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex)) + continue; + for (int n=0; n < pcoin->vout.size(); n++) + { + if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull())) + { + printf("CheckSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + } + else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull())) + { + printf("CheckSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + } + } + } + } + return (nMismatchFound == 0); +} + +// ppcoin: fix wallet spent state according to txindex +void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion) +{ + nMismatchFound = 0; + nBalanceInQuestion = 0; + CRITICAL_BLOCK(cs_wallet) + { + vector vCoins; + vCoins.reserve(mapWallet.size()); + for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + vCoins.push_back(&(*it).second); + + CTxDB txdb("r"); + BOOST_FOREACH(CWalletTx* pcoin, vCoins) + { + // Find the corresponding transaction index + CTxIndex txindex; + if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex)) + continue; + for (int n=0; n < pcoin->vout.size(); n++) + { + if (pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull())) + { + printf("FixSpentCoins found lost coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + pcoin->MarkUnspent(n); + pcoin->WriteToDisk(); + } + else if (!pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull())) + { + printf("FixSpentCoins found spent coin %sppc %s[%d]\n", FormatMoney(pcoin->GetCredit()).c_str(), pcoin->GetHash().ToString().c_str(), n); + nMismatchFound++; + nBalanceInQuestion += pcoin->vout[n].nValue; + pcoin->MarkSpent(n); + pcoin->WriteToDisk(); + } + } + } + } +} + +// ppcoin: disable transaction (only for coinstake) +void CWallet::DisableTransaction(const CTransaction &tx) +{ + if (!tx.IsCoinStake() || !IsFromMe(tx)) + return; // only disconnecting coinstake requires marking input unspent + CRITICAL_BLOCK(cs_wallet) + { + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + map::iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) + { + CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.vout.size() && IsMine(prev.vout[txin.prevout.n])) + { + prev.MarkUnspent(txin.prevout.n); + prev.WriteToDisk(); + } + } + } + } +} + +vector CReserveKey::GetReservedKey() { if (nIndex == -1) { CKeyPool keypool; - CWalletDB().ReserveKeyFromKeyPool(nIndex, keypool); - vchPubKey = keypool.vchPubKey; + 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; + } } assert(!vchPubKey.empty()); return vchPubKey; @@ -1042,7 +1646,7 @@ std::vector CReserveKey::GetReservedKey() void CReserveKey::KeepKey() { if (nIndex != -1) - CWalletDB().KeepKey(nIndex); + pwallet->KeepKey(nIndex); nIndex = -1; vchPubKey.clear(); } @@ -1050,7 +1654,8 @@ void CReserveKey::KeepKey() void CReserveKey::ReturnKey() { if (nIndex != -1) - CWalletDB::ReturnKey(nIndex); + pwallet->ReturnKey(nIndex); nIndex = -1; vchPubKey.clear(); } +