aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r--src/wallet/wallet.cpp1197
1 files changed, 577 insertions, 620 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 599e74149c..bb7be2df33 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,40 +1,39 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2016 The Bitcoin Core developers
+// Copyright (c) 2009-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/wallet.h"
-
-#include "base58.h"
-#include "checkpoints.h"
-#include "chain.h"
-#include "wallet/coincontrol.h"
-#include "consensus/consensus.h"
-#include "consensus/validation.h"
-#include "fs.h"
-#include "init.h"
-#include "key.h"
-#include "keystore.h"
-#include "validation.h"
-#include "net.h"
-#include "policy/fees.h"
-#include "policy/policy.h"
-#include "policy/rbf.h"
-#include "primitives/block.h"
-#include "primitives/transaction.h"
-#include "script/script.h"
-#include "script/sign.h"
-#include "scheduler.h"
-#include "timedata.h"
-#include "txmempool.h"
-#include "util.h"
-#include "ui_interface.h"
-#include "utilmoneystr.h"
+#include <wallet/wallet.h>
+
+#include <base58.h>
+#include <checkpoints.h>
+#include <chain.h>
+#include <wallet/coincontrol.h>
+#include <consensus/consensus.h>
+#include <consensus/validation.h>
+#include <fs.h>
+#include <wallet/init.h>
+#include <key.h>
+#include <keystore.h>
+#include <validation.h>
+#include <net.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <primitives/block.h>
+#include <primitives/transaction.h>
+#include <script/script.h>
+#include <scheduler.h>
+#include <timedata.h>
+#include <txmempool.h>
+#include <util.h>
+#include <utilmoneystr.h>
+#include <wallet/fees.h>
#include <assert.h>
+#include <future>
#include <boost/algorithm/string/replace.hpp>
-#include <boost/thread.hpp>
std::vector<CWalletRef> vpwallets;
/** Transaction fee set by the user */
@@ -42,6 +41,9 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
bool fWalletRbf = DEFAULT_WALLET_RBF;
+OutputType g_address_type = OUTPUT_TYPE_NONE;
+OutputType g_change_type = OUTPUT_TYPE_NONE;
+bool g_wallet_allow_fallback_fee = true; //<! will be defined via chainparams
const char * DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
@@ -110,7 +112,26 @@ public:
Process(script);
}
- void operator()(const CNoDestination &none) {}
+ void operator()(const WitnessV0ScriptHash& scriptID)
+ {
+ CScriptID id;
+ CRIPEMD160().Write(scriptID.begin(), 32).Finalize(id.begin());
+ CScript script;
+ if (keystore.GetCScript(id, script)) {
+ Process(script);
+ }
+ }
+
+ void operator()(const WitnessV0KeyHash& keyid)
+ {
+ CKeyID id(keyid);
+ if (keystore.HaveKey(id)) {
+ vKeys.push_back(id);
+ }
+ }
+
+ template<typename X>
+ void operator()(const X &none) {}
};
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
@@ -263,7 +284,7 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
}
}
-bool CWallet::LoadKeyMetadata(const CTxDestination& keyID, const CKeyMetadata &meta)
+bool CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
UpdateTimeFirstKey(meta.nCreateTime);
@@ -271,6 +292,14 @@ bool CWallet::LoadKeyMetadata(const CTxDestination& keyID, const CKeyMetadata &m
return true;
}
+bool CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta)
+{
+ AssertLockHeld(cs_wallet); // m_script_metadata
+ UpdateTimeFirstKey(meta.nCreateTime);
+ m_script_metadata[script_id] = meta;
+ return true;
+}
+
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
@@ -306,7 +335,7 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
* these. Do not add them to the wallet and warn. */
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
{
- std::string strAddr = CBitcoinAddress(CScriptID(redeemScript)).ToString();
+ std::string strAddr = EncodeDestination(CScriptID(redeemScript));
LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
return true;
@@ -319,7 +348,7 @@ bool CWallet::AddWatchOnly(const CScript& dest)
{
if (!CCryptoKeyStore::AddWatchOnly(dest))
return false;
- const CKeyMetadata& meta = mapKeyMetadata[CScriptID(dest)];
+ const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
return CWalletDB(*dbw).WriteWatchOnly(dest, meta);
@@ -327,7 +356,7 @@ bool CWallet::AddWatchOnly(const CScript& dest)
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
{
- mapKeyMetadata[CScriptID(dest)].nCreateTime = nCreateTime;
+ m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
return AddWatchOnly(dest);
}
@@ -389,11 +418,11 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
{
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
- pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
+ pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(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;
+ pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>(pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
@@ -494,63 +523,6 @@ void CWallet::Flush(bool shutdown)
dbw->Flush(shutdown);
}
-bool CWallet::Verify()
-{
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
- return true;
-
- uiInterface.InitMessage(_("Verifying wallet(s)..."));
-
- // Keep track of each wallet absolute path to detect duplicates.
- std::set<fs::path> wallet_paths;
-
- for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
- if (boost::filesystem::path(walletFile).filename() != walletFile) {
- return InitError(strprintf(_("Error loading wallet %s. -wallet parameter must only specify a filename (not a path)."), walletFile));
- }
-
- if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) {
- return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile));
- }
-
- fs::path wallet_path = fs::absolute(walletFile, GetDataDir());
-
- if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) {
- return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile));
- }
-
- if (!wallet_paths.insert(wallet_path).second) {
- return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile));
- }
-
- std::string strError;
- if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) {
- return InitError(strError);
- }
-
- if (gArgs.GetBoolArg("-salvagewallet", false)) {
- // Recover readable keypairs:
- CWallet dummyWallet;
- std::string backup_filename;
- if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) {
- return false;
- }
- }
-
- std::string strWarning;
- bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
- if (!strWarning.empty()) {
- InitWarning(strWarning);
- }
- if (!dbV) {
- InitError(strError);
- return false;
- }
- }
-
- return true;
-}
-
void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> range)
{
// We want all the wallet transactions in range to have the same metadata as
@@ -559,22 +531,23 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx* copyFrom = nullptr;
- for (TxSpends::iterator it = range.first; it != range.second; ++it)
- {
- const uint256& hash = it->second;
- int n = mapWallet[hash].nOrderPos;
- if (n < nMinOrderPos)
- {
- nMinOrderPos = n;
- copyFrom = &mapWallet[hash];
+ for (TxSpends::iterator it = range.first; it != range.second; ++it) {
+ const CWalletTx* wtx = &mapWallet[it->second];
+ if (wtx->nOrderPos < nMinOrderPos) {
+ nMinOrderPos = wtx->nOrderPos;;
+ copyFrom = wtx;
}
}
+
+ assert(copyFrom);
+
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
+ assert(copyFrom && "Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
@@ -623,8 +596,9 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
void CWallet::AddToSpends(const uint256& wtxid)
{
- assert(mapWallet.count(wtxid));
- CWalletTx& thisTx = mapWallet[wtxid];
+ auto it = mapWallet.find(wtxid);
+ assert(it != mapWallet.end());
+ CWalletTx& thisTx = it->second;
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
@@ -650,11 +624,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
- kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
+ kMasterKey.nDeriveIterations = static_cast<unsigned int>(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;
+ kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime)))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
@@ -736,16 +710,16 @@ DBErrors CWallet::ReorderTransactions()
typedef std::multimap<int64_t, TxPair > TxItems;
TxItems txByTime;
- for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (auto& entry : mapWallet)
{
- CWalletTx* wtx = &((*it).second);
- txByTime.insert(std::make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
+ CWalletTx* wtx = &entry.second;
+ txByTime.insert(std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr)));
}
std::list<CAccountingEntry> acentries;
walletdb.ListAccountCreditDebit("", acentries);
for (CAccountingEntry& entry : acentries)
{
- txByTime.insert(std::make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
+ txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &entry)));
}
nOrderPosNext = 0;
@@ -754,7 +728,7 @@ DBErrors CWallet::ReorderTransactions()
{
CWalletTx *const pwtx = (*it).second.first;
CAccountingEntry *const pacentry = (*it).second.second;
- int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
+ int64_t& nOrderPos = (pwtx != nullptr) ? pwtx->nOrderPos : pacentry->nOrderPos;
if (nOrderPos == -1)
{
@@ -846,7 +820,7 @@ bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmoun
return true;
}
-bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew)
+bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew)
{
CWalletDB walletdb(*dbw);
@@ -857,8 +831,8 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
if (!account.vchPubKey.IsValid())
bForceNew = true;
else {
- // Check if the current key has been used
- CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID());
+ // Check if the current key has been used (TODO: check other addresses with the same key)
+ CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, g_address_type));
for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid();
++it)
@@ -875,12 +849,14 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
if (!GetKeyFromPool(account.vchPubKey, false))
return false;
- SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
+ LearnRelatedScripts(account.vchPubKey, g_address_type);
+ dest = GetDestinationForKey(account.vchPubKey, g_address_type);
+ SetAddressBook(dest, strAccount, "receive");
walletdb.WriteAccount(strAccount, account);
+ } else {
+ dest = GetDestinationForKey(account.vchPubKey, g_address_type);
}
- pubKey = account.vchPubKey;
-
return true;
}
@@ -939,7 +915,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
{
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&walletdb);
- wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
+ wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
}
@@ -969,6 +945,15 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
+ // If we have a witness-stripped version of this transaction, and we
+ // see a new version with a witness, then we must be upgrading a pre-segwit
+ // wallet. Store the new version of the transaction with the witness,
+ // as the stripped-version must be invalid.
+ // TODO: Store all versions of the transaction, instead of just one.
+ if (wtxIn.tx->HasWitness() && !wtx.tx->HasWitness()) {
+ wtx.SetTx(wtxIn.tx);
+ fUpdated = true;
+ }
}
//// debug print
@@ -988,10 +973,11 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = gArgs.GetArg("-walletnotify", "");
- if ( !strCmd.empty())
+ if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
- boost::thread t(runCommand, strCmd); // thread runs free
+ std::thread t(runCommand, strCmd);
+ t.detach(); // thread runs free
}
return true;
@@ -1000,15 +986,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
{
uint256 hash = wtxIn.GetHash();
-
- mapWallet[hash] = wtxIn;
- CWalletTx& wtx = mapWallet[hash];
+ CWalletTx& wtx = mapWallet.emplace(hash, wtxIn).first->second;
wtx.BindWallet(this);
- wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
+ wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
AddToSpends(hash);
for (const CTxIn& txin : wtx.tx->vin) {
- if (mapWallet.count(txin.prevout.hash)) {
- CWalletTx& prevtx = mapWallet[txin.prevout.hash];
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ CWalletTx& prevtx = it->second;
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
}
@@ -1094,7 +1079,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK2(cs_main, cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
- return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
+ return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
}
bool CWallet::AbandonTransaction(const uint256& hashTx)
@@ -1107,9 +1092,10 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
std::set<uint256> done;
// Can't mark abandoned if confirmed or in mempool
- assert(mapWallet.count(hashTx));
- CWalletTx& origtx = mapWallet[hashTx];
- if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
+ auto it = mapWallet.find(hashTx);
+ assert(it != mapWallet.end());
+ CWalletTx& origtx = it->second;
+ if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -1119,8 +1105,9 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
- assert(mapWallet.count(now));
- CWalletTx& wtx = mapWallet[now];
+ auto it = mapWallet.find(now);
+ assert(it != mapWallet.end());
+ CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be
assert(currentconfirm <= 0);
@@ -1145,8 +1132,10 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
// available of the outputs it spends. So force those to be recomputed
for (const CTxIn& txin : wtx.tx->vin)
{
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ it->second.MarkDirty();
+ }
}
}
}
@@ -1184,8 +1173,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
- assert(mapWallet.count(now));
- CWalletTx& wtx = mapWallet[now];
+ auto it = mapWallet.find(now);
+ assert(it != mapWallet.end());
+ CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
@@ -1204,10 +1194,11 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed
- for (const CTxIn& txin : wtx.tx->vin)
- {
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
+ for (const CTxIn& txin : wtx.tx->vin) {
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ it->second.MarkDirty();
+ }
}
}
}
@@ -1222,16 +1213,30 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
- for (const CTxIn& txin : tx.vin)
- {
- if (mapWallet.count(txin.prevout.hash))
- mapWallet[txin.prevout.hash].MarkDirty();
+ for (const CTxIn& txin : tx.vin) {
+ auto it = mapWallet.find(txin.prevout.hash);
+ if (it != mapWallet.end()) {
+ it->second.MarkDirty();
+ }
}
}
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
LOCK2(cs_main, cs_wallet);
SyncTransaction(ptx);
+
+ auto it = mapWallet.find(ptx->GetHash());
+ if (it != mapWallet.end()) {
+ it->second.fInMempool = true;
+ }
+}
+
+void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
+ LOCK(cs_wallet);
+ auto it = mapWallet.find(ptx->GetHash());
+ if (it != mapWallet.end()) {
+ it->second.fInMempool = false;
+ }
}
void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) {
@@ -1246,10 +1251,14 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const
for (const CTransactionRef& ptx : vtxConflicted) {
SyncTransaction(ptx);
+ TransactionRemovedFromMempool(ptx);
}
for (size_t i = 0; i < pblock->vtx.size(); i++) {
SyncTransaction(pblock->vtx[i], pindex, i);
+ TransactionRemovedFromMempool(pblock->vtx[i]);
}
+
+ m_last_block_processed = pindex;
}
void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
@@ -1262,6 +1271,31 @@ void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
+void CWallet::BlockUntilSyncedToCurrentChain() {
+ AssertLockNotHeld(cs_main);
+ AssertLockNotHeld(cs_wallet);
+
+ {
+ // Skip the queue-draining stuff if we know we're caught up with
+ // chainActive.Tip()...
+ // We could also take cs_wallet here, and call m_last_block_processed
+ // protected by cs_wallet instead of cs_main, but as long as we need
+ // cs_main here anyway, its easier to just call it cs_main-protected.
+ LOCK(cs_main);
+ const CBlockIndex* initialChainTip = chainActive.Tip();
+
+ if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) {
+ return;
+ }
+ }
+
+ // ...otherwise put a callback in the validation interface queue and wait
+ // for the queue to drain enough to execute it (indicating we are caught up
+ // at least with the time we entered this function).
+ SyncWithValidationInterfaceQueue();
+}
+
+
isminetype CWallet::IsMine(const CTxIn &txin) const
{
{
@@ -1574,19 +1608,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
* @return Earliest timestamp that could be successfully scanned from. Timestamp
* returned will be higher than startTime if relevant blocks could not be read.
*/
-int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
+int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update)
{
- AssertLockHeld(cs_main);
- AssertLockHeld(cs_wallet);
-
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
- CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
- LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ CBlockIndex* startBlock = nullptr;
+ {
+ LOCK(cs_main);
+ startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
+ LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ }
if (startBlock) {
- const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, update);
+ const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
if (failedBlock) {
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
@@ -1602,47 +1637,82 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
* Returns null if scan was successful. Otherwise, if a complete rescan was not
* possible (due to pruning or corruption), returns pointer to the most recent
* block that could not be scanned.
+ *
+ * If pindexStop is not a nullptr, the scan will stop at the block-index
+ * defined by pindexStop
+ *
+ * Caller needs to make sure pindexStop (and the optional pindexStart) are on
+ * the main chain after to the addition of any new keys you want to detect
+ * transactions for.
*/
-CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
+CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
{
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
+ assert(reserver.isReserved());
+ if (pindexStop) {
+ assert(pindexStop->nHeight >= pindexStart->nHeight);
+ }
+
CBlockIndex* pindex = pindexStart;
CBlockIndex* ret = nullptr;
{
- LOCK2(cs_main, cs_wallet);
fAbortRescan = false;
- fScanningWallet = true;
-
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
- double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
- double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
+ CBlockIndex* tip = nullptr;
+ double dProgressStart;
+ double dProgressTip;
+ {
+ LOCK(cs_main);
+ tip = chainActive.Tip();
+ dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
+ double gvp = dProgressStart;
while (pindex && !fAbortRescan)
{
- if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
- ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
+ ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ }
if (GetTime() >= nNow + 60) {
nNow = GetTime();
- LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
+ LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, gvp);
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
+ LOCK2(cs_main, cs_wallet);
+ if (pindex && !chainActive.Contains(pindex)) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ ret = pindex;
+ break;
+ }
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
} else {
ret = pindex;
}
- pindex = chainActive.Next(pindex);
+ if (pindex == pindexStop) {
+ break;
+ }
+ {
+ LOCK(cs_main);
+ pindex = chainActive.Next(pindex);
+ gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
+ if (tip != chainActive.Tip()) {
+ tip = chainActive.Tip();
+ // in case the tip has changed, update progress max
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
+ }
}
if (pindex && fAbortRescan) {
- LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
+ LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, gvp);
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
-
- fScanningWallet = false;
}
return ret;
}
@@ -1670,11 +1740,8 @@ void CWallet::ReacceptWalletTransactions()
}
// Try to add wallet transactions to memory pool
- for (std::pair<const int64_t, CWalletTx*>& item : mapSorted)
- {
+ for (std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second);
-
- LOCK(mempool.cs);
CValidationState state;
wtx.AcceptToMemoryPool(maxTxFee, state);
}
@@ -1726,7 +1793,7 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
debit += nDebitCached;
else
{
- nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
+ nDebitCached = pwallet->GetDebit(*tx, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
@@ -1737,7 +1804,7 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
debit += nWatchDebitCached;
else
{
- nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
+ nWatchDebitCached = pwallet->GetDebit(*tx, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += nWatchDebitCached;
}
@@ -1759,7 +1826,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
credit += nCreditCached;
else
{
- nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
+ nCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
@@ -1770,7 +1837,7 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const
credit += nWatchCreditCached;
else
{
- nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
+ nWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
@@ -1784,7 +1851,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (fUseCache && fImmatureCreditCached)
return nImmatureCreditCached;
- nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
+ nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
@@ -1794,7 +1861,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{
- if (pwallet == 0)
+ if (pwallet == nullptr)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
@@ -1822,13 +1889,13 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
return nCredit;
}
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
+CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureWatchCreditCached)
return nImmatureWatchCreditCached;
- nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
+ nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
@@ -1836,9 +1903,9 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
return 0;
}
-CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
+CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const
{
- if (pwallet == 0)
+ if (pwallet == nullptr)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
@@ -1869,21 +1936,20 @@ CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
- nChangeCached = pwallet->GetChange(*this);
+ nChangeCached = pwallet->GetChange(*tx);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const
{
- LOCK(mempool.cs);
- return mempool.exists(GetHash());
+ return fInMempool;
}
bool CWalletTx::IsTrusted() const
{
// Quick answer in most cases
- if (!CheckFinalTx(*this))
+ if (!CheckFinalTx(*tx))
return false;
int nDepth = GetDepthInMainChain();
if (nDepth >= 1)
@@ -1984,9 +2050,9 @@ CAmount CWallet::GetBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableCredit();
}
@@ -2000,9 +2066,9 @@ CAmount CWallet::GetUnconfirmedBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableCredit();
}
@@ -2015,9 +2081,9 @@ CAmount CWallet::GetImmatureBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
nTotal += pcoin->GetImmatureCredit();
}
}
@@ -2029,9 +2095,9 @@ CAmount CWallet::GetWatchOnlyBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
@@ -2045,9 +2111,9 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
@@ -2060,9 +2126,9 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const CWalletTx* pcoin = &(*it).second;
+ const CWalletTx* pcoin = &entry.second;
nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
}
@@ -2127,113 +2193,111 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
return balance;
}
-void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const
+void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const
{
+ AssertLockHeld(cs_main);
+ AssertLockHeld(cs_wallet);
+
vCoins.clear();
+ CAmount nTotal = 0;
+ for (const auto& entry : mapWallet)
{
- LOCK2(cs_main, cs_wallet);
+ const uint256& wtxid = entry.first;
+ const CWalletTx* pcoin = &entry.second;
- CAmount nTotal = 0;
-
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- const uint256& wtxid = it->first;
- const CWalletTx* pcoin = &(*it).second;
-
- if (!CheckFinalTx(*pcoin))
- continue;
+ if (!CheckFinalTx(*pcoin->tx))
+ continue;
- if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
- continue;
+ if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ continue;
- int nDepth = pcoin->GetDepthInMainChain();
- if (nDepth < 0)
- continue;
+ int nDepth = pcoin->GetDepthInMainChain();
+ 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;
+ // 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;
- bool safeTx = pcoin->IsTrusted();
-
- // We should not consider coins from transactions that are replacing
- // other transactions.
- //
- // Example: There is a transaction A which is replaced by bumpfee
- // transaction B. In this case, we want to prevent creation of
- // a transaction B' which spends an output of B.
- //
- // Reason: If transaction A were initially confirmed, transactions B
- // and B' would no longer be valid, so the user would have to create
- // a new transaction C to replace B'. However, in the case of a
- // one-block reorg, transactions B' and C might BOTH be accepted,
- // when the user only wanted one of them. Specifically, there could
- // be a 1-block reorg away from the chain where transactions A and C
- // were accepted to another chain where B, B', and C were all
- // accepted.
- if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
- safeTx = false;
- }
+ bool safeTx = pcoin->IsTrusted();
+
+ // We should not consider coins from transactions that are replacing
+ // other transactions.
+ //
+ // Example: There is a transaction A which is replaced by bumpfee
+ // transaction B. In this case, we want to prevent creation of
+ // a transaction B' which spends an output of B.
+ //
+ // Reason: If transaction A were initially confirmed, transactions B
+ // and B' would no longer be valid, so the user would have to create
+ // a new transaction C to replace B'. However, in the case of a
+ // one-block reorg, transactions B' and C might BOTH be accepted,
+ // when the user only wanted one of them. Specifically, there could
+ // be a 1-block reorg away from the chain where transactions A and C
+ // were accepted to another chain where B, B', and C were all
+ // accepted.
+ if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
+ safeTx = false;
+ }
+
+ // Similarly, we should not consider coins from transactions that
+ // have been replaced. In the example above, we would want to prevent
+ // creation of a transaction A' spending an output of A, because if
+ // transaction B were initially confirmed, conflicting with A and
+ // A', we wouldn't want to the user to create a transaction D
+ // intending to replace A', but potentially resulting in a scenario
+ // where A, A', and D could all be accepted (instead of just B and
+ // D, or just A and A' like the user would want).
+ if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
+ safeTx = false;
+ }
+
+ if (fOnlySafe && !safeTx) {
+ continue;
+ }
- // Similarly, we should not consider coins from transactions that
- // have been replaced. In the example above, we would want to prevent
- // creation of a transaction A' spending an output of A, because if
- // transaction B were initially confirmed, conflicting with A and
- // A', we wouldn't want to the user to create a transaction D
- // intending to replace A', but potentially resulting in a scenario
- // where A, A', and D could all be accepted (instead of just B and
- // D, or just A and A' like the user would want).
- if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
- safeTx = false;
- }
+ if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ continue;
- if (fOnlySafe && !safeTx) {
+ for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
+ if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
continue;
- }
- if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue;
- for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
- if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
- continue;
-
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i)))
- continue;
-
- if (IsLockedCoin((*it).first, i))
- continue;
+ if (IsLockedCoin(entry.first, i))
+ continue;
- if (IsSpent(wtxid, i))
- continue;
+ if (IsSpent(wtxid, i))
+ continue;
- isminetype mine = IsMine(pcoin->tx->vout[i]);
+ isminetype mine = IsMine(pcoin->tx->vout[i]);
- if (mine == ISMINE_NO) {
- continue;
- }
-
- bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
- bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
+ if (mine == ISMINE_NO) {
+ continue;
+ }
- vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
+ bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
+ bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
- // Checks the sum amount of all UTXO's.
- if (nMinimumSumAmount != MAX_MONEY) {
- nTotal += pcoin->tx->vout[i].nValue;
+ vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
- if (nTotal >= nMinimumSumAmount) {
- return;
- }
- }
+ // Checks the sum amount of all UTXO's.
+ if (nMinimumSumAmount != MAX_MONEY) {
+ nTotal += pcoin->tx->vout[i].nValue;
- // Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ if (nTotal >= nMinimumSumAmount) {
return;
}
}
+
+ // Checks the maximum number of UTXO's.
+ if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ return;
+ }
}
}
}
@@ -2251,11 +2315,11 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
// avoid adding some extra complexity to the Qt code.
std::map<CTxDestination, std::vector<COutput>> result;
-
std::vector<COutput> availableCoins;
- AvailableCoins(availableCoins);
LOCK2(cs_main, cs_wallet);
+ AvailableCoins(availableCoins);
+
for (auto& coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
@@ -2550,9 +2614,8 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
{
std::vector<CRecipient> vecSend;
- // Turn the txout set into a CRecipient vector
- for (size_t idx = 0; idx < tx.vout.size(); idx++)
- {
+ // Turn the txout set into a CRecipient vector.
+ for (size_t idx = 0; idx < tx.vout.size(); idx++) {
const CTxOut& txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient);
@@ -2560,8 +2623,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
coinControl.fAllowOtherInputs = true;
- for (const CTxIn& txin : tx.vin)
+ for (const CTxIn& txin : tx.vin) {
coinControl.Select(txin.prevout);
+ }
+
+ // Acquire the locks to prevent races to the new locked unspents between the
+ // CreateTransaction call and LockCoin calls (when lockUnspents is true).
+ LOCK2(cs_main, cs_wallet);
CReserveKey reservekey(this);
CWalletTx wtx;
@@ -2571,43 +2639,57 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]);
- // we dont have the normal Create/Commit cycle, and dont want to risk reusing change,
- // so just remove the key from the keypool here.
+ // We don't have the normal Create/Commit cycle, and don't want to risk
+ // reusing change, so just remove the key from the keypool here.
reservekey.KeepKey();
}
- // Copy output sizes from new transaction; they may have had the fee subtracted from them
- for (unsigned int idx = 0; idx < tx.vout.size(); idx++)
+ // Copy output sizes from new transaction; they may have had the fee
+ // subtracted from them.
+ for (unsigned int idx = 0; idx < tx.vout.size(); idx++) {
tx.vout[idx].nValue = wtx.tx->vout[idx].nValue;
+ }
- // Add new txins (keeping original txin scriptSig/order)
- for (const CTxIn& txin : wtx.tx->vin)
- {
- if (!coinControl.IsSelected(txin.prevout))
- {
+ // Add new txins while keeping original txin scriptSig/order.
+ for (const CTxIn& txin : wtx.tx->vin) {
+ if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
- if (lockUnspents)
- {
- LOCK2(cs_main, cs_wallet);
- LockCoin(txin.prevout);
+ if (lockUnspents) {
+ LockCoin(txin.prevout);
}
}
}
-
return true;
}
-static CFeeRate GetDiscardRate(const CBlockPolicyEstimator& estimator)
+OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend)
{
- unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
- CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */);
- // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate
- discard_rate = (discard_rate == CFeeRate(0)) ? CWallet::m_discard_rate : std::min(discard_rate, CWallet::m_discard_rate);
- // Discard rate must be at least dustRelayFee
- discard_rate = std::max(discard_rate, ::dustRelayFee);
- return discard_rate;
+ // If -changetype is specified, always use that change type.
+ if (change_type != OUTPUT_TYPE_NONE) {
+ return change_type;
+ }
+
+ // if g_address_type is legacy, use legacy address as change (even
+ // if some of the outputs are P2WPKH or P2WSH).
+ if (g_address_type == OUTPUT_TYPE_LEGACY) {
+ return OUTPUT_TYPE_LEGACY;
+ }
+
+ // if any destination is P2WPKH or P2WSH, use P2WPKH for the change
+ // output.
+ for (const auto& recipient : vecSend) {
+ // Check if any destination contains a witness program:
+ int witnessversion = 0;
+ std::vector<unsigned char> witnessprogram;
+ if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
+ return OUTPUT_TYPE_BECH32;
+ }
+ }
+
+ // else use g_address_type for change
+ return g_address_type;
}
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
@@ -2670,6 +2752,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
FeeCalculation feeCalc;
+ CAmount nFeeNeeded;
unsigned int nBytes;
{
std::set<CInputCoin> setCoins;
@@ -2704,7 +2787,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
- scriptChange = GetScriptForDestination(vchPubKey.GetID());
+ const OutputType change_type = TransactionChangeType(coin_control.change_type, vecSend);
+
+ LearnRelatedScripts(vchPubKey, change_type);
+ scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
}
CTxOut change_prototype_txout(0, scriptChange);
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
@@ -2732,6 +2818,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (recipient.fSubtractFeeFromAmount)
{
+ assert(nSubtractFeeFromAmount != 0);
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
@@ -2831,7 +2918,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
vin.scriptWitness.SetNull();
}
- CAmount nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
+ nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
+ if (feeCalc.reason == FeeReason::FALLBACK && !g_wallet_allow_fallback_fee) {
+ // eventually allow a fallback fee
+ strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
+ return false;
+ }
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
@@ -2852,13 +2944,15 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// new inputs. We now know we only need the smaller fee
// (because of reduced tx size) and so we should add a
// change output. Only try this once.
- CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, coin_control, ::mempool, ::feeEstimator, nullptr);
- CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
- CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change;
- if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
- pick_new_inputs = false;
- nFeeRet = nFeeNeeded + fee_needed_for_change;
- continue;
+ if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
+ unsigned int tx_size_with_change = nBytes + change_prototype_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
+ CAmount fee_needed_with_change = GetMinimumFee(tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr);
+ CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
+ if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
+ pick_new_inputs = false;
+ nFeeRet = fee_needed_with_change;
+ continue;
+ }
}
// If we have change output already, just increase it
@@ -2930,7 +3024,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
wtxNew.SetTx(MakeTransactionRef(std::move(txNew)));
// Limit size
- if (GetTransactionWeight(wtxNew) >= MAX_STANDARD_TX_WEIGHT)
+ if (GetTransactionWeight(*wtxNew.tx) >= MAX_STANDARD_TX_WEIGHT)
{
strFailReason = _("Transaction too large");
return false;
@@ -2953,8 +3047,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
}
}
- LogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
- nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
+ LogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
+ nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
feeCalc.est.pass.start, feeCalc.est.pass.end,
100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool),
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
@@ -2992,14 +3086,18 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
// Track how many getdata requests our transaction gets
mapRequestCount[wtxNew.GetHash()] = 0;
+ // Get the inserted-CWalletTx from mapWallet so that the
+ // fInMempool flag is cached properly
+ CWalletTx& wtx = mapWallet[wtxNew.GetHash()];
+
if (fBroadcastTransactions)
{
// Broadcast
- if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
- LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
+ if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
+ LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
} else {
- wtxNew.RelayWalletTransaction(connman);
+ wtx.RelayWalletTransaction(connman);
}
}
}
@@ -3026,85 +3124,21 @@ bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB *pwa
laccentries.push_back(acentry);
CAccountingEntry & entry = laccentries.back();
- wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
+ wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry)));
return true;
}
-CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
-{
- return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
-}
-
-CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc)
-{
- /* User control of how to calculate fee uses the following parameter precedence:
- 1. coin_control.m_feerate
- 2. coin_control.m_confirm_target
- 3. payTxFee (user-set global variable)
- 4. nTxConfirmTarget (user-set global variable)
- The first parameter that is set is used.
- */
- CAmount fee_needed;
- if (coin_control.m_feerate) { // 1.
- fee_needed = coin_control.m_feerate->GetFee(nTxBytes);
- if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
- // Allow to override automatic min/max check over coin control instance
- if (coin_control.fOverrideFeeRate) return fee_needed;
- }
- else if (!coin_control.m_confirm_target && ::payTxFee != CFeeRate(0)) { // 3. TODO: remove magic value of 0 for global payTxFee
- fee_needed = ::payTxFee.GetFee(nTxBytes);
- if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
- }
- else { // 2. or 4.
- // We will use smart fee estimation
- unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : ::nTxConfirmTarget;
- // By default estimates are economical iff we are signaling opt-in-RBF
- bool conservative_estimate = !coin_control.signalRbf;
- // Allow to override the default fee estimate mode over the CoinControl instance
- if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true;
- else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false;
-
- fee_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate).GetFee(nTxBytes);
- if (fee_needed == 0) {
- // if we don't have enough data for estimateSmartFee, then use fallbackFee
- fee_needed = fallbackFee.GetFee(nTxBytes);
- if (feeCalc) feeCalc->reason = FeeReason::FALLBACK;
- }
- // Obey mempool min fee when using smart fee estimation
- CAmount min_mempool_fee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nTxBytes);
- if (fee_needed < min_mempool_fee) {
- fee_needed = min_mempool_fee;
- if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
- }
- }
-
- // prevent user from paying a fee below minRelayTxFee or minTxFee
- CAmount required_fee = GetRequiredFee(nTxBytes);
- if (required_fee > fee_needed) {
- fee_needed = required_fee;
- if (feeCalc) feeCalc->reason = FeeReason::REQUIRED;
- }
- // But always obey the maximum
- if (fee_needed > maxTxFee) {
- fee_needed = maxTxFee;
- if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
- }
- return fee_needed;
-}
-
-
-
-
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
+ LOCK2(cs_main, cs_wallet);
+
fFirstRunRet = false;
DBErrors nLoadWalletRet = CWalletDB(*dbw,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE)
{
if (dbw->Rewrite("\x04pool"))
{
- LOCK(cs_wallet);
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
@@ -3114,9 +3148,11 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
}
}
+ // This wallet is in its first run if all of these are empty
+ fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty();
+
if (nLoadWalletRet != DB_LOAD_OK)
return nLoadWalletRet;
- fFirstRunRet = !vchDefaultKey.IsValid();
uiInterface.LoadWallet(this);
@@ -3126,7 +3162,6 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
AssertLockHeld(cs_wallet); // mapWallet
- vchDefaultKey = CPubKey();
DBErrors nZapSelectTxRet = CWalletDB(*dbw,"cr+").ZapSelectTx(vHashIn, vHashOut);
for (uint256 hash : vHashOut)
mapWallet.erase(hash);
@@ -3155,7 +3190,6 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
- vchDefaultKey = CPubKey();
DBErrors nZapWalletTxRet = CWalletDB(*dbw,"cr+").ZapWalletTx(vWtx);
if (nZapWalletTxRet == DB_NEED_REWRITE)
{
@@ -3191,9 +3225,9 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
- if (!strPurpose.empty() && !CWalletDB(*dbw).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
+ if (!strPurpose.empty() && !CWalletDB(*dbw).WritePurpose(EncodeDestination(address), strPurpose))
return false;
- return CWalletDB(*dbw).WriteName(CBitcoinAddress(address).ToString(), strName);
+ return CWalletDB(*dbw).WriteName(EncodeDestination(address), strName);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
@@ -3202,7 +3236,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
LOCK(cs_wallet); // mapAddressBook
// Delete destdata tuples associated with address
- std::string strAddress = CBitcoinAddress(address).ToString();
+ std::string strAddress = EncodeDestination(address);
for (const std::pair<std::string, std::string> &item : mapAddressBook[address].destdata)
{
CWalletDB(*dbw).EraseDestData(strAddress, item.first);
@@ -3212,8 +3246,8 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED);
- CWalletDB(*dbw).ErasePurpose(CBitcoinAddress(address).ToString());
- return CWalletDB(*dbw).EraseName(CBitcoinAddress(address).ToString());
+ CWalletDB(*dbw).ErasePurpose(EncodeDestination(address));
+ return CWalletDB(*dbw).EraseName(EncodeDestination(address));
}
const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const
@@ -3231,14 +3265,6 @@ const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const
return DEFAULT_ACCOUNT_NAME;
}
-bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
-{
- if (!CWalletDB(*dbw).WriteDefaultKey(vchPubKey))
- return false;
- vchDefaultKey = vchPubKey;
- return true;
-}
-
/**
* Mark old keypool keys as used,
* and generate all new keys
@@ -3658,16 +3684,13 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
}
+ LearnAllRelatedScripts(keypool.vchPubKey);
walletdb.ErasePool(index);
+ LogPrintf("keypool index %d removed\n", index);
it = setKeyPool->erase(it);
}
}
-bool CWallet::HasUnusedKeys(int min_keys) const
-{
- return setExternalKeyPool.size() >= min_keys && (setInternalKeyPool.size() >= min_keys || !CanSupportFeature(FEATURE_HD_SPLIT));
-}
-
void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script)
{
std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this);
@@ -3731,13 +3754,10 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
// map in which we'll infer heights of other keys
CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
- std::set<CKeyID> setKeys;
- GetKeys(setKeys);
- for (const CKeyID &keyid : setKeys) {
+ for (const CKeyID &keyid : GetKeys()) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax;
}
- setKeys.clear();
// if there are no such keys, we're done
if (mapKeyFirstBlock.empty())
@@ -3745,9 +3765,9 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
// find first block that affects those keys, if there are any left
std::vector<CKeyID> vAffected;
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) {
+ for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
- const CWalletTx &wtx = (*it).second;
+ const CWalletTx &wtx = entry.second;
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block
@@ -3767,8 +3787,8 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
}
// Extract block timestamps for those keys
- for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
- mapKeyBirth[it->first] = it->second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off
+ for (const auto& entry : mapKeyFirstBlock)
+ mapKeyBirth[entry.first] = entry.second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off
}
/**
@@ -3842,14 +3862,14 @@ bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, co
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
- return CWalletDB(*dbw).WriteDestData(CBitcoinAddress(dest).ToString(), key, value);
+ return CWalletDB(*dbw).WriteDestData(EncodeDestination(dest), key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
- return CWalletDB(*dbw).EraseDestData(CBitcoinAddress(dest).ToString(), key);
+ return CWalletDB(*dbw).EraseDestData(EncodeDestination(dest), key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
@@ -3888,46 +3908,6 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values;
}
-std::string CWallet::GetWalletHelpString(bool showDebug)
-{
- std::string strUsage = HelpMessageGroup(_("Wallet options:"));
- strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
- strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
- strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
- CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)));
- strUsage += HelpMessageOpt("-discardfee=<amt>", strprintf(_("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "
- "Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target"),
- CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)));
- strUsage += HelpMessageOpt("-mintxfee=<amt>", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"),
- CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)));
- strUsage += HelpMessageOpt("-paytxfee=<amt>", strprintf(_("Fee (in %s/kB) to add to transactions you send (default: %s)"),
- 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("-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("-usehd", _("Use hierarchical deterministic key generation (HD) after BIP32. Only has effect during wallet creation/first start") + " " + strprintf(_("(default: %u)"), DEFAULT_USE_HD_WALLET));
- strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (default: %u)"), DEFAULT_WALLET_RBF));
- strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup"));
- strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
- strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST));
- strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)"));
- strUsage += HelpMessageOpt("-zapwallettxes=<mode>", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") +
- " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)"));
-
- if (showDebug)
- {
- strUsage += HelpMessageGroup(_("Wallet debugging/testing options:"));
-
- strUsage += HelpMessageOpt("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE));
- strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET));
- strUsage += HelpMessageOpt("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB));
- strUsage += HelpMessageOpt("-walletrejectlongchains", strprintf(_("Wallet will not create transactions that violate mempool chain limits (default: %u)"), DEFAULT_WALLET_REJECT_LONG_CHAINS));
- }
-
- return strUsage;
-}
-
CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
{
// needed to restore wallet transaction meta data after -zapwallettxes
@@ -3937,15 +3917,12 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
- CWallet *tempWallet = new CWallet(std::move(dbw));
+ std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(std::move(dbw));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
-
- delete tempWallet;
- tempWallet = nullptr;
}
uiInterface.InitMessage(_("Loading wallet..."));
@@ -4003,30 +3980,28 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
if (fFirstRun)
{
- // Create new keyUser and set as default key
- if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
+ // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
+ if (!gArgs.GetBoolArg("-usehd", true)) {
+ InitError(strprintf(_("Error creating %s: You can't create non-HD wallets with this version."), walletFile));
+ return nullptr;
+ }
+ walletInstance->SetMinVersion(FEATURE_NO_DEFAULT_KEY);
- // ensure this wallet.dat can only be opened by clients supporting HD with chain split
- walletInstance->SetMinVersion(FEATURE_HD_SPLIT);
+ // generate a new master key
+ CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
+ if (!walletInstance->SetHDMasterKey(masterPubKey))
+ throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
- // generate a new master key
- CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
- if (!walletInstance->SetHDMasterKey(masterPubKey))
- throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
- }
- CPubKey newDefaultKey;
- if (walletInstance->GetKeyFromPool(newDefaultKey, false)) {
- walletInstance->SetDefaultKey(newDefaultKey);
- if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) {
- InitError(_("Cannot write default address") += "\n");
- return nullptr;
- }
+ // Top up the keypool
+ if (!walletInstance->TopUpKeyPool()) {
+ InitError(_("Unable to generate initial keys") += "\n");
+ return nullptr;
}
walletInstance->SetBestChain(chainActive.GetLocator());
}
else if (gArgs.IsArgSet("-usehd")) {
- bool useHD = gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET);
+ bool useHD = gArgs.GetBoolArg("-usehd", true);
if (walletInstance->IsHDEnabled() && !useHD) {
InitError(strprintf(_("Error loading %s: You can't disable HD on an already existing HD wallet"), walletFile));
return nullptr;
@@ -4039,8 +4014,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
- RegisterValidationInterface(walletInstance);
-
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
@@ -4052,6 +4025,10 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
if (walletdb.ReadBestBlock(locator))
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
}
+
+ walletInstance->m_last_block_processed = chainActive.Tip();
+ RegisterValidationInterface(walletInstance);
+
if (chainActive.Tip() && chainActive.Tip() != pindexRescan)
{
//We can't rescan beyond non-pruned blocks, stop and throw an error
@@ -4079,7 +4056,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
nStart = GetTimeMillis();
- walletInstance->ScanForWalletTransactions(pindexRescan, true);
+ {
+ WalletRescanReserver reserver(walletInstance);
+ if (!reserver.reserve()) {
+ InitError(_("Failed to rescan the wallet during initialization"));
+ return nullptr;
+ }
+ walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
+ }
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
@@ -4121,24 +4105,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
return walletInstance;
}
-bool CWallet::InitLoadWallet()
-{
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
- LogPrintf("Wallet disabled!\n");
- return true;
- }
-
- for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
- CWallet * const pwallet = CreateWalletFromFile(walletFile);
- if (!pwallet) {
- return false;
- }
- vpwallets.push_back(pwallet);
- }
-
- return true;
-}
-
std::atomic<bool> CWallet::fFlushScheduled(false);
void CWallet::postInitProcess(CScheduler& scheduler)
@@ -4153,126 +4119,6 @@ void CWallet::postInitProcess(CScheduler& scheduler)
}
}
-bool CWallet::ParameterInteraction()
-{
- gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
- const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
-
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
- return true;
-
- if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
- LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
- }
-
- if (gArgs.GetBoolArg("-salvagewallet", false)) {
- if (is_multiwallet) {
- return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet"));
- }
- // Rewrite just private keys: rescan to find transactions
- if (gArgs.SoftSetBoolArg("-rescan", true)) {
- LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__);
- }
- }
-
- int zapwallettxes = gArgs.GetArg("-zapwallettxes", 0);
- // -zapwallettxes implies dropping the mempool on startup
- if (zapwallettxes != 0 && gArgs.SoftSetBoolArg("-persistmempool", false)) {
- LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -persistmempool=0\n", __func__, zapwallettxes);
- }
-
- // -zapwallettxes implies a rescan
- if (zapwallettxes != 0) {
- if (is_multiwallet) {
- return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes"));
- }
- if (gArgs.SoftSetBoolArg("-rescan", true)) {
- LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting -rescan=1\n", __func__, zapwallettxes);
- }
- }
-
- if (is_multiwallet) {
- if (gArgs.GetBoolArg("-upgradewallet", false)) {
- return InitError(strprintf("%s is only allowed with a single wallet file", "-upgradewallet"));
- }
- }
-
- if (gArgs.GetBoolArg("-sysperms", false))
- return InitError("-sysperms is not allowed in combination with enabled wallet functionality");
- if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false))
- return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again."));
-
- if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB)
- InitWarning(AmountHighWarn("-minrelaytxfee") + " " +
- _("The wallet will avoid paying less than the minimum relay fee."));
-
- if (gArgs.IsArgSet("-mintxfee"))
- {
- CAmount n = 0;
- if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n)
- return InitError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")));
- if (n > HIGH_TX_FEE_PER_KB)
- InitWarning(AmountHighWarn("-mintxfee") + " " +
- _("This is the minimum transaction fee you pay on every transaction."));
- CWallet::minTxFee = CFeeRate(n);
- }
- if (gArgs.IsArgSet("-fallbackfee"))
- {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK))
- return InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", "")));
- if (nFeePerK > HIGH_TX_FEE_PER_KB)
- InitWarning(AmountHighWarn("-fallbackfee") + " " +
- _("This is the transaction fee you may pay when fee estimates are not available."));
- CWallet::fallbackFee = CFeeRate(nFeePerK);
- }
- if (gArgs.IsArgSet("-discardfee"))
- {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK))
- return InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", "")));
- if (nFeePerK > HIGH_TX_FEE_PER_KB)
- InitWarning(AmountHighWarn("-discardfee") + " " +
- _("This is the transaction fee you may discard if change is smaller than dust at this level"));
- CWallet::m_discard_rate = CFeeRate(nFeePerK);
- }
- if (gArgs.IsArgSet("-paytxfee"))
- {
- CAmount nFeePerK = 0;
- if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK))
- return InitError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")));
- if (nFeePerK > HIGH_TX_FEE_PER_KB)
- InitWarning(AmountHighWarn("-paytxfee") + " " +
- _("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)"),
- gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString()));
- }
- }
- if (gArgs.IsArgSet("-maxtxfee"))
- {
- CAmount nMaxFee = 0;
- if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee))
- return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-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)"),
- gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
- }
- }
- nTxConfirmTarget = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
- bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
- fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
-
- return true;
-}
-
bool CWallet::BackupWallet(const std::string& strDest)
{
return dbw->Backup(strDest);
@@ -4333,7 +4179,118 @@ int CMerkleTx::GetBlocksToMaturity() const
}
-bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
+bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
+{
+ // We must set fInMempool here - while it will be re-set to true by the
+ // entered-mempool callback, if we did not there would be a race where a
+ // user could call sendmoney in a loop and hit spurious out of funds errors
+ // because we think that the transaction they just generated's change is
+ // unavailable as we're not yet aware its in mempool.
+ bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
+ nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee);
+ fInMempool |= ret;
+ return ret;
+}
+
+static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
+static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
+static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
+
+OutputType ParseOutputType(const std::string& type, OutputType default_type)
{
- return ::AcceptToMemoryPool(mempool, state, tx, true, nullptr, nullptr, false, nAbsurdFee);
+ if (type.empty()) {
+ return default_type;
+ } else if (type == OUTPUT_TYPE_STRING_LEGACY) {
+ return OUTPUT_TYPE_LEGACY;
+ } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) {
+ return OUTPUT_TYPE_P2SH_SEGWIT;
+ } else if (type == OUTPUT_TYPE_STRING_BECH32) {
+ return OUTPUT_TYPE_BECH32;
+ } else {
+ return OUTPUT_TYPE_NONE;
+ }
+}
+
+const std::string& FormatOutputType(OutputType type)
+{
+ switch (type) {
+ case OUTPUT_TYPE_LEGACY: return OUTPUT_TYPE_STRING_LEGACY;
+ case OUTPUT_TYPE_P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
+ case OUTPUT_TYPE_BECH32: return OUTPUT_TYPE_STRING_BECH32;
+ default: assert(false);
+ }
+}
+
+void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
+{
+ if (key.IsCompressed() && (type == OUTPUT_TYPE_P2SH_SEGWIT || type == OUTPUT_TYPE_BECH32)) {
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ // Make sure the resulting program is solvable.
+ assert(IsSolvable(*this, witprog));
+ AddCScript(witprog);
+ }
+}
+
+void CWallet::LearnAllRelatedScripts(const CPubKey& key)
+{
+ // OUTPUT_TYPE_P2SH_SEGWIT always adds all necessary scripts for all types.
+ LearnRelatedScripts(key, OUTPUT_TYPE_P2SH_SEGWIT);
+}
+
+CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
+{
+ switch (type) {
+ case OUTPUT_TYPE_LEGACY: return key.GetID();
+ case OUTPUT_TYPE_P2SH_SEGWIT:
+ case OUTPUT_TYPE_BECH32: {
+ if (!key.IsCompressed()) return key.GetID();
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ if (type == OUTPUT_TYPE_P2SH_SEGWIT) {
+ return CScriptID(witprog);
+ } else {
+ return witdest;
+ }
+ }
+ default: assert(false);
+ }
+}
+
+std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
+{
+ CKeyID keyid = key.GetID();
+ if (key.IsCompressed()) {
+ CTxDestination segwit = WitnessV0KeyHash(keyid);
+ CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit));
+ return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)};
+ } else {
+ return std::vector<CTxDestination>{std::move(keyid)};
+ }
+}
+
+CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type)
+{
+ // Note that scripts over 520 bytes are not yet supported.
+ switch (type) {
+ case OUTPUT_TYPE_LEGACY:
+ return CScriptID(script);
+ case OUTPUT_TYPE_P2SH_SEGWIT:
+ case OUTPUT_TYPE_BECH32: {
+ WitnessV0ScriptHash hash;
+ CSHA256().Write(script.data(), script.size()).Finalize(hash.begin());
+ CTxDestination witdest = hash;
+ CScript witprog = GetScriptForDestination(witdest);
+ // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
+ if (!IsSolvable(*this, witprog)) return CScriptID(script);
+ // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
+ AddCScript(witprog);
+ if (type == OUTPUT_TYPE_BECH32) {
+ return witdest;
+ } else {
+ return CScriptID(witprog);
+ }
+ }
+ default: assert(false);
+ }
}