diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 247 |
1 files changed, 176 insertions, 71 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5900828247..29d7138547 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -23,6 +23,7 @@ #include "timedata.h" #include "txmempool.h" #include "util.h" +#include "ui_interface.h" #include "utilmoneystr.h" #include <assert.h> @@ -33,6 +34,7 @@ using namespace std; +CWallet* pwalletMain = NULL; /** Transaction fee set by the user */ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; @@ -364,8 +366,17 @@ void CWallet::Flush(bool shutdown) bitdb.Flush(shutdown); } -bool CWallet::Verify(const string& walletFile, string& warningString, string& errorString) +bool CWallet::Verify() { + std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT); + + LogPrintf("Using wallet %s\n", walletFile); + uiInterface.InitMessage(_("Verifying wallet...")); + + // Wallet file must be a plain filename without a directory + if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile)) + return InitError(strprintf(_("Wallet %s resides outside data directory %s"), walletFile, GetDataDir().string())); + if (!bitdb.Open(GetDataDir())) { // try moving the database env out of the way @@ -381,9 +392,7 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er // try again if (!bitdb.Open(GetDataDir())) { // if it still fails, it probably means we can't even create the database env - string msg = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()); - errorString += msg; - return true; + return InitError(strprintf(_("Error initializing wallet database environment %s!"), GetDataDir())); } } @@ -399,14 +408,14 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover); if (r == CDBEnv::RECOVER_OK) { - warningString += strprintf(_("Warning: Wallet file corrupt, data salvaged!" + InitWarning(strprintf(_("Warning: Wallet file corrupt, data salvaged!" " Original %s saved as %s in %s; if" " your balance or transactions are incorrect you should" " restore from a backup."), - walletFile, "wallet.{timestamp}.bak", GetDataDir()); + walletFile, "wallet.{timestamp}.bak", GetDataDir())); } if (r == CDBEnv::RECOVER_FAIL) - errorString += strprintf(_("%s corrupt, salvage failed"), walletFile); + return InitError(strprintf(_("%s corrupt, salvage failed"), walletFile)); } return true; @@ -1268,7 +1277,9 @@ bool CWalletTx::RelayWalletTransaction() { if (GetDepthInMainChain() == 0 && !isAbandoned() && InMempool()) { LogPrintf("Relaying wtx %s\n", GetHash().ToString()); - RelayTransaction((CTransaction)*this); + CFeeRate feeRate; + mempool.lookupFeeRate(GetHash(), feeRate); + RelayTransaction((CTransaction)*this, feeRate); return true; } } @@ -1578,7 +1589,7 @@ CAmount CWallet::GetUnconfirmedBalance() const for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0)) + if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) nTotal += pcoin->GetAvailableCredit(); } } @@ -1623,7 +1634,7 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0)) + if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) nTotal += pcoin->GetAvailableWatchOnlyCredit(); } } @@ -1668,11 +1679,16 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const if (nDepth < 0) continue; + // We should not consider coins which aren't at least in our mempool + // It's possible for these to be conflicted via ancestors which we may never be able to detect + if (nDepth == 0 && !pcoin->InMempool()) + 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 || fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) + (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i)))) vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO), @@ -1840,10 +1856,9 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int return true; } -bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const +bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const { - vector<COutput> vCoins; - AvailableCoins(vCoins, true, coinControl); + vector<COutput> vCoins(vAvailableCoins); // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) @@ -1903,7 +1918,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx* return res; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const CTxDestination& destChange) { vector<CRecipient> vecSend; @@ -1915,6 +1930,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC } CCoinControl coinControl; + coinControl.destChange = destChange; coinControl.fAllowOtherInputs = true; coinControl.fAllowWatchOnly = includeWatching; BOOST_FOREACH(const CTxIn& txin, tx.vin) @@ -1922,35 +1938,35 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC CReserveKey reservekey(this); CWalletTx wtx; - if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false)) + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false)) return false; - if (nChangePosRet != -1) - tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]); + if (nChangePosInOut != -1) + tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.vout[nChangePosInOut]); // Add new txins (keeping original txin scriptSig/order) BOOST_FOREACH(const CTxIn& txin, wtx.vin) { - bool found = false; - BOOST_FOREACH(const CTxIn& origTxIn, tx.vin) + if (!coinControl.IsSelected(txin.prevout)) { - if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n) + tx.vin.push_back(txin); + + if (lockUnspents) { - found = true; - break; + LOCK2(cs_main, cs_wallet); + LockCoin(txin.prevout); } } - if (!found) - tx.vin.push_back(txin); } return true; } bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) + int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign) { CAmount nValue = 0; + int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; BOOST_FOREACH (const CRecipient& recipient, vecSend) { @@ -2009,14 +2025,17 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt { LOCK2(cs_main, cs_wallet); { + std::vector<COutput> vAvailableCoins; + AvailableCoins(vAvailableCoins, true, coinControl); + nFeeRet = 0; // Start with no fee and loop until there is enough fee while (true) { + nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; - nChangePosRet = -1; bool fFirst = true; CAmount nValueToSelect = nValue; @@ -2058,7 +2077,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt // Choose coins to use set<pair<const CWalletTx*,unsigned int> > setCoins; CAmount nValueIn = 0; - if (!SelectCoins(nValueToSelect, setCoins, nValueIn, coinControl)) + if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) { strFailReason = _("Insufficient funds"); return false; @@ -2136,14 +2155,24 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt // add the dust to the fee. if (newTxOut.IsDust(::minRelayTxFee)) { + nChangePosInOut = -1; nFeeRet += nChange; reservekey.ReturnKey(); } else { - // Insert change txn at random position: - nChangePosRet = GetRandInt(txNew.vout.size()+1); - vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet; + if (nChangePosInOut == -1) + { + // Insert change txn at random position: + nChangePosInOut = GetRandInt(txNew.vout.size()+1); + } + else if (nChangePosInOut > txNew.vout.size()) + { + strFailReason = _("Change index out of range"); + return false; + } + + vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut; txNew.vout.insert(position, newTxOut); } } @@ -2354,6 +2383,31 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) return DB_LOAD_OK; } +DBErrors CWallet::ZapSelectTx(vector<uint256>& vHashIn, vector<uint256>& vHashOut) +{ + if (!fFileBacked) + return DB_LOAD_OK; + DBErrors nZapSelectTxRet = CWalletDB(strWalletFile,"cr+").ZapSelectTx(this, vHashIn, vHashOut); + if (nZapSelectTxRet == 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. + } + } + + if (nZapSelectTxRet != DB_LOAD_OK) + return nZapSelectTxRet; + + MarkDirty(); + + return DB_LOAD_OK; + +} DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) { @@ -2563,12 +2617,19 @@ bool CWallet::GetKeyFromPool(CPubKey& result) int64_t CWallet::GetOldestKeyPoolTime() { - int64_t nIndex = 0; - CKeyPool keypool; - ReserveKeyFromKeyPool(nIndex, keypool); - if (nIndex == -1) + LOCK(cs_wallet); + + // if the keypool is empty, return <NOW> + if (setKeyPool.empty()) return GetTime(); - ReturnKey(nIndex); + + // load oldest key from keypool, get time and return + CKeyPool keypool; + CWalletDB walletdb(strWalletFile); + int64_t nIndex = *(setKeyPool.begin()); + if (!walletdb.ReadPool(nIndex, keypool)) + throw runtime_error("GetOldestKeyPoolTime(): read oldest key in keypool failed"); + assert(keypool.vchPubKey.IsValid()); return keypool.nTime; } @@ -2794,13 +2855,13 @@ void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script) script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; } -void CWallet::LockCoin(COutPoint& output) +void CWallet::LockCoin(const COutPoint& output) { AssertLockHeld(cs_wallet); // setLockedCoins setLockedCoins.insert(output); } -void CWallet::UnlockCoin(COutPoint& output) +void CWallet::UnlockCoin(const COutPoint& output) { AssertLockHeld(cs_wallet); // setLockedCoins setLockedCoins.erase(output); @@ -2971,7 +3032,8 @@ std::string CWallet::GetWalletHelpString(bool showDebug) CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK()))); strUsage += HelpMessageOpt("-rescan", _("Rescan the block chain for missing wallet transactions on startup")); strUsage += HelpMessageOpt("-salvagewallet", _("Attempt to recover private keys from a corrupt wallet on startup")); - strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS)); + if (showDebug) + strUsage += HelpMessageOpt("-sendfreetransactions", strprintf(_("Send transactions as zero-fee transactions if possible (default: %u)"), DEFAULT_SEND_FREE_TRANSACTIONS)); strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE)); strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET)); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); @@ -2993,20 +3055,20 @@ std::string CWallet::GetWalletHelpString(bool showDebug) return strUsage; } -CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWalletFile, std::string& warningString, std::string& errorString) +bool CWallet::InitLoadWallet() { + std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT); + // needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; if (GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); - CWallet *tempWallet = new CWallet(strWalletFile); + CWallet *tempWallet = new CWallet(walletFile); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DB_LOAD_OK) { - errorString = strprintf(_("Error loading %s: Wallet corrupted"), strWalletFile); - uiInterface.InitMessage(strprintf(_("Error loading %s: Wallet corrupted"), strWalletFile)); - return NULL; + return InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); } delete tempWallet; @@ -3017,32 +3079,27 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall int64_t nStart = GetTimeMillis(); bool fFirstRun = true; - CWallet *walletInstance = new CWallet(strWalletFile); + CWallet *walletInstance = new CWallet(walletFile); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DB_LOAD_OK) { if (nLoadWalletRet == DB_CORRUPT) - errorString += strprintf(_("Error loading %s: Wallet corrupted"), strWalletFile) + "\n"; + return InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); else if (nLoadWalletRet == DB_NONCRITICAL_ERROR) { - warningString += strprintf(_("Error reading %s! All keys read correctly, but transaction data" + InitWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data" " or address book entries might be missing or incorrect."), - strWalletFile); + walletFile)); } else if (nLoadWalletRet == DB_TOO_NEW) - errorString += strprintf(_("Error loading %s: Wallet requires newer version of %s"), - strWalletFile, _(PACKAGE_NAME)) + - "\n"; + return InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), + walletFile, _(PACKAGE_NAME))); else if (nLoadWalletRet == DB_NEED_REWRITE) { - errorString += strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME)) + "\n"; - LogPrintf("%s", errorString); + return InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); } else - errorString += strprintf(_("Error loading %s"), strWalletFile) + "\n"; - - if (!errorString.empty()) - return NULL; + return InitError(strprintf(_("Error loading %s"), walletFile)); } if (GetBoolArg("-upgradewallet", fFirstRun)) @@ -3058,8 +3115,7 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); if (nMaxVersion < walletInstance->GetVersion()) { - errorString += _("Cannot downgrade wallet") + "\n"; - return NULL; + return InitError(_("Cannot downgrade wallet")); } walletInstance->SetMaxVersion(nMaxVersion); } @@ -3073,10 +3129,7 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall if (walletInstance->GetKeyFromPool(newDefaultKey)) { walletInstance->SetDefaultKey(newDefaultKey); if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) - { - errorString += _("Cannot write default address") += "\n"; - return NULL; - } + return InitError(_("Cannot write default address") += "\n"); } walletInstance->SetBestChain(chainActive.GetLocator()); @@ -3091,7 +3144,7 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall pindexRescan = chainActive.Genesis(); else { - CWalletDB walletdb(strWalletFile); + CWalletDB walletdb(walletFile); CBlockLocator locator; if (walletdb.ReadBestBlock(locator)) pindexRescan = FindForkInGlobalIndex(chainActive, locator); @@ -3110,10 +3163,7 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall block = block->pprev; if (pindexRescan != block) - { - errorString = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"); - return NULL; - } + return InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); } uiInterface.InitMessage(_("Rescanning...")); @@ -3127,7 +3177,7 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall // Restore wallet transaction metadata after -zapwallettxes=1 if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2") { - CWalletDB walletdb(strWalletFile); + CWalletDB walletdb(walletFile); BOOST_FOREACH(const CWalletTx& wtxOld, vWtx) { @@ -3151,7 +3201,62 @@ CWallet* CWallet::InitLoadWallet(bool fDisableWallet, const std::string& strWall } walletInstance->SetBroadcastTransactions(GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); - return walletInstance; + pwalletMain = walletInstance; + return true; +} + +bool CWallet::ParameterInteraction() +{ + if (mapArgs.count("-mintxfee")) + { + CAmount n = 0; + if (ParseMoney(mapArgs["-mintxfee"], n) && n > 0) + CWallet::minTxFee = CFeeRate(n); + else + return InitError(AmountErrMsg("mintxfee", mapArgs["-mintxfee"])); + } + if (mapArgs.count("-fallbackfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(mapArgs["-fallbackfee"], nFeePerK)) + return InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), mapArgs["-fallbackfee"])); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(_("-fallbackfee is set very high! This is the transaction fee you may pay when fee estimates are not available.")); + CWallet::fallbackFee = CFeeRate(nFeePerK); + } + if (mapArgs.count("-paytxfee")) + { + CAmount nFeePerK = 0; + if (!ParseMoney(mapArgs["-paytxfee"], nFeePerK)) + return InitError(AmountErrMsg("paytxfee", mapArgs["-paytxfee"])); + if (nFeePerK > HIGH_TX_FEE_PER_KB) + InitWarning(_("-paytxfee is set very high! This is the transaction fee you will pay if you send a transaction.")); + payTxFee = CFeeRate(nFeePerK, 1000); + if (payTxFee < ::minRelayTxFee) + { + return InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), + mapArgs["-paytxfee"], ::minRelayTxFee.ToString())); + } + } + if (mapArgs.count("-maxtxfee")) + { + CAmount nMaxFee = 0; + if (!ParseMoney(mapArgs["-maxtxfee"], nMaxFee)) + return InitError(AmountErrMsg("maxtxfee", mapArgs["-maxtxfee"])); + if (nMaxFee > HIGH_MAX_TX_FEE) + InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); + maxTxFee = nMaxFee; + if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) + { + return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + mapArgs["-maxtxfee"], ::minRelayTxFee.ToString())); + } + } + nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); + bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); + fSendFreeTransactions = GetBoolArg("-sendfreetransactions", DEFAULT_SEND_FREE_TRANSACTIONS); + + return true; } CKeyPool::CKeyPool() @@ -3231,5 +3336,5 @@ int CMerkleTx::GetBlocksToMaturity() const bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee) { CValidationState state; - return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, nAbsurdFee); + return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, NULL, false, nAbsurdFee); } |